feat: Implement Demo Test Tracker for Go-Live readiness assessment
NEW MODULE: demo_test_tracker.py - DemoTestTracker class for comprehensive statistics collection - TradeRecord dataclass for structured trade logging - Automatic Win Rate, Profit Factor, Drawdown calculation - Session-based and Signal Quality breakdown - Error/Bug tracking - Persistent JSON storage GO-LIVE CRITERIA (configurable): - min_trades: 50 trades required - min_win_rate: 55% - min_profit_factor: 1.3 - max_drawdown: 15% - min_days: 14 days running - max_errors: 5 critical errors - min_sessions_tested: 2 different sessions NEW NOTEBOOK CELLS: - Cell 92: Performance Report & Go-Live Check - Cell 93: MT5 History Sync (imports past trades) FEATURES: - print_report(): Full performance breakdown - print_go_live_check(): Visual checklist with pass/fail - get_daily_summary(): Quick daily stats - sync_closed_trades_to_tracker(): Import from MT5 history INTEGRATION: - Added to Cell 78 (Advanced Optimizations) - Tracks trades automatically after execution - Persistent data in demo_test_stats.json Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,662 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
📊 Demo Test Tracker
|
||||
Sammelt Statistiken während der Demo-Phase und gibt Go-Live Empfehlungen
|
||||
|
||||
FEATURES:
|
||||
1. Trade Logging (Entry, Exit, Profit/Loss)
|
||||
2. Performance Metriken (Win Rate, Profit Factor, etc.)
|
||||
3. Drawdown Tracking
|
||||
4. Session-basierte Analyse
|
||||
5. Error/Bug Tracking
|
||||
6. Go-Live Readiness Check
|
||||
7. Automatische Reports
|
||||
|
||||
VERWENDUNG:
|
||||
from demo_test_tracker import DemoTestTracker
|
||||
|
||||
tracker = DemoTestTracker()
|
||||
|
||||
# Nach jedem Trade:
|
||||
tracker.log_trade(trade_result)
|
||||
|
||||
# Report anzeigen:
|
||||
tracker.print_report()
|
||||
|
||||
# Go-Live Check:
|
||||
tracker.check_go_live_readiness()
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
from dataclasses import dataclass, asdict
|
||||
from collections import defaultdict
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TradeRecord:
|
||||
"""Einzelner Trade-Eintrag"""
|
||||
ticket: int
|
||||
symbol: str
|
||||
direction: str # "LONG" or "SHORT"
|
||||
entry_price: float
|
||||
exit_price: float
|
||||
volume: float
|
||||
profit: float
|
||||
profit_pips: float
|
||||
entry_time: str
|
||||
exit_time: str
|
||||
duration_minutes: float
|
||||
session: str
|
||||
|
||||
# Signal Info
|
||||
base_confidence: float
|
||||
enhanced_score: float
|
||||
hybrid_score: float
|
||||
signal_quality: str
|
||||
|
||||
# Filters applied
|
||||
equity_curve_multiplier: float
|
||||
|
||||
# Result
|
||||
is_win: bool
|
||||
|
||||
# Optional metadata
|
||||
stop_loss: float = 0.0
|
||||
take_profit: float = 0.0
|
||||
close_reason: str = "" # "TP", "SL", "Manual", "Trailing"
|
||||
|
||||
|
||||
class DemoTestTracker:
|
||||
"""
|
||||
Demo Test Tracker für Go-Live Vorbereitung
|
||||
|
||||
Sammelt alle relevanten Statistiken und gibt
|
||||
Empfehlungen wann der Bot produktionsreif ist.
|
||||
"""
|
||||
|
||||
# Go-Live Kriterien
|
||||
GO_LIVE_CRITERIA = {
|
||||
'min_trades': 50,
|
||||
'min_win_rate': 0.55, # 55%
|
||||
'min_profit_factor': 1.3,
|
||||
'max_drawdown': 0.15, # 15%
|
||||
'min_days': 14,
|
||||
'max_errors': 5,
|
||||
'min_sessions_tested': 2, # Mindestens 2 verschiedene Sessions
|
||||
}
|
||||
|
||||
def __init__(self,
|
||||
data_file: str = "demo_test_stats.json",
|
||||
criteria: Optional[Dict] = None):
|
||||
"""
|
||||
Args:
|
||||
data_file: Datei für persistente Speicherung
|
||||
criteria: Custom Go-Live Kriterien (optional)
|
||||
"""
|
||||
self.data_file = data_file
|
||||
self.criteria = criteria or self.GO_LIVE_CRITERIA.copy()
|
||||
|
||||
# Daten laden oder initialisieren
|
||||
self.data = self._load_data()
|
||||
|
||||
logger.info("=" * 60)
|
||||
logger.info("📊 DEMO TEST TRACKER INITIALIZED")
|
||||
logger.info("=" * 60)
|
||||
logger.info(f" Data File: {data_file}")
|
||||
logger.info(f" Total Trades: {len(self.data['trades'])}")
|
||||
logger.info(f" Start Date: {self.data['start_date']}")
|
||||
logger.info(f" Days Running: {self._days_running()}")
|
||||
logger.info("=" * 60)
|
||||
|
||||
# ==========================================
|
||||
# TRADE LOGGING
|
||||
# ==========================================
|
||||
|
||||
def log_trade(self,
|
||||
ticket: int,
|
||||
symbol: str,
|
||||
direction: str,
|
||||
entry_price: float,
|
||||
exit_price: float,
|
||||
volume: float,
|
||||
profit: float,
|
||||
entry_time: datetime,
|
||||
exit_time: datetime,
|
||||
session: str = "unknown",
|
||||
base_confidence: float = 0.0,
|
||||
enhanced_score: float = 0.0,
|
||||
hybrid_score: float = 0.0,
|
||||
signal_quality: str = "unknown",
|
||||
equity_curve_multiplier: float = 1.0,
|
||||
stop_loss: float = 0.0,
|
||||
take_profit: float = 0.0,
|
||||
close_reason: str = "") -> TradeRecord:
|
||||
"""
|
||||
Loggt einen abgeschlossenen Trade
|
||||
|
||||
Returns:
|
||||
TradeRecord object
|
||||
"""
|
||||
# Calculate derived values
|
||||
if direction == "LONG":
|
||||
profit_pips = (exit_price - entry_price) * 10 # For Gold
|
||||
else:
|
||||
profit_pips = (entry_price - exit_price) * 10
|
||||
|
||||
duration = (exit_time - entry_time).total_seconds() / 60
|
||||
is_win = profit > 0
|
||||
|
||||
record = TradeRecord(
|
||||
ticket=ticket,
|
||||
symbol=symbol,
|
||||
direction=direction,
|
||||
entry_price=entry_price,
|
||||
exit_price=exit_price,
|
||||
volume=volume,
|
||||
profit=profit,
|
||||
profit_pips=profit_pips,
|
||||
entry_time=entry_time.isoformat(),
|
||||
exit_time=exit_time.isoformat(),
|
||||
duration_minutes=duration,
|
||||
session=session,
|
||||
base_confidence=base_confidence,
|
||||
enhanced_score=enhanced_score,
|
||||
hybrid_score=hybrid_score,
|
||||
signal_quality=signal_quality,
|
||||
equity_curve_multiplier=equity_curve_multiplier,
|
||||
is_win=is_win,
|
||||
stop_loss=stop_loss,
|
||||
take_profit=take_profit,
|
||||
close_reason=close_reason
|
||||
)
|
||||
|
||||
# Add to data
|
||||
self.data['trades'].append(asdict(record))
|
||||
self.data['last_updated'] = datetime.now().isoformat()
|
||||
|
||||
# Update running stats
|
||||
self._update_stats()
|
||||
|
||||
# Save
|
||||
self._save_data()
|
||||
|
||||
# Log
|
||||
emoji = "🟢" if is_win else "🔴"
|
||||
logger.info(f"📊 Trade logged: {emoji} #{ticket} {direction} {symbol} | Profit: ${profit:.2f}")
|
||||
|
||||
return record
|
||||
|
||||
def log_trade_from_mt5(self, position, close_reason: str = "",
|
||||
session: str = "unknown",
|
||||
base_confidence: float = 0.0,
|
||||
enhanced_score: float = 0.0,
|
||||
hybrid_score: float = 0.0,
|
||||
signal_quality: str = "unknown",
|
||||
equity_curve_multiplier: float = 1.0) -> TradeRecord:
|
||||
"""
|
||||
Loggt Trade direkt von MT5 Position Object
|
||||
"""
|
||||
direction = "LONG" if position.type == 0 else "SHORT"
|
||||
|
||||
return self.log_trade(
|
||||
ticket=position.ticket,
|
||||
symbol=position.symbol,
|
||||
direction=direction,
|
||||
entry_price=position.price_open,
|
||||
exit_price=position.price_current,
|
||||
volume=position.volume,
|
||||
profit=position.profit,
|
||||
entry_time=datetime.fromtimestamp(position.time),
|
||||
exit_time=datetime.now(),
|
||||
session=session,
|
||||
base_confidence=base_confidence,
|
||||
enhanced_score=enhanced_score,
|
||||
hybrid_score=hybrid_score,
|
||||
signal_quality=signal_quality,
|
||||
equity_curve_multiplier=equity_curve_multiplier,
|
||||
stop_loss=position.sl,
|
||||
take_profit=position.tp,
|
||||
close_reason=close_reason
|
||||
)
|
||||
|
||||
def log_error(self, error_type: str, message: str, details: Optional[Dict] = None):
|
||||
"""Loggt einen Fehler/Bug"""
|
||||
error_entry = {
|
||||
'timestamp': datetime.now().isoformat(),
|
||||
'type': error_type,
|
||||
'message': message,
|
||||
'details': details or {}
|
||||
}
|
||||
self.data['errors'].append(error_entry)
|
||||
self._save_data()
|
||||
|
||||
logger.warning(f"📊 Error logged: {error_type} - {message}")
|
||||
|
||||
def log_filtered_signal(self, reason: str, base_confidence: float,
|
||||
enhanced_score: float, hybrid_score: float):
|
||||
"""Loggt ein Signal das gefiltert wurde"""
|
||||
entry = {
|
||||
'timestamp': datetime.now().isoformat(),
|
||||
'reason': reason,
|
||||
'base_confidence': base_confidence,
|
||||
'enhanced_score': enhanced_score,
|
||||
'hybrid_score': hybrid_score
|
||||
}
|
||||
self.data['filtered_signals'].append(entry)
|
||||
self._save_data()
|
||||
|
||||
# ==========================================
|
||||
# STATISTICS
|
||||
# ==========================================
|
||||
|
||||
def _update_stats(self):
|
||||
"""Aktualisiert alle Statistiken"""
|
||||
trades = self.data['trades']
|
||||
|
||||
if not trades:
|
||||
return
|
||||
|
||||
# Basic stats
|
||||
wins = [t for t in trades if t['is_win']]
|
||||
losses = [t for t in trades if not t['is_win']]
|
||||
|
||||
total_profit = sum(t['profit'] for t in trades)
|
||||
total_wins = sum(t['profit'] for t in wins) if wins else 0
|
||||
total_losses = abs(sum(t['profit'] for t in losses)) if losses else 0
|
||||
|
||||
# Win Rate
|
||||
win_rate = len(wins) / len(trades) if trades else 0
|
||||
|
||||
# Profit Factor
|
||||
profit_factor = total_wins / total_losses if total_losses > 0 else float('inf')
|
||||
|
||||
# Average Win/Loss
|
||||
avg_win = total_wins / len(wins) if wins else 0
|
||||
avg_loss = total_losses / len(losses) if losses else 0
|
||||
|
||||
# Drawdown calculation
|
||||
equity_curve = []
|
||||
running_equity = 0
|
||||
peak_equity = 0
|
||||
max_drawdown = 0
|
||||
|
||||
for t in trades:
|
||||
running_equity += t['profit']
|
||||
equity_curve.append(running_equity)
|
||||
|
||||
if running_equity > peak_equity:
|
||||
peak_equity = running_equity
|
||||
|
||||
drawdown = (peak_equity - running_equity) / peak_equity if peak_equity > 0 else 0
|
||||
if drawdown > max_drawdown:
|
||||
max_drawdown = drawdown
|
||||
|
||||
# Session stats
|
||||
session_stats = defaultdict(lambda: {'trades': 0, 'wins': 0, 'profit': 0})
|
||||
for t in trades:
|
||||
session = t.get('session', 'unknown')
|
||||
session_stats[session]['trades'] += 1
|
||||
session_stats[session]['wins'] += 1 if t['is_win'] else 0
|
||||
session_stats[session]['profit'] += t['profit']
|
||||
|
||||
# Signal quality stats
|
||||
quality_stats = defaultdict(lambda: {'trades': 0, 'wins': 0, 'profit': 0})
|
||||
for t in trades:
|
||||
quality = t.get('signal_quality', 'unknown')
|
||||
quality_stats[quality]['trades'] += 1
|
||||
quality_stats[quality]['wins'] += 1 if t['is_win'] else 0
|
||||
quality_stats[quality]['profit'] += t['profit']
|
||||
|
||||
# Store stats
|
||||
self.data['stats'] = {
|
||||
'total_trades': len(trades),
|
||||
'wins': len(wins),
|
||||
'losses': len(losses),
|
||||
'win_rate': win_rate,
|
||||
'profit_factor': profit_factor,
|
||||
'total_profit': total_profit,
|
||||
'total_wins': total_wins,
|
||||
'total_losses': total_losses,
|
||||
'avg_win': avg_win,
|
||||
'avg_loss': avg_loss,
|
||||
'avg_trade': total_profit / len(trades) if trades else 0,
|
||||
'max_drawdown': max_drawdown,
|
||||
'current_equity': running_equity,
|
||||
'peak_equity': peak_equity,
|
||||
'sessions_tested': list(session_stats.keys()),
|
||||
'session_stats': dict(session_stats),
|
||||
'quality_stats': dict(quality_stats),
|
||||
'best_trade': max(trades, key=lambda t: t['profit'])['profit'] if trades else 0,
|
||||
'worst_trade': min(trades, key=lambda t: t['profit'])['profit'] if trades else 0,
|
||||
'avg_duration_minutes': sum(t['duration_minutes'] for t in trades) / len(trades) if trades else 0,
|
||||
}
|
||||
|
||||
def get_stats(self) -> Dict:
|
||||
"""Gibt aktuelle Statistiken zurück"""
|
||||
self._update_stats()
|
||||
return self.data['stats']
|
||||
|
||||
# ==========================================
|
||||
# GO-LIVE READINESS CHECK
|
||||
# ==========================================
|
||||
|
||||
def check_go_live_readiness(self) -> Tuple[bool, Dict]:
|
||||
"""
|
||||
Prüft ob alle Kriterien für Go-Live erfüllt sind
|
||||
|
||||
Returns:
|
||||
(is_ready, detailed_check_results)
|
||||
"""
|
||||
self._update_stats()
|
||||
stats = self.data['stats']
|
||||
|
||||
checks = {}
|
||||
|
||||
# 1. Minimum Trades
|
||||
checks['min_trades'] = {
|
||||
'passed': stats['total_trades'] >= self.criteria['min_trades'],
|
||||
'current': stats['total_trades'],
|
||||
'required': self.criteria['min_trades'],
|
||||
'label': f"Trades: {stats['total_trades']}/{self.criteria['min_trades']}"
|
||||
}
|
||||
|
||||
# 2. Win Rate
|
||||
checks['win_rate'] = {
|
||||
'passed': stats['win_rate'] >= self.criteria['min_win_rate'],
|
||||
'current': stats['win_rate'],
|
||||
'required': self.criteria['min_win_rate'],
|
||||
'label': f"Win Rate: {stats['win_rate']*100:.1f}% (min {self.criteria['min_win_rate']*100:.0f}%)"
|
||||
}
|
||||
|
||||
# 3. Profit Factor
|
||||
pf = stats['profit_factor'] if stats['profit_factor'] != float('inf') else 999
|
||||
checks['profit_factor'] = {
|
||||
'passed': pf >= self.criteria['min_profit_factor'],
|
||||
'current': pf,
|
||||
'required': self.criteria['min_profit_factor'],
|
||||
'label': f"Profit Factor: {pf:.2f} (min {self.criteria['min_profit_factor']:.1f})"
|
||||
}
|
||||
|
||||
# 4. Max Drawdown
|
||||
checks['max_drawdown'] = {
|
||||
'passed': stats['max_drawdown'] <= self.criteria['max_drawdown'],
|
||||
'current': stats['max_drawdown'],
|
||||
'required': self.criteria['max_drawdown'],
|
||||
'label': f"Max Drawdown: {stats['max_drawdown']*100:.1f}% (max {self.criteria['max_drawdown']*100:.0f}%)"
|
||||
}
|
||||
|
||||
# 5. Days Running
|
||||
days = self._days_running()
|
||||
checks['min_days'] = {
|
||||
'passed': days >= self.criteria['min_days'],
|
||||
'current': days,
|
||||
'required': self.criteria['min_days'],
|
||||
'label': f"Days Running: {days}/{self.criteria['min_days']}"
|
||||
}
|
||||
|
||||
# 6. Errors
|
||||
error_count = len(self.data['errors'])
|
||||
checks['max_errors'] = {
|
||||
'passed': error_count <= self.criteria['max_errors'],
|
||||
'current': error_count,
|
||||
'required': self.criteria['max_errors'],
|
||||
'label': f"Errors: {error_count} (max {self.criteria['max_errors']})"
|
||||
}
|
||||
|
||||
# 7. Sessions Tested
|
||||
sessions = len(stats.get('sessions_tested', []))
|
||||
checks['sessions_tested'] = {
|
||||
'passed': sessions >= self.criteria['min_sessions_tested'],
|
||||
'current': sessions,
|
||||
'required': self.criteria['min_sessions_tested'],
|
||||
'label': f"Sessions Tested: {sessions}/{self.criteria['min_sessions_tested']}"
|
||||
}
|
||||
|
||||
# Overall
|
||||
all_passed = all(c['passed'] for c in checks.values())
|
||||
passed_count = sum(1 for c in checks.values() if c['passed'])
|
||||
|
||||
return all_passed, {
|
||||
'checks': checks,
|
||||
'passed_count': passed_count,
|
||||
'total_checks': len(checks),
|
||||
'is_ready': all_passed
|
||||
}
|
||||
|
||||
# ==========================================
|
||||
# REPORTS
|
||||
# ==========================================
|
||||
|
||||
def print_report(self):
|
||||
"""Druckt einen vollständigen Report"""
|
||||
self._update_stats()
|
||||
stats = self.data['stats']
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("📊 DEMO TEST TRACKER - PERFORMANCE REPORT")
|
||||
print("=" * 70)
|
||||
|
||||
print(f"\n📅 Demo Period:")
|
||||
print(f" Start: {self.data['start_date'][:10]}")
|
||||
print(f" Days Running: {self._days_running()}")
|
||||
print(f" Last Trade: {self.data.get('last_updated', 'N/A')[:10] if self.data.get('last_updated') else 'N/A'}")
|
||||
|
||||
print(f"\n📈 PERFORMANCE METRICS:")
|
||||
print(f" Total Trades: {stats.get('total_trades', 0)}")
|
||||
print(f" Wins/Losses: {stats.get('wins', 0)}/{stats.get('losses', 0)}")
|
||||
print(f" Win Rate: {stats.get('win_rate', 0)*100:.1f}%")
|
||||
print(f" Profit Factor: {stats.get('profit_factor', 0):.2f}")
|
||||
|
||||
print(f"\n💰 PROFIT/LOSS:")
|
||||
print(f" Total Profit: ${stats.get('total_profit', 0):,.2f}")
|
||||
print(f" Avg Win: ${stats.get('avg_win', 0):,.2f}")
|
||||
print(f" Avg Loss: ${stats.get('avg_loss', 0):,.2f}")
|
||||
print(f" Avg Trade: ${stats.get('avg_trade', 0):,.2f}")
|
||||
print(f" Best Trade: ${stats.get('best_trade', 0):,.2f}")
|
||||
print(f" Worst Trade: ${stats.get('worst_trade', 0):,.2f}")
|
||||
|
||||
print(f"\n📉 RISK METRICS:")
|
||||
print(f" Max Drawdown: {stats.get('max_drawdown', 0)*100:.1f}%")
|
||||
print(f" Current Equity: ${stats.get('current_equity', 0):,.2f}")
|
||||
print(f" Peak Equity: ${stats.get('peak_equity', 0):,.2f}")
|
||||
|
||||
print(f"\n⏱️ TIMING:")
|
||||
print(f" Avg Duration: {stats.get('avg_duration_minutes', 0):.0f} min")
|
||||
|
||||
# Session breakdown
|
||||
session_stats = stats.get('session_stats', {})
|
||||
if session_stats:
|
||||
print(f"\n🌍 SESSION BREAKDOWN:")
|
||||
for session, data in session_stats.items():
|
||||
wr = data['wins'] / data['trades'] * 100 if data['trades'] > 0 else 0
|
||||
print(f" {session.upper():10} | Trades: {data['trades']:3} | Win Rate: {wr:5.1f}% | Profit: ${data['profit']:,.2f}")
|
||||
|
||||
# Signal quality breakdown
|
||||
quality_stats = stats.get('quality_stats', {})
|
||||
if quality_stats:
|
||||
print(f"\n🎯 SIGNAL QUALITY BREAKDOWN:")
|
||||
for quality, data in quality_stats.items():
|
||||
wr = data['wins'] / data['trades'] * 100 if data['trades'] > 0 else 0
|
||||
print(f" {quality.upper():10} | Trades: {data['trades']:3} | Win Rate: {wr:5.1f}% | Profit: ${data['profit']:,.2f}")
|
||||
|
||||
# Errors
|
||||
if self.data['errors']:
|
||||
print(f"\n⚠️ ERRORS ({len(self.data['errors'])} total):")
|
||||
for err in self.data['errors'][-5:]: # Last 5
|
||||
print(f" {err['timestamp'][:10]} | {err['type']}: {err['message'][:50]}")
|
||||
|
||||
# Filtered signals
|
||||
filtered = len(self.data.get('filtered_signals', []))
|
||||
if filtered > 0:
|
||||
print(f"\n🚫 Filtered Signals: {filtered}")
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
|
||||
def print_go_live_check(self):
|
||||
"""Druckt den Go-Live Readiness Check"""
|
||||
is_ready, results = self.check_go_live_readiness()
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("🚦 GO-LIVE READINESS CHECK")
|
||||
print("=" * 70)
|
||||
|
||||
for name, check in results['checks'].items():
|
||||
emoji = "✅" if check['passed'] else "⬜"
|
||||
print(f" {emoji} {check['label']}")
|
||||
|
||||
print("-" * 70)
|
||||
print(f" Passed: {results['passed_count']}/{results['total_checks']}")
|
||||
|
||||
if is_ready:
|
||||
print("\n 🎉 STATUS: READY FOR GO-LIVE!")
|
||||
print(" ✅ All criteria met. You can start with small real money.")
|
||||
else:
|
||||
remaining = [c['label'] for c in results['checks'].values() if not c['passed']]
|
||||
print(f"\n ⏳ STATUS: NOT READY YET")
|
||||
print(f" Still needed:")
|
||||
for r in remaining:
|
||||
print(f" • {r}")
|
||||
|
||||
print("=" * 70)
|
||||
|
||||
return is_ready
|
||||
|
||||
def get_daily_summary(self) -> str:
|
||||
"""Generiert eine tägliche Zusammenfassung"""
|
||||
self._update_stats()
|
||||
stats = self.data['stats']
|
||||
|
||||
today = datetime.now().date().isoformat()
|
||||
today_trades = [t for t in self.data['trades']
|
||||
if t['exit_time'].startswith(today)]
|
||||
|
||||
today_profit = sum(t['profit'] for t in today_trades)
|
||||
today_wins = sum(1 for t in today_trades if t['is_win'])
|
||||
today_wr = today_wins / len(today_trades) * 100 if today_trades else 0
|
||||
|
||||
summary = f"""
|
||||
📊 Daily Summary - {today}
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Today: {len(today_trades)} trades | {today_wins} wins | WR: {today_wr:.0f}% | P/L: ${today_profit:+.2f}
|
||||
Overall: {stats['total_trades']} trades | WR: {stats['win_rate']*100:.1f}% | Total: ${stats['total_profit']:+.2f}
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
"""
|
||||
return summary
|
||||
|
||||
# ==========================================
|
||||
# HELPER METHODS
|
||||
# ==========================================
|
||||
|
||||
def _days_running(self) -> int:
|
||||
"""Berechnet Tage seit Start"""
|
||||
start = datetime.fromisoformat(self.data['start_date'])
|
||||
return (datetime.now() - start).days
|
||||
|
||||
def _load_data(self) -> Dict:
|
||||
"""Lädt Daten aus Datei"""
|
||||
default_data = {
|
||||
'start_date': datetime.now().isoformat(),
|
||||
'last_updated': None,
|
||||
'trades': [],
|
||||
'errors': [],
|
||||
'filtered_signals': [],
|
||||
'stats': {}
|
||||
}
|
||||
|
||||
try:
|
||||
if os.path.exists(self.data_file):
|
||||
with open(self.data_file, 'r') as f:
|
||||
data = json.load(f)
|
||||
# Merge with defaults for any missing keys
|
||||
for key in default_data:
|
||||
if key not in data:
|
||||
data[key] = default_data[key]
|
||||
return data
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not load demo tracker data: {e}")
|
||||
|
||||
return default_data
|
||||
|
||||
def _save_data(self):
|
||||
"""Speichert Daten in Datei"""
|
||||
try:
|
||||
with open(self.data_file, 'w') as f:
|
||||
json.dump(self.data, f, indent=2, default=str)
|
||||
except Exception as e:
|
||||
logger.error(f"Could not save demo tracker data: {e}")
|
||||
|
||||
def reset(self):
|
||||
"""Setzt alle Daten zurück (Vorsicht!)"""
|
||||
self.data = {
|
||||
'start_date': datetime.now().isoformat(),
|
||||
'last_updated': None,
|
||||
'trades': [],
|
||||
'errors': [],
|
||||
'filtered_signals': [],
|
||||
'stats': {}
|
||||
}
|
||||
self._save_data()
|
||||
logger.warning("⚠️ Demo tracker data has been reset!")
|
||||
|
||||
|
||||
# ==========================================
|
||||
# STANDALONE USAGE
|
||||
# ==========================================
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("📊 Demo Test Tracker - Demo Mode")
|
||||
print("=" * 50)
|
||||
|
||||
tracker = DemoTestTracker(data_file="demo_test_example.json")
|
||||
|
||||
# Simulate some trades
|
||||
from datetime import timedelta
|
||||
|
||||
base_time = datetime.now() - timedelta(days=10)
|
||||
|
||||
test_trades = [
|
||||
{"profit": 15.50, "direction": "LONG", "session": "ny"},
|
||||
{"profit": -8.20, "direction": "SHORT", "session": "ny"},
|
||||
{"profit": 22.30, "direction": "LONG", "session": "asian"},
|
||||
{"profit": 12.10, "direction": "LONG", "session": "ny"},
|
||||
{"profit": -5.50, "direction": "SHORT", "session": "asian"},
|
||||
{"profit": 18.90, "direction": "LONG", "session": "london"},
|
||||
{"profit": -12.30, "direction": "LONG", "session": "ny"},
|
||||
{"profit": 25.60, "direction": "SHORT", "session": "asian"},
|
||||
{"profit": 8.40, "direction": "LONG", "session": "ny"},
|
||||
{"profit": -3.20, "direction": "SHORT", "session": "london"},
|
||||
]
|
||||
|
||||
for i, t in enumerate(test_trades):
|
||||
entry_time = base_time + timedelta(hours=i*8)
|
||||
exit_time = entry_time + timedelta(minutes=45)
|
||||
|
||||
tracker.log_trade(
|
||||
ticket=1000 + i,
|
||||
symbol="XAUUSD",
|
||||
direction=t["direction"],
|
||||
entry_price=2850.00,
|
||||
exit_price=2850.00 + (t["profit"] / 0.10), # Reverse calculate
|
||||
volume=0.10,
|
||||
profit=t["profit"],
|
||||
entry_time=entry_time,
|
||||
exit_time=exit_time,
|
||||
session=t["session"],
|
||||
base_confidence=75.0,
|
||||
enhanced_score=68.0,
|
||||
hybrid_score=72.2,
|
||||
signal_quality="good"
|
||||
)
|
||||
|
||||
# Print reports
|
||||
tracker.print_report()
|
||||
tracker.print_go_live_check()
|
||||
|
||||
# Cleanup
|
||||
os.remove("demo_test_example.json")
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"timestamp": "2026-01-26T00:27:01.306793",
|
||||
"timestamp": "2026-01-27T00:00:00.107097",
|
||||
"session_thresholds": {
|
||||
"asian": 60,
|
||||
"ny": 60,
|
||||
"london": 75,
|
||||
"overlap": 65
|
||||
"london": 60,
|
||||
"overlap": 60
|
||||
},
|
||||
"settings": {
|
||||
"lookback_trades": 20,
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
[
|
||||
{
|
||||
"timestamp": "2026-01-26T12:13:46.930465",
|
||||
"equity": 9022.43,
|
||||
"trade_count": 1
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T12:19:46.966901",
|
||||
"equity": 8957.23,
|
||||
"trade_count": 2
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T13:28:47.062937",
|
||||
"equity": 9041.93,
|
||||
"trade_count": 3
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T13:42:46.844143",
|
||||
"equity": 8972.23,
|
||||
"trade_count": 4
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T14:02:47.503834",
|
||||
"equity": 8896.53,
|
||||
"trade_count": 5
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T14:19:46.495217",
|
||||
"equity": 8945.63,
|
||||
"trade_count": 6
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T14:23:47.782515",
|
||||
"equity": 8863.63,
|
||||
"trade_count": 7
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T14:30:47.450110",
|
||||
"equity": 8775.53,
|
||||
"trade_count": 8
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T14:57:47.581206",
|
||||
"equity": 8829.93,
|
||||
"trade_count": 9
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T15:43:47.029833",
|
||||
"equity": 8956.33,
|
||||
"trade_count": 10
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T16:01:47.190356",
|
||||
"equity": 9035.33,
|
||||
"trade_count": 11
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T16:16:50.346575",
|
||||
"equity": 9058.73,
|
||||
"trade_count": 12
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T16:35:47.053141",
|
||||
"equity": 8938.53,
|
||||
"trade_count": 13
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T17:20:48.048421",
|
||||
"equity": 9083.63,
|
||||
"trade_count": 14
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T17:28:50.189681",
|
||||
"equity": 8968.73,
|
||||
"trade_count": 15
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T17:39:47.578487",
|
||||
"equity": 8860.43,
|
||||
"trade_count": 16
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T18:25:28.537885",
|
||||
"equity": 9031.93,
|
||||
"trade_count": 17
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T19:07:29.047627",
|
||||
"equity": 9105.43,
|
||||
"trade_count": 18
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T19:12:28.985632",
|
||||
"equity": 9000.29,
|
||||
"trade_count": 19
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T19:36:28.604004",
|
||||
"equity": 8894.25,
|
||||
"trade_count": 20
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T19:41:28.577100",
|
||||
"equity": 8831.2,
|
||||
"trade_count": 21
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T19:58:27.699220",
|
||||
"equity": 8866.25,
|
||||
"trade_count": 22
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T20:02:28.281716",
|
||||
"equity": 8798.45,
|
||||
"trade_count": 23
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T20:11:30.929472",
|
||||
"equity": 8821.55,
|
||||
"trade_count": 24
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T20:21:28.550560",
|
||||
"equity": 8735.3,
|
||||
"trade_count": 25
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T20:48:32.186217",
|
||||
"equity": 8640.1,
|
||||
"trade_count": 26
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T21:33:27.589864",
|
||||
"equity": 8891.2,
|
||||
"trade_count": 27
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T21:37:29.173485",
|
||||
"equity": 8740.18,
|
||||
"trade_count": 28
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T22:10:27.827652",
|
||||
"equity": 8759.33,
|
||||
"trade_count": 29
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T22:16:28.542996",
|
||||
"equity": 8683.68,
|
||||
"trade_count": 30
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T22:23:29.348033",
|
||||
"equity": 8595.38,
|
||||
"trade_count": 31
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-26T22:36:27.686118",
|
||||
"equity": 8501.08,
|
||||
"trade_count": 32
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-27T00:09:28.170412",
|
||||
"equity": 8726.46,
|
||||
"trade_count": 33
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-27T00:59:27.666816",
|
||||
"equity": 8820.68,
|
||||
"trade_count": 34
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-27T01:43:27.684421",
|
||||
"equity": 8681.88,
|
||||
"trade_count": 35
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-27T01:51:29.464065",
|
||||
"equity": 8617.73,
|
||||
"trade_count": 36
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-27T02:00:31.411314",
|
||||
"equity": 8671.03,
|
||||
"trade_count": 37
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-27T02:12:28.296523",
|
||||
"equity": 8817.38,
|
||||
"trade_count": 38
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-27T02:33:27.507351",
|
||||
"equity": 8851.98,
|
||||
"trade_count": 39
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-27T03:55:28.891918",
|
||||
"equity": 8881.98,
|
||||
"trade_count": 40
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-27T05:11:28.289256",
|
||||
"equity": 8908.88,
|
||||
"trade_count": 41
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-27T05:42:28.758925",
|
||||
"equity": 8821.98,
|
||||
"trade_count": 42
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-27T06:02:29.934062",
|
||||
"equity": 8853.28,
|
||||
"trade_count": 43
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-27T06:48:27.496834",
|
||||
"equity": 9047.68,
|
||||
"trade_count": 44
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-27T07:56:27.536971",
|
||||
"equity": 9096.08,
|
||||
"trade_count": 45
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-27T08:13:28.986584",
|
||||
"equity": 9132.98,
|
||||
"trade_count": 46
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-27T08:45:27.690803",
|
||||
"equity": 9193.48,
|
||||
"trade_count": 47
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-27T09:00:28.478655",
|
||||
"equity": 9088.98,
|
||||
"trade_count": 48
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-27T09:29:27.489522",
|
||||
"equity": 9005.74,
|
||||
"trade_count": 49
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-27T09:52:30.080743",
|
||||
"equity": 9028.68,
|
||||
"trade_count": 50
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-27T10:46:29.927974",
|
||||
"equity": 9076.28,
|
||||
"trade_count": 51
|
||||
}
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user