Spaces:
Paused
Paused
Update simulation_engine/sim_runner.py
Browse files- simulation_engine/sim_runner.py +49 -36
simulation_engine/sim_runner.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
# simulation_engine/sim_runner.py
|
| 2 |
-
#
|
| 3 |
|
| 4 |
import asyncio
|
| 5 |
import os
|
|
@@ -15,33 +15,36 @@ try:
|
|
| 15 |
from .mock_kucoin import MockKuCoin
|
| 16 |
from .virtual_exchange import VirtualExchange
|
| 17 |
except ImportError:
|
| 18 |
-
|
|
|
|
|
|
|
| 19 |
|
| 20 |
SIM_CONFIG = {
|
|
|
|
| 21 |
"START_DATE": "2025-09-10",
|
| 22 |
"END_DATE": "2025-11-09",
|
| 23 |
|
| 24 |
-
#
|
| 25 |
"INITIAL_BALANCE": 10.0,
|
| 26 |
-
"FEE_RATE": 0.001,
|
| 27 |
|
| 28 |
-
#
|
| 29 |
-
"STEP_TF": "5m", # قرار
|
| 30 |
-
"ENTRY_THRESHOLD": 0.
|
| 31 |
|
| 32 |
-
#
|
| 33 |
-
"POSITION_FRACTION": 0.
|
| 34 |
-
"MAX_CONCURRENT_POSITIONS": 1,
|
| 35 |
"MIN_TRADE_USD": 0.10,
|
| 36 |
|
| 37 |
-
#
|
| 38 |
-
"TP_PCT": 1.
|
| 39 |
-
"SL_PCT": 0.8, # 0.8%
|
| 40 |
-
"SOFT_TIME_STOP_BARS": 12, # ≈ 1 ساعة عند STEP_TF=5m
|
| 41 |
-
"HARD_TIME_STOP_BARS": 24, # ≈ 2 ساعة
|
| 42 |
-
"COOLDOWN_BARS":
|
| 43 |
|
| 44 |
-
#
|
| 45 |
"TEST_SYMBOLS": [
|
| 46 |
"BTC/USDT","ETH/USDT","SOL/USDT","BNB/USDT","XRP/USDT",
|
| 47 |
"DOGE/USDT","ADA/USDT","AVAX/USDT","LINK/USDT"
|
|
@@ -55,9 +58,10 @@ TF_MS = {'5m': 300_000, '15m': 900_000, '1h': 3_600_000, '4h': 14_400_000, '1d':
|
|
| 55 |
REQUIRED_TFS = ['5m','15m','1h','4h','1d']
|
| 56 |
FETCH_LIMIT = 500
|
| 57 |
|
|
|
|
| 58 |
MIN_CANDLES_BASE = {'5m':220,'15m':220,'1h':220,'4h':220,'1d':60}
|
| 59 |
MIN_CANDLES_REQ = dict(MIN_CANDLES_BASE)
|
| 60 |
-
MIN_CANDLES_REQ['1d'] = 59
|
| 61 |
|
| 62 |
async def run_realistic_simulation():
|
| 63 |
SIM_STATUS["running"] = True
|
|
@@ -65,21 +69,23 @@ async def run_realistic_simulation():
|
|
| 65 |
f"🚀 Sim {SIM_CONFIG['START_DATE']} -> {SIM_CONFIG['END_DATE']} | symbols={len(SIM_CONFIG['TEST_SYMBOLS'])}\n"
|
| 66 |
f"STEP_TF={SIM_CONFIG['STEP_TF']} THR={SIM_CONFIG['ENTRY_THRESHOLD']} "
|
| 67 |
f"TP/SL={SIM_CONFIG['TP_PCT']}/{SIM_CONFIG['SL_PCT']} "
|
| 68 |
-
f"SOFT/HARD={SIM_CONFIG['SOFT_TIME_STOP_BARS']}/{SIM_CONFIG['HARD_TIME_STOP_BARS']} bars"
|
|
|
|
| 69 |
flush=True
|
| 70 |
)
|
| 71 |
|
| 72 |
r2_service = R2Service()
|
| 73 |
-
|
| 74 |
-
|
|
|
|
| 75 |
SIM_STATUS["running"] = False
|
| 76 |
return
|
| 77 |
|
| 78 |
print("🛠️ Bootstrapping...", flush=True)
|
| 79 |
-
mock_exchange = MockKuCoin(
|
| 80 |
await mock_exchange.load_data(SIM_CONFIG["TEST_SYMBOLS"], REQUIRED_TFS)
|
| 81 |
|
| 82 |
-
#
|
| 83 |
step_ms = TF_MS.get(SIM_CONFIG["STEP_TF"], TF_MS['5m'])
|
| 84 |
virtual_wallet = VirtualExchange(
|
| 85 |
initial_balance=SIM_CONFIG["INITIAL_BALANCE"],
|
|
@@ -95,7 +101,7 @@ async def run_realistic_simulation():
|
|
| 95 |
hard_time_stop_bars=SIM_CONFIG["HARD_TIME_STOP_BARS"],
|
| 96 |
)
|
| 97 |
|
| 98 |
-
#
|
| 99 |
min_ts, max_ts = None, None
|
| 100 |
for sym in SIM_CONFIG["TEST_SYMBOLS"]:
|
| 101 |
for tf in REQUIRED_TFS:
|
|
@@ -106,7 +112,7 @@ async def run_realistic_simulation():
|
|
| 106 |
min_ts = mn if min_ts is None else min(min_ts, mn)
|
| 107 |
max_ts = mx if max_ts is None else max(max_ts, mx)
|
| 108 |
if min_ts is None or max_ts is None:
|
| 109 |
-
print("❌ لا توجد بيانات صالحة
|
| 110 |
|
| 111 |
boot_requirements_ms = []
|
| 112 |
for tf in REQUIRED_TFS:
|
|
@@ -133,24 +139,26 @@ async def run_realistic_simulation():
|
|
| 133 |
|
| 134 |
print("🧠 Initializing hybrid system...", flush=True)
|
| 135 |
data_manager = DataManager(None, None, r2_service=r2_service, mock_exchange=mock_exchange)
|
| 136 |
-
learning_hub = LearningHubManager(r2_service, None, data_manager, disable_llm=True)
|
| 137 |
await learning_hub.initialize()
|
| 138 |
processor = MLProcessor(None, data_manager, learning_hub)
|
| 139 |
await processor.initialize()
|
| 140 |
|
|
|
|
| 141 |
setattr(data_manager, 'HYBRID_ENTRY_THRESHOLD', float(SIM_CONFIG["ENTRY_THRESHOLD"]))
|
| 142 |
print(f"✅ Ready. Threshold={getattr(data_manager, 'HYBRID_ENTRY_THRESHOLD', 'NA')}", flush=True)
|
| 143 |
|
|
|
|
| 144 |
timeline = range(safe_start_ts, safe_end_ts, step_ms)
|
| 145 |
total = len(timeline)
|
| 146 |
bar = tqdm(timeline, desc="Simulating", unit="step")
|
| 147 |
|
| 148 |
-
def log(msg):
|
| 149 |
try: bar.write(msg)
|
| 150 |
except Exception: print(msg, flush=True)
|
| 151 |
|
| 152 |
-
# loop
|
| 153 |
for i, ts in enumerate(bar):
|
|
|
|
| 154 |
mock_exchange.set_time(ts)
|
| 155 |
|
| 156 |
if i % 10 == 0:
|
|
@@ -158,17 +166,17 @@ async def run_realistic_simulation():
|
|
| 158 |
SIM_STATUS["current_balance"] = virtual_wallet.get_balance()
|
| 159 |
SIM_STATUS["trades_count"] = len(virtual_wallet.trade_history)
|
| 160 |
|
| 161 |
-
#
|
| 162 |
prices = {s:(await mock_exchange.fetch_ticker(s))['last'] for s in SIM_CONFIG["TEST_SYMBOLS"]}
|
| 163 |
closed = virtual_wallet.update_positions(prices, ts)
|
| 164 |
for tr in closed:
|
| 165 |
log(f"[CLOSE] {tr['symbol']} reason={tr['close_reason']} pnl={tr['pnl_percent']:.2f}%")
|
| 166 |
await learning_hub.analyze_trade_and_learn(tr, tr['close_reason'])
|
| 167 |
|
| 168 |
-
#
|
| 169 |
if virtual_wallet.open_positions_count() < SIM_CONFIG["MAX_CONCURRENT_POSITIONS"]:
|
| 170 |
for symbol in SIM_CONFIG["TEST_SYMBOLS"]:
|
| 171 |
-
#
|
| 172 |
enough = True
|
| 173 |
for tf in REQUIRED_TFS:
|
| 174 |
candles = await mock_exchange.fetch_ohlcv(symbol, tf, limit=FETCH_LIMIT)
|
|
@@ -178,19 +186,24 @@ async def run_realistic_simulation():
|
|
| 178 |
continue
|
| 179 |
|
| 180 |
packet = {tf: await mock_exchange.fetch_ohlcv(symbol, tf, limit=FETCH_LIMIT) for tf in REQUIRED_TFS}
|
| 181 |
-
|
|
|
|
|
|
|
|
|
|
| 182 |
if not res:
|
| 183 |
continue
|
| 184 |
|
| 185 |
final_s = float(res['enhanced_final_score'])
|
| 186 |
thr = getattr(data_manager, 'HYBRID_ENTRY_THRESHOLD', 0.75)
|
| 187 |
-
|
|
|
|
|
|
|
| 188 |
if final_s >= thr and virtual_wallet.can_enter(symbol, ts):
|
| 189 |
-
if virtual_wallet.execute_buy(symbol,
|
| 190 |
-
log(f"[BUY] {symbol} @ {
|
| 191 |
break # نحافظ على صفقة واحدة فقط
|
| 192 |
|
| 193 |
-
#
|
| 194 |
print("\n🏁 نهاية فترة المحاكاة. إغلاق الصفقات المتبقية...", flush=True)
|
| 195 |
final_ts = safe_end_ts
|
| 196 |
mock_exchange.set_time(final_ts)
|
|
|
|
| 1 |
# simulation_engine/sim_runner.py
|
| 2 |
+
# V3.0 — Balanced-Fast: more trades, better expectancy, single-position, 1–2h time stops.
|
| 3 |
|
| 4 |
import asyncio
|
| 5 |
import os
|
|
|
|
| 15 |
from .mock_kucoin import MockKuCoin
|
| 16 |
from .virtual_exchange import VirtualExchange
|
| 17 |
except ImportError:
|
| 18 |
+
# إذا كانت البنية مختلفة، حدّث المسارات حسب مشروعك
|
| 19 |
+
from simulation_engine.mock_kucoin import MockKuCoin
|
| 20 |
+
from simulation_engine.virtual_exchange import VirtualExchange
|
| 21 |
|
| 22 |
SIM_CONFIG = {
|
| 23 |
+
# نافذة الاختبار
|
| 24 |
"START_DATE": "2025-09-10",
|
| 25 |
"END_DATE": "2025-11-09",
|
| 26 |
|
| 27 |
+
# المحفظة والتنفيذ
|
| 28 |
"INITIAL_BALANCE": 10.0,
|
| 29 |
+
"FEE_RATE": 0.001, # 0.1%
|
| 30 |
|
| 31 |
+
# سرعة القرار وجودته
|
| 32 |
+
"STEP_TF": "5m", # قرار كل 5 دقائق
|
| 33 |
+
"ENTRY_THRESHOLD": 0.22, # عتبة دخول متوازنة لرفع العدد دون ضوضاء مفرطة
|
| 34 |
|
| 35 |
+
# حجم الصفقة والقيود
|
| 36 |
+
"POSITION_FRACTION": 0.45, # 45% من الرصيد لكل صفقة
|
| 37 |
+
"MAX_CONCURRENT_POSITIONS": 1, # صفقة واحدة في نفس الوقت
|
| 38 |
"MIN_TRADE_USD": 0.10,
|
| 39 |
|
| 40 |
+
# الخروج: هدف/وقف + إيقاف زمني مرن
|
| 41 |
+
"TP_PCT": 1.6, # 1.6%
|
| 42 |
+
"SL_PCT": 0.8, # 0.8%
|
| 43 |
+
"SOFT_TIME_STOP_BARS": 12, # ≈ 1 ساعة عند STEP_TF=5m: يغلق عند التعادل أو أفضل
|
| 44 |
+
"HARD_TIME_STOP_BARS": 24, # ≈ 2 ساعة: إغلاق قسري
|
| 45 |
+
"COOLDOWN_BARS": 1, # تبريد قصير لمنع إعادة الدخول الفوري
|
| 46 |
|
| 47 |
+
# الكون
|
| 48 |
"TEST_SYMBOLS": [
|
| 49 |
"BTC/USDT","ETH/USDT","SOL/USDT","BNB/USDT","XRP/USDT",
|
| 50 |
"DOGE/USDT","ADA/USDT","AVAX/USDT","LINK/USDT"
|
|
|
|
| 58 |
REQUIRED_TFS = ['5m','15m','1h','4h','1d']
|
| 59 |
FETCH_LIMIT = 500
|
| 60 |
|
| 61 |
+
# الحد الأدنى للشموع المطلوبة لكل إطار
|
| 62 |
MIN_CANDLES_BASE = {'5m':220,'15m':220,'1h':220,'4h':220,'1d':60}
|
| 63 |
MIN_CANDLES_REQ = dict(MIN_CANDLES_BASE)
|
| 64 |
+
MIN_CANDLES_REQ['1d'] = 59 # مرونة بسيطة لبدء المحاكاة
|
| 65 |
|
| 66 |
async def run_realistic_simulation():
|
| 67 |
SIM_STATUS["running"] = True
|
|
|
|
| 69 |
f"🚀 Sim {SIM_CONFIG['START_DATE']} -> {SIM_CONFIG['END_DATE']} | symbols={len(SIM_CONFIG['TEST_SYMBOLS'])}\n"
|
| 70 |
f"STEP_TF={SIM_CONFIG['STEP_TF']} THR={SIM_CONFIG['ENTRY_THRESHOLD']} "
|
| 71 |
f"TP/SL={SIM_CONFIG['TP_PCT']}/{SIM_CONFIG['SL_PCT']} "
|
| 72 |
+
f"SOFT/HARD={SIM_CONFIG['SOFT_TIME_STOP_BARS']}/{SIM_CONFIG['HARD_TIME_STOP_BARS']} bars "
|
| 73 |
+
f"| COOLDOWN={SIM_CONFIG['COOLDOWN_BARS']} | POS_FRAC={SIM_CONFIG['POSITION_FRACTION']}",
|
| 74 |
flush=True
|
| 75 |
)
|
| 76 |
|
| 77 |
r2_service = R2Service()
|
| 78 |
+
data_dir = SIM_CONFIG["LOCAL_DATA_DIR"]
|
| 79 |
+
if not os.path.exists(data_dir):
|
| 80 |
+
print(f"❌ Missing data dir: {data_dir}", flush=True)
|
| 81 |
SIM_STATUS["running"] = False
|
| 82 |
return
|
| 83 |
|
| 84 |
print("🛠️ Bootstrapping...", flush=True)
|
| 85 |
+
mock_exchange = MockKuCoin(data_dir)
|
| 86 |
await mock_exchange.load_data(SIM_CONFIG["TEST_SYMBOLS"], REQUIRED_TFS)
|
| 87 |
|
| 88 |
+
# إنشاء المحفظة بمنطق الإيقاف الزمني المرن
|
| 89 |
step_ms = TF_MS.get(SIM_CONFIG["STEP_TF"], TF_MS['5m'])
|
| 90 |
virtual_wallet = VirtualExchange(
|
| 91 |
initial_balance=SIM_CONFIG["INITIAL_BALANCE"],
|
|
|
|
| 101 |
hard_time_stop_bars=SIM_CONFIG["HARD_TIME_STOP_BARS"],
|
| 102 |
)
|
| 103 |
|
| 104 |
+
# تحديد مجال الزمن الآمن
|
| 105 |
min_ts, max_ts = None, None
|
| 106 |
for sym in SIM_CONFIG["TEST_SYMBOLS"]:
|
| 107 |
for tf in REQUIRED_TFS:
|
|
|
|
| 112 |
min_ts = mn if min_ts is None else min(min_ts, mn)
|
| 113 |
max_ts = mx if max_ts is None else max(max_ts, mx)
|
| 114 |
if min_ts is None or max_ts is None:
|
| 115 |
+
print("❌ لا توجد بيانات صالحة.", flush=True); SIM_STATUS["running"] = False; return
|
| 116 |
|
| 117 |
boot_requirements_ms = []
|
| 118 |
for tf in REQUIRED_TFS:
|
|
|
|
| 139 |
|
| 140 |
print("🧠 Initializing hybrid system...", flush=True)
|
| 141 |
data_manager = DataManager(None, None, r2_service=r2_service, mock_exchange=mock_exchange)
|
| 142 |
+
learning_hub = LearningHubManager(r2_service, None, data_manager, disable_llm=True) # NO_LLM
|
| 143 |
await learning_hub.initialize()
|
| 144 |
processor = MLProcessor(None, data_manager, learning_hub)
|
| 145 |
await processor.initialize()
|
| 146 |
|
| 147 |
+
# تمرير عتبة الدخول للنظام الهجين
|
| 148 |
setattr(data_manager, 'HYBRID_ENTRY_THRESHOLD', float(SIM_CONFIG["ENTRY_THRESHOLD"]))
|
| 149 |
print(f"✅ Ready. Threshold={getattr(data_manager, 'HYBRID_ENTRY_THRESHOLD', 'NA')}", flush=True)
|
| 150 |
|
| 151 |
+
# حلقة المحاكاة
|
| 152 |
timeline = range(safe_start_ts, safe_end_ts, step_ms)
|
| 153 |
total = len(timeline)
|
| 154 |
bar = tqdm(timeline, desc="Simulating", unit="step")
|
| 155 |
|
| 156 |
+
def log(msg):
|
| 157 |
try: bar.write(msg)
|
| 158 |
except Exception: print(msg, flush=True)
|
| 159 |
|
|
|
|
| 160 |
for i, ts in enumerate(bar):
|
| 161 |
+
# تحديث الوقت
|
| 162 |
mock_exchange.set_time(ts)
|
| 163 |
|
| 164 |
if i % 10 == 0:
|
|
|
|
| 166 |
SIM_STATUS["current_balance"] = virtual_wallet.get_balance()
|
| 167 |
SIM_STATUS["trades_count"] = len(virtual_wallet.trade_history)
|
| 168 |
|
| 169 |
+
# إغلاقات قائمة (TP/SL/Soft/Hard)
|
| 170 |
prices = {s:(await mock_exchange.fetch_ticker(s))['last'] for s in SIM_CONFIG["TEST_SYMBOLS"]}
|
| 171 |
closed = virtual_wallet.update_positions(prices, ts)
|
| 172 |
for tr in closed:
|
| 173 |
log(f"[CLOSE] {tr['symbol']} reason={tr['close_reason']} pnl={tr['pnl_percent']:.2f}%")
|
| 174 |
await learning_hub.analyze_trade_and_learn(tr, tr['close_reason'])
|
| 175 |
|
| 176 |
+
# فتح مركز جديد إن لم توجد صفقات مفتوحة
|
| 177 |
if virtual_wallet.open_positions_count() < SIM_CONFIG["MAX_CONCURRENT_POSITIONS"]:
|
| 178 |
for symbol in SIM_CONFIG["TEST_SYMBOLS"]:
|
| 179 |
+
# تحقق توافر حزمة الفريمات
|
| 180 |
enough = True
|
| 181 |
for tf in REQUIRED_TFS:
|
| 182 |
candles = await mock_exchange.fetch_ohlcv(symbol, tf, limit=FETCH_LIMIT)
|
|
|
|
| 186 |
continue
|
| 187 |
|
| 188 |
packet = {tf: await mock_exchange.fetch_ohlcv(symbol, tf, limit=FETCH_LIMIT) for tf in REQUIRED_TFS}
|
| 189 |
+
cur_price = prices.get(symbol, 0.0)
|
| 190 |
+
res = await processor.process_and_score_symbol_enhanced({
|
| 191 |
+
'symbol': symbol, 'ohlcv': packet, 'current_price': cur_price
|
| 192 |
+
})
|
| 193 |
if not res:
|
| 194 |
continue
|
| 195 |
|
| 196 |
final_s = float(res['enhanced_final_score'])
|
| 197 |
thr = getattr(data_manager, 'HYBRID_ENTRY_THRESHOLD', 0.75)
|
| 198 |
+
comps = dict(res.get('components', {})); comps['final_score'] = final_s
|
| 199 |
+
log(f"[SCORE] {symbol} final={final_s:.3f} thr={thr:.3f} comps={comps}")
|
| 200 |
+
|
| 201 |
if final_s >= thr and virtual_wallet.can_enter(symbol, ts):
|
| 202 |
+
if virtual_wallet.execute_buy(symbol, cur_price, ts, comps):
|
| 203 |
+
log(f"[BUY] {symbol} @ {cur_price} bal=${virtual_wallet.get_balance():.2f}")
|
| 204 |
break # نحافظ على صفقة واحدة فقط
|
| 205 |
|
| 206 |
+
# إغلاق ما تبقى عند نهاية النافذة
|
| 207 |
print("\n🏁 نهاية فترة المحاكاة. إغلاق الصفقات المتبقية...", flush=True)
|
| 208 |
final_ts = safe_end_ts
|
| 209 |
mock_exchange.set_time(final_ts)
|