feat: Implement Equity Curve Trading for automatic drawdown protection

NEW MODULE: equity_curve_trading.py
- EquityCurveManager class for meta-strategy control
- Tracks equity history after each trade
- Calculates Moving Average over configurable period (default: 10 trades)
- Soft Mode: Reduces lot size to 50% when equity < MA
- Hard Mode: Completely stops trading when equity < MA
- Recovery detection with buffer percentage
- Persistent storage in equity_curve_history.json

CONFIGURATION:
- ma_period: 10 trades (Moving Average window)
- min_trades_required: 5 (warmup period)
- soft_mode: True (reduce lots instead of stopping)
- soft_mode_multiplier: 0.5 (50% lots when under MA)
- recovery_buffer_pct: 0.5% (buffer for recovery status)

INTEGRATION:
- Added to Cell 78 (Advanced Optimizations setup)
- Integrated in enhanced_trading_check_wrapper (Cells 85, 90)
- Added lot_multiplier parameter to execute_trade_v2_adaptive
- Equity update after each successful trade

EXAMPLE FLOW:
1. Before trade: Check should_trade() → returns (allowed, reason, lot_multiplier)
2. If equity < MA: lot_multiplier = 0.5 (or 0.0 in hard mode)
3. Position size adjusted: volume = volume * lot_multiplier
4. After trade: update_equity() called to track new equity

BENEFITS:
- Automatic protection during losing streaks
- Reduces exposure when strategy underperforms
- Capitalizes fully when strategy is working
- No emotional decisions needed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-26 10:54:29 +01:00
co-authored by Claude Opus 4.5
parent 96f259aed6
commit c51862c4ec
5 changed files with 1828 additions and 7 deletions
@@ -1230,7 +1230,9 @@
" debug=True,\n",
" # Enhanced Scoring Overrides\n",
" signal_info_override=None,\n",
" confidence_override=None\n",
" confidence_override=None,\n",
" # Equity Curve Trading\n",
" lot_multiplier=1.0\n",
"):\n",
" \"\"\"\n",
" V1.6 Adaptive Complete Trade-Ausführung:\n",
@@ -1351,6 +1353,13 @@
" else:\n",
" volume = TRADING_CONFIG[\"lot_sizing\"][\"default_lot\"]\n",
" \n",
" # Apply Equity Curve lot multiplier\n",
" if lot_multiplier != 1.0:\n",
" original_volume = volume\n",
" volume = round(volume * lot_multiplier, 2)\n",
" volume = max(TRADING_CONFIG[\"lot_sizing\"][\"min_lot\"], volume) # Ensure minimum\n",
" print(f\"📈 Equity Curve: Lot adjusted {original_volume:.2f} → {volume:.2f} ({lot_multiplier:.0%})\")\n",
" \n",
" # Log Trade Info\n",
" print(f\"\\n🚀 V1.6 ADAPTIVE COMPLETE TRADE EXECUTION\")\n",
" print(f\"Direction: {'LONG' if entry_signal == 1 else 'SHORT'}\")\n",
@@ -3011,6 +3020,7 @@
"from dynamic_threshold_optimizer import DynamicThresholdOptimizer, auto_optimize_thresholds\n",
"from enhanced_signal_scoring import EnhancedSignalScorer\n",
"from enhanced_trailing_stop import EnhancedTrailingStopManager, create_enhanced_position_monitor\n",
"from equity_curve_trading import EquityCurveManager\n",
"\n",
"print(\"🚀 INITIALIZING ADVANCED OPTIMIZATIONS...\")\n",
"print(\"=\" * 70)\n",
@@ -3075,6 +3085,18 @@
"print(\"✅ Enhanced Trailing Stop Manager initialized\")\n",
"print()\n",
"\n",
"# 4. Equity Curve Trading\n",
"equity_curve_manager = EquityCurveManager(\n",
" ma_period=10, # MA über 10 Trades\n",
" min_trades_required=5, # Warmup: 5 Trades\n",
" soft_mode=True, # Reduzierte Lots statt Stop\n",
" soft_mode_multiplier=0.5, # 50% Lots wenn unter MA\n",
" recovery_buffer_pct=0.5, # 0.5% über MA = Recovery\n",
" data_file=\"equity_curve_history.json\"\n",
")\n",
"print(\"✅ Equity Curve Manager initialized\")\n",
"print()\n",
"\n",
"# 4. Run initial threshold optimization\n",
"print(\"🔄 Running initial threshold optimization...\")\n",
"try:\n",
@@ -3092,6 +3114,7 @@
"print(\" • Dynamic Thresholds: ✅ (auto-adjusts daily)\")\n",
"print(\" • Enhanced Scoring: ✅ (5-factor analysis)\")\n",
"print(\" • Enhanced Trailing: ✅ (multi-tier protection)\")\n",
"print(\" • Equity Curve Trading: ✅ (auto-pause on drawdown)\")\n",
"print()\n",
"print(\"💡 Tip: Use 'threshold_optimizer.generate_report()' for details\")"
]
@@ -3362,6 +3385,14 @@
"\n",
" print(f\"✅ Position-Check OK: {position_info['count']}/{max_positions}\")\n",
"\n",
" # SCHRITT 1.5: EQUITY CURVE CHECK\n",
" ec_allowed, ec_reason, lot_multiplier = equity_curve_manager.should_trade()\n",
" print(f\"📈 Equity Curve: {ec_reason}\")\n",
" \n",
" if not ec_allowed:\n",
" print(f\"⛔ TRADE BLOCKIERT durch Equity Curve Filter\")\n",
" return None\n",
"\n",
" # SCHRITT 2: Signal Analysis (wie vorher)\n",
" signal_info = extended_top_down_v2_adaptive(symbol)\n",
" if signal_info is None:\n",
@@ -3424,9 +3455,15 @@
" result = execute_trade_v2_adaptive(\n",
" symbol=symbol,\n",
" signal_info_override=signal_info,\n",
" confidence_override=final_confidence # ← Use hybrid score!\n",
" confidence_override=final_confidence, # ← Use hybrid score!\n",
" lot_multiplier=lot_multiplier # ← Equity Curve adjustment\n",
" )\n",
" \n",
" # Update Equity Curve nach Trade\n",
" if result is not None:\n",
" equity_curve_manager.update_equity()\n",
" print(f\"📈 Equity Curve updated\")\n",
"\n",
" return result\n",
" else:\n",
" print(f\"\\n❌ Signal below threshold: {final_confidence:.1f}% < {adaptive_threshold:.1f}%\")\n",
@@ -3629,6 +3666,14 @@
"\n",
" print(f\"✅ Position-Check OK: {position_info['count']}/{max_positions}\")\n",
"\n",
" # SCHRITT 1.5: EQUITY CURVE CHECK\n",
" ec_allowed, ec_reason, lot_multiplier = equity_curve_manager.should_trade()\n",
" print(f\"📈 Equity Curve: {ec_reason}\")\n",
" \n",
" if not ec_allowed:\n",
" print(f\"⛔ TRADE BLOCKIERT durch Equity Curve Filter\")\n",
" return None\n",
"\n",
" # SCHRITT 2: Signal Analysis (wie vorher)\n",
" signal_info = extended_top_down_v2_adaptive(symbol)\n",
" if signal_info is None:\n",
@@ -3691,9 +3736,15 @@
" result = execute_trade_v2_adaptive(\n",
" symbol=symbol,\n",
" signal_info_override=signal_info,\n",
" confidence_override=final_confidence # ← Use hybrid score!\n",
" confidence_override=final_confidence, # ← Use hybrid score!\n",
" lot_multiplier=lot_multiplier # ← Equity Curve adjustment\n",
" )\n",
" \n",
" # Update Equity Curve nach Trade\n",
" if result is not None:\n",
" equity_curve_manager.update_equity()\n",
" print(f\"📈 Equity Curve updated\")\n",
"\n",
" return result\n",
" else:\n",
" print(f\"\\n❌ Signal below threshold: {final_confidence:.1f}% < {adaptive_threshold:.1f}%\")\n",
+15 -1
View File
@@ -66,6 +66,14 @@ def enhanced_trading_check_wrapper(symbol="XAUUSD", debug=False):
print(f"✅ Position-Check OK: {position_info['count']}/{max_positions}")
# SCHRITT 1.5: EQUITY CURVE CHECK
ec_allowed, ec_reason, lot_multiplier = equity_curve_manager.should_trade()
print(f"📈 Equity Curve: {ec_reason}")
if not ec_allowed:
print(f"⛔ TRADE BLOCKIERT durch Equity Curve Filter")
return None
# SCHRITT 2: Signal Analysis (wie vorher)
signal_info = extended_top_down_v2_adaptive(symbol)
if signal_info is None:
@@ -127,9 +135,15 @@ def enhanced_trading_check_wrapper(symbol="XAUUSD", debug=False):
result = execute_trade_v2_adaptive(
symbol=symbol,
signal_info_override=signal_info,
confidence_override=final_confidence # ← Use hybrid score!
confidence_override=final_confidence, # ← Use hybrid score!
lot_multiplier=lot_multiplier # ← Equity Curve adjustment
)
# Update Equity Curve nach Trade
if result is not None:
equity_curve_manager.update_equity()
print(f"📈 Equity Curve updated")
return result
else:
print(f"\\n❌ Signal below threshold: {final_confidence:.1f}% < {adaptive_threshold:.1f}%")
+3 -3
View File
@@ -1,10 +1,10 @@
{
"timestamp": "2026-01-23T08:15:25.214791",
"timestamp": "2026-01-26T00:27:01.306793",
"session_thresholds": {
"asian": 60,
"ny": 60,
"london": 80,
"overlap": 70
"london": 75,
"overlap": 65
},
"settings": {
"lookback_trades": 20,
+352
View File
@@ -0,0 +1,352 @@
#!/usr/bin/env python3
"""
📈 Equity Curve Trading Module
Meta-Strategy: Trade nur wenn Equity über Moving Average
KONZEPT:
- Trackt Equity-Historie nach jedem Trade
- Berechnet Moving Average der Equity
- Erlaubt Trading nur wenn Equity >= MA
- Reduziert Drawdowns durch automatische Pausen
VERWENDUNG:
from equity_curve_trading import EquityCurveManager
ecm = EquityCurveManager(ma_period=10)
# Vor jedem Trade prüfen:
if ecm.should_trade():
execute_trade(...)
# Nach jedem Trade updaten:
ecm.update_equity()
"""
import json
import os
from datetime import datetime
from typing import List, Dict, Optional, Tuple
import logging
logger = logging.getLogger(__name__)
class EquityCurveManager:
"""
Equity Curve Trading Manager
Features:
- Automatisches Equity-Tracking
- Konfigurierbarer MA-Zeitraum
- Optionaler "Soft Mode" (reduzierte Lots statt Stop)
- Persistente Speicherung der Historie
- Recovery-Erkennung
"""
def __init__(self,
ma_period: int = 10,
min_trades_required: int = 5,
soft_mode: bool = True,
soft_mode_multiplier: float = 0.5,
recovery_buffer_pct: float = 0.5,
data_file: str = "equity_curve_history.json"):
"""
Args:
ma_period: Anzahl der Trades für Moving Average (default: 10)
min_trades_required: Minimum Trades bevor Filter aktiv wird (default: 5)
soft_mode: True = reduzierte Lots, False = komplett stoppen
soft_mode_multiplier: Lot-Multiplikator wenn unter MA (default: 0.5 = 50%)
recovery_buffer_pct: Prozent über MA für "Recovery" Status (default: 0.5%)
data_file: Datei für persistente Speicherung
"""
self.ma_period = ma_period
self.min_trades = min_trades_required
self.soft_mode = soft_mode
self.soft_multiplier = soft_mode_multiplier
self.recovery_buffer = recovery_buffer_pct / 100
self.data_file = data_file
# Equity Historie laden oder initialisieren
self.equity_history: List[Dict] = []
self._load_history()
# Status
self.current_status = "ACTIVE" # ACTIVE, PAUSED, RECOVERY
self.trades_while_paused = 0
logger.info("=" * 60)
logger.info("📈 EQUITY CURVE TRADING INITIALIZED")
logger.info("=" * 60)
logger.info(f" MA Period: {ma_period} trades")
logger.info(f" Min Trades: {min_trades_required}")
logger.info(f" Mode: {'Soft (reduced lots)' if soft_mode else 'Hard (full stop)'}")
if soft_mode:
logger.info(f" Soft Multiplier: {soft_mode_multiplier:.0%}")
logger.info(f" Recovery Buffer: {recovery_buffer_pct}%")
logger.info(f" History File: {data_file}")
logger.info(f" Loaded Trades: {len(self.equity_history)}")
logger.info("=" * 60)
# ==========================================
# CORE METHODS
# ==========================================
def should_trade(self, mt5_account_info=None) -> Tuple[bool, str, float]:
"""
Prüft ob Trading erlaubt ist basierend auf Equity Curve
Args:
mt5_account_info: Optional MT5 account info object
Returns:
(should_trade, reason, lot_multiplier)
- should_trade: True wenn traden erlaubt
- reason: Erklärung
- lot_multiplier: 1.0 = normal, 0.5 = reduziert, etc.
"""
# Nicht genug Historie
if len(self.equity_history) < self.min_trades:
return True, f"Warmup: {len(self.equity_history)}/{self.min_trades} trades", 1.0
# Aktuelle Equity holen
current_equity = self._get_current_equity(mt5_account_info)
if current_equity is None:
return True, "Could not get equity, allowing trade", 1.0
# MA berechnen
ma_equity = self._calculate_ma()
# Status bestimmen
equity_vs_ma_pct = ((current_equity - ma_equity) / ma_equity) * 100
if current_equity >= ma_equity * (1 + self.recovery_buffer):
# Deutlich über MA = ACTIVE
self.current_status = "ACTIVE"
self.trades_while_paused = 0
return True, f"✅ Equity ${current_equity:,.2f} > MA ${ma_equity:,.2f} (+{equity_vs_ma_pct:.1f}%)", 1.0
elif current_equity >= ma_equity:
# Knapp über MA = RECOVERY (vorsichtig)
self.current_status = "RECOVERY"
if self.soft_mode:
return True, f"🔄 Recovery: ${current_equity:,.2f} ≈ MA ${ma_equity:,.2f} ({equity_vs_ma_pct:+.1f}%)", 0.75
else:
return True, f"🔄 Recovery: ${current_equity:,.2f} ≈ MA ${ma_equity:,.2f}", 1.0
else:
# Unter MA = PAUSED oder SOFT
self.current_status = "PAUSED"
self.trades_while_paused += 1
if self.soft_mode:
return True, f"⚠️ Soft Mode: ${current_equity:,.2f} < MA ${ma_equity:,.2f} ({equity_vs_ma_pct:.1f}%)", self.soft_multiplier
else:
return False, f"⛔ PAUSED: ${current_equity:,.2f} < MA ${ma_equity:,.2f} ({equity_vs_ma_pct:.1f}%)", 0.0
def update_equity(self, mt5_account_info=None, trade_result: Optional[Dict] = None):
"""
Updated Equity-Historie nach einem Trade
Args:
mt5_account_info: Optional MT5 account info
trade_result: Optional dict mit Trade-Details
"""
current_equity = self._get_current_equity(mt5_account_info)
if current_equity is None:
logger.warning("Could not get equity for update")
return
entry = {
"timestamp": datetime.now().isoformat(),
"equity": current_equity,
"trade_count": len(self.equity_history) + 1
}
if trade_result:
entry["trade_profit"] = trade_result.get("profit", 0)
entry["trade_symbol"] = trade_result.get("symbol", "UNKNOWN")
self.equity_history.append(entry)
self._save_history()
# Log status
ma = self._calculate_ma() if len(self.equity_history) >= self.min_trades else None
if ma:
diff_pct = ((current_equity - ma) / ma) * 100
status_emoji = "" if current_equity >= ma else "⚠️"
logger.info(f"📈 Equity Update: ${current_equity:,.2f} | MA: ${ma:,.2f} | {status_emoji} {diff_pct:+.1f}%")
else:
logger.info(f"📈 Equity Update: ${current_equity:,.2f} | Warmup: {len(self.equity_history)}/{self.min_trades}")
def get_status(self, mt5_account_info=None) -> Dict:
"""
Gibt detaillierten Status zurück
Returns:
Dict mit allen relevanten Informationen
"""
current_equity = self._get_current_equity(mt5_account_info)
ma = self._calculate_ma() if len(self.equity_history) >= self.min_trades else None
status = {
"current_equity": current_equity,
"ma_equity": ma,
"ma_period": self.ma_period,
"total_trades": len(self.equity_history),
"min_trades_required": self.min_trades,
"warmup_complete": len(self.equity_history) >= self.min_trades,
"status": self.current_status,
"soft_mode": self.soft_mode,
"soft_multiplier": self.soft_multiplier if self.soft_mode else None
}
if current_equity and ma:
status["equity_vs_ma_pct"] = ((current_equity - ma) / ma) * 100
status["equity_above_ma"] = current_equity >= ma
return status
def get_report(self, mt5_account_info=None) -> str:
"""
Generiert einen formatierten Status-Report
"""
status = self.get_status(mt5_account_info)
report = []
report.append("")
report.append("=" * 60)
report.append("📈 EQUITY CURVE TRADING STATUS")
report.append("=" * 60)
if status["current_equity"]:
report.append(f" Current Equity: ${status['current_equity']:,.2f}")
if status["ma_equity"]:
report.append(f" MA ({self.ma_period} trades): ${status['ma_equity']:,.2f}")
diff = status.get("equity_vs_ma_pct", 0)
if status.get("equity_above_ma"):
report.append(f" Status: ✅ ABOVE MA (+{diff:.1f}%)")
else:
report.append(f" Status: ⚠️ BELOW MA ({diff:.1f}%)")
else:
report.append(f" Status: 🔄 Warmup ({status['total_trades']}/{status['min_trades_required']} trades)")
report.append("")
report.append(f" Trading Status: {status['status']}")
report.append(f" Mode: {'Soft' if status['soft_mode'] else 'Hard'}")
if status['soft_mode'] and status['status'] == 'PAUSED':
report.append(f" Lot Multiplier: {status['soft_multiplier']:.0%}")
report.append("")
report.append(f" Total Trades: {status['total_trades']}")
report.append("=" * 60)
return "\n".join(report)
# ==========================================
# HELPER METHODS
# ==========================================
def _get_current_equity(self, mt5_account_info=None) -> Optional[float]:
"""Holt aktuelle Equity von MT5 oder übergebenem Object"""
if mt5_account_info:
return mt5_account_info.equity
try:
import MetaTrader5 as mt
account = mt.account_info()
if account:
return account.equity
except Exception as e:
logger.debug(f"Could not get MT5 equity: {e}")
return None
def _calculate_ma(self) -> float:
"""Berechnet Moving Average der letzten N Equity-Werte"""
if len(self.equity_history) < self.ma_period:
# Nutze alle verfügbaren wenn nicht genug
recent = self.equity_history
else:
recent = self.equity_history[-self.ma_period:]
equities = [entry["equity"] for entry in recent]
return sum(equities) / len(equities) if equities else 0
def _load_history(self):
"""Lädt Equity-Historie aus Datei"""
try:
if os.path.exists(self.data_file):
with open(self.data_file, 'r') as f:
self.equity_history = json.load(f)
logger.info(f"📂 Loaded {len(self.equity_history)} equity records")
except Exception as e:
logger.warning(f"Could not load equity history: {e}")
self.equity_history = []
def _save_history(self):
"""Speichert Equity-Historie in Datei"""
try:
with open(self.data_file, 'w') as f:
json.dump(self.equity_history, f, indent=2)
except Exception as e:
logger.error(f"Could not save equity history: {e}")
def reset_history(self):
"""Setzt Historie zurück (Vorsicht!)"""
self.equity_history = []
self._save_history()
logger.warning("⚠️ Equity history has been reset!")
def add_initial_equity(self, equity: float):
"""
Fügt initiale Equity hinzu (für Warmup)
Nützlich wenn du mit bestehendem Konto startest
"""
for i in range(self.min_trades):
self.equity_history.append({
"timestamp": datetime.now().isoformat(),
"equity": equity,
"trade_count": i + 1,
"note": "Initial warmup entry"
})
self._save_history()
logger.info(f"📈 Added {self.min_trades} initial equity entries at ${equity:,.2f}")
# ==========================================
# STANDALONE USAGE
# ==========================================
if __name__ == "__main__":
# Demo
print("📈 Equity Curve Trading Demo")
print("=" * 50)
ecm = EquityCurveManager(
ma_period=5,
min_trades_required=3,
soft_mode=True,
soft_mode_multiplier=0.5
)
# Simulate some trades
test_equities = [10000, 10200, 10150, 9900, 9700, 9500, 9600, 9800, 10000, 10300]
print("\nSimulating trades:")
for i, eq in enumerate(test_equities):
# Fake the history
ecm.equity_history.append({
"timestamp": datetime.now().isoformat(),
"equity": eq,
"trade_count": i + 1
})
# Check if should trade
should, reason, mult = ecm.should_trade()
print(f"Trade {i+1}: Equity ${eq:,} | {reason} | Lot mult: {mult}")
print("\n" + ecm.get_report())
File diff suppressed because it is too large Load Diff