File size: 10,684 Bytes
12edcfb
9dd6d6b
12edcfb
 
 
 
3d8478b
12edcfb
 
dabf7c9
12edcfb
 
 
 
 
 
 
9dd6d6b
 
 
12edcfb
 
9dd6d6b
0a66704
6f2b215
b0a91f6
9dd6d6b
b9ac513
9dd6d6b
b9ac513
9dd6d6b
 
 
b9ac513
9dd6d6b
 
 
b9ac513
 
9dd6d6b
 
 
 
 
 
b0a91f6
9dd6d6b
12edcfb
b9ac513
 
12edcfb
22847cc
12edcfb
 
8ba8c3d
5766c42
8ba8c3d
 
 
6f2b215
9dd6d6b
8ba8c3d
 
9dd6d6b
2bd1c15
12edcfb
5766c42
b0a91f6
b9ac513
 
 
9dd6d6b
 
b0a91f6
 
12edcfb
 
9dd6d6b
 
 
5766c42
12edcfb
 
22847cc
9dd6d6b
6f2b215
b0a91f6
9dd6d6b
b9ac513
b0a91f6
 
 
 
 
b9ac513
b0a91f6
 
 
 
b9ac513
 
b0a91f6
12edcfb
9dd6d6b
6f2b215
 
 
 
 
 
8ba8c3d
6f2b215
 
 
9dd6d6b
6f2b215
1bf16da
 
 
 
 
 
 
6f2b215
 
 
 
 
 
 
1bf16da
b9ac513
6f2b215
 
1bf16da
b9ac513
6f2b215
 
 
22847cc
b9ac513
9dd6d6b
5766c42
6f2b215
7b309e2
b0a91f6
9dd6d6b
b0a91f6
22847cc
12edcfb
9dd6d6b
6f2b215
b9ac513
 
22847cc
9dd6d6b
b9ac513
8ba8c3d
22847cc
b9ac513
9dd6d6b
b9ac513
22847cc
3d8478b
b9ac513
22847cc
6f2b215
5766c42
9dd6d6b
b9ac513
 
 
22847cc
 
 
9dd6d6b
b9ac513
 
9dd6d6b
b9ac513
 
 
 
 
 
 
 
 
9dd6d6b
 
 
 
b9ac513
 
 
 
 
9dd6d6b
 
 
b9ac513
9dd6d6b
 
b9ac513
 
9dd6d6b
22847cc
6f2b215
3d8478b
b9ac513
 
 
22847cc
 
12edcfb
22847cc
5766c42
 
22847cc
b9ac513
22847cc
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# 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)