Spaces:
Paused
Paused
Update trade_manager.py
Browse files- trade_manager.py +35 -25
trade_manager.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
# ============================================================
|
| 2 |
-
# 🛡️ trade_manager.py (V35.
|
| 3 |
# ============================================================
|
| 4 |
|
| 5 |
import asyncio
|
|
@@ -32,7 +32,7 @@ class TradeManager:
|
|
| 32 |
"stagnation": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0}
|
| 33 |
}
|
| 34 |
self.execution_lock = asyncio.Lock()
|
| 35 |
-
print(f"🛡️ [TradeManager V35.
|
| 36 |
|
| 37 |
async def initialize_sentry_exchanges(self):
|
| 38 |
print("🛡️ [TradeManager] Syncing state & Initializing Portfolio...")
|
|
@@ -200,10 +200,9 @@ class TradeManager:
|
|
| 200 |
except: continue
|
| 201 |
|
| 202 |
if d1 and d5 and d15 and len(d5) >= 6:
|
| 203 |
-
#
|
| 204 |
-
# candle format: [ts, o, h, l, c, vol]
|
| 205 |
last_6_5m = d5[-6:]
|
| 206 |
-
vol_30m_sum = sum([float(c[5]) * float(c[4]) for c in last_6_5m])
|
| 207 |
|
| 208 |
context_data = {
|
| 209 |
'entry_price': trade['entry_price'],
|
|
@@ -214,7 +213,7 @@ class TradeManager:
|
|
| 214 |
'system_conf': trade.get('decision_data', {}).get('system_confidence', 0.8),
|
| 215 |
'highest_price': float(trade['highest_price']),
|
| 216 |
'time_in_trade_mins': (datetime.now() - datetime.fromisoformat(trade['entry_time'])).total_seconds() / 60,
|
| 217 |
-
'volume_30m_usd': vol_30m_sum
|
| 218 |
}
|
| 219 |
|
| 220 |
decision = self.processor.consult_dual_guardians(symbol, d1, d5, d15, context_data, order_book_snapshot=d_ob)
|
|
@@ -317,6 +316,9 @@ class TradeManager:
|
|
| 317 |
await self.learning_hub.register_trade_outcome(trade_obj)
|
| 318 |
except Exception: pass
|
| 319 |
|
|
|
|
|
|
|
|
|
|
| 320 |
async def _execute_exit(self, symbol, price, reason, ai_scores=None):
|
| 321 |
if symbol not in self.open_positions: return
|
| 322 |
try:
|
|
@@ -324,44 +326,52 @@ class TradeManager:
|
|
| 324 |
entry_price = float(trade['entry_price']); exit_price = float(price)
|
| 325 |
entry_capital = float(trade.get('entry_capital', 100.0)); entry_fee = float(trade.get('entry_fee_usd', 0.0))
|
| 326 |
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
|
|
|
|
|
|
| 330 |
total_fees = entry_fee + exit_fee
|
| 331 |
|
| 332 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 333 |
|
| 334 |
-
trade.update({
|
|
|
|
|
|
|
|
|
|
| 335 |
|
|
|
|
| 336 |
portfolio = await self.r2.get_portfolio_state_async()
|
| 337 |
portfolio['total_trades'] = portfolio.get('total_trades', 0) + 1
|
| 338 |
-
if
|
| 339 |
portfolio['winning_trades'] = portfolio.get('winning_trades', 0) + 1
|
| 340 |
-
portfolio['total_profit_usd'] = portfolio.get('total_profit_usd', 0) +
|
| 341 |
trade['result'] = 'WIN'
|
| 342 |
else:
|
| 343 |
portfolio['losing_trades'] = portfolio.get('losing_trades', 0) + 1
|
| 344 |
-
portfolio['total_loss_usd'] = portfolio.get('total_loss_usd', 0) + abs(
|
| 345 |
trade['result'] = 'LOSS'
|
| 346 |
|
| 347 |
await self.r2.save_portfolio_state_async(portfolio)
|
| 348 |
await self.r2.save_open_trades_async(list(self.open_positions.values()))
|
| 349 |
await self.r2.append_to_closed_trades_history(trade)
|
| 350 |
|
| 351 |
-
print(f"✅ [EXIT] {symbol} | Net PnL: {
|
| 352 |
self._launch_post_exit_analysis(symbol, exit_price, trade.get('exit_time'), entry_capital, ai_scores, trade)
|
| 353 |
self.latest_guardian_log = f"✅ Closed {symbol} ({reason})"
|
| 354 |
if symbol in self.sentry_tasks: self.sentry_tasks[symbol].cancel(); del self.sentry_tasks[symbol]
|
| 355 |
|
| 356 |
except Exception as e:
|
| 357 |
print(f"❌ [Exit Error] {e}"); traceback.print_exc()
|
| 358 |
-
if symbol not in self.open_positions: self.open_positions[symbol] = trade
|
| 359 |
-
|
| 360 |
-
async def force_exit_by_manager(self, symbol, reason):
|
| 361 |
-
p = await self.data_manager.get_latest_price_async(symbol)
|
| 362 |
-
async with self.execution_lock: await self._execute_exit(symbol, p, reason)
|
| 363 |
-
|
| 364 |
-
async def start_sentry_loops(self): await self.ensure_active_guardians()
|
| 365 |
-
async def stop_sentry_loops(self):
|
| 366 |
-
self.running = False
|
| 367 |
-
for task in self.sentry_tasks.values(): task.cancel()
|
|
|
|
| 1 |
# ============================================================
|
| 2 |
+
# 🛡️ trade_manager.py (V35.3 - GEM-Architect: Math & Fee Sync Fixed)
|
| 3 |
# ============================================================
|
| 4 |
|
| 5 |
import asyncio
|
|
|
|
| 32 |
"stagnation": {"total": 0, "good": 0, "saved": 0.0, "missed": 0.0}
|
| 33 |
}
|
| 34 |
self.execution_lock = asyncio.Lock()
|
| 35 |
+
print(f"🛡️ [TradeManager V35.3] True PnL Math Active.")
|
| 36 |
|
| 37 |
async def initialize_sentry_exchanges(self):
|
| 38 |
print("🛡️ [TradeManager] Syncing state & Initializing Portfolio...")
|
|
|
|
| 200 |
except: continue
|
| 201 |
|
| 202 |
if d1 and d5 and d15 and len(d5) >= 6:
|
| 203 |
+
# 30m Volume for Context
|
|
|
|
| 204 |
last_6_5m = d5[-6:]
|
| 205 |
+
vol_30m_sum = sum([float(c[5]) * float(c[4]) for c in last_6_5m])
|
| 206 |
|
| 207 |
context_data = {
|
| 208 |
'entry_price': trade['entry_price'],
|
|
|
|
| 213 |
'system_conf': trade.get('decision_data', {}).get('system_confidence', 0.8),
|
| 214 |
'highest_price': float(trade['highest_price']),
|
| 215 |
'time_in_trade_mins': (datetime.now() - datetime.fromisoformat(trade['entry_time'])).total_seconds() / 60,
|
| 216 |
+
'volume_30m_usd': vol_30m_sum
|
| 217 |
}
|
| 218 |
|
| 219 |
decision = self.processor.consult_dual_guardians(symbol, d1, d5, d15, context_data, order_book_snapshot=d_ob)
|
|
|
|
| 316 |
await self.learning_hub.register_trade_outcome(trade_obj)
|
| 317 |
except Exception: pass
|
| 318 |
|
| 319 |
+
# ==============================================================================
|
| 320 |
+
# 🔴 Exit Logic (MATH FIXED)
|
| 321 |
+
# ==============================================================================
|
| 322 |
async def _execute_exit(self, symbol, price, reason, ai_scores=None):
|
| 323 |
if symbol not in self.open_positions: return
|
| 324 |
try:
|
|
|
|
| 326 |
entry_price = float(trade['entry_price']); exit_price = float(price)
|
| 327 |
entry_capital = float(trade.get('entry_capital', 100.0)); entry_fee = float(trade.get('entry_fee_usd', 0.0))
|
| 328 |
|
| 329 |
+
# 1. Gross Calculation
|
| 330 |
+
exit_val_gross = (exit_price / entry_price) * entry_capital
|
| 331 |
+
|
| 332 |
+
# 2. Fees
|
| 333 |
+
exit_fee = exit_val_gross * self.FEE_RATE
|
| 334 |
total_fees = entry_fee + exit_fee
|
| 335 |
|
| 336 |
+
# 3. PnL for Portfolio (Gross PnL so portfolio handles fees correctly)
|
| 337 |
+
# Gross PnL = Difference in Asset Value
|
| 338 |
+
gross_pnl_usd = exit_val_gross - entry_capital
|
| 339 |
+
|
| 340 |
+
# 4. PnL for Statistics (True Net PnL)
|
| 341 |
+
# True Net = Gross PnL - All Fees
|
| 342 |
+
true_net_pnl_usd = gross_pnl_usd - total_fees
|
| 343 |
+
true_net_pct = (true_net_pnl_usd / entry_capital) * 100
|
| 344 |
+
|
| 345 |
+
# ✅ Notify Portfolio with Gross PnL (Because it subtracts fees internally)
|
| 346 |
+
# Impact on Wallet = Gross PnL - Total Fees
|
| 347 |
+
await self.smart_portfolio.register_closed_position(entry_capital, gross_pnl_usd, total_fees)
|
| 348 |
|
| 349 |
+
trade.update({
|
| 350 |
+
'status': 'CLOSED', 'exit_price': exit_price, 'exit_reason': reason,
|
| 351 |
+
'profit_pct': true_net_pct, 'net_pnl_usd': true_net_pnl_usd, 'fees_paid_usd': total_fees
|
| 352 |
+
})
|
| 353 |
|
| 354 |
+
# Update Stats with TRUE NET
|
| 355 |
portfolio = await self.r2.get_portfolio_state_async()
|
| 356 |
portfolio['total_trades'] = portfolio.get('total_trades', 0) + 1
|
| 357 |
+
if true_net_pnl_usd >= 0:
|
| 358 |
portfolio['winning_trades'] = portfolio.get('winning_trades', 0) + 1
|
| 359 |
+
portfolio['total_profit_usd'] = portfolio.get('total_profit_usd', 0) + true_net_pnl_usd
|
| 360 |
trade['result'] = 'WIN'
|
| 361 |
else:
|
| 362 |
portfolio['losing_trades'] = portfolio.get('losing_trades', 0) + 1
|
| 363 |
+
portfolio['total_loss_usd'] = portfolio.get('total_loss_usd', 0) + abs(true_net_pnl_usd)
|
| 364 |
trade['result'] = 'LOSS'
|
| 365 |
|
| 366 |
await self.r2.save_portfolio_state_async(portfolio)
|
| 367 |
await self.r2.save_open_trades_async(list(self.open_positions.values()))
|
| 368 |
await self.r2.append_to_closed_trades_history(trade)
|
| 369 |
|
| 370 |
+
print(f"✅ [EXIT] {symbol} | True Net PnL: {true_net_pct:.2f}% (${true_net_pnl_usd:.2f}) | {reason}")
|
| 371 |
self._launch_post_exit_analysis(symbol, exit_price, trade.get('exit_time'), entry_capital, ai_scores, trade)
|
| 372 |
self.latest_guardian_log = f"✅ Closed {symbol} ({reason})"
|
| 373 |
if symbol in self.sentry_tasks: self.sentry_tasks[symbol].cancel(); del self.sentry_tasks[symbol]
|
| 374 |
|
| 375 |
except Exception as e:
|
| 376 |
print(f"❌ [Exit Error] {e}"); traceback.print_exc()
|
| 377 |
+
if symbol not in self.open_positions: self.open_positions[symbol] = trade
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|