Riy777 commited on
Commit
505cee1
·
verified ·
1 Parent(s): fa87138

Create statistical_analyzer.py

Browse files
Files changed (1) hide show
  1. learning_hub/statistical_analyzer.py +340 -0
learning_hub/statistical_analyzer.py ADDED
@@ -0,0 +1,340 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # learning_hub/statistical_analyzer.py
2
+ # (هذا الملف هو النسخة المطورة من learning_engine (39).py القديم)
3
+ # وهو يمثل "التعلم البطيء" (الإحصائي)
4
+
5
+ import json
6
+ import asyncio
7
+ from datetime import datetime
8
+ from typing import Dict, Any, List
9
+ import numpy as np
10
+
11
+ # (نفترض أن هذه الدوال المساعدة سيتم نقلها إلى ملف helpers.py عام)
12
+ # (لأغراض هذا الملف، سنعرفها هنا مؤقتاً)
13
+ def normalize_weights(weights_dict):
14
+ total = sum(weights_dict.values())
15
+ if total > 0:
16
+ for key in weights_dict:
17
+ weights_dict[key] /= total
18
+ return weights_dict
19
+
20
+ def should_update_weights(history_length):
21
+ return history_length % 5 == 0 # (تحديث الأوزان كل 5 صفقات)
22
+
23
+
24
+ class StatisticalAnalyzer:
25
+ def __init__(self, r2_service: Any, data_manager: Any):
26
+ self.r2_service = r2_service
27
+ self.data_manager = data_manager # (لجلب سياق السوق)
28
+
29
+ # --- (هذه هي نفس متغيرات الحالة من learning_engine القديم) ---
30
+ self.weights = {} # (أوزان استراتيجيات الدخول)
31
+ self.performance_history = []
32
+ self.strategy_effectiveness = {} # (إحصائيات استراتيجيات الدخول)
33
+ self.exit_profile_effectiveness = {} # (إحصائيات مزيج الدخول+الخروج)
34
+ self.market_patterns = {}
35
+ # --- (نهاية متغيرات الحالة) ---
36
+
37
+ self.initialized = False
38
+ self.lock = asyncio.Lock()
39
+
40
+ print("✅ Learning Hub Module: Statistical Analyzer (Slow-Learner) loaded")
41
+
42
+ async def initialize(self):
43
+ """تهيئة المحلل الإحصائي (التعلم البطيء)"""
44
+ async with self.lock:
45
+ if self.initialized:
46
+ return
47
+ print("🔄 [StatsAnalyzer] تهيئة نظام التعلم الإحصائي (البطيء)...")
48
+ try:
49
+ await self.load_weights_from_r2()
50
+ await self.load_performance_history()
51
+ await self.load_exit_profile_effectiveness()
52
+
53
+ if not self.weights or not self.strategy_effectiveness:
54
+ await self.initialize_default_weights()
55
+
56
+ self.initialized = True
57
+ print("✅ [StatsAnalyzer] نظام التعلم الإحصائي جاهز.")
58
+ except Exception as e:
59
+ print(f"❌ [StatsAnalyzer] فشل التهيئة: {e}")
60
+ await self.initialize_default_weights()
61
+ self.initialized = True
62
+
63
+ # ---------------------------------------------------------------------------
64
+ # (الدوال التالية مأخوذة مباشرة من learning_engine (39).py القديم)
65
+ # (مع تعديلات طفيفة)
66
+ # ---------------------------------------------------------------------------
67
+
68
+ async def initialize_default_weights(self):
69
+ """إعادة تعيين الأوزان إلى الوضع الافتراضي"""
70
+ self.weights = {
71
+ "strategy_weights": {
72
+ "trend_following": 0.18, "mean_reversion": 0.15, "breakout_momentum": 0.22,
73
+ "volume_spike": 0.12, "whale_tracking": 0.15, "pattern_recognition": 0.10,
74
+ "hybrid_ai": 0.08
75
+ },
76
+ # (يمكننا إضافة أوزان المؤشرات والأنماط هنا كما طلبت)
77
+ "indicator_weights": {
78
+ "rsi": 0.2, "macd": 0.2, "bbands": 0.15, "atr": 0.1,
79
+ "volume_ratio": 0.2, "vwap": 0.15
80
+ },
81
+ "pattern_weights": {
82
+ "Double Bottom": 0.3, "Breakout Up": 0.3, "Uptrend": 0.2,
83
+ "Near Support": 0.2, "Double Top": -0.3 # (وزن سلبي)
84
+ }
85
+ }
86
+ self.strategy_effectiveness = {}
87
+ self.exit_profile_effectiveness = {}
88
+ self.market_patterns = {}
89
+
90
+ async def load_weights_from_r2(self):
91
+ key = "learning_statistical_weights.json" # (ملف جديد)
92
+ try:
93
+ response = self.r2_service.s3_client.get_object(Bucket="trading", Key=key)
94
+ data = json.loads(response['Body'].read())
95
+ self.weights = data.get("weights", {})
96
+ self.strategy_effectiveness = data.get("strategy_effectiveness", {})
97
+ self.market_patterns = data.get("market_patterns", {})
98
+ print(f"✅ [StatsAnalyzer] تم تحميل الأوزان والإحصائيات من R2.")
99
+ except Exception as e:
100
+ print(f"ℹ️ [StatsAnalyzer] فشل تحميل الأوزان ({e}). استخدام الافتراضيات.")
101
+ await self.initialize_default_weights()
102
+
103
+ async def save_weights_to_r2(self):
104
+ key = "learning_statistical_weights.json"
105
+ try:
106
+ data = {
107
+ "weights": self.weights,
108
+ "strategy_effectiveness": self.strategy_effectiveness,
109
+ "market_patterns": self.market_patterns,
110
+ "last_updated": datetime.now().isoformat()
111
+ }
112
+ data_json = json.dumps(data, indent=2, ensure_ascii=False).encode('utf-8')
113
+ self.r2_service.s3_client.put_object(
114
+ Bucket="trading", Key=key, Body=data_json, ContentType="application/json"
115
+ )
116
+ except Exception as e:
117
+ print(f"❌ [StatsAnalyzer] فشل حفظ الأوزان في R2: {e}")
118
+
119
+ async def load_performance_history(self):
120
+ key = "learning_performance_history.json" # (مشترك)
121
+ try:
122
+ response = self.r2_service.s3_client.get_object(Bucket="trading", Key=key)
123
+ data = json.loads(response['Body'].read())
124
+ self.performance_history = data.get("history", [])
125
+ except Exception as e:
126
+ self.performance_history = []
127
+
128
+ async def save_performance_history(self):
129
+ key = "learning_performance_history.json"
130
+ try:
131
+ data = {"history": self.performance_history[-1000:]} # (آخر 1000 صفقة فقط)
132
+ data_json = json.dumps(data, indent=2, ensure_ascii=False).encode('utf-8')
133
+ self.r2_service.s3_client.put_object(
134
+ Bucket="trading", Key=key, Body=data_json, ContentType="application/json"
135
+ )
136
+ except Exception as e:
137
+ print(f"❌ [StatsAnalyzer] فشل حفظ تاريخ الأداء: {e}")
138
+
139
+ async def load_exit_profile_effectiveness(self):
140
+ key = "learning_exit_profile_effectiveness.json" # (مشترك)
141
+ try:
142
+ response = self.r2_service.s3_client.get_object(Bucket="trading", Key=key)
143
+ data = json.loads(response['Body'].read())
144
+ self.exit_profile_effectiveness = data.get("effectiveness", {})
145
+ except Exception as e:
146
+ self.exit_profile_effectiveness = {}
147
+
148
+ async def save_exit_profile_effectiveness(self):
149
+ key = "learning_exit_profile_effectiveness.json"
150
+ try:
151
+ data = {
152
+ "effectiveness": self.exit_profile_effectiveness,
153
+ "last_updated": datetime.now().isoformat()
154
+ }
155
+ data_json = json.dumps(data, indent=2, ensure_ascii=False).encode('utf-8')
156
+ self.r2_service.s3_client.put_object(
157
+ Bucket="trading", Key=key, Body=data_json, ContentType="application/json"
158
+ )
159
+ except Exception as e:
160
+ print(f"❌ [StatsAnalyzer] فشل حفظ أداء ملف الخروج: {e}")
161
+
162
+ async def update_statistics(self, trade_object: Dict[str, Any], close_reason: str):
163
+ """
164
+ هذه هي الدالة الرئيسية التي تحدث الإحصائيات (التعلم البطيء).
165
+ (تدمج update_strategy_effectiveness و update_market_patterns من الملف القديم)
166
+ """
167
+ if not self.initialized:
168
+ await self.initialize()
169
+
170
+ try:
171
+ strategy = trade_object.get('strategy', 'unknown')
172
+ decision_data = trade_object.get('decision_data', {})
173
+ exit_profile = decision_data.get('exit_profile', 'unknown')
174
+ combined_key = f"{strategy}_{exit_profile}"
175
+
176
+ pnl_percent = trade_object.get('pnl_percent', 0)
177
+ is_success = pnl_percent > 0.1 # (اعتبار الربح الطفيف نجاحاً)
178
+
179
+ market_context = await self.get_current_market_conditions()
180
+ market_condition = market_context.get('current_trend', 'sideways_market')
181
+
182
+ # --- 1. تحديث تاريخ الأداء (للتتبع العام) ---
183
+ analysis_entry = {
184
+ "timestamp": datetime.now().isoformat(),
185
+ "trade_id": trade_object.get('id', 'N/A'),
186
+ "symbol": trade_object.get('symbol', 'N/A'),
187
+ "outcome": close_reason,
188
+ "market_conditions": market_context,
189
+ "strategy_used": strategy,
190
+ "exit_profile_used": exit_profile,
191
+ "pnl_percent": pnl_percent
192
+ }
193
+ self.performance_history.append(analysis_entry)
194
+
195
+ # --- 2. تحديث إحصائيات استراتيجية الدخول (strategy_effectiveness) ---
196
+ if strategy not in self.strategy_effectiveness:
197
+ self.strategy_effectiveness[strategy] = {"total_trades": 0, "successful_trades": 0, "total_pnl_percent": 0}
198
+
199
+ self.strategy_effectiveness[strategy]["total_trades"] += 1
200
+ self.strategy_effectiveness[strategy]["total_pnl_percent"] += pnl_percent
201
+ if is_success:
202
+ self.strategy_effectiveness[strategy]["successful_trades"] += 1
203
+
204
+ # --- 3. تحديث إحصائيات مزيج (الدخول + الخروج) (exit_profile_effectiveness) ---
205
+ if combined_key not in self.exit_profile_effectiveness:
206
+ self.exit_profile_effectiveness[combined_key] = {"total_trades": 0, "successful_trades": 0, "total_pnl_percent": 0, "pnl_list": []}
207
+
208
+ self.exit_profile_effectiveness[combined_key]["total_trades"] += 1
209
+ self.exit_profile_effectiveness[combined_key]["total_pnl_percent"] += pnl_percent
210
+ self.exit_profile_effectiveness[combined_key]["pnl_list"].append(pnl_percent)
211
+ if len(self.exit_profile_effectiveness[combined_key]["pnl_list"]) > 100:
212
+ self.exit_profile_effectiveness[combined_key]["pnl_list"] = self.exit_profile_effectiveness[combined_key]["pnl_list"][-100:]
213
+ if is_success:
214
+ self.exit_profile_effectiveness[combined_key]["successful_trades"] += 1
215
+
216
+ # --- 4. تحديث إحصائيات ظروف السوق (market_patterns) ---
217
+ if market_condition not in self.market_patterns:
218
+ self.market_patterns[market_condition] = {"total_trades": 0, "successful_trades": 0, "total_pnl_percent": 0}
219
+
220
+ self.market_patterns[market_condition]["total_trades"] += 1
221
+ self.market_patterns[market_condition]["total_pnl_percent"] += pnl_percent
222
+ if is_success:
223
+ self.market_patterns[market_condition]["successful_trades"] += 1
224
+
225
+ # --- 5. تكييف الأوزان والحفظ (إذا لزم الأمر) ---
226
+ if should_update_weights(len(self.performance_history)):
227
+ await self.adapt_weights_based_on_performance()
228
+ await self.save_weights_to_r2()
229
+ await self.save_performance_history()
230
+ await self.save_exit_profile_effectiveness()
231
+
232
+ print(f"✅ [StatsAnalyzer] تم تحديث الإحصائيات لـ {strategy} / {exit_profile}")
233
+
234
+ except Exception as e:
235
+ print(f"❌ [StatsAnalyzer] فشل تحديث الإحصائيات: {e}")
236
+ traceback.print_exc()
237
+
238
+ async def adapt_weights_based_on_performance(self):
239
+ """تكييف أوزان استراتيجيات الدخول بناءً على الأداء الإحصائي"""
240
+ print("🔄 [StatsAnalyzer] تكييف أوزان الاستراتيجيات (التعلم البطيء)...")
241
+ try:
242
+ strategy_performance = {}
243
+ total_performance = 0
244
+
245
+ for strategy, data in self.strategy_effectiveness.items():
246
+ if data.get("total_trades", 0) > 2: # (يتطلب 3 صفقات على الأقل للتكيف)
247
+ success_rate = data["successful_trades"] / data["total_trades"]
248
+ avg_pnl = data["total_pnl_percent"] / data["total_trades"]
249
+
250
+ # مقياس مركب: (معدل النجاح * 60%) + (متوسط الربح * 40%)
251
+ # (يتم تقييد متوسط الربح بين -5 و +5)
252
+ normalized_pnl = min(max(avg_pnl, -5.0), 5.0) / 5.0 # (من -1 إلى 1)
253
+
254
+ composite_performance = (success_rate * 0.6) + (normalized_pnl * 0.4)
255
+
256
+ strategy_performance[strategy] = composite_performance
257
+ total_performance += composite_performance
258
+
259
+ if total_performance > 0 and strategy_performance:
260
+ base_weights = self.weights.get("strategy_weights", {})
261
+ for strategy, performance in strategy_performance.items():
262
+ current_weight = base_weights.get(strategy, 0.1)
263
+
264
+ # (تعديل طفيف: 80% من الوزن الحالي + 20% من الأداء)
265
+ new_weight = (current_weight * 0.8) + (performance * 0.2)
266
+ base_weights[strategy] = max(new_weight, 0.05) # (الحد الأدنى للوزن 5%)
267
+
268
+ normalize_weights(base_weights)
269
+ self.weights["strategy_weights"] = base_weights
270
+ print(f"✅ [StatsAnalyzer] تم تكييف الأوزان: {base_weights}")
271
+
272
+ except Exception as e:
273
+ print(f"❌ [StatsAnalyzer] فشل تكييف الأوزان: {e}")
274
+
275
+ # --- (الدوال المساعدة لجلب البيانات - مأخوذة من الملف القديم) ---
276
+ async def get_best_exit_profile(self, entry_strategy: str) -> str:
277
+ """يجد أفضل ملف خروج إحصائياً لاستراتيجية دخول معينة."""
278
+ if not self.initialized or not self.exit_profile_effectiveness:
279
+ return "unknown"
280
+
281
+ relevant_profiles = {}
282
+ for combined_key, data in self.exit_profile_effectiveness.items():
283
+ if combined_key.startswith(f"{entry_strategy}_"):
284
+ if data.get("total_trades", 0) >= 3: # (يتطلب 3 صفقات)
285
+ exit_profile_name = combined_key.replace(f"{entry_strategy}_", "", 1)
286
+ avg_pnl = data["total_pnl_percent"] / data["total_trades"]
287
+ relevant_profiles[exit_profile_name] = avg_pnl
288
+
289
+ if not relevant_profiles:
290
+ return "unknown"
291
+
292
+ best_profile = max(relevant_profiles, key=relevant_profiles.get)
293
+ return best_profile
294
+
295
+ async def get_optimized_strategy_weights(self, market_condition: str) -> Dict[str, float]:
296
+ """جلب أوزان الاستراتيجيات المعدلة إحصائياً."""
297
+ if not self.initialized or "strategy_weights" not in self.weights:
298
+ await self.initialize()
299
+
300
+ base_weights = self.weights.get("strategy_weights", {}).copy()
301
+
302
+ # (يمكننا إضافة منطق تعديل الأوزان بناءً على ظروف السوق هنا)
303
+ # (لكن في الوقت الحالي، سنعيد الأوزان المعدلة إحصائياً)
304
+
305
+ if not base_weights:
306
+ # (العودة إلى الافتراضيات إذا كانت الأوزان فارغة)
307
+ defaults = await self.get_default_strategy_weights()
308
+ return defaults.get("strategy_weights", {})
309
+
310
+ return base_weights
311
+
312
+ async def get_default_strategy_weights(self) -> Dict[str, float]:
313
+ """إرجاع الأوزان الافتراضية عند الفشل"""
314
+ return {
315
+ "strategy_weights": {
316
+ "trend_following": 0.18, "mean_reversion": 0.15, "breakout_momentum": 0.22,
317
+ "volume_spike": 0.12, "whale_tracking": 0.15, "pattern_recognition": 0.10,
318
+ "hybrid_ai": 0.08
319
+ }
320
+ }
321
+
322
+ async def get_current_market_conditions(self) -> Dict[str, Any]:
323
+ """جلب سياق السوق الحالي (من الملف القديم)"""
324
+ try:
325
+ if not self.data_manager:
326
+ raise ValueError("DataManager unavailable")
327
+ market_context = await self.data_manager.get_market_context_async()
328
+ if not market_context:
329
+ raise ValueError("Market context fetch failed")
330
+
331
+ # (نحتاج دالة لحساب التقلب - نفترض أنها في helpers)
332
+ # volatility = calculate_market_volatility(market_context)
333
+
334
+ return {
335
+ "current_trend": market_context.get('market_trend', 'sideways_market'),
336
+ "volatility": "medium", # (قيمة مؤقتة)
337
+ "market_sentiment": market_context.get('btc_sentiment', 'NEUTRAL'),
338
+ }
339
+ except Exception as e:
340
+ return {"current_trend": "sideways_market", "volatility": "medium", "market_sentiment": "NEUTRAL"}