|
|
|
|
|
import asyncio |
|
|
import json |
|
|
import uuid |
|
|
import traceback |
|
|
from datetime import datetime |
|
|
|
|
|
class TradeManager: |
|
|
def __init__(self, r2_service, data_manager, titan_engine, pattern_engine): |
|
|
self.r2 = r2_service |
|
|
self.data_manager = data_manager |
|
|
self.titan = titan_engine |
|
|
self.pattern_engine = pattern_engine |
|
|
|
|
|
|
|
|
self.open_positions = {} |
|
|
self.watchlist = {} |
|
|
self.sentry_tasks = {} |
|
|
self.running = True |
|
|
|
|
|
|
|
|
|
|
|
self.execution_lock = asyncio.Lock() |
|
|
|
|
|
print("🛡️ [TradeManager V8.7] Sentry Module Initialized (Sync & Lock Enabled).") |
|
|
|
|
|
async def initialize_sentry_exchanges(self): |
|
|
""" |
|
|
تحميل الصفقات المفتوحة من R2 عند بدء تشغيل النظام لاستكمال إدارتها. |
|
|
تعتمد الآن على وظيفة المزامنة المركزية. |
|
|
""" |
|
|
print("🛡️ [TradeManager] Initializing and syncing state with R2...") |
|
|
await self.sync_internal_state() |
|
|
|
|
|
async def sync_internal_state(self): |
|
|
""" |
|
|
مزامنة الحالة الداخلية مع R2 لضمان عدم فقدان أي صفقة مفتوحة. |
|
|
تستخدم عند بدء النظام وعند بدء كل دورة تحليل جديدة لضمان تطابق البيانات. |
|
|
""" |
|
|
try: |
|
|
|
|
|
loaded_trades = await self.r2.get_open_trades_async() |
|
|
|
|
|
if loaded_trades: |
|
|
|
|
|
r2_symbols = set(t['symbol'] for t in loaded_trades) |
|
|
|
|
|
|
|
|
for t in loaded_trades: |
|
|
symbol = t['symbol'] |
|
|
if symbol not in self.open_positions: |
|
|
self.open_positions[symbol] = t |
|
|
print(f"🔄 [TradeManager] SYNC: Resumed watching trade from R2: {symbol}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await self.start_sentry_loops() |
|
|
|
|
|
except Exception as e: |
|
|
print(f"⚠️ [TradeManager] Critical State Sync Warning: {e}") |
|
|
traceback.print_exc() |
|
|
|
|
|
async def update_sentry_watchlist(self, approved_candidates): |
|
|
""" |
|
|
تحديث قائمة المراقبة النشطة للحارس (Sniper) بناءً على ترشيحات الدماغ الجديدة. |
|
|
""" |
|
|
|
|
|
new_symbols = set(c['symbol'] for c in approved_candidates) |
|
|
for symbol in list(self.watchlist.keys()): |
|
|
if symbol not in new_symbols: |
|
|
if symbol in self.sentry_tasks: |
|
|
self.sentry_tasks[symbol].cancel() |
|
|
del self.watchlist[symbol] |
|
|
|
|
|
|
|
|
for cand in approved_candidates: |
|
|
symbol = cand['symbol'] |
|
|
if symbol not in self.watchlist: |
|
|
self.watchlist[symbol] = { |
|
|
'data': cand, |
|
|
'added_at': datetime.utcnow().isoformat(), |
|
|
'monitoring_started': False |
|
|
} |
|
|
|
|
|
print(f"📋 [Sniper] Watchlist updated. Active targets: {len(self.watchlist)}") |
|
|
|
|
|
|
|
|
for symbol in list(self.watchlist.keys()): |
|
|
|
|
|
if symbol not in self.sentry_tasks or self.sentry_tasks[symbol].done(): |
|
|
print(f"🎯 [Sniper] Locking radar on target: {symbol}") |
|
|
self.sentry_tasks[symbol] = asyncio.create_task(self._sniper_loop(symbol)) |
|
|
|
|
|
async def _sniper_loop(self, symbol): |
|
|
""" |
|
|
حلقة المراقبة التكتيكية (القناص) - تراقب الرمز لحظة بلحظة لاقتناص فرصة الدخول. |
|
|
""" |
|
|
print(f"👁️ [Sniper] Monitoring {symbol} for tactical entry...") |
|
|
consecutive_signals = 0 |
|
|
required_signals = 2 |
|
|
|
|
|
try: |
|
|
while self.running and symbol in self.watchlist and symbol not in self.open_positions: |
|
|
|
|
|
|
|
|
if len(self.open_positions) >= 1: |
|
|
await asyncio.sleep(60) |
|
|
continue |
|
|
|
|
|
|
|
|
ohlcv = await self.data_manager.get_latest_ohlcv(symbol, timeframe='5m', limit=30) |
|
|
if not ohlcv or len(ohlcv) < 20: |
|
|
await asyncio.sleep(30) |
|
|
continue |
|
|
|
|
|
|
|
|
titan_res = self.titan.predict({'5m': ohlcv}) |
|
|
current_score = titan_res.get('score', 0.0) |
|
|
|
|
|
|
|
|
is_signal = False |
|
|
entry_reason = "" |
|
|
|
|
|
|
|
|
if current_score > 0.80: |
|
|
is_signal = True |
|
|
entry_reason = f"Titan Surge ({current_score:.2f})" |
|
|
|
|
|
if is_signal: |
|
|
consecutive_signals += 1 |
|
|
print(f"🔥 [Sniper] {symbol} Signal detected! ({consecutive_signals}/{required_signals}) - {entry_reason}") |
|
|
|
|
|
if consecutive_signals >= required_signals: |
|
|
|
|
|
current_price = ohlcv[-1][4] |
|
|
|
|
|
executed = await self.execute_buy_order(symbol, entry_reason, current_price) |
|
|
|
|
|
if executed: |
|
|
|
|
|
print(f"✅ [Sniper] Mission accomplished for {symbol}. Standing down.") |
|
|
break |
|
|
else: |
|
|
|
|
|
consecutive_signals = 0 |
|
|
else: |
|
|
|
|
|
if consecutive_signals > 0: |
|
|
|
|
|
consecutive_signals = 0 |
|
|
|
|
|
await asyncio.sleep(30) |
|
|
|
|
|
except asyncio.CancelledError: |
|
|
|
|
|
pass |
|
|
except Exception as e: |
|
|
print(f"❌ [Sniper Error] Loop crashed for {symbol}: {e}") |
|
|
traceback.print_exc() |
|
|
|
|
|
async def execute_buy_order(self, symbol, reason, price): |
|
|
""" |
|
|
تنفيذ أمر الشراء. هذه الدالة حرجة جداً ومحمية بقفل (Lock). |
|
|
""" |
|
|
async with self.execution_lock: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if len(self.open_positions) >= 1: |
|
|
print(f"⚠️ [Execution Blocked] Cannot buy {symbol}. Max positions (1) reached.") |
|
|
return False |
|
|
|
|
|
|
|
|
portfolio = await self.r2.get_portfolio_state_async() |
|
|
capital = portfolio.get('current_capital_usd', 0.0) |
|
|
|
|
|
if capital < 5.0: |
|
|
print(f"⚠️ [Execution Failed] Insufficient capital for {symbol}: ${capital:.2f}") |
|
|
return False |
|
|
|
|
|
|
|
|
invest_amount = capital * 0.99 |
|
|
quantity = invest_amount / price |
|
|
|
|
|
|
|
|
trade_id = str(uuid.uuid4())[:8] |
|
|
new_trade = { |
|
|
'trade_id': trade_id, |
|
|
'symbol': symbol, |
|
|
'status': 'OPEN', |
|
|
'entry_time': datetime.utcnow().isoformat(), |
|
|
'entry_price': price, |
|
|
'quantity': quantity, |
|
|
'invested_usd': invest_amount, |
|
|
'entry_reason': reason, |
|
|
'tp_price': price * 1.05, |
|
|
'sl_price': price * 0.95, |
|
|
'highest_price': price |
|
|
} |
|
|
|
|
|
|
|
|
self.open_positions[symbol] = new_trade |
|
|
if symbol in self.watchlist: |
|
|
del self.watchlist[symbol] |
|
|
|
|
|
|
|
|
portfolio['current_capital_usd'] -= invest_amount |
|
|
portfolio['invested_capital_usd'] += invest_amount |
|
|
portfolio['total_trades'] += 1 |
|
|
|
|
|
await self.r2.save_portfolio_state_async(portfolio) |
|
|
await self.r2.save_open_trades_async(list(self.open_positions.values())) |
|
|
|
|
|
print(f"🚀 [EXECUTED] BUY {symbol} @ {price:.4f} | Invested: ${invest_amount:.2f} | Reason: {reason}") |
|
|
|
|
|
|
|
|
if symbol in self.sentry_tasks and not self.sentry_tasks[symbol].done(): |
|
|
self.sentry_tasks[symbol].cancel() |
|
|
self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol)) |
|
|
|
|
|
return True |
|
|
|
|
|
async def _guardian_loop(self, symbol): |
|
|
""" |
|
|
حلقة الحماية (Guardian) - تراقب الصفقة المفتوحة وتدير الخروج. |
|
|
""" |
|
|
print(f"🛡️ [Guardian] Activated for position: {symbol}") |
|
|
try: |
|
|
while self.running and symbol in self.open_positions: |
|
|
trade = self.open_positions[symbol] |
|
|
|
|
|
|
|
|
current_price = await self.data_manager.get_latest_price_async(symbol) |
|
|
if not current_price or current_price <= 0: |
|
|
await asyncio.sleep(30) |
|
|
continue |
|
|
|
|
|
|
|
|
if current_price > trade.get('highest_price', 0): |
|
|
trade['highest_price'] = current_price |
|
|
|
|
|
|
|
|
|
|
|
if current_price <= trade['sl_price']: |
|
|
print(f"🛡️ [Guardian] STOP LOSS triggered for {symbol} @ {current_price:.4f} (SL: {trade['sl_price']:.4f})") |
|
|
await self.execute_sell_order(symbol, "Stop Loss Hit", current_price) |
|
|
break |
|
|
|
|
|
elif current_price >= trade['tp_price']: |
|
|
print(f"🛡️ [Guardian] TAKE PROFIT triggered for {symbol} @ {current_price:.4f} (TP: {trade['tp_price']:.4f})") |
|
|
await self.execute_sell_order(symbol, "Take Profit Hit", current_price) |
|
|
break |
|
|
|
|
|
await asyncio.sleep(10) |
|
|
|
|
|
except asyncio.CancelledError: |
|
|
print(f"🛡️ [Guardian] Task cancelled for {symbol}") |
|
|
except Exception as e: |
|
|
print(f"❌ [Guardian Error] Loop crashed for {symbol}: {e}") |
|
|
traceback.print_exc() |
|
|
|
|
|
async def execute_sell_order(self, symbol, reason, price): |
|
|
""" |
|
|
تنفيذ أمر البيع. محمي بـ execution_lock. |
|
|
""" |
|
|
async with self.execution_lock: |
|
|
|
|
|
if symbol not in self.open_positions: |
|
|
print(f"⚠️ [Sell Failed] {symbol} not found in open positions during execution.") |
|
|
return |
|
|
|
|
|
trade = self.open_positions.pop(symbol) |
|
|
|
|
|
|
|
|
revenue = trade['quantity'] * price |
|
|
profit_usd = revenue - trade['invested_usd'] |
|
|
profit_pct = (profit_usd / trade['invested_usd']) * 100 |
|
|
|
|
|
|
|
|
portfolio = await self.r2.get_portfolio_state_async() |
|
|
portfolio['current_capital_usd'] += revenue |
|
|
portfolio['invested_capital_usd'] -= trade['invested_usd'] |
|
|
portfolio['total_profit_usd'] += profit_usd |
|
|
|
|
|
if profit_usd > 0: |
|
|
portfolio['winning_trades'] += 1 |
|
|
|
|
|
|
|
|
await self.r2.save_portfolio_state_async(portfolio) |
|
|
await self.r2.save_open_trades_async(list(self.open_positions.values())) |
|
|
|
|
|
print(f"💰 [SOLD] {symbol} @ {price:.4f} | PnL: ${profit_usd:.2f} ({profit_pct:+.2f}%) | Reason: {reason}") |
|
|
|
|
|
async def execute_emergency_exit(self, symbol, reason): |
|
|
"""واجهة للخروج الطارئ اليدوي أو من الدماغ""" |
|
|
print(f"🚨 [Emergency] Requested exit for {symbol} due to: {reason}") |
|
|
current_price = await self.data_manager.get_latest_price_async(symbol) |
|
|
if current_price and current_price > 0: |
|
|
await self.execute_sell_order(symbol, f"EMERGENCY: {reason}", current_price) |
|
|
else: |
|
|
print(f"❌ [Emergency Failed] Could not fetch valid price for {symbol}") |
|
|
|
|
|
async def update_trade_targets(self, symbol, new_tp, new_sl, reason): |
|
|
"""تحديث أهداف الصفقة بناءً على أوامر الدماغ""" |
|
|
if symbol in self.open_positions: |
|
|
trade = self.open_positions[symbol] |
|
|
old_tp = trade['tp_price'] |
|
|
old_sl = trade['sl_price'] |
|
|
|
|
|
if new_tp is not None: trade['tp_price'] = float(new_tp) |
|
|
if new_sl is not None: trade['sl_price'] = float(new_sl) |
|
|
|
|
|
self.open_positions[symbol] = trade |
|
|
|
|
|
await self.r2.save_open_trades_async(list(self.open_positions.values())) |
|
|
|
|
|
print(f"🎯 [Targets Updated] {symbol} | TP: {old_tp:.4f}->{trade['tp_price']:.4f} | SL: {old_sl:.4f}->{trade['sl_price']:.4f} | Reason: {reason}") |
|
|
else: |
|
|
print(f"⚠️ [Target Update Failed] {symbol} is not currently open.") |
|
|
|
|
|
async def start_sentry_loops(self): |
|
|
"""بدء أو استئناف جميع مهام الحراسة""" |
|
|
for symbol in list(self.open_positions.keys()): |
|
|
|
|
|
if symbol not in self.sentry_tasks or self.sentry_tasks[symbol].done(): |
|
|
print(f"🛡️ [Sentry Restart] Activating guardian for existing trade: {symbol}") |
|
|
self.sentry_tasks[symbol] = asyncio.create_task(self._guardian_loop(symbol)) |
|
|
|
|
|
async def stop_sentry_loops(self): |
|
|
"""إيقاف نظيف لجميع المهام الخلفية""" |
|
|
print("🛑 [TradeManager] Stopping all sentry loops...") |
|
|
self.running = False |
|
|
for sym, task in self.sentry_tasks.items(): |
|
|
task.cancel() |
|
|
|
|
|
|
|
|
if self.sentry_tasks: |
|
|
await asyncio.gather(*self.sentry_tasks.values(), return_exceptions=True) |
|
|
|
|
|
print("✅ [TradeManager] All loops stopped.") |