Deploy to Windows VPS / deploy (push) Has been cancelled
PROBLEM: - Trailing stop values were optimized for Forex, not Gold - breakeven_buffer_pips=5 → only $0.05 buffer for Gold (way too small!) - min_distance_points=100 → only $1.00 minimum (too tight!) - Trades were being stopped out with only ~$0.50 profit SOLUTION (Gold-optimized): - breakeven_buffer_pips: 5 → 300 ($3.00 buffer) - min_distance_points: 100 → 500 ($5.00 minimum distance) - atr_multiplier: 1.0 → 1.5 (more breathing room) IMPACT: - Trades now have proper room to develop - Less premature stop-outs - Better profit potential per trade Updated in: - enhanced_trailing_stop.py (class defaults + initialization) - TradingBot_V1.6_Adaptive_Complete_CORRECTED.ipynb (Cell 78) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
507 lines
19 KiB
Python
507 lines
19 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
📈 Enhanced Trailing Stop Management
|
||
Verbesserte Trailing Stops mit ATR-basierter Dynamic Trailing
|
||
|
||
IMPROVEMENTS:
|
||
1. ATR-based Trailing (nicht fix, sondern dynamisch)
|
||
2. Time-based Breakeven (nach X Stunden)
|
||
3. Profit-based Aggressive Trailing
|
||
4. Session-aware Trailing (Asian vs NY)
|
||
5. Multi-tier Profit Locking
|
||
"""
|
||
|
||
import MetaTrader5 as mt
|
||
from datetime import datetime, timedelta
|
||
from typing import Tuple, Optional, Dict
|
||
import logging
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class EnhancedTrailingStopManager:
|
||
"""
|
||
Verbesserte Trailing Stop Logik
|
||
|
||
Features:
|
||
- Früher Breakeven (30% statt 50%)
|
||
- ATR-basiertes Trailing (dynamisch statt fix)
|
||
- Time-based Protection (nach 4h auf BE)
|
||
- Multi-tier Profit Locking (50%, 75%, 90%)
|
||
"""
|
||
|
||
def __init__(self,
|
||
# Breakeven Settings
|
||
breakeven_trigger_pct: float = 0.30, # ← Früher! (war 0.50)
|
||
breakeven_buffer_pips: int = 300, # ← +$3 für Gold (300 × 0.01)
|
||
|
||
# Profit Locking (Multi-tier)
|
||
tier1_trigger: float = 0.50, # Bei 50% zu TP
|
||
tier1_lock_pct: float = 0.25, # Lock 25% profit
|
||
|
||
tier2_trigger: float = 0.75, # Bei 75% zu TP
|
||
tier2_lock_pct: float = 0.50, # Lock 50% profit
|
||
|
||
tier3_trigger: float = 0.90, # Bei 90% zu TP
|
||
tier3_lock_pct: float = 0.75, # Lock 75% profit
|
||
|
||
# ATR-based Trailing
|
||
use_atr_trailing: bool = True,
|
||
atr_multiplier: float = 1.5, # Trail by 1.5 × ATR (mehr Spielraum)
|
||
|
||
# Time-based Protection
|
||
time_based_breakeven: bool = True,
|
||
hours_to_breakeven: float = 4.0, # Nach 4h → BE
|
||
|
||
# Session-aware
|
||
session_trailing_multipliers: Optional[Dict[str, float]] = None,
|
||
|
||
# Technical
|
||
min_distance_points: int = 500): # Min $5 für Gold (500 × 0.01)
|
||
"""
|
||
Args:
|
||
breakeven_trigger_pct: Bei wie viel % zu TP → Breakeven
|
||
breakeven_buffer_pips: Zusätzliche Pips über Breakeven
|
||
tier1/2/3_trigger: Multi-tier Trigger Points
|
||
tier1/2/3_lock_pct: Lock Amounts pro Tier
|
||
use_atr_trailing: ATR-basiertes Trailing nutzen
|
||
atr_multiplier: ATR Multiplikator für Trailing
|
||
time_based_breakeven: Time-based BE aktivieren
|
||
hours_to_breakeven: Stunden bis Auto-Breakeven
|
||
session_trailing_multipliers: Custom Multiplier pro Session
|
||
min_distance_points: Minimum Distanz (Anti-Stop-Hunting)
|
||
"""
|
||
self.breakeven_trigger = breakeven_trigger_pct
|
||
self.breakeven_buffer_pips = breakeven_buffer_pips
|
||
|
||
self.tier1_trigger = tier1_trigger
|
||
self.tier1_lock = tier1_lock_pct
|
||
|
||
self.tier2_trigger = tier2_trigger
|
||
self.tier2_lock = tier2_lock_pct
|
||
|
||
self.tier3_trigger = tier3_trigger
|
||
self.tier3_lock = tier3_lock_pct
|
||
|
||
self.use_atr_trailing = use_atr_trailing
|
||
self.atr_multiplier = atr_multiplier
|
||
|
||
self.time_based_be = time_based_breakeven
|
||
self.hours_to_be = hours_to_breakeven
|
||
|
||
self.session_multipliers = session_trailing_multipliers or {
|
||
'asian': 1.0, # Standard
|
||
'ny': 1.5, # Größer (mehr Volatilität)
|
||
'london': 1.2,
|
||
'overlap': 1.3
|
||
}
|
||
|
||
self.min_distance = min_distance_points
|
||
|
||
# Tracking
|
||
self.position_tiers = {} # ticket → current tier
|
||
|
||
logger.info("✅ Enhanced Trailing Stop Manager initialized")
|
||
logger.info(f" Breakeven: {breakeven_trigger_pct*100:.0f}% + {breakeven_buffer_pips} pips")
|
||
logger.info(f" Multi-tier: {tier1_trigger*100:.0f}%/{tier2_trigger*100:.0f}%/{tier3_trigger*100:.0f}%")
|
||
logger.info(f" ATR Trailing: {'✅' if use_atr_trailing else '❌'}")
|
||
logger.info(f" Time-based BE: {'✅' if time_based_breakeven else '❌'} ({hours_to_breakeven}h)")
|
||
|
||
# ==========================================
|
||
# MAIN LOGIC
|
||
# ==========================================
|
||
|
||
def should_update_trailing_stop(self,
|
||
position,
|
||
current_session: str = 'asian',
|
||
atr_value: Optional[float] = None) -> Tuple[bool, Optional[float], str]:
|
||
"""
|
||
Prüft ob Trailing Stop aktualisiert werden sollte
|
||
|
||
Args:
|
||
position: MT5 Position Object
|
||
current_session: Aktuelle Session (für session-aware trailing)
|
||
atr_value: Aktueller ATR (für ATR-based trailing)
|
||
|
||
Returns:
|
||
(should_update, new_sl_price, reason)
|
||
"""
|
||
try:
|
||
ticket = position.ticket
|
||
position_type = position.type # 0=BUY, 1=SELL
|
||
entry_price = position.price_open
|
||
current_sl = position.sl
|
||
tp = position.tp
|
||
entry_time = datetime.fromtimestamp(position.time)
|
||
|
||
# Current Price
|
||
symbol_info = mt.symbol_info_tick(position.symbol)
|
||
if not symbol_info:
|
||
return False, None, "No symbol info"
|
||
|
||
current_price = symbol_info.bid if position_type == 0 else symbol_info.ask
|
||
point = mt.symbol_info(position.symbol).point
|
||
|
||
# Calculate progress
|
||
if position_type == 0: # BUY
|
||
tp_distance = tp - entry_price
|
||
current_distance = current_price - entry_price
|
||
else: # SELL
|
||
tp_distance = entry_price - tp
|
||
current_distance = entry_price - current_price
|
||
|
||
if tp_distance <= 0:
|
||
return False, None, "Invalid TP distance"
|
||
|
||
progress_pct = current_distance / tp_distance
|
||
trade_age_hours = (datetime.now() - entry_time).total_seconds() / 3600
|
||
|
||
# ==========================================
|
||
# 1. TIME-BASED BREAKEVEN
|
||
# ==========================================
|
||
if self.time_based_be and trade_age_hours >= self.hours_to_be:
|
||
if current_distance > 0: # In profit
|
||
new_sl = entry_price + (self.breakeven_buffer_pips * point if position_type == 0 else -self.breakeven_buffer_pips * point)
|
||
|
||
if self._is_valid_sl_update(position_type, current_price, new_sl, current_sl, point):
|
||
return True, new_sl, f"Time-based BE after {trade_age_hours:.1f}h"
|
||
|
||
# ==========================================
|
||
# 2. EARLY BREAKEVEN (30% statt 50%)
|
||
# ==========================================
|
||
if progress_pct >= self.breakeven_trigger:
|
||
if position_type == 0: # BUY
|
||
new_sl = entry_price + (self.breakeven_buffer_pips * point)
|
||
else: # SELL
|
||
new_sl = entry_price - (self.breakeven_buffer_pips * point)
|
||
|
||
if self._is_valid_sl_update(position_type, current_price, new_sl, current_sl, point):
|
||
return True, new_sl, f"Early BE at {progress_pct*100:.1f}% (+{self.breakeven_buffer_pips} pips buffer)"
|
||
|
||
# ==========================================
|
||
# 3. MULTI-TIER PROFIT LOCKING
|
||
# ==========================================
|
||
|
||
# Get current tier
|
||
current_tier = self.position_tiers.get(ticket, 0)
|
||
|
||
# Tier 3 (90%)
|
||
if progress_pct >= self.tier3_trigger and current_tier < 3:
|
||
locked_profit = tp_distance * self.tier3_lock
|
||
|
||
if position_type == 0: # BUY
|
||
new_sl = entry_price + locked_profit
|
||
else: # SELL
|
||
new_sl = entry_price - locked_profit
|
||
|
||
# ATR-based trailing wenn verfügbar
|
||
if self.use_atr_trailing and atr_value:
|
||
session_mult = self.session_multipliers.get(current_session, 1.0)
|
||
atr_distance = atr_value * self.atr_multiplier * session_mult
|
||
|
||
if position_type == 0:
|
||
new_sl = max(new_sl, current_price - atr_distance)
|
||
else:
|
||
new_sl = min(new_sl, current_price + atr_distance)
|
||
|
||
if self._is_valid_sl_update(position_type, current_price, new_sl, current_sl, point):
|
||
self.position_tiers[ticket] = 3
|
||
return True, new_sl, f"Tier 3: Locking {self.tier3_lock*100:.0f}% profit at {progress_pct*100:.1f}%"
|
||
|
||
# Tier 2 (75%)
|
||
if progress_pct >= self.tier2_trigger and current_tier < 2:
|
||
locked_profit = tp_distance * self.tier2_lock
|
||
|
||
if position_type == 0: # BUY
|
||
new_sl = entry_price + locked_profit
|
||
else: # SELL
|
||
new_sl = entry_price - locked_profit
|
||
|
||
# ATR-based trailing
|
||
if self.use_atr_trailing and atr_value:
|
||
session_mult = self.session_multipliers.get(current_session, 1.0)
|
||
atr_distance = atr_value * self.atr_multiplier * session_mult
|
||
|
||
if position_type == 0:
|
||
new_sl = max(new_sl, current_price - atr_distance)
|
||
else:
|
||
new_sl = min(new_sl, current_price + atr_distance)
|
||
|
||
if self._is_valid_sl_update(position_type, current_price, new_sl, current_sl, point):
|
||
self.position_tiers[ticket] = 2
|
||
return True, new_sl, f"Tier 2: Locking {self.tier2_lock*100:.0f}% profit at {progress_pct*100:.1f}%"
|
||
|
||
# Tier 1 (50%)
|
||
if progress_pct >= self.tier1_trigger and current_tier < 1:
|
||
locked_profit = tp_distance * self.tier1_lock
|
||
|
||
if position_type == 0: # BUY
|
||
new_sl = entry_price + locked_profit
|
||
else: # SELL
|
||
new_sl = entry_price - locked_profit
|
||
|
||
if self._is_valid_sl_update(position_type, current_price, new_sl, current_sl, point):
|
||
self.position_tiers[ticket] = 1
|
||
return True, new_sl, f"Tier 1: Locking {self.tier1_lock*100:.0f}% profit at {progress_pct*100:.1f}%"
|
||
|
||
# ==========================================
|
||
# 4. AGGRESSIVE ATR TRAILING (wenn Tier 3)
|
||
# ==========================================
|
||
if current_tier >= 3 and self.use_atr_trailing and atr_value:
|
||
session_mult = self.session_multipliers.get(current_session, 1.0)
|
||
atr_distance = atr_value * 0.5 * session_mult # Tighter: 0.5 × ATR
|
||
|
||
if position_type == 0: # BUY
|
||
new_sl = current_price - atr_distance
|
||
else: # SELL
|
||
new_sl = current_price + atr_distance
|
||
|
||
if self._is_valid_sl_update(position_type, current_price, new_sl, current_sl, point):
|
||
return True, new_sl, f"Aggressive ATR trail (0.5 × ATR)"
|
||
|
||
return False, None, f"Progress {progress_pct*100:.1f}% (Tier {current_tier})"
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error checking trailing stop: {e}")
|
||
return False, None, str(e)
|
||
|
||
# ==========================================
|
||
# HELPER FUNCTIONS
|
||
# ==========================================
|
||
|
||
def _is_valid_sl_update(self,
|
||
position_type: int,
|
||
current_price: float,
|
||
new_sl: float,
|
||
current_sl: float,
|
||
point: float) -> bool:
|
||
"""
|
||
Validiert SL Update
|
||
|
||
Checks:
|
||
- Minimum distance
|
||
- No backward movement
|
||
"""
|
||
# Check minimum distance
|
||
if position_type == 0: # BUY
|
||
distance_points = (current_price - new_sl) / point
|
||
else: # SELL
|
||
distance_points = (new_sl - current_price) / point
|
||
|
||
if distance_points < self.min_distance:
|
||
logger.debug(f"Distance too small: {distance_points:.0f} < {self.min_distance}")
|
||
return False
|
||
|
||
# Don't move SL backwards
|
||
if current_sl > 0:
|
||
if position_type == 0: # BUY
|
||
if new_sl <= current_sl:
|
||
logger.debug(f"Would move SL backwards: {new_sl} <= {current_sl}")
|
||
return False
|
||
else: # SELL
|
||
if new_sl >= current_sl:
|
||
logger.debug(f"Would move SL backwards: {new_sl} >= {current_sl}")
|
||
return False
|
||
|
||
return True
|
||
|
||
def update_stop_loss(self, position, new_sl: float) -> bool:
|
||
"""
|
||
Aktualisiert Stop-Loss für Position
|
||
|
||
Args:
|
||
position: MT5 Position
|
||
new_sl: Neuer Stop-Loss Preis
|
||
|
||
Returns:
|
||
Success
|
||
"""
|
||
try:
|
||
request = {
|
||
"action": mt.TRADE_ACTION_SLTP,
|
||
"position": position.ticket,
|
||
"symbol": position.symbol,
|
||
"sl": new_sl,
|
||
"tp": position.tp,
|
||
"magic": 234000,
|
||
"comment": "Enhanced Trailing"
|
||
}
|
||
|
||
result = mt.order_send(request)
|
||
|
||
if result.retcode == mt.TRADE_RETCODE_DONE:
|
||
logger.info(f"✅ Enhanced Trailing Stop updated for #{position.ticket}")
|
||
logger.info(f" Old SL: {position.sl:.5f}")
|
||
logger.info(f" New SL: {new_sl:.5f}")
|
||
logger.info(f" Buffer: {abs(new_sl - position.sl):.5f}")
|
||
return True
|
||
else:
|
||
logger.error(f"❌ Failed to update trailing stop: {result.comment}")
|
||
return False
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error updating stop loss: {e}")
|
||
return False
|
||
|
||
def cleanup_closed_positions(self):
|
||
"""Entfernt geschlossene Positions aus Tier-Tracking"""
|
||
open_tickets = {pos.ticket for pos in mt.positions_get()}
|
||
closed_tickets = set(self.position_tiers.keys()) - open_tickets
|
||
|
||
for ticket in closed_tickets:
|
||
del self.position_tiers[ticket]
|
||
|
||
if closed_tickets:
|
||
logger.info(f"🧹 Cleaned up {len(closed_tickets)} closed position(s) from tier tracking")
|
||
|
||
|
||
# ==========================================
|
||
# INTEGRATION HELPER
|
||
# ==========================================
|
||
|
||
def create_enhanced_position_monitor(
|
||
trailing_manager: EnhancedTrailingStopManager,
|
||
rhythm_manager,
|
||
symbol: str = "XAUUSD"
|
||
):
|
||
"""
|
||
Factory für Enhanced Position Monitor
|
||
|
||
Args:
|
||
trailing_manager: EnhancedTrailingStopManager Instanz
|
||
rhythm_manager: AdaptiveRhythmManager (für Session)
|
||
symbol: Trading Symbol
|
||
|
||
Returns:
|
||
Monitor Function (für Scheduler)
|
||
"""
|
||
|
||
def enhanced_position_monitor():
|
||
"""
|
||
Überwacht Positionen mit Enhanced Trailing
|
||
|
||
Features:
|
||
- Session-aware Trailing
|
||
- ATR-based Dynamic Trailing
|
||
- Multi-tier Profit Locking
|
||
- Time-based Breakeven
|
||
"""
|
||
try:
|
||
positions = mt.positions_get(symbol=symbol)
|
||
|
||
if not positions:
|
||
return
|
||
|
||
# Get current session
|
||
session = rhythm_manager.get_current_session()
|
||
|
||
# Get current ATR
|
||
atr_value = None
|
||
try:
|
||
rates = mt.copy_rates_from_pos(symbol, mt.TIMEFRAME_M5, 0, 20)
|
||
if rates is not None:
|
||
import pandas as pd
|
||
df = pd.DataFrame(rates)
|
||
df['tr'] = df[['high', 'low', 'close']].apply(
|
||
lambda x: max(x['high'] - x['low'],
|
||
abs(x['high'] - x['close']),
|
||
abs(x['low'] - x['close'])),
|
||
axis=1
|
||
)
|
||
atr_value = df['tr'].rolling(14).mean().iloc[-1]
|
||
except Exception as e:
|
||
logger.debug(f"Could not calculate ATR: {e}")
|
||
|
||
logger.info(f"\n🔍 Enhanced Position Monitor - {len(positions)} position(s)")
|
||
atr_display = f"{atr_value:.5f}" if atr_value else "N/A"
|
||
logger.info(f" Session: {session.upper()} | ATR: {atr_display}")
|
||
|
||
for position in positions:
|
||
should_update, new_sl, reason = trailing_manager.should_update_trailing_stop(
|
||
position,
|
||
current_session=session,
|
||
atr_value=atr_value
|
||
)
|
||
|
||
if should_update:
|
||
logger.info(f"📈 Trailing Trigger for #{position.ticket}: {reason}")
|
||
trailing_manager.update_stop_loss(position, new_sl)
|
||
else:
|
||
logger.debug(f"⏸️ No update: {reason}")
|
||
|
||
# Cleanup
|
||
trailing_manager.cleanup_closed_positions()
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in enhanced position monitor: {e}")
|
||
|
||
return enhanced_position_monitor
|
||
|
||
|
||
# ==========================================
|
||
# USAGE EXAMPLE
|
||
# ==========================================
|
||
|
||
"""
|
||
INTEGRATION IN NOTEBOOK:
|
||
|
||
# Cell: Setup Enhanced Trailing Stop
|
||
|
||
from enhanced_trailing_stop import EnhancedTrailingStopManager, create_enhanced_position_monitor
|
||
|
||
# Initialize Manager
|
||
enhanced_trailing = EnhancedTrailingStopManager(
|
||
breakeven_trigger_pct=0.30, # Früher BE (30% statt 50%)
|
||
breakeven_buffer_pips=300, # +$3 über BE (300 points × 0.01 = $3 für Gold)
|
||
|
||
tier1_trigger=0.50, # Multi-tier Locking
|
||
tier1_lock_pct=0.25,
|
||
tier2_trigger=0.75,
|
||
tier2_lock_pct=0.50,
|
||
tier3_trigger=0.90,
|
||
tier3_lock_pct=0.75,
|
||
|
||
use_atr_trailing=True, # ATR-based Trailing
|
||
atr_multiplier=1.5, # Erhöht von 1.0 auf 1.5 für mehr Spielraum
|
||
|
||
time_based_breakeven=True, # Time-based BE
|
||
hours_to_breakeven=4.0,
|
||
|
||
min_distance_points=500, # Min $5 Abstand (500 × 0.01 = $5 für Gold)
|
||
|
||
session_trailing_multipliers={ # Session-aware
|
||
'asian': 1.0,
|
||
'ny': 1.5,
|
||
'london': 1.2,
|
||
'overlap': 1.3
|
||
}
|
||
)
|
||
|
||
print("✅ Enhanced Trailing Stop Manager activated!")
|
||
|
||
|
||
# Cell: Add to Scheduler
|
||
|
||
# Remove old trailing stop if exists
|
||
try:
|
||
scheduler.remove_job('advanced_position_management')
|
||
except:
|
||
pass
|
||
|
||
# Add enhanced version
|
||
enhanced_monitor = create_enhanced_position_monitor(
|
||
enhanced_trailing,
|
||
rhythm_manager,
|
||
symbol="XAUUSD"
|
||
)
|
||
|
||
scheduler.add_job(
|
||
func=enhanced_monitor,
|
||
trigger='interval',
|
||
minutes=1,
|
||
id='enhanced_trailing_stop'
|
||
)
|
||
|
||
print("✅ Enhanced Trailing Stop scheduler added (checks every 1 min)")
|
||
"""
|