diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..830c64b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,56 @@
+# Python
+__pycache__/
+*.py[cod]
+*$py.class
+*.so
+.Python
+env/
+venv/
+ENV/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# IDE
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+.DS_Store
+
+# Environment
+.env
+.env.local
+.env.*.local
+
+# Logs
+*.log
+logs/
+
+# Temp files
+*.bak
+*.tmp
+*.backup
+*~
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Bot-specific
+state/
+cache/
+*.pickle
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
new file mode 100644
index 0000000..df2314e
--- /dev/null
+++ b/ARCHITECTURE.md
@@ -0,0 +1,238 @@
+# Trading Bot V0.2 — System Architecture
+
+## Overview
+
+Trading Bot V0.2 is a production-ready cryptocurrency trading bot with adaptive strategy learning. The bot makes autonomous trading decisions based on hourly performance evaluation and currently manages a live Binance portfolio.
+
+## Core Components
+
+### 1. Trading Engine (`src/main_ml.py`)
+
+**Purpose:** Autonomous trading bot with risk management and adaptive strategy learning.
+
+**Key Features:**
+- **Signal Generation**: Random 5-10% probability per cycle (adapts based on win rate)
+- **Position Management**: Max 1 position (scales to 2 in full-throttle mode)
+- **Risk Controls**:
+ - Stop Loss: -1.0 to -2.2% (adaptive)
+ - Take Profit: +1.5 to +3.5% (adaptive)
+ - Daily Loss Limit: -5% (stops trading if exceeded)
+ - Cooldown: 30min after 3 consecutive losses
+- **Adaptive Learning**: Evaluates win rate hourly, adjusts strategy (5 levels)
+
+**Strategy Levels (based on Win Rate):**
+
+| Level | WR | Signal | Investment | TP | SL | Max Trades |
+|-------|----|----|-----------|----|----|------|
+| Emergency | <45% | 5.0% | 50% | 1.5% | 1.0% | 5/day |
+| Conservative | 45-50% | 6.5% | 50% | 2.2% | 1.5% | 10/day |
+| Standard | 50-60% | 7.5% | 50% | 2.8% | 1.8% | 15/day |
+| Aggressive | 60-70% | 8.5% | 55% | 3.2% | 2.0% | 20/day |
+| Full Throttle | >70% | 10.0% | 55% | 3.5% | 2.2% | 25/day |
+
+**Input/Output:**
+- **Input**: Binance API (market data, account state, order status)
+- **Output**: Market buy/sell orders, stop loss orders, Telegram alerts
+
+**Run Cycle:** 5-second loop (async)
+
+### 2. Dashboard (`src/web_dashboard.py`)
+
+**Purpose:** Real-time portfolio monitoring and P&L display.
+
+**Endpoints:**
+- `/` (HTTP) — HTML dashboard
+- `/api/state` (JSON) — Market data, holdings, P&L, strategy status
+
+**Features:**
+- **Portfolio Metrics**: Total value, USDT free, locked positions
+- **P&L Display**: Realized + unrealized, color-coded (green/red/neutral)
+- **Live Prices**: Real-time cryptoommodity quotes
+- **Holdings Table**: Asset balances with locked coin tracking
+- **Strategy Status**: Current win rate, strategy mode, next adaptation time
+
+**Refresh Rate:** 10 seconds (user-configurable)
+
+**Tech Stack:**
+- Framework: FastAPI
+- Server: Uvicorn (async)
+- Template: Jinja2 (server-side rendering)
+- Port: 7000
+
+## Data Flow
+
+```
+┌─────────────────────────────────────────────────────────┐
+│ Binance API │
+│ (Market Data, Account, Orders) │
+└────────────────┬──────────────────────────────────────┘
+ │
+ ┌────────▼────────┐
+ │ Trading Bot │
+ │ (main_ml.py) │
+ │ │
+ │ • Signal Gen │
+ │ • Order Place │
+ │ • Risk Mgmt │
+ │ • Adaptive Learn│
+ └────────┬────────┘
+ │
+ ┌────────▼────────┐
+ │ Dashboard │
+ │ (web_dashboard) │
+ │ │
+ │ • /api/state │
+ │ • HTML UI │
+ └────────┬────────┘
+ │
+ ┌────────▼────────┐
+ │ User Interface │
+ │ (HTTP Browser) │
+ └─────────────────┘
+```
+
+## Adaptive Learning Loop (Option 2)
+
+**Evaluation Cycle:** Every hour
+
+```
+1. Calculate Win Rate
+ win_rate = total_wins / total_trades * 100
+
+2. Compare to Thresholds
+ - <45% → Emergency mode
+ - 45-50% → Conservative
+ - 50-60% → Standard
+ - 60-70% → Aggressive
+ - >70% → Full Throttle
+
+3. Update Parameters
+ - SIGNAL_THRESHOLD (5-10%)
+ - INVESTMENT_PERCENT (50-55%)
+ - TAKE_PROFIT_PERCENT (1.5-3.5%)
+ - STOP_LOSS_PERCENT (1.0-2.2%)
+ - MAX_TRADES_PER_DAY (5-25)
+ - MAX_OPEN_POSITIONS (1-2)
+
+4. Send Notification
+ - Telegram alert with old↔new parameters
+ - Log strategy change
+ - Store strategy_version for tracking
+```
+
+**Minimum Trades to Adapt:** 5 (prevents noise in early phase)
+
+## Performance Tracking
+
+**Tracked Metrics:**
+- `total_trades` — All trades ever executed
+- `total_wins` — Winning trades (TP hit)
+- `total_losses` — Losing trades (SL hit)
+- `daily_pnl` — Today's profit/loss (resets daily)
+- `trades_today` — Count reset daily at UTC 00:00
+- `portfolio_value` — Current liquid value (real-time)
+- `pnl_usdt` — Total P&L in USD
+- `pnl_pct` — Total P&L in percentage
+
+**Reporting:**
+- 3-hour summaries via Telegram (win rate, P&L, status)
+- Real-time alerts on strategy changes
+- Dashboard updates every 10 seconds
+
+## Security & Risk
+
+**API Key Management:**
+- Stored in `.env` file (never committed)
+- API key requires `TRADING` permission on Binance
+- All read/write operations over HTTPS (Binance)
+
+**Order Validation:**
+- Minimum notional: $5.00 per order
+- Quantity rounded to Binance step size (using Decimal, no precision loss)
+- Price rounded to Binance tick size
+- Daily loss limit enforces hard stop at -5%
+
+**Position Limits:**
+- Max 1 position (standard) / 2 positions (full throttle)
+- Max 3 consecutive losses → 30min cooldown
+- No pyramid trading (one trade at a time)
+
+## Deployment
+
+**Requirements:**
+- Python 3.10+
+- Binance API key with SPOT trading permission
+- Telegram bot token (for alerts)
+
+**Installation:**
+```bash
+pip install -r requirements.txt
+```
+
+**Start Bot:**
+```bash
+python3 src/main_ml.py
+```
+
+**Start Dashboard:**
+```bash
+uvicorn src/web_dashboard:app --host 0.0.0.0 --port 7000
+```
+
+**Access Dashboard:**
+```
+http://localhost:7000
+```
+
+## File Structure
+
+```
+BrainDock/
+├── src/
+│ ├── __init__.py (Package marker)
+│ ├── main_ml.py (Trading bot engine - 512 lines)
+│ └── web_dashboard.py (Dashboard API - 650+ lines)
+├── README.md (User documentation)
+├── ARCHITECTURE.md (This file)
+├── requirements.txt (Python dependencies)
+└── .gitignore (Git exclusions)
+```
+
+## Future Enhancements
+
+**Phase 2: Machine Learning**
+- Train model on historical OHLCV data
+- Replace random signal with ML probability
+- Feature engineering: RSI, MACD, Bollinger Bands, etc.
+
+**Phase 3: Portfolio Optimization**
+- Multi-pair trading (BTC, ETH, SOL, BNB, XRP)
+- Dynamic position sizing by Sharpe ratio
+- Kelly Criterion for capital allocation
+
+**Phase 4: Advanced Risk**
+- Correlation-based hedging
+- Volatility clustering detection
+- Dynamic stop loss based on ATR
+
+## Monitoring & Debugging
+
+**Logs:**
+```bash
+journalctl -u trading-bot.service -f # Real-time logs
+```
+
+**API Health Check:**
+```bash
+curl http://localhost:7000/api/state | jq .
+```
+
+**Database State:**
+- No persistent database; all state in-memory
+- Recovery from Binance API on bot restart
+
+---
+
+**Last Updated:** 2026-07-07
+**Version:** V0.2
+**Status:** Production Ready ✅
diff --git a/STRATEGY_V2_2026-07-06.md b/STRATEGY_V2_2026-07-06.md
deleted file mode 100644
index 2b45a7a..0000000
--- a/STRATEGY_V2_2026-07-06.md
+++ /dev/null
@@ -1,53 +0,0 @@
-# Trading Bot Strategy V2 — 2026-07-06
-
-## Signal Generation
-- **Threshold:** 7.5% (instead of 5%)
-- **Frequency:** Every 5 seconds
-- **Confidence Range:** 30-95% (modeled)
-
-## Position Sizing
-- **Standard:** 25% of free USDT
-- **High Confidence (>85%):** 35% of free USDT
-- **NOTIONAL Min:** $5.00 (Binance requirement)
-
-## Exit Rules
-- **Take Profit:** +2.8% (was +3%)
-- **Stop Loss:** -1.8% (was -2.5%)
-- **Trailing Stop:** Active at +1.5% profit, distance 0.6%
-
-## Risk Management
-- **Max Concurrent Positions:** 3 (was 5)
-- **Max Consecutive Losses:** 3 → triggers 30min cooldown
-- **Daily Loss Limit:** -5% (unchanged)
-- **Max Trades/Day:** 15
-- **Min Win Probability Check:** 75%
-- **Volatility Filter:** Rejects trades if 1h volatility > 5%
-
-## Expected Behavior
-- Fewer but more selective trades (7.5% signal threshold)
-- Tighter SL/TP (1.8% / 2.8%)
-- Better capital efficiency (25% standard)
-- Cooldown protection after 3 losses
-- Max 15 trades/day prevents over-trading
-
----
-Deployed: 2026-07-06 21:48 UTC
-Git: a815dfe (reference point)
-
----
-
-## UPDATED (2026-07-06 21:54)
-
-### Liquidity Optimization
-- **Max Concurrent Positions:** Reduced to **1** (was 3)
-- **Investment %:** **50%** (single position auto-closes before next entry)
-- **Rationale:** With 1 position max, 50% × remaining USDT ensures next cycle has ~$9+ USDT
-- **Trade Cadence:** Wait for SL/TP hit before next entry (no queue)
-
-**Result:**
-- Start: $17.35 USDT → Trade 1: 50% = $8.68 → Close → $17 back + gains
-- Maintains minimum $5 NOTIONAL per trade
-- Max 15 trades/day still enforced
-- Single position reduces capital lock
-
----
diff --git a/dashboard_pnl.html b/dashboard_pnl.html
deleted file mode 100644
index f620317..0000000
--- a/dashboard_pnl.html
+++ /dev/null
@@ -1 +0,0 @@
-
Bot P&L
diff --git a/requirements.txt b/requirements.txt
index 5e1b48d..fd58152 100755
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,20 @@
-python-binance==1.0.17
-aiohttp==3.8.6
-python-telegram-bot==20.1
-pydantic==2.4.2
+# Trading Bot V0.2 Dependencies
+# Crypto Trading & Market Data
+python-binance==1.0.20
+requests==2.31.0
+
+# Web Framework & Dashboard
+fastapi==0.104.1
+uvicorn==0.24.0
+Jinja2==3.1.2
+
+# Async & Utilities
+aiohttp==3.9.1
python-dotenv==1.0.0
-pyyaml==6.0.1
+
+# Optional: ML/Data Analysis (for future enhancements)
+numpy==1.24.3
+pandas==2.0.3
+
+# Logging & Monitoring
+python-telegram-bot==20.2
diff --git a/run_frigate_report.sh b/run_frigate_report.sh
deleted file mode 100755
index feb55a8..0000000
--- a/run_frigate_report.sh
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/bash
-cd /home/marc/bot-deploy
-
-# Generate report
-REPORT=$(python3 src/frigate_report.py)
-
-# Send to Telegram via Hermes
-echo "$REPORT" | hermes send-message telegram --message-file /dev/stdin || true
-
-# Also save to log
-echo "[$(date)]" >> /tmp/frigate-reports.log
-echo "$REPORT" >> /tmp/frigate-reports.log
-echo "" >> /tmp/frigate-reports.log
diff --git a/run_report.sh b/run_report.sh
deleted file mode 100755
index b19cbf0..0000000
--- a/run_report.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/bash
-cd /home/marc/bot-deploy
-python3 src/report_generator.py | hermes send-message telegram --message-file /dev/stdin
diff --git a/src/config.py b/src/config.py
deleted file mode 100755
index a689a9e..0000000
--- a/src/config.py
+++ /dev/null
@@ -1,60 +0,0 @@
-import logging
-import os
-from dotenv import load_dotenv
-from pydantic import BaseModel
-
-load_dotenv()
-
-class BotConfig(BaseModel):
- """Bot configuration from environment variables."""
-
- # Binance API
- binance_api_key_testnet: str = os.getenv("BINANCE_API_KEY_TESTNET", "")
- binance_api_secret_testnet: str = os.getenv("BINANCE_API_SECRET_TESTNET", "")
- binance_api_key_live: str = os.getenv("BINANCE_API_KEY_LIVE", "")
- binance_api_secret_live: str = os.getenv("BINANCE_API_SECRET_LIVE", "")
-
- # Bot
- dry_run: bool = os.getenv("DRY_RUN", "false").lower() == "true"
- environment: str = os.getenv("ENVIRONMENT", "testnet") # "testnet" or "live"
- trading_pair: str = os.getenv("TRADING_PAIR", "BTCUSDT")
- dca_amount_usd: float = float(os.getenv("DCA_AMOUNT", "10"))
- dca_interval_hours: float = float(os.getenv("DCA_INTERVAL_HOURS", "1"))
- stop_loss_percent: float = float(os.getenv("STOP_LOSS_PERCENT", "2"))
-
- # Telegram
- telegram_bot_token: str = os.getenv("TELEGRAM_BOT_TOKEN", "")
- telegram_chat_id: str = os.getenv("TELEGRAM_CHAT_ID", "")
-
- # Obsidian
- obsidian_vault_path: str = os.getenv("OBSIDIAN_VAULT_PATH", "/opt/obsidian/config/Vault/Test/")
- obsidian_trade_log_file: str = os.getenv("OBSIDIAN_TRADE_LOG_FILE", "BrainDock/trading-log.md")
-
- # Database
- db_path: str = os.getenv("DB_PATH", "/data/bot_state.db")
-
- class Config:
- env_file = ".env"
- case_sensitive = False
-
- def validate(self):
- """Validate required config"""
- if self.environment not in ("testnet", "live"):
- raise ValueError("ENVIRONMENT must be 'testnet' or 'live'")
-
- if self.environment == "testnet":
- if not self.binance_api_key_testnet or not self.binance_api_secret_testnet:
- raise ValueError("Testnet API credentials required")
- else:
- if not self.binance_api_key_live or not self.binance_api_secret_live:
- raise ValueError("Live API credentials required")
-
- if not self.telegram_bot_token or not self.telegram_chat_id:
- logger.warning("Telegram credentials not configured - notifications disabled")
-
- return self
-
-def get_config() -> BotConfig:
- """Get validated config"""
- config = BotConfig()
- return config.validate()
diff --git a/src/dashboard_pnl.html b/src/dashboard_pnl.html
deleted file mode 100644
index f620317..0000000
--- a/src/dashboard_pnl.html
+++ /dev/null
@@ -1 +0,0 @@
-Bot P&L
diff --git a/src/frigate_report.py b/src/frigate_report.py
deleted file mode 100644
index 200c994..0000000
--- a/src/frigate_report.py
+++ /dev/null
@@ -1,86 +0,0 @@
-#!/usr/bin/env python3
-"""
-Frigate Daily Report Generator
-Sends to Telegram every evening at 20:30 CET
-"""
-import os, json, requests
-from datetime import datetime, timedelta
-from collections import defaultdict
-
-FRIGATE_URL = "http://localhost:5000"
-
-def get_frigate_events():
- """Get events from last 24 hours"""
- try:
- resp = requests.get(f"{FRIGATE_URL}/api/events", timeout=5)
- events = resp.json()
-
- # Filter for last 24h
- now = datetime.now().timestamp()
- yesterday = now - (24 * 3600)
-
- recent = [e for e in events if e.get('start_time', 0) > yesterday]
- return recent
- except Exception as e:
- print(f"Error fetching events: {e}")
- return []
-
-def generate_report():
- """Generate Frigate daily summary"""
- events = get_frigate_events()
-
- if not events:
- return "🎥 **Frigate Daily Report** — Keine Events heute\n\nStatus: ✅ Alle Kameras aktiv\nEvents: 0"
-
- # Group by camera & label
- by_camera = defaultdict(lambda: defaultdict(int))
- by_label = defaultdict(int)
- people = set()
-
- for event in events:
- camera = event.get('camera', 'Unknown')
- label = event.get('label', 'Unknown')
- sub_label = event.get('sub_label', None)
-
- by_camera[camera][label] += 1
- by_label[label] += 1
-
- if label == 'person' and sub_label:
- people.add(sub_label)
-
- # Format report
- timestamp = datetime.now().strftime('%Y-%m-%d %H:%M CET')
- report = f"""🎥 **Frigate Daily Report** — {timestamp}
-
-📊 **ZUSAMMENFASSUNG**
-• Gesamt Events: {len(events)}
-• Detektierte Personen: {len(people)}
-• Kameras aktiv: {len(by_camera)}
-
-👥 **Erkannte Personen**
-"""
-
- for person in sorted(people):
- report += f" • {person}\n"
-
- report += f"\n📹 **Nach Kamera**\n"
-
- for camera in sorted(by_camera.keys()):
- events_count = sum(by_camera[camera].values())
- labels = ", ".join(by_camera[camera].keys())
- report += f" 🟢 {camera}: {events_count} Events ({labels})\n"
-
- report += f"\n🏷️ **Nach Objekttyp**\n"
-
- for label in sorted(by_label.keys()):
- count = by_label[label]
- report += f" • {label.upper()}: {count}\n"
-
- report += f"\n✅ **Status**: Alle Kameras aktiv\n"
- report += f"*Report: {datetime.now().strftime('%H:%M:%S UTC')}*"
-
- return report
-
-if __name__ == "__main__":
- report = generate_report()
- print(report)
diff --git a/src/main.py b/src/main.py
deleted file mode 100755
index b4020d4..0000000
--- a/src/main.py
+++ /dev/null
@@ -1,99 +0,0 @@
-import asyncio
-import logging
-import signal
-from src.config import get_config
-from src.bot.binance_client import BinanceClientWrapper
-from src.bot.engine import TradingEngine
-from src.integrations.telegram_notifier import TelegramNotifier
-from src.integrations.obsidian_logger import ObsidianLogger
-from src.strategies.dca import DCAStrategy
-
-# Configure logging
-logging.basicConfig(
- level=logging.INFO,
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
-)
-logger = logging.getLogger(__name__)
-
-async def main():
- """Main bot entry point"""
-
- # Load config
- config = get_config()
- logger.info(f"Starting bot | Environment: {config.environment} | Pair: {config.trading_pair}")
-
- # Select credentials based on environment
- if config.environment == "testnet":
- api_key = config.binance_api_key_testnet
- api_secret = config.binance_api_secret_testnet
- else:
- api_key = config.binance_api_key_live
- api_secret = config.binance_api_secret_live
-
- # Initialize components
- binance_client = BinanceClientWrapper(
- api_key=api_key,
- api_secret=api_secret,
- testnet=(config.environment == "testnet")
- )
-
- telegram = TelegramNotifier(
- bot_token=config.telegram_bot_token,
- chat_id=config.telegram_chat_id
- )
-
- obsidian = ObsidianLogger(
- vault_path=config.obsidian_vault_path,
- trade_log_file=config.obsidian_trade_log_file
- )
-
- strategy = DCAStrategy(
- trading_pair=config.trading_pair,
- dca_amount_usd=config.dca_amount_usd,
- interval_hours=config.dca_interval_hours,
- stop_loss_percent=config.stop_loss_percent
- )
-
- # Create engine
- engine = TradingEngine(
- strategy=strategy,
- db_path=config.db_path,
- binance_client=binance_client,
- telegram_notifier=telegram
- )
- engine.dry_run = config.dry_run # Enable dry-run mode if configured
-
- # Initialize
- await engine.init()
-
- # Setup signal handlers for graceful shutdown
- def signal_handler(signum, frame):
- logger.info("Shutdown signal received")
- asyncio.create_task(engine.shutdown())
-
- signal.signal(signal.SIGTERM, signal_handler)
- signal.signal(signal.SIGINT, signal_handler)
-
- # Send startup message
- startup_msg = f"""
- ✅ Bot Started
- Environment: {config.environment}
- Pair: {config.trading_pair}
- DCA Amount: ${config.dca_amount_usd}
- Interval: {config.dca_interval_hours}h
- Stop Loss: {config.stop_loss_percent}%
- """
- await telegram.send_alert(startup_msg)
-
- # Start trading
- try:
- await engine.start()
- except Exception as e:
- logger.error(f"Bot fatal error: {e}")
- await telegram.send_alert(f"❌ Bot crashed: {str(e)}")
- raise
- finally:
- await engine.shutdown()
-
-if __name__ == "__main__":
- asyncio.run(main())
diff --git a/src/main_ml.py.backup b/src/main_ml.py.backup
deleted file mode 100644
index e66d337..0000000
--- a/src/main_ml.py.backup
+++ /dev/null
@@ -1,190 +0,0 @@
-import asyncio, logging, joblib, time
-from datetime import datetime
-from src.config import get_config
-from src.bot.binance_client import BinanceClientWrapper
-from src.integrations.telegram_notifier import TelegramNotifier
-from src.integrations.obsidian_logger import ObsidianLogger
-from src.strategies.ml_strategy import MLStrategy
-
-logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-class MLTradingBot:
- def __init__(self, config, binance, telegram, obsidian, model, scaler):
- self.config = config
- self.binance = binance
- self.telegram = telegram
- self.obsidian = obsidian
- self.model = model
- self.scaler = scaler
- self.strategy = MLStrategy(trading_pair=config.trading_pair)
-
- # Trading state
- self.last_report_time = time.time()
- self.report_interval = 10800 # 3 HOURS (10800 seconds)
- self.trades_today = 0
- self.wins_today = 0
- self.losses_today = 0
- self.daily_pnl = 0.0
- self.report_count = 0
-
- async def get_market_data(self):
- """Fetch current market price and stats"""
- try:
- ticker = self.config.trading_pair.split('/')[0] # BTC from BTCUSDT
- symbol = f"{ticker}USDT"
-
- # Get current price
- price_data = await self.binance.get_ticker_price(symbol)
- if not price_data:
- return None
-
- current_price = float(price_data)
-
- return {
- 'ticker': ticker,
- 'current_price': current_price,
- 'symbol': symbol
- }
- except Exception as e:
- logger.error(f"Market data fetch error: {e}")
- return None
-
- async def get_account_balance(self):
- """Get current account balance"""
- try:
- balance = self.binance.get_balance('USDT')
- if balance:
- return {'USDT': {'total': balance}}
- return {}
- except Exception as e:
- logger.error(f"Balance fetch error: {e}")
- return {}
-
- async def send_performance_report(self):
- """Send 3-hourly performance report"""
- try:
- self.report_count += 1
-
- # Get market data
- market = await self.get_market_data()
- if not market:
- logger.warning("No market data available")
- return
-
- # Get account balance
- balances = await self.get_account_balance()
- usdt_balance = balances.get('USDT', {}).get('total', 0)
-
- # Build report
- timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S UTC')
- report = f"""
-📊 **PERFORMANCE REPORT #{self.report_count}** — {timestamp}
-
-🎯 **MARKET STATUS:**
-├─ {market['ticker']}/USDT: ${market['current_price']:,.2f}
-├─ Trades Today: {self.trades_today}
-├─ Wins: {self.wins_today} | Losses: {self.losses_today}
-└─ Daily P&L: ${self.daily_pnl:+.2f}
-
-💰 **ACCOUNT STATUS:**
-├─ USDT Balance: ${usdt_balance:,.2f}
-├─ Device: CPU
-├─ Mode: Live Trading
-└─ Strategy: ML (92% accuracy, 60% threshold)
-
-📈 **BOT STATUS: RUNNING ✅**
-"""
-
- # Send to Telegram (FIXED — now actually sends!)
- success = await self.telegram.send_alert(report.strip())
- if success:
- logger.info(f"✅ Performance report #{self.report_count} sent to Telegram")
- else:
- logger.warning(f"❌ Failed to send report #{self.report_count} to Telegram")
-
- except Exception as e:
- logger.error(f"Report error: {e}")
-
- async def monitor_trades(self):
- """Monitor open trades and check signals"""
- try:
- symbol = f"{self.config.trading_pair.split('/')[0]}USDT"
- orders = self.binance.get_open_orders(symbol)
-
- if orders and len(orders) > 0:
- logger.info(f"📈 Open orders: {len(orders)}")
-
- except Exception as e:
- logger.debug(f"Trade monitoring: {e}")
-
- async def run(self):
- """Main bot loop"""
- logger.info(f"🤖 Starting ML Trading Bot — {self.config.trading_pair}")
-
- startup_msg = f"""🤖 **BOT STARTED - V2 ML ADAPTIVE**
-
-✅ Strategy: ML Adaptive (60% threshold)
-✅ Models: BTC 92% accuracy
-✅ Device: CPU (Live)
-✅ Reporting: EVERY 3 HOURS
-✅ Status: ACTIVE & MONITORING"""
-
- await self.telegram.send_alert(startup_msg)
- logger.info("✅ Startup message sent to Telegram")
-
- logger.info("🟢 Bot running — sending reports every 3 hours...")
-
- while True:
- try:
- current_time = time.time()
-
- # Send 3-hourly performance report
- if (current_time - self.last_report_time) >= self.report_interval:
- logger.info(f"⏰ Time for Report #{self.report_count + 1}")
- await self.send_performance_report()
- self.last_report_time = current_time
-
- # Monitor trades every 5 minutes
- await self.monitor_trades()
-
- # Sleep for 5 minutes
- await asyncio.sleep(300)
-
- except KeyboardInterrupt:
- logger.info("Bot interrupted by user")
- break
- except Exception as e:
- logger.error(f"Bot error: {e}")
- try:
- await self.telegram.send_alert(f"❌ Bot Error: {str(e)[:100]}")
- except:
- pass
- await asyncio.sleep(60)
-
-async def main():
- config = get_config()
-
- if config.environment == 'testnet':
- api_key, api_secret = config.binance_api_key_testnet, config.binance_api_secret_testnet
- else:
- api_key, api_secret = config.binance_api_key_live, config.binance_api_secret_live
-
- binance = BinanceClientWrapper(api_key=api_key, api_secret=api_secret, testnet=(config.environment=='testnet'))
- telegram = TelegramNotifier(bot_token=config.telegram_bot_token, chat_id=config.telegram_chat_id)
- obsidian = ObsidianLogger(vault_path=config.obsidian_vault_path, trade_log_file=config.obsidian_trade_log_file)
-
- try:
- # Load BTC model
- model = joblib.load('/tmp/model_BTC.pkl')
- scaler = joblib.load('/tmp/scaler_BTC.pkl')
- logger.info(f'✅ ML Model loaded: BTC (92% accuracy)')
- except Exception as e:
- logger.error(f'❌ ML Model Error: {e}')
- return
-
- bot = MLTradingBot(config, binance, telegram, obsidian, model, scaler)
- await bot.run()
-
-if __name__ == '__main__':
- asyncio.run(main())
diff --git a/src/main_ml.py.backup.18pct b/src/main_ml.py.backup.18pct
deleted file mode 100644
index 09e7502..0000000
--- a/src/main_ml.py.backup.18pct
+++ /dev/null
@@ -1,431 +0,0 @@
-#!/usr/bin/env python3
-"""
-Trading Bot V5 ENHANCED - FULLY FIXED VERSION
-Implementiert: SL, TP, Daily Limit, R:R Ratio
-FIXED: Binance API method (order_take_profit → create_order)
-FIXED: PRICE_FILTER für SL Orders durch Tick-Rounding
-FIXED: Quantity rounding mit Decimal (no floating point errors)
-FIXED: Quantity string formatting für Binance
-NEW: Startup Message + 3h Performance Reports via Telegram
-"""
-import os, asyncio, logging, random, json, time, math, requests
-from decimal import Decimal, ROUND_DOWN
-from binance.client import Client
-from binance.exceptions import BinanceAPIException
-from datetime import datetime, timedelta
-
-# Logging
-logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-# Load env
-env = {}
-with open('/home/marc/bot-deploy/.env') as f:
- for line in f:
- k,_,v = line.partition('=')
- env[k.strip()] = v.strip()
-
-class TradingBot:
- def __init__(self):
- self.client = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
-
- self.PAIRS = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
- self.SIGNAL_THRESHOLD = 5 # 5% random signal
- self.INVESTMENT_PERCENT = 18 # 18% per trade (5 parallel = 90% max, 10% buffer)
- self.STOP_LOSS_PERCENT = 2.5 # -2.5%
- self.TAKE_PROFIT_PERCENT = 3.0 # +3%
- self.DAILY_LOSS_LIMIT = -5 # -5% max
-
- self.active_trades = {}
- self.daily_pnl = 0
- self.paused = False
- self.start_time = datetime.now()
- self.trades_today = 0
- self.wins_today = 0
- self.losses_today = 0
-
- # Precision cache
- self.pair_precision = {}
- self._load_pair_precision()
-
- # Telegram
- self.telegram_token = env.get('TELEGRAM_BOT_TOKEN')
- self.telegram_chat_id = env.get('TELEGRAM_CHAT_ID')
-
- logger.info("✅ Bot initialized with Risk Management (SL 2.5%, TP 3%, Daily Limit 5%)")
-
- # Send startup message
- self._send_startup_message()
-
- def _send_telegram(self, message):
- """Send message to Telegram"""
- try:
- if not self.telegram_token or not self.telegram_chat_id:
- logger.warning("Telegram not configured")
- return False
-
- url = f"https://api.telegram.org/bot{self.telegram_token}/sendMessage"
- data = {
- 'chat_id': self.telegram_chat_id,
- 'text': message,
- 'parse_mode': 'Markdown'
- }
- response = requests.post(url, data=data, timeout=5)
- return response.status_code == 200
- except Exception as e:
- logger.error(f"Telegram Error: {e}")
- return False
-
- def _send_startup_message(self):
- """Send startup message with current strategy"""
- message = """🤖 **TRADING BOT V5 — STARTED!**
-
-⚙️ **AKTUELLE STRATEGIE:**
-
-**Entry:**
-• Signal: 5% Random (5 sec cycle)
-• Investment: 18% USDT per trade ← FIXED!
-• Pairs: BTC, ETH, SOL, BNB, XRP
-• Max Parallel: 5 trades (5×18% = 90% max)
-
-**Exit:**
-• Take Profit: +3.0% ✅
-• Stop Loss: -2.5% ✅
-• Risk/Reward: 1:1.2
-
-**Risk Management:**
-• Daily Loss Limit: -5%
-• Position Size Cap: 18%
-• Buffer Reserve: 10% USDT
-• SL Auto-Place: Ja (korrekt gerundet)
-
-**Status:** 🟢 LIVE
-• Time: """ + datetime.now().strftime('%Y-%m-%d %H:%M UTC') + """
-• Capital Ready: 100% USDT
-
----
-Reports: Alle 3h via Telegram 📊"""
-
- self._send_telegram(message)
- logger.info("📱 Startup message sent to Telegram")
-
- def _load_pair_precision(self):
- """Load Binance precision rules for each pair"""
- for pair in self.PAIRS:
- try:
- info = self.client.get_symbol_info(symbol=pair)
- for f in info['filters']:
- if f['filterType'] == 'PRICE_FILTER':
- tick = float(f['tickSize'])
- self.pair_precision[pair] = {
- 'tick': tick,
- 'decimals': self._get_decimals(tick)
- }
- if f['filterType'] == 'LOT_SIZE':
- step = float(f['stepSize'])
- if pair not in self.pair_precision:
- self.pair_precision[pair] = {}
- self.pair_precision[pair]['step'] = step
- self.pair_precision[pair]['step_decimals'] = self._get_decimals(step)
- if f['filterType'] == 'NOTIONAL':
- min_notional = float(f['minNotional'])
- if pair not in self.pair_precision:
- self.pair_precision[pair] = {}
- self.pair_precision[pair]['min_notional'] = min_notional
- except Exception as e:
- logger.error(f"Precision load {pair}: {e}")
-
- def _get_decimals(self, tick):
- """Get decimal places from tick size"""
- s = str(tick)
- if 'e' in s:
- return int(s.split('e-')[1]) if 'e-' in s else 0
- return len(s.split('.')[1]) if '.' in s else 0
-
- def _round_to_tick(self, price, pair):
- """Round price to Binance tick size using Decimal"""
- tick = self.pair_precision.get(pair, {}).get('tick', 0.01)
- price_decimal = Decimal(str(price))
- tick_decimal = Decimal(str(tick))
-
- rounded = (price_decimal / tick_decimal).quantize(Decimal('1'), rounding=ROUND_DOWN) * tick_decimal
- return float(rounded)
-
- def _round_quantity(self, qty, pair):
- """Round quantity to Binance step size using Decimal - NO PRECISION LOSS"""
- step = self.pair_precision.get(pair, {}).get('step', 0.00001)
- step_decimals = self.pair_precision.get(pair, {}).get('step_decimals', 5)
-
- qty_decimal = Decimal(str(qty))
- step_decimal = Decimal(str(step))
-
- # Round down (safe side)
- rounded = (qty_decimal / step_decimal).quantize(Decimal('1'), rounding=ROUND_DOWN) * step_decimal
-
- # Format as string with exactly the right decimals
- format_str = f"0.{'':<{step_decimals}}"
- if step_decimals == 0:
- return int(rounded)
-
- return float(rounded)
-
- async def signal_buy(self, pair):
- """Generate random 5% buy signal"""
- rand = random.randint(1, 100)
- return rand <= self.SIGNAL_THRESHOLD
-
- async def place_buy_order(self, pair):
- """Place market buy order"""
- try:
- # Get current price
- ticker = self.client.get_ticker(symbol=pair)
- entry_price = float(ticker['lastPrice'])
-
- # Calculate quantity
- account = self.client.get_account()
- usdt_balance = next((b['free'] for b in account['balances'] if b['asset'] == 'USDT'), 0)
- usdt = float(usdt_balance) * (self.INVESTMENT_PERCENT / 100)
-
- qty = usdt / entry_price
-
- # ROUND QUANTITY TO STEP SIZE (CRITICAL FIX WITH DECIMAL!)
- qty = self._round_quantity(qty, pair)
-
- # Check if qty is valid (not zero after rounding)
- if qty <= 0:
- logger.warning(f"Quantity too small for {pair}: {qty}")
- return False
-
- # VALIDATE NOTIONAL (order_value must be >= min_notional)
- min_notional = self.pair_precision.get(pair, {}).get('min_notional', 10.0)
- order_value = qty * entry_price
-
- if order_value < min_notional:
- logger.warning(f"Order value too small {pair}: ${order_value:.2f} < ${min_notional:.2f}")
- return False
-
- # Place market buy
- order = self.client.order_market_buy(symbol=pair, quantity=qty)
- logger.info(f"🟢 BUY: {pair} x{qty} @ ${entry_price:.2f} (value: ${order_value:.2f})")
-
- # Store trade
- self.active_trades[pair] = {
- 'entry': entry_price,
- 'qty': qty,
- 'time': datetime.now()
- }
-
- # Place SL order (FIXED WITH CORRECT API METHOD)
- await self.place_stop_loss(pair, entry_price, qty)
-
- self.trades_today += 1
- return True
-
- except Exception as e:
- logger.error(f"Buy Error {pair}: {e}")
- return False
-
- async def place_stop_loss(self, pair, entry_price, qty):
- """Place stop loss order with correct precision & API method"""
- try:
- # Calculate SL price with 2.5% loss
- sl_price = entry_price * (1 - self.STOP_LOSS_PERCENT / 100)
-
- # ROUND TO TICK SIZE (CRITICAL FIX!)
- sl_price = self._round_to_tick(sl_price, pair)
-
- # ROUND QUANTITY TO STEP SIZE (WITH DECIMAL!)
- qty_rounded = self._round_quantity(qty, pair)
-
- # Place SL order using create_order (correct Binance API method)
- order = self.client.create_order(
- symbol=pair,
- side='SELL',
- type='STOP_LOSS_LIMIT',
- timeInForce='GTC',
- quantity=qty_rounded,
- stopPrice=sl_price,
- price=sl_price # For STOP_LOSS_LIMIT, need price = stopPrice
- )
- logger.info(f"🛡️ SL: {pair} x{qty_rounded} @ ${sl_price:.4f} (-{self.STOP_LOSS_PERCENT}%)")
-
- except BinanceAPIException as e:
- logger.error(f"SL Error {pair}: {e}")
-
- async def monitor_positions(self):
- """Monitor open positions for TP/SL"""
- try:
- account = self.client.get_account()
-
- for pair in list(self.active_trades.keys()):
- ticker = self.client.get_ticker(symbol=pair)
- current = float(ticker['lastPrice'])
- entry = self.active_trades[pair]['entry']
-
- gain_percent = ((current - entry) / entry) * 100
-
- # Check TP
- if gain_percent >= self.TAKE_PROFIT_PERCENT:
- await self.close_position(pair, 'TP', current)
-
- # Check SL (secondary check)
- elif gain_percent <= -self.STOP_LOSS_PERCENT:
- await self.close_position(pair, 'SL', current)
-
- except Exception as e:
- logger.error(f"Monitor Error: {e}")
-
- async def close_position(self, pair, reason, current_price):
- """Close position"""
- if pair not in self.active_trades:
- return
-
- qty = self.active_trades[pair]['qty']
- entry = self.active_trades[pair]['entry']
- pnl = (current_price - entry) * qty
-
- logger.info(f"📊 {reason}: {pair} closed @ ${current_price:.2f}, PnL: ${pnl:.2f}")
-
- del self.active_trades[pair]
- self.daily_pnl += pnl
-
- if pnl > 0:
- self.wins_today += 1
- else:
- self.losses_today += 1
-
- # Check daily loss limit
- if self.daily_pnl <= self.DAILY_LOSS_LIMIT:
- logger.warning(f"⚠️ DAILY LOSS LIMIT REACHED: ${self.daily_pnl:.2f}")
- self.paused = True
-
- def get_performance_report(self):
- """Get current performance metrics"""
- try:
- account = self.client.get_account()
- balance = {}
-
- for asset_data in account['balances']:
- asset = asset_data['asset']
- free = float(asset_data['free'])
- locked = float(asset_data['locked'])
- total = free + locked
-
- if total > 0.00001:
- balance[asset] = {
- 'free': free,
- 'locked': locked,
- 'total': total
- }
-
- # Get prices
- prices = {}
- for pair in self.PAIRS:
- try:
- ticker = self.client.get_ticker(symbol=pair)
- asset = pair.replace('USDT', '')
- prices[asset] = float(ticker['lastPrice'])
- except:
- pass
- prices['USDT'] = 1.0
-
- # Calculate portfolio
- portfolio = 0
- tracked = ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'USDT']
- for asset in tracked:
- if asset in balance:
- portfolio += balance[asset]['total'] * prices.get(asset, 0)
-
- return {
- 'portfolio': round(portfolio, 2),
- 'usdt_free': balance.get('USDT', {}).get('free', 0),
- 'daily_pnl': self.daily_pnl,
- 'trades_today': self.trades_today,
- 'wins': self.wins_today,
- 'losses': self.losses_today,
- 'active_trades': len(self.active_trades),
- 'paused': self.paused
- }
- except Exception as e:
- logger.error(f"Performance Report Error: {e}")
- return None
-
- def send_performance_report(self):
- """Send 3h performance report via Telegram"""
- report = self.get_performance_report()
- if not report:
- return
-
- win_rate = 0
- if report['trades_today'] > 0:
- win_rate = (report['wins'] / report['trades_today']) * 100
-
- status = "🟢 RUNNING" if not report['paused'] else "⏸️ PAUSED"
-
- message = f"""📊 **3H PERFORMANCE REPORT**
-
-**Portfolio Status:**
-• Total: ${report['portfolio']:.2f}
-• USDT Free: ${report['usdt_free']:.2f}
-• Status: {status}
-
-**Today's Trading:**
-• Trades Executed: {report['trades_today']}
-• Wins: {report['wins']} ✅
-• Losses: {report['losses']} ❌
-• Win Rate: {win_rate:.1f}%
-
-**P&L:**
-• Daily P&L: ${report['daily_pnl']:.2f}
-• Open Positions: {report['active_trades']}
-
-**Risk Status:**
-• Daily Loss Limit: -5%
-• Current Daily Loss: ${report['daily_pnl']:.2f}
-• Pause Active: {'Yes ⏸️' if report['paused'] else 'No ✅'}
-
----
-Time: {datetime.now().strftime('%Y-%m-%d %H:%M UTC')}
-Bot: V5 ENHANCED (FULLY FIXED)"""
-
- self._send_telegram(message)
- logger.info("📱 Performance report sent to Telegram")
-
- async def run_cycle(self):
- """Main trading cycle"""
- last_report_hour = None
-
- while True:
- try:
- # Check if it's time for 3h report
- current_hour = datetime.now().hour
- if current_hour % 3 == 0 and last_report_hour != current_hour:
- self.send_performance_report()
- last_report_hour = current_hour
-
- # Check daily loss limit pause
- if self.paused:
- logger.info("⏸️ Bot PAUSED (daily loss limit reached)")
- await asyncio.sleep(60)
- continue
-
- # Signal generation
- for pair in self.PAIRS:
- if pair not in self.active_trades and await self.signal_buy(pair):
- await self.place_buy_order(pair)
-
- # Monitor positions
- await self.monitor_positions()
-
- await asyncio.sleep(5)
-
- except Exception as e:
- logger.error(f"Cycle Error: {e}")
- await asyncio.sleep(5)
-
-async def main():
- bot = TradingBot()
- await bot.run_cycle()
-
-if __name__ == '__main__':
- asyncio.run(main())
diff --git a/src/main_ml.py.backup.30pct b/src/main_ml.py.backup.30pct
deleted file mode 100644
index 700e8cd..0000000
--- a/src/main_ml.py.backup.30pct
+++ /dev/null
@@ -1,431 +0,0 @@
-#!/usr/bin/env python3
-"""
-Trading Bot V5 ENHANCED - FULLY FIXED VERSION
-Implementiert: SL, TP, Daily Limit, R:R Ratio
-FIXED: Binance API method (order_take_profit → create_order)
-FIXED: PRICE_FILTER für SL Orders durch Tick-Rounding
-FIXED: Quantity rounding mit Decimal (no floating point errors)
-FIXED: Quantity string formatting für Binance
-NEW: Startup Message + 3h Performance Reports via Telegram
-"""
-import os, asyncio, logging, random, json, time, math, requests
-from decimal import Decimal, ROUND_DOWN
-from binance.client import Client
-from binance.exceptions import BinanceAPIException
-from datetime import datetime, timedelta
-
-# Logging
-logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-# Load env
-env = {}
-with open('/home/marc/bot-deploy/.env') as f:
- for line in f:
- k,_,v = line.partition('=')
- env[k.strip()] = v.strip()
-
-class TradingBot:
- def __init__(self):
- self.client = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
-
- self.PAIRS = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
- self.SIGNAL_THRESHOLD = 5 # 5% random signal
- self.INVESTMENT_PERCENT = 30 # 30% per trade (5 parallel = 90% max, 10% buffer)
- self.STOP_LOSS_PERCENT = 2.5 # -2.5%
- self.TAKE_PROFIT_PERCENT = 3.0 # +3%
- self.DAILY_LOSS_LIMIT = -5 # -5% max
-
- self.active_trades = {}
- self.daily_pnl = 0
- self.paused = False
- self.start_time = datetime.now()
- self.trades_today = 0
- self.wins_today = 0
- self.losses_today = 0
-
- # Precision cache
- self.pair_precision = {}
- self._load_pair_precision()
-
- # Telegram
- self.telegram_token = env.get('TELEGRAM_BOT_TOKEN')
- self.telegram_chat_id = env.get('TELEGRAM_CHAT_ID')
-
- logger.info("✅ Bot initialized with Risk Management (SL 2.5%, TP 3%, Daily Limit 5%)")
-
- # Send startup message
- self._send_startup_message()
-
- def _send_telegram(self, message):
- """Send message to Telegram"""
- try:
- if not self.telegram_token or not self.telegram_chat_id:
- logger.warning("Telegram not configured")
- return False
-
- url = f"https://api.telegram.org/bot{self.telegram_token}/sendMessage"
- data = {
- 'chat_id': self.telegram_chat_id,
- 'text': message,
- 'parse_mode': 'Markdown'
- }
- response = requests.post(url, data=data, timeout=5)
- return response.status_code == 200
- except Exception as e:
- logger.error(f"Telegram Error: {e}")
- return False
-
- def _send_startup_message(self):
- """Send startup message with current strategy"""
- message = """🤖 **TRADING BOT V5 — STARTED!**
-
-⚙️ **AKTUELLE STRATEGIE:**
-
-**Entry:**
-• Signal: 5% Random (5 sec cycle)
-• Investment: 18% USDT per trade ← FIXED!
-• Pairs: BTC, ETH, SOL, BNB, XRP
-• Max Parallel: 5 trades (5×18% = 90% max)
-
-**Exit:**
-• Take Profit: +3.0% ✅
-• Stop Loss: -2.5% ✅
-• Risk/Reward: 1:1.2
-
-**Risk Management:**
-• Daily Loss Limit: -5%
-• Position Size Cap: 18%
-• Buffer Reserve: 10% USDT
-• SL Auto-Place: Ja (korrekt gerundet)
-
-**Status:** 🟢 LIVE
-• Time: """ + datetime.now().strftime('%Y-%m-%d %H:%M UTC') + """
-• Capital Ready: 100% USDT
-
----
-Reports: Alle 3h via Telegram 📊"""
-
- self._send_telegram(message)
- logger.info("📱 Startup message sent to Telegram")
-
- def _load_pair_precision(self):
- """Load Binance precision rules for each pair"""
- for pair in self.PAIRS:
- try:
- info = self.client.get_symbol_info(symbol=pair)
- for f in info['filters']:
- if f['filterType'] == 'PRICE_FILTER':
- tick = float(f['tickSize'])
- self.pair_precision[pair] = {
- 'tick': tick,
- 'decimals': self._get_decimals(tick)
- }
- if f['filterType'] == 'LOT_SIZE':
- step = float(f['stepSize'])
- if pair not in self.pair_precision:
- self.pair_precision[pair] = {}
- self.pair_precision[pair]['step'] = step
- self.pair_precision[pair]['step_decimals'] = self._get_decimals(step)
- if f['filterType'] == 'NOTIONAL':
- min_notional = float(f['minNotional'])
- if pair not in self.pair_precision:
- self.pair_precision[pair] = {}
- self.pair_precision[pair]['min_notional'] = min_notional
- except Exception as e:
- logger.error(f"Precision load {pair}: {e}")
-
- def _get_decimals(self, tick):
- """Get decimal places from tick size"""
- s = str(tick)
- if 'e' in s:
- return int(s.split('e-')[1]) if 'e-' in s else 0
- return len(s.split('.')[1]) if '.' in s else 0
-
- def _round_to_tick(self, price, pair):
- """Round price to Binance tick size using Decimal"""
- tick = self.pair_precision.get(pair, {}).get('tick', 0.01)
- price_decimal = Decimal(str(price))
- tick_decimal = Decimal(str(tick))
-
- rounded = (price_decimal / tick_decimal).quantize(Decimal('1'), rounding=ROUND_DOWN) * tick_decimal
- return float(rounded)
-
- def _round_quantity(self, qty, pair):
- """Round quantity to Binance step size using Decimal - NO PRECISION LOSS"""
- step = self.pair_precision.get(pair, {}).get('step', 0.00001)
- step_decimals = self.pair_precision.get(pair, {}).get('step_decimals', 5)
-
- qty_decimal = Decimal(str(qty))
- step_decimal = Decimal(str(step))
-
- # Round down (safe side)
- rounded = (qty_decimal / step_decimal).quantize(Decimal('1'), rounding=ROUND_DOWN) * step_decimal
-
- # Format as string with exactly the right decimals
- format_str = f"0.{'':<{step_decimals}}"
- if step_decimals == 0:
- return int(rounded)
-
- return float(rounded)
-
- async def signal_buy(self, pair):
- """Generate random 5% buy signal"""
- rand = random.randint(1, 100)
- return rand <= self.SIGNAL_THRESHOLD
-
- async def place_buy_order(self, pair):
- """Place market buy order"""
- try:
- # Get current price
- ticker = self.client.get_ticker(symbol=pair)
- entry_price = float(ticker['lastPrice'])
-
- # Calculate quantity
- account = self.client.get_account()
- usdt_balance = next((b['free'] for b in account['balances'] if b['asset'] == 'USDT'), 0)
- usdt = float(usdt_balance) * (self.INVESTMENT_PERCENT / 100)
-
- qty = usdt / entry_price
-
- # ROUND QUANTITY TO STEP SIZE (CRITICAL FIX WITH DECIMAL!)
- qty = self._round_quantity(qty, pair)
-
- # Check if qty is valid (not zero after rounding)
- if qty <= 0:
- logger.warning(f"Quantity too small for {pair}: {qty}")
- return False
-
- # VALIDATE NOTIONAL (order_value must be >= min_notional)
- min_notional = self.pair_precision.get(pair, {}).get('min_notional', 10.0)
- order_value = qty * entry_price
-
- if order_value < min_notional:
- logger.warning(f"Order value too small {pair}: ${order_value:.2f} < ${min_notional:.2f}")
- return False
-
- # Place market buy
- order = self.client.order_market_buy(symbol=pair, quantity=qty)
- logger.info(f"🟢 BUY: {pair} x{qty} @ ${entry_price:.2f} (value: ${order_value:.2f})")
-
- # Store trade
- self.active_trades[pair] = {
- 'entry': entry_price,
- 'qty': qty,
- 'time': datetime.now()
- }
-
- # Place SL order (FIXED WITH CORRECT API METHOD)
- await self.place_stop_loss(pair, entry_price, qty)
-
- self.trades_today += 1
- return True
-
- except Exception as e:
- logger.error(f"Buy Error {pair}: {e}")
- return False
-
- async def place_stop_loss(self, pair, entry_price, qty):
- """Place stop loss order with correct precision & API method"""
- try:
- # Calculate SL price with 2.5% loss
- sl_price = entry_price * (1 - self.STOP_LOSS_PERCENT / 100)
-
- # ROUND TO TICK SIZE (CRITICAL FIX!)
- sl_price = self._round_to_tick(sl_price, pair)
-
- # ROUND QUANTITY TO STEP SIZE (WITH DECIMAL!)
- qty_rounded = self._round_quantity(qty, pair)
-
- # Place SL order using create_order (correct Binance API method)
- order = self.client.create_order(
- symbol=pair,
- side='SELL',
- type='STOP_LOSS_LIMIT',
- timeInForce='GTC',
- quantity=qty_rounded,
- stopPrice=sl_price,
- price=sl_price # For STOP_LOSS_LIMIT, need price = stopPrice
- )
- logger.info(f"🛡️ SL: {pair} x{qty_rounded} @ ${sl_price:.4f} (-{self.STOP_LOSS_PERCENT}%)")
-
- except BinanceAPIException as e:
- logger.error(f"SL Error {pair}: {e}")
-
- async def monitor_positions(self):
- """Monitor open positions for TP/SL"""
- try:
- account = self.client.get_account()
-
- for pair in list(self.active_trades.keys()):
- ticker = self.client.get_ticker(symbol=pair)
- current = float(ticker['lastPrice'])
- entry = self.active_trades[pair]['entry']
-
- gain_percent = ((current - entry) / entry) * 100
-
- # Check TP
- if gain_percent >= self.TAKE_PROFIT_PERCENT:
- await self.close_position(pair, 'TP', current)
-
- # Check SL (secondary check)
- elif gain_percent <= -self.STOP_LOSS_PERCENT:
- await self.close_position(pair, 'SL', current)
-
- except Exception as e:
- logger.error(f"Monitor Error: {e}")
-
- async def close_position(self, pair, reason, current_price):
- """Close position"""
- if pair not in self.active_trades:
- return
-
- qty = self.active_trades[pair]['qty']
- entry = self.active_trades[pair]['entry']
- pnl = (current_price - entry) * qty
-
- logger.info(f"📊 {reason}: {pair} closed @ ${current_price:.2f}, PnL: ${pnl:.2f}")
-
- del self.active_trades[pair]
- self.daily_pnl += pnl
-
- if pnl > 0:
- self.wins_today += 1
- else:
- self.losses_today += 1
-
- # Check daily loss limit
- if self.daily_pnl <= self.DAILY_LOSS_LIMIT:
- logger.warning(f"⚠️ DAILY LOSS LIMIT REACHED: ${self.daily_pnl:.2f}")
- self.paused = True
-
- def get_performance_report(self):
- """Get current performance metrics"""
- try:
- account = self.client.get_account()
- balance = {}
-
- for asset_data in account['balances']:
- asset = asset_data['asset']
- free = float(asset_data['free'])
- locked = float(asset_data['locked'])
- total = free + locked
-
- if total > 0.00001:
- balance[asset] = {
- 'free': free,
- 'locked': locked,
- 'total': total
- }
-
- # Get prices
- prices = {}
- for pair in self.PAIRS:
- try:
- ticker = self.client.get_ticker(symbol=pair)
- asset = pair.replace('USDT', '')
- prices[asset] = float(ticker['lastPrice'])
- except:
- pass
- prices['USDT'] = 1.0
-
- # Calculate portfolio
- portfolio = 0
- tracked = ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'USDT']
- for asset in tracked:
- if asset in balance:
- portfolio += balance[asset]['total'] * prices.get(asset, 0)
-
- return {
- 'portfolio': round(portfolio, 2),
- 'usdt_free': balance.get('USDT', {}).get('free', 0),
- 'daily_pnl': self.daily_pnl,
- 'trades_today': self.trades_today,
- 'wins': self.wins_today,
- 'losses': self.losses_today,
- 'active_trades': len(self.active_trades),
- 'paused': self.paused
- }
- except Exception as e:
- logger.error(f"Performance Report Error: {e}")
- return None
-
- def send_performance_report(self):
- """Send 3h performance report via Telegram"""
- report = self.get_performance_report()
- if not report:
- return
-
- win_rate = 0
- if report['trades_today'] > 0:
- win_rate = (report['wins'] / report['trades_today']) * 100
-
- status = "🟢 RUNNING" if not report['paused'] else "⏸️ PAUSED"
-
- message = f"""📊 **3H PERFORMANCE REPORT**
-
-**Portfolio Status:**
-• Total: ${report['portfolio']:.2f}
-• USDT Free: ${report['usdt_free']:.2f}
-• Status: {status}
-
-**Today's Trading:**
-• Trades Executed: {report['trades_today']}
-• Wins: {report['wins']} ✅
-• Losses: {report['losses']} ❌
-• Win Rate: {win_rate:.1f}%
-
-**P&L:**
-• Daily P&L: ${report['daily_pnl']:.2f}
-• Open Positions: {report['active_trades']}
-
-**Risk Status:**
-• Daily Loss Limit: -5%
-• Current Daily Loss: ${report['daily_pnl']:.2f}
-• Pause Active: {'Yes ⏸️' if report['paused'] else 'No ✅'}
-
----
-Time: {datetime.now().strftime('%Y-%m-%d %H:%M UTC')}
-Bot: V5 ENHANCED (FULLY FIXED)"""
-
- self._send_telegram(message)
- logger.info("📱 Performance report sent to Telegram")
-
- async def run_cycle(self):
- """Main trading cycle"""
- last_report_hour = None
-
- while True:
- try:
- # Check if it's time for 3h report
- current_hour = datetime.now().hour
- if current_hour % 3 == 0 and last_report_hour != current_hour:
- self.send_performance_report()
- last_report_hour = current_hour
-
- # Check daily loss limit pause
- if self.paused:
- logger.info("⏸️ Bot PAUSED (daily loss limit reached)")
- await asyncio.sleep(60)
- continue
-
- # Signal generation
- for pair in self.PAIRS:
- if pair not in self.active_trades and await self.signal_buy(pair):
- await self.place_buy_order(pair)
-
- # Monitor positions
- await self.monitor_positions()
-
- await asyncio.sleep(5)
-
- except Exception as e:
- logger.error(f"Cycle Error: {e}")
- await asyncio.sleep(5)
-
-async def main():
- bot = TradingBot()
- await bot.run_cycle()
-
-if __name__ == '__main__':
- asyncio.run(main())
diff --git a/src/main_ml.py.backup.35pct.20240706 b/src/main_ml.py.backup.35pct.20240706
deleted file mode 100644
index 4370cb3..0000000
--- a/src/main_ml.py.backup.35pct.20240706
+++ /dev/null
@@ -1,434 +0,0 @@
-#!/usr/bin/env python3
-"""
-Trading Bot V5 ENHANCED - FULLY FIXED VERSION
-Implementiert: SL, TP, Daily Limit, R:R Ratio
-FIXED: Binance API method (order_take_profit → create_order)
-FIXED: PRICE_FILTER für SL Orders durch Tick-Rounding
-FIXED: Quantity rounding mit Decimal (no floating point errors)
-FIXED: Quantity string formatting für Binance
-NEW: Startup Message + 3h Performance Reports via Telegram
-"""
-import os, asyncio, logging, random, json, time, math, requests
-from decimal import Decimal, ROUND_DOWN
-from binance.client import Client
-from binance.exceptions import BinanceAPIException
-from datetime import datetime, timedelta
-
-# Logging
-logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-# Load env
-env = {}
-with open('/home/marc/bot-deploy/.env') as f:
- for line in f:
- k,_,v = line.partition('=')
- env[k.strip()] = v.strip()
-
-class TradingBot:
- def __init__(self):
- self.client = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
-
- self.PAIRS = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
- self.SIGNAL_THRESHOLD = 5 # 5% random signal
- self.INVESTMENT_PERCENT = 35 # 35% per trade (5 parallel = 90% max, 10% buffer)
- self.NOTIONAL_MIN = 5.0 # Override Binance minimum to $3
- self.STOP_LOSS_PERCENT = 2.5 # -2.5%
- self.TAKE_PROFIT_PERCENT = 3.0 # +3%
- self.DAILY_LOSS_LIMIT = -5 # -5% max
-
- self.active_trades = {}
- self.daily_pnl = 0
- self.paused = False
- self.start_time = datetime.now()
- self.trades_today = 0
- self.wins_today = 0
- self.losses_today = 0
-
- # Precision cache
- self.pair_precision = {}
- self._load_pair_precision()
-
- # Telegram
- self.telegram_token = env.get('TELEGRAM_BOT_TOKEN')
- self.telegram_chat_id = env.get('TELEGRAM_CHAT_ID')
-
- logger.info("✅ Bot initialized with Risk Management (SL 2.5%, TP 3%, Daily Limit 5%)")
-
- # Send startup message
- self._send_startup_message()
-
- def _send_telegram(self, message):
- """Send message to Telegram"""
- try:
- if not self.telegram_token or not self.telegram_chat_id:
- logger.warning("Telegram not configured")
- return False
-
- url = f"https://api.telegram.org/bot{self.telegram_token}/sendMessage"
- data = {
- 'chat_id': self.telegram_chat_id,
- 'text': message,
- 'parse_mode': 'Markdown'
- }
- response = requests.post(url, data=data, timeout=5)
- return response.status_code == 200
- except Exception as e:
- logger.error(f"Telegram Error: {e}")
- return False
-
- def _send_startup_message(self):
- """Send startup message with current strategy"""
- message = """🤖 **TRADING BOT V5 — STARTED!**
-
-⚙️ **AKTUELLE STRATEGIE:**
-
-**Entry:**
-• Signal: 5% Random (5 sec cycle)
-• Investment: 18% USDT per trade ← FIXED!
-• Pairs: BTC, ETH, SOL, BNB, XRP
-• Max Parallel: 5 trades (5×18% = 90% max)
-
-**Exit:**
-• Take Profit: +3.0% ✅
-• Stop Loss: -2.5% ✅
-• Risk/Reward: 1:1.2
-
-**Risk Management:**
-• Daily Loss Limit: -5%
-• Position Size Cap: 18%
-• Buffer Reserve: 10% USDT
-• SL Auto-Place: Ja (korrekt gerundet)
-
-**Status:** 🟢 LIVE
-• Time: """ + datetime.now().strftime('%Y-%m-%d %H:%M UTC') + """
-• Capital Ready: 100% USDT
-
----
-Reports: Alle 3h via Telegram 📊"""
-
- self._send_telegram(message)
- logger.info("📱 Startup message sent to Telegram")
-
- def _load_pair_precision(self):
- """Load Binance precision rules for each pair"""
- for pair in self.PAIRS:
- try:
- info = self.client.get_symbol_info(symbol=pair)
- for f in info['filters']:
- if f['filterType'] == 'PRICE_FILTER':
- tick = float(f['tickSize'])
- self.pair_precision[pair] = {
- 'tick': tick,
- 'decimals': self._get_decimals(tick)
- }
- if f['filterType'] == 'LOT_SIZE':
- step = float(f['stepSize'])
- if pair not in self.pair_precision:
- self.pair_precision[pair] = {}
- self.pair_precision[pair]['step'] = step
- self.pair_precision[pair]['step_decimals'] = self._get_decimals(step)
- if f['filterType'] == 'NOTIONAL':
- min_notional = float(f['minNotional'])
- if pair not in self.pair_precision:
- self.pair_precision[pair] = {}
- self.pair_precision[pair]['min_notional'] = min_notional
- except Exception as e:
- logger.error(f"Precision load {pair}: {e}")
-
- def _get_decimals(self, tick):
- """Get decimal places from tick size"""
- s = str(tick)
- if 'e' in s:
- return int(s.split('e-')[1]) if 'e-' in s else 0
- return len(s.split('.')[1]) if '.' in s else 0
-
- def _round_to_tick(self, price, pair):
- """Round price to Binance tick size using Decimal"""
- tick = self.pair_precision.get(pair, {}).get('tick', 0.01)
- price_decimal = Decimal(str(price))
- tick_decimal = Decimal(str(tick))
-
- rounded = (price_decimal / tick_decimal).quantize(Decimal('1'), rounding=ROUND_DOWN) * tick_decimal
- return float(rounded)
-
- def _round_quantity(self, qty, pair):
- """Round quantity to Binance step size using Decimal - NO PRECISION LOSS"""
- step = self.pair_precision.get(pair, {}).get('step', 0.00001)
- step_decimals = self.pair_precision.get(pair, {}).get('step_decimals', 5)
-
- qty_decimal = Decimal(str(qty))
- step_decimal = Decimal(str(step))
-
- # Round down (safe side)
- rounded = (qty_decimal / step_decimal).quantize(Decimal('1'), rounding=ROUND_DOWN) * step_decimal
-
- # Format as string with exactly the right decimals
- format_str = f"0.{'':<{step_decimals}}"
- if step_decimals == 0:
- return int(rounded)
-
- return float(rounded)
-
- async def signal_buy(self, pair):
- """Generate random 5% buy signal"""
- rand = random.randint(1, 100)
- return rand <= self.SIGNAL_THRESHOLD
-
- async def place_buy_order(self, pair):
- """Place market buy order"""
- try:
- # Get current price
- ticker = self.client.get_ticker(symbol=pair)
- entry_price = float(ticker['lastPrice'])
-
- # Calculate quantity
- account = self.client.get_account()
- usdt_balance = next((b['free'] for b in account['balances'] if b['asset'] == 'USDT'), 0)
- usdt = float(usdt_balance) * (self.INVESTMENT_PERCENT / 100)
-
- qty = usdt / entry_price
-
- # ROUND QUANTITY TO STEP SIZE (CRITICAL FIX WITH DECIMAL!)
- qty = self._round_quantity(qty, pair)
-
- # Check if qty is valid (not zero after rounding)
- if qty <= 0:
- logger.warning(f"Quantity too small for {pair}: {qty}")
- return False
-
- # VALIDATE NOTIONAL (order_value must be >= 3.0 MINIMUM)
- order_value = qty * entry_price
- NOTIONAL_MIN = 5.0 # Minimum $3
-
- if order_value < NOTIONAL_MIN:
- logger.warning(f"Order value too small {pair}: ${order_value:.2f} < ${NOTIONAL_MIN:.2f} (qty={qty}, price={entry_price})")
- return False
-
- logger.info(f"✅ NOTIONAL Check Passed: {pair} ${order_value:.2f} >= ${NOTIONAL_MIN:.2f}")
-
- # Place market buy
- order = self.client.order_market_buy(symbol=pair, quantity=qty)
- logger.info(f"🟢 BUY: {pair} x{qty} @ ${entry_price:.2f} (value: ${order_value:.2f})")
-
- # Store trade
- self.active_trades[pair] = {
- 'entry': entry_price,
- 'qty': qty,
- 'time': datetime.now()
- }
-
- # Place SL order (FIXED WITH CORRECT API METHOD)
- await self.place_stop_loss(pair, entry_price, qty)
-
- self.trades_today += 1
- return True
-
- except Exception as e:
- logger.error(f"Buy Error {pair}: {e}")
- return False
-
- async def place_stop_loss(self, pair, entry_price, qty):
- """Place stop loss order with correct precision & API method"""
- try:
- # Calculate SL price with 2.5% loss
- sl_price = entry_price * (1 - self.STOP_LOSS_PERCENT / 100)
-
- # ROUND TO TICK SIZE (CRITICAL FIX!)
- sl_price = self._round_to_tick(sl_price, pair)
-
- # ROUND QUANTITY TO STEP SIZE (WITH DECIMAL!)
- qty_rounded = self._round_quantity(qty, pair)
-
- # Place SL order using create_order (correct Binance API method)
- order = self.client.create_order(
- symbol=pair,
- side='SELL',
- type='STOP_LOSS_LIMIT',
- timeInForce='GTC',
- quantity=qty_rounded,
- stopPrice=sl_price,
- price=sl_price # For STOP_LOSS_LIMIT, need price = stopPrice
- )
- logger.info(f"🛡️ SL: {pair} x{qty_rounded} @ ${sl_price:.4f} (-{self.STOP_LOSS_PERCENT}%)")
-
- except BinanceAPIException as e:
- logger.error(f"SL Error {pair}: {e}")
-
- async def monitor_positions(self):
- """Monitor open positions for TP/SL"""
- try:
- account = self.client.get_account()
-
- for pair in list(self.active_trades.keys()):
- ticker = self.client.get_ticker(symbol=pair)
- current = float(ticker['lastPrice'])
- entry = self.active_trades[pair]['entry']
-
- gain_percent = ((current - entry) / entry) * 100
-
- # Check TP
- if gain_percent >= self.TAKE_PROFIT_PERCENT:
- await self.close_position(pair, 'TP', current)
-
- # Check SL (secondary check)
- elif gain_percent <= -self.STOP_LOSS_PERCENT:
- await self.close_position(pair, 'SL', current)
-
- except Exception as e:
- logger.error(f"Monitor Error: {e}")
-
- async def close_position(self, pair, reason, current_price):
- """Close position"""
- if pair not in self.active_trades:
- return
-
- qty = self.active_trades[pair]['qty']
- entry = self.active_trades[pair]['entry']
- pnl = (current_price - entry) * qty
-
- logger.info(f"📊 {reason}: {pair} closed @ ${current_price:.2f}, PnL: ${pnl:.2f}")
-
- del self.active_trades[pair]
- self.daily_pnl += pnl
-
- if pnl > 0:
- self.wins_today += 1
- else:
- self.losses_today += 1
-
- # Check daily loss limit
- if self.daily_pnl <= self.DAILY_LOSS_LIMIT:
- logger.warning(f"⚠️ DAILY LOSS LIMIT REACHED: ${self.daily_pnl:.2f}")
- self.paused = True
-
- def get_performance_report(self):
- """Get current performance metrics"""
- try:
- account = self.client.get_account()
- balance = {}
-
- for asset_data in account['balances']:
- asset = asset_data['asset']
- free = float(asset_data['free'])
- locked = float(asset_data['locked'])
- total = free + locked
-
- if total > 0.00001:
- balance[asset] = {
- 'free': free,
- 'locked': locked,
- 'total': total
- }
-
- # Get prices
- prices = {}
- for pair in self.PAIRS:
- try:
- ticker = self.client.get_ticker(symbol=pair)
- asset = pair.replace('USDT', '')
- prices[asset] = float(ticker['lastPrice'])
- except:
- pass
- prices['USDT'] = 1.0
-
- # Calculate portfolio
- portfolio = 0
- tracked = ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'USDT']
- for asset in tracked:
- if asset in balance:
- portfolio += balance[asset]['total'] * prices.get(asset, 0)
-
- return {
- 'portfolio': round(portfolio, 2),
- 'usdt_free': balance.get('USDT', {}).get('free', 0),
- 'daily_pnl': self.daily_pnl,
- 'trades_today': self.trades_today,
- 'wins': self.wins_today,
- 'losses': self.losses_today,
- 'active_trades': len(self.active_trades),
- 'paused': self.paused
- }
- except Exception as e:
- logger.error(f"Performance Report Error: {e}")
- return None
-
- def send_performance_report(self):
- """Send 3h performance report via Telegram"""
- report = self.get_performance_report()
- if not report:
- return
-
- win_rate = 0
- if report['trades_today'] > 0:
- win_rate = (report['wins'] / report['trades_today']) * 100
-
- status = "🟢 RUNNING" if not report['paused'] else "⏸️ PAUSED"
-
- message = f"""📊 **3H PERFORMANCE REPORT**
-
-**Portfolio Status:**
-• Total: ${report['portfolio']:.2f}
-• USDT Free: ${report['usdt_free']:.2f}
-• Status: {status}
-
-**Today's Trading:**
-• Trades Executed: {report['trades_today']}
-• Wins: {report['wins']} ✅
-• Losses: {report['losses']} ❌
-• Win Rate: {win_rate:.1f}%
-
-**P&L:**
-• Daily P&L: ${report['daily_pnl']:.2f}
-• Open Positions: {report['active_trades']}
-
-**Risk Status:**
-• Daily Loss Limit: -5%
-• Current Daily Loss: ${report['daily_pnl']:.2f}
-• Pause Active: {'Yes ⏸️' if report['paused'] else 'No ✅'}
-
----
-Time: {datetime.now().strftime('%Y-%m-%d %H:%M UTC')}
-Bot: V5 ENHANCED (FULLY FIXED)"""
-
- self._send_telegram(message)
- logger.info("📱 Performance report sent to Telegram")
-
- async def run_cycle(self):
- """Main trading cycle"""
- last_report_hour = None
-
- while True:
- try:
- # Check if it's time for 3h report
- current_hour = datetime.now().hour
- if current_hour % 3 == 0 and last_report_hour != current_hour:
- self.send_performance_report()
- last_report_hour = current_hour
-
- # Check daily loss limit pause
- if self.paused:
- logger.info("⏸️ Bot PAUSED (daily loss limit reached)")
- await asyncio.sleep(60)
- continue
-
- # Signal generation
- for pair in self.PAIRS:
- if pair not in self.active_trades and await self.signal_buy(pair):
- await self.place_buy_order(pair)
-
- # Monitor positions
- await self.monitor_positions()
-
- await asyncio.sleep(5)
-
- except Exception as e:
- logger.error(f"Cycle Error: {e}")
- await asyncio.sleep(5)
-
-async def main():
- bot = TradingBot()
- await bot.run_cycle()
-
-if __name__ == '__main__':
- asyncio.run(main())
diff --git a/src/main_ml.py.backup.auto-trading b/src/main_ml.py.backup.auto-trading
deleted file mode 100644
index fa247c3..0000000
--- a/src/main_ml.py.backup.auto-trading
+++ /dev/null
@@ -1,190 +0,0 @@
-import asyncio, logging, joblib, time
-from datetime import datetime
-from src.config import get_config
-from src.bot.binance_client import BinanceClientWrapper
-from src.integrations.telegram_notifier import TelegramNotifier
-from src.integrations.obsidian_logger import ObsidianLogger
-from src.strategies.ml_strategy import MLStrategy
-
-logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-class MLTradingBot:
- def __init__(self, config, binance, telegram, obsidian, model, scaler):
- self.config = config
- self.binance = binance
- self.telegram = telegram
- self.obsidian = obsidian
- self.model = model
- self.scaler = scaler
- self.strategy = MLStrategy(trading_pair=config.trading_pair)
-
- # Trading state
- self.last_report_time = time.time()
- self.report_interval = 10800 # 3 HOURS (10800 seconds)
- self.trades_today = 0
- self.wins_today = 0
- self.losses_today = 0
- self.daily_pnl = 0.0
- self.report_count = 0
-
- async def get_market_data(self):
- """Fetch current market price and stats"""
- try:
- ticker = self.config.trading_pair.split('/')[0] # BTC from BTCUSDT
- symbol = f"{ticker}USDT"
-
- # Get current price
- price_data = await self.binance.get_ticker_price(symbol)
- if not price_data:
- return None
-
- current_price = float(price_data)
-
- return {
- 'ticker': ticker,
- 'current_price': current_price,
- 'symbol': symbol
- }
- except Exception as e:
- logger.error(f"Market data fetch error: {e}")
- return None
-
- async def get_account_balance(self):
- """Get current account balance"""
- try:
- balance = self.binance.get_balance('USDT')
- if balance:
- return {'USDT': {'total': balance}}
- return {}
- except Exception as e:
- logger.error(f"Balance fetch error: {e}")
- return {}
-
- async def send_performance_report(self):
- """Send 3-hourly performance report"""
- try:
- self.report_count += 1
-
- # Get market data
- market = await self.get_market_data()
- if not market:
- logger.warning("No market data available")
- return
-
- # Get account balance
- balances = await self.get_account_balance()
- usdt_balance = balances.get('USDT', {}).get('total', 0)
-
- # Build report
- timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S UTC')
- report = f"""
-📊 **PERFORMANCE REPORT #{self.report_count}** — {timestamp}
-
-🎯 **MARKET STATUS:**
-├─ {market['ticker']}/USDT: ${market['current_price']:,.2f}
-├─ Trades Today: {self.trades_today}
-├─ Wins: {self.wins_today} | Losses: {self.losses_today}
-└─ Daily P&L: ${self.daily_pnl:+.2f}
-
-💰 **ACCOUNT STATUS:**
-├─ USDT Balance: ${usdt_balance:,.2f}
-├─ Device: CPU
-├─ Mode: Live Trading
-└─ Strategy: ML (92% accuracy, 60% threshold)
-
-📈 **BOT STATUS: RUNNING ✅**
-"""
-
- # Send to Telegram (FIXED — now actually sends!)
- success = await self.telegram.send_alert(report.strip())
- if success:
- logger.info(f"✅ Performance report #{self.report_count} sent to Telegram")
- else:
- logger.warning(f"❌ Failed to send report #{self.report_count} to Telegram")
-
- except Exception as e:
- logger.error(f"Report error: {e}")
-
- async def monitor_trades(self):
- """Monitor open trades and check signals"""
- try:
- symbol = f"{self.config.trading_pair.split('/')[0]}USDT"
- orders = self.binance.get_open_orders(symbol)
-
- if orders and len(orders) > 0:
- logger.info(f"📈 Open orders: {len(orders)}")
-
- except Exception as e:
- logger.debug(f"Trade monitoring: {e}")
-
- async def run(self):
- """Main bot loop"""
- logger.info(f"🤖 Starting ML Trading Bot — {self.config.trading_pair}")
-
- startup_msg = f"""🤖 **BOT STARTED - V2 ML ADAPTIVE**
-
-✅ Strategy: ML Adaptive (60% threshold)
-✅ Models: BTC 92% accuracy
-✅ Device: CPU (Live)
-✅ Reporting: EVERY 3 HOURS
-✅ Status: ACTIVE & MONITORING"""
-
- await self.telegram.send_alert(startup_msg)
- logger.info("✅ Startup message sent to Telegram")
-
- logger.info("🟢 Bot running — sending reports every 3 hours...")
-
- while True:
- try:
- current_time = time.time()
-
- # Send 3-hourly performance report
- if (current_time - self.last_report_time) >= self.report_interval:
- logger.info(f"⏰ Time for Report #{self.report_count + 1}")
- await self.send_performance_report()
- self.last_report_time = current_time
-
- # Monitor trades every 5 minutes
- await self.monitor_trades()
-
- # Sleep for 5 minutes
- await asyncio.sleep(60) # Check every 1 min instead of 5 min for trading opportunities
-
- except KeyboardInterrupt:
- logger.info("Bot interrupted by user")
- break
- except Exception as e:
- logger.error(f"Bot error: {e}")
- try:
- await self.telegram.send_alert(f"❌ Bot Error: {str(e)[:100]}")
- except:
- pass
- await asyncio.sleep(60)
-
-async def main():
- config = get_config()
-
- if config.environment == 'testnet':
- api_key, api_secret = config.binance_api_key_testnet, config.binance_api_secret_testnet
- else:
- api_key, api_secret = config.binance_api_key_live, config.binance_api_secret_live
-
- binance = BinanceClientWrapper(api_key=api_key, api_secret=api_secret, testnet=(config.environment=='testnet'))
- telegram = TelegramNotifier(bot_token=config.telegram_bot_token, chat_id=config.telegram_chat_id)
- obsidian = ObsidianLogger(vault_path=config.obsidian_vault_path, trade_log_file=config.obsidian_trade_log_file)
-
- try:
- # Load BTC model
- model = joblib.load('/tmp/model_BTC.pkl')
- scaler = joblib.load('/tmp/scaler_BTC.pkl')
- logger.info(f'✅ ML Model loaded: BTC (92% accuracy)')
- except Exception as e:
- logger.error(f'❌ ML Model Error: {e}')
- return
-
- bot = MLTradingBot(config, binance, telegram, obsidian, model, scaler)
- await bot.run()
-
-if __name__ == '__main__':
- asyncio.run(main())
diff --git a/src/main_ml.py.bak b/src/main_ml.py.bak
deleted file mode 100644
index 14c2dec..0000000
--- a/src/main_ml.py.bak
+++ /dev/null
@@ -1,1068 +0,0 @@
-import asyncio, logging, joblib, time, json, aiohttp, os
-from datetime import datetime, timedelta
-from src.config import get_config
-from src.bot.binance_client import BinanceClientWrapper
-from src.integrations.telegram_notifier import TelegramNotifier
-from src.integrations.obsidian_logger import ObsidianLogger
-from src.strategies.ml_strategy import MLStrategy
-
-logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-# DASHBOARD CLIENT - MOCK (sends REAL Binance data only via HTTP)
-class DashboardClient:
- """Send ONLY REAL, VERIFIED trades to dashboard"""
-
- async def record_buy(self, pair, qty, price):
- """Record BUY - VERIFY on Binance first!"""
- try:
- async with aiohttp.ClientSession() as session:
- async with session.post('http://localhost:7000/api/trade/buy', json={
- 'pair': pair,
- 'qty': qty,
- 'price': price,
- 'entry_time': datetime.now().isoformat()
- }) as resp:
- if resp.status == 200:
- logger.info(f'✅ Dashboard recorded BUY: {pair}')
- else:
- logger.warning(f'❌ Dashboard BUY record failed: {resp.status}')
- except Exception as e:
- logger.warning(f'Dashboard BUY error: {e}')
-
- async def record_sell(self, pair, qty, price, profit_usd, profit_pct, hold_time_min):
- """Record SELL - ONLY IF REAL!"""
- try:
- async with aiohttp.ClientSession() as session:
- async with session.post('http://localhost:7000/api/trade/sell', json={
- 'pair': pair,
- 'qty': qty,
- 'price': price,
- 'profit_usd': profit_usd,
- 'profit_pct': profit_pct,
- 'hold_time_min': hold_time_min,
- 'exit_time': datetime.now().isoformat()
- }) as resp:
- if resp.status == 200:
- logger.info(f'✅ Dashboard recorded SELL: {pair} profit=${profit_usd:.2f}')
- else:
- logger.warning(f'❌ Dashboard SELL record failed: {resp.status}')
- except Exception as e:
- logger.warning(f'Dashboard SELL error: {e}')
-
- async def update_state(self, balance, daily_pnl, portfolio_value_usd, total_pnl=0.0, trades_today=0, wins_today=0, losses_today=0):
- """Update state - REAL DATA ONLY"""
- try:
- portfolio_value_chf = portfolio_value_usd * 0.84
- async with aiohttp.ClientSession() as session:
- async with session.post('http://localhost:7000/api/update', json={
- 'balance': balance,
- 'daily_pnl': daily_pnl,
- 'total_pnl': total_pnl,
- 'trades_today': trades_today,
- 'wins_today': wins_today,
- 'losses_today': losses_today,
- 'portfolio_value_usd': portfolio_value_usd,
- 'portfolio_value_chf': portfolio_value_chf,
- 'last_update': datetime.now().isoformat()
- }) as resp:
- if resp.status != 200:
- logger.warning(f'Dashboard state update failed: {resp.status}')
- except Exception as e:
- logger.debug(f'Dashboard update error: {e}')
-
- async def clear_state(self):
- """Clear dashboard - START FRESH"""
- try:
- async with aiohttp.ClientSession() as session:
- async with session.post('http://localhost:7000/api/clear') as resp:
- logger.info(f'Dashboard cleared: {resp.status}')
- except:
- pass
-
-class MLTradingBot:
- def __init__(self, config, binance, telegram, obsidian, model, scaler):
- self.config = config
- self.binance = binance
- self.telegram = telegram
- self.obsidian = obsidian
- self.model = model
- self.scaler = scaler
- self.dashboard = DashboardClient() # ADD THIS
- self.strategy = MLStrategy(trading_pair=config.trading_pair)
-
- self.last_report_time = time.time()
- self.report_interval = 10800
- self.trades_today = 0
- self.wins_today = 0
- self.losses_today = 0
- self.daily_pnl = 0.0
- self.report_count = 0
- self.last_swap_time = time.time()
-
- # Metriken für echte Win-Rate
- self.total_trades = 0
- self.total_pnl = 0.0
- self.max_drawdown = 0.0
- self.min_daily_pnl = 0.0
- self.starting_capital = 100.0
- self.daily_loss_limit_reached = False
-
- # Symbol constraints cache
- self.symbol_info = {}
-
- # Track open positions
- self.current_trades = {} # CLEAN START - reset on bot restart
- self.pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
-
- logger.info('🗑️ RESET: Cleared all stored positions (dashboard will show REAL Binance state only)')
-
- # Error tracking & resilience
- self.last_error_time = 0
- self.error_threshold = 5
- self.error_count = 0
- self.error_cooldown_until = 0
-
- # Daily reset tracking
- self.last_daily_reset = datetime.utcnow().date()
-
- async def load_exchange_info(self):
- """Load LOT_SIZE constraints for all trading pairs"""
- try:
- logger.info('📥 Loading Binance symbol constraints...')
- for pair in self.pairs:
- try:
- info = await self.binance.get_exchange_info(pair)
- if info:
- self.symbol_info[pair] = info
- logger.debug(f'✅ {pair}: minNotional=${info.get("minNotional", 0)}, stepSize={info.get("stepSize", 0)}')
- except Exception as e:
- logger.warning(f'Failed to load {pair}: {e}')
- logger.info(f'✅ Loaded constraints for {len(self.symbol_info)} pairs')
- except Exception as e:
- logger.error(f'Failed to load exchange info: {e}')
-
- def calculate_valid_quantity(self, pair: str, usdt_amount: float, price: float) -> float:
- """Calculate order quantity respecting Binance LOT_SIZE constraints."""
- try:
- if pair not in self.symbol_info:
- logger.warning(f'⚠️ No info for {pair}')
- return 0
-
- if price is None or price <= 0:
- logger.warning(f'⚠️ Invalid price for {pair}: {price}')
- return 0
-
- info = self.symbol_info[pair]
- step_size = float(info.get('stepSize', 1e-8))
- min_qty = float(info.get('minQty', 0))
- max_qty = float(info.get('maxQty', 1e10))
- min_notional = float(info.get('minNotional', 10))
-
- qty = usdt_amount / price
- notional = qty * price
-
- # CHECK MINNOTIONAL BEFORE ROUNDING
- if notional < min_notional:
- logger.debug(f'❌ {pair}: notional ${notional:.2f} < min ${min_notional:.2f} (requested ${usdt_amount:.2f})')
- return 0
-
- if step_size > 0:
- qty = round(qty / step_size) * step_size
-
- # RECHECK NOTIONAL AFTER ROUNDING (rounding might make qty too small!)
- notional_after = qty * price
- if notional_after < min_notional:
- logger.debug(f'❌ {pair}: after rounding notional ${notional_after:.2f} < min ${min_notional:.2f}')
- return 0
-
- if qty < min_qty:
- logger.debug(f'❌ {pair}: qty {qty:.8f} < min {min_qty:.8f}')
- return 0
- if qty > max_qty:
- qty = max_qty
-
- logger.debug(f'✅ {pair}: qty={qty:.8f} (notional=${notional:.2f})')
- return qty
- except Exception as e:
- logger.error(f'Quantity calculation error: {e}')
- return 0
-
- def format_quantity(self, qty: float, step_size: float) -> str:
- """Format quantity to proper decimal places - STRICTLY NO scientific notation"""
- try:
- if step_size is None or step_size <= 0:
- # Use Decimal for strict formatting
- from decimal import Decimal, ROUND_DOWN
- d = Decimal(str(qty))
- return str(d.quantize(Decimal('0.00000001'), rounding=ROUND_DOWN))
-
- if step_size >= 1:
- return str(int(qty))
-
- # Force no scientific notation using format string
- from decimal import Decimal, ROUND_DOWN
-
- # Determine decimal places from step_size
- step_str = str(step_size)
- if 'e' in step_str.lower():
- # Scientific notation in step_size
- decimal_places = 8
- elif '.' in step_str:
- decimal_places = len(step_str.split('.')[-1])
- else:
- decimal_places = 0
-
- decimal_places = max(decimal_places, 1)
- decimal_places = min(decimal_places, 20)
-
- # Round down to step_size
- multiplier = 10 ** decimal_places
- rounded_qty = int(qty * multiplier) / multiplier
-
- # Format without scientific notation
- formatted = f'{rounded_qty:.{decimal_places}f}'
-
- # Validate: no 'e' in result
- if 'e' in formatted.lower():
- logger.error(f'SCIENTIFIC NOTATION DETECTED: {qty} → {formatted}')
- return f'{rounded_qty:.8f}'
-
- logger.debug(f'Formatted: {qty} → {formatted} (step={step_size})')
- return formatted
- except Exception as e:
- logger.error(f'Format error: {e}')
- # Fallback: use Decimal
- from decimal import Decimal
- d = Decimal(str(qty))
- return str(d)
-
- def increment_error_count(self) -> bool:
- """Track error frequency — return True if limit reached"""
- self.error_count += 1
- self.last_error_time = time.time()
-
- if self.error_count >= self.error_threshold:
- self.error_cooldown_until = time.time() + 300
- logger.warning(f'🛑 Error threshold ({self.error_count}) reached! Pausing 5 minutes.')
- return True
- return False
-
- def reset_error_count(self):
- """Reset error counter if no errors in 60 seconds"""
- if time.time() - self.last_error_time > 60:
- if self.error_count > 0:
- logger.info(f'✅ Error counter reset (was {self.error_count})')
- self.error_count = 0
-
- async def liquidate_btc_to_usdt_on_startup(self):
- """ONE-TIME: Sell all BTC to USDT if USDT is too small"""
- pass # Disabled - use force_liquidate_all instead
-
- async def force_liquidate_all(self):
- """MANUAL LIQUIDATION: Force sell ALL holdings to USDT (Marc can trigger on demand)"""
- logger.warning('🔥 FORCE LIQUIDATION STARTED')
-
- results = {}
- try:
- balance = await self.binance.get_balance()
-
- # Liquidate BTC (round down to valid LOT_SIZE)
- btc_free = float(balance.get('BTC', {}).get('free', 0))
- if btc_free > 0.00001:
- try:
- btc_qty = int(btc_free * 100000) / 100000
- btc_qty = max(btc_qty, 0.00001)
- logger.info(f'📤 Selling {btc_qty:.8f} BTC')
- result = await self.binance.place_order('BTCUSDT', 'SELL', f'{btc_qty:.8f}', 'MARKET')
- if result:
- results['BTC'] = {'status': result.get('status'), 'qty': btc_qty}
- logger.info(f'✅ BTC SOLD: {result.get("status")}')
- except Exception as e:
- logger.error(f'BTC SELL failed: {e}')
- results['BTC'] = {'error': str(e)[:50]}
-
- # Liquidate other holdings
- for asset in ['ETH', 'SOL', 'BNB', 'XRP']:
- qty = float(balance.get(asset, {}).get('free', 0))
- if qty > 0.001:
- try:
- pair = f'{asset}USDT'
- logger.info(f'📤 Selling {qty:.8f} {asset}')
- result = await self.binance.place_order(pair, 'SELL', f'{qty:.8f}', 'MARKET')
- if result:
- results[asset] = {'status': result.get('status'), 'qty': qty}
- logger.info(f'✅ {asset} SOLD: {result.get("status")}')
- except Exception as e:
- logger.error(f'{asset} SELL failed: {e}')
- results[asset] = {'error': str(e)[:50]}
-
- await asyncio.sleep(2)
- new_balance = await self.binance.get_balance()
- new_usdt = float(new_balance.get('USDT', {}).get('free', 0))
- logger.warning(f'🔥 LIQUIDATION DONE! New USDT: ${new_usdt:.2f}')
-
- return {'status': 'success', 'results': results, 'new_usdt': new_usdt}
- except Exception as e:
- logger.error(f'Liquidation error: {e}')
- return {'status': 'error', 'message': str(e)[:100]}
-
- async def auto_swap_holdings_to_usdt(self):
- """ONE-TIME: Sell all BTC to USDT if USDT is too small"""
- try:
- balance = await self.binance.get_balance()
- btc_free = float(balance.get('BTC', {}).get('free', 0))
- usdt_free = float(balance.get('USDT', {}).get('free', 0))
- btc_price = 62500 # Approximate current price
-
- # If BTC > 0.001 AND USDT < $10, LIQUIDATE BTC
- if btc_free > 0.0001 and usdt_free < 10:
- logger.warning(f'🔄 STARTUP LIQUIDATION: Selling {btc_free:.8f} BTC (~${btc_free * btc_price:.2f}) to USDT...')
-
- try:
- # Use calculate_valid_quantity to respect LOT_SIZE
- valid_qty = self.calculate_valid_quantity('BTCUSDT', btc_free * btc_price, btc_price)
-
- if valid_qty <= 0:
- logger.warning(f'⚠️ BTC qty too small after LOT_SIZE check: {valid_qty}')
- return
-
- # IMPORTANT: Don't sell MORE than we actually have!
- valid_qty = min(valid_qty, btc_free)
- logger.info(f'📤 Placing SELL: {valid_qty:.8f} BTC (actual balance: {btc_free:.8f})')
- result = await self.binance.place_order(
- symbol='BTCUSDT',
- side='SELL',
- quantity=f'{valid_qty:.8f}',
- order_type='MARKET'
- )
-
- if result and result.get('status') in ['FILLED', 'NEW', 'PARTIALLY_FILLED']:
- # Ensure current_trades reflects completed trade
- logger.info(f'✅ BTC LIQUIDATED! Order ID: {result.get("orderId")}, Status: {result.get("status")}')
- # Wait for balance to update
- await asyncio.sleep(3)
-
- new_balance = await self.binance.get_balance()
- new_usdt = float(new_balance.get('USDT', {}).get('free', 0))
- new_btc = float(new_balance.get('BTC', {}).get('free', 0))
- logger.info(f'💰 After liquidation: USDT=${new_usdt:.2f}, BTC={new_btc:.8f}')
- except Exception as e:
- logger.error(f'❌ Liquidation order failed: {type(e).__name__}: {str(e)[:100]}')
- except Exception as e:
- logger.error(f'Liquidation check failed: {e}')
-
- async def auto_swap_periodically(self):
- """Periodically swap small holdings back to USDT for liquidity"""
- try:
- if time.time() - self.last_swap_time < 300:
- return
-
- if time.time() < self.error_cooldown_until:
- logger.debug('⏸️ Error cooldown active — skipping swap')
- return
-
- balance = await self.binance.get_balance()
- if not balance:
- logger.warning('No balance data for swap')
- return
-
- swapped_any = False
-
- for asset in ['BTC', 'ETH', 'SOL', 'BNB', 'XRP']:
- try:
- qty = float(balance.get(asset, {}).get('free', 0))
-
- if qty <= 0.00001:
- continue
-
- pair = f'{asset}USDT'
-
- # FIX: Robust price fetching with error handling
- try:
- price = await self.binance.get_ticker_price(pair)
- if price is None or price <= 0:
- logger.warning(f'⚠️ Invalid price for {pair}: {price} — skipping swap')
- continue
- except Exception as e:
- logger.warning(f'Failed to get price for {pair}: {e}')
- continue
-
- notional = qty * price
-
- # Only swap if in safe range
- if notional < 5 or notional > 15:
- logger.debug(f'⏸️ {pair} notional ${notional:.2f} outside swap range [5-15]')
- continue
-
- logger.info(f'🔄 ATTEMPTING SWAP: {qty:.8f} {asset} (${notional:.2f}) → USDT @ ${price:.2f}')
-
- try:
- if pair not in self.symbol_info:
- await self.load_exchange_info()
-
- if pair not in self.symbol_info:
- logger.warning(f'No symbol info for {pair} — skipping')
- continue
-
- step_size = self.symbol_info[pair].get('stepSize', 1e-8)
- qty_str = self.format_quantity(qty, step_size)
-
- logger.info(f'📤 Placing SWAP SELL: {qty_str} {pair} @ ${price:.2f}')
- result = await self.binance.place_order(
- symbol=pair,
- side='SELL',
- quantity=qty_str, # Pass STRING
- order_type='MARKET'
- )
-
- if result:
- # SWAP Alert disabled — user only wants profit notifications
- pass
- swapped_any = True
- self.error_count = 0 # Reset errors on success
- logger.info(f'✅ SWAP EXECUTED!')
- else:
- logger.warning(f'SWAP order returned no result for {pair}')
-
- except Exception as e:
- logger.warning(f'Swap order failed for {asset}: {e}')
- self.increment_error_count()
- continue
-
- except Exception as e:
- logger.warning(f'Swap check error for {asset}: {e}')
- continue
-
- if swapped_any:
- self.last_swap_time = time.time()
-
- except Exception as e:
- logger.error(f'Auto-swap error: {e}')
-
- async def check_take_profit(self):
- """Check all positions for exits"""
- try:
- balance = await self.binance.get_balance()
- if not balance:
- return
-
- positions_to_close = []
-
- for pair in self.pairs:
- try:
- asset = pair.replace('USDT', '')
- current_qty = float(balance.get(asset, {}).get('free', 0))
-
- if current_qty <= 0.00001:
- continue
-
- # FIX: Robust price fetching
- try:
- current_price = await self.binance.get_ticker_price(pair)
- if current_price is None or current_price <= 0:
- logger.debug(f'⚠️ Invalid price for {pair}: {current_price}')
- continue
- except Exception as e:
- logger.warning(f'Failed to get price for {pair}: {e}')
- continue
-
- if pair not in self.current_trades:
- logger.debug(f'⏭️ {pair} not in open_positions (keys: {list(self.current_trades.keys())})')
- continue
-
- pos = self.current_trades[pair]
- buy_price = pos['buy_price']
- buy_qty = pos['qty']
- buy_time = datetime.fromisoformat(pos['buy_time'])
-
- profit_pct = ((current_price - buy_price) / buy_price) * 100
- hold_time_minutes = (datetime.now() - buy_time).total_seconds() / 60
-
- if profit_pct > pos.get('peak_profit', 0):
- pos['peak_profit'] = profit_pct
-
- exit_reason = None
-
- # 1. STOP-LOSS
- if profit_pct <= -3.0:
- exit_reason = "STOP_LOSS"
- logger.warning(f'🛑 {pair}: STOP-LOSS triggered! {profit_pct:.2f}%')
- positions_to_close.append((pair, current_qty, current_price, buy_price, buy_qty, profit_pct, exit_reason))
-
- # 2. MAX HOLD TIME
- elif hold_time_minutes >= 240:
- exit_reason = "MAX_HOLD_TIMEOUT"
- logger.info(f'⏱️ {pair}: MAX_HOLD_TIME reached! {hold_time_minutes:.0f} min')
- positions_to_close.append((pair, current_qty, current_price, buy_price, buy_qty, profit_pct, exit_reason))
-
- # 3. TAKE PROFIT
- elif profit_pct >= 1.0:
- exit_reason = "TAKE_PROFIT"
- logger.info(f'💰 {pair}: TAKE_PROFIT reached! +{profit_pct:.2f}%')
- positions_to_close.append((pair, current_qty, current_price, buy_price, buy_qty, profit_pct, exit_reason))
-
- # 4. TRAILING STOP
- elif pos['peak_profit'] >= 1.0:
- trailing_stop_level = pos['peak_profit'] - 0.4
- if profit_pct <= trailing_stop_level:
- exit_reason = "TRAILING_STOP"
- logger.info(f'📉 {pair}: TRAILING_STOP triggered! Peak: {pos["peak_profit"]:.2f}%, Current: {profit_pct:.2f}%')
- positions_to_close.append((pair, current_qty, current_price, buy_price, buy_qty, profit_pct, exit_reason))
-
- except Exception as e:
- logger.warning(f'Check exit for {pair} failed: {e}')
- continue
-
- # Execute all closes
- for pair, current_qty, current_price, buy_price, buy_qty, profit_pct, exit_reason in positions_to_close:
- try:
- # Format quantity BEFORE placing order (keep as STRING!)
- if pair in self.symbol_info:
- step_size = self.symbol_info[pair].get('stepSize', 1e-8)
- qty_str = self.format_quantity(current_qty, step_size)
- else:
- qty_str = f'{current_qty:.8f}'
-
- logger.info(f'📤 Placing EXIT order: {qty_str} {pair} (Reason: {exit_reason})')
- result = await self.binance.place_order(
- symbol=pair,
- side='SELL',
- quantity=qty_str, # Pass STRING
- order_type='MARKET'
- )
-
- if result and result.get('status') in ['FILLED', 'NEW', 'PARTIALLY_FILLED']:
- # Ensure current_trades reflects completed trade
- # ONLY record if order was actually EXECUTED
- profit_usd = (current_qty * current_price) - (buy_qty * buy_price)
- self.daily_pnl += profit_usd
- self.total_pnl += profit_usd
- self.total_trades += 1
-
- if profit_pct >= 0:
- self.wins_today += 1
- icon = "✅"
- else:
- self.losses_today += 1
- icon = "❌"
-
- # Only send Telegram alert if PROFITABLE (profit_pct >= 0)
- if profit_pct >= 0:
- await self.telegram.send_alert(
- f'{icon} CLOSED {exit_reason}\n'
- f'{pair}: {current_qty:.8f} @ ${current_price:.2f}\n'
- f'Profit: ${profit_usd:+.2f} ({profit_pct:+.2f}%)\n'
- f'Hold: {(datetime.now() - datetime.fromisoformat(self.current_trades[pair]["buy_time"])).total_seconds() / 60:.0f} min'
- )
- else:
- logger.info(f'Loss trade skipped from Telegram (visible on dashboard): {profit_pct:.2f}%')
-
- if self.daily_pnl < self.min_daily_pnl:
- self.min_daily_pnl = self.daily_pnl
- if abs(self.min_daily_pnl) > self.max_drawdown:
- self.max_drawdown = abs(self.min_daily_pnl)
-
- self.error_count = 0
-
- # Send to dashboard BEFORE deleting position (ONLY for REAL executed trades!)
- hold_time_s = (datetime.now() - datetime.fromisoformat(self.current_trades[pair]["buy_time"])).total_seconds()
- hold_time_min = hold_time_s / 60
- await self.dashboard.record_sell(pair, current_qty, current_price, profit_usd, profit_pct, hold_time_min)
-
- # ALSO record to completed_trades before deletion
- completed_trade = {
- 'pair': pair,
- 'entry_price': buy_price,
- 'exit_price': current_price,
- 'qty': current_qty,
- 'profit_usd': profit_usd,
- 'profit_pct': profit_pct,
- 'entry_time': self.current_trades[pair]['buy_time'],
- 'exit_time': datetime.now().isoformat(),
- 'hold_time_min': hold_time_min
- }
- # Send to dashboard's completed_trades
- try:
- await self.dashboard.update_state({'completed_trades': [completed_trade]})
- except:
- pass
-
- del self.current_trades[pair]
- logger.info(f'✅ EXIT EXECUTED & RECORDED TO DASHBOARD!')
- else:
- logger.warning(f'❌ EXIT order FAILED or returned no result for {pair} - NOT recording')
-
- except Exception as e:
- logger.warning(f'Exit order failed for {pair}: {e}')
- continue
-
- except Exception as e:
- logger.error(f'Take profit check error: {e}')
-
- async def find_best_trade(self):
- """Scan multiple pairs for best signal"""
- try:
- best_signal = {'pair': None, 'signal': 'HOLD'}
-
- for pair in self.pairs:
- try:
- # FIX: Robust price fetching
- try:
- price = await self.binance.get_ticker_price(pair)
- if price is None or price <= 0:
- logger.debug(f'⚠️ Invalid price for {pair}: {price}')
- continue
- except Exception as e:
- logger.debug(f'{pair} price fetch failed: {e}')
- continue
-
- # Get signal
- try:
- signal = self.strategy.predict(price) if hasattr(self.strategy, 'predict') else 'BUY'
- except Exception as e:
- logger.debug(f'{pair} signal generation failed: {e}')
- signal = 'HOLD'
-
- if signal == 'BUY':
- logger.info(f'🟢 BUY signal: {pair} at ${price:.2f}')
- return {'pair': pair, 'price': price, 'signal': signal}
-
- except Exception as e:
- logger.debug(f'{pair} scan failed: {e}')
- continue
-
- return best_signal
-
- except Exception as e:
- logger.error(f'Find trade error: {e}')
- return {'pair': None, 'signal': 'HOLD'}
-
- async def monitor_trades(self):
- """Main trade monitoring with error handling"""
- try:
- self.reset_error_count()
-
- if time.time() < self.error_cooldown_until:
- logger.info('⏸️ ERROR COOLDOWN ACTIVE — pausing 5 minutes')
- return
-
- # 1. Check for SELL opportunities
- await self.check_take_profit()
-
- # 2. Periodically swap small holdings
- await self.auto_swap_periodically()
-
- # 3. Get balance
- try:
- balance = await self.binance.get_balance()
- except Exception as e:
- logger.error(f'Balance fetch failed: {e}')
- self.increment_error_count()
- return
-
- usdt = float(balance.get('USDT', {}).get('free', 0)) if balance else 0
-
- # DEBUG: Log FULL balance
- logger.info(f'💰 Full Balance Breakdown:')
- for asset, amounts in balance.items():
- free = float(amounts.get('free', 0))
- locked = float(amounts.get('locked', 0))
- if free > 0 or locked > 0:
- logger.info(f' {asset}: FREE={free:.8f}, LOCKED={locked:.8f}, TOTAL={free+locked:.8f}')
- self.starting_capital = usdt
-
- logger.info(f'💰 Balance: {usdt:.2f} USDT | Daily P&L: ${self.daily_pnl:.2f}')
-
- # 4. Check DAILY LOSS LIMIT
- daily_loss_pct = (self.daily_pnl / self.starting_capital) * 100 if self.starting_capital > 0 else 0
-
- if daily_loss_pct <= -5.0:
- self.daily_loss_limit_reached = True
- logger.warning(f'🛑 Daily loss limit reached! ({daily_loss_pct:.1f}%) Pausing.')
- await self.telegram.send_alert(
- f'🛑 Daily Loss Limit reached!\n'
- f'P&L: ${self.daily_pnl:.2f} ({daily_loss_pct:.1f}%)\n'
- f'Bot paused until Midnight UTC'
- )
- return
-
- # Reset at midnight UTC
- now = datetime.utcnow()
- if now.date() > self.last_daily_reset:
- self.daily_loss_limit_reached = False
- self.daily_pnl = 0.0
- self.min_daily_pnl = 0.0
- self.trades_today = 0
- self.wins_today = 0
- self.losses_today = 0
- self.last_daily_reset = now.date()
- logger.info('🔄 Daily metrics reset at Midnight UTC')
- await self.telegram.send_alert('🔄 Daily reset complete — trading resumed')
-
- # 5. Find BUY signal
- if self.daily_loss_limit_reached:
- logger.info('⏸️ BUY orders paused (loss limit)')
- return
-
- try:
- trade = await self.find_best_trade()
- except Exception as e:
- logger.error(f'Find trade error: {e}')
- self.increment_error_count()
- return
-
- if trade['signal'] == 'BUY' and usdt >= 5:
- pair = trade['pair']
- price = trade['price']
-
- # Use ALL available capital (not just 50%!) to maximize first trade
- order_amount = usdt # Use 100% capital
- qty = self.calculate_valid_quantity(pair, order_amount, price)
-
- if qty <= 0:
- logger.debug(f'⚠️ No valid quantity for {pair}')
- return
-
- notional = qty * price
- logger.info(f'📈 Order: {qty:.8f} {pair} @ ${price:.2f} = ${notional:.2f}')
-
- # SKIP if notional is too small (Binance minimum ~$5 for testing)
- if notional < 5:
- logger.warning(f'⏭️ SKIP {pair}: notional ${notional:.2f} < $5 minimum')
- return
-
- try:
- # Format quantity BEFORE passing to API (keep as STRING!)
- if pair in self.symbol_info:
- step_size = self.symbol_info[pair].get('stepSize', 1e-8)
- qty_str = self.format_quantity(qty, step_size) # STRING!
- else:
- qty_str = f'{qty:.8f}'
-
- logger.info(f'📤 Placing BUY: {qty_str} {pair} @ ${price:.2f}')
- try:
- result = await self.binance.place_order(
- symbol=pair,
- side='BUY',
- quantity=qty_str, # Pass STRING
- order_type='MARKET'
- )
- logger.info(f'✅ Order result: {result}')
- except Exception as e:
- logger.error(f'❌ Order FAILED: {type(e).__name__}: {str(e)}')
- result = None
-
- # ONLY RECORD if order was SUCCESSFUL
- if result and (result.get('status') in ['FILLED', 'NEW', 'PARTIALLY_FILLED'] or order_type == 'MARKET'):
- # Record immediately — market orders always fill
- logger.info(f'🔍 DEBUG: Storing BUY position {pair}: qty={qty}, price={price}')
- self.current_trades[pair] = {
- 'qty': qty,
- 'buy_price': price,
- 'buy_time': datetime.now().isoformat(),
- 'peak_profit': 0.0,
- 'trailing_stop': None,
- 'order_id': result.get('orderId', 'unknown')
- }
- logger.info(f'🔍 DEBUG: After storage, open_positions keys = {list(self.current_trades.keys())}')
-
- self.trades_today += 1
- self.error_count = 0
-
- # BUY Alert disabled — user only wants profit notifications
- logger.info(f'✅ BUY FILLED & RECORDED! Order ID: {result.get("orderId", "unknown")}')
- # Ensure current_trades reflects completed trade
-
- # Send to dashboard
- await self.dashboard.record_buy(pair, qty, price)
- else:
- # Order FAILED - do NOT record
- logger.warning(f'❌ Order REJECTED or FAILED - NOT recording in open_positions')
-
- except Exception as e:
- logger.error(f'Trade execution failed: {e}')
- if self.increment_error_count():
- await self.telegram.send_alert('🛑 Too many errors! Bot paused 5 minutes')
-
- except Exception as e:
- logger.error(f'Monitor error: {e}')
- self.increment_error_count()
-
- async def send_performance_report(self):
- """Send detailed 3-hourly report"""
- try:
- self.report_count += 1
-
- balance = await self.binance.get_balance()
- usdt = float(balance.get('USDT', {}).get('free', 0)) if balance else 0
-
- total_assets_usd = usdt
- for asset in ['BTC', 'ETH', 'SOL', 'BNB', 'XRP']:
- try:
- qty = float(balance.get(asset, {}).get('free', 0)) if balance else 0
- if qty > 0:
- pair = f'{asset}USDT'
- try:
- price = await self.binance.get_ticker_price(pair)
- if price and price > 0:
- total_assets_usd += qty * price
- except:
- pass
- except:
- pass
-
- real_win_rate = (self.wins_today / (self.wins_today + self.losses_today) * 100) if (self.wins_today + self.losses_today) > 0 else 0
- avg_profit = (self.daily_pnl / (self.wins_today + self.losses_today)) if (self.wins_today + self.losses_today) > 0 else 0
- daily_loss_pct = (self.daily_pnl / self.starting_capital) * 100 if self.starting_capital > 0 else 0
-
- report = f'''📊 REPORT #{self.report_count}
-
-💹 PORTFOLIO:
- USDT: ${usdt:.2f} (CHF {usdt * 0.84:.2f})
- Total Assets: ${total_assets_usd:.2f} (CHF {total_assets_usd * 0.84:.2f})
-
-📈 TODAY'S PERFORMANCE:
- Trades: {self.trades_today}
- Wins: {self.wins_today} | Losses: {self.losses_today}
-
-📊 REAL METRICS:
- Win Rate: {real_win_rate:.1f}%
- Avg P/L per Trade: ${avg_profit:+.2f} (CHF {avg_profit * 0.84:+.2f})
- Daily P&L: ${self.daily_pnl:+.2f} (CHF {self.daily_pnl * 0.84:+.2f}) ({daily_loss_pct:+.1f}%)
- Max Drawdown: ${self.max_drawdown:.2f} (CHF {self.max_drawdown * 0.84:.2f})
-
-🤖 BOT STATUS: {'🟢 RUNNING' if time.time() >= self.error_cooldown_until else '🟡 ERROR_COOLDOWN'}
- Open Positions: {len(self.current_trades)}
- Error Count: {self.error_count}/{self.error_threshold}'''
-
- logger.info(report)
- await self.telegram.send_alert(report)
-
- # Send all metrics to dashboard
- balance = await self.binance.get_balance()
- usdt = float(balance.get('USDT', {}).get('free', 0)) if balance else 0
-
- total_assets_usd = usdt
- for asset in ['BTC', 'ETH', 'SOL', 'BNB', 'XRP']:
- try:
- qty = float(balance.get(asset, {}).get('free', 0)) if balance else 0
- if qty > 0:
- pair = f'{asset}USDT'
- price = await self.binance.get_ticker_price(pair)
- if price and price > 0:
- total_assets_usd += qty * price
- except:
- pass
-
- await self.dashboard.update_state(
- balance={'USDT': usdt},
- daily_pnl=self.daily_pnl,
- total_pnl=self.total_pnl,
- trades_today=self.trades_today,
- wins_today=self.wins_today,
- losses_today=self.losses_today,
- portfolio_value_usd=total_assets_usd
- )
-
- except Exception as e:
- logger.error(f'Report error: {e}')
-
- async def cancel_all_open_orders(self):
- """Cancel ALL open orders to free up locked capital"""
- try:
- logger.info('🗑️ CANCELLING ALL OPEN ORDERS...')
-
- # Get all open orders
- open_orders = await self.binance._get('openOrders')
-
- if not open_orders:
- logger.info('✅ No open orders to cancel')
- return True
-
- logger.warning(f'⚠️ Found {len(open_orders)} open orders!')
-
- cancelled_count = 0
- for order in open_orders:
- try:
- symbol = order.get('symbol')
- order_id = order.get('orderId')
- side = order.get('side')
- qty = order.get('origQty')
-
- logger.warning(f' Cancelling: {symbol} {side} {qty} (Order {order_id})')
-
- result = await self.binance._delete(
- 'order',
- True,
- symbol=symbol,
- orderId=order_id
- )
-
- cancelled_count += 1
- logger.info(f' ✅ Cancelled: {symbol} {order_id}')
-
- except Exception as e:
- logger.error(f' ❌ Failed to cancel {symbol} {order_id}: {e}')
- continue
-
- logger.info(f'✅ CANCELLATION COMPLETE: {cancelled_count}/{len(open_orders)} orders cancelled')
- return True
-
- except Exception as e:
- logger.error(f'❌ Failed to cancel orders: {e}')
- return False
-
- async def run(self):
- """Main bot loop"""
- logger.info('🤖 BOT STARTED (V5 - SUSTAINABLE)')
-
- # DISABLED: Recovery code was causing infinite loop
- # Auto-recover open positions from Binance on restart
-
- # STARTUP: Load open orders from Binance so Bot knows its positions
-
- # THIRD: ONE-TIME LIQUIDATE BTC TO USDT IF NEEDED
- await self.liquidate_btc_to_usdt_on_startup()
- logger.info('🗑️ RESET: Clearing dashboard cache...')
- try:
- await self.dashboard.clear_state()
- except:
- pass
-
- await self.telegram.send_alert(
- '🤖 BOT V5 SUSTAINABLE STARTED\n'
- '✅ All Bugs Fixed:\n'
- ' • Price fetching robust\n'
- ' • SWAP errors handled\n'
- ' • BUY orders executing\n'
- ' • EXIT orders scheduled\n'
- ' • Error resilience active'
- )
-
- while True:
- try:
- # CHECK FOR LIQUIDATION TRIGGER FILE (every cycle)
- trigger_file = '/tmp/bot_liquidate_trigger'
- trigger_exists = os.path.exists(trigger_file)
- logger.info(f'🔍 Checking trigger file: exists={trigger_exists}') # DEBUG
-
- if trigger_exists:
- logger.warning(f'🔥🔥🔥 LIQUIDATION TRIGGER DETECTED! File exists at: {trigger_file}')
- try:
- os.remove(trigger_file)
- logger.warning(f'🔥 Removed trigger file')
- except Exception as e:
- logger.error(f'Could not remove trigger: {e}')
- result = await self.force_liquidate_all()
- logger.warning(f'🔥 Force liquidation result: {result}')
- await asyncio.sleep(2)
-
- current_time = time.time()
-
- # KONTINUIERLICH: Update dashboard mit aktueller Balance (every cycle!)
- try:
- balance = await self.binance.get_balance()
- usdt_live = float(balance.get('USDT', {}).get('free', 0)) if balance else 0
-
- # Calculate portfolio value with REAL prices from symbol_info
- portfolio_value_usd = usdt_live # Start with USDT
-
- for pair in self.pairs:
- asset = pair.replace('USDT', '')
- if asset in balance:
- qty = float(balance[asset].get('free', 0))
- if qty > 0 and pair in self.symbol_info:
- # Use current price from symbol_info or last known
- try:
- current_price = await self.binance.get_ticker_price(pair)
- if current_price and current_price > 0:
- portfolio_value_usd += qty * current_price
- except:
- pass
-
- logger.info(f'✅ Sending to dashboard: USDT={usdt_live:.2f}, portfolio={portfolio_value_usd:.2f}, trades_today={self.trades_today}, open_trades={len(self.current_trades)}')
-
- # SYNC open_positions with dashboard
- async with aiohttp.ClientSession() as session:
- async with session.post('http://localhost:7000/api/update', json={
- 'balance': {'USDT': usdt_live},
- 'current_trades': self.current_trades, # Send ALL open positions!
- 'daily_pnl': self.daily_pnl,
- 'total_pnl': self.total_pnl,
- 'trades_today': self.trades_today,
- 'wins_today': self.wins_today,
- 'losses_today': self.losses_today,
- 'portfolio_value_usd': portfolio_value_usd,
- 'portfolio_value_chf': portfolio_value_usd * 0.84,
- 'last_update': datetime.now().isoformat()
- }) as resp:
- if resp.status == 200:
- logger.debug('✅ Dashboard state synced')
- else:
- logger.warning(f'Dashboard sync failed: {resp.status}')
-
- # Keep the old update_state call for compatibility
- await self.dashboard.update_state(
- balance={'USDT': usdt_live},
- daily_pnl=self.daily_pnl,
- portfolio_value_usd=portfolio_value_usd,
- total_pnl=self.total_pnl,
- trades_today=self.trades_today,
- wins_today=self.wins_today,
- losses_today=self.losses_today
- )
- except Exception as e:
- logger.warning(f'⚠️ Dashboard update error: {e}')
-
- now = datetime.now()
- should_report = (now.hour in [22, 1, 4, 7, 10, 13, 16, 19]) and now.minute == 0
-
- if should_report and (current_time - self.last_report_time) > 60:
- await self.send_performance_report()
- self.last_report_time = current_time
-
- await self.monitor_trades()
- await asyncio.sleep(10) # Check every 10 seconds for trigger and trading signals
-
- except Exception as e:
- logger.error(f'Main loop error: {e}')
- await asyncio.sleep(60)
-
-async def main():
- logger.info('▶️ MAIN STARTUP')
- try:
- config = get_config()
- logger.info(f'✅ Config loaded')
-
- try:
- dashboard = DashboardClient()
- logger.info(f'✅ Dashboard client initialized')
- except Exception as e:
- logger.error(f'❌ Dashboard init failed: {e}')
- dashboard = None
-
- binance = BinanceClientWrapper(
- api_key=config.binance_api_key_live,
- api_secret=config.binance_api_secret_live,
- testnet=False
- )
- logger.info(f'✅ Binance client initialized')
-
- telegram = TelegramNotifier(bot_token=config.telegram_bot_token, chat_id=config.telegram_chat_id)
- obsidian = ObsidianLogger(vault_path=config.obsidian_vault_path, trade_log_file=config.obsidian_trade_log_file)
-
- model = joblib.load(config.model_path) if hasattr(config, 'model_path') else None
- scaler = None
-
- logger.info(f'✅ BOT CREATING...')
- bot = MLTradingBot(config, binance, telegram, obsidian, model, scaler)
-
- logger.info(f'✅ BOT CREATED. STARTING RUN()...')
- await bot.run()
- except Exception as e:
- logger.critical(f'❌ FATAL ERROR IN MAIN: {e}', exc_info=True)
-
-if __name__ == '__main__':
- asyncio.run(main())
-# Version marker: Auto-sync test Sat Jul 4 10:37:55 UTC 2026
diff --git a/src/main_ml_BACKUP_before_precision_fix.py b/src/main_ml_BACKUP_before_precision_fix.py
deleted file mode 100644
index d44a05b..0000000
--- a/src/main_ml_BACKUP_before_precision_fix.py
+++ /dev/null
@@ -1,220 +0,0 @@
-#!/usr/bin/env python3
-"""
-Trading Bot V5 ENHANCED - Mit kritischen Risk Management Fixes
-Implementiert: SL, TP Anpassung, Daily Limit, R:R Ratio
-"""
-import os, asyncio, logging, random, json, time
-from datetime import datetime, timedelta
-from binance.client import Client
-from binance.exceptions import BinanceAPIException
-
-logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-# Load config
-env = {}
-with open('/home/marc/bot-deploy/.env') as f:
- for line in f:
- k, _, v = line.partition('=')
- env[k.strip()] = v.strip()
-
-class TradingBotV5Enhanced:
- def __init__(self):
- self.binance = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
- self.state_file = '/home/marc/bot-deploy/trades.json'
- self.load_state()
-
- # NEW: Risk Management Settings
- self.STOP_LOSS_PERCENT = 2.5 # 2.5% SL (-2.5%)
- self.TAKE_PROFIT_PERCENT = 3.0 # 3.0% TP (+3%) - was +1%
- self.DAILY_LOSS_LIMIT = 5.0 # Max -5% daily
- self.MIN_RISK_REWARD = 1.5 # Min R:R ratio
- self.MAX_POSITION_PERCENT = 25 # Max 25% per trade
-
- logger.info("✅ Bot initialized with Risk Management (SL 2.5%, TP 3%, Daily Limit 5%)")
-
- def load_state(self):
- if os.path.exists(self.state_file):
- with open(self.state_file) as f:
- self.state = json.load(f)
- else:
- self.state = {'current': {}, 'completed': [], 'daily_start_balance': 0}
-
- def save_state(self):
- with open(self.state_file, 'w') as f:
- json.dump(self.state, f, indent=2)
-
- def check_and_place_sl_orders(self, pair, qty, entry_price):
- """
- NEW: Automatically place Stop Loss orders for existing positions
- SL = Entry - 2.5%
- """
- sl_price = entry_price * (1 - self.STOP_LOSS_PERCENT / 100)
-
- try:
- # Check if already has SL order
- orders = self.binance.get_open_orders(symbol=pair)
- has_sl = any(o['side'] == 'SELL' and float(o['price']) < entry_price for o in orders)
-
- if not has_sl:
- # Place SL order
- order = self.binance.order_limit_sell(
- symbol=pair,
- quantity=qty,
- price=round(sl_price, 8)
- )
- logger.info(f"🛡️ Stop Loss set: {pair} {qty} @ ${sl_price:.4f}")
- return True
- except Exception as e:
- logger.error(f"SL Error {pair}: {e}")
-
- return False
-
- def place_buy(self, pair):
- """Place market buy with Risk Management checks"""
- try:
- # Get balance
- balance = self.binance.get_account()
- usdt_free = float([a['free'] for a in balance['balances'] if a['asset'] == 'USDT'][0])
-
- # NEW: Daily loss check
- daily_loss = self.calculate_daily_loss()
- if daily_loss <= -self.DAILY_LOSS_LIMIT:
- logger.warning(f"⛔ Daily loss limit hit: {daily_loss:.2f}% (limit: -{self.DAILY_LOSS_LIMIT}%)")
- return None
-
- # Calculate position size (25% of USDT)
- qty_usdt = usdt_free * (self.MAX_POSITION_PERCENT / 100)
-
- if qty_usdt < 10: # Binance minimum
- return None
-
- # Get current price
- ticker = self.binance.get_symbol_info(pair)
- price = float(self.binance.get_ticker(symbol=pair)['lastPrice'])
-
- # Calculate quantity with LOT_SIZE filter
- lot_filter = next(f for f in ticker['filters'] if f['filterType'] == 'LOT_SIZE')
- step_size = float(lot_filter['stepSize'])
- qty = float(int(qty_usdt / price / step_size) * step_size)
-
- if qty < float(lot_filter['minQty']):
- return None
-
- # Place market buy
- order = self.binance.order_market_buy(symbol=pair, quantity=qty)
- logger.info(f"🟢 BUY: {pair} x{qty:.6f} @ ${price:.4f}")
-
- # NEW: Auto-place Stop Loss
- self.check_and_place_sl_orders(pair, qty, price)
-
- return order
-
- except Exception as e:
- logger.error(f"Buy Error {pair}: {e}")
- return None
-
- def check_take_profit(self):
- """NEW: Check and close at +3% TP with SL protection"""
- try:
- balance = self.binance.get_account()
-
- for pair in ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']:
- ticker = self.binance.get_ticker(symbol=pair)
- current_price = float(ticker['lastPrice'])
-
- # Check if we have open trade
- if pair in self.state['current']:
- entry_price = self.state['current'][pair]['buy_price']
- gain_percent = (current_price - entry_price) / entry_price * 100
-
- # TP at +3%
- if gain_percent >= self.TAKE_PROFIT_PERCENT:
- qty = self.state['current'][pair]['qty']
- try:
- order = self.binance.order_market_sell(symbol=pair, quantity=qty)
- profit_usd = (current_price - entry_price) * qty
- logger.info(f"💰 TP HIT: {pair} +{gain_percent:.2f}% = ${profit_usd:.2f}")
-
- # Record completion
- self.state['completed'].append({
- 'pair': pair,
- 'qty': qty,
- 'buy_price': entry_price,
- 'sell_price': current_price,
- 'profit_percent': gain_percent,
- 'profit_usd': profit_usd
- })
- del self.state['current'][pair]
- self.save_state()
- except Exception as e:
- logger.error(f"TP sell error {pair}: {e}")
-
- # SL at -2.5% (auto-cancelled by limit order but check anyway)
- elif gain_percent <= -self.STOP_LOSS_PERCENT:
- qty = self.state['current'][pair]['qty']
- try:
- order = self.binance.order_market_sell(symbol=pair, quantity=qty)
- loss_usd = (current_price - entry_price) * qty
- logger.warning(f"🛑 SL HIT: {pair} {gain_percent:.2f}% = ${loss_usd:.2f}")
-
- self.state['completed'].append({
- 'pair': pair,
- 'qty': qty,
- 'buy_price': entry_price,
- 'sell_price': current_price,
- 'profit_percent': gain_percent,
- 'profit_usd': loss_usd
- })
- del self.state['current'][pair]
- self.save_state()
- except Exception as e:
- logger.error(f"SL sell error {pair}: {e}")
-
- except Exception as e:
- logger.error(f"TP check error: {e}")
-
- def calculate_daily_loss(self):
- """Calculate daily loss percentage"""
- try:
- if not self.state['completed']:
- return 0
-
- today_trades = [t for t in self.state['completed']
- if datetime.fromisoformat(t.get('timestamp', datetime.now().isoformat())).date() == datetime.now().date()]
-
- daily_loss = sum(t.get('profit_usd', 0) for t in today_trades)
-
- balance = self.binance.get_account()
- portfolio = sum(float(a['free']) for a in balance['balances'])
-
- loss_percent = (daily_loss / portfolio * 100) if portfolio > 0 else 0
- return loss_percent
- except:
- return 0
-
- async def run(self):
- """Main trading loop"""
- logger.info("🚀 Trading Bot V5 ENHANCED started (SL+TP+DailyLimit)")
-
- while True:
- try:
- # Check exits first (TP/SL)
- self.check_take_profit()
-
- # Generate signal (5% probability)
- if random.random() < 0.05:
- pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
- for pair in pairs:
- if pair not in self.state['current']:
- self.place_buy(pair)
-
- await asyncio.sleep(5)
-
- except Exception as e:
- logger.error(f"Loop error: {e}")
- await asyncio.sleep(5)
-
-if __name__ == "__main__":
- bot = TradingBotV5Enhanced()
- asyncio.run(bot.run())
diff --git a/src/main_ml_enhanced.py b/src/main_ml_enhanced.py
deleted file mode 100644
index d44a05b..0000000
--- a/src/main_ml_enhanced.py
+++ /dev/null
@@ -1,220 +0,0 @@
-#!/usr/bin/env python3
-"""
-Trading Bot V5 ENHANCED - Mit kritischen Risk Management Fixes
-Implementiert: SL, TP Anpassung, Daily Limit, R:R Ratio
-"""
-import os, asyncio, logging, random, json, time
-from datetime import datetime, timedelta
-from binance.client import Client
-from binance.exceptions import BinanceAPIException
-
-logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-# Load config
-env = {}
-with open('/home/marc/bot-deploy/.env') as f:
- for line in f:
- k, _, v = line.partition('=')
- env[k.strip()] = v.strip()
-
-class TradingBotV5Enhanced:
- def __init__(self):
- self.binance = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
- self.state_file = '/home/marc/bot-deploy/trades.json'
- self.load_state()
-
- # NEW: Risk Management Settings
- self.STOP_LOSS_PERCENT = 2.5 # 2.5% SL (-2.5%)
- self.TAKE_PROFIT_PERCENT = 3.0 # 3.0% TP (+3%) - was +1%
- self.DAILY_LOSS_LIMIT = 5.0 # Max -5% daily
- self.MIN_RISK_REWARD = 1.5 # Min R:R ratio
- self.MAX_POSITION_PERCENT = 25 # Max 25% per trade
-
- logger.info("✅ Bot initialized with Risk Management (SL 2.5%, TP 3%, Daily Limit 5%)")
-
- def load_state(self):
- if os.path.exists(self.state_file):
- with open(self.state_file) as f:
- self.state = json.load(f)
- else:
- self.state = {'current': {}, 'completed': [], 'daily_start_balance': 0}
-
- def save_state(self):
- with open(self.state_file, 'w') as f:
- json.dump(self.state, f, indent=2)
-
- def check_and_place_sl_orders(self, pair, qty, entry_price):
- """
- NEW: Automatically place Stop Loss orders for existing positions
- SL = Entry - 2.5%
- """
- sl_price = entry_price * (1 - self.STOP_LOSS_PERCENT / 100)
-
- try:
- # Check if already has SL order
- orders = self.binance.get_open_orders(symbol=pair)
- has_sl = any(o['side'] == 'SELL' and float(o['price']) < entry_price for o in orders)
-
- if not has_sl:
- # Place SL order
- order = self.binance.order_limit_sell(
- symbol=pair,
- quantity=qty,
- price=round(sl_price, 8)
- )
- logger.info(f"🛡️ Stop Loss set: {pair} {qty} @ ${sl_price:.4f}")
- return True
- except Exception as e:
- logger.error(f"SL Error {pair}: {e}")
-
- return False
-
- def place_buy(self, pair):
- """Place market buy with Risk Management checks"""
- try:
- # Get balance
- balance = self.binance.get_account()
- usdt_free = float([a['free'] for a in balance['balances'] if a['asset'] == 'USDT'][0])
-
- # NEW: Daily loss check
- daily_loss = self.calculate_daily_loss()
- if daily_loss <= -self.DAILY_LOSS_LIMIT:
- logger.warning(f"⛔ Daily loss limit hit: {daily_loss:.2f}% (limit: -{self.DAILY_LOSS_LIMIT}%)")
- return None
-
- # Calculate position size (25% of USDT)
- qty_usdt = usdt_free * (self.MAX_POSITION_PERCENT / 100)
-
- if qty_usdt < 10: # Binance minimum
- return None
-
- # Get current price
- ticker = self.binance.get_symbol_info(pair)
- price = float(self.binance.get_ticker(symbol=pair)['lastPrice'])
-
- # Calculate quantity with LOT_SIZE filter
- lot_filter = next(f for f in ticker['filters'] if f['filterType'] == 'LOT_SIZE')
- step_size = float(lot_filter['stepSize'])
- qty = float(int(qty_usdt / price / step_size) * step_size)
-
- if qty < float(lot_filter['minQty']):
- return None
-
- # Place market buy
- order = self.binance.order_market_buy(symbol=pair, quantity=qty)
- logger.info(f"🟢 BUY: {pair} x{qty:.6f} @ ${price:.4f}")
-
- # NEW: Auto-place Stop Loss
- self.check_and_place_sl_orders(pair, qty, price)
-
- return order
-
- except Exception as e:
- logger.error(f"Buy Error {pair}: {e}")
- return None
-
- def check_take_profit(self):
- """NEW: Check and close at +3% TP with SL protection"""
- try:
- balance = self.binance.get_account()
-
- for pair in ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']:
- ticker = self.binance.get_ticker(symbol=pair)
- current_price = float(ticker['lastPrice'])
-
- # Check if we have open trade
- if pair in self.state['current']:
- entry_price = self.state['current'][pair]['buy_price']
- gain_percent = (current_price - entry_price) / entry_price * 100
-
- # TP at +3%
- if gain_percent >= self.TAKE_PROFIT_PERCENT:
- qty = self.state['current'][pair]['qty']
- try:
- order = self.binance.order_market_sell(symbol=pair, quantity=qty)
- profit_usd = (current_price - entry_price) * qty
- logger.info(f"💰 TP HIT: {pair} +{gain_percent:.2f}% = ${profit_usd:.2f}")
-
- # Record completion
- self.state['completed'].append({
- 'pair': pair,
- 'qty': qty,
- 'buy_price': entry_price,
- 'sell_price': current_price,
- 'profit_percent': gain_percent,
- 'profit_usd': profit_usd
- })
- del self.state['current'][pair]
- self.save_state()
- except Exception as e:
- logger.error(f"TP sell error {pair}: {e}")
-
- # SL at -2.5% (auto-cancelled by limit order but check anyway)
- elif gain_percent <= -self.STOP_LOSS_PERCENT:
- qty = self.state['current'][pair]['qty']
- try:
- order = self.binance.order_market_sell(symbol=pair, quantity=qty)
- loss_usd = (current_price - entry_price) * qty
- logger.warning(f"🛑 SL HIT: {pair} {gain_percent:.2f}% = ${loss_usd:.2f}")
-
- self.state['completed'].append({
- 'pair': pair,
- 'qty': qty,
- 'buy_price': entry_price,
- 'sell_price': current_price,
- 'profit_percent': gain_percent,
- 'profit_usd': loss_usd
- })
- del self.state['current'][pair]
- self.save_state()
- except Exception as e:
- logger.error(f"SL sell error {pair}: {e}")
-
- except Exception as e:
- logger.error(f"TP check error: {e}")
-
- def calculate_daily_loss(self):
- """Calculate daily loss percentage"""
- try:
- if not self.state['completed']:
- return 0
-
- today_trades = [t for t in self.state['completed']
- if datetime.fromisoformat(t.get('timestamp', datetime.now().isoformat())).date() == datetime.now().date()]
-
- daily_loss = sum(t.get('profit_usd', 0) for t in today_trades)
-
- balance = self.binance.get_account()
- portfolio = sum(float(a['free']) for a in balance['balances'])
-
- loss_percent = (daily_loss / portfolio * 100) if portfolio > 0 else 0
- return loss_percent
- except:
- return 0
-
- async def run(self):
- """Main trading loop"""
- logger.info("🚀 Trading Bot V5 ENHANCED started (SL+TP+DailyLimit)")
-
- while True:
- try:
- # Check exits first (TP/SL)
- self.check_take_profit()
-
- # Generate signal (5% probability)
- if random.random() < 0.05:
- pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
- for pair in pairs:
- if pair not in self.state['current']:
- self.place_buy(pair)
-
- await asyncio.sleep(5)
-
- except Exception as e:
- logger.error(f"Loop error: {e}")
- await asyncio.sleep(5)
-
-if __name__ == "__main__":
- bot = TradingBotV5Enhanced()
- asyncio.run(bot.run())
diff --git a/src/main_ml_fixed.py b/src/main_ml_fixed.py
deleted file mode 100644
index c984497..0000000
--- a/src/main_ml_fixed.py
+++ /dev/null
@@ -1,205 +0,0 @@
-#!/usr/bin/env python3
-"""
-Trading Bot V5 ENHANCED - Risk Management FIXED
-Implementiert: SL (mit korrekter Precision), TP, Daily Limit, R:R Ratio
-FIXED: PRICE_FILTER für SL Orders durch Tick-Rounding
-"""
-import os, asyncio, logging, random, json, time, math
-from binance.client import Client
-from binance.exceptions import BinanceAPIException
-from datetime import datetime, timedelta
-
-# Logging
-logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-# Load env
-env = {}
-with open('/home/marc/bot-deploy/.env') as f:
- for line in f:
- k,_,v = line.partition('=')
- env[k.strip()] = v.strip()
-
-class TradingBot:
- def __init__(self):
- self.client = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
-
- self.PAIRS = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
- self.SIGNAL_THRESHOLD = 5 # 5% random signal
- self.INVESTMENT_PERCENT = 25 # 25% per trade
- self.STOP_LOSS_PERCENT = 2.5 # -2.5%
- self.TAKE_PROFIT_PERCENT = 3.0 # +3%
- self.DAILY_LOSS_LIMIT = -5 # -5% max
-
- self.active_trades = {}
- self.daily_pnl = 0
- self.paused = False
-
- # Precision cache
- self.pair_precision = {}
- self._load_pair_precision()
-
- logger.info("✅ Bot initialized with Risk Management (SL 2.5%, TP 3%, Daily Limit 5%)")
-
- def _load_pair_precision(self):
- """Load Binance precision rules for each pair"""
- for pair in self.PAIRS:
- try:
- info = self.client.get_symbol_info(symbol=pair)
- for f in info['filters']:
- if f['filterType'] == 'PRICE_FILTER':
- tick = float(f['tickSize'])
- self.pair_precision[pair] = {
- 'tick': tick,
- 'decimals': self._get_decimals(tick)
- }
- except Exception as e:
- logger.error(f"Precision load {pair}: {e}")
-
- def _get_decimals(self, tick):
- """Get decimal places from tick size"""
- s = str(tick)
- if 'e' in s:
- return int(s.split('e-')[1]) if 'e-' in s else 0
- return len(s.split('.')[1]) if '.' in s else 0
-
- def _round_to_tick(self, price, pair):
- """Round price to Binance tick size"""
- tick = self.pair_precision.get(pair, {}).get('tick', 0.01)
- return round(price / tick) * tick
-
- async def signal_buy(self, pair):
- """Generate random 5% buy signal"""
- rand = random.randint(1, 100)
- return rand <= self.SIGNAL_THRESHOLD
-
- async def place_buy_order(self, pair):
- """Place market buy order"""
- try:
- # Get current price
- ticker = self.client.get_ticker(symbol=pair)
- entry_price = float(ticker['lastPrice'])
-
- # Calculate quantity
- account = self.client.get_account()
- usdt_balance = next((b['free'] for b in account['balances'] if b['asset'] == 'USDT'), 0)
- usdt = float(usdt_balance) * (self.INVESTMENT_PERCENT / 100)
-
- qty = usdt / entry_price
-
- # Place market buy
- order = self.client.order_market_buy(symbol=pair, quantity=qty)
- logger.info(f"🟢 BUY: {pair} x{qty:.6f} @ ${entry_price:.2f}")
-
- # Store trade
- self.active_trades[pair] = {
- 'entry': entry_price,
- 'qty': qty,
- 'time': datetime.now()
- }
-
- # Place SL order (FIXED WITH ROUNDING)
- await self.place_stop_loss(pair, entry_price, qty)
-
- return True
-
- except Exception as e:
- logger.error(f"Buy Error {pair}: {e}")
- return False
-
- async def place_stop_loss(self, pair, entry_price, qty):
- """Place stop loss order with correct precision"""
- try:
- # Calculate SL price with 2.5% loss
- sl_price = entry_price * (1 - self.STOP_LOSS_PERCENT / 100)
-
- # ROUND TO TICK SIZE (CRITICAL FIX!)
- sl_price = self._round_to_tick(sl_price, pair)
-
- # Place SL order
- order = self.client.order_take_profit(
- symbol=pair,
- side='SELL',
- type='STOP_LOSS',
- timeInForce='GTC',
- quantity=qty,
- stopPrice=sl_price,
- price=sl_price # Binance requires price = stopPrice for STOP_LOSS
- )
- logger.info(f"🛡️ SL: {pair} @ ${sl_price:.4f} (-{self.STOP_LOSS_PERCENT}%)")
-
- except BinanceAPIException as e:
- logger.error(f"SL Error {pair}: {e}")
-
- async def monitor_positions(self):
- """Monitor open positions for TP/SL"""
- try:
- account = self.client.get_account()
-
- for pair in self.active_trades.keys():
- ticker = self.client.get_ticker(symbol=pair)
- current = float(ticker['lastPrice'])
- entry = self.active_trades[pair]['entry']
-
- gain_percent = ((current - entry) / entry) * 100
-
- # Check TP
- if gain_percent >= self.TAKE_PROFIT_PERCENT:
- await self.close_position(pair, 'TP', current)
-
- # Check SL (secondary check)
- elif gain_percent <= -self.STOP_LOSS_PERCENT:
- await self.close_position(pair, 'SL', current)
-
- except Exception as e:
- logger.error(f"Monitor Error: {e}")
-
- async def close_position(self, pair, reason, current_price):
- """Close position"""
- if pair not in self.active_trades:
- return
-
- qty = self.active_trades[pair]['qty']
- entry = self.active_trades[pair]['entry']
- pnl = (current_price - entry) * qty
-
- logger.info(f"📊 {reason}: {pair} closed @ ${current_price:.2f}, PnL: ${pnl:.2f}")
-
- del self.active_trades[pair]
- self.daily_pnl += pnl
-
- # Check daily loss limit
- if self.daily_pnl <= self.DAILY_LOSS_LIMIT:
- logger.warning(f"⚠️ DAILY LOSS LIMIT REACHED: ${self.daily_pnl:.2f}")
- self.paused = True
-
- async def run_cycle(self):
- """Main trading cycle"""
- while True:
- try:
- # Check daily loss limit pause
- if self.paused:
- logger.info("⏸️ Bot PAUSED (daily loss limit reached)")
- await asyncio.sleep(60)
- continue
-
- # Signal generation
- for pair in self.PAIRS:
- if pair not in self.active_trades and await self.signal_buy(pair):
- await self.place_buy_order(pair)
-
- # Monitor positions
- await self.monitor_positions()
-
- await asyncio.sleep(5)
-
- except Exception as e:
- logger.error(f"Cycle Error: {e}")
- await asyncio.sleep(5)
-
-async def main():
- bot = TradingBot()
- await bot.run_cycle()
-
-if __name__ == '__main__':
- asyncio.run(main())
diff --git a/src/main_ml_v2.py b/src/main_ml_v2.py
deleted file mode 100644
index 073afab..0000000
--- a/src/main_ml_v2.py
+++ /dev/null
@@ -1,157 +0,0 @@
-import asyncio, logging, joblib, time
-from datetime import datetime
-from src.config import get_config
-from src.bot.binance_client import BinanceClientWrapper
-from src.integrations.telegram_notifier import TelegramNotifier
-from src.integrations.obsidian_logger import ObsidianLogger
-from src.strategies.ml_strategy import MLStrategy
-
-logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-class MLTradingBot:
- def __init__(self, config, binance, telegram, obsidian, model, scaler):
- self.config = config
- self.binance = binance
- self.telegram = telegram
- self.obsidian = obsidian
- self.model = model
- self.scaler = scaler
- self.strategy = MLStrategy(trading_pair=config.trading_pair)
-
- self.last_report_time = time.time()
- self.report_interval = 10800
- self.trades_today = 0
- self.wins_today = 0
- self.losses_today = 0
- self.daily_pnl = 0.0
- self.report_count = 0
-
- async def auto_swap_to_usdt(self):
- """Auto-swap holdings to USDT if needed"""
- try:
- balance = await self.binance.get_balance()
- usdt_free = float(balance.get('USDT', {}).get('free', 0)) if balance else 0
-
- # If low on USDT, sell any BTC/ETH/SOL holdings
- for crypto in ['BTC', 'ETH', 'SOL']:
- crypto_balance = float(balance.get(crypto, {}).get('free', 0)) if balance else 0
- if usdt_free < 20 and crypto_balance > 0.0001:
- pair = crypto + 'USDT'
- logger.info(f'SWAP: Selling {crypto_balance:.6f} {crypto} for USDT')
- try:
- await self.binance.place_order(pair, 'SELL', 'MARKET', crypto_balance * 0.95)
- await self.telegram.send_alert(f'SWAP: Sold {crypto_balance:.6f} {crypto}')
- return True
- except Exception as e:
- logger.error(f'Swap failed: {e}')
- except Exception as e:
- logger.error(f'Auto-swap error: {e}')
- return False
-
- async def find_best_trade(self):
- """Scan multiple pairs for best signal"""
- pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
-
- for pair in pairs:
- try:
- price = await self.binance.get_ticker_price(pair)
- signal = self.strategy.predict(price) if hasattr(self.strategy, 'predict') else 'HOLD'
-
- if signal == 'BUY':
- logger.info(f'BUY signal: {pair} at {price:.2f}')
- return {'pair': pair, 'price': price, 'signal': signal}
-
- except Exception as e:
- logger.debug(f'{pair}: {e}')
-
- return {'pair': None, 'signal': 'HOLD'}
-
- async def monitor_trades(self):
- """Monitor & execute trades"""
- try:
- balance = await self.binance.get_balance()
- usdt = float(balance.get('USDT', {}).get('free', 0)) if balance else 0
-
- # Auto-swap if needed
- if usdt < 15:
- await self.auto_swap_to_usdt()
- return
-
- # Find best trade
- trade = await self.find_best_trade()
-
- if trade['signal'] == 'BUY' and usdt > 15:
- pair = trade['pair']
- price = trade['price']
- qty = (usdt * 0.7) / price
-
- logger.info(f'EXECUTE BUY: {qty:.6f} {pair} @ {price:.2f}')
- try:
- await self.binance.place_order(pair, 'BUY', 'MARKET', qty)
- self.trades_today += 1
- await self.telegram.send_alert(f'BUY {pair}\n{qty:.6f} @ {price:.2f}')
- except Exception as e:
- logger.error(f'Trade failed: {e}')
-
- except Exception as e:
- logger.debug(f'Monitor: {e}')
-
- async def send_performance_report(self):
- """Send 3-hourly report"""
- try:
- self.report_count += 1
- price = await self.binance.get_ticker_price(self.config.trading_pair)
- balance = await self.binance.get_balance()
- usdt = float(balance.get('USDT', {}).get('free', 0)) if balance else 0
-
- report = f'''REPORT #{self.report_count}
-BTC: {price:.2f}
-Balance: {usdt:.2f} USDT
-Trades: {self.trades_today}
-Wins: {self.wins_today}'''
-
- logger.info(report)
- await self.telegram.send_alert(report)
-
- except Exception as e:
- logger.error(f'Report error: {e}')
-
- async def run(self):
- """Main bot loop"""
- logger.info('BOT STARTED - Multi-Crypto Auto-Trading')
- await self.telegram.send_alert('BOT STARTED - Multi-Crypto Mode with Auto-Swap')
-
- while True:
- try:
- current_time = time.time()
-
- if (current_time - self.last_report_time) >= self.report_interval:
- await self.send_performance_report()
- self.last_report_time = current_time
-
- await self.monitor_trades()
- await asyncio.sleep(60)
-
- except Exception as e:
- logger.error(f'Bot error: {e}')
- await asyncio.sleep(60)
-
-async def main():
- config = get_config()
- binance = BinanceClientWrapper(
- api_key=config.binance_api_key_live,
- api_secret=config.binance_api_secret_live,
- testnet=False
- )
- telegram = TelegramNotifier(bot_token=config.telegram_bot_token, chat_id=config.telegram_chat_id)
- obsidian = ObsidianLogger(vault_path=config.obsidian_vault_path, trade_log_file=config.obsidian_trade_log_file)
-
- model = joblib.load(config.model_path) if hasattr(config, 'model_path') else None
- scaler = None
-
- bot = MLTradingBot(config, binance, telegram, obsidian, model, scaler)
- await bot.run()
-
-if __name__ == '__main__':
- asyncio.run(main())
diff --git a/src/main_ml_v4_backup.py b/src/main_ml_v4_backup.py
deleted file mode 100644
index 657417f..0000000
--- a/src/main_ml_v4_backup.py
+++ /dev/null
@@ -1,173 +0,0 @@
-#!/usr/bin/env python3
-import os, asyncio, aiohttp, logging, random
-from datetime import datetime
-from binance.client import Client
-from decimal import Decimal
-
-logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
-logger = logging.getLogger(__name__)
-
-with open("/home/marc/bot-deploy/.env") as f:
- env = {}
- for line in f:
- k, _, v = line.partition("=")
- env[k.strip()] = v.strip()
-
-class Bot:
- def __init__(self):
- self.binance = Client(env.get("BINANCE_API_KEY_LIVE"), env.get("BINANCE_API_SECRET_LIVE"))
- self.current_trades = {}
- self.completed_trades = []
- self.balance = {}
- self.trades_today = 0
- self.daily_pnl = 0.0
- self.dashboard = "http://localhost:7000/api/update"
- logger.info("🤖 Bot initialized")
-
- def get_balance(self):
- try:
- acc = self.binance.get_account()
- self.balance = {}
- for a in acc["balances"]:
- free, locked = float(a["free"]), float(a["locked"])
- if free + locked > 0:
- self.balance[a["asset"]] = {"free": free, "locked": locked, "total": free+locked}
- logger.info(f"💰 Balance updated: USDT")
- except Exception as e:
- logger.error(f"Balance error: {e}")
-
- def place_buy(self, pair):
- try:
- usdt_free = self.balance.get("USDT", {}).get("free", 0)
- if usdt_free < 5:
- return None
-
- # Use 25% per trade
- qty_usdt = usdt_free * 0.25
-
- ticker = self.binance.get_symbol_ticker(symbol=pair)
- price = float(ticker["price"])
-
- # Get symbol info for filters
- info = self.binance.get_symbol_info(pair)
- filters = {f["filterType"]: f for f in info["filters"]}
-
- # LOT_SIZE check
- if "LOT_SIZE" in filters:
- lot = filters["LOT_SIZE"]
- min_qty = float(lot["minQty"])
- step = float(lot["stepSize"])
-
- # Calculate quantity
- qty_calc = qty_usdt / price
-
- # Round down to step
- qty = round(qty_calc / step) * step
-
- if qty < min_qty or qty <= 0:
- return None
- else:
- qty = float(round(qty_usdt / price, 6))
-
- # Format as string to avoid scientific notation
- qty_str = f"{qty:.8f}".rstrip("0").rstrip(".")
-
- try:
- order = self.binance.order_market_buy(symbol=pair, quantity=qty_str)
- logger.info(f"🟢 BUY: {pair} x{qty_str}")
-
- self.current_trades[pair] = {
- "qty": float(qty_str),
- "buy_price": price,
- "buy_time": datetime.now().isoformat(),
- "order_id": order["orderId"]
- }
- self.trades_today += 1
- return order
- except Exception as e:
- logger.error(f"Buy {pair} error: {e}")
- return None
- except Exception as e:
- logger.error(f"place_buy error: {e}")
- return None
-
- def check_tp(self):
- remove = []
- for pair in list(self.current_trades.keys()):
- try:
- trade = self.current_trades[pair]
- ticker = self.binance.get_symbol_ticker(symbol=pair)
- current = float(ticker["price"])
-
- profit_pct = (current / trade["buy_price"]) - 1
-
- if profit_pct >= 0.01:
- logger.info(f"🎯 TP HIT: {pair} +{profit_pct*100:.2f}%")
-
- sell = self.binance.order_market_sell(symbol=pair, quantity=trade["qty"])
- sell_price = float(sell["fills"][0]["price"]) if sell.get("fills") else current
- profit = (sell_price - trade["buy_price"]) * trade["qty"]
-
- self.completed_trades.append({
- "pair": pair,
- "buy_price": trade["buy_price"],
- "sell_price": sell_price,
- "qty": trade["qty"],
- "profit_usd": profit,
- "profit_pct": profit_pct,
- "buy_time": trade["buy_time"],
- "sell_time": datetime.now().isoformat()
- })
-
- self.daily_pnl += profit
- remove.append(pair)
- except Exception as e:
- pass
-
- for p in remove:
- del self.current_trades[p]
-
- async def send_dashboard(self):
- try:
- state = {
- "current_trades": self.current_trades,
- "completed_trades": self.completed_trades[-20:],
- "balance": self.balance,
- "trades_today": self.trades_today,
- "daily_pnl": self.daily_pnl,
- "total_pnl": self.daily_pnl,
- "wins_today": len([t for t in self.completed_trades if t.get("profit_usd", 0) > 0]),
- "losses_today": len([t for t in self.completed_trades if t.get("profit_usd", 0) < 0]),
- "last_update": datetime.now().isoformat()
- }
- async with aiohttp.ClientSession() as s:
- async with s.post(self.dashboard, json=state, timeout=2) as r:
- pass
- except:
- pass
-
- async def run(self):
- logger.info("🎯 Bot started")
-
- while True:
- try:
- self.get_balance()
- self.check_tp()
-
- pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
-
- for pair in pairs:
- if pair not in self.current_trades and random.random() < 0.05:
- logger.info(f"🟢 Signal: {pair}")
- self.place_buy(pair)
-
- await self.send_dashboard()
- await asyncio.sleep(5)
-
- except Exception as e:
- logger.error(f"Run error: {e}")
- await asyncio.sleep(10)
-
-if __name__ == "__main__":
- bot = Bot()
- asyncio.run(bot.run())
diff --git a/src/main_ml_v6.py b/src/main_ml_v6.py
deleted file mode 100644
index 78c8abb..0000000
--- a/src/main_ml_v6.py
+++ /dev/null
@@ -1,200 +0,0 @@
-#!/usr/bin/env python3
-"""
-Trading Bot V5 CLEAN — Minimal, Reliable, Profitable
-Architecture: Single trading loop, live dashboard updates
-"""
-
-import os
-import asyncio
-import aiohttp
-from datetime import datetime
-from binance.client import Client
-from dotenv import load_dotenv
-import logging
-
-logging.basicConfig(
- level=logging.INFO,
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
-)
-logger = logging.getLogger(__name__)
-
-load_dotenv()
-
-class TradingBotClean:
- def __init__(self):
- self.binance = Client(
- os.getenv('BINANCE_API_KEY'),
- os.getenv('BINANCE_API_SECRET')
- )
- self.pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
-
- # Trading state - SINGLE SOURCE OF TRUTH
- self.current_trades = {}
- self.completed_trades = []
- self.balance = {}
- self.trades_today = 0
- self.daily_pnl = 0.0
- self.total_pnl = 0.0
- self.wins_today = 0
- self.losses_today = 0
-
- self.dashboard_url = 'http://localhost:7000/api/update'
- self.TP = 1.01
- self.SL = 0.97
- self.BUY_AMOUNT = 0.5
- self.MIN_ORDER = 10
-
- logger.info('🤖 Bot CLEAN initialized')
-
- async def update_balance(self):
- """Get current balance from Binance"""
- try:
- account = self.binance.get_account()
- self.balance = {}
- for asset in account['balances']:
- free = float(asset['free'])
- locked = float(asset['locked'])
- if free + locked > 0:
- self.balance[asset['asset']] = {
- 'free': free,
- 'locked': locked,
- 'total': free + locked
- }
- except Exception as e:
- logger.error(f'Balance error: {e}')
-
- async def get_ml_signal(self, pair, price):
- """Get ML trading signal"""
- import random
- return 'BUY' if random.random() > 0.95 else None
-
- async def place_buy_order(self, pair, price):
- """Place BUY order"""
- try:
- usdt_free = self.balance.get('USDT', {}).get('free', 0)
- qty_usdt = usdt_free * self.BUY_AMOUNT
-
- if qty_usdt < self.MIN_ORDER:
- return None
-
- qty = qty_usdt / price
- order = self.binance.order_market_buy(symbol=pair, quantity=qty)
-
- logger.info(f'🟢 BUY: {pair} x{qty:.4f} @ ${price:.2f}')
-
- self.current_trades[pair] = {
- 'qty': qty,
- 'buy_price': price,
- 'buy_time': datetime.now().isoformat(),
- 'order_id': order['orderId'],
- }
- self.trades_today += 1
-
- return order
-
- except Exception as e:
- logger.error(f'Buy error {pair}: {e}')
- return None
-
- async def check_take_profit(self):
- """Check for +1% take profit"""
- pairs_to_remove = []
-
- for pair in list(self.current_trades.keys()):
- try:
- trade = self.current_trades[pair]
- ticker = self.binance.get_symbol_ticker(symbol=pair)
- current_price = float(ticker['price'])
-
- profit_pct = (current_price / trade['buy_price']) - 1
-
- if profit_pct >= (self.TP - 1): # +1%
- logger.info(f'🎯 TP HIT: {pair} +{profit_pct*100:.2f}%')
-
- sell_order = self.binance.order_market_sell(symbol=pair, quantity=trade['qty'])
- sell_price = float(sell_order['fills'][0]['price']) if sell_order.get('fills') else current_price
- profit_usd = (sell_price - trade['buy_price']) * trade['qty']
-
- self.completed_trades.append({
- 'pair': pair,
- 'buy_price': trade['buy_price'],
- 'sell_price': sell_price,
- 'qty': trade['qty'],
- 'profit_usd': profit_usd,
- 'profit_pct': profit_pct,
- 'buy_time': trade['buy_time'],
- 'sell_time': datetime.now().isoformat()
- })
-
- self.daily_pnl += profit_usd
- self.total_pnl += profit_usd
- self.wins_today += 1
-
- pairs_to_remove.append(pair)
-
- except Exception as e:
- logger.warning(f'TP check error {pair}: {e}')
-
- for pair in pairs_to_remove:
- del self.current_trades[pair]
-
- async def send_to_dashboard(self):
- """Send state to dashboard"""
- try:
- state = {
- 'current_trades': self.current_trades,
- 'completed_trades': self.completed_trades[-20:],
- 'balance': self.balance,
- 'trades_today': self.trades_today,
- 'daily_pnl': self.daily_pnl,
- 'total_pnl': self.total_pnl,
- 'wins_today': self.wins_today,
- 'losses_today': self.losses_today,
- 'last_update': datetime.now().isoformat()
- }
-
- async with aiohttp.ClientSession() as session:
- async with session.post(self.dashboard_url, json=state, timeout=2) as resp:
- pass
- except Exception as e:
- logger.warning(f'Dashboard send error: {e}')
-
- async def run(self):
- """Main trading loop"""
- logger.info('🎯 Bot started')
-
- while True:
- try:
- await self.update_balance()
-
- for pair in self.pairs:
- if pair in self.current_trades:
- continue
-
- try:
- ticker = self.binance.get_symbol_ticker(symbol=pair)
- price = float(ticker['price'])
- signal = await self.get_ml_signal(pair, price)
-
- if signal == 'BUY':
- logger.info(f'🟢 BUY signal: {pair}')
- await self.place_buy_order(pair, price)
-
- except Exception as e:
- pass
-
- await self.check_take_profit()
- await self.send_to_dashboard()
-
- await asyncio.sleep(1)
-
- except Exception as e:
- logger.error(f'Loop error: {e}')
- await asyncio.sleep(5)
-
-async def main():
- bot = TradingBotClean()
- await bot.run()
-
-if __name__ == '__main__':
- asyncio.run(main())
diff --git a/src/persistence.py b/src/persistence.py
deleted file mode 100644
index 13e3185..0000000
--- a/src/persistence.py
+++ /dev/null
@@ -1,99 +0,0 @@
-#!/usr/bin/env python3
-"""
-Bot Persistence & Auto-Recovery System
-- Saves all trades to persistent storage (JSON)
-- On restart: Loads all trades + binance positions
-- Dashboard syncs with persistent storage
-- Bot operates autonomously even after restart
-"""
-
-import json
-import os
-import sys
-
-sys.path.insert(0, '/home/marc/bot-deploy')
-
-# Paths
-TRADES_FILE = '/home/marc/bot-deploy/data/trades_persistent.json'
-BOT_STATE_FILE = '/home/marc/bot-deploy/data/bot_state.json'
-DATA_DIR = '/home/marc/bot-deploy/data'
-
-# Ensure data directory exists
-os.makedirs(DATA_DIR, exist_ok=True)
-
-def init_persistence():
- """Initialize persistence files if they don't exist"""
- if not os.path.exists(TRADES_FILE):
- with open(TRADES_FILE, 'w') as f:
- json.dump({
- 'current_trades': {},
- 'completed_trades': [],
- 'swaps': []
- }, f, indent=2)
-
- if not os.path.exists(BOT_STATE_FILE):
- with open(BOT_STATE_FILE, 'w') as f:
- json.dump({
- 'last_restart': None,
- 'total_capital_deployed': 0.0,
- 'session_start': None
- }, f, indent=2)
-
-def load_persistent_trades():
- """Load trades from persistent storage"""
- try:
- with open(TRADES_FILE, 'r') as f:
- data = json.load(f)
- return data.get('current_trades', {}), data.get('completed_trades', []), data.get('swaps', [])
- except:
- return {}, [], []
-
-def save_persistent_trades(current_trades, completed_trades, swaps):
- """Save trades to persistent storage"""
- data = {
- 'current_trades': current_trades,
- 'completed_trades': completed_trades,
- 'swaps': swaps
- }
- with open(TRADES_FILE, 'w') as f:
- json.dump(data, f, indent=2)
-
-def load_binance_positions_on_startup():
- """Load current open positions from Binance on startup"""
- from src.bot.binance_client import BinanceClient
- import asyncio
-
- async def _load():
- client = BinanceClient()
- positions = {}
-
- # Get account balances
- balances = await client.get_balance()
-
- # Scan for open positions (non-zero balances excluding USDT)
- for symbol, amount in balances.items():
- if symbol != 'USDT' and amount > 0.00001:
- # Get current price for this asset
- price = await client.get_price(f'{symbol}USDT')
- positions[f'{symbol}USDT'] = {
- 'qty': amount,
- 'buy_price': price, # Current price as reference
- 'entry_time': None, # Lost on restart
- 'status': 'open'
- }
- print(f'✅ Loaded from Binance: {symbol}USDT - Qty: {amount} @ ${price}')
-
- return positions
-
- try:
- loop = asyncio.get_event_loop()
- except:
- loop = asyncio.new_event_loop()
- asyncio.set_event_loop(loop)
-
- return loop.run_until_complete(_load())
-
-# Initialize on import
-init_persistence()
-
-print('✅ Persistence module initialized')
diff --git a/src/report_generator.py b/src/report_generator.py
deleted file mode 100644
index 23d7abe..0000000
--- a/src/report_generator.py
+++ /dev/null
@@ -1,58 +0,0 @@
-#!/usr/bin/env python3
-import os, json, subprocess
-from datetime import datetime
-from binance.client import Client
-
-with open('/home/marc/bot-deploy/.env') as f:
- env = {}
- for line in f:
- k, _, v = line.partition('=')
- env[k.strip()] = v.strip()
-
-# Load bot state
-with open('/home/marc/bot-deploy/trades.json') as f:
- bot_state = json.load(f)
-
-# Get balance from Binance
-c = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
-acc = c.get_account()
-balance = {a['asset']: float(a['free']) for a in acc['balances']}
-
-# Calculate metrics
-portfolio_value = balance.get('USDT', 0)
-for asset in ['ETH', 'BTC', 'SOL', 'BNB', 'XRP']:
- if asset in balance:
- # Rough values (should use ticker for precision)
- prices = {'ETH': 1790, 'BTC': 63000, 'SOL': 83.5, 'BNB': 578, 'XRP': 2.5}
- portfolio_value += balance.get(asset, 0) * prices.get(asset, 0)
-
-completed = bot_state.get('completed', [])
-daily_pnl = sum(t.get('profit_usd', 0) for t in completed)
-wins = len([t for t in completed if t.get('profit_usd', 0) > 0])
-losses = len([t for t in completed if t.get('profit_usd', 0) < 0])
-
-# Format report
-timestamp = datetime.now().strftime('%Y-%m-%d %H:%M UTC')
-report = f'''📊 **TRADING BOT REPORT** — {timestamp}
-
-💰 **PORTFOLIO**
-• Total: ${portfolio_value:.2f}
-• USDT Free: ${balance.get('USDT', 0):.2f}
-• Open Trades: {len(bot_state.get('current', {}))}
-
-📈 **TODAY'S PERFORMANCE**
-• Trades: {len(completed)}
-• Wins: {wins} | Losses: {losses}
-• Win Rate: {(wins/(wins+losses)*100) if (wins+losses) > 0 else 0:.1f}%
-• Daily P&L: ${daily_pnl:.2f}
-
-🟢 **BOT STATUS**: OPERATIONAL
-🔗 Dashboard: https://bot.bizmark.cloud
-
----
-*Next report in 3 hours*
-'''
-
-# Send via Telegram using Hermes send_message
-print(report)
-
diff --git a/src/state_manager.py b/src/state_manager.py
deleted file mode 100644
index e9ec034..0000000
--- a/src/state_manager.py
+++ /dev/null
@@ -1,139 +0,0 @@
-#!/usr/bin/env python3
-"""
-State Manager V3: Ultra-Simple Binance Direct
-- Uses environment variables directly
-- No .env nonsense, uses os.environ
-"""
-
-import asyncio
-import json
-import logging
-import os
-import sys
-from datetime import datetime
-from pathlib import Path
-from fastapi import FastAPI
-from fastapi.middleware.cors import CORSMiddleware
-import uvicorn
-from binance.client import Client
-
-# Read .env directly into os.environ BEFORE importing anything else
-env_file = '/home/marc/bot-deploy/.env'
-for line in open(env_file).readlines():
- line = line.strip()
- if line and not line.startswith('#') and '=' in line:
- k, v = line.split('=', 1)
- os.environ[k] = v.strip('"').strip("'")
-
-API_KEY = os.environ.get('BINANCE_API_KEY')
-API_SECRET = os.environ.get('BINANCE_API_SECRET')
-
-logging.basicConfig(level=logging.INFO)
-logger = logging.getLogger(__name__)
-
-if not API_KEY or not API_SECRET:
- logger.error(f"Missing credentials: key={bool(API_KEY)}, secret={bool(API_SECRET)}")
- sys.exit(1)
-
-logger.info(f"✅ API credentials loaded")
-
-client = Client(API_KEY, API_SECRET)
-
-app = FastAPI()
-app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
-
-state = {
- 'current_trades': {},
- 'completed_trades': [],
- 'swaps': [],
- 'balance': {},
- 'portfolio_value_usd': 0.0,
- 'daily_pnl': 0.0,
- 'total_pnl': 0.0,
- 'last_sync': datetime.now().isoformat()
-}
-
-def load_from_binance():
- """Load real data from Binance"""
- global state
- try:
- logger.info('🔄 Syncing with Binance...')
-
- account = client.get_account()
- balances = {b['asset']: float(b['free']) for b in account['balances'] if float(b['free']) > 0.00001}
- state['balance'] = balances
- logger.info(f"Balance: USDT={balances.get('USDT', 0):.2f}")
-
- open_trades = {}
- for symbol in ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']:
- try:
- orders = client.get_open_orders(symbol=symbol)
- if orders:
- o = orders[0]
- qty = float(o['origQty'])
- buy_price = float(o['price'])
- current_price = float(client.get_symbol_ticker(symbol=symbol)['price'])
-
- profit = (current_price - buy_price) * qty
- profit_pct = ((current_price - buy_price) / buy_price * 100) if buy_price > 0 else 0
-
- open_trades[symbol] = {
- 'qty': qty,
- 'buy_price': buy_price,
- 'current_price': current_price,
- 'buy_time': datetime.fromtimestamp(o['time']/1000).isoformat(),
- 'profit': profit,
- 'profit_pct': profit_pct,
- 'order_id': o['orderId']
- }
- logger.info(f" {symbol}: {qty:.8f} → ${current_price:.2f} P&L: {profit_pct:.2f}%")
- except Exception as e:
- logger.debug(f"Error {symbol}: {e}")
-
- state['current_trades'] = open_trades
-
- usdt = balances.get('USDT', 0)
- portfolio = usdt + sum(t['qty']*t['current_price'] for t in open_trades.values())
- pnl = sum(t['profit'] for t in open_trades.values())
-
- state['portfolio_value_usd'] = portfolio
- state['daily_pnl'] = pnl
- state['total_pnl'] = pnl
- state['last_sync'] = datetime.now().isoformat()
-
- logger.info(f"✅ Portfolio: ${portfolio:.2f}, Trades: {len(open_trades)}, P&L: ${pnl:.2f}")
- return True
-
- except Exception as e:
- logger.error(f"❌ Error: {e}")
- import traceback
- traceback.print_exc()
- return False
-
-async def background_sync():
- while True:
- try:
- load_from_binance()
- await asyncio.sleep(10)
- except Exception as e:
- logger.error(f"Sync loop: {e}")
- await asyncio.sleep(10)
-
-@app.on_event("startup")
-async def startup():
- logger.info("🚀 Starting State Manager...")
- load_from_binance()
- asyncio.create_task(background_sync())
- logger.info("✅ Sync active")
-
-@app.get("/state")
-async def get_state():
- return state
-
-@app.get("/health")
-async def health():
- return {"status": "ok", "trades": len(state['current_trades']), "portfolio": state['portfolio_value_usd']}
-
-if __name__ == "__main__":
- logger.info("Starting on :8001")
- uvicorn.run(app, host="0.0.0.0", port=8001, log_level="error")
diff --git a/web_dashboard.py b/web_dashboard.py
deleted file mode 100644
index 163e94a..0000000
--- a/web_dashboard.py
+++ /dev/null
@@ -1,657 +0,0 @@
-#!/usr/bin/env python3
-from fastapi import FastAPI, Response
-from binance.client import Client
-import json, os, time
-from datetime import datetime
-
-app = FastAPI()
-
-env = {}
-with open('/home/marc/bot-deploy/.env') as f:
- for line in f:
- k,_,v = line.partition('=')
- env[k.strip()] = v.strip()
-
-binance = Client(env.get('BINANCE_API_KEY_LIVE'), env.get('BINANCE_API_SECRET_LIVE'))
-
-price_cache = {'prices': {}, 'timestamp': 0}
-
-def get_live_prices():
- global price_cache
- if time.time() - price_cache['timestamp'] < 5:
- return price_cache['prices']
-
- prices = {'USDT': 1.0}
- pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT']
- for pair in pairs:
- try:
- ticker = binance.get_ticker(symbol=pair)
- asset = pair.replace('USDT', '')
- prices[asset] = float(ticker['lastPrice'])
- except:
- pass
-
- price_cache['prices'] = prices
- price_cache['timestamp'] = time.time()
- return prices
-
-def load_bot_state():
- state_file = '/home/marc/bot-deploy/trades.json'
- if os.path.exists(state_file):
- try:
- with open(state_file) as f:
- return json.load(f)
- except:
- pass
- return {'current': {}, 'completed': [], 'balance': {}}
-
-@app.get('/api/state')
-async def get_state():
- try:
- account = binance.get_account()
- balance = {}
-
- for asset_data in account['balances']:
- asset = asset_data['asset']
- free = float(asset_data['free'])
- locked = float(asset_data['locked'])
- total = free + locked
-
- if total > 0.00001:
- balance[asset] = {
- 'free': free,
- 'locked': locked,
- 'total': total
- }
-
- prices = get_live_prices()
-
- portfolio_value = 0
- tracked_assets = ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'USDT', 'USDC']
-
- for asset in tracked_assets:
- if asset in balance:
- data = balance[asset]
- price = prices.get(asset, 0)
- portfolio_value += data['total'] * price
-
- usdt_free = balance.get('USDT', {}).get('free', 0)
-
- # P&L CALCULATION
- initial_capital = 137.79
- pnl_usdt = portfolio_value - initial_capital
- pnl_pct = (pnl_usdt / initial_capital * 100) if initial_capital > 0 else 0
- pnl_status = "🟢 PROFIT" if pnl_usdt > 0.01 else ("🔴 LOSS" if pnl_usdt < -0.01 else "⚪ BREAK")
- pnl_color = "accent" if pnl_usdt > 0.01 else ("negative" if pnl_usdt < -0.01 else "neutral")
-
- # Count active positions = locked coins (NOT trades.json)
- active_positions = 0
- for asset in ['BTC', 'ETH', 'SOL', 'BNB', 'XRP']:
- if asset in balance and balance[asset]['locked'] > 0.00001:
- active_positions += 1
-
- trades = load_bot_state()
-
- return {
- 'balance': balance,
- 'portfolio_value': round(portfolio_value, 2),
- 'usdt_free': round(usdt_free, 2),
- 'active_positions': active_positions, # ← NEW: Real count!
- 'current_trades': trades.get('current', {}),
- 'pnl_usdt': round(pnl_usdt, 2),
- 'pnl_pct': round(pnl_pct, 2),
- 'pnl_status': pnl_status,
- 'pnl_color': pnl_color,
- 'completed_trades': trades.get('completed', []),
- 'prices': prices,
- 'timestamp': datetime.now().isoformat()
- }
- except Exception as e:
- return {'error': str(e), 'portfolio_value': 0, 'usdt_free': 0, 'active_positions': 0}
-
-@app.get('/')
-async def root():
- state = await get_state()
- portfolio_val = state.get('portfolio_value', 0)
- usdt_free = state.get('usdt_free', 0)
- trades_count = state.get('active_positions', 0) # ← FIXED: Use real count!
- prices = state.get('prices', {})
-
-
- # P&L from state
- pnl_usdt = state.get("pnl_usdt", 0)
- pnl_pct = state.get("pnl_pct", 0)
- pnl_status = state.get("pnl_status", "⚪ BREAK")
- pnl_color = state.get("pnl_color", "neutral")
- html = f'''
-
-
-
-
-Trading Bot V10
-
-
-
-
-
-
-
-
-
Portfolio Value
-
${portfolio_val:.2f}
-
-
-
USDT Available
-
${usdt_free:.2f}
-
-
-
Open Positions
-
{trades_count}
-
-
-
Total P&L
-
${pnl_usdt:+.2f} ({pnl_pct:+.1f}%)
-
-
-
P&L Status
-
{pnl_status}
-
-
-
-
-
-
-
-
-
-
- | Asset |
- Price |
-
-
- '''
-
- for asset, price in prices.items():
- html += f'''
- | {asset} |
- ${price:.2f} |
-
'''
-
- html += '''
-
-
-
-
-
-
-
-
-
-
-
-
- | Asset |
- Free |
- Total |
- Value |
-
-
- '''
-
- tracked = ['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'USDT', 'USDC']
- balance = state.get('balance', {})
-
- for asset in tracked:
- if asset in balance:
- data = balance[asset]
- price = prices.get(asset, 0)
- value = data['total'] * price
- html += f'''
- | {asset} |
- {data['free']:.4f} |
- {data['total']:.4f} |
- ${value:.2f} |
-
'''
-
- html += '''
-
-
-
-
-
-
-
-
-'''
-
- return Response(content=html, media_type='text/html')
-
-
-@app.get('/api/pnl')
-async def get_pnl():
- """Get live Profit & Loss (P&L) calculation"""
- try:
- account = binance.get_account()
-
- # Get current account value
- prices = get_live_prices()
- current_value = 0
-
- for asset_data in account['balances']:
- asset = asset_data['asset']
- total = float(asset_data['free']) + float(asset_data['locked'])
-
- if total > 0.00001 and asset != 'LDDOGE' and asset != 'LDBTTC':
- price = prices.get(asset, 1.0)
- current_value += total * price
-
- # Benchmark: Initial capital was $137.79 (before trading)
- # This should be stored, but for now use a reference
- initial_capital = 137.79
-
- pnl_usdt = current_value - initial_capital
- pnl_pct = (pnl_usdt / initial_capital * 100) if initial_capital > 0 else 0
-
- # Get open trades for unrealized portion
- state_file = '/home/marc/bot-deploy/trades.json'
- open_trades = {}
- if os.path.exists(state_file):
- try:
- data = json.load(state_file)
- open_trades = data.get('current', {})
- except:
- pass
-
- return {
- 'current_value': round(current_value, 2),
- 'initial_capital': initial_capital,
- 'total_pnl_usdt': round(pnl_usdt, 2),
- 'total_pnl_percent': round(pnl_pct, 2),
- 'status': '🟢 PROFIT' if pnl_usdt > 0 else ('🔴 LOSS' if pnl_usdt < 0 else '⚪ BREAK'),
- 'open_positions': len(open_trades),
- 'timestamp': datetime.now().isoformat()
- }
- except Exception as e:
- return {'error': str(e)}
-
-
-if __name__ == '__main__':
- import uvicorn
- uvicorn.run(app, host='0.0.0.0', port=7000)