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:
2026-01-27 11:32:38 +01:00
co-authored by Claude Opus 4.5
parent 57b9c31fea
commit 2f521ae0ac
5 changed files with 2626 additions and 16576 deletions
File diff suppressed because it is too large Load Diff
+662
View File
@@ -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")
+3 -3
View File
@@ -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,
+257
View File
@@ -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