Files
Place-Order-Trading-Bot/enhanced_trailing_stop.py
T

510 lines
19 KiB
Python
Raw Normal View History

#!/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 mt5
import pandas as pd
from datetime import datetime, timedelta, timezone
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, tz=timezone.utc).replace(tzinfo=None)
# Current Price — fetch tick and symbol info once each
tick = mt5.symbol_info_tick(position.symbol)
if not tick:
return False, None, "No symbol info"
current_price = tick.bid if position_type == 0 else tick.ask
sym_info = mt5.symbol_info(position.symbol)
if not sym_info:
return False, None, "No symbol info"
point = sym_info.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": mt5.TRADE_ACTION_SLTP,
"position": position.ticket,
"symbol": position.symbol,
"sl": new_sl,
"tp": position.tp,
"magic": 234000,
"comment": "Enhanced Trailing"
}
result = mt5.order_send(request)
if result.retcode == mt5.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 (mt5.positions_get() or [])}
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 = mt5.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 = mt5.copy_rates_from_pos(symbol, mt5.TIMEFRAME_M5, 0, 20)
if rates is not None:
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)")
"""