From d4dabe38fc1293da6e1491bb6d76d198db9906d5 Mon Sep 17 00:00:00 2001 From: Marc Blatter Date: Wed, 8 Jul 2026 23:30:01 +0200 Subject: [PATCH] Bot auto-update: src/__pycache__/main_ml.cpython-310.pyc,src/config.py,src/dashboard_pnl.html,src/frigate_report.py,src/main.py,src/main_ml.py.backup.18pct,src/main_ml.py.backup.30pct,src/main_ml.py.backup.35pct.20240706,src/main_ml.py.backup.auto-trading,src/main_ml.py.backup_sweep,src/main_ml_BACKUP_before_precision_fix.py,src/main_ml_enhanced.py,src/main_ml_fixed.py,src/main_ml_v2.py,src/main_ml_v4_backup.py,src/main_ml_v6.py,src/persistence.py,src/report_generator.py --- src/__pycache__/main_ml.cpython-310.pyc | Bin 16377 -> 18633 bytes src/config.py | 60 +++ src/dashboard_pnl.html | 1 + src/frigate_report.py | 86 +++ src/main.py | 99 ++++ src/main_ml.py.backup.18pct | 431 +++++++++++++++ src/main_ml.py.backup.30pct | 431 +++++++++++++++ src/main_ml.py.backup.35pct.20240706 | 434 +++++++++++++++ src/main_ml.py.backup.auto-trading | 190 +++++++ src/main_ml.py.backup_sweep | 590 +++++++++++++++++++++ src/main_ml_BACKUP_before_precision_fix.py | 220 ++++++++ src/main_ml_enhanced.py | 220 ++++++++ src/main_ml_fixed.py | 205 +++++++ src/main_ml_v2.py | 157 ++++++ src/main_ml_v4_backup.py | 173 ++++++ src/main_ml_v6.py | 200 +++++++ src/persistence.py | 99 ++++ src/report_generator.py | 58 ++ 18 files changed, 3654 insertions(+) create mode 100755 src/config.py create mode 100644 src/dashboard_pnl.html create mode 100644 src/frigate_report.py create mode 100755 src/main.py create mode 100644 src/main_ml.py.backup.18pct create mode 100644 src/main_ml.py.backup.30pct create mode 100644 src/main_ml.py.backup.35pct.20240706 create mode 100644 src/main_ml.py.backup.auto-trading create mode 100644 src/main_ml.py.backup_sweep create mode 100644 src/main_ml_BACKUP_before_precision_fix.py create mode 100644 src/main_ml_enhanced.py create mode 100644 src/main_ml_fixed.py create mode 100644 src/main_ml_v2.py create mode 100644 src/main_ml_v4_backup.py create mode 100644 src/main_ml_v6.py create mode 100644 src/persistence.py create mode 100644 src/report_generator.py diff --git a/src/__pycache__/main_ml.cpython-310.pyc b/src/__pycache__/main_ml.cpython-310.pyc index 7b00564f320a55f2b1a44adfe292f0fed2f6ef2e..ef5d3f99355ac60b3933a572f7c16a7a605740b9 100644 GIT binary patch delta 4381 zcmZWtYj9J?6~24#^_48kw(t|<*EJYp$=I02yo7nbVlajvgM&({3f*fVTbAU#R~V~T z$O+IU8Au?@G);lQq>q#~C2iC^X3|MJnY@}N({zUZ(X`17ePojKk^E~~n)aNPj2%}V zecC;H_UzfSXU{q7&oaNdK+58!r49i;fBEIW$fv_UExSN^GDHv@gEXi`VqtY_+*A*B zcYR5{HFZ(lq6HIXS@7Wp|xm>;kQy- z;uGAhSyR4gBe`s!a|t1zv$4`z34~_Plzv03-3`mrH3(|~+#+3%?Ypx}TuG7S*b^1Y zOLpF2*~Q+isB`Vd{yhjjgud)&6*fZp7^&)6h+{co&|VzjumM{`Y;V&=e zUquMAf7dMOMyd2D0vC7$TU=^!UaB;1PUdBD%@44F+Ge7$SnUVo-t3`;%S5u5om`Y7 zqwH{fBN=29^@e<1qyy~l^}Qs*IvTpkA$FvpfsC-phTRq1Cc9t}w=-|Cd)ekO0j&OF!;B~P^WBA@pL7Cza`n=+f4ump>g)HabP@~B)e%P6(dM>Vu8X~(3z8o|h_I{8 z^@~npvkzh3u}o!eHiwC8FAl{JzJ@T$_OvV|C)s#QwUS1{A$GQNZEIt?MIFZ;?d$60#G@%1P;r%*j;#ACVAg6u%vLGH-$ONm%?{?;DKcD4SQY)ax{afER$DjE-s z>KYw}j$7h1dJYGQ%(xkhQf`R^vWF33>_A(m!uyY~Gi_nlNu;7%(K>`QyWQ5A`3g2W z5mq3~vr}3AOwdj_+tFY&7z^osN6;{IGoS^{;H-9B6Z!};DtwE^tYPVSAp31_^SSmtfwqrVrUI67NziOp#I&8ulPvo+k}kH2 zjm@T}&MZ89cFeHpuTEetq*?>t*J1hr^qy2gMh%f?lpcS7J(kd<#afBxJS|wx?VtuIQff(7=^7y|S@N_)E9EMb!OZeYvgWc%wFQ^tD+;Wb5lnfy zM60kIpkpOxtC};+6_N$hIr$tB(q%+QEe8G~t(tSzSY;!nFs~MV%U}yAP~ZzLEG(F( z%lWMx7PPu_*~zl;%KYyHk%f#|KKWijbkiKsWfltL6XyyvT~^sCGBYwH{J+QvL0e== z<^oGTMJ!pXzbvQAwFXPV9cztdh1PV6OjlYU85D1}GQxD#HGvPRS8Qn)7{A(bp}yQM zErp4TuSiI<%7+A>0QM+9PbSwE)NTaM8f^(zm>(j_&{}8v+S0t*TH9rDNKo!5?E`yc z&>$0JLU1ojz5y=iPz#?e2M70c`i8db_o{p1vEw>5BRbX9U^J?$!&HyxF||Dz%il`3 zx-A}w8S1UCKc*h^Z6CZNq%RzcQ1%WGBQ(0 z)dJ-aX2qQl!$S+7lqwsDt7Zi5D^-UvbWZ?#u4p(CjT-E4y^AvK(7#`>g!{_V#1t%| z33&NU2!M4Guv9YT+tjF{d3A!gY-@u;T$i>N7u@3l3h@BuaD@~T@SF${Wz3cF5| z&e@D)D5M*PzmkVxu23Kt8c#;3uKCMRT_X@QO?@n3>RL{U#=}Y6@F!Oo1cZTtEL zeCpsn?lmfUo4Re^-u?YM26uQo4u{7xh`}AahC86Hc|9JrHJK@y*$a0z$A?&orRtD> zK%dCV+M18od_s;mQZ65;qK4vQiKuSsUbQvlfL=i?8UsdkOw?XASqFyIyixTDZ-!hC-Po<%~}z-_>!e74_?v;dq5Pi1}?+5L97-Fg6vt^rho7Y8NK zMStV!pUxot2^3n&m7sb^hpVjdN(Ga?k&K!-Y0L=EbM72hBCpkq`<_By#}dY_$fASD zqD-8koj8e05{U>Drz8^>0jgvNbV*JrQ$g&aN*0qcz*b_PDkC+d4SvPXMMWeEf$we6 zE{e+MPFcRKD2{(Q>{X&dT%rp^Eke%!q#Ea6t1sND5Qd1Ej|2_8KRie8hVvIvO?Of= zf%^})9#Zmxv02|;;-_J*a@BlSB+j^i#=n6 zvGbl!>-GRgs?q_$%$t89&Ag7-_dB|)zlD;})U+F+A7MB9xMKymo7FGxAn#{)EeF52 z&h~V5R*&K68H9%s_=VZU&UJMtSbfqy_HtJvxB>e^*FI9kI#+o5`88RGgFOgC2s#2@ zRXQtg3cFnBL)hY4J%uf_Aia)ogPq${%YL%LL0Z{MD_UgxJ@yjz`xU#%4YsVic|ki= zat3ZhoKnEV^jS94eYPeF1ivjAi$Gc#n|ZQD*=ODE@@|}faoa9C34NZeT>0bDHAsW6 zP$SY8vLCP9OsXyD(WMBpapnlyeAixay~rNDtBJhGZrt@z4KIf=<@m*w$LMBgfO8D6 z8$EkVAH~Vv27pHrWsX%X;z5yhuIlS~3AqSG-{9l)=h&L(LN5dD5U50dfrF#$hpSWt zUp7$ozq_gjURp(~J4(3uF>uhcZ2js3Vn3+1dNFy0J-hl<@*_5}<_NrbZ>~8?j$&@%sdK2}B{h!CqOvWYa}#VswUQ z8khGtwEeC^#EijfCJGNRQ5sADWo>LlaFkG4T>_F;4#|UJ}oFtAdOD@$|gsocFxv zecp3U`|gyuGO@sLI_(AYDcadH`u!cREtnE(4~T*S`v7Q>SR$(Wjg&gHUTyE~+|;|d zeT(W<1B2b&JJmqf9kd#1-_hUI*PH!7EK$UR_=DvkYkz@9%^tRXSSW(oI~}z`{DBkB z3b8(W%6UW)U0CBP6CK$GS6UH4yijtJubobZ28IBErwDGPubefh#Q+*v6vNQR=4}i+ z7&^1gMTJ6iVp*wgG23#&gl4u#*vQf#YNhXsK3r4g_3mVsqP=M?k&0z>u&{lAz*Cq% zY+e##VI788-leO1V4Atx!W*EVJ)yuoDdCWWs6KsnT1dGggNHU(M1B`Sc zqDSCgXO_l$D z%L^viLB97vl7fZdG=QE+$;d%RREs4-Ngcwh$`iO~>FuJPvzIREc_htFoX|FW+{tI3CrF&Hvb z+z9U>mLjz9SfmafX2ZOck<#KI*-5aw&Jf4p&mM1J>DTA(h@5C+^ znKxL)Fo&xJ`D&UZQ?PKi7S|GCJ!scVQ%{8=T1vZ`oy-J|lVi>%U)H=zE}W42Q=rNG zuYF*AtN!KevNdak?Iiioi|$%$*04LI2-4RP*gv0qULEY;GN1-P*PDpmM{#!Tn0O5D zUiVYoR`SSH+i6HO{bx$uc+HUV;D~Q^*%KVq#;}^}yzQlVu~T!ch$AL?}ESj_ca(-ee_Rk<}sK1r$veoS(2xAzirwXR?=@ z+Cvqj)n*L3B0uD4z)oHCKtVc(D1qa#>{>8H3825jhd57JKmE#-4?bkoKIA zC3a4MB!0ZHM!6#J_l;e?7s=!iP{KjaF}%bur_0Zi?l(#dF0e6%`&(2S4;SjEXIgwT z@IGi+Isf05g=1LUx2cvtIE@hVQXzAc`@_O@MO3ZHM=U2UW5nWeP7NUdr zq-iCsJ=VWRE!(6+nkirE06lC#B*c?r{3cGqP96%C6(`{O11{mr=676I8&%Gf*YU9} zwXIVetAc~)e?b6A4!UH2htdg}C2{)sipbUq+T{t?^x-l5c}s^_k4^1$Vg~PNzvxcV z_MAw~B^pTGg{uP_=H)DAdJGeRHO>yslq;fCfy=bH65kCp+b)v=W-u98-06%`)8{jx zk#*ZY?k)x1A}5bGR~QOKjBqHFQw$SMkuF#mH=@Rvu+tfavkb2?WEn0pTw<7ExQyFd7U7%q_Ju{wV@4z$ W*ITHH3QRv9>hvf@>+qG%>;D4XU_>kc 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
LΓ€d…
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) +