feat: Add Signal Cache for ML training data collection

- Add signal_cache.py module to persist signal data between trade open/close
- Modify execute_trade_v2_adaptive to cache signal info when trade opens
- Update sync_closed_trades_to_tracker to retrieve cached ML features
- Update scheduled_demo_tracker_sync with same ML feature retrieval

This enables proper ML training by capturing:
- base_confidence, enhanced_score, hybrid_score
- signal_quality, market_regime, regime_strength
- session and lot_multiplier

Previously all trades were logged with 0 values for ML features.
After ~50-100 new trades, the ML model can be properly trained.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
cbazzaandClaude Opus 4.5 committed 2026-02-16 12:50:55 +01:00
1 parent 7f2de4d227
commit d25727839a
2 files changed
+374 -464

No files matched your search

+86 -464
View File
@@ -1217,231 +1217,7 @@
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"def execute_trade_v2_adaptive(\n", "def execute_trade_v2_adaptive(\n symbol=None,\n atr_mult=None,\n base_confidence=None,\n max_risk_per_trade=None,\n risk_filter=True,\n min_atr=0.0008,\n use_pullback_entry=False, # DISABLED\n max_positions=None,\n strategy_name=\"TradingBot_V1.6\",\n debug=True,\n # Enhanced Scoring Overrides\n signal_info_override=None,\n confidence_override=None,\n # Equity Curve Trading\n lot_multiplier=1.0\n):\n \"\"\"\n V1.6 Adaptive Complete Trade-Ausführung:\n - Position Control\n - Relaxed Parameter\n - Adaptive Rhythm Integration\n \"\"\"\n \n # ========================================================================\n # LOAD DEFAULTS FROM TRADING_CONFIG\n # ========================================================================\n if symbol is None:\n symbol = TRADING_CONFIG[\"symbols\"][\"primary\"]\n if atr_mult is None:\n atr_mult = TRADING_CONFIG[\"atr\"][\"base_multiplier\"]\n if base_confidence is None:\n base_confidence = TRADING_CONFIG[\"confidence\"][\"base_threshold\"]\n if max_risk_per_trade is None:\n max_risk_per_trade = TRADING_CONFIG[\"risk\"][\"max_risk_per_trade\"]\n if max_positions is None:\n max_positions = TRADING_CONFIG[\"risk\"][\"max_positions\"]\n \n \n # SCHRITT 1: POSITION CHECK\n print(f\"\\n🔍 POSITION CHECK für {symbol} (V1.6 Adaptive Complete)\")\n has_position, position_info = check_existing_positions(symbol, strategy_name)\n \n if has_position and position_info['count'] >= max_positions:\n if debug:\n print(f\"🛑 TRADE BLOCKIERT: {position_info['count']}/{max_positions} Positionen aktiv\")\n for pos in position_info['details']:\n profit_emoji = \"🟢\" if pos['profit'] >= 0 else \"🔴\"\n print(f\" {pos['type']} @ {pos['price_open']} | {profit_emoji} {pos['profit']:.2f}\")\n return None\n \n print(f\"✅ Position-Check OK: {position_info['count']}/{max_positions}\")\n \n # SCHRITT 2: Signal Analysis (use override if provided)\n if signal_info_override is not None:\n signal_info = signal_info_override\n print(\"📊 Using pre-calculated signal info (Enhanced Scoring)\")\n else:\n signal_info = extended_top_down_v2_adaptive(symbol)\n if signal_info is None:\n print(\"❌ Signal-Analyse fehlgeschlagen\")\n return None\n \n entry_signal = signal_info[\"entry_signal\"]\n # Use override confidence if provided (from Enhanced Scoring)\n confidence = confidence_override if confidence_override is not None else signal_info[\"confidence\"]\n adaptive_threshold = signal_info[\"adaptive_threshold\"]\n signal_quality = signal_info[\"signal_quality\"]\n market_regime = signal_info[\"market_regime\"]\n \n # SCHRITT 3: Get Price/ATR\n m5_info = signal_info[\"trend_info\"][\"M5\"]\n price = m5_info[\"price\"]\n atr = m5_info[\"atr\"]\n \n # SCHRITT 4: Pre-checks\n reason = \"\"\n \n if confidence < adaptive_threshold:\n reason = f\"Confidence {confidence}% < threshold {adaptive_threshold}%\"\n elif entry_signal == 0:\n reason = f\"No entry signal\"\n elif price is None or atr is None:\n reason = \"Price/ATR not available\"\n elif risk_filter and atr < min_atr:\n reason = f\"ATR {atr:.5f} < min_atr {min_atr}\"\n else:\n risk_ok = check_risk_limits(symbol, max_risk_per_trade=max_risk_per_trade)\n if not risk_ok:\n reason = \"Risk limits exceeded\"\n \n # SCHRITT 5: Execute Trade\n if not reason:\n # Final Position Check\n final_check, _ = check_existing_positions(symbol, strategy_name)\n if final_check:\n print(f\"🛑 Position wurde zwischen Checks eröffnet!\")\n return None\n \n # SL/TP Calculation\n regime_mult = 1.0\n if market_regime['regime'] == 'volatile':\n regime_mult = 1.2\n elif market_regime['regime'] == 'ranging':\n regime_mult = 0.9\n \n adjusted_atr_mult = atr_mult * regime_mult\n \n if entry_signal == 1: # Long\n stop_loss = price - adjusted_atr_mult * atr\n take_profit = price + adjusted_atr_mult * atr * 2.5\n else: # Short\n stop_loss = price + adjusted_atr_mult * atr\n take_profit = price - adjusted_atr_mult * atr * 2.5\n \n # Position Sizing\n account_info = mt.account_info()\n if account_info:\n balance = account_info.balance\n risk_amount = balance * max_risk_per_trade\n if symbol == \"XAUUSD\":\n # 🎯 ADAPTIVE POSITION SIZING\n if 'adv_position_mgr' in globals() and adv_position_mgr.adaptive_sizing:\n volume = adv_position_mgr.adaptive_sizing.calculate_position_size(\n Line truncated
" symbol=None,\n",
" atr_mult=None,\n",
" base_confidence=None,\n",
" max_risk_per_trade=None,\n",
" risk_filter=True,\n",
" min_atr=0.0008,\n",
" use_pullback_entry=False, # DISABLED\n",
" max_positions=None,\n",
" strategy_name=\"TradingBot_V1.6\",\n",
" debug=True,\n",
" # Enhanced Scoring Overrides\n",
" signal_info_override=None,\n",
" confidence_override=None,\n",
" # Equity Curve Trading\n",
" lot_multiplier=1.0\n",
"):\n",
" \"\"\"\n",
" V1.6 Adaptive Complete Trade-Ausführung:\n",
" - Position Control\n",
" - Relaxed Parameter\n",
" - Adaptive Rhythm Integration\n",
" \"\"\"\n",
" \n",
" # ========================================================================\n",
" # LOAD DEFAULTS FROM TRADING_CONFIG\n",
" # ========================================================================\n",
" if symbol is None:\n",
" symbol = TRADING_CONFIG[\"symbols\"][\"primary\"]\n",
" if atr_mult is None:\n",
" atr_mult = TRADING_CONFIG[\"atr\"][\"base_multiplier\"]\n",
" if base_confidence is None:\n",
" base_confidence = TRADING_CONFIG[\"confidence\"][\"base_threshold\"]\n",
" if max_risk_per_trade is None:\n",
" max_risk_per_trade = TRADING_CONFIG[\"risk\"][\"max_risk_per_trade\"]\n",
" if max_positions is None:\n",
" max_positions = TRADING_CONFIG[\"risk\"][\"max_positions\"]\n",
" \n",
" \n",
" # SCHRITT 1: POSITION CHECK\n",
" print(f\"\\n🔍 POSITION CHECK für {symbol} (V1.6 Adaptive Complete)\")\n",
" has_position, position_info = check_existing_positions(symbol, strategy_name)\n",
" \n",
" if has_position and position_info['count'] >= max_positions:\n",
" if debug:\n",
" print(f\"🛑 TRADE BLOCKIERT: {position_info['count']}/{max_positions} Positionen aktiv\")\n",
" for pos in position_info['details']:\n",
" profit_emoji = \"🟢\" if pos['profit'] >= 0 else \"🔴\"\n",
" print(f\" {pos['type']} @ {pos['price_open']} | {profit_emoji} {pos['profit']:.2f}\")\n",
" return None\n",
" \n",
" print(f\"✅ Position-Check OK: {position_info['count']}/{max_positions}\")\n",
" \n",
" # SCHRITT 2: Signal Analysis (use override if provided)\n",
" if signal_info_override is not None:\n",
" signal_info = signal_info_override\n",
" print(\"📊 Using pre-calculated signal info (Enhanced Scoring)\")\n",
" else:\n",
" signal_info = extended_top_down_v2_adaptive(symbol)\n",
" if signal_info is None:\n",
" print(\"❌ Signal-Analyse fehlgeschlagen\")\n",
" return None\n",
" \n",
" entry_signal = signal_info[\"entry_signal\"]\n",
" # Use override confidence if provided (from Enhanced Scoring)\n",
" confidence = confidence_override if confidence_override is not None else signal_info[\"confidence\"]\n",
" adaptive_threshold = signal_info[\"adaptive_threshold\"]\n",
" signal_quality = signal_info[\"signal_quality\"]\n",
" market_regime = signal_info[\"market_regime\"]\n",
" \n",
" # SCHRITT 3: Get Price/ATR\n",
" m5_info = signal_info[\"trend_info\"][\"M5\"]\n",
" price = m5_info[\"price\"]\n",
" atr = m5_info[\"atr\"]\n",
" \n",
" # SCHRITT 4: Pre-checks\n",
" reason = \"\"\n",
" \n",
" if confidence < adaptive_threshold:\n",
" reason = f\"Confidence {confidence}% < threshold {adaptive_threshold}%\"\n",
" elif entry_signal == 0:\n",
" reason = f\"No entry signal\"\n",
" elif price is None or atr is None:\n",
" reason = \"Price/ATR not available\"\n",
" elif risk_filter and atr < min_atr:\n",
" reason = f\"ATR {atr:.5f} < min_atr {min_atr}\"\n",
" else:\n",
" risk_ok = check_risk_limits(symbol, max_risk_per_trade=max_risk_per_trade)\n",
" if not risk_ok:\n",
" reason = \"Risk limits exceeded\"\n",
" \n",
" # SCHRITT 5: Execute Trade\n",
" if not reason:\n",
" # Final Position Check\n",
" final_check, _ = check_existing_positions(symbol, strategy_name)\n",
" if final_check:\n",
" print(f\"🛑 Position wurde zwischen Checks eröffnet!\")\n",
" return None\n",
" \n",
" # SL/TP Calculation\n",
" regime_mult = 1.0\n",
" if market_regime['regime'] == 'volatile':\n",
" regime_mult = 1.2\n",
" elif market_regime['regime'] == 'ranging':\n",
" regime_mult = 0.9\n",
" \n",
" adjusted_atr_mult = atr_mult * regime_mult\n",
" \n",
" if entry_signal == 1: # Long\n",
" stop_loss = price - adjusted_atr_mult * atr\n",
" take_profit = price + adjusted_atr_mult * atr * 2.5\n",
" else: # Short\n",
" stop_loss = price + adjusted_atr_mult * atr\n",
" take_profit = price - adjusted_atr_mult * atr * 2.5\n",
" \n",
" # Position Sizing\n",
" account_info = mt.account_info()\n",
" if account_info:\n",
" balance = account_info.balance\n",
" risk_amount = balance * max_risk_per_trade\n",
" if symbol == \"XAUUSD\":\n",
" # 🎯 ADAPTIVE POSITION SIZING\n",
" if 'adv_position_mgr' in globals() and adv_position_mgr.adaptive_sizing:\n",
" volume = adv_position_mgr.adaptive_sizing.calculate_position_size(\n",
" confidence=confidence,\n",
" balance=balance,\n",
" stop_loss_distance=adjusted_atr_mult * atr * 10000, # Convert to pips\n",
" symbol=symbol\n",
" )\n",
" else:\n",
" volume = round(min(TRADING_CONFIG[\"lot_sizing\"][\"max_lot\"], max(TRADING_CONFIG[\"lot_sizing\"][\"min_lot\"], risk_amount / (adjusted_atr_mult * atr * 100))),2)\n",
" else:\n",
" volume = TRADING_CONFIG[\"lot_sizing\"][\"default_lot\"]\n",
" else:\n",
" volume = TRADING_CONFIG[\"lot_sizing\"][\"default_lot\"]\n",
" \n",
" # Apply Equity Curve lot multiplier\n",
" if lot_multiplier != 1.0:\n",
" original_volume = volume\n",
" volume = round(volume * lot_multiplier, 2)\n",
" volume = max(TRADING_CONFIG[\"lot_sizing\"][\"min_lot\"], volume) # Ensure minimum\n",
" print(f\"📈 Equity Curve: Lot adjusted {original_volume:.2f} → {volume:.2f} ({lot_multiplier:.0%})\")\n",
" \n",
" # Log Trade Info\n",
" print(f\"\\n🚀 V1.6 ADAPTIVE COMPLETE TRADE EXECUTION\")\n",
" print(f\"Direction: {'LONG' if entry_signal == 1 else 'SHORT'}\")\n",
" print(f\"Price: {price:.5f} | Volume: {volume:.2f}\")\n",
" print(f\"SL: {stop_loss:.5f} | TP: {take_profit:.5f}\")\n",
" print(f\"Confidence: {confidence}% | Quality: {signal_quality.upper()}\")\n",
" print(f\"Regime: {market_regime['regime'].upper()}\")\n",
" print(f\"Adaptive Interval: {signal_info['adaptive_interval']} min\")\n",
" print(f\"Session: {signal_info['session'].upper()}\")\n",
" \n",
" # Execute\n",
" try:\n",
" order_result = market_order(\n",
" symbol=symbol,\n",
" volume=volume,\n",
" order_type=\"buy\" if entry_signal == 1 else \"sell\",\n",
" stoploss=stop_loss,\n",
" take_profit=take_profit\n",
" )\n",
" \n",
" if order_result and order_result.retcode == mt.TRADE_RETCODE_DONE:\n",
" print(f\"✅ Trade erfolgreich! Ticket: {order_result.order}\")\n",
" \n",
" # ==========================================\n",
" # LOG TRADE ENTRY (V1.8)\n",
" # ==========================================\n",
" try:\n",
" # Hole Position Info\n",
" positions = mt.positions_get(symbol=symbol)\n",
" if positions and infra:\n",
" position = positions[0]\n",
"\n",
" # Erstelle Trade Data\n",
" trade_data = {\n",
" 'ticket': position.ticket,\n",
" 'position_id': position.identifier,\n",
" 'symbol': symbol,\n",
" 'strategy_name': strategy_name,\n",
" 'type': 'BUY' if entry_signal == 1 else 'SELL',\n",
" 'volume': volume,\n",
" 'entry_price': position.price_open,\n",
" 'sl_price': position.sl,\n",
" 'tp_price': position.tp,\n",
" 'entry_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),\n",
" 'session': rhythm_manager.get_current_session(),\n",
" 'regime': market_regime['regime'],\n",
" 'quality': signal_quality,\n",
" 'confidence': confidence if 'confidence' in locals() else None,\n",
" 'timeframe_alignment': signal_info.get('required_alignment', 2),\n",
" 'risk_amount': risk_amount if 'risk_amount' in locals() else None,\n",
" 'risk_pct': max_risk_per_trade\n",
" }\n",
"\n",
" # Log to Database + Send Telegram\n",
" infra.log_trade_entry(trade_data)\n",
" logger.info(\"📱 Trade logged to DB + Telegram notification sent\")\n",
"\n",
" except Exception as e:\n",
" logger.error(f\"⚠️ Infrastructure logging failed: {e}\")\n",
" # ==========================================\n",
"\n",
"\n",
" # Verify & Log\n",
" new_check, new_info = check_existing_positions(symbol, strategy_name)\n",
" print(f\"📊 Positionen: {new_info['count']}\")\n",
" log_trade_performance_adaptive(signal_info, order_result)\n",
" else:\n",
" print(f\"❌ Trade failed: {order_result.comment if order_result else 'No result'}\")\n",
" \n",
" return order_result\n",
" \n",
" except Exception as e:\n",
" print(f\"❌ Execution failed: {e}\")\n",
" return None\n",
" \n",
" else:\n",
" if debug:\n",
" print(f\"\\n⏸️ TRADE SKIPPED: {reason}\")\n",
" return None\n",
"\n",
"\n",
"print(\"✅ V1.6 Adaptive Complete Execute Trade defined\")"
] ]
}, },
{ {
@@ -3819,8 +3595,7 @@
" \n", " \n",
" ml_result = check_ml_signal(ml_signal_info)\n", " ml_result = check_ml_signal(ml_signal_info)\n",
" \n", " \n",
" print(f\"\n", " print(f\"🤖 ML Signal Check: {ml_result['action']}\")\n",
"🤖 ML Signal Check: {ml_result['action']}\")\n",
" print(f\" Win Probability: {ml_result['win_probability']:.1%}\")\n", " print(f\" Win Probability: {ml_result['win_probability']:.1%}\")\n",
" print(f\" Reason: {ml_result['reason']}\")\n", " print(f\" Reason: {ml_result['reason']}\")\n",
" \n", " \n",
@@ -4060,242 +3835,7 @@
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"# ==========================================\n", "# ==========================================\n# 📊 SYNC MT5 TRADES TO DEMO TRACKER\n# ==========================================\n# Führe diese Cell aus um geschlossene Trades zu importieren\n\nfrom datetime import datetime, timedelta\nfrom signal_cache import get_trade_signal\n\ndef sync_closed_trades_to_tracker(days_back=7):\n \"\"\"\n Synchronisiert geschlossene Trades aus MT5 History zum Demo Tracker\n \n WICHTIG: MT5 überschreibt den Kommentar bei SL/TP Exit!\n - Entry: \"TradingBot_V1.6\"\n - Exit: \"[sl 5229.75]\" oder \"[tp 5250.00]\"\n \n Daher: Finde Entry-Deals mit TradingBot, dann suche Exit via position_id\n \"\"\"\n print(\"🔄 Syncing closed trades from MT5...\")\n\n # Get trade history\n from_date = datetime.now() - timedelta(days=days_back)\n to_date = datetime.now()\n\n # Get ALL deals\n deals = mt.history_deals_get(from_date, to_date)\n\n if deals is None or len(deals) == 0:\n print(\" No deals found in history\")\n return 0\n\n # Step 1: Find ENTRY deals with TradingBot comment\n entry_deals = {}\n for deal in deals:\n if deal.entry == 0 and deal.comment and \"TradingBot\" in deal.comment:\n entry_deals[deal.position_id] = deal\n \n print(f\" Found {len(entry_deals)} TradingBot entry deals\")\n \n if not entry_deals:\n print(\" No TradingBot trades found\")\n return 0\n\n # Step 2: Find EXIT deals for those positions (any comment)\n exit_deals = {}\n for deal in deals:\n if deal.entry == 1 and deal.position_id in entry_deals:\n exit_deals[deal.position_id] = deal\n\n print(f\" Found {len(exit_deals)} matching exit deals\")\n\n synced = 0\n already_logged = [t['ticket'] for t in demo_tracker.data['trades']]\n\n for pos_id in entry_deals:\n # Skip if no exit yet (still open)\n if pos_id not in exit_deals:\n continue\n \n # Skip if already logged\n if pos_id in already_logged:\n continue\n\n entry_deal = entry_deals[pos_id]\n exit_deal = exit_deals[pos_id]\n\n # Determine direction\n direction = \"LONG\" if entry_deal.type == 0 else \"SHORT\" # 0=BUY, 1=SELL\n\n # Calculate profit (includes swap and commission)\n profit = exit_deal.profit + exit_deal.swap + exit_deal.commission\n\n # Determine session\n hour = datetime.fromtimestamp(entry_deal.time).hour\n if 0 <= hour < 8:\n session = \"asian\"\n elif 8 <= hour < 13:\n session = \"london\"\n elif 13 <= hour < 22:\n session = \"ny\"\n else:\n session = \"asian\"\n\n # Determine close reason from exit comment\n exit_comment = exit_deal.comment or \"\"\n if \"[sl\" in exit_comment.lower():\n close_reason = \"stop_loss\"\n elif \"[tp\" in exit_comment.lower():\n close_reason = \"take_profit\"\n else:\n close_reason = \"manual\"\n\n # Get cached signal data for ML training\n cached_signal = get_trade_signal(pos_id)\n \n # Log to tracker with ML features\n demo_tracker.log_trade(\n ticket=pos_id,\n symbol=entry_deal.symbol,\n direction=direction,\n entry_price=entry_deal.price,\n exit_price=exit_deal.price,\n volume=entry_deal.volume,\n profit=profit,\n entry_time=datetime.fromtimestamp(entry_deal.time),\n exit_time=datetime.fromtimestamp(exit_deal.time),\n session=session,\n base_confidence=cached_signal.get('base_confidence', 0) if cached_signal else 0,\n enhanced_score=cached_signal.get('enhanced_score', 0) if cached_signal else 0,\n hybrid_score=cached_signal.get('hybrid_score', 0) if cached_signal else 0,\n signal_quality=cached_signal.get('signal_quality', 'unknown') if cached_signal else 'unknown',\n close_reason=close_reason\n )\n synced += 1\n status = \"✅\" if profit >= 0 else \"❌\"\n print(f\" {status} Synced #{pos_id}: {direction} {entry_deal.symbol} | {close_reason} | P/L: ${profit:.2f}\")\n\n print(f\"\\n📊 Synced {synced} trades to Demo Tracker\")\n return synced\n\n# ==========================================\n# WRAPPER FÜR SCHEDULER (Silent Mode)\n# ==========================================\ndef scheduled_demo_tracker_sync():\n \"\"\"Silent sync für Scheduler - loggt nur wenn neue Trades gefunden\"\"\"\n try:\n from_date = datetime.now() - timedelta(days=1)\n deals = mt.history_deals_get(from_date, datetime.now())\n \n if deals is None or len(deals) == 0:\n return 0\n \n # Find entry deals with TradingBot\n entry_deals = {}\n for deal in deals:\n if deal.entry == 0 and deal.comment and Line truncated
"# 📊 SYNC MT5 TRADES TO DEMO TRACKER\n",
"# ==========================================\n",
"# Führe diese Cell aus um geschlossene Trades zu importieren\n",
"\n",
"from datetime import datetime, timedelta\n",
"\n",
"def sync_closed_trades_to_tracker(days_back=7):\n",
" \"\"\"\n",
" Synchronisiert geschlossene Trades aus MT5 History zum Demo Tracker\n",
" \n",
" WICHTIG: MT5 überschreibt den Kommentar bei SL/TP Exit!\n",
" - Entry: \"TradingBot_V1.6\"\n",
" - Exit: \"[sl 5229.75]\" oder \"[tp 5250.00]\"\n",
" \n",
" Daher: Finde Entry-Deals mit TradingBot, dann suche Exit via position_id\n",
" \"\"\"\n",
" print(\"🔄 Syncing closed trades from MT5...\")\n",
"\n",
" # Get trade history\n",
" from_date = datetime.now() - timedelta(days=days_back)\n",
" to_date = datetime.now()\n",
"\n",
" # Get ALL deals\n",
" deals = mt.history_deals_get(from_date, to_date)\n",
"\n",
" if deals is None or len(deals) == 0:\n",
" print(\" No deals found in history\")\n",
" return 0\n",
"\n",
" # Step 1: Find ENTRY deals with TradingBot comment\n",
" entry_deals = {}\n",
" for deal in deals:\n",
" if deal.entry == 0 and deal.comment and \"TradingBot\" in deal.comment:\n",
" entry_deals[deal.position_id] = deal\n",
" \n",
" print(f\" Found {len(entry_deals)} TradingBot entry deals\")\n",
" \n",
" if not entry_deals:\n",
" print(\" No TradingBot trades found\")\n",
" return 0\n",
"\n",
" # Step 2: Find EXIT deals for those positions (any comment)\n",
" exit_deals = {}\n",
" for deal in deals:\n",
" if deal.entry == 1 and deal.position_id in entry_deals:\n",
" exit_deals[deal.position_id] = deal\n",
"\n",
" print(f\" Found {len(exit_deals)} matching exit deals\")\n",
"\n",
" synced = 0\n",
" already_logged = [t['ticket'] for t in demo_tracker.data['trades']]\n",
"\n",
" for pos_id in entry_deals:\n",
" # Skip if no exit yet (still open)\n",
" if pos_id not in exit_deals:\n",
" continue\n",
" \n",
" # Skip if already logged\n",
" if pos_id in already_logged:\n",
" continue\n",
"\n",
" entry_deal = entry_deals[pos_id]\n",
" exit_deal = exit_deals[pos_id]\n",
"\n",
" # Determine direction\n",
" direction = \"LONG\" if entry_deal.type == 0 else \"SHORT\" # 0=BUY, 1=SELL\n",
"\n",
" # Calculate profit (includes swap and commission)\n",
" profit = exit_deal.profit + exit_deal.swap + exit_deal.commission\n",
"\n",
" # Determine session\n",
" hour = datetime.fromtimestamp(entry_deal.time).hour\n",
" if 0 <= hour < 8:\n",
" session = \"asian\"\n",
" elif 8 <= hour < 13:\n",
" session = \"london\"\n",
" elif 13 <= hour < 22:\n",
" session = \"ny\"\n",
" else:\n",
" session = \"asian\"\n",
"\n",
" # Determine close reason from exit comment\n",
" exit_comment = exit_deal.comment or \"\"\n",
" if \"[sl\" in exit_comment.lower():\n",
" close_reason = \"stop_loss\"\n",
" elif \"[tp\" in exit_comment.lower():\n",
" close_reason = \"take_profit\"\n",
" else:\n",
" close_reason = \"manual\"\n",
"\n",
" # Log to tracker\n",
" demo_tracker.log_trade(\n",
" ticket=pos_id,\n",
" symbol=entry_deal.symbol,\n",
" direction=direction,\n",
" entry_price=entry_deal.price,\n",
" exit_price=exit_deal.price,\n",
" volume=entry_deal.volume,\n",
" profit=profit,\n",
" entry_time=datetime.fromtimestamp(entry_deal.time),\n",
" exit_time=datetime.fromtimestamp(exit_deal.time),\n",
" session=session,\n",
" base_confidence=0,\n",
" enhanced_score=0,\n",
" hybrid_score=0,\n",
" signal_quality=\"unknown\",\n",
" close_reason=close_reason\n",
" )\n",
" synced += 1\n",
" status = \"✅\" if profit >= 0 else \"❌\"\n",
" print(f\" {status} Synced #{pos_id}: {direction} {entry_deal.symbol} | {close_reason} | P/L: ${profit:.2f}\")\n",
"\n",
" print(f\"\\n📊 Synced {synced} trades to Demo Tracker\")\n",
" return synced\n",
"\n",
"# ==========================================\n",
"# WRAPPER FÜR SCHEDULER (Silent Mode)\n",
"# ==========================================\n",
"def scheduled_demo_tracker_sync():\n",
" \"\"\"Silent sync für Scheduler - loggt nur wenn neue Trades gefunden\"\"\"\n",
" try:\n",
" from_date = datetime.now() - timedelta(days=1)\n",
" deals = mt.history_deals_get(from_date, datetime.now())\n",
" \n",
" if deals is None or len(deals) == 0:\n",
" return 0\n",
" \n",
" # Find entry deals with TradingBot\n",
" entry_deals = {}\n",
" for deal in deals:\n",
" if deal.entry == 0 and deal.comment and \"TradingBot\" in deal.comment:\n",
" entry_deals[deal.position_id] = deal\n",
" \n",
" if not entry_deals:\n",
" return 0\n",
" \n",
" # Find exit deals\n",
" exit_deals = {}\n",
" for deal in deals:\n",
" if deal.entry == 1 and deal.position_id in entry_deals:\n",
" exit_deals[deal.position_id] = deal\n",
" \n",
" synced = 0\n",
" already_logged = [t['ticket'] for t in demo_tracker.data['trades']]\n",
" \n",
" for pos_id in entry_deals:\n",
" if pos_id not in exit_deals:\n",
" continue\n",
" if pos_id in already_logged:\n",
" continue\n",
" \n",
" entry_deal = entry_deals[pos_id]\n",
" exit_deal = exit_deals[pos_id]\n",
" \n",
" direction = \"LONG\" if entry_deal.type == 0 else \"SHORT\"\n",
" profit = exit_deal.profit + exit_deal.swap + exit_deal.commission\n",
" \n",
" hour = datetime.fromtimestamp(entry_deal.time).hour\n",
" if 0 <= hour < 8:\n",
" session = \"asian\"\n",
" elif 8 <= hour < 13:\n",
" session = \"london\"\n",
" elif 13 <= hour < 22:\n",
" session = \"ny\"\n",
" else:\n",
" session = \"asian\"\n",
" \n",
" exit_comment = exit_deal.comment or \"\"\n",
" if \"[sl\" in exit_comment.lower():\n",
" close_reason = \"stop_loss\"\n",
" elif \"[tp\" in exit_comment.lower():\n",
" close_reason = \"take_profit\"\n",
" else:\n",
" close_reason = \"manual\"\n",
" \n",
" demo_tracker.log_trade(\n",
" ticket=pos_id,\n",
" symbol=entry_deal.symbol,\n",
" direction=direction,\n",
" entry_price=entry_deal.price,\n",
" exit_price=exit_deal.price,\n",
" volume=entry_deal.volume,\n",
" profit=profit,\n",
" entry_time=datetime.fromtimestamp(entry_deal.time),\n",
" exit_time=datetime.fromtimestamp(exit_deal.time),\n",
" session=session,\n",
" close_reason=close_reason\n",
" )\n",
" synced += 1\n",
" status = \"✅\" if profit >= 0 else \"❌\"\n",
" print(f\"📊 Auto-synced #{pos_id}: {direction} {entry_deal.symbol} | {close_reason} | P/L: ${profit:.2f}\")\n",
" \n",
" return synced\n",
" except Exception as e:\n",
" logger.debug(f\"Demo sync error: {e}\")\n",
" return 0\n",
"\n",
"# Run initial sync\n",
"synced_count = sync_closed_trades_to_tracker(days_back=30)\n",
"\n",
"# Show updated stats\n",
"print(\"\\n\" + \"=\" * 50)\n",
"stats = demo_tracker.get_stats()\n",
"print(f\"📊 Total Trades in Tracker: {stats.get('total_trades', 0)}\")\n",
"print(f\"📈 Win Rate: {stats.get('win_rate', 0)*100:.1f}%\")\n",
"print(f\"💰 Total Profit: ${stats.get('total_profit', 0):.2f}\")\n",
"\n",
"# ==========================================\n",
"# ADD AUTO-SYNC TO SCHEDULER\n",
"# ==========================================\n",
"from apscheduler.triggers.interval import IntervalTrigger\n",
"\n",
"print(\"\\n\" + \"=\" * 50)\n",
"print(\"🔄 Adding Demo Tracker sync to scheduler...\")\n",
"\n",
"# Remove old job if exists\n",
"try:\n",
" scheduler.remove_job('demo_tracker_sync')\n",
" print(\" Removed old Demo Tracker sync job\")\n",
"except:\n",
" pass\n",
"\n",
"# Add sync job - every 5 minutes\n",
"scheduler.add_job(\n",
" scheduled_demo_tracker_sync,\n",
" trigger=IntervalTrigger(minutes=5),\n",
" id='demo_tracker_sync',\n",
" name='Demo Tracker Sync',\n",
" replace_existing=True,\n",
" max_instances=1\n",
")\n",
"\n",
"print(\"✅ Demo Tracker auto-sync scheduled (every 5 minutes)\")\n",
"print(\" Automatically logs closed trades to demo_test_stats.json\")\n",
"print(\"=\" * 50)"
] ]
}, },
{ {
@@ -4639,7 +4179,89 @@
"execution_count": null, "execution_count": null,
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [] "source": [
"# Schnelle Signal-Analyse\n",
"signal_info = extended_top_down_v2_adaptive(\"XAUUSD\")\n",
"print(f\"Signal: {signal_info['entry_signal']}\")\n",
"print(f\"Confidence: {signal_info['confidence']:.1f}%\")\n",
"print(f\"Threshold: {signal_info['adaptive_threshold']:.1f}%\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Detaillierter Equity Curve Status\n",
"print(\"=\" * 60)\n",
"print(\"EQUITY CURVE DETAILED STATUS\")\n",
"print(\"=\" * 60)\n",
"\n",
"# Verwende get_report() - das funktioniert sicher\n",
"print(equity_curve_manager.get_report())\n",
"\n",
"# Status Dict\n",
"status = equity_curve_manager.get_status()\n",
"\n",
"print(\"\\n📊 Equity History (letzte 10 Einträge):\")\n",
"history = equity_curve_manager.equity_history[-10:]\n",
"for i, entry in enumerate(history):\n",
" eq = entry.get('equity', 0) or 0\n",
" ts = str(entry.get('timestamp', ''))[:19]\n",
" print(f\" {i+1}. ${eq:,.2f} ({ts})\")\n",
"\n",
"# Mit None-Handling\n",
"eq = status.get('current_equity') or 0\n",
"ma = status.get('ma_equity') or 0\n",
"diff_pct = status.get('equity_vs_ma_pct') or 0\n",
"\n",
"print(f\"\\n📈 Detaillierter Status:\")\n",
"print(f\" Aktuelle Equity: ${eq:,.2f}\")\n",
"print(f\" MA ({status.get('ma_period', 10)} Trades): ${ma:,.2f}\")\n",
"print(f\" Differenz: {diff_pct:+.1f}%\")\n",
"print(f\" Über MA: {status.get('equity_above_ma', False)}\")\n",
"print(f\" Trading Status: {status.get('status', 'unknown')}\")\n",
"\n",
"# Recovery Info\n",
"if ma > 0 and eq < ma:\n",
" needed = ma - eq\n",
" print(f\"\\n🎯 Recovery benötigt: ${needed:,.2f} Profit\")\n",
" print(f\" Aktueller Lot Multiplier: 50%\")\n",
"elif ma > 0:\n",
" print(f\"\\n✅ NORMAL MODE - Volle Lot Size\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Debug: Session-Erkennung prüfen\n",
"import pytz\n",
"from datetime import datetime\n",
"\n",
"now_utc = datetime.now(pytz.UTC)\n",
"now_cet = datetime.now()\n",
"\n",
"print(f\"System Zeit: {now_cet.strftime('%H:%M:%S')}\")\n",
"print(f\"UTC Zeit: {now_utc.strftime('%H:%M:%S')}\")\n",
"print(f\"Erkannte Session: {rhythm_manager.get_current_session()}\")\n",
"\n",
"# Manueller Check\n",
"utc_hour = now_utc.hour\n",
"print(f\"\\nUTC Stunde: {utc_hour}\")\n",
"print(f\"Erwartete Session:\")\n",
"if 13 <= utc_hour < 16:\n",
" print(\" → OVERLAP (13:00-16:00 UTC)\")\n",
"elif 8 <= utc_hour < 13:\n",
" print(\" → LONDON (08:00-13:00 UTC, vor Overlap)\")\n",
"elif 16 <= utc_hour < 21:\n",
" print(\" → NY (16:00-21:00 UTC)\")\n",
"else:\n",
" print(\" → ASIAN\")"
]
}, },
{ {
"cell_type": "code", "cell_type": "code",
+288
View File
@@ -0,0 +1,288 @@
#!/usr/bin/env python3
"""
SIGNAL CACHE - Speichert Signal-Daten für ML Training
Wenn ein Trade geöffnet wird, werden die Signal-Informationen hier gespeichert.
Wenn der Trade geschlossen wird, können die Daten abgerufen und zum demo_tracker geloggt werden.
VERWENDUNG:
from signal_cache import signal_cache
# Beim Trade-Open:
signal_cache.store(ticket_id, signal_info, enhanced_score, hybrid_score)
# Beim Trade-Close:
cached = signal_cache.get(ticket_id)
if cached:
demo_tracker.log_trade(...,
base_confidence=cached['base_confidence'],
enhanced_score=cached['enhanced_score'],
hybrid_score=cached['hybrid_score'],
signal_quality=cached['signal_quality']
)
"""
import json
import os
import logging
from datetime import datetime
from typing import Dict, Optional, Any
logger = logging.getLogger(__name__)
# ==========================================
# CONFIGURATION
# ==========================================
CACHE_FILE = "signal_cache.json"
MAX_CACHE_AGE_HOURS = 48 # Alte Einträge nach 48h löschen
# ==========================================
# SIGNAL CACHE CLASS
# ==========================================
class SignalCache:
"""
Cache für Trade-Signal-Daten
Persistiert auf Disk für Restart-Sicherheit
"""
def __init__(self, cache_file: str = CACHE_FILE):
self.cache_file = cache_file
self.cache: Dict[str, Dict] = {}
self._load_cache()
logger.info(f"SignalCache initialized with {len(self.cache)} entries")
def _load_cache(self):
"""Lädt Cache von Disk"""
if os.path.exists(self.cache_file):
try:
with open(self.cache_file, 'r') as f:
self.cache = json.load(f)
self._cleanup_old_entries()
except Exception as e:
logger.warning(f"Could not load signal cache: {e}")
self.cache = {}
def _save_cache(self):
"""Speichert Cache auf Disk"""
try:
with open(self.cache_file, 'w') as f:
json.dump(self.cache, f, indent=2, default=str)
except Exception as e:
logger.error(f"Could not save signal cache: {e}")
def _cleanup_old_entries(self):
"""Entfernt Einträge älter als MAX_CACHE_AGE_HOURS"""
now = datetime.now()
to_remove = []
for ticket, data in self.cache.items():
try:
timestamp = datetime.fromisoformat(data.get('timestamp', ''))
age_hours = (now - timestamp).total_seconds() / 3600
if age_hours > MAX_CACHE_AGE_HOURS:
to_remove.append(ticket)
except:
to_remove.append(ticket)
for ticket in to_remove:
del self.cache[ticket]
if to_remove:
logger.info(f"Cleaned up {len(to_remove)} old cache entries")
self._save_cache()
def store(self,
ticket: int,
signal_info: Dict[str, Any],
enhanced_score: float = 0.0,
hybrid_score: float = 0.0,
lot_multiplier: float = 1.0) -> bool:
"""
Speichert Signal-Daten für einen Trade
Args:
ticket: MT5 Order Ticket ID
signal_info: Dictionary mit Signal-Analyse Daten
enhanced_score: Enhanced Signal Score (0-100)
hybrid_score: Hybrid Score (0-100)
lot_multiplier: Equity Curve Lot Multiplier
Returns:
True wenn erfolgreich gespeichert
"""
try:
# Extrahiere relevante Daten aus signal_info
cache_entry = {
'ticket': ticket,
'timestamp': datetime.now().isoformat(),
# Signal Analysis
'base_confidence': signal_info.get('confidence', 0),
'adaptive_threshold': signal_info.get('adaptive_threshold', 0),
'signal_quality': signal_info.get('signal_quality', 'unknown'),
'entry_signal': signal_info.get('entry_signal', 0),
# Market Regime
'market_regime': signal_info.get('market_regime', 'unknown'),
'regime_strength': signal_info.get('regime_strength', 0),
'risk_adjusted_strength': signal_info.get('risk_adjusted_strength', 0),
# Enhanced Scoring
'enhanced_score': enhanced_score,
'hybrid_score': hybrid_score,
# Equity Curve
'lot_multiplier': lot_multiplier,
# Session
'session': signal_info.get('session', 'unknown'),
}
self.cache[str(ticket)] = cache_entry
self._save_cache()
logger.info(f"Cached signal for ticket {ticket}: conf={cache_entry['base_confidence']:.1f}%, quality={cache_entry['signal_quality']}")
return True
except Exception as e:
logger.error(f"Failed to cache signal for ticket {ticket}: {e}")
return False
def get(self, ticket: int) -> Optional[Dict]:
"""
Holt gecachte Signal-Daten für einen Trade
Args:
ticket: MT5 Order Ticket ID
Returns:
Dict mit Signal-Daten oder None
"""
return self.cache.get(str(ticket))
def remove(self, ticket: int) -> bool:
"""
Entfernt einen Eintrag nach Verwendung
Args:
ticket: MT5 Order Ticket ID
Returns:
True wenn erfolgreich entfernt
"""
ticket_str = str(ticket)
if ticket_str in self.cache:
del self.cache[ticket_str]
self._save_cache()
return True
return False
def get_and_remove(self, ticket: int) -> Optional[Dict]:
"""
Holt und entfernt Signal-Daten (für Trade-Close)
Args:
ticket: MT5 Order Ticket ID
Returns:
Dict mit Signal-Daten oder None
"""
data = self.get(ticket)
if data:
self.remove(ticket)
return data
def get_stats(self) -> Dict:
"""Gibt Cache-Statistiken zurück"""
return {
'total_entries': len(self.cache),
'cache_file': self.cache_file,
'oldest_entry': min(
(datetime.fromisoformat(d.get('timestamp', datetime.now().isoformat()))
for d in self.cache.values()),
default=None
),
'tickets': list(self.cache.keys())
}
def print_status(self):
"""Zeigt Cache-Status an"""
stats = self.get_stats()
print("\n" + "=" * 50)
print("SIGNAL CACHE STATUS")
print("=" * 50)
print(f" Cached Trades: {stats['total_entries']}")
print(f" Cache File: {stats['cache_file']}")
if stats['tickets']:
print(f" Tickets: {', '.join(stats['tickets'][:5])}")
if len(stats['tickets']) > 5:
print(f" ... and {len(stats['tickets']) - 5} more")
print("=" * 50)
# ==========================================
# GLOBAL INSTANCE
# ==========================================
signal_cache = SignalCache()
# ==========================================
# HELPER FUNCTIONS
# ==========================================
def store_trade_signal(ticket: int,
signal_info: Dict,
enhanced_score: float = 0.0,
hybrid_score: float = 0.0,
lot_multiplier: float = 1.0) -> bool:
"""Convenience function to store signal data"""
return signal_cache.store(ticket, signal_info, enhanced_score, hybrid_score, lot_multiplier)
def get_trade_signal(ticket: int) -> Optional[Dict]:
"""Convenience function to get and remove signal data"""
return signal_cache.get_and_remove(ticket)
def get_cache_status() -> str:
"""Returns formatted cache status"""
stats = signal_cache.get_stats()
return f"SignalCache: {stats['total_entries']} entries cached"
# ==========================================
# STANDALONE TEST
# ==========================================
if __name__ == "__main__":
print("Testing Signal Cache...")
# Test data
test_signal = {
'confidence': 85.5,
'adaptive_threshold': 70,
'signal_quality': 'excellent',
'entry_signal': 1,
'market_regime': 'trending',
'regime_strength': 65.0,
'risk_adjusted_strength': 12500.0,
'session': 'asian'
}
# Store
signal_cache.store(12345, test_signal, enhanced_score=78.5, hybrid_score=82.0)
# Retrieve
cached = signal_cache.get(12345)
print(f"\nCached data: {json.dumps(cached, indent=2)}")
# Status
signal_cache.print_status()
# Cleanup
signal_cache.remove(12345)
print("\nTest completed!")