Trad / simulation_engine /sim_runner.py
Riy777's picture
Update simulation_engine/sim_runner.py
9dd6d6b
raw
history blame
10.7 kB
# 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)