# simulation_engine/sim_runner.py # V3.0 — Balanced-Fast: more trades, better expectancy, single-position, 1–2h time stops. import asyncio import os import pandas as pd from tqdm import tqdm from r2 import R2Service from data_manager import DataManager from ml_engine.processor import MLProcessor from learning_hub.hub_manager import LearningHubManager try: from .mock_kucoin import MockKuCoin from .virtual_exchange import VirtualExchange except ImportError: # إذا كانت البنية مختلفة، حدّث المسارات حسب مشروعك from simulation_engine.mock_kucoin import MockKuCoin from simulation_engine.virtual_exchange import VirtualExchange SIM_CONFIG = { # نافذة الاختبار "START_DATE": "2025-09-10", "END_DATE": "2025-11-09", # المحفظة والتنفيذ "INITIAL_BALANCE": 10.0, "FEE_RATE": 0.001, # 0.1% # سرعة القرار وجودته "STEP_TF": "5m", # قرار كل 5 دقائق "ENTRY_THRESHOLD": 0.22, # عتبة دخول متوازنة لرفع العدد دون ضوضاء مفرطة # حجم الصفقة والقيود "POSITION_FRACTION": 0.45, # 45% من الرصيد لكل صفقة "MAX_CONCURRENT_POSITIONS": 1, # صفقة واحدة في نفس الوقت "MIN_TRADE_USD": 0.10, # الخروج: هدف/وقف + إيقاف زمني مرن "TP_PCT": 1.6, # 1.6% "SL_PCT": 0.8, # 0.8% "SOFT_TIME_STOP_BARS": 12, # ≈ 1 ساعة عند STEP_TF=5m: يغلق عند التعادل أو أفضل "HARD_TIME_STOP_BARS": 24, # ≈ 2 ساعة: إغلاق قسري "COOLDOWN_BARS": 1, # تبريد قصير لمنع إعادة الدخول الفوري # الكون "TEST_SYMBOLS": [ "BTC/USDT","ETH/USDT","SOL/USDT","BNB/USDT","XRP/USDT", "DOGE/USDT","ADA/USDT","AVAX/USDT","LINK/USDT" ], "LOCAL_DATA_DIR": "./simulation_data" } SIM_STATUS = {"running": False, "progress": 0.0, "current_balance": 0.0, "trades_count": 0} TF_MS = {'5m': 300_000, '15m': 900_000, '1h': 3_600_000, '4h': 14_400_000, '1d': 86_400_000} REQUIRED_TFS = ['5m','15m','1h','4h','1d'] FETCH_LIMIT = 500 # الحد الأدنى للشموع المطلوبة لكل إطار MIN_CANDLES_BASE = {'5m':220,'15m':220,'1h':220,'4h':220,'1d':60} MIN_CANDLES_REQ = dict(MIN_CANDLES_BASE) MIN_CANDLES_REQ['1d'] = 59 # مرونة بسيطة لبدء المحاكاة async def run_realistic_simulation(): SIM_STATUS["running"] = True print( f"🚀 Sim {SIM_CONFIG['START_DATE']} -> {SIM_CONFIG['END_DATE']} | symbols={len(SIM_CONFIG['TEST_SYMBOLS'])}\n" f"STEP_TF={SIM_CONFIG['STEP_TF']} THR={SIM_CONFIG['ENTRY_THRESHOLD']} " f"TP/SL={SIM_CONFIG['TP_PCT']}/{SIM_CONFIG['SL_PCT']} " f"SOFT/HARD={SIM_CONFIG['SOFT_TIME_STOP_BARS']}/{SIM_CONFIG['HARD_TIME_STOP_BARS']} bars " f"| COOLDOWN={SIM_CONFIG['COOLDOWN_BARS']} | POS_FRAC={SIM_CONFIG['POSITION_FRACTION']}", flush=True ) r2_service = R2Service() data_dir = SIM_CONFIG["LOCAL_DATA_DIR"] if not os.path.exists(data_dir): print(f"❌ Missing data dir: {data_dir}", flush=True) SIM_STATUS["running"] = False return print("🛠️ Bootstrapping...", flush=True) mock_exchange = MockKuCoin(data_dir) await mock_exchange.load_data(SIM_CONFIG["TEST_SYMBOLS"], REQUIRED_TFS) # إنشاء المحفظة بمنطق الإيقاف الزمني المرن step_ms = TF_MS.get(SIM_CONFIG["STEP_TF"], TF_MS['5m']) virtual_wallet = VirtualExchange( initial_balance=SIM_CONFIG["INITIAL_BALANCE"], fee_rate=SIM_CONFIG["FEE_RATE"], tp_pct=SIM_CONFIG["TP_PCT"], sl_pct=SIM_CONFIG["SL_PCT"], bar_ms=step_ms, cooldown_bars=SIM_CONFIG["COOLDOWN_BARS"], min_trade_usd=SIM_CONFIG["MIN_TRADE_USD"], position_fraction=SIM_CONFIG["POSITION_FRACTION"], max_concurrent=SIM_CONFIG["MAX_CONCURRENT_POSITIONS"], soft_time_stop_bars=SIM_CONFIG["SOFT_TIME_STOP_BARS"], hard_time_stop_bars=SIM_CONFIG["HARD_TIME_STOP_BARS"], ) # تحديد مجال الزمن الآمن min_ts, max_ts = None, None for sym in SIM_CONFIG["TEST_SYMBOLS"]: for tf in REQUIRED_TFS: df = mock_exchange.data_store.get(sym, {}).get(tf) if df is None or df.empty: continue mn = int(df['timestamp'].iloc[0]); mx = int(df['timestamp'].iloc[-1]) min_ts = mn if min_ts is None else min(min_ts, mn) max_ts = mx if max_ts is None else max(max_ts, mx) if min_ts is None or max_ts is None: print("❌ لا توجد بيانات صالحة.", flush=True); SIM_STATUS["running"] = False; return boot_requirements_ms = [] for tf in REQUIRED_TFS: need = MIN_CANDLES_BASE[tf] guard = 1 if tf == '1d' else 0 eff_need = max(need - guard, 1) boot_requirements_ms.append(eff_need * TF_MS[tf]) boot_ms = max(boot_requirements_ms) cfg_start = int(pd.Timestamp(SIM_CONFIG["START_DATE"]).timestamp() * 1000) cfg_end = int(pd.Timestamp(SIM_CONFIG["END_DATE"]).timestamp() * 1000) safe_start_ts = max(cfg_start, min_ts + boot_ms) safe_end_ts = min(cfg_end, max_ts) if safe_end_ts - safe_start_ts < step_ms: print("❌ نافذة المحاكاة قصيرة.", flush=True); SIM_STATUS["running"] = False; return print( f"🗓️ Data span: [{pd.to_datetime(min_ts, unit='ms')} .. {pd.to_datetime(max_ts, unit='ms')}] | " f"Sim window: [{pd.to_datetime(safe_start_ts, unit='ms')} .. {pd.to_datetime(safe_end_ts, unit='ms')}]", flush=True ) print("🧠 Initializing hybrid system...", flush=True) data_manager = DataManager(None, None, r2_service=r2_service, mock_exchange=mock_exchange) learning_hub = LearningHubManager(r2_service, None, data_manager, disable_llm=True) # NO_LLM await learning_hub.initialize() processor = MLProcessor(None, data_manager, learning_hub) await processor.initialize() # تمرير عتبة الدخول للنظام الهجين setattr(data_manager, 'HYBRID_ENTRY_THRESHOLD', float(SIM_CONFIG["ENTRY_THRESHOLD"])) print(f"✅ Ready. Threshold={getattr(data_manager, 'HYBRID_ENTRY_THRESHOLD', 'NA')}", flush=True) # حلقة المحاكاة timeline = range(safe_start_ts, safe_end_ts, step_ms) total = len(timeline) bar = tqdm(timeline, desc="Simulating", unit="step") def log(msg): try: bar.write(msg) except Exception: print(msg, flush=True) for i, ts in enumerate(bar): # تحديث الوقت mock_exchange.set_time(ts) if i % 10 == 0: SIM_STATUS["progress"] = (i / total) * 100 SIM_STATUS["current_balance"] = virtual_wallet.get_balance() SIM_STATUS["trades_count"] = len(virtual_wallet.trade_history) # إغلاقات قائمة (TP/SL/Soft/Hard) prices = {s:(await mock_exchange.fetch_ticker(s))['last'] for s in SIM_CONFIG["TEST_SYMBOLS"]} closed = virtual_wallet.update_positions(prices, ts) for tr in closed: log(f"[CLOSE] {tr['symbol']} reason={tr['close_reason']} pnl={tr['pnl_percent']:.2f}%") await learning_hub.analyze_trade_and_learn(tr, tr['close_reason']) # فتح مركز جديد إن لم توجد صفقات مفتوحة if virtual_wallet.open_positions_count() < SIM_CONFIG["MAX_CONCURRENT_POSITIONS"]: for symbol in SIM_CONFIG["TEST_SYMBOLS"]: # تحقق توافر حزمة الفريمات enough = True for tf in REQUIRED_TFS: candles = await mock_exchange.fetch_ohlcv(symbol, tf, limit=FETCH_LIMIT) if not candles or len(candles) < MIN_CANDLES_REQ[tf]: enough = False; break if not enough: continue packet = {tf: await mock_exchange.fetch_ohlcv(symbol, tf, limit=FETCH_LIMIT) for tf in REQUIRED_TFS} cur_price = prices.get(symbol, 0.0) res = await processor.process_and_score_symbol_enhanced({ 'symbol': symbol, 'ohlcv': packet, 'current_price': cur_price }) if not res: continue final_s = float(res['enhanced_final_score']) thr = getattr(data_manager, 'HYBRID_ENTRY_THRESHOLD', 0.75) comps = dict(res.get('components', {})); comps['final_score'] = final_s log(f"[SCORE] {symbol} final={final_s:.3f} thr={thr:.3f} comps={comps}") if final_s >= thr and virtual_wallet.can_enter(symbol, ts): if virtual_wallet.execute_buy(symbol, cur_price, ts, comps): log(f"[BUY] {symbol} @ {cur_price} bal=${virtual_wallet.get_balance():.2f}") break # نحافظ على صفقة واحدة فقط # إغلاق ما تبقى عند نهاية النافذة print("\n🏁 نهاية فترة المحاكاة. إغلاق الصفقات المتبقية...", flush=True) final_ts = safe_end_ts mock_exchange.set_time(final_ts) end_prices = {s:(await mock_exchange.fetch_ticker(s))['last'] for s in SIM_CONFIG["TEST_SYMBOLS"]} for s in list(virtual_wallet.positions.keys()): tr = virtual_wallet.execute_sell(s, end_prices.get(s,0), final_ts, "END_OF_SIM") if tr: await learning_hub.analyze_trade_and_learn(tr, "END_OF_SIM") print("💾 جاري حفظ النتائج إلى R2...", flush=True) await r2_service.save_simulation_results(virtual_wallet.trade_history, virtual_wallet.metrics) await learning_hub.shutdown() SIM_STATUS.update({"running": False, "progress": 100.0}) print("\n🎉 === تقرير المحاكاة السريع ===", flush=True) print(f"💰 الرصيد النهائي: ${virtual_wallet.get_balance():.2f} (من ${SIM_CONFIG['INITIAL_BALANCE']})", flush=True) print(f"📈 إجمالي الربح/الخسارة: ${virtual_wallet.metrics['total_pnl_usd']:.2f}", flush=True) print(f"🔢 عدد الصفقات: {len(virtual_wallet.trade_history)} (فوز: {virtual_wallet.metrics['wins']} | خسارة: {virtual_wallet.metrics['losses']})", flush=True) print(f"📉 أقصى تراجع (Max Drawdown): {virtual_wallet.metrics['max_drawdown']*100:.2f}%", flush=True) print("===============================\n", flush=True)