Riy777 commited on
Commit
c7cd3b6
·
1 Parent(s): ab034d5

Update learning_hub/hub_manager.py

Browse files
Files changed (1) hide show
  1. learning_hub/hub_manager.py +122 -371
learning_hub/hub_manager.py CHANGED
@@ -1,400 +1,151 @@
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
- # 🔴 --- START OF CHANGE --- 🔴
71
- self.weights = {
72
- # 1. أوزان اختيار الاستراتيجية (MLProcessor)
73
- "strategy_weights": {
74
- "trend_following": 0.18, "mean_reversion": 0.15, "breakout_momentum": 0.22,
75
- "volume_spike": 0.12, "whale_tracking": 0.15, "pattern_recognition": 0.10,
76
- "hybrid_ai": 0.08
77
- },
78
- # 2. أوزان المؤشرات العامة (MLProcessor)
79
- "indicator_weights": {
80
- "rsi": 0.2, "macd": 0.2, "bbands": 0.15, "atr": 0.1,
81
- "volume_ratio": 0.2, "vwap": 0.15
82
- },
83
- # 3. أوزان الأنماط العامة (MLProcessor)
84
- "pattern_weights": {
85
- "Double Bottom": 0.3, "Breakout Up": 0.3, "Uptrend": 0.2,
86
- "Near Support": 0.2, "Double Top": -0.3 # (وزن سلبي)
87
- },
88
- # 4. أوزان كاشف الانعكاس 5m (للحارس)
89
- "reversal_indicator_weights": {
90
- "pattern": 0.4,
91
- "rsi": 0.3,
92
- "macd": 0.3
93
- },
94
- # 5. أوزان زناد الدخول 1m (للحارس)
95
- "entry_trigger_weights": {
96
- "cvd": 0.25,
97
- "order_book": 0.25,
98
- "ema_1m": 0.25,
99
- "macd_1m": 0.25
100
- },
101
- # 6. عتبة تفعيل زناد الدخول
102
- "entry_trigger_threshold": 0.75
103
- }
104
- # 🔴 --- END OF CHANGE --- 🔴
105
-
106
- self.strategy_effectiveness = {}
107
- self.exit_profile_effectiveness = {}
108
- self.market_patterns = {}
109
-
110
- async def load_weights_from_r2(self):
111
- key = "learning_statistical_weights.json" # (ملف جديد)
112
- try:
113
- response = self.r2_service.s3_client.get_object(Bucket="trading", Key=key)
114
- data = json.loads(response['Body'].read())
115
- self.weights = data.get("weights", {})
116
- # (إضافة: التحقق من وجود الأوزان الجديدة، وإلا إضافتها من الافتراضيات)
117
- if "reversal_indicator_weights" not in self.weights:
118
- defaults = await self.get_default_strategy_weights() # (سيحتوي على كل شيء)
119
- self.weights["reversal_indicator_weights"] = defaults.get("reversal_indicator_weights")
120
- self.weights["entry_trigger_weights"] = defaults.get("entry_trigger_weights")
121
- self.weights["entry_trigger_threshold"] = defaults.get("entry_trigger_threshold")
122
- print("ℹ️ [StatsAnalyzer] تم تحديث ملف الأوزان ببيانات الحارس الجديدة.")
123
-
124
- self.strategy_effectiveness = data.get("strategy_effectiveness", {})
125
- self.market_patterns = data.get("market_patterns", {})
126
- print(f"✅ [StatsAnalyzer] تم تحميل الأوزان والإحصائيات من R2.")
127
- except Exception as e:
128
- print(f"ℹ️ [StatsAnalyzer] فشل تحميل الأوزان ({e}). استخدام الافتراضيات.")
129
- await self.initialize_default_weights()
130
-
131
- async def save_weights_to_r2(self):
132
- key = "learning_statistical_weights.json"
133
- try:
134
- data = {
135
- "weights": self.weights,
136
- "strategy_effectiveness": self.strategy_effectiveness,
137
- "market_patterns": self.market_patterns,
138
- "last_updated": datetime.now().isoformat()
139
- }
140
- data_json = json.dumps(data, indent=2, ensure_ascii=False).encode('utf-8')
141
- self.r2_service.s3_client.put_object(
142
- Bucket="trading", Key=key, Body=data_json, ContentType="application/json"
143
- )
144
- except Exception as e:
145
- print(f"❌ [StatsAnalyzer] فشل حفظ الأوزان في R2: {e}")
146
 
147
- async def load_performance_history(self):
148
- key = "learning_performance_history.json" # (مشترك)
149
- try:
150
- response = self.r2_service.s3_client.get_object(Bucket="trading", Key=key)
151
- data = json.loads(response['Body'].read())
152
- self.performance_history = data.get("history", [])
153
- except Exception as e:
154
- self.performance_history = []
155
 
156
- async def save_performance_history(self):
157
- key = "learning_performance_history.json"
158
- try:
159
- data = {"history": self.performance_history[-1000:]} # (آخر 1000 صفقة فقط)
160
- data_json = json.dumps(data, indent=2, ensure_ascii=False).encode('utf-8')
161
- self.r2_service.s3_client.put_object(
162
- Bucket="trading", Key=key, Body=data_json, ContentType="application/json"
163
- )
164
- except Exception as e:
165
- print(f"❌ [StatsAnalyzer] فشل حفظ تاريخ الأداء: {e}")
166
 
167
- async def load_exit_profile_effectiveness(self):
168
- key = "learning_exit_profile_effectiveness.json" # (مشترك)
169
  try:
170
- response = self.r2_service.s3_client.get_object(Bucket="trading", Key=key)
171
- data = json.loads(response['Body'].read())
172
- self.exit_profile_effectiveness = data.get("effectiveness", {})
173
  except Exception as e:
174
- self.exit_profile_effectiveness = {}
175
 
176
- async def save_exit_profile_effectiveness(self):
177
- key = "learning_exit_profile_effectiveness.json"
178
  try:
179
- data = {
180
- "effectiveness": self.exit_profile_effectiveness,
181
- "last_updated": datetime.now().isoformat()
182
- }
183
- data_json = json.dumps(data, indent=2, ensure_ascii=False).encode('utf-8')
184
- self.r2_service.s3_client.put_object(
185
- Bucket="trading", Key=key, Body=data_json, ContentType="application/json"
186
- )
187
  except Exception as e:
188
- print(f"❌ [StatsAnalyzer] فشل حفظ أداء ملف الخروج: {e}")
189
-
190
- async def update_statistics(self, trade_object: Dict[str, Any], close_reason: str):
 
 
191
  """
192
- هذه هي الدالة الرئيسية التي تحدث الإحصائيات (التعلم البطيء).
193
- (تدمج update_strategy_effectiveness و update_market_patterns من الملف القديم)
194
  """
195
  if not self.initialized:
196
- await self.initialize()
197
-
198
- try:
199
- strategy = trade_object.get('strategy', 'unknown')
200
- decision_data = trade_object.get('decision_data', {})
201
- exit_profile = decision_data.get('exit_profile', 'unknown')
202
- combined_key = f"{strategy}_{exit_profile}"
203
-
204
- pnl_percent = trade_object.get('pnl_percent', 0)
205
- is_success = pnl_percent > 0.1 # (اعتبار الربح الطفيف نجاحاً)
206
 
207
- # 🔴 --- START OF CHANGE --- 🔴
208
- # (استخدام بيانات السوق وقت القرار إذا كانت مخزنة، وإلا جلب الحالية)
209
- market_context = decision_data.get('market_context_at_decision', {})
210
- if not market_context:
211
- market_context = await self.get_current_market_conditions()
212
- market_condition = market_context.get('current_trend', 'sideways_market')
213
- # 🔴 --- END OF CHANGE --- 🔴
214
-
215
- # --- 1. تحديث تاريخ الأداء (للتتبع العام) ---
216
- analysis_entry = {
217
- "timestamp": datetime.now().isoformat(),
218
- "trade_id": trade_object.get('id', 'N/A'),
219
- "symbol": trade_object.get('symbol', 'N/A'),
220
- "outcome": close_reason,
221
- "market_conditions": market_context,
222
- "strategy_used": strategy,
223
- "exit_profile_used": exit_profile,
224
- "pnl_percent": pnl_percent
225
- }
226
- self.performance_history.append(analysis_entry)
227
-
228
- # --- 2. تحديث إحصائيات استراتيجية الدخول (strategy_effectiveness) ---
229
- if strategy not in self.strategy_effectiveness:
230
- self.strategy_effectiveness[strategy] = {"total_trades": 0, "successful_trades": 0, "total_pnl_percent": 0}
231
-
232
- self.strategy_effectiveness[strategy]["total_trades"] += 1
233
- self.strategy_effectiveness[strategy]["total_pnl_percent"] += pnl_percent
234
- if is_success:
235
- self.strategy_effectiveness[strategy]["successful_trades"] += 1
236
-
237
- # --- 3. تحديث إحصائيات مزيج (الدخول + الخروج) (exit_profile_effectiveness) ---
238
- if combined_key not in self.exit_profile_effectiveness:
239
- self.exit_profile_effectiveness[combined_key] = {"total_trades": 0, "successful_trades": 0, "total_pnl_percent": 0, "pnl_list": []}
240
-
241
- self.exit_profile_effectiveness[combined_key]["total_trades"] += 1
242
- self.exit_profile_effectiveness[combined_key]["total_pnl_percent"] += pnl_percent
243
- self.exit_profile_effectiveness[combined_key]["pnl_list"].append(pnl_percent)
244
- if len(self.exit_profile_effectiveness[combined_key]["pnl_list"]) > 100:
245
- self.exit_profile_effectiveness[combined_key]["pnl_list"] = self.exit_profile_effectiveness[combined_key]["pnl_list"][-100:]
246
- if is_success:
247
- self.exit_profile_effectiveness[combined_key]["successful_trades"] += 1
248
-
249
- # --- 4. تحديث إحصائيات ظروف السوق (market_patterns) ---
250
- if market_condition not in self.market_patterns:
251
- self.market_patterns[market_condition] = {"total_trades": 0, "successful_trades": 0, "total_pnl_percent": 0}
252
-
253
- self.market_patterns[market_condition]["total_trades"] += 1
254
- self.market_patterns[market_condition]["total_pnl_percent"] += pnl_percent
255
- if is_success:
256
- self.market_patterns[market_condition]["successful_trades"] += 1
257
-
258
- # --- 5. تكييف الأوزان والحفظ (إذا لزم الأمر) ---
259
- # (ملاحظة: نحتاج إلى إضافة منطق لتعلم أوزان الحارس هنا مستقبلاً)
260
- if should_update_weights(len(self.performance_history)):
261
- await self.adapt_weights_based_on_performance()
262
- await self.save_weights_to_r2()
263
- await self.save_performance_history()
264
- await self.save_exit_profile_effectiveness()
265
-
266
- print(f"✅ [StatsAnalyzer] تم تحديث الإحصائيات لـ {strategy} / {exit_profile}")
267
-
268
- except Exception as e:
269
- print(f"❌ [StatsAnalyzer] فشل تحديث الإحصائيات: {e}")
270
- traceback.print_exc()
271
-
272
- async def adapt_weights_based_on_performance(self):
273
- """تكييف أوزان استراتيجيات الدخول بناءً على الأداء الإحصائي"""
274
- # (ملاحظة: هذا المنطق حالياً يكيف فقط strategy_weights)
275
- # (سنحتاج لتطويره لاحقاً ليكيف أوزان الحارس)
276
- print("🔄 [StatsAnalyzer] تكييف أوزان الاستراتيجيات (التعلم البطيء)...")
277
- try:
278
- strategy_performance = {}
279
- total_performance = 0
280
-
281
- for strategy, data in self.strategy_effectiveness.items():
282
- if data.get("total_trades", 0) > 2: # (يتطلب 3 صفقات على الأقل للتكيف)
283
- success_rate = data["successful_trades"] / data["total_trades"]
284
- avg_pnl = data["total_pnl_percent"] / data["total_trades"]
285
-
286
- # مقياس مركب: (معدل النجاح * 60%) + (متوسط الربح * 40%)
287
- # (يتم تقييد متوسط الربح بين -5 و +5)
288
- normalized_pnl = min(max(avg_pnl, -5.0), 5.0) / 5.0 # (من -1 إلى 1)
289
-
290
- composite_performance = (success_rate * 0.6) + (normalized_pnl * 0.4)
291
-
292
- strategy_performance[strategy] = composite_performance
293
- total_performance += composite_performance
294
-
295
- if total_performance > 0 and strategy_performance:
296
- base_weights = self.weights.get("strategy_weights", {})
297
- for strategy, performance in strategy_performance.items():
298
- current_weight = base_weights.get(strategy, 0.1)
299
-
300
- # (تعديل طفيف: 80% من الوزن الحالي + 20% من الأداء)
301
- new_weight = (current_weight * 0.8) + (performance * 0.2)
302
- base_weights[strategy] = max(new_weight, 0.05) # (الحد الأدنى للوزن 5%)
303
-
304
- normalize_weights(base_weights)
305
- self.weights["strategy_weights"] = base_weights
306
- print(f"✅ [StatsAnalyzer] تم تكييف الأوزان: {base_weights}")
307
-
308
- except Exception as e:
309
- print(f"❌ [StatsAnalyzer] فشل تكييف الأوزان: {e}")
310
 
311
- # --- (الدوال المساعدة لجلب البيانات - مأخوذة من الملف القديم) ---
312
- async def get_best_exit_profile(self, entry_strategy: str) -> str:
313
- """يجد أفضل ملف خروج إحصائياً لاستراتيجية دخول معينة."""
314
- if not self.initialized or not self.exit_profile_effectiveness:
315
- return "unknown"
316
-
317
- relevant_profiles = {}
318
- for combined_key, data in self.exit_profile_effectiveness.items():
319
- if combined_key.startswith(f"{entry_strategy}_"):
320
- if data.get("total_trades", 0) >= 3: # (يتطلب 3 صفقات)
321
- exit_profile_name = combined_key.replace(f"{entry_strategy}_", "", 1)
322
- avg_pnl = data["total_pnl_percent"] / data["total_trades"]
323
- relevant_profiles[exit_profile_name] = avg_pnl
324
 
325
- if not relevant_profiles:
326
- return "unknown"
327
-
328
- best_profile = max(relevant_profiles, key=relevant_profiles.get)
329
- return best_profile
 
330
 
331
  # 🔴 --- START OF CHANGE --- 🔴
332
  async def get_optimized_weights(self, market_condition: str) -> Dict[str, float]:
333
  """
334
- جلب جميع الأوزان المعدلة إحصائياً (لكل من MLProcessor والحارس).
335
  """
336
- if not self.initialized or "strategy_weights" not in self.weights:
337
- await self.initialize()
338
-
339
- base_weights = self.weights.copy()
340
-
341
- # (يمكننا إضافة منطق تعديل الأوزان بناءً على ظروف السوق هنا)
342
- # (لكن في الوقت الحالي، سنعيد الأوزان المعدلة إحصائياً كما هي)
343
-
344
- if not base_weights:
345
- # (العودة إلى الافتراضيات إذا كانت الأوزان فارغة)
346
- return await self.get_default_strategy_weights()
347
-
348
- return base_weights
349
  # 🔴 --- END OF CHANGE --- 🔴
350
-
351
- async def get_default_strategy_weights(self) -> Dict[str, float]:
352
- """إرجاع الأوزان الافتراضية عند الفشل"""
353
- # 🔴 --- START OF CHANGE --- 🔴
354
- # (إرجاع كل شيء، وليس فقط أوزان الاستراتيجية)
355
- return {
356
- "strategy_weights": {
357
- "trend_following": 0.18, "mean_reversion": 0.15, "breakout_momentum": 0.22,
358
- "volume_spike": 0.12, "whale_tracking": 0.15, "pattern_recognition": 0.10,
359
- "hybrid_ai": 0.08
360
- },
361
- "indicator_weights": {
362
- "rsi": 0.2, "macd": 0.2, "bbands": 0.15, "atr": 0.1,
363
- "volume_ratio": 0.2, "vwap": 0.15
364
- },
365
- "pattern_weights": {
366
- "Double Bottom": 0.3, "Breakout Up": 0.3, "Uptrend": 0.2,
367
- "Near Support": 0.2, "Double Top": -0.3
368
- },
369
- "reversal_indicator_weights": {
370
- "pattern": 0.4, "rsi": 0.3, "macd": 0.3
371
- },
372
- "entry_trigger_weights": {
373
- "cvd": 0.25, "order_book": 0.25, "ema_1m": 0.25, "macd_1m": 0.25
374
- },
375
- "entry_trigger_threshold": 0.75
376
- }
377
- # 🔴 --- END OF CHANGE --- 🔴
378
-
379
- async def get_current_market_conditions(self) -> Dict[str, Any]:
380
- """جلب سياق السوق الحالي (من الملف القديم)"""
381
- try:
382
- if not self.data_manager:
383
- raise ValueError("DataManager unavailable")
384
- market_context = await self.data_manager.get_market_context_async()
385
- if not market_context:
386
- raise ValueError("Market context fetch failed")
387
 
388
- # (نحتاج دالة لحساب التقلب - نفترض أنها في helpers)
389
- # volatility = calculate_market_volatility(market_context)
 
 
 
 
 
 
 
 
 
 
390
 
391
- return {
392
- "current_trend": market_context.get('market_trend', 'sideways_market'),
393
- "volatility": "medium", # (قيمة مؤقتة)
394
- "market_sentiment": market_context.get('btc_sentiment', 'NEUTRAL'),
395
- }
 
396
  except Exception as e:
397
- return {"current_trend": "sideways_market", "volatility": "medium", "market_sentiment": "NEUTRAL"}
398
- # 🔴 --- START OF CHANGE --- 🔴
399
- # (تم حذف القوس } الزائد من هنا)
400
- # 🔴 --- END OF CHANGE --- 🔴
 
1
+ # learning_hub/hub_manager.py
 
 
 
 
2
  import asyncio
3
+ from typing import Any, Dict
4
+
5
+ # (استيراد جميع المكونات الداخلية للمركز)
6
+ from .schemas import *
7
+ from .policy_engine import PolicyEngine
8
+ from .memory_store import MemoryStore
9
+ from .statistical_analyzer import StatisticalAnalyzer
10
+ from .reflector import Reflector
11
+ from .curator import Curator
12
+
13
+ class LearningHubManager:
14
+ def __init__(self, r2_service: Any, llm_service: Any, data_manager: Any):
15
+ print("🚀 Initializing Learning Hub Manager...")
 
 
 
 
 
 
 
 
16
 
17
+ # 1. الخدمات الأساسية (يتم تمريرها من app.py)
18
+ self.r2_service = r2_service
19
+ self.llm_service = llm_service
20
+ self.data_manager = data_manager
21
+
22
+ # 2. تهيئة المكونات (بناء النظام)
23
+ self.policy_engine = PolicyEngine()
24
+ self.memory_store = MemoryStore(
25
+ r2_service=self.r2_service,
26
+ policy_engine=self.policy_engine,
27
+ llm_service=self.llm_service
28
+ )
29
+ self.reflector = Reflector(
30
+ llm_service=self.llm_service,
31
+ memory_store=self.memory_store
32
+ )
33
+ self.curator = Curator(
34
+ llm_service=self.llm_service,
35
+ memory_store=self.memory_store
36
+ )
37
+ self.statistical_analyzer = StatisticalAnalyzer(
38
+ r2_service=self.r2_service,
39
+ data_manager=self.data_manager
40
+ )
41
 
42
  self.initialized = False
43
+ print("✅ Learning Hub Manager constructed. Ready for initialization.")
 
 
44
 
45
  async def initialize(self):
46
+ """
47
+ تهيئة جميع الأنظمة الفرعية، وخاصة تحميل الإحصائيات والأوزان.
48
+ """
49
+ if self.initialized:
50
+ return
51
+
52
+ print("🔄 [HubManager] Initializing all sub-modules...")
53
+ await self.statistical_analyzer.initialize()
54
+ self.initialized = True
55
+ print("✅ [HubManager] All sub-modules initialized. Learning Hub is LIVE.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
+ async def analyze_trade_and_learn(self, trade_object: Dict[str, Any], close_reason: str):
58
+ """
59
+ هذه هي الدالة الرئيسية التي يستدعيها TradeManager.
60
+ إنها تشغل كلاً من نظام التعلم السريع (Reflector) والبطيء (StatsAnalyzer).
61
+ """
62
+ if not self.initialized:
63
+ print("⚠️ [HubManager] Learning Hub not initialized. Skipping learning.")
64
+ return
65
 
66
+ print(f"🧠 [HubManager] Learning from trade {trade_object.get('symbol')}...")
 
 
 
 
 
 
 
 
 
67
 
 
 
68
  try:
69
+ # 1. التعلم السريع (Reflector):
70
+ await self.reflector.analyze_trade_outcome(trade_object, close_reason)
 
71
  except Exception as e:
72
+ print(f"❌ [HubManager] Reflector (Fast-Learner) failed: {e}")
73
 
 
 
74
  try:
75
+ # 2. التعلم البطيء (StatisticalAnalyzer):
76
+ await self.statistical_analyzer.update_statistics(trade_object, close_reason)
 
 
 
 
 
 
77
  except Exception as e:
78
+ print(f"❌ [HubManager] StatisticalAnalyzer (Slow-Learner) failed: {e}")
79
+
80
+ print(f"✅ [HubManager] Learning complete for {trade_object.get('symbol')}.")
81
+
82
+ async def get_active_context_for_llm(self, domain: str, query: str) -> str:
83
  """
84
+ يُستخدم بواسطة LLMService لجلب "الدفتر" (Playbook) / القواعد (Deltas).
 
85
  """
86
  if not self.initialized:
87
+ return "Learning Hub not initialized."
 
 
 
 
 
 
 
 
 
88
 
89
+ return await self.memory_store.get_active_context(domain, query)
90
+
91
+ async def get_statistical_feedback_for_llm(self, entry_strategy: str) -> str:
92
+ """
93
+ يُستخدم بواسطة LLMService لجلب أفضل ملف خروج (إحصائياً).
94
+ """
95
+ if not self.initialized:
96
+ return "Learning Hub not initialized."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
+ best_profile = await self.statistical_analyzer.get_best_exit_profile(entry_strategy)
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
+ if best_profile != "unknown":
101
+ # (Prompt in English as requested)
102
+ feedback = f"Statistical Feedback: For the '{entry_strategy}' strategy, the '{best_profile}' exit profile has historically performed best."
103
+ return feedback
104
+ else:
105
+ return "No statistical feedback available for this strategy yet."
106
 
107
  # 🔴 --- START OF CHANGE --- 🔴
108
  async def get_optimized_weights(self, market_condition: str) -> Dict[str, float]:
109
  """
110
+ يُستخدم بواسطة MLProcessor/StrategyEngine/Sentry لجلب الأوزان المعدلة إحصائياً.
111
  """
112
+ if not self.initialized:
113
+ # (الحصول على كل الأوزان الافتراضية)
114
+ return await self.statistical_analyzer.get_default_strategy_weights()
115
+
116
+ # (الحصول على كل الأوزان المحسنة)
117
+ return await self.statistical_analyzer.get_optimized_weights(market_condition)
 
 
 
 
 
 
 
118
  # 🔴 --- END OF CHANGE --- 🔴
119
+
120
+ async def run_distillation_check(self):
121
+ """
122
+ (يتم استدعاؤها دورياً من app.py)
123
+ للتحقق من جميع المجالات وتشغيل التقطير إذا لزم الأمر.
124
+ """
125
+ if not self.initialized:
126
+ return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
+ print("ℹ️ [HubManager] Running periodic distillation check...")
129
+ for domain in self.memory_store.domain_files.keys():
130
+ await self.curator.check_and_distill_domain(domain)
131
+ print("✅ [HubManager] Distillation check complete.")
132
+
133
+ # (No change to shutdown function)
134
+ async def shutdown(self):
135
+ """
136
+ Saves all persistent data from the statistical analyzer.
137
+ """
138
+ if not self.initialized:
139
+ return
140
 
141
+ print("🔄 [HubManager] Shutting down... Saving all learning data.")
142
+ try:
143
+ await self.statistical_analyzer.save_weights_to_r2()
144
+ await self.statistical_analyzer.save_performance_history()
145
+ await self.statistical_analyzer.save_exit_profile_effectiveness()
146
+ print("✅ [HubManager] All statistical (slow-learner) data saved.")
147
  except Exception as e:
148
+ print(f" [HubManager] Failed to save learning data on shutdown: {e}")
149
+ # 🔴 --- START OF CHANGE --- 🔴
150
+ # (تم حذف القوس } الزائد من هنا)
151
+ # 🔴 --- END OF CHANGE --- 🔴