Riy777 commited on
Commit
01fca6c
·
verified ·
1 Parent(s): 1406547

Update smart_portfolio.py

Browse files
Files changed (1) hide show
  1. smart_portfolio.py +94 -102
smart_portfolio.py CHANGED
@@ -1,11 +1,10 @@
1
  # ==============================================================================
2
- # 💼 smart_portfolio.py (V1.2 - GEM-Architect: True Risk & Market Mood)
3
  # ==============================================================================
4
- # التحديثات الجذرية:
5
- # 1. إدارة صارمة لرأس المال (Allocated vs Free).
6
- # 2. دمج مؤشر الخوف والجشع (Fear & Greed Index) في حساب الثقة.
7
- # 3. فرض حقيقي لعدد الخانات (Slots Enforcement).
8
- # 4. معالجة الرسوم بشكل منفصل ودقيق.
9
  # ==============================================================================
10
 
11
  import asyncio
@@ -17,38 +16,43 @@ import pandas_ta as ta
17
  from datetime import datetime, timedelta
18
  from typing import Dict, Any, Tuple, Optional
19
 
 
 
 
 
 
 
 
 
20
  class SmartPortfolio:
21
  def __init__(self, r2_service, data_manager):
22
  self.r2 = r2_service
23
  self.data_manager = data_manager
24
 
25
- # ⚙️ إعدادات المحفظة (الدستور المالي)
26
  self.MIN_CAPITAL_FOR_SPLIT = 20.0
27
- self.MAX_SLOTS_BELOW_THRESHOLD = 1
28
- self.MAX_SLOTS_ABOVE_THRESHOLD = 5
29
-
30
  self.DAILY_LOSS_LIMIT_PCT = 0.20
31
 
32
- # 📊 أوزان الثقة المركبة (تم التحديث لإضافة Market Mood)
33
  self.WEIGHTS = {
34
  "L2_TECHNICAL": 0.25,
35
  "L3_ORACLE": 0.35,
36
  "L4_SNIPER": 0.20,
37
- "CONTEXT": 0.10, # حيتان وأخبار
38
- "MARKET_MOOD": 0.10 # Fear & Greed + BTC Trend
39
  }
40
 
41
- # حالة السوق والمزاج العام
42
- self.market_trend = "NEUTRAL"
43
- self.fear_greed_index = 50 # 0-100 (50 = Neutral)
44
  self.fear_greed_label = "Neutral"
45
 
46
  self.capital_lock = asyncio.Lock()
47
 
48
- # 📂 حالة المحفظة (تمت إضافة allocated_capital_usd)
49
  self.state = {
50
- "current_capital": 10.0, # إجمالي حقوق الملكية (Equity)
51
- "allocated_capital_usd": 0.0, # الأموال المحجوزة في صفقات نشطة
52
  "session_start_balance": 10.0,
53
  "last_session_reset": datetime.now().isoformat(),
54
  "daily_net_pnl": 0.0,
@@ -56,45 +60,36 @@ class SmartPortfolio:
56
  "halt_reason": None
57
  }
58
 
59
- print("💼 [SmartPortfolio V1.2] True Risk & Market Mood Initialized.")
60
 
61
  async def initialize(self):
62
  await self._sync_state_from_r2()
63
  await self._check_daily_reset()
64
- # تشغيل المراقبين
65
  asyncio.create_task(self._market_monitor_loop())
66
 
67
  # ==============================================================================
68
- # 🦅 Market Monitor: BTC Trend + Fear & Greed
69
  # ==============================================================================
70
  async def _market_monitor_loop(self):
71
- """مراقبة خلفية لاتجاه البيتكوين ومؤشر الخوف والجشع"""
72
- print("🦅 [SmartPortfolio] Market Sentinel Started.")
73
  async with httpx.AsyncClient() as client:
74
  while True:
75
  try:
76
- # 1. تحليل ترند BTC
77
- ohlcv = await self.data_manager.get_latest_ohlcv("BTC/USDT", "4h", limit=50)
78
- if ohlcv:
79
- df = pd.DataFrame(ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
80
- ema20 = df['close'].ewm(span=20).mean().iloc[-1]
81
- ema50 = df['close'].ewm(span=50).mean().iloc[-1]
82
- close = df['close'].iloc[-1]
83
-
84
- if close > ema20 and ema20 > ema50: self.market_trend = "BULLISH"
85
- elif close < ema20 and ema20 < ema50: self.market_trend = "BEARISH"
86
- else: self.market_trend = "NEUTRAL"
87
 
88
- # 2. جلب مؤشر الخوف والجشع (Fear & Greed API)
89
- # نجلبه بمعدل أقل لتجنب الحظر، لكن هنا في نفس الحلقة للتبسيط
90
  try:
91
- resp = await client.get("https://api.alternative.me/fng/?limit=1", timeout=5)
92
  data = resp.json()
93
  if data['data']:
94
  self.fear_greed_index = int(data['data'][0]['value'])
95
  self.fear_greed_label = data['data'][0]['value_classification']
96
  except Exception:
97
- pass # الحفاظ على القيمة السابقة عند الفشل
98
 
99
  await asyncio.sleep(300) # كل 5 دقائق
100
  except Exception as e:
@@ -102,7 +97,7 @@ class SmartPortfolio:
102
  await asyncio.sleep(60)
103
 
104
  # ==============================================================================
105
- # 🧮 The Consensus Engine (Updated with Mood)
106
  # ==============================================================================
107
  def _calculate_composite_confidence(self, signal_data: Dict[str, Any]) -> float:
108
  try:
@@ -110,24 +105,22 @@ class SmartPortfolio:
110
  oracle_conf = float(signal_data.get('confidence', 0.5))
111
  sniper_score = float(signal_data.get('sniper_score', 0.5))
112
 
113
- # تطبيع سياق الحيتان والأخبار (0.0 - 1.0)
114
  whale_impact = float(signal_data.get('whale_score', 0.0))
115
  news_impact = float(signal_data.get('news_score', 0.0))
116
  context_val = max(0.0, min(1.0, 0.5 + whale_impact + news_impact))
117
 
118
- # حساب سكور "مزاج السوق" (Market Mood Score)
119
- # الخوف الشديد (أقل من 20) قد يكون فرصة شراء ممتازة (Contrarian)
120
- # الجشع الشديد (فوق 80) قد يكون خطر تصحيح
121
  mood_score = 0.5
122
- if self.market_trend == "BULLISH":
123
- mood_score += 0.2
124
- elif self.market_trend == "BEARISH":
125
- mood_score -= 0.2
126
 
127
- # دمج F&G: إذا السوق صاعد وجشع معتدل = جيد. جشع مفرط = سيء.
128
  fg = self.fear_greed_index
129
- if fg < 20: mood_score += 0.1 # خوف شديد = فرصة ارتداد محتملة
130
- elif fg > 80: mood_score -= 0.1 # جشع شديد = خطر القمة
131
 
132
  mood_score = max(0.0, min(1.0, mood_score))
133
 
@@ -144,18 +137,18 @@ class SmartPortfolio:
144
  return float(signal_data.get('confidence', 0.5))
145
 
146
  # ==============================================================================
147
- # 🧠 Core Logic: Entry Approval (True Risk)
148
  # ==============================================================================
149
  async def request_entry_approval(self, signal_data: Dict[str, Any], open_positions_count: int) -> Tuple[bool, Dict[str, Any]]:
150
  """
151
- تطلب الموافقة مع مراعاة رأس المال الحر (Free Capital) وعدد الخانات المشغولة.
152
  """
153
  async with self.capital_lock:
154
- # 1. Circuit Breaker Check
155
  if self.state["is_trading_halted"]:
156
  return False, {"reason": f"Halted: {self.state['halt_reason']}"}
157
 
158
- # 2. Daily Loss Limit Check
159
  current_cap = float(self.state["current_capital"])
160
  start_cap = float(self.state["session_start_balance"])
161
 
@@ -166,11 +159,30 @@ class SmartPortfolio:
166
  await self._save_state_to_r2()
167
  return False, {"reason": "Daily Limit Hit"}
168
 
169
- # 3. Slot Availability Check (Hard Limit)
170
- max_slots = self.MAX_SLOTS_ABOVE_THRESHOLD if current_cap >= self.MIN_CAPITAL_FOR_SPLIT else self.MAX_SLOTS_BELOW_THRESHOLD
 
171
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  if open_positions_count >= max_slots:
173
- return False, {"reason": f"Max slots reached ({open_positions_count}/{max_slots})"}
174
 
175
  # 4. Free Capital Check
176
  allocated = float(self.state.get("allocated_capital_usd", 0.0))
@@ -179,33 +191,29 @@ class SmartPortfolio:
179
  if free_capital < 5.0:
180
  return False, {"reason": f"Insufficient Free Capital (${free_capital:.2f})"}
181
 
182
- # 5. Position Sizing (Island Logic)
183
- # نقسم رأس المال الحر على عدد الخانات المتبقية لضمان التوزيع العادل
184
  remaining_slots = max_slots - open_positions_count
185
  base_allocation = 0.0
186
 
187
  if current_cap >= self.MIN_CAPITAL_FOR_SPLIT:
188
- # توزيع ذكي: رأس المال الكلي / عدد الخانات الكلي (للحفاظ على حجم ثابت)
189
- # لكن بشرط ألا يتجاوز رأس المال الحر
190
- target_size = current_cap / self.MAX_SLOTS_ABOVE_THRESHOLD
191
  base_allocation = min(target_size, free_capital)
192
  else:
193
- # حساب النمو (All-in Safe)
194
- base_allocation = free_capital * 0.95
195
 
196
  # 6. Consensus & Risk Multiplier
197
  system_confidence = self._calculate_composite_confidence(signal_data)
 
 
198
  risk_multiplier = 1.0
 
 
199
 
200
- if system_confidence >= 0.80: risk_multiplier = 1.35
201
- elif system_confidence >= 0.70: risk_multiplier = 1.0
202
- elif system_confidence >= 0.60: risk_multiplier = 0.80
203
- else: risk_multiplier = 0.50
204
-
205
- if self.market_trend == "BEARISH": risk_multiplier *= 0.5
206
 
207
- # الحجم النهائي (لا يتجاوز Free Capital ولا 98% من الإجمالي)
208
- final_size_usd = base_allocation * risk_multiplier
209
  final_size_usd = min(final_size_usd, free_capital, current_cap * 0.98)
210
 
211
  if final_size_usd < 5.0:
@@ -215,20 +223,16 @@ class SmartPortfolio:
215
  entry_price = float(signal_data.get('sniper_entry_price') or signal_data.get('current_price'))
216
  tp_map = signal_data.get('tp_map', {})
217
 
218
- # منطق الأهداف مع F&G
219
- # إذا كان الخوف شديد (القاع) + إشارة قوية -> استهدف القمر (TP4)
220
- if system_confidence >= 0.85 and (self.market_trend == "BULLISH" or self.fear_greed_index < 20):
221
- selected_tp = tp_map.get('TP4') or tp_map.get('TP3')
222
- target_label = "TP4 (Moonbag)"
223
- elif system_confidence >= 0.75:
224
- selected_tp = tp_map.get('TP3')
225
- target_label = "TP3"
226
- elif system_confidence >= 0.65:
227
  selected_tp = tp_map.get('TP2')
228
  target_label = "TP2"
229
- else:
230
- selected_tp = tp_map.get('TP1')
231
- target_label = "TP1"
232
 
233
  if not selected_tp or selected_tp <= entry_price: selected_tp = entry_price * 1.02
234
 
@@ -237,35 +241,30 @@ class SmartPortfolio:
237
  "approved_tp": float(selected_tp),
238
  "target_label": target_label,
239
  "system_confidence": system_confidence,
240
- "risk_multiplier": risk_multiplier,
241
- "market_mood": f"{self.market_trend} | FG:{self.fear_greed_index}"
242
  }
243
 
244
  # ==============================================================================
245
- # 🔒 Capital Tracking (Atomic Operations)
246
  # ==============================================================================
247
  async def register_new_position(self, size_usd: float):
248
- """يتم استدعاؤها عند فتح الصفقة لحجز رأس المال"""
249
  async with self.capital_lock:
250
  self.state["allocated_capital_usd"] = float(self.state.get("allocated_capital_usd", 0.0)) + float(size_usd)
251
- # حماية من الأخطاء الحسابية (لا يتجاوز الإجمالي)
252
  if self.state["allocated_capital_usd"] > self.state["current_capital"]:
253
  self.state["allocated_capital_usd"] = self.state["current_capital"]
254
  await self._save_state_to_r2()
255
 
256
  async def register_closed_position(self, released_capital_usd: float, net_pnl: float, fees: float):
257
- """يتم استدعاؤها عند الإغلاق لتحرير رأس المال وتحديث الربح"""
258
  async with self.capital_lock:
259
- # 1. تحرير رأس المال المحجوز
260
  current_allocated = float(self.state.get("allocated_capital_usd", 0.0))
261
  self.state["allocated_capital_usd"] = max(0.0, current_allocated - released_capital_usd)
262
 
263
- # 2. تحديث الرصيد الإجمالي (الصافي بعد الرسوم)
264
- net_impact = net_pnl - fees # نخصم الرسوم هنا لضمان الدقة
265
  self.state["current_capital"] += net_impact
266
  self.state["daily_net_pnl"] += net_impact
267
 
268
- # 3. فحص الخسارة اليومية
269
  start = self.state["session_start_balance"]
270
  dd = (start - self.state["current_capital"]) / start if start > 0 else 0
271
  if dd >= self.DAILY_LOSS_LIMIT_PCT:
@@ -273,7 +272,6 @@ class SmartPortfolio:
273
  self.state["halt_reason"] = "Daily Limit Hit After Exit"
274
 
275
  await self._save_state_to_r2()
276
- print(f"💰 [Portfolio] Capital Released: ${released_capital_usd:.2f} | Net Change: ${net_impact:+.2f}")
277
 
278
  # ==============================================================================
279
  # 💾 Utilities
@@ -293,8 +291,6 @@ class SmartPortfolio:
293
  if data:
294
  loaded = json.loads(data)
295
  self.state.update(loaded)
296
- # ضمان وجود المفاتيح الجديدة في حال الترقية من نسخة قديمة
297
- if "allocated_capital_usd" not in self.state: self.state["allocated_capital_usd"] = 0.0
298
  else:
299
  old = await self.r2.get_portfolio_state_async()
300
  if old:
@@ -305,8 +301,4 @@ class SmartPortfolio:
305
  async def _save_state_to_r2(self):
306
  try:
307
  await self.r2.upload_json_async(self.state, "smart_portfolio_state.json")
308
- # تحديث الملف القديم للتوافق
309
- existing = await self.r2.get_portfolio_state_async()
310
- existing["current_capital_usd"] = self.state["current_capital"]
311
- await self.r2.save_portfolio_state_async(existing)
312
  except: pass
 
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
 
16
  from datetime import datetime, timedelta
17
  from typing import Dict, Any, Tuple, Optional
18
 
19
+ # استيراد الدستور المركزي لقراءة حالة السوق
20
+ try:
21
+ from ml_engine.processor import SystemLimits
22
+ except ImportError:
23
+ # Fallback في حال التشغيل المنفصل
24
+ class SystemLimits:
25
+ CURRENT_REGIME = "RANGE"
26
+
27
  class SmartPortfolio:
28
  def __init__(self, r2_service, data_manager):
29
  self.r2 = r2_service
30
  self.data_manager = data_manager
31
 
32
+ # ⚙️ إعدادات المحفظة الأساسية
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
 
50
  self.capital_lock = asyncio.Lock()
51
 
52
+ # 📂 حالة المحفظة
53
  self.state = {
54
+ "current_capital": 10.0,
55
+ "allocated_capital_usd": 0.0,
56
  "session_start_balance": 10.0,
57
  "last_session_reset": datetime.now().isoformat(),
58
  "daily_net_pnl": 0.0,
 
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:
 
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:
 
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
 
 
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
  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))
 
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:
 
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')
235
  target_label = "TP2"
 
 
 
236
 
237
  if not selected_tp or selected_tp <= entry_price: selected_tp = entry_price * 1.02
238
 
 
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
  # ==============================================================================
249
+ # 🔒 Capital Tracking
250
  # ==============================================================================
251
  async def register_new_position(self, size_usd: float):
 
252
  async with self.capital_lock:
253
  self.state["allocated_capital_usd"] = float(self.state.get("allocated_capital_usd", 0.0)) + float(size_usd)
 
254
  if self.state["allocated_capital_usd"] > self.state["current_capital"]:
255
  self.state["allocated_capital_usd"] = self.state["current_capital"]
256
  await self._save_state_to_r2()
257
 
258
  async def register_closed_position(self, released_capital_usd: float, net_pnl: float, fees: float):
 
259
  async with self.capital_lock:
 
260
  current_allocated = float(self.state.get("allocated_capital_usd", 0.0))
261
  self.state["allocated_capital_usd"] = max(0.0, current_allocated - released_capital_usd)
262
 
263
+ net_impact = net_pnl - fees
 
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:
 
272
  self.state["halt_reason"] = "Daily Limit Hit After Exit"
273
 
274
  await self._save_state_to_r2()
 
275
 
276
  # ==============================================================================
277
  # 💾 Utilities
 
291
  if data:
292
  loaded = json.loads(data)
293
  self.state.update(loaded)
 
 
294
  else:
295
  old = await self.r2.get_portfolio_state_async()
296
  if old:
 
301
  async def _save_state_to_r2(self):
302
  try:
303
  await self.r2.upload_json_async(self.state, "smart_portfolio_state.json")
 
 
 
 
304
  except: pass