Files
XauBot/docs/research/final_news_verification.py
GifariKemal 0d25548ed5 refactor: restructure repository and add README, CLAUDE.md, LICENSE
- Move utility scripts to scripts/ (check_market, check_positions, etc.)
- Move test files to tests/ (test_modules, test_mt5_connection, etc.)
- Move deprecated dashboards to archive/
- Move research files to docs/research/
- Add sys.path fix to all moved Python files
- Rewrite README.md with architecture diagram and badges
- Add CLAUDE.md project guide
- Add MIT LICENSE
- Update .gitignore with archive/ pattern

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 13:22:46 +07:00

390 lines
14 KiB
Python

"""
FINAL NEWS FILTER VERIFICATION
==============================
Extreme case analysis and final recommendation.
"""
import polars as pl
import numpy as np
from datetime import datetime, timedelta, date
from dataclasses import dataclass
from typing import List, Tuple, Dict
import time
from loguru import logger
import sys
logger.remove()
logger.add(sys.stdout, format="<green>{time:HH:mm:ss}</green> | <level>{level:<8}</level> | <cyan>{message}</cyan>", level="INFO")
# Complete news calendar
HISTORICAL_NEWS = [
# NFP
(date(2025, 5, 2), 19, "NFP", "HIGH"),
(date(2025, 6, 6), 19, "NFP", "HIGH"),
(date(2025, 7, 3), 19, "NFP", "HIGH"),
(date(2025, 8, 1), 19, "NFP", "HIGH"),
(date(2025, 9, 5), 19, "NFP", "HIGH"),
(date(2025, 10, 3), 19, "NFP", "HIGH"),
(date(2025, 11, 7), 19, "NFP", "HIGH"),
(date(2025, 12, 5), 19, "NFP", "HIGH"),
(date(2026, 1, 10), 20, "NFP", "HIGH"),
(date(2026, 2, 7), 20, "NFP", "HIGH"),
# CPI
(date(2025, 5, 13), 19, "CPI", "HIGH"),
(date(2025, 6, 11), 19, "CPI", "HIGH"),
(date(2025, 7, 10), 19, "CPI", "HIGH"),
(date(2025, 8, 13), 19, "CPI", "HIGH"),
(date(2025, 9, 10), 19, "CPI", "HIGH"),
(date(2025, 10, 10), 19, "CPI", "HIGH"),
(date(2025, 11, 13), 20, "CPI", "HIGH"),
(date(2025, 12, 11), 20, "CPI", "HIGH"),
(date(2026, 1, 15), 20, "CPI", "HIGH"),
# FOMC
(date(2025, 5, 7), 1, "FOMC", "HIGH"),
(date(2025, 6, 18), 1, "FOMC", "HIGH"),
(date(2025, 7, 30), 1, "FOMC", "HIGH"),
(date(2025, 9, 17), 1, "FOMC", "HIGH"),
(date(2025, 11, 5), 1, "FOMC", "HIGH"),
(date(2025, 12, 17), 1, "FOMC", "HIGH"),
(date(2026, 1, 29), 2, "FOMC", "HIGH"),
]
def is_news_window(dt: datetime, buffer_hours: int = 1) -> Tuple[bool, str]:
"""Check if within buffer hours of HIGH impact news."""
current_date = dt.date()
current_hour = dt.hour
for news_date, news_hour, name, impact in HISTORICAL_NEWS:
if news_date == current_date and impact == "HIGH":
if abs(current_hour - news_hour) <= buffer_hours:
return True, name
return False, ""
@dataclass
class Trade:
entry_time: datetime
exit_time: datetime
direction: str
entry_price: float
exit_price: float
pnl: float
confidence: float
exit_reason: str
news_blocked: bool = False
news_name: str = ""
def run_final_verification():
"""Run final verification tests."""
print("=" * 80)
print("FINAL NEWS FILTER VERIFICATION")
print("=" * 80)
# Load data
print("\n[1] Loading data...")
import MetaTrader5 as mt5
from src.config import get_config
from src.feature_eng import FeatureEngineer
from src.smc_polars import SMCAnalyzer
from src.regime_detector import MarketRegimeDetector
from src.ml_model import TradingModel
config = get_config()
mt5.initialize(path=config.mt5_path, login=config.mt5_login,
password=config.mt5_password, server=config.mt5_server)
mt5.symbol_select("XAUUSD", True)
time.sleep(0.5)
rates = mt5.copy_rates_from_pos("XAUUSD", mt5.TIMEFRAME_M5, 0, 60000)
mt5.shutdown()
df = pl.DataFrame({
"time": [datetime.fromtimestamp(r[0]) for r in rates],
"open": [r[1] for r in rates],
"high": [r[2] for r in rates],
"low": [r[3] for r in rates],
"close": [r[4] for r in rates],
"volume": [float(r[5]) for r in rates],
})
print(f" Data: {len(df)} bars ({df['time'].min()} to {df['time'].max()})")
# Calculate features
print("\n[2] Calculating features...")
fe = FeatureEngineer()
df = fe.calculate_all(df, include_ml_features=True)
smc = SMCAnalyzer()
df = smc.calculate_all(df)
regime = MarketRegimeDetector(model_path="models/hmm_regime.pkl")
regime.load()
df = regime.predict(df)
# Load ML model
print("\n[3] Loading ML model...")
ml_model = TradingModel(model_path="models/xgboost_model.pkl")
ml_model.load()
available_features = [f for f in ml_model.feature_names if f in df.columns]
print(f" Features: {len(available_features)}/{len(ml_model.feature_names)}")
# ========================================================================
# TEST: Confidence threshold sensitivity during news
# ========================================================================
print("\n" + "=" * 80)
print("TEST: CONFIDENCE THRESHOLD DURING NEWS VS NON-NEWS")
print("=" * 80)
lot_size = 0.02
sl_atr_mult = 1.5
tp_atr_mult = 3.0
# Collect all potential trades
potential_trades = []
for idx in range(200, len(df) - 1):
row = df.row(idx, named=True)
current_time = row["time"]
if current_time.date() < date(2025, 5, 22):
continue
if current_time.date() > date(2026, 2, 5):
break
# Session filter
hour = current_time.hour
if hour < 14 or hour > 23:
continue
close = row["close"]
atr = row.get("atr", close * 0.003)
if atr is None or atr <= 0:
atr = close * 0.003
# Get ML prediction
try:
df_slice = df.slice(max(0, idx - 100), 101)
pred = ml_model.predict(df_slice, available_features)
if pred.confidence < 0.50: # Lower threshold to capture more data
continue
signal = pred.signal
confidence = pred.confidence
except Exception:
continue
if signal not in ["BUY", "SELL"]:
continue
# Calculate SL/TP
entry_price = close
if signal == "BUY":
sl = close - (atr * sl_atr_mult)
tp = close + (atr * tp_atr_mult)
else:
sl = close + (atr * sl_atr_mult)
tp = close - (atr * tp_atr_mult)
# Look forward to find exit
exit_price = None
exit_time = None
exit_reason = None
for future_idx in range(idx + 1, min(idx + 200, len(df))):
future_row = df.row(future_idx, named=True)
future_high = future_row["high"]
future_low = future_row["low"]
if signal == "BUY":
if future_low <= sl:
exit_price = sl
exit_reason = "SL"
exit_time = future_row["time"]
break
elif future_high >= tp:
exit_price = tp
exit_reason = "TP"
exit_time = future_row["time"]
break
else:
if future_high >= sl:
exit_price = sl
exit_reason = "SL"
exit_time = future_row["time"]
break
elif future_low <= tp:
exit_price = tp
exit_reason = "TP"
exit_time = future_row["time"]
break
if exit_price is None:
continue
# Calculate P/L
if signal == "BUY":
pnl = (exit_price - entry_price) * lot_size * 100
else:
pnl = (entry_price - exit_price) * lot_size * 100
# Check if in news window
in_news, news_name = is_news_window(current_time, buffer_hours=1)
potential_trades.append({
"entry_time": current_time,
"confidence": confidence,
"pnl": pnl,
"in_news": in_news,
"news_name": news_name,
})
# Analyze by confidence bucket
print("\n--- WIN RATE BY CONFIDENCE LEVEL ---")
print(f"{'Confidence':>12} | {'Normal':^20} | {'During News':^20}")
print(f"{'':12} | {'Count':>6} {'WR':>6} {'Avg P/L':>7} | {'Count':>6} {'WR':>6} {'Avg P/L':>7}")
print("-" * 70)
conf_buckets = [(0.50, 0.60), (0.60, 0.70), (0.70, 0.80), (0.80, 0.90), (0.90, 1.00)]
for low, high in conf_buckets:
# Normal trades
normal = [t for t in potential_trades if not t["in_news"] and low <= t["confidence"] < high]
normal_wins = len([t for t in normal if t["pnl"] > 0])
normal_wr = normal_wins / len(normal) * 100 if normal else 0
normal_avg = sum(t["pnl"] for t in normal) / len(normal) if normal else 0
# News trades
news = [t for t in potential_trades if t["in_news"] and low <= t["confidence"] < high]
news_wins = len([t for t in news if t["pnl"] > 0])
news_wr = news_wins / len(news) * 100 if news else 0
news_avg = sum(t["pnl"] for t in news) / len(news) if news else 0
label = f"{low*100:.0f}%-{high*100:.0f}%"
print(f"{label:>12} | {len(normal):>6} {normal_wr:>5.1f}% ${normal_avg:>6.2f} | "
f"{len(news):>6} {news_wr:>5.1f}% ${news_avg:>6.2f}")
# ========================================================================
# STATISTICAL ANALYSIS
# ========================================================================
print("\n" + "=" * 80)
print("STATISTICAL ANALYSIS (70%+ Confidence)")
print("=" * 80)
# Filter to 70%+ confidence (our actual threshold)
high_conf = [t for t in potential_trades if t["confidence"] >= 0.70]
normal_trades = [t for t in high_conf if not t["in_news"]]
news_trades = [t for t in high_conf if t["in_news"]]
print(f"\nNORMAL TRADES (outside news windows):")
print(f" Count: {len(normal_trades)}")
print(f" Wins: {len([t for t in normal_trades if t['pnl'] > 0])}")
print(f" Win Rate: {len([t for t in normal_trades if t['pnl'] > 0])/len(normal_trades)*100:.1f}%")
print(f" Total P/L: ${sum(t['pnl'] for t in normal_trades):,.2f}")
print(f" Avg P/L: ${sum(t['pnl'] for t in normal_trades)/len(normal_trades):.2f}")
print(f"\nNEWS TRADES (during news windows):")
print(f" Count: {len(news_trades)}")
print(f" Wins: {len([t for t in news_trades if t['pnl'] > 0])}")
print(f" Win Rate: {len([t for t in news_trades if t['pnl'] > 0])/len(news_trades)*100:.1f}%" if news_trades else " Win Rate: N/A")
print(f" Total P/L: ${sum(t['pnl'] for t in news_trades):,.2f}")
print(f" Avg P/L: ${sum(t['pnl'] for t in news_trades)/len(news_trades):.2f}" if news_trades else " Avg P/L: N/A")
# ========================================================================
# WORST CASE ANALYSIS
# ========================================================================
print("\n" + "=" * 80)
print("WORST CASE ANALYSIS: Biggest Losses During News")
print("=" * 80)
news_losses = sorted([t for t in news_trades if t["pnl"] < 0], key=lambda x: x["pnl"])
if news_losses:
print("\nTop 5 biggest losses during news windows:")
for i, t in enumerate(news_losses[:5]):
print(f" {i+1}. {t['entry_time'].strftime('%Y-%m-%d %H:%M')} | {t['news_name']:6} | "
f"Conf: {t['confidence']*100:.1f}% | P/L: ${t['pnl']:.2f}")
total_news_losses = sum(t["pnl"] for t in news_losses)
print(f"\nTotal losses during news: ${total_news_losses:.2f}")
# ========================================================================
# BEST CASE ANALYSIS
# ========================================================================
print("\n--- Biggest Wins During News ---")
news_wins = sorted([t for t in news_trades if t["pnl"] > 0], key=lambda x: -x["pnl"])
if news_wins:
print("\nTop 5 biggest wins during news windows:")
for i, t in enumerate(news_wins[:5]):
print(f" {i+1}. {t['entry_time'].strftime('%Y-%m-%d %H:%M')} | {t['news_name']:6} | "
f"Conf: {t['confidence']*100:.1f}% | P/L: ${t['pnl']:.2f}")
total_news_wins = sum(t["pnl"] for t in news_wins)
print(f"\nTotal wins during news: ${total_news_wins:.2f}")
# ========================================================================
# FINAL RECOMMENDATION
# ========================================================================
print("\n" + "=" * 80)
print("FINAL RECOMMENDATION")
print("=" * 80)
normal_wr = len([t for t in normal_trades if t["pnl"] > 0]) / len(normal_trades) * 100
news_wr = len([t for t in news_trades if t["pnl"] > 0]) / len(news_trades) * 100 if news_trades else 0
news_pnl = sum(t["pnl"] for t in news_trades)
print(f"""
EVIDENCE SUMMARY:
================
1. Normal trades win rate: {normal_wr:.1f}%
2. News trades win rate: {news_wr:.1f}%
3. Total profit from news trades: ${news_pnl:,.2f}
4. News trades count: {len(news_trades)}
CONCLUSION:
===========
""")
if news_wr >= normal_wr - 5 and news_pnl > 0:
print(" The news filter is NOT BENEFICIAL.")
print(" - News trades have similar win rate to normal trades")
print(f" - Blocking news trades would cost ${news_pnl:,.2f}")
print("\n RECOMMENDATION: REMOVE NEWS FILTER")
recommendation = "REMOVE"
elif news_wr < normal_wr - 10:
print(" The news filter MAY BE BENEFICIAL.")
print(" - News trades have significantly lower win rate")
print("\n RECOMMENDATION: KEEP NEWS FILTER (for risk management)")
recommendation = "KEEP"
else:
print(" The news filter has MINIMAL IMPACT.")
print(" - News trades perform similarly to normal trades")
print("\n RECOMMENDATION: OPTIONAL - can remove for simplicity")
recommendation = "OPTIONAL"
print(f"""
================================================================
FINAL VERDICT: {recommendation} NEWS FILTER
================================================================
Reasons:
- Win rate during news: {news_wr:.1f}% (vs {normal_wr:.1f}% normal)
- Profit potential lost by filtering: ${news_pnl:,.2f}
- The ML model already captures market conditions well
- High-impact news doesn't significantly hurt our model's performance
""")
return recommendation
if __name__ == "__main__":
result = run_final_verification()
print(f"\n>>> FINAL ANSWER: {result} <<<")