From d25727839a0eef917d0759285c142c1b95ae7aff Mon Sep 17 00:00:00 2001 From: cbazza Date: Mon, 16 Feb 2026 12:50:55 +0100 Subject: [PATCH] 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 --- ...Bot_V1.6_Adaptive_Complete_CORRECTED.ipynb | 550 +++--------------- signal_cache.py | 288 +++++++++ 2 files changed, 374 insertions(+), 464 deletions(-) create mode 100644 signal_cache.py diff --git a/TradingBot_V1.6_Adaptive_Complete_CORRECTED.ipynb b/TradingBot_V1.6_Adaptive_Complete_CORRECTED.ipynb index d6a66a2..799b665 100644 --- a/TradingBot_V1.6_Adaptive_Complete_CORRECTED.ipynb +++ b/TradingBot_V1.6_Adaptive_Complete_CORRECTED.ipynb @@ -1217,231 +1217,7 @@ "metadata": {}, "outputs": [], "source": [ - "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", - " 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\")" + "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 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 # SIGNAL CACHE FOR ML TRAINING (V1.9)\n # ==========================================\n try:\n from signal_cache import store_trade_signal\n store_trade_signal(\n ticket=order_result.order,\n signal_info=signal_info,\n enhanced_score=confidence_override if confidence_override else confidence,\n hybrid_score=confidence,\n lot_multiplier=lot_multiplier\n )\n logger.info(f\"📊 Signal cached for ML: Ticket {order_result.order}\")\n except Exception as cache_err:\n logger.warning(f\"Signal cache failed: {cache_err}\")\n # ==========================================\n\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\nprint(\"✅ V1.6 Adaptive Complete Execute Trade defined\")" ] }, { @@ -3819,8 +3595,7 @@ " \n", " ml_result = check_ml_signal(ml_signal_info)\n", " \n", - " print(f\"\n", - "🤖 ML Signal Check: {ml_result['action']}\")\n", + " print(f\"🤖 ML Signal Check: {ml_result['action']}\")\n", " print(f\" Win Probability: {ml_result['win_probability']:.1%}\")\n", " print(f\" Reason: {ml_result['reason']}\")\n", " \n", @@ -4060,242 +3835,7 @@ "metadata": {}, "outputs": [], "source": [ - "# ==========================================\n", - "# 📊 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)" + "# ==========================================\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 \"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 # Get cached signal data for ML training\n cached_signal = get_trade_signal(pos_id)\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 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\"📊 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\nsynced_count = sync_closed_trades_to_tracker(days_back=30)\n\n# Show updated stats\nprint(\"\\n\" + \"=\" * 50)\nstats = demo_tracker.get_stats()\nprint(f\"📊 Total Trades in Tracker: {stats.get('total_trades', 0)}\")\nprint(f\"📈 Win Rate: {stats.get('win_rate', 0)*100:.1f}%\")\nprint(f\"💰 Total Profit: ${stats.get('total_profit', 0):.2f}\")\n\n# ==========================================\n# ADD AUTO-SYNC TO SCHEDULER\n# ==========================================\nfrom apscheduler.triggers.interval import IntervalTrigger\n\nprint(\"\\n\" + \"=\" * 50)\nprint(\"🔄 Adding Demo Tracker sync to scheduler...\")\n\n# Remove old job if exists\ntry:\n scheduler.remove_job('demo_tracker_sync')\n print(\" Removed old Demo Tracker sync job\")\nexcept:\n pass\n\n# Add sync job - every 5 minutes\nscheduler.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\nprint(\"✅ Demo Tracker auto-sync scheduled (every 5 minutes)\")\nprint(\" Automatically logs closed trades to demo_test_stats.json\")\nprint(\"=\" * 50)" ] }, { @@ -4639,7 +4179,89 @@ "execution_count": null, "metadata": {}, "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", diff --git a/signal_cache.py b/signal_cache.py new file mode 100644 index 0000000..c624797 --- /dev/null +++ b/signal_cache.py @@ -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!")