Spaces:
Running
Running
Update trade_manager.py
Browse files- trade_manager.py +77 -35
trade_manager.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# trade_manager.py (Updated to V7.
|
| 2 |
import asyncio
|
| 3 |
import json
|
| 4 |
import time
|
|
@@ -442,7 +442,7 @@ class TradeManager:
|
|
| 442 |
|
| 443 |
|
| 444 |
async def _run_tactical_analysis_loop(self, symbol: str, strategy_hint: str):
|
| 445 |
-
"""(محدث V7.
|
| 446 |
while self.is_running:
|
| 447 |
await asyncio.sleep(1)
|
| 448 |
try:
|
|
@@ -459,19 +459,21 @@ class TradeManager:
|
|
| 459 |
|
| 460 |
# 2. إذا لم يتم ضرب SL/TP، تحقق من "مراقب حماية الأرباح" 5m
|
| 461 |
if not exit_reason and tactical_data.new_5m_data_added:
|
| 462 |
-
# 🔴 --- START OF CHANGE (V7.
|
| 463 |
-
# (تعديل
|
| 464 |
analysis_result = await self._run_5m_profit_saver(trade, list(tactical_data.ohlcv_5m), tactical_data)
|
| 465 |
tactical_data.new_5m_data_added = False # (إعادة ضبط المؤشر)
|
| 466 |
|
| 467 |
if analysis_result:
|
|
|
|
| 468 |
score = analysis_result.get("score", 0)
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
|
|
|
| 472 |
else:
|
| 473 |
-
# (
|
| 474 |
-
print(f" [Sentry 5m] {symbol} (Profit-Saver):
|
| 475 |
# 🔴 --- END OF CHANGE --- 🔴
|
| 476 |
|
| 477 |
if exit_reason:
|
|
@@ -617,29 +619,31 @@ class TradeManager:
|
|
| 617 |
except Exception:
|
| 618 |
return pd.DataFrame()
|
| 619 |
|
| 620 |
-
# 🔴 --- START OF CHANGE (V7.
|
| 621 |
async def _run_5m_profit_saver(self, trade: Dict, ohlcv_5m_list: List, tactical_data: TacticalData) -> Dict:
|
| 622 |
"""
|
| 623 |
-
(محدث V7.
|
| 624 |
-
|
|
|
|
|
|
|
| 625 |
"""
|
| 626 |
-
reversal_threshold = 0.
|
| 627 |
|
| 628 |
try:
|
| 629 |
-
# --- 1. التحقق من الربحية (
|
| 630 |
best_bid_price = None
|
| 631 |
if tactical_data.order_book and tactical_data.order_book.get('bids') and len(tactical_data.order_book['bids']) > 0:
|
| 632 |
best_bid_price = tactical_data.order_book['bids'][0][0]
|
| 633 |
|
| 634 |
if best_bid_price is None:
|
| 635 |
-
return None # لا يمكن تحديد
|
| 636 |
|
| 637 |
entry_price = trade.get('entry_price')
|
| 638 |
-
|
| 639 |
-
return {"decision": "HOLD", "score": 0.0, "threshold": reversal_threshold, "reason": "Trade not profitable"}
|
| 640 |
|
| 641 |
-
# --- 2.
|
| 642 |
-
|
|
|
|
| 643 |
return None # بيانات غير كافية للتحليل
|
| 644 |
|
| 645 |
df_5m = self._create_dataframe_5m(ohlcv_5m_list)
|
|
@@ -649,32 +653,70 @@ class TradeManager:
|
|
| 649 |
indicators_5m = self.sentry_technical_analyzer.calculate_all_indicators(df_5m, '5m')
|
| 650 |
pattern_analysis_5m = await self.sentry_pattern_analyzer.detect_chart_patterns({'5m': ohlcv_5m_list})
|
| 651 |
|
| 652 |
-
# --- 3. حساب درجة الانعكاس (Reversal Score) ---
|
| 653 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 654 |
|
|
|
|
|
|
|
| 655 |
pattern_name = pattern_analysis_5m.get('pattern_detected', '')
|
| 656 |
pattern_conf = pattern_analysis_5m.get('pattern_confidence', 0)
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
|
|
|
|
|
|
|
|
|
| 660 |
rsi_5m = indicators_5m.get('rsi', 50)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 661 |
macd_hist_5m = indicators_5m.get('macd_hist', 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 662 |
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
reversal_score += 0.3
|
| 667 |
-
|
| 668 |
-
# --- 4. القرار ---
|
| 669 |
if reversal_score >= reversal_threshold:
|
| 670 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 671 |
else:
|
| 672 |
-
|
|
|
|
| 673 |
|
| 674 |
except Exception as e:
|
| 675 |
print(f"❌ [Sentry] خطأ في مراقب حماية الأرباح 5m: {e}")
|
| 676 |
-
|
| 677 |
-
|
|
|
|
| 678 |
|
| 679 |
|
| 680 |
async def _execute_smart_entry(self, symbol: str, strategy_hint: str, tactical_data: Dict, explorer_context: Dict):
|
|
@@ -939,4 +981,4 @@ class TradeManager:
|
|
| 939 |
except Exception as e: print(f"❌ Failed to get trade by symbol {symbol}: {e}"); return None
|
| 940 |
|
| 941 |
|
| 942 |
-
print(f"✅ Trade Manager loaded - V7.
|
|
|
|
| 1 |
+
# trade_manager.py (Updated to V7.2 - Fixed 5m Profit-Saver Logic)
|
| 2 |
import asyncio
|
| 3 |
import json
|
| 4 |
import time
|
|
|
|
| 442 |
|
| 443 |
|
| 444 |
async def _run_tactical_analysis_loop(self, symbol: str, strategy_hint: str):
|
| 445 |
+
"""(محدث V7.2) (دماغ الحارس) يشغل التحليل التكتيكي كل ثانية."""
|
| 446 |
while self.is_running:
|
| 447 |
await asyncio.sleep(1)
|
| 448 |
try:
|
|
|
|
| 459 |
|
| 460 |
# 2. إذا لم يتم ضرب SL/TP، تحقق من "مراقب حماية الأرباح" 5m
|
| 461 |
if not exit_reason and tactical_data.new_5m_data_added:
|
| 462 |
+
# 🔴 --- START OF CHANGE (V7.2) --- 🔴
|
| 463 |
+
# (تعديل لاستيعاب المنطق الجديد لحامي الأرباح)
|
| 464 |
analysis_result = await self._run_5m_profit_saver(trade, list(tactical_data.ohlcv_5m), tactical_data)
|
| 465 |
tactical_data.new_5m_data_added = False # (إعادة ضبط المؤشر)
|
| 466 |
|
| 467 |
if analysis_result:
|
| 468 |
+
decision = analysis_result.get("decision")
|
| 469 |
score = analysis_result.get("score", 0)
|
| 470 |
+
reason = analysis_result.get("reason", "N/A")
|
| 471 |
+
|
| 472 |
+
if decision == "EXIT":
|
| 473 |
+
exit_reason = f"Tactical 5m Profit Save: {reason} (Score: {score:.2f})"
|
| 474 |
else:
|
| 475 |
+
# (طباعة تشخيصية أفضل)
|
| 476 |
+
print(f" [Sentry 5m] {symbol} (Profit-Saver): {decision}. {reason} (Score: {score:.2f})")
|
| 477 |
# 🔴 --- END OF CHANGE --- 🔴
|
| 478 |
|
| 479 |
if exit_reason:
|
|
|
|
| 619 |
except Exception:
|
| 620 |
return pd.DataFrame()
|
| 621 |
|
| 622 |
+
# 🔴 --- START OF CHANGE (V7.2 - Logic Fix) --- 🔴
|
| 623 |
async def _run_5m_profit_saver(self, trade: Dict, ohlcv_5m_list: List, tactical_data: TacticalData) -> Dict:
|
| 624 |
"""
|
| 625 |
+
(محدث V7.2) "مراقب الانعكاس" 5m.
|
| 626 |
+
- يحسب درجة الخطر دائماً.
|
| 627 |
+
- يستخدم نظام تسجيل مرن وموزون.
|
| 628 |
+
- لا يخرج إلا إذا كان الخطر مرتفعاً *و* الصفقة رابحة.
|
| 629 |
"""
|
| 630 |
+
reversal_threshold = 0.70 # (عتبة خروج مرنة للنظام الموزون)
|
| 631 |
|
| 632 |
try:
|
| 633 |
+
# --- 1. التحقق من السعر الحالي وحالة الربحية (سنستخدمها في النهاية) ---
|
| 634 |
best_bid_price = None
|
| 635 |
if tactical_data.order_book and tactical_data.order_book.get('bids') and len(tactical_data.order_book['bids']) > 0:
|
| 636 |
best_bid_price = tactical_data.order_book['bids'][0][0]
|
| 637 |
|
| 638 |
if best_bid_price is None:
|
| 639 |
+
return None # لا يمكن تحديد السعر، لا يمكن المتابعة
|
| 640 |
|
| 641 |
entry_price = trade.get('entry_price')
|
| 642 |
+
is_profitable = best_bid_price > entry_price
|
|
|
|
| 643 |
|
| 644 |
+
# --- 2. تشغيل الكاشف 5m (يجب أن يعمل دائماً لحساب الخطر) ---
|
| 645 |
+
# (يتطلب 26 شمعة على الأقل لـ MACD)
|
| 646 |
+
if len(ohlcv_5m_list) < 26:
|
| 647 |
return None # بيانات غير كافية للتحليل
|
| 648 |
|
| 649 |
df_5m = self._create_dataframe_5m(ohlcv_5m_list)
|
|
|
|
| 653 |
indicators_5m = self.sentry_technical_analyzer.calculate_all_indicators(df_5m, '5m')
|
| 654 |
pattern_analysis_5m = await self.sentry_pattern_analyzer.detect_chart_patterns({'5m': ohlcv_5m_list})
|
| 655 |
|
| 656 |
+
# --- 3. حساب درجة الانعكاس (Reversal Score) - نظام مرن وموزون ---
|
| 657 |
+
|
| 658 |
+
# الأوزان
|
| 659 |
+
weights = {
|
| 660 |
+
'pattern': 0.40,
|
| 661 |
+
'rsi': 0.30,
|
| 662 |
+
'macd': 0.30
|
| 663 |
+
}
|
| 664 |
|
| 665 |
+
# الدرجة 1: الأنماط (Patterns)
|
| 666 |
+
pattern_score = 0.0
|
| 667 |
pattern_name = pattern_analysis_5m.get('pattern_detected', '')
|
| 668 |
pattern_conf = pattern_analysis_5m.get('pattern_confidence', 0)
|
| 669 |
+
# (الأنماط السلبية فقط تساهم في خطر الانعكاس)
|
| 670 |
+
if pattern_name in ['Double Top', 'Downtrend', 'Breakout Down', 'Near Resistance'] and pattern_conf > 0.5:
|
| 671 |
+
pattern_score = pattern_conf # (استخدام الثقة مباشرة كدرجة)
|
| 672 |
+
|
| 673 |
+
# الدرجة 2: مؤشر القوة النسبية (RSI) - درجة متدرجة
|
| 674 |
+
rsi_score = 0.0
|
| 675 |
rsi_5m = indicators_5m.get('rsi', 50)
|
| 676 |
+
if rsi_5m < 50:
|
| 677 |
+
# (كلما انخفض تحت 50، زادت درجة الخطر، تصل إلى 1.0 عند 30 أو أقل)
|
| 678 |
+
# (النطاق: 50 -> 30)
|
| 679 |
+
rsi_score = min(1.0, (50 - rsi_5m) / 20.0)
|
| 680 |
+
|
| 681 |
+
# الدرجة 3: الماكد (MACD) - درجة متدرجة
|
| 682 |
+
macd_score = 0.0
|
| 683 |
macd_hist_5m = indicators_5m.get('macd_hist', 0)
|
| 684 |
+
if macd_hist_5m < 0:
|
| 685 |
+
# (نحتاج إلى "تطبيع" قيمة الهيستوجرام. سنستخدم نسبة مئوية من السعر)
|
| 686 |
+
current_price = df_5m['close'].iloc[-1]
|
| 687 |
+
if current_price > 0:
|
| 688 |
+
normalized_macd_hist = abs(macd_hist_5m) / current_price
|
| 689 |
+
# (تقدير: إذا كان الهيستوجرام سلبي بنسبة 0.1% من السعر، فهذا خطر 100%)
|
| 690 |
+
# (النطاق: 0 -> 0.001)
|
| 691 |
+
macd_score = min(1.0, normalized_macd_hist / 0.001)
|
| 692 |
+
|
| 693 |
+
# الحساب النهائي للدرجة
|
| 694 |
+
reversal_score = (
|
| 695 |
+
(pattern_score * weights['pattern']) +
|
| 696 |
+
(rsi_score * weights['rsi']) +
|
| 697 |
+
(macd_score * weights['macd'])
|
| 698 |
+
)
|
| 699 |
|
| 700 |
+
# --- 4. القرار (المنطق الجديد) ---
|
| 701 |
+
|
| 702 |
+
# (التحقق من زناد الخروج)
|
|
|
|
|
|
|
|
|
|
| 703 |
if reversal_score >= reversal_threshold:
|
| 704 |
+
# (الخطر مرتفع. هل الصفقة رابحة؟)
|
| 705 |
+
if is_profitable:
|
| 706 |
+
# (نعم، الخطر مرتفع والصفقة رابحة -> اخرج!)
|
| 707 |
+
return {"decision": "EXIT", "score": reversal_score, "threshold": reversal_threshold, "reason": "Reversal signal detected and trade is profitable"}
|
| 708 |
+
else:
|
| 709 |
+
# (الخطر مرتفع، لكن الصفقة خاسرة/متعادلة -> لا تخرج، فقط سجل)
|
| 710 |
+
return {"decision": "HOLD", "score": reversal_score, "threshold": reversal_threshold, "reason": "Reversal signal detected, but trade is not profitable"}
|
| 711 |
else:
|
| 712 |
+
# (الخطر منخفض -> لا تخرج)
|
| 713 |
+
return {"decision": "HOLD", "score": reversal_score, "threshold": reversal_threshold, "reason": "Trend intact / Reversal score low"}
|
| 714 |
|
| 715 |
except Exception as e:
|
| 716 |
print(f"❌ [Sentry] خطأ في مراقب حماية الأرباح 5m: {e}")
|
| 717 |
+
# (إرجاع نتيجة آمنة)
|
| 718 |
+
return {"decision": "HOLD", "score": 0.0, "threshold": reversal_threshold, "reason": f"Error in 5m analysis: {e}"}
|
| 719 |
+
# 🔴 --- END OF CHANGE (V7.2) --- 🔴
|
| 720 |
|
| 721 |
|
| 722 |
async def _execute_smart_entry(self, symbol: str, strategy_hint: str, tactical_data: Dict, explorer_context: Dict):
|
|
|
|
| 981 |
except Exception as e: print(f"❌ Failed to get trade by symbol {symbol}: {e}"); return None
|
| 982 |
|
| 983 |
|
| 984 |
+
print(f"✅ Trade Manager loaded - V7.2 (Fixed 5m Profit-Saver Logic) (ccxt.async_support: {CCXT_ASYNC_AVAILABLE})")
|