Riy777 commited on
Commit
09cd0b8
·
verified ·
1 Parent(s): 5f1c540

Update smart_portfolio.py

Browse files
Files changed (1) hide show
  1. smart_portfolio.py +48 -121
smart_portfolio.py CHANGED
@@ -1,10 +1,5 @@
1
  # ==============================================================================
2
- # 💼 smart_portfolio.py (V36.0 - GEM-Architect: Regime-Aligned Risk)
3
- # ==============================================================================
4
- # التحديثات:
5
- # 1. إلغاء تحديد الاتجاه المستقل. الاعتماد الكلي على SystemLimits.CURRENT_REGIME.
6
- # 2. إدارة الخانات الديناميكية (Dynamic Slots) بناءً على حالة السوق.
7
- # 3. تعديل مضاعفات المخاطرة (Risk Multipliers) لتتوافق مع الـ DNA الحالي.
8
  # ==============================================================================
9
 
10
  import asyncio
@@ -33,17 +28,8 @@ class SmartPortfolio:
33
  self.MIN_CAPITAL_FOR_SPLIT = 20.0
34
  self.DAILY_LOSS_LIMIT_PCT = 0.20
35
 
36
- # 📊 أوزان الثقة المركبة
37
- self.WEIGHTS = {
38
- "L2_TECHNICAL": 0.25,
39
- "L3_ORACLE": 0.35,
40
- "L4_SNIPER": 0.20,
41
- "CONTEXT": 0.10,
42
- "MARKET_MOOD": 0.10
43
- }
44
-
45
- # حالة السوق (تقرأ الآن من SystemLimits)
46
- self.market_trend = "NEUTRAL" # للعرض فقط
47
  self.fear_greed_index = 50
48
  self.fear_greed_label = "Neutral"
49
 
@@ -60,95 +46,48 @@ class SmartPortfolio:
60
  "halt_reason": None
61
  }
62
 
63
- print("💼 [SmartPortfolio V36.0] Regime-Aligned Risk System Initialized.")
64
 
65
  async def initialize(self):
66
  await self._sync_state_from_r2()
67
  await self._check_daily_reset()
68
- # تشغيل مراقب الخوف والجشع فقط (أما الاتجاه فيأتي من العقل المركزي)
69
  asyncio.create_task(self._market_monitor_loop())
70
 
71
- # ==============================================================================
72
- # 🦅 Market Monitor: Fear & Greed Only (Trend from Central)
73
- # ==============================================================================
74
  async def _market_monitor_loop(self):
75
  """مراقبة مؤشر الخوف والجشع فقط"""
76
  print("🦅 [SmartPortfolio] Sentiment Sentinel Started.")
77
  async with httpx.AsyncClient() as client:
78
  while True:
79
  try:
80
- # تحديث العرض بناءً على النظام المركزي
81
  regime = getattr(SystemLimits, 'CURRENT_REGIME', 'RANGE')
82
- self.market_trend = regime # تحديث المتغير للعرض في الواجهة
83
 
84
- # جلب مؤشر الخوف والجشع
85
  try:
86
  resp = await client.get("https://api.alternative.me/fng/?limit=1", timeout=10)
87
  data = resp.json()
88
  if data['data']:
89
  self.fear_greed_index = int(data['data'][0]['value'])
90
  self.fear_greed_label = data['data'][0]['value_classification']
91
- except Exception:
92
- pass
93
 
94
- await asyncio.sleep(300) # كل 5 دقائق
95
  except Exception as e:
96
  print(f"⚠️ [Market Monitor] Error: {e}")
97
  await asyncio.sleep(60)
98
 
99
  # ==============================================================================
100
- # 🧮 The Consensus Engine
101
- # ==============================================================================
102
- def _calculate_composite_confidence(self, signal_data: Dict[str, Any]) -> float:
103
- try:
104
- l2_score = float(signal_data.get('enhanced_final_score', 0.5))
105
- oracle_conf = float(signal_data.get('confidence', 0.5))
106
- sniper_score = float(signal_data.get('sniper_score', 0.5))
107
-
108
- whale_impact = float(signal_data.get('whale_score', 0.0))
109
- news_impact = float(signal_data.get('news_score', 0.0))
110
- context_val = max(0.0, min(1.0, 0.5 + whale_impact + news_impact))
111
-
112
- # حساب سكور المزاج بناءً على Regime المركزي
113
- regime = getattr(SystemLimits, 'CURRENT_REGIME', 'RANGE')
114
- mood_score = 0.5
115
-
116
- if regime == "BULL": mood_score += 0.2
117
- elif regime == "BEAR": mood_score -= 0.2
118
- elif regime == "DEAD": mood_score -= 0.1
119
-
120
- # دمج F&G (Contrarian Logic)
121
- fg = self.fear_greed_index
122
- if fg < 20: mood_score += 0.1 # خوف شديد = شراء
123
- elif fg > 80: mood_score -= 0.1 # جشع شديد = حذر
124
-
125
- mood_score = max(0.0, min(1.0, mood_score))
126
-
127
- final_conf = (
128
- (l2_score * self.WEIGHTS["L2_TECHNICAL"]) +
129
- (oracle_conf * self.WEIGHTS["L3_ORACLE"]) +
130
- (sniper_score * self.WEIGHTS["L4_SNIPER"]) +
131
- (context_val * self.WEIGHTS["CONTEXT"]) +
132
- (mood_score * self.WEIGHTS["MARKET_MOOD"])
133
- )
134
- return round(final_conf, 3)
135
-
136
- except Exception:
137
- return float(signal_data.get('confidence', 0.5))
138
-
139
- # ==============================================================================
140
- # 🧠 Core Logic: Entry Approval (Regime-Adaptive Risk)
141
  # ==============================================================================
142
  async def request_entry_approval(self, signal_data: Dict[str, Any], open_positions_count: int) -> Tuple[bool, Dict[str, Any]]:
143
  """
144
- تطلب الموافقة مع مراعاة حالة السوق المركزية (Regime).
145
  """
146
  async with self.capital_lock:
147
  # 1. Circuit Breaker
148
  if self.state["is_trading_halted"]:
149
  return False, {"reason": f"Halted: {self.state['halt_reason']}"}
150
 
151
- # 2. Daily Loss Limit
152
  current_cap = float(self.state["current_capital"])
153
  start_cap = float(self.state["session_start_balance"])
154
 
@@ -159,76 +98,68 @@ class SmartPortfolio:
159
  await self._save_state_to_r2()
160
  return False, {"reason": "Daily Limit Hit"}
161
 
162
- # 3. Regime-Based Slots & Risk
163
- # قراءة الحالة من النظام المركزي
 
 
 
 
 
 
164
  regime = getattr(SystemLimits, 'CURRENT_REGIME', 'RANGE')
165
 
166
- # تحديد الحد الأقصى للصفقات بناءً على الحالة
167
- if regime == "BULL":
168
- max_slots = 6 # هجومي
169
- risk_factor_base = 1.2
170
- elif regime == "BEAR":
171
- max_slots = 3 # دفاعي جداً
172
- risk_factor_base = 0.5
173
- elif regime == "DEAD":
174
- max_slots = 2 # حذر جداً
175
- risk_factor_base = 0.4
176
- else: # RANGE
177
- max_slots = 4 # متوازن
178
- risk_factor_base = 0.8
179
 
180
- # تعديل للخزين الصغير
181
- if current_cap < self.MIN_CAPITAL_FOR_SPLIT:
182
- max_slots = min(max_slots, 2) # لا نفتح الكثير إذا الرصيد قليل
183
 
184
  if open_positions_count >= max_slots:
185
  return False, {"reason": f"Max slots reached for {regime} ({open_positions_count}/{max_slots})"}
186
 
187
- # 4. Free Capital Check
188
  allocated = float(self.state.get("allocated_capital_usd", 0.0))
189
  free_capital = max(0.0, current_cap - allocated)
190
 
191
  if free_capital < 5.0:
192
  return False, {"reason": f"Insufficient Free Capital (${free_capital:.2f})"}
193
 
194
- # 5. Position Sizing
195
- remaining_slots = max_slots - open_positions_count
196
- base_allocation = 0.0
197
 
 
198
  if current_cap >= self.MIN_CAPITAL_FOR_SPLIT:
199
- # تقسيم ذكي: نحاول الحفاظ على توازن، لكن نأخذ حصة أكبر في الـ Bull
200
- target_size = current_cap / max_slots
201
- base_allocation = min(target_size, free_capital)
202
  else:
203
- base_allocation = free_capital * 0.95 # All-in تقريباً للحسابات الصغيرة
204
 
205
- # 6. Consensus & Risk Multiplier
206
- system_confidence = self._calculate_composite_confidence(signal_data)
207
-
208
- # تعديل المخاطرة بناءً على الثقة + الحالة
209
- risk_multiplier = 1.0
210
- if system_confidence >= 0.80: risk_multiplier = 1.2
211
- elif system_confidence < 0.60: risk_multiplier = 0.7
212
-
213
- # دمج عامل الحالة (Regime Factor)
214
- final_risk_mult = risk_multiplier * risk_factor_base
215
-
216
- final_size_usd = base_allocation * final_risk_mult
217
- final_size_usd = min(final_size_usd, free_capital, current_cap * 0.98)
218
 
219
  if final_size_usd < 5.0:
220
- return False, {"reason": "Calculated size too small"}
 
 
221
 
222
  # 7. Dynamic TP Selection
223
  entry_price = float(signal_data.get('sniper_entry_price') or signal_data.get('current_price'))
224
  tp_map = signal_data.get('tp_map', {})
225
 
226
- # أهداف طموحة في Bull، ومحافظة في Bear
227
- if regime == "BULL" and system_confidence >= 0.75:
228
  selected_tp = tp_map.get('TP3') or tp_map.get('TP4')
229
  target_label = "TP3/4 (Bull Run)"
230
  elif regime == "BEAR":
231
- selected_tp = tp_map.get('TP1') # خروج سريع في الهبوط
232
  target_label = "TP1 (Scalp)"
233
  else:
234
  selected_tp = tp_map.get('TP2')
@@ -240,9 +171,9 @@ class SmartPortfolio:
240
  "approved_size_usd": float(final_size_usd),
241
  "approved_tp": float(selected_tp),
242
  "target_label": target_label,
243
- "system_confidence": system_confidence,
244
- "risk_multiplier": final_risk_mult,
245
- "market_mood": f"{regime} | FG:{self.fear_greed_index}"
246
  }
247
 
248
  # ==============================================================================
@@ -264,7 +195,6 @@ class SmartPortfolio:
264
  self.state["current_capital"] += net_impact
265
  self.state["daily_net_pnl"] += net_impact
266
 
267
- # Check Daily Limit after trade
268
  start = self.state["session_start_balance"]
269
  dd = (start - self.state["current_capital"]) / start if start > 0 else 0
270
  if dd >= self.DAILY_LOSS_LIMIT_PCT:
@@ -273,9 +203,6 @@ class SmartPortfolio:
273
 
274
  await self._save_state_to_r2()
275
 
276
- # ==============================================================================
277
- # 💾 Utilities
278
- # ==============================================================================
279
  async def _check_daily_reset(self):
280
  last_reset = datetime.fromisoformat(self.state.get("last_session_reset", datetime.now().isoformat()))
281
  if datetime.now() - last_reset > timedelta(hours=24):
 
1
  # ==============================================================================
2
+ # 💼 smart_portfolio.py (V37.0 - GEM-Architect: Grade-Based Sizing)
 
 
 
 
 
3
  # ==============================================================================
4
 
5
  import asyncio
 
28
  self.MIN_CAPITAL_FOR_SPLIT = 20.0
29
  self.DAILY_LOSS_LIMIT_PCT = 0.20
30
 
31
+ # حالة السوق
32
+ self.market_trend = "NEUTRAL"
 
 
 
 
 
 
 
 
 
33
  self.fear_greed_index = 50
34
  self.fear_greed_label = "Neutral"
35
 
 
46
  "halt_reason": None
47
  }
48
 
49
+ print("💼 [SmartPortfolio V37.0] Grade-Based Sizing System Initialized.")
50
 
51
  async def initialize(self):
52
  await self._sync_state_from_r2()
53
  await self._check_daily_reset()
 
54
  asyncio.create_task(self._market_monitor_loop())
55
 
 
 
 
56
  async def _market_monitor_loop(self):
57
  """مراقبة مؤشر الخوف والجشع فقط"""
58
  print("🦅 [SmartPortfolio] Sentiment Sentinel Started.")
59
  async with httpx.AsyncClient() as client:
60
  while True:
61
  try:
 
62
  regime = getattr(SystemLimits, 'CURRENT_REGIME', 'RANGE')
63
+ self.market_trend = regime
64
 
 
65
  try:
66
  resp = await client.get("https://api.alternative.me/fng/?limit=1", timeout=10)
67
  data = resp.json()
68
  if data['data']:
69
  self.fear_greed_index = int(data['data'][0]['value'])
70
  self.fear_greed_label = data['data'][0]['value_classification']
71
+ except Exception: pass
 
72
 
73
+ await asyncio.sleep(300)
74
  except Exception as e:
75
  print(f"⚠️ [Market Monitor] Error: {e}")
76
  await asyncio.sleep(60)
77
 
78
  # ==============================================================================
79
+ # 🧠 Core Logic: Entry Approval (Grade-Based)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  # ==============================================================================
81
  async def request_entry_approval(self, signal_data: Dict[str, Any], open_positions_count: int) -> Tuple[bool, Dict[str, Any]]:
82
  """
83
+ تطلب الموافقة وتحدد الحجم بناءً على جودة الحوكمة (Grade).
84
  """
85
  async with self.capital_lock:
86
  # 1. Circuit Breaker
87
  if self.state["is_trading_halted"]:
88
  return False, {"reason": f"Halted: {self.state['halt_reason']}"}
89
 
90
+ # 2. Daily Loss Limit Check
91
  current_cap = float(self.state["current_capital"])
92
  start_cap = float(self.state["session_start_balance"])
93
 
 
98
  await self._save_state_to_r2()
99
  return False, {"reason": "Daily Limit Hit"}
100
 
101
+ # 3. Governance Check (Quality Control)
102
+ gov_grade = signal_data.get('governance_grade', 'NORMAL')
103
+ gov_score = signal_data.get('governance_score', 50.0)
104
+
105
+ if gov_grade == 'REJECT':
106
+ return False, {"reason": f"Governance Rejected (Score: {gov_score})"}
107
+
108
+ # 4. Regime-Based Slots
109
  regime = getattr(SystemLimits, 'CURRENT_REGIME', 'RANGE')
110
 
111
+ if regime == "BULL": max_slots = 6
112
+ elif regime == "BEAR": max_slots = 3
113
+ elif regime == "DEAD": max_slots = 2
114
+ else: max_slots = 4
 
 
 
 
 
 
 
 
 
115
 
116
+ if current_cap < self.MIN_CAPITAL_FOR_SPLIT: max_slots = min(max_slots, 2)
 
 
117
 
118
  if open_positions_count >= max_slots:
119
  return False, {"reason": f"Max slots reached for {regime} ({open_positions_count}/{max_slots})"}
120
 
121
+ # 5. Free Capital Check
122
  allocated = float(self.state.get("allocated_capital_usd", 0.0))
123
  free_capital = max(0.0, current_cap - allocated)
124
 
125
  if free_capital < 5.0:
126
  return False, {"reason": f"Insufficient Free Capital (${free_capital:.2f})"}
127
 
128
+ # ✅ 6. Position Sizing (Grade Logic)
129
+ # القاعدة: الحصة الكاملة = الكابيتال / عدد الخانات.
130
+ # الجودة تحدد كم نأخذ من هذه الحصة.
131
 
132
+ target_slot_size = 0.0
133
  if current_cap >= self.MIN_CAPITAL_FOR_SPLIT:
134
+ target_slot_size = current_cap / max_slots
 
 
135
  else:
136
+ target_slot_size = free_capital * 0.95 # All-in for small accounts
137
 
138
+ # مضاعف الجودة
139
+ quality_multiplier = 0.5 # Default NORMAL
140
+ if gov_grade == "ULTRA": quality_multiplier = 1.0 # 100% of slot
141
+ elif gov_grade == "STRONG": quality_multiplier = 0.75 # 75% of slot
142
+ elif gov_grade == "NORMAL": quality_multiplier = 0.50 # 50% of slot
143
+ elif gov_grade == "WEAK": quality_multiplier = 0.25 # 25% of slot
144
+
145
+ # حساب الحجم النهائي
146
+ final_size_usd = target_slot_size * quality_multiplier
147
+ final_size_usd = min(final_size_usd, free_capital) # لا نتجاوز المتوفر
 
 
 
148
 
149
  if final_size_usd < 5.0:
150
+ # إذا كانت النسبة صغيرة جداً ولكن الحساب يسمح، نرفعها للحد الأدنى
151
+ if free_capital >= 5.0: final_size_usd = 5.0
152
+ else: return False, {"reason": "Calculated size too small"}
153
 
154
  # 7. Dynamic TP Selection
155
  entry_price = float(signal_data.get('sniper_entry_price') or signal_data.get('current_price'))
156
  tp_map = signal_data.get('tp_map', {})
157
 
158
+ if regime == "BULL" and gov_grade in ["STRONG", "ULTRA"]:
 
159
  selected_tp = tp_map.get('TP3') or tp_map.get('TP4')
160
  target_label = "TP3/4 (Bull Run)"
161
  elif regime == "BEAR":
162
+ selected_tp = tp_map.get('TP1')
163
  target_label = "TP1 (Scalp)"
164
  else:
165
  selected_tp = tp_map.get('TP2')
 
171
  "approved_size_usd": float(final_size_usd),
172
  "approved_tp": float(selected_tp),
173
  "target_label": target_label,
174
+ "system_confidence": gov_score / 100.0,
175
+ "risk_multiplier": quality_multiplier,
176
+ "market_mood": f"{regime} | Grade: {gov_grade}"
177
  }
178
 
179
  # ==============================================================================
 
195
  self.state["current_capital"] += net_impact
196
  self.state["daily_net_pnl"] += net_impact
197
 
 
198
  start = self.state["session_start_balance"]
199
  dd = (start - self.state["current_capital"]) / start if start > 0 else 0
200
  if dd >= self.DAILY_LOSS_LIMIT_PCT:
 
203
 
204
  await self._save_state_to_r2()
205
 
 
 
 
206
  async def _check_daily_reset(self):
207
  last_reset = datetime.fromisoformat(self.state.get("last_session_reset", datetime.now().isoformat()))
208
  if datetime.now() - last_reset > timedelta(hours=24):