feat: Add session filter to trading check + fix drawdown calculation
Deploy to Windows VPS / deploy (push) Has been cancelled
Deploy to Windows VPS / deploy (push) Has been cancelled
- Add session check (SCHRITT 0) to enhanced_trading_check_wrapper - Fix max_drawdown calculation to cap at 100% when equity goes negative - Add _save_data() after _update_stats() to persist stats - Add auto-sync scheduler job for demo tracker (every 5 min) - Fix MT5 trade sync to match entry deals by position_id - Enable Asian session in session_filter_patch.py Session config now: - Asian: ENABLED - London: BLOCKED - Overlap: ENABLED - NY: ENABLED Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -47,15 +47,7 @@
|
|||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": null,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [],
|
||||||
{
|
|
||||||
"name": "stdout",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"📦 Installing python-telegram-bot...\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
"source": [
|
||||||
"# ==========================================\n",
|
"# ==========================================\n",
|
||||||
"# INSTALL TELEGRAM DEPENDENCIES (Run FIRST!)\n",
|
"# INSTALL TELEGRAM DEPENDENCIES (Run FIRST!)\n",
|
||||||
@@ -3678,6 +3670,18 @@
|
|||||||
" \"\"\"\n",
|
" \"\"\"\n",
|
||||||
"\n",
|
"\n",
|
||||||
" try:\n",
|
" try:\n",
|
||||||
|
" # ⭐ SCHRITT 0: SESSION CHECK (NEU!)\n",
|
||||||
|
" from session_filter_patch import is_session_allowed\n",
|
||||||
|
" current_session = rhythm_manager.get_current_session()\n",
|
||||||
|
" session_allowed, session_reason = is_session_allowed(current_session)\n",
|
||||||
|
" \n",
|
||||||
|
" if not session_allowed:\n",
|
||||||
|
" print(f\"⛔ SESSION BLOCKED: {current_session.upper()}\")\n",
|
||||||
|
" print(f\" Reason: {session_reason}\")\n",
|
||||||
|
" return None\n",
|
||||||
|
" \n",
|
||||||
|
" print(f\"✅ Session OK: {current_session.upper()}\")\n",
|
||||||
|
"\n",
|
||||||
" # SCHRITT 1: Position Check (wie vorher)\n",
|
" # SCHRITT 1: Position Check (wie vorher)\n",
|
||||||
" max_positions = TRADING_CONFIG['risk']['max_positions']\n",
|
" max_positions = TRADING_CONFIG['risk']['max_positions']\n",
|
||||||
" has_position, position_info = check_existing_positions(symbol)\n",
|
" has_position, position_info = check_existing_positions(symbol)\n",
|
||||||
@@ -3749,40 +3753,47 @@
|
|||||||
"\n",
|
"\n",
|
||||||
" except Exception as e:\n",
|
" except Exception as e:\n",
|
||||||
" print(f\"⚠️ Enhanced scoring failed: {e}\")\n",
|
" print(f\"⚠️ Enhanced scoring failed: {e}\")\n",
|
||||||
" print(\" Falling back to base confidence\")\n",
|
" print(f\" Using base confidence only: {base_confidence:.1f}%\")\n",
|
||||||
" final_confidence = base_confidence\n",
|
" final_confidence = base_confidence\n",
|
||||||
|
" enhanced_score = base_confidence\n",
|
||||||
|
"\n",
|
||||||
|
" # SCHRITT 4: Signal Qualification Check\n",
|
||||||
|
" if entry_signal not in [1, -1]:\n",
|
||||||
|
" print(f\"\\n❌ No clear signal: {entry_signal}\")\n",
|
||||||
|
" return None\n",
|
||||||
|
"\n",
|
||||||
|
" if final_confidence < adaptive_threshold:\n",
|
||||||
|
" print(f\"\\n❌ Confidence too low: {final_confidence:.1f}% < {adaptive_threshold:.1f}%\")\n",
|
||||||
|
" return None\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # SCHRITT 4: Threshold Check\n",
|
|
||||||
" if entry_signal in [1, -1]: # 1=LONG, -1=SHORT\n",
|
|
||||||
" if final_confidence >= adaptive_threshold:\n",
|
|
||||||
" print(f\"\\n🎯 Signal qualified! {final_confidence:.1f}% >= {adaptive_threshold:.1f}%\")\n",
|
" print(f\"\\n🎯 Signal qualified! {final_confidence:.1f}% >= {adaptive_threshold:.1f}%\")\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # Execute trade with ENHANCED confidence\n",
|
" # SCHRITT 5: Execute Trade with Enhanced Confidence\n",
|
||||||
" # Execute trade with pre-calculated signal_info and enhanced confidence\n",
|
" direction = \"BUY\" if entry_signal == 1 else \"SELL\"\n",
|
||||||
|
"\n",
|
||||||
|
" # Apply Equity Curve lot multiplier\n",
|
||||||
|
" adjusted_lot = TRADING_CONFIG['lot_sizing']['default_lot'] * lot_multiplier\n",
|
||||||
|
" adjusted_lot = max(adjusted_lot, TRADING_CONFIG['lot_sizing']['min_lot'])\n",
|
||||||
|
" adjusted_lot = min(adjusted_lot, TRADING_CONFIG['lot_sizing']['max_lot'])\n",
|
||||||
|
"\n",
|
||||||
|
" if lot_multiplier < 1.0:\n",
|
||||||
|
" print(f\"📉 Equity Curve: Lot adjusted {TRADING_CONFIG['lot_sizing']['default_lot']} → {adjusted_lot:.2f} ({lot_multiplier:.0%})\")\n",
|
||||||
|
"\n",
|
||||||
" result = execute_trade_v2_adaptive(\n",
|
" result = execute_trade_v2_adaptive(\n",
|
||||||
" symbol=symbol,\n",
|
" symbol=symbol,\n",
|
||||||
|
" strategy_name=strategy_name,\n",
|
||||||
" signal_info_override=signal_info,\n",
|
" signal_info_override=signal_info,\n",
|
||||||
" confidence_override=final_confidence, # ← Use hybrid score!\n",
|
" confidence_override=final_confidence,\n",
|
||||||
" lot_multiplier=lot_multiplier # ← Equity Curve adjustment\n",
|
" lot_multiplier=lot_multiplier\n",
|
||||||
" )\n",
|
" )\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # Update Equity Curve nach Trade\n",
|
" if result and result.retcode == 10009:\n",
|
||||||
" if result is not None:\n",
|
" print(f\"\\n✅ Trade executed with enhanced confidence: {final_confidence:.1f}%\")\n",
|
||||||
|
"\n",
|
||||||
|
" # Update equity curve after trade\n",
|
||||||
" equity_curve_manager.update_equity()\n",
|
" equity_curve_manager.update_equity()\n",
|
||||||
" print(f\"📈 Equity Curve updated\")\n",
|
|
||||||
"\n",
|
"\n",
|
||||||
" return result\n",
|
" return result\n",
|
||||||
" else:\n",
|
|
||||||
" print(f\"\\n❌ Signal below threshold: {final_confidence:.1f}% < {adaptive_threshold:.1f}%\")\n",
|
|
||||||
" print(f\" Base would have been: {base_confidence:.1f}%\")\n",
|
|
||||||
"\n",
|
|
||||||
" if final_confidence < base_confidence:\n",
|
|
||||||
" print(f\" ⚠️ Enhanced scoring filtered out weak setup!\")\n",
|
|
||||||
"\n",
|
|
||||||
" return None\n",
|
|
||||||
" else:\n",
|
|
||||||
" print(f\"\\n⏸️ No clear signal: {entry_signal}\")\n",
|
|
||||||
" return None\n",
|
|
||||||
"\n",
|
"\n",
|
||||||
" except Exception as e:\n",
|
" except Exception as e:\n",
|
||||||
" print(f\"❌ Enhanced trading check error: {e}\")\n",
|
" print(f\"❌ Enhanced trading check error: {e}\")\n",
|
||||||
@@ -3790,8 +3801,41 @@
|
|||||||
" traceback.print_exc()\n",
|
" traceback.print_exc()\n",
|
||||||
" return None\n",
|
" return None\n",
|
||||||
"\n",
|
"\n",
|
||||||
"print(\"✅ Enhanced trading check wrapper created!\")\n",
|
"print(\"✅ Enhanced Trading Check Wrapper definiert (MIT SESSION FILTER)\")\n",
|
||||||
"print(\" This will use multi-factor analysis for all trades\")\n"
|
"print(\" Session-Check ist jetzt AKTIV!\")\n",
|
||||||
|
"\n",
|
||||||
|
"# ==========================================\n",
|
||||||
|
"# UPDATE SCHEDULER\n",
|
||||||
|
"# ==========================================\n",
|
||||||
|
"\n",
|
||||||
|
"print(\"\\n🔄 Updating scheduler with enhanced trading check...\")\n",
|
||||||
|
"\n",
|
||||||
|
"# Remove old job if exists\n",
|
||||||
|
"try:\n",
|
||||||
|
" scheduler.remove_job('adaptive_trading_check')\n",
|
||||||
|
" print(\" Removed old trading check job\")\n",
|
||||||
|
"except:\n",
|
||||||
|
" pass\n",
|
||||||
|
"\n",
|
||||||
|
"# Add enhanced version\n",
|
||||||
|
"scheduler.add_job(\n",
|
||||||
|
" func=lambda: enhanced_trading_check_wrapper(\"XAUUSD\", debug=True),\n",
|
||||||
|
" trigger='interval',\n",
|
||||||
|
" minutes=1,\n",
|
||||||
|
" id='adaptive_trading_check',\n",
|
||||||
|
" name='Enhanced Adaptive Trading Check',\n",
|
||||||
|
" replace_existing=True,\n",
|
||||||
|
" max_instances=1\n",
|
||||||
|
")\n",
|
||||||
|
"\n",
|
||||||
|
"print(\"✅ Scheduler updated!\")\n",
|
||||||
|
"print(\" Trading check runs every 1 minute\")\n",
|
||||||
|
"print(\" Session filter is now ACTIVE!\")\n",
|
||||||
|
"\n",
|
||||||
|
"# Show current jobs\n",
|
||||||
|
"print(\"\\n📋 Active Scheduler Jobs:\")\n",
|
||||||
|
"for job in scheduler.get_jobs():\n",
|
||||||
|
" print(f\" • {job.id}: {job.trigger}\")"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -3846,12 +3890,108 @@
|
|||||||
"print(\"=\" * 70)\n"
|
"print(\"=\" * 70)\n"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Demo Test Tracker & Reports"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": 74,
|
||||||
"id": "a5c25689",
|
"id": "a5c25689",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"\n",
|
||||||
|
"======================================================================\n",
|
||||||
|
"📊 DEMO TEST TRACKER\n",
|
||||||
|
"======================================================================\n",
|
||||||
|
"\n",
|
||||||
|
"======================================================================\n",
|
||||||
|
"📊 DEMO TEST TRACKER - PERFORMANCE REPORT\n",
|
||||||
|
"======================================================================\n",
|
||||||
|
"\n",
|
||||||
|
"📅 Demo Period:\n",
|
||||||
|
" Start: 2026-01-28\n",
|
||||||
|
" Days Running: 1\n",
|
||||||
|
" Last Trade: 2026-01-29\n",
|
||||||
|
"\n",
|
||||||
|
"📈 PERFORMANCE METRICS:\n",
|
||||||
|
" Total Trades: 348\n",
|
||||||
|
" Wins/Losses: 177/171\n",
|
||||||
|
" Win Rate: 50.9%\n",
|
||||||
|
" Profit Factor: 1.42\n",
|
||||||
|
"\n",
|
||||||
|
"💰 PROFIT/LOSS:\n",
|
||||||
|
" Total Profit: $4,598.21\n",
|
||||||
|
" Avg Win: $88.50\n",
|
||||||
|
" Avg Loss: $64.72\n",
|
||||||
|
" Avg Trade: $13.21\n",
|
||||||
|
" Best Trade: $915.60\n",
|
||||||
|
" Worst Trade: $-345.70\n",
|
||||||
|
"\n",
|
||||||
|
"📉 RISK METRICS:\n",
|
||||||
|
" Max Drawdown: 100.0% ($1,972.65)\n",
|
||||||
|
" Current Equity: $4,598.21\n",
|
||||||
|
" Peak Equity: $4,914.21\n",
|
||||||
|
"\n",
|
||||||
|
"⏱️ TIMING:\n",
|
||||||
|
" Avg Duration: 49 min\n",
|
||||||
|
"\n",
|
||||||
|
"🌍 SESSION BREAKDOWN:\n",
|
||||||
|
" ASIAN | Trades: 155 | Win Rate: 54.2% | Profit: $3,270.77\n",
|
||||||
|
" LONDON | Trades: 81 | Win Rate: 46.9% | Profit: $654.12\n",
|
||||||
|
" NY | Trades: 112 | Win Rate: 49.1% | Profit: $673.32\n",
|
||||||
|
"\n",
|
||||||
|
"🎯 SIGNAL QUALITY BREAKDOWN:\n",
|
||||||
|
" UNKNOWN | Trades: 348 | Win Rate: 50.9% | Profit: $4,598.21\n",
|
||||||
|
"\n",
|
||||||
|
"======================================================================\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"======================================================================\n",
|
||||||
|
"🚦 GO-LIVE READINESS CHECK\n",
|
||||||
|
"======================================================================\n",
|
||||||
|
" ✅ Trades: 348/50\n",
|
||||||
|
" ⬜ Win Rate: 50.9% (min 55%)\n",
|
||||||
|
" ✅ Profit Factor: 1.42 (min 1.3)\n",
|
||||||
|
" ⬜ Max Drawdown: 100.0% (max 15%)\n",
|
||||||
|
" ⬜ Days Running: 1/14\n",
|
||||||
|
" ✅ Errors: 0 (max 5)\n",
|
||||||
|
" ✅ Sessions Tested: 3/2\n",
|
||||||
|
"----------------------------------------------------------------------\n",
|
||||||
|
" Passed: 4/7\n",
|
||||||
|
"\n",
|
||||||
|
" ⏳ STATUS: NOT READY YET\n",
|
||||||
|
" Still needed:\n",
|
||||||
|
" • Win Rate: 50.9% (min 55%)\n",
|
||||||
|
" • Max Drawdown: 100.0% (max 15%)\n",
|
||||||
|
" • Days Running: 1/14\n",
|
||||||
|
"======================================================================\n",
|
||||||
|
"\n",
|
||||||
|
"📊 Daily Summary - 2026-01-29\n",
|
||||||
|
"━━━━━━━━━━━━━━━━━━━━━━━━━━━\n",
|
||||||
|
"Today: 16 trades | 9 wins | WR: 56% | P/L: $+1111.80\n",
|
||||||
|
"Overall: 348 trades | WR: 50.9% | Total: $+4598.21\n",
|
||||||
|
"━━━━━━━━━━━━━━━━━━━━━━━━━━━\n",
|
||||||
|
"\n",
|
||||||
|
"⏳ Weiter testen... Der Bot sammelt noch Daten.\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stderr",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"2026-01-29 10:46:29,000 - INFO - HTTP Request: POST https://api.telegram.org/bot7783303065:AAHVVvwWGqmhJ2BVq8LqkLRSsicKy1CUsD8/getUpdates \"HTTP/1.1 200 OK\"\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"# ==========================================\n",
|
"# ==========================================\n",
|
||||||
"# 📊 DEMO TEST TRACKER - REPORTS & GO-LIVE CHECK\n",
|
"# 📊 DEMO TEST TRACKER - REPORTS & GO-LIVE CHECK\n",
|
||||||
@@ -3896,6 +4036,12 @@
|
|||||||
"def sync_closed_trades_to_tracker(days_back=7):\n",
|
"def sync_closed_trades_to_tracker(days_back=7):\n",
|
||||||
" \"\"\"\n",
|
" \"\"\"\n",
|
||||||
" Synchronisiert geschlossene Trades aus MT5 History zum Demo Tracker\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",
|
" \"\"\"\n",
|
||||||
" print(\"🔄 Syncing closed trades from MT5...\")\n",
|
" print(\"🔄 Syncing closed trades from MT5...\")\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -3903,55 +4049,55 @@
|
|||||||
" from_date = datetime.now() - timedelta(days=days_back)\n",
|
" from_date = datetime.now() - timedelta(days=days_back)\n",
|
||||||
" to_date = datetime.now()\n",
|
" to_date = datetime.now()\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # Get deals (closed trades)\n",
|
" # Get ALL deals\n",
|
||||||
" deals = mt.history_deals_get(from_date, to_date)\n",
|
" deals = mt.history_deals_get(from_date, to_date)\n",
|
||||||
"\n",
|
"\n",
|
||||||
" if deals is None or len(deals) == 0:\n",
|
" if deals is None or len(deals) == 0:\n",
|
||||||
" print(\" No deals found in history\")\n",
|
" print(\" No deals found in history\")\n",
|
||||||
" return 0\n",
|
" return 0\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # Filter for our strategy\n",
|
" # Step 1: Find ENTRY deals with TradingBot comment\n",
|
||||||
" our_deals = [d for d in deals if d.comment and \"TradingBot\" in d.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",
|
" \n",
|
||||||
" # Group by position (entry + exit)\n",
|
" print(f\" Found {len(entry_deals)} TradingBot entry deals\")\n",
|
||||||
" positions = {}\n",
|
" \n",
|
||||||
" for deal in our_deals:\n",
|
" if not entry_deals:\n",
|
||||||
" pos_id = deal.position_id\n",
|
" print(\" No TradingBot trades found\")\n",
|
||||||
" if pos_id not in positions:\n",
|
" return 0\n",
|
||||||
" positions[pos_id] = []\n",
|
"\n",
|
||||||
" positions[pos_id].append(deal)\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",
|
"\n",
|
||||||
" synced = 0\n",
|
" synced = 0\n",
|
||||||
" already_logged = [t['ticket'] for t in demo_tracker.data['trades']]\n",
|
" already_logged = [t['ticket'] for t in demo_tracker.data['trades']]\n",
|
||||||
"\n",
|
"\n",
|
||||||
" for pos_id, deals_list in positions.items():\n",
|
" for pos_id in entry_deals:\n",
|
||||||
" # Need both entry and exit\n",
|
" # Skip if no exit yet (still open)\n",
|
||||||
" if len(deals_list) < 2:\n",
|
" if pos_id not in exit_deals:\n",
|
||||||
" continue\n",
|
|
||||||
"\n",
|
|
||||||
" entry_deal = None\n",
|
|
||||||
" exit_deal = None\n",
|
|
||||||
"\n",
|
|
||||||
" for d in deals_list:\n",
|
|
||||||
" if d.entry == 0: # DEAL_ENTRY_IN\n",
|
|
||||||
" entry_deal = d\n",
|
|
||||||
" elif d.entry == 1: # DEAL_ENTRY_OUT\n",
|
|
||||||
" exit_deal = d\n",
|
|
||||||
"\n",
|
|
||||||
" if entry_deal is None or exit_deal is None:\n",
|
|
||||||
" continue\n",
|
" continue\n",
|
||||||
" \n",
|
" \n",
|
||||||
" # Skip if already logged\n",
|
" # Skip if already logged\n",
|
||||||
" if pos_id in already_logged:\n",
|
" if pos_id in already_logged:\n",
|
||||||
" continue\n",
|
" continue\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
" entry_deal = entry_deals[pos_id]\n",
|
||||||
|
" exit_deal = exit_deals[pos_id]\n",
|
||||||
|
"\n",
|
||||||
" # Determine direction\n",
|
" # Determine direction\n",
|
||||||
" direction = \"LONG\" if entry_deal.type == 0 else \"SHORT\" # 0=BUY, 1=SELL\n",
|
" direction = \"LONG\" if entry_deal.type == 0 else \"SHORT\" # 0=BUY, 1=SELL\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # Calculate profit\n",
|
" # Calculate profit (includes swap and commission)\n",
|
||||||
" profit = exit_deal.profit + exit_deal.swap + exit_deal.commission\n",
|
" profit = exit_deal.profit + exit_deal.swap + exit_deal.commission\n",
|
||||||
"\n",
|
"\n",
|
||||||
" # Determine session (simplified)\n",
|
" # Determine session\n",
|
||||||
" hour = datetime.fromtimestamp(entry_deal.time).hour\n",
|
" hour = datetime.fromtimestamp(entry_deal.time).hour\n",
|
||||||
" if 0 <= hour < 8:\n",
|
" if 0 <= hour < 8:\n",
|
||||||
" session = \"asian\"\n",
|
" session = \"asian\"\n",
|
||||||
@@ -3962,6 +4108,15 @@
|
|||||||
" else:\n",
|
" else:\n",
|
||||||
" session = \"asian\"\n",
|
" session = \"asian\"\n",
|
||||||
"\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",
|
" # Log to tracker\n",
|
||||||
" demo_tracker.log_trade(\n",
|
" demo_tracker.log_trade(\n",
|
||||||
" ticket=pos_id,\n",
|
" ticket=pos_id,\n",
|
||||||
@@ -3974,19 +4129,102 @@
|
|||||||
" entry_time=datetime.fromtimestamp(entry_deal.time),\n",
|
" entry_time=datetime.fromtimestamp(entry_deal.time),\n",
|
||||||
" exit_time=datetime.fromtimestamp(exit_deal.time),\n",
|
" exit_time=datetime.fromtimestamp(exit_deal.time),\n",
|
||||||
" session=session,\n",
|
" session=session,\n",
|
||||||
" base_confidence=0, # Not available from history\n",
|
" base_confidence=0,\n",
|
||||||
" enhanced_score=0,\n",
|
" enhanced_score=0,\n",
|
||||||
" hybrid_score=0,\n",
|
" hybrid_score=0,\n",
|
||||||
" signal_quality=\"unknown\",\n",
|
" signal_quality=\"unknown\",\n",
|
||||||
" close_reason=\"history_sync\"\n",
|
" close_reason=close_reason\n",
|
||||||
" )\n",
|
" )\n",
|
||||||
" synced += 1\n",
|
" synced += 1\n",
|
||||||
" print(f\" ✅ Synced trade #{pos_id}: {direction} {entry_deal.symbol} | Profit: ${profit:.2f}\")\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",
|
"\n",
|
||||||
" print(f\"\\n📊 Synced {synced} trades to Demo Tracker\")\n",
|
" print(f\"\\n📊 Synced {synced} trades to Demo Tracker\")\n",
|
||||||
" return synced\n",
|
" return synced\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Run sync\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",
|
"synced_count = sync_closed_trades_to_tracker(days_back=30)\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Show updated stats\n",
|
"# Show updated stats\n",
|
||||||
@@ -3994,7 +4232,64 @@
|
|||||||
"stats = demo_tracker.get_stats()\n",
|
"stats = demo_tracker.get_stats()\n",
|
||||||
"print(f\"📊 Total Trades in Tracker: {stats.get('total_trades', 0)}\")\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\"📈 Win Rate: {stats.get('win_rate', 0)*100:.1f}%\")\n",
|
||||||
"print(f\"💰 Total Profit: ${stats.get('total_profit', 0):.2f}\")\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)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Debug: Zeige alle Deals der letzten 30 Tage\n",
|
||||||
|
"from datetime import datetime, timedelta\n",
|
||||||
|
"\n",
|
||||||
|
"from_date = datetime.now() - timedelta(days=30)\n",
|
||||||
|
"deals = mt.history_deals_get(from_date, datetime.now())\n",
|
||||||
|
"\n",
|
||||||
|
"if deals:\n",
|
||||||
|
" print(f\"📊 Gefundene Deals: {len(deals)}\")\n",
|
||||||
|
" print(\"\\nLetzte 10 Deals mit Kommentaren:\")\n",
|
||||||
|
" print(\"-\" * 80)\n",
|
||||||
|
" \n",
|
||||||
|
" for deal in deals[-10:]:\n",
|
||||||
|
" print(f\" Ticket: {deal.ticket}\")\n",
|
||||||
|
" print(f\" Symbol: {deal.symbol}\")\n",
|
||||||
|
" print(f\" Comment: '{deal.comment}'\")\n",
|
||||||
|
" print(f\" Type: {deal.type} | Entry: {deal.entry}\")\n",
|
||||||
|
" print(f\" Profit: ${deal.profit:.2f}\")\n",
|
||||||
|
" print(\"-\" * 40)\n",
|
||||||
|
"else:\n",
|
||||||
|
" print(\"❌ Keine Deals gefunden\")"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -4265,7 +4560,94 @@
|
|||||||
"execution_count": null,
|
"execution_count": null,
|
||||||
"id": "cf605503",
|
"id": "cf605503",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"================================================================================\n",
|
||||||
|
"💰 MT5 P&L TRACKER - LIVE PERFORMANCE DASHBOARD\n",
|
||||||
|
"================================================================================\n",
|
||||||
|
"\n",
|
||||||
|
"Generated: 2026-01-29 10:46:35\n",
|
||||||
|
"\n",
|
||||||
|
"================================================================================\n",
|
||||||
|
"📊 ALL TIME PERFORMANCE\n",
|
||||||
|
"================================================================================\n",
|
||||||
|
"\n",
|
||||||
|
"Total Trades: 408\n",
|
||||||
|
"Winning Trades: 202 (49.5%)\n",
|
||||||
|
"Losing Trades: 206\n",
|
||||||
|
"\n",
|
||||||
|
"Net Profit: $3513.54\n",
|
||||||
|
"Total Profit: $12873.24\n",
|
||||||
|
"Total Loss: $9359.70\n",
|
||||||
|
"Profit Factor: 1.38\n",
|
||||||
|
"\n",
|
||||||
|
"Average Win: $63.73\n",
|
||||||
|
"Average Loss: $-45.44\n",
|
||||||
|
"Largest Win: $499.30\n",
|
||||||
|
"Largest Loss: $-151.75\n",
|
||||||
|
"\n",
|
||||||
|
"Max Drawdown: $-1981.78\n",
|
||||||
|
"Avg Duration: 1.0 hours\n",
|
||||||
|
"Total Pips: 5626600.0\n",
|
||||||
|
"\n",
|
||||||
|
"================================================================================\n",
|
||||||
|
"📅 THIS MONTH\n",
|
||||||
|
"================================================================================\n",
|
||||||
|
"\n",
|
||||||
|
"Trades: 332 (50.6% WR)\n",
|
||||||
|
"Net Profit: $3486.41\n",
|
||||||
|
"Profit/Loss: +$12558.73 / -$9072.32\n",
|
||||||
|
"\n",
|
||||||
|
"================================================================================\n",
|
||||||
|
"📅 THIS WEEK\n",
|
||||||
|
"================================================================================\n",
|
||||||
|
"\n",
|
||||||
|
"Trades: 166 (58.4% WR)\n",
|
||||||
|
"Net Profit: $3545.17\n",
|
||||||
|
"Profit/Loss: +$9536.97 / -$5991.80\n",
|
||||||
|
"\n",
|
||||||
|
"================================================================================\n",
|
||||||
|
"📅 TODAY\n",
|
||||||
|
"================================================================================\n",
|
||||||
|
"\n",
|
||||||
|
"❌ No trades today\n",
|
||||||
|
"\n",
|
||||||
|
"================================================================================\n",
|
||||||
|
"\n",
|
||||||
|
"================================================================================\n",
|
||||||
|
"📜 RECENT TRADES (Last 10)\n",
|
||||||
|
"================================================================================\n",
|
||||||
|
"\n",
|
||||||
|
" position_id symbol type entry_time exit_time net_profit pips duration_hours status\n",
|
||||||
|
" 719790790 XAUUSD LONG 2026-01-28 18:31 2026-01-28 21:24 499.30 499300.0 2.9 ✅ WIN\n",
|
||||||
|
" 719088260 XAUUSD LONG 2026-01-28 17:07 2026-01-28 17:58 153.20 306400.0 0.9 ✅ WIN\n",
|
||||||
|
" 718932869 XAUUSD LONG 2026-01-28 16:39 2026-01-28 17:06 -59.05 -118100.0 0.5 ❌ LOSS\n",
|
||||||
|
" 718905388 XAUUSD LONG 2026-01-28 16:35 2026-01-28 16:38 -87.76 -109700.0 0.1 ❌ LOSS\n",
|
||||||
|
" 718788969 XAUUSD LONG 2026-01-28 16:12 2026-01-28 16:34 34.45 68900.0 0.4 ✅ WIN\n",
|
||||||
|
" 718746714 XAUUSD LONG 2026-01-28 16:05 2026-01-28 16:11 -106.10 -106100.0 0.1 ❌ LOSS\n",
|
||||||
|
" 718592815 XAUUSD LONG 2026-01-28 15:29 2026-01-28 16:04 131.10 262200.0 0.6 ✅ WIN\n",
|
||||||
|
" 718467996 XAUUSD LONG 2026-01-28 15:03 2026-01-28 15:28 15.00 30000.0 0.4 ✅ WIN\n",
|
||||||
|
" 718377011 XAUUSD LONG 2026-01-28 14:38 2026-01-28 15:03 15.00 30000.0 0.4 ✅ WIN\n",
|
||||||
|
" 718312889 XAUUSD LONG 2026-01-28 14:22 2026-01-28 14:37 15.00 30000.0 0.3 ✅ WIN\n",
|
||||||
|
"\n",
|
||||||
|
"================================================================================\n",
|
||||||
|
"✅ Dashboard refresh complete!\n",
|
||||||
|
"Last updated: 2026-01-29 10:46:35\n",
|
||||||
|
"================================================================================\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stderr",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"2026-01-29 10:46:39,047 - INFO - HTTP Request: POST https://api.telegram.org/bot7783303065:AAHVVvwWGqmhJ2BVq8LqkLRSsicKy1CUsD8/getUpdates \"HTTP/1.1 200 OK\"\n",
|
||||||
|
"2026-01-29 10:46:49,067 - INFO - HTTP Request: POST https://api.telegram.org/bot7783303065:AAHVVvwWGqmhJ2BVq8LqkLRSsicKy1CUsD8/getUpdates \"HTTP/1.1 200 OK\"\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"# ==========================================\n",
|
"# ==========================================\n",
|
||||||
"# 💰 P&L PERFORMANCE DASHBOARD\n",
|
"# 💰 P&L PERFORMANCE DASHBOARD\n",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+30
-6
@@ -114,6 +114,9 @@ class DemoTestTracker:
|
|||||||
logger.info(f" Days Running: {self._days_running()}")
|
logger.info(f" Days Running: {self._days_running()}")
|
||||||
logger.info("=" * 60)
|
logger.info("=" * 60)
|
||||||
|
|
||||||
|
# Datei sofort erstellen falls sie nicht existiert
|
||||||
|
self._save_data()
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# TRADE LOGGING
|
# TRADE LOGGING
|
||||||
# ==========================================
|
# ==========================================
|
||||||
@@ -280,11 +283,14 @@ class DemoTestTracker:
|
|||||||
avg_win = total_wins / len(wins) if wins else 0
|
avg_win = total_wins / len(wins) if wins else 0
|
||||||
avg_loss = total_losses / len(losses) if losses else 0
|
avg_loss = total_losses / len(losses) if losses else 0
|
||||||
|
|
||||||
# Drawdown calculation
|
# Drawdown calculation (proper method)
|
||||||
|
# Max drawdown = largest drop from peak (in dollars and percentage)
|
||||||
|
# Note: Percentage is relative to peak equity at that moment
|
||||||
equity_curve = []
|
equity_curve = []
|
||||||
running_equity = 0
|
running_equity = 0
|
||||||
peak_equity = 0
|
peak_equity = 0
|
||||||
max_drawdown = 0
|
max_drawdown_dollars = 0
|
||||||
|
max_drawdown_pct = 0
|
||||||
|
|
||||||
for t in trades:
|
for t in trades:
|
||||||
running_equity += t['profit']
|
running_equity += t['profit']
|
||||||
@@ -293,9 +299,23 @@ class DemoTestTracker:
|
|||||||
if running_equity > peak_equity:
|
if running_equity > peak_equity:
|
||||||
peak_equity = running_equity
|
peak_equity = running_equity
|
||||||
|
|
||||||
drawdown = (peak_equity - running_equity) / peak_equity if peak_equity > 0 else 0
|
# Calculate drawdown in dollars
|
||||||
if drawdown > max_drawdown:
|
current_drawdown_dollars = peak_equity - running_equity
|
||||||
max_drawdown = drawdown
|
if current_drawdown_dollars > max_drawdown_dollars:
|
||||||
|
max_drawdown_dollars = current_drawdown_dollars
|
||||||
|
|
||||||
|
# Calculate percentage relative to peak
|
||||||
|
# Only meaningful when peak > 0 and we're still positive
|
||||||
|
if peak_equity > 0 and running_equity >= 0:
|
||||||
|
current_drawdown_pct = current_drawdown_dollars / peak_equity
|
||||||
|
if current_drawdown_pct > max_drawdown_pct:
|
||||||
|
max_drawdown_pct = current_drawdown_pct
|
||||||
|
elif peak_equity > 0 and running_equity < 0:
|
||||||
|
# If equity goes negative, that's 100%+ drawdown
|
||||||
|
max_drawdown_pct = 1.0 # Cap at 100%
|
||||||
|
|
||||||
|
# Final max drawdown (capped at 100%)
|
||||||
|
max_drawdown = min(max_drawdown_pct, 1.0)
|
||||||
|
|
||||||
# Session stats
|
# Session stats
|
||||||
session_stats = defaultdict(lambda: {'trades': 0, 'wins': 0, 'profit': 0})
|
session_stats = defaultdict(lambda: {'trades': 0, 'wins': 0, 'profit': 0})
|
||||||
@@ -327,6 +347,7 @@ class DemoTestTracker:
|
|||||||
'avg_loss': avg_loss,
|
'avg_loss': avg_loss,
|
||||||
'avg_trade': total_profit / len(trades) if trades else 0,
|
'avg_trade': total_profit / len(trades) if trades else 0,
|
||||||
'max_drawdown': max_drawdown,
|
'max_drawdown': max_drawdown,
|
||||||
|
'max_drawdown_dollars': max_drawdown_dollars,
|
||||||
'current_equity': running_equity,
|
'current_equity': running_equity,
|
||||||
'peak_equity': peak_equity,
|
'peak_equity': peak_equity,
|
||||||
'sessions_tested': list(session_stats.keys()),
|
'sessions_tested': list(session_stats.keys()),
|
||||||
@@ -337,6 +358,9 @@ class DemoTestTracker:
|
|||||||
'avg_duration_minutes': sum(t['duration_minutes'] for t in trades) / len(trades) if trades else 0,
|
'avg_duration_minutes': sum(t['duration_minutes'] for t in trades) / len(trades) if trades else 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Persist updated stats to file
|
||||||
|
self._save_data()
|
||||||
|
|
||||||
def get_stats(self) -> Dict:
|
def get_stats(self) -> Dict:
|
||||||
"""Gibt aktuelle Statistiken zurück"""
|
"""Gibt aktuelle Statistiken zurück"""
|
||||||
self._update_stats()
|
self._update_stats()
|
||||||
@@ -469,7 +493,7 @@ class DemoTestTracker:
|
|||||||
print(f" Worst Trade: ${stats.get('worst_trade', 0):,.2f}")
|
print(f" Worst Trade: ${stats.get('worst_trade', 0):,.2f}")
|
||||||
|
|
||||||
print(f"\n📉 RISK METRICS:")
|
print(f"\n📉 RISK METRICS:")
|
||||||
print(f" Max Drawdown: {stats.get('max_drawdown', 0)*100:.1f}%")
|
print(f" Max Drawdown: {stats.get('max_drawdown', 0)*100:.1f}% (${stats.get('max_drawdown_dollars', 0):,.2f})")
|
||||||
print(f" Current Equity: ${stats.get('current_equity', 0):,.2f}")
|
print(f" Current Equity: ${stats.get('current_equity', 0):,.2f}")
|
||||||
print(f" Peak Equity: ${stats.get('peak_equity', 0):,.2f}")
|
print(f" Peak Equity: ${stats.get('peak_equity', 0):,.2f}")
|
||||||
|
|
||||||
|
|||||||
@@ -253,9 +253,9 @@ SCHRITT 3: Scheduler neu starten
|
|||||||
|
|
||||||
SCHRITT 4: Teste mit verschiedenen Modi:
|
SCHRITT 4: Teste mit verschiedenen Modi:
|
||||||
|
|
||||||
# Standard Mode (NY + Overlap)
|
# Standard Mode (Asian + NY + Overlap)
|
||||||
SESSION_WHITELIST_CONFIG['enabled_sessions'] = {
|
SESSION_WHITELIST_CONFIG['enabled_sessions'] = {
|
||||||
'asian': False, 'london': False,
|
'asian': True, 'london': False,
|
||||||
'overlap': True, 'ny': True
|
'overlap': True, 'ny': True
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user