diff --git a/src/__pycache__/main_ml.cpython-310.pyc b/src/__pycache__/main_ml.cpython-310.pyc
index 7b00564..ef5d3f9 100644
Binary files a/src/__pycache__/main_ml.cpython-310.pyc and b/src/__pycache__/main_ml.cpython-310.pyc differ
diff --git a/src/config.py b/src/config.py
new file mode 100755
index 0000000..a689a9e
--- /dev/null
+++ b/src/config.py
@@ -0,0 +1,60 @@
+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
new file mode 100644
index 0000000..f620317
--- /dev/null
+++ b/src/dashboard_pnl.html
@@ -0,0 +1 @@
+
Bot P&L
diff --git a/src/frigate_report.py b/src/frigate_report.py
new file mode 100644
index 0000000..200c994
--- /dev/null
+++ b/src/frigate_report.py
@@ -0,0 +1,86 @@
+#!/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
new file mode 100755
index 0000000..b4020d4
--- /dev/null
+++ b/src/main.py
@@ -0,0 +1,99 @@
+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.18pct b/src/main_ml.py.backup.18pct
new file mode 100644
index 0000000..09e7502
--- /dev/null
+++ b/src/main_ml.py.backup.18pct
@@ -0,0 +1,431 @@
+#!/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
new file mode 100644
index 0000000..700e8cd
--- /dev/null
+++ b/src/main_ml.py.backup.30pct
@@ -0,0 +1,431 @@
+#!/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
new file mode 100644
index 0000000..4370cb3
--- /dev/null
+++ b/src/main_ml.py.backup.35pct.20240706
@@ -0,0 +1,434 @@
+#!/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
new file mode 100644
index 0000000..fa247c3
--- /dev/null
+++ b/src/main_ml.py.backup.auto-trading
@@ -0,0 +1,190 @@
+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.backup_sweep b/src/main_ml.py.backup_sweep
new file mode 100644
index 0000000..48d950f
--- /dev/null
+++ b/src/main_ml.py.backup_sweep
@@ -0,0 +1,590 @@
+#!/usr/bin/env python3
+"""
+Trading Bot V0.2 β Adaptive Strategy Learning
+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 = 7.5 # 7-8% range (midpoint 7.5%) # 5% random signal
+ self.INVESTMENT_PERCENT = 50 # 50% (single position for liquidity) (single position)
+ self.INVESTMENT_PERCENT_HIGH = 55 # 55% when confidence > 85% > 85%
+ self.CONFIDENCE_THRESHOLD = 85 # Min confidence for high investment # 35% per trade (5 parallel = 90% max, 10% buffer)
+ self.NOTIONAL_MIN = 5.0 # Override Binance minimum to $3
+ self.STOP_LOSS_PERCENT = 1.8 # -2.5%
+ self.TAKE_PROFIT_PERCENT = 2.8 # +3%
+ self.DAILY_LOSS_LIMIT = -5
+
+ # Trailing Stop
+ self.TRAILING_STOP_ENTRY = 1.5 # Activate trailing stop at +1.5%
+ self.TRAILING_STOP_DISTANCE = 0.6 # 0.6% distance
+
+ # Position & Trade Limits
+ self.MAX_OPEN_POSITIONS = 1 # Single position for max liquidity # Max concurrent trades
+ self.MAX_CONSECUTIVE_LOSSES = 3 # Stop after 3 losses
+ self.CONSECUTIVE_LOSS_COOLDOWN = 30 * 60 # 30 minutes in seconds
+ self.MAX_TRADES_PER_DAY = 15
+ self.MIN_WIN_PROBABILITY = 75 # Min expected win %
+
+ # Tracking
+ self.consecutive_losses = 0
+ self.last_loss_time = None
+ self.trades_today = 0
+ self.last_trade_reset = None # -5% max
+
+ # Profit tracking
+ self.entry_price_history = {} # symbol -> entry price
+ self.closed_trades = [] # list of {symbol, entry, exit, profit_pct, profit_usdt}
+ self.session_start_balance = None
+
+ self.active_trades = {}
+ self.daily_pnl = 0
+ self.paused = False
+
+ # ADAPTIVE TRACKING (Option 2: Win Rate based Strategy)
+ self.total_trades = 0
+ self.total_wins = 0
+ self.total_losses = 0
+ self.last_win_rate = 50.0 # Start neutral
+ self.strategy_version = 1
+ 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(f"β
Bot initialized with Risk Management (SL {self.STOP_LOSS_PERCENT}%, TP {self.TAKE_PROFIT_PERCENT}%, Daily Limit {-self.DAILY_LOSS_LIMIT}%, Max Pos: {self.MAX_OPEN_POSITIONS})")
+
+ # 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 V0.2 β 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 {self.STOP_LOSS_PERCENT}% 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: V0.2 Adaptive"""
+
+ 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())
+
+
+ def get_signal_confidence(self):
+ """Calculate confidence level for current signal (0-100%)"""
+ # This can be enhanced with actual ML model
+ # For now: random 30-95%
+ import random
+ return random.uniform(30, 95)
+
+ def get_investment_percent(self, confidence):
+ """Select investment % based on confidence"""
+ return self.INVESTMENT_PERCENT_HIGH if confidence > self.CONFIDENCE_THRESHOLD else self.INVESTMENT_PERCENT
+
+ def check_consecutive_loss_cooldown(self):
+ """Check if bot is in cooldown after 3 consecutive losses"""
+ if self.consecutive_losses >= self.MAX_CONSECUTIVE_LOSSES:
+ if self.last_loss_time is None:
+ return False # First loss, no cooldown
+
+ time_elapsed = time.time() - self.last_loss_time
+ if time_elapsed < self.CONSECUTIVE_LOSS_COOLDOWN:
+ logger.warning(f"π« Cooldown active: {int(self.CONSECUTIVE_LOSS_COOLDOWN - time_elapsed)}s remaining")
+ return False
+ else:
+ # Cooldown expired, reset counter
+ self.consecutive_losses = 0
+ logger.info("β
Cooldown expired, consecutive loss counter reset")
+ return True
+ return True
+
+ def check_volatility(self, pair):
+ """Check market volatility (simplified)"""
+ try:
+ ticker = self.client.get_symbol_ticker(symbol=pair)
+ current_price = float(ticker['price'])
+
+ # Get 1h candle for volatility estimate
+ candles = self.client.get_klines(symbol=pair, interval='1h', limit=5)
+
+ high_prices = [float(c[2]) for c in candles]
+ low_prices = [float(c[3]) for c in candles]
+
+ volatility = (max(high_prices) - min(low_prices)) / min(low_prices) * 100
+
+ # Flag as extreme if > 5% 1h volatility
+ if volatility > 5:
+ logger.warning(f"β οΈ High volatility {pair}: {volatility:.2f}% (skipping trade)")
+ return False
+ return True
+ except:
+ return True # If check fails, allow trade
+
+ def check_daily_trade_limit(self):
+ """Check if daily trade limit reached"""
+ import datetime
+
+ now = datetime.datetime.now()
+ today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
+
+ if self.last_trade_reset is None or self.last_trade_reset < today_start:
+ self.trades_today = 0
+ self.last_trade_reset = now
+
+ if self.trades_today >= self.MAX_TRADES_PER_DAY:
+ logger.warning(f"β οΈ Daily limit reached: {self.trades_today}/{self.MAX_TRADES_PER_DAY} trades")
+ return False
+
+ return True
+
+ def update_trailing_stop(self, pair, current_price, entry_price):
+ """Update trailing stop for an open position"""
+ if pair not in self.active_trades:
+ return False
+
+ trade_data = self.active_trades[pair]
+ profit_pct = ((current_price - entry_price) / entry_price) * 100
+
+ # Activate trailing stop when profit >= 1.5%
+ if profit_pct >= self.TRAILING_STOP_ENTRY:
+ trailing_stop_price = current_price * (1 - self.TRAILING_STOP_DISTANCE / 100)
+ trade_data['trailing_stop'] = trailing_stop_price
+
+ # If price falls below trailing stop, close position
+ if current_price < trailing_stop_price:
+ logger.info(f"π Trailing stop triggered {pair}: Sell @ ${current_price:.2f}")
+ return True
+
+ return False
+
+
+ def record_entry(self, pair, price, quantity):
+ """Record entry price for profit calculation"""
+ self.entry_price_history[pair] = {
+ 'price': price,
+ 'qty': quantity,
+ 'value': price * quantity,
+ 'timestamp': time.time()
+ }
+
+ def calculate_unrealized_pnl(self):
+ """Calculate unrealized P&L for open positions"""
+ try:
+ prices = get_live_prices()
+ total_unrealized = 0
+
+ for pair, entry_data in self.entry_price_history.items():
+ asset = pair.replace('USDT', '')
+ current_price = prices.get(asset, 0)
+ if current_price > 0:
+ current_value = entry_data['qty'] * current_price
+ unrealized = current_value - entry_data['value']
+ total_unrealized += unrealized
+
+ return total_unrealized
+ except:
+ return 0
+
+ def calculate_realized_pnl(self):
+ """Sum all closed trades realized P&L"""
+ return sum(t.get('profit_usdt', 0) for t in self.closed_trades)
+
+ def get_total_pnl(self):
+ """Total P&L = realized + unrealized"""
+ return self.calculate_realized_pnl() + self.calculate_unrealized_pnl()
+
diff --git a/src/main_ml_BACKUP_before_precision_fix.py b/src/main_ml_BACKUP_before_precision_fix.py
new file mode 100644
index 0000000..d44a05b
--- /dev/null
+++ b/src/main_ml_BACKUP_before_precision_fix.py
@@ -0,0 +1,220 @@
+#!/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
new file mode 100644
index 0000000..d44a05b
--- /dev/null
+++ b/src/main_ml_enhanced.py
@@ -0,0 +1,220 @@
+#!/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
new file mode 100644
index 0000000..c984497
--- /dev/null
+++ b/src/main_ml_fixed.py
@@ -0,0 +1,205 @@
+#!/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
new file mode 100644
index 0000000..073afab
--- /dev/null
+++ b/src/main_ml_v2.py
@@ -0,0 +1,157 @@
+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
new file mode 100644
index 0000000..657417f
--- /dev/null
+++ b/src/main_ml_v4_backup.py
@@ -0,0 +1,173 @@
+#!/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
new file mode 100644
index 0000000..78c8abb
--- /dev/null
+++ b/src/main_ml_v6.py
@@ -0,0 +1,200 @@
+#!/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
new file mode 100644
index 0000000..13e3185
--- /dev/null
+++ b/src/persistence.py
@@ -0,0 +1,99 @@
+#!/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
new file mode 100644
index 0000000..23d7abe
--- /dev/null
+++ b/src/report_generator.py
@@ -0,0 +1,58 @@
+#!/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)
+