Riy777 commited on
Commit
713e0f7
·
1 Parent(s): adacd38

Update ml_engine/monte_carlo.py

Browse files
Files changed (1) hide show
  1. ml_engine/monte_carlo.py +194 -60
ml_engine/monte_carlo.py CHANGED
@@ -1,7 +1,16 @@
1
  # ml_engine/monte_carlo.py
2
  import numpy as np
3
- # 🔴 لا نحتاج scipy.stats إذا استخدمنا np.random.standard_t
4
- # from scipy.stats import t as student_t
 
 
 
 
 
 
 
 
 
5
 
6
  class MonteCarloAnalyzer:
7
  def __init__(self):
@@ -9,23 +18,21 @@ class MonteCarloAnalyzer:
9
 
10
  async def generate_1h_price_distribution(self, ohlcv_data, target_profit_percent=0.005):
11
  """
12
- (مُرقّى - المرحلة 1)
13
- محاكاة مونت كارلو متقدمة لتوليد توزيع سعري للساعة القادمة.
14
  - تستخدم توزيع Student-t (للذيول الثقيلة).
15
  - تستخدم نموذج Merton Jump-Diffusion (للقفزات السعرية).
16
- - تُرجع توزيعاً كاملاً، فترات ثقة، ومقاييس مخاطرة.
17
  """
18
  try:
19
  # 1. التحقق من جودة البيانات
20
  if not ohlcv_data or '1h' not in ohlcv_data or len(ohlcv_data['1h']) < 30:
21
  if '15m' in ohlcv_data and len(ohlcv_data['15m']) >= 50:
22
- # استخدام بيانات 15m كاحتياطي إذا كانت 1h غير كافية
23
  closes = np.array([candle[4] for candle in ohlcv_data['15m']])
24
  else:
25
  self.simulation_results = {'error': 'Insufficient OHLCV data (< 30 candles 1h)'}
26
  return None
27
  else:
28
- # دمج بيانات 1h و 15m (إن وجدت) لبيانات إحصائية أفضل
29
  all_closes = [candle[4] for candle in ohlcv_data['1h']]
30
  if '15m' in ohlcv_data and len(ohlcv_data['15m']) >= 16:
31
  all_closes.extend([candle[4] for candle in ohlcv_data['15m'][-16:]])
@@ -48,102 +55,229 @@ class MonteCarloAnalyzer:
48
  self.simulation_results = {'error': 'Insufficient log returns (< 20)'}
49
  return None
50
 
 
51
  mean_return = np.mean(log_returns)
52
  std_return = np.std(log_returns)
53
 
54
- # 3. إعداد باراميترات المحاكاة (المرحلة 1)
55
- num_simulations = 5000 # عدد المسارات (5k كافية مع Student-t)
56
- t_df = 10 # درجات الحرية (DOF) لتوزيع Student-t (أقل = ذيول أثقل)
57
-
58
- # باراميترات القفز (Merton Jump-Diffusion)
59
  jump_lambda = 0.05 # احتمالية حدوث قفزة في الساعة (5%)
60
- jump_mean = 0.0 # متوسط حجم القفزة (متمركز حول الصفر)
61
- jump_std = std_return * 3.0 # تقلب القفزة (3 أضعاف التقلب العادي)
62
 
63
- # 4. تشغيل المحاكاة
64
-
65
- # المكون الأول: الانجراف (Drift)
66
- # (التعديل لـ Geometric Brownian Motion)
67
  drift = (mean_return - 0.5 * std_return**2)
68
-
69
- # المكون الثاني: التقلب (Diffusion) - باستخدام Student-t
70
- # (يولد أرقاماً عشوائية تتبع توزيع t)
71
  diffusion = std_return * np.random.standard_t(df=t_df, size=num_simulations)
72
-
73
- # المكون الثالث: القفزات (Jumps)
74
  jump_mask = np.random.rand(num_simulations) < jump_lambda
75
  jump_sizes = np.random.normal(jump_mean, jump_std, size=num_simulations)
76
  jump_component = np.zeros(num_simulations)
77
  jump_component[jump_mask] = jump_sizes[jump_mask]
78
-
79
- # تجميع العوائد المتوقعة
80
  simulated_log_returns = drift + diffusion + jump_component
81
-
82
- # حساب الأسعار النهائية المتوقعة
83
  simulated_prices = current_price * np.exp(simulated_log_returns)
84
 
85
- # 5. حساب المخرجات والتوزيع
86
-
87
- # حساب التوزيع الإحصائي
88
  mean_price = np.mean(simulated_prices)
89
  median_price = np.median(simulated_prices)
90
-
91
- # حساب فترات الثقة (Prediction Intervals)
92
  percentiles = np.percentile(simulated_prices, [2.5, 5, 25, 50, 75, 95, 97.5])
93
-
94
  pi_95 = [percentiles[0], percentiles[-1]]
95
  pi_90 = [percentiles[1], percentiles[-2]]
96
- pi_50 = [percentiles[2], percentiles[4]] # يُعرف أيضاً بـ Interquartile Range (IQR)
97
-
98
- # حساب مقاييس المخاطرة (VaR و CVaR)
99
- # VaR @ 95%: ما هي أقصى خسارة متوقعة في 95% من السيناريوهات؟ (الخسارة عند النسبة المئوية 5)
100
  VaR_95_price = percentiles[1]
101
  VaR_95_value = current_price - VaR_95_price
102
-
103
- # CVaR @ 95%: ما هو متوسط الخسارة إذا تجاوزنا عتبة VaR؟ (متوسط كل الأسعار الأقل من VaR_95_price)
104
  losses_beyond_var = simulated_prices[simulated_prices <= VaR_95_price]
105
- CVaR_95_price = np.mean(losses_beyond_var) if len(losses_beyond_var) > 0 else VaR_95_price
106
- CVaR_95_value = current_price - CVaR_95_price
107
-
108
- # حساب الاحتمالية المطلوبة (للتوافق الجزئي مع الدرجات)
109
  target_price = current_price * (1 + target_profit_percent)
110
  probability_of_gain = np.mean(simulated_prices >= target_price)
111
 
112
- # 6. تجميع النتائج
113
  self.simulation_results = {
114
  'simulation_model': 'Phase1_Student-t_JumpDiffusion',
115
  'num_simulations': num_simulations,
116
  'current_price': current_price,
117
- 'distribution_summary': {
118
- 'mean_price': mean_price,
119
- 'median_price': median_price,
120
- },
121
  'prediction_interval_50': pi_50,
122
  'prediction_interval_90': pi_90,
123
  'prediction_interval_95': pi_95,
124
  'risk_metrics': {
125
  'VaR_95_price': VaR_95_price,
126
  'VaR_95_value': VaR_95_value,
127
- 'CVaR_95_price': CVaR_95_price,
128
  'CVaR_95_value': CVaR_95_value,
129
  },
130
- 'probability_of_gain': probability_of_gain, # (P >= 0.5% profit)
131
- 'raw_simulated_prices': simulated_prices[:100] # عينة صغيرة للتتبع
132
  }
133
-
134
  return self.simulation_results
135
 
136
  except Exception as e:
137
- print(f"❌ خطأ فادح في محاكاة مونت كارلو المتقدمة: {e}")
138
- self.simulation_results = {'error': f'Fatal simulation error: {e}'}
139
  return None
140
-
141
- # 🔴 الدالة القديمة (محذوفة)
142
- # async def predict_1h_probability(self, ohlcv_data): ...
143
 
144
- # 🔴 دالة حساب الاتجاه (غير مستخدمة حالياً في المرحلة 1، لكنها قد تكون مفيدة لاحقاً)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  def _calculate_trend_adjustment(self, closes):
146
- """حساب معامل تعديل الاتجاه"""
147
  try:
148
  if len(closes) < 10: return 1.0
149
  recent_trend = (closes[-1] - closes[-10]) / closes[-10]
@@ -154,4 +288,4 @@ class MonteCarloAnalyzer:
154
  else: return 1.0
155
  except Exception: return 1.0
156
 
157
- print("✅ ML Module: Advanced Monte Carlo Analyzer loaded (Phase 1: T-Dist + Jumps)")
 
1
  # ml_engine/monte_carlo.py
2
  import numpy as np
3
+ import pandas as pd
4
+ from arch import arch_model
5
+ import lightgbm as lgb
6
+ from sklearn.preprocessing import StandardScaler
7
+ # نستورد مكتبات المؤشرات (نفترض وجودها في ملف المؤشرات أو نحسبها يدوياً)
8
+ # لتبسيط الأمر هنا، سنستخدم pandas_ta إذا كانت متاحة، أو حسابات بسيطة
9
+ try:
10
+ import pandas_ta as ta
11
+ except ImportError:
12
+ print("⚠️ مكتبة pandas_ta غير موجودة، سيتم استخدام حسابات يدوية للمؤشرات.")
13
+ ta = None
14
 
15
  class MonteCarloAnalyzer:
16
  def __init__(self):
 
18
 
19
  async def generate_1h_price_distribution(self, ohlcv_data, target_profit_percent=0.005):
20
  """
21
+ (المرحلة 1 - سريعة)
22
+ محاكاة مونت كارلو لتوليد توزيع سعري للساعة القادمة (للفرز الأولي).
23
  - تستخدم توزيع Student-t (للذيول الثقيلة).
24
  - تستخدم نموذج Merton Jump-Diffusion (للقفزات السعرية).
25
+ - تستخدم المتوسط/الانحراف التاريخي البسيط.
26
  """
27
  try:
28
  # 1. التحقق من جودة البيانات
29
  if not ohlcv_data or '1h' not in ohlcv_data or len(ohlcv_data['1h']) < 30:
30
  if '15m' in ohlcv_data and len(ohlcv_data['15m']) >= 50:
 
31
  closes = np.array([candle[4] for candle in ohlcv_data['15m']])
32
  else:
33
  self.simulation_results = {'error': 'Insufficient OHLCV data (< 30 candles 1h)'}
34
  return None
35
  else:
 
36
  all_closes = [candle[4] for candle in ohlcv_data['1h']]
37
  if '15m' in ohlcv_data and len(ohlcv_data['15m']) >= 16:
38
  all_closes.extend([candle[4] for candle in ohlcv_data['15m'][-16:]])
 
55
  self.simulation_results = {'error': 'Insufficient log returns (< 20)'}
56
  return None
57
 
58
+ # 🔴 استخدام المتوسط والانحراف التاريخي (بسيط وسريع)
59
  mean_return = np.mean(log_returns)
60
  std_return = np.std(log_returns)
61
 
62
+ # 3. إعداد باراميترات المحاكاة
63
+ num_simulations = 5000 # عدد المسارات
64
+ t_df = 10 # درجات الحرية (DOF) لتوزيع Student-t
 
 
65
  jump_lambda = 0.05 # احتمالية حدوث قفزة في الساعة (5%)
66
+ jump_mean = 0.0 # متوسط حجم القفزة
67
+ jump_std = std_return * 3.0 # تقلب القفزة
68
 
69
+ # 4. تشغيل المحاكاة (كما في الإصدار السابق)
 
 
 
70
  drift = (mean_return - 0.5 * std_return**2)
 
 
 
71
  diffusion = std_return * np.random.standard_t(df=t_df, size=num_simulations)
 
 
72
  jump_mask = np.random.rand(num_simulations) < jump_lambda
73
  jump_sizes = np.random.normal(jump_mean, jump_std, size=num_simulations)
74
  jump_component = np.zeros(num_simulations)
75
  jump_component[jump_mask] = jump_sizes[jump_mask]
 
 
76
  simulated_log_returns = drift + diffusion + jump_component
 
 
77
  simulated_prices = current_price * np.exp(simulated_log_returns)
78
 
79
+ # 5. حساب المخرجات والتوزيع (كما في الإصدار السابق)
 
 
80
  mean_price = np.mean(simulated_prices)
81
  median_price = np.median(simulated_prices)
 
 
82
  percentiles = np.percentile(simulated_prices, [2.5, 5, 25, 50, 75, 95, 97.5])
 
83
  pi_95 = [percentiles[0], percentiles[-1]]
84
  pi_90 = [percentiles[1], percentiles[-2]]
85
+ pi_50 = [percentiles[2], percentiles[4]]
 
 
 
86
  VaR_95_price = percentiles[1]
87
  VaR_95_value = current_price - VaR_95_price
 
 
88
  losses_beyond_var = simulated_prices[simulated_prices <= VaR_95_price]
89
+ CVR_95_price = np.mean(losses_beyond_var) if len(losses_beyond_var) > 0 else VaR_95_price
90
+ CVaR_95_value = current_price - CVR_95_price
 
 
91
  target_price = current_price * (1 + target_profit_percent)
92
  probability_of_gain = np.mean(simulated_prices >= target_price)
93
 
 
94
  self.simulation_results = {
95
  'simulation_model': 'Phase1_Student-t_JumpDiffusion',
96
  'num_simulations': num_simulations,
97
  'current_price': current_price,
98
+ 'distribution_summary': {'mean_price': mean_price, 'median_price': median_price},
 
 
 
99
  'prediction_interval_50': pi_50,
100
  'prediction_interval_90': pi_90,
101
  'prediction_interval_95': pi_95,
102
  'risk_metrics': {
103
  'VaR_95_price': VaR_95_price,
104
  'VaR_95_value': VaR_95_value,
105
+ 'CVaR_95_price': CVR_95_price,
106
  'CVaR_95_value': CVaR_95_value,
107
  },
108
+ 'probability_of_gain': probability_of_gain,
109
+ 'raw_simulated_prices': simulated_prices[:100]
110
  }
 
111
  return self.simulation_results
112
 
113
  except Exception as e:
114
+ self.simulation_results = {'error': f'Phase 1 MC Error: {e}'}
 
115
  return None
 
 
 
116
 
117
+ # 🔴 --- دالة جديدة --- 🔴
118
+ async def generate_1h_distribution_advanced(self, ohlcv_data, target_profit_percent=0.005):
119
+ """
120
+ (المرحلة 2+3 - متقدمة)
121
+ محاكاة مونت كارلو لتوليد توزيع سعري دقيق (لأفضل 10 مرشحين).
122
+ - تستخدم GARCH(1,1) لتوقع التقلب (Phase 2).
123
+ - تستخدم LightGBM لتوقع الميل/Drift (Phase 3).
124
+ - تستخدم Student-t و Jumps للمحاكاة.
125
+ """
126
+ try:
127
+ # 1. إعداد البيانات (DataFrame)
128
+ # نستخدم إطار 1h لأنه الأنسب لـ GARCH/LGBM لتوقع الساعة القادمة
129
+ if not ohlcv_data or '1h' not in ohlcv_data or len(ohlcv_data['1h']) < 50:
130
+ self.simulation_results = {'error': 'Advanced MC requires 1h data (>= 50 candles)'}
131
+ # كحل احتياطي، يمكننا العودة للنموذج البسيط إذا فشل المتقدم
132
+ return await self.generate_1h_price_distribution(ohlcv_data, target_profit_percent)
133
+
134
+ candles = ohlcv_data['1h']
135
+ df = pd.DataFrame(candles, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
136
+ df[['open', 'high', 'low', 'close', 'volume']] = df[['open', 'high', 'low', 'close', 'volume']].astype(float)
137
+ df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
138
+ df.set_index('timestamp', inplace=True)
139
+ df.sort_index(inplace=True)
140
+
141
+ if df.empty or len(df) < 50:
142
+ raise ValueError("DataFrame creation failed or insufficient data after processing")
143
+
144
+ current_price = df['close'].iloc[-1]
145
+
146
+ # 2. حساب العوائد اللوغاريتمية (أساس كل الحسابات)
147
+ df['log_returns'] = np.log(df['close'] / df['close'].shift(1)).fillna(0)
148
+ log_returns_series = df['log_returns'].replace([np.inf, -np.inf], 0)
149
+
150
+ # 3. (Phase 2) توقع التقلب باستخدام GARCH(1,1)
151
+ try:
152
+ # نضرب العوائد في 100 لتساعد GARCH على الاستقرار (ممارسة شائعة)
153
+ garch_model = arch_model(log_returns_series * 100, vol='Garch', p=1, q=1, dist='t')
154
+ # 🔴 استخدام disp='off' لإيقاف الطباعة الكثيفة، كما طلبت
155
+ res = garch_model.fit(update_freq=0, disp='off')
156
+ forecast = res.forecast(horizon=1)
157
+ # أخذ التباين المتوقع (variance) وقسمته على 10000 (لأننا ضربنا في 100)
158
+ forecasted_var = forecast.variance.iloc[-1, 0] / 10000
159
+ forecasted_std_return = np.sqrt(forecasted_var)
160
+ except Exception as garch_err:
161
+ # في حال فشل GARCH (بيانات غير مستقرة)، نعود للانحراف المعياري العادي
162
+ forecasted_std_return = np.std(log_returns_series.iloc[-30:]) # انحراف آخر 30 شمعة
163
+ print(f"⚠️ GARCH failed, using std: {garch_err}")
164
+
165
+
166
+ # 4. (Phase 3) توقع الميل (Drift) باستخدام LightGBM
167
+ try:
168
+ # 4a. هندسة الميزات
169
+ if ta:
170
+ df['rsi'] = ta.rsi(df['close'], length=14)
171
+ macd = ta.macd(df['close'], fast=12, slow=26, signal=9)
172
+ df['macd_hist'] = macd['MACDh_12_26_9']
173
+ else: # حسابات يدوية بسيطة
174
+ delta = df['close'].diff()
175
+ gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
176
+ loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
177
+ rs = gain / loss
178
+ df['rsi'] = 100 - (100 / (1 + rs))
179
+ df['macd_hist'] = df['close'].ewm(span=12).mean() - df['close'].ewm(span=26).mean()
180
+
181
+ df['lag_1'] = df['log_returns'].shift(1)
182
+ df['lag_2'] = df['log_returns'].shift(2)
183
+
184
+ features = ['rsi', 'macd_hist', 'lag_1', 'lag_2']
185
+ df.dropna(inplace=True)
186
+
187
+ if df.empty or len(df) < 20:
188
+ raise ValueError("Insufficient data after feature engineering")
189
+
190
+ # 4b. إعداد بيانات التدريب والتنبؤ
191
+ df['target'] = df['log_returns'].shift(-1) # الهدف هو العائد *التالي*
192
+ df.dropna(inplace=True)
193
+
194
+ X = df[features]
195
+ y = df['target']
196
+
197
+ X_train, y_train = X.iloc[:-1], y.iloc[:-1] # كل البيانات ما عدا الأخيرة
198
+ X_predict = X.iloc[-1:] # آخر صف من الميزات للتنبؤ
199
+
200
+ # 4c. تدريب نموذج LGBM
201
+ lgbm_model = lgb.LGBMRegressor(n_estimators=100, learning_rate=0.1, n_jobs=1, verbose=-1)
202
+ lgbm_model.fit(X_train, y_train)
203
+
204
+ # 4d. التنبؤ بالميل
205
+ forecasted_mean_return = lgbm_model.predict(X_predict)[0]
206
+
207
+ except Exception as lgbm_err:
208
+ # في حال فشل LGBM، نعود للمتوسط العادي
209
+ forecasted_mean_return = np.mean(log_returns_series.iloc[-30:]) # متوسط آخر 30 شمعة
210
+ print(f"⚠️ LGBM failed, using mean: {lgbm_err}")
211
+
212
+ # 5. تشغيل المحاكاة بالقيم الديناميكية
213
+
214
+ # استخدام نفس الباراميترات (T-Dist, Jumps)
215
+ num_simulations = 5000
216
+ t_df = 10
217
+ jump_lambda = 0.05
218
+ jump_mean = 0.0
219
+ # تقلب القفزة يعتمد الآن على التقلب المتوقع من GARCH
220
+ jump_std = forecasted_std_return * 3.0
221
+
222
+ # 🔴 استخدام القيم المتوقعة
223
+ mean_return = forecasted_mean_return
224
+ std_return = forecasted_std_return
225
+
226
+ drift = (mean_return - 0.5 * std_return**2)
227
+ diffusion = std_return * np.random.standard_t(df=t_df, size=num_simulations)
228
+ jump_mask = np.random.rand(num_simulations) < jump_lambda
229
+ jump_sizes = np.random.normal(jump_mean, jump_std, size=num_simulations)
230
+ jump_component = np.zeros(num_simulations)
231
+ jump_component[jump_mask] = jump_sizes[jump_mask]
232
+
233
+ simulated_log_returns = drift + diffusion + jump_component
234
+ simulated_prices = current_price * np.exp(simulated_log_returns)
235
+
236
+ # 6. حساب المخرجات والتوزيع (نفس منطق المرحلة 1)
237
+ mean_price = np.mean(simulated_prices)
238
+ median_price = np.median(simulated_prices)
239
+ percentiles = np.percentile(simulated_prices, [2.5, 5, 25, 50, 75, 95, 97.5])
240
+ pi_95 = [percentiles[0], percentiles[-1]]
241
+ pi_90 = [percentiles[1], percentiles[-2]]
242
+ pi_50 = [percentiles[2], percentiles[4]]
243
+ VaR_95_price = percentiles[1]
244
+ VaR_95_value = current_price - VaR_95_price
245
+ losses_beyond_var = simulated_prices[simulated_prices <= VaR_95_price]
246
+ CVR_95_price = np.mean(losses_beyond_var) if len(losses_beyond_var) > 0 else VaR_95_price
247
+ CVaR_95_value = current_price - CVR_95_price
248
+ target_price = current_price * (1 + target_profit_percent)
249
+ probability_of_gain = np.mean(simulated_prices >= target_price)
250
+
251
+ self.simulation_results = {
252
+ 'simulation_model': 'Phase2_GARCH_LGBM', # 🔴 تحديد النموذج المتقدم
253
+ 'num_simulations': num_simulations,
254
+ 'current_price': current_price,
255
+ 'forecasted_drift_lgbm': forecasted_mean_return, # 🔴 إضافة للتتبع
256
+ 'forecasted_vol_garch': forecasted_std_return, # 🔴 إضافة للتتبع
257
+ 'distribution_summary': {'mean_price': mean_price, 'median_price': median_price},
258
+ 'prediction_interval_50': pi_50,
259
+ 'prediction_interval_90': pi_90,
260
+ 'prediction_interval_95': pi_95,
261
+ 'risk_metrics': {
262
+ 'VaR_95_price': VaR_95_price,
263
+ 'VaR_95_value': VaR_95_value,
264
+ 'CVaR_95_price': CVR_95_price,
265
+ 'CVaR_95_value': CVaR_95_value,
266
+ },
267
+ 'probability_of_gain': probability_of_gain,
268
+ 'raw_simulated_prices': simulated_prices[:100]
269
+ }
270
+ return self.simulation_results
271
+
272
+ except Exception as e:
273
+ print(f"❌ خطأ فادح في محاكاة مونت كارلو المتقدمة (GARCH/LGBM): {e}")
274
+ traceback.print_exc()
275
+ self.simulation_results = {'error': f'Advanced MC Error: {e}'}
276
+ # العودة إلى المرحلة 1 في حالة الفشل الفادح
277
+ return await self.generate_1h_price_distribution(ohlcv_data, target_profit_percent)
278
+
279
  def _calculate_trend_adjustment(self, closes):
280
+ """(غير مستخدمة حالياً)"""
281
  try:
282
  if len(closes) < 10: return 1.0
283
  recent_trend = (closes[-1] - closes[-10]) / closes[-10]
 
288
  else: return 1.0
289
  except Exception: return 1.0
290
 
291
+ print("✅ ML Module: Advanced Monte Carlo Analyzer loaded (Phase 1 + Phase 2/3)")