Created comprehensive database maintenance tools: 1. database_cleanup.py (✅ EXECUTED) - Fixed 102 unknown sessions → assigned to correct sessions - Revealed true win rate: 67.8% (not 18.5%!) - Created automatic backup before changes 2. cleanup_historical_trades.py - Handles 240 'historical' trades with NULL profit - 3 options: Delete / Mark / Set to breakeven - Interactive selection with backup 3. setup_automated_backup.py - Daily automated backups - Windows Task Scheduler integration - 7-day backup rotation - Manual backup option Results after cleanup: - ✅ Unknown sessions: 0 (was 102) - ✅ Session distribution: asian 132, ny 64, overlap 75, london 58 - ✅ Win rate: 67.8% (61 wins / 90 trades) - ✅ Backup created: trading_bot_before_cleanup_20251226_162438.db - ⏳ 240 historical trades pending decision (recommend delete) Next steps: 1. Run cleanup_historical_trades.py (option 1: delete) 2. Setup automated backups via Task Scheduler 3. Re-analyze performance with correct session data
322 lines
9.2 KiB
Python
322 lines
9.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
🔧 Database Cleanup Script
|
|
Behebt Daten-Inkonsistenzen in trading_bot.db
|
|
"""
|
|
|
|
import sqlite3
|
|
import shutil
|
|
from datetime import datetime
|
|
import os
|
|
|
|
# ==========================================
|
|
# CONFIGURATION
|
|
# ==========================================
|
|
|
|
DB_PATH = "trading_bot.db"
|
|
BACKUP_DIR = "backups"
|
|
|
|
# ==========================================
|
|
# BACKUP FUNCTION
|
|
# ==========================================
|
|
|
|
def create_backup():
|
|
"""Erstellt Backup vor Cleanup"""
|
|
if not os.path.exists(BACKUP_DIR):
|
|
os.makedirs(BACKUP_DIR)
|
|
|
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
backup_path = f"{BACKUP_DIR}/trading_bot_before_cleanup_{timestamp}.db"
|
|
|
|
shutil.copy(DB_PATH, backup_path)
|
|
print(f"✅ Backup erstellt: {backup_path}")
|
|
return backup_path
|
|
|
|
# ==========================================
|
|
# CLEANUP FUNCTIONS
|
|
# ==========================================
|
|
|
|
def fix_session_unknown(conn):
|
|
"""
|
|
Behebt 'unknown' Sessions basierend auf entry_time
|
|
|
|
Sessions (UTC):
|
|
- Asian: 23:00-08:00
|
|
- London: 08:00-16:00
|
|
- NY: 13:00-22:00
|
|
- Overlap: 13:00-16:00
|
|
"""
|
|
print("\n" + "="*80)
|
|
print("1️⃣ FIXING UNKNOWN SESSIONS")
|
|
print("="*80)
|
|
|
|
cursor = conn.cursor()
|
|
|
|
# Hole alle unknown session trades
|
|
cursor.execute("""
|
|
SELECT id, ticket, entry_time
|
|
FROM trades
|
|
WHERE session = 'unknown' OR session IS NULL
|
|
""")
|
|
|
|
unknown_trades = cursor.fetchall()
|
|
print(f"Gefunden: {len(unknown_trades)} Trades mit unknown session")
|
|
|
|
fixed = 0
|
|
for trade_id, ticket, entry_time in unknown_trades:
|
|
# Parse entry_time
|
|
dt = datetime.fromisoformat(entry_time.replace('Z', '+00:00'))
|
|
hour = dt.hour
|
|
|
|
# Bestimme Session basierend auf UTC Hour
|
|
if 23 <= hour or hour < 8:
|
|
session = 'asian'
|
|
elif 8 <= hour < 13:
|
|
session = 'london'
|
|
elif 13 <= hour < 16:
|
|
session = 'overlap'
|
|
elif 16 <= hour < 22:
|
|
session = 'ny'
|
|
else:
|
|
session = 'ny' # 22-23 = NY tail
|
|
|
|
# Update
|
|
cursor.execute("""
|
|
UPDATE trades
|
|
SET session = ?
|
|
WHERE id = ?
|
|
""", (session, trade_id))
|
|
fixed += 1
|
|
|
|
conn.commit()
|
|
print(f"✅ Fixed: {fixed} Sessions")
|
|
|
|
# Verify
|
|
cursor.execute("SELECT COUNT(*) FROM trades WHERE session = 'unknown'")
|
|
remaining = cursor.fetchone()[0]
|
|
print(f"Verbleibend: {remaining} unknown sessions")
|
|
|
|
def fix_null_profits(conn):
|
|
"""
|
|
Analysiert und behebt NULL profits
|
|
|
|
Problem: 240 'historical' Trades haben NULL profit
|
|
Vermutlich: Alte Trades die nicht korrekt migriert wurden
|
|
"""
|
|
print("\n" + "="*80)
|
|
print("2️⃣ FIXING NULL PROFITS")
|
|
print("="*80)
|
|
|
|
cursor = conn.cursor()
|
|
|
|
# Hole alle NULL profit trades
|
|
cursor.execute("""
|
|
SELECT id, ticket, entry_price, exit_price, volume, status
|
|
FROM trades
|
|
WHERE net_profit IS NULL
|
|
""")
|
|
|
|
null_trades = cursor.fetchall()
|
|
print(f"Gefunden: {len(null_trades)} Trades mit NULL profit")
|
|
|
|
# Analyse: Warum NULL?
|
|
cursor.execute("""
|
|
SELECT
|
|
COUNT(*) as total,
|
|
SUM(CASE WHEN entry_price = 0 THEN 1 ELSE 0 END) as zero_entry,
|
|
SUM(CASE WHEN exit_price IS NULL THEN 1 ELSE 0 END) as no_exit,
|
|
SUM(CASE WHEN status = 'historical' THEN 1 ELSE 0 END) as historical
|
|
FROM trades
|
|
WHERE net_profit IS NULL
|
|
""")
|
|
|
|
stats = cursor.fetchone()
|
|
print(f"\nAnalyse:")
|
|
print(f" Total NULL profits: {stats[0]}")
|
|
print(f" Entry Price = 0: {stats[1]}")
|
|
print(f" Keine Exit Price: {stats[2]}")
|
|
print(f" Status = historical: {stats[3]}")
|
|
|
|
# Decision
|
|
print("\n⚠️ ENTSCHEIDUNG NÖTIG:")
|
|
print(" Option 1: Alle 'historical' Trades mit NULL profit LÖSCHEN")
|
|
print(" Option 2: Profit = 0 setzen (als Breakeven behandeln)")
|
|
print(" Option 3: Trades behalten wie sie sind (ignorieren)")
|
|
|
|
# Für jetzt: Option 3 (safe)
|
|
print("\n➡️ AKTION: Trades werden markiert aber NICHT gelöscht")
|
|
print(" Grund: Vermutlich alte Migrations-Daten")
|
|
print(" Empfehlung: Manuell reviewen und entscheiden")
|
|
|
|
# Markiere sie in einem neuen Feld (falls gewünscht)
|
|
# Für jetzt: Nur Info
|
|
|
|
def fix_status_field(conn):
|
|
"""
|
|
Analysiert Status-Field und fügt win/loss Klassifikation hinzu
|
|
|
|
Aktuell:
|
|
- 'closed' = Trade ist abgeschlossen
|
|
- 'historical' = Alte Trades
|
|
|
|
Wir brauchen: win/loss Status basierend auf net_profit
|
|
"""
|
|
print("\n" + "="*80)
|
|
print("3️⃣ STATUS FIELD ANALYSE")
|
|
print("="*80)
|
|
|
|
cursor = conn.cursor()
|
|
|
|
# Check ob exit_reason Feld existiert
|
|
cursor.execute("PRAGMA table_info(trades)")
|
|
columns = [col[1] for col in cursor.fetchall()]
|
|
|
|
print(f"Verfügbare Felder: {', '.join(columns)}")
|
|
|
|
# Count by status
|
|
cursor.execute("""
|
|
SELECT
|
|
status,
|
|
COUNT(*) as count,
|
|
SUM(CASE WHEN net_profit > 0 THEN 1 ELSE 0 END) as wins,
|
|
SUM(CASE WHEN net_profit < 0 THEN 1 ELSE 0 END) as losses,
|
|
SUM(CASE WHEN net_profit IS NULL THEN 1 ELSE 0 END) as nulls
|
|
FROM trades
|
|
GROUP BY status
|
|
""")
|
|
|
|
results = cursor.fetchall()
|
|
print("\nStatus Breakdown:")
|
|
for row in results:
|
|
status, count, wins, losses, nulls = row
|
|
print(f" {status:12} | Total: {count:3} | Wins: {wins:3} | Losses: {losses:3} | NULL: {nulls:3}")
|
|
|
|
# Info: exit_reason kann verwendet werden um win/loss zu tracken
|
|
print("\n💡 INFO:")
|
|
print(" - 'closed' Trades haben net_profit (wins/losses)")
|
|
print(" - 'historical' Trades haben NULL profit (alte Daten)")
|
|
print(" - exit_reason Feld kann für Klassifikation genutzt werden")
|
|
|
|
def add_backup_automation(conn):
|
|
"""
|
|
Info über Backup-Automation
|
|
"""
|
|
print("\n" + "="*80)
|
|
print("4️⃣ BACKUP AUTOMATION SETUP")
|
|
print("="*80)
|
|
|
|
print("Empfehlung: Tägliche automatische Backups")
|
|
print("\nMöglichkeiten:")
|
|
print(" 1. Windows Task Scheduler (täglich um 00:00)")
|
|
print(" 2. Python Script mit Scheduler")
|
|
print(" 3. Manuell vor wichtigen Änderungen")
|
|
print("\nAktuell: Backup vor jedem Cleanup (manuell)")
|
|
|
|
def verify_fixes(conn):
|
|
"""
|
|
Verifiziert die durchgeführten Fixes
|
|
"""
|
|
print("\n" + "="*80)
|
|
print("5️⃣ VERIFICATION")
|
|
print("="*80)
|
|
|
|
cursor = conn.cursor()
|
|
|
|
# Check unknown sessions
|
|
cursor.execute("SELECT COUNT(*) FROM trades WHERE session = 'unknown'")
|
|
unknown = cursor.fetchone()[0]
|
|
print(f"Unknown Sessions: {unknown} (Ziel: 0)")
|
|
|
|
# Check session distribution
|
|
cursor.execute("""
|
|
SELECT session, COUNT(*) as count
|
|
FROM trades
|
|
GROUP BY session
|
|
ORDER BY count DESC
|
|
""")
|
|
print("\nSession Distribution:")
|
|
for session, count in cursor.fetchall():
|
|
print(f" {session:10} {count:3} Trades")
|
|
|
|
# Check NULL profits
|
|
cursor.execute("SELECT COUNT(*) FROM trades WHERE net_profit IS NULL")
|
|
null_profits = cursor.fetchone()[0]
|
|
print(f"\nNULL Profits: {null_profits}")
|
|
|
|
# Performance nach Cleanup
|
|
cursor.execute("""
|
|
SELECT
|
|
COUNT(*) as total,
|
|
SUM(CASE WHEN net_profit > 0 THEN 1 ELSE 0 END) as wins,
|
|
SUM(CASE WHEN net_profit < 0 THEN 1 ELSE 0 END) as losses,
|
|
ROUND(AVG(CASE WHEN net_profit > 0 THEN net_profit END), 2) as avg_win,
|
|
ROUND(AVG(CASE WHEN net_profit < 0 THEN net_profit END), 2) as avg_loss
|
|
FROM trades
|
|
WHERE net_profit IS NOT NULL
|
|
""")
|
|
|
|
total, wins, losses, avg_win, avg_loss = cursor.fetchone()
|
|
if total > 0:
|
|
win_rate = (wins / total * 100) if total > 0 else 0
|
|
print(f"\nPerformance (nur Trades mit Profit-Daten):")
|
|
print(f" Total: {total}")
|
|
print(f" Wins: {wins} ({win_rate:.1f}%)")
|
|
print(f" Losses: {losses}")
|
|
print(f" Avg Win: ${avg_win}")
|
|
print(f" Avg Loss: ${avg_loss}")
|
|
|
|
# ==========================================
|
|
# MAIN
|
|
# ==========================================
|
|
|
|
def main():
|
|
print("="*80)
|
|
print("🔧 DATABASE CLEANUP SCRIPT")
|
|
print("="*80)
|
|
print()
|
|
|
|
# Check if DB exists
|
|
if not os.path.exists(DB_PATH):
|
|
print(f"❌ ERROR: {DB_PATH} nicht gefunden!")
|
|
return
|
|
|
|
print(f"Database: {DB_PATH}")
|
|
print(f"Size: {os.path.getsize(DB_PATH) / 1024:.2f} KB")
|
|
print()
|
|
|
|
# Create backup
|
|
backup_path = create_backup()
|
|
|
|
# Connect
|
|
conn = sqlite3.connect(DB_PATH)
|
|
|
|
try:
|
|
# Run cleanup functions
|
|
fix_session_unknown(conn)
|
|
fix_null_profits(conn)
|
|
fix_status_field(conn)
|
|
add_backup_automation(conn)
|
|
verify_fixes(conn)
|
|
|
|
print("\n" + "="*80)
|
|
print("✅ CLEANUP ABGESCHLOSSEN")
|
|
print("="*80)
|
|
print(f"\nBackup: {backup_path}")
|
|
print("Database wurde aktualisiert!")
|
|
|
|
except Exception as e:
|
|
print(f"\n❌ ERROR: {e}")
|
|
print("Rollback...")
|
|
conn.rollback()
|
|
|
|
# Restore backup
|
|
print(f"Stelle Backup wieder her: {backup_path}")
|
|
shutil.copy(backup_path, DB_PATH)
|
|
print("✅ Backup wiederhergestellt")
|
|
|
|
finally:
|
|
conn.close()
|
|
|
|
if __name__ == "__main__":
|
|
main()
|