2026-01-16 11:36:46 +01:00
|
|
|
|
#!/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)
|
2026-01-22 08:33:00 +01:00
|
|
|
|
breakeven_buffer_pips: int = 300, # ← +$3 für Gold (300 × 0.01)
|
2026-01-16 11:36:46 +01:00
|
|
|
|
|
|
|
|
|
|
# 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,
|
2026-01-22 08:33:00 +01:00
|
|
|
|
atr_multiplier: float = 1.5, # Trail by 1.5 × ATR (mehr Spielraum)
|
2026-01-16 11:36:46 +01:00
|
|
|
|
|
|
|
|
|
|
# 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
|
2026-01-22 08:33:00 +01:00
|
|
|
|
min_distance_points: int = 500): # Min $5 für Gold (500 × 0.01)
|
2026-01-16 11:36:46 +01:00
|
|
|
|
"""
|
|
|
|
|
|
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)")
|
2026-01-21 22:31:29 +01:00
|
|
|
|
atr_display = f"{atr_value:.5f}" if atr_value else "N/A"
|
|
|
|
|
|
logger.info(f" Session: {session.upper()} | ATR: {atr_display}")
|
2026-01-16 11:36:46 +01:00
|
|
|
|
|
|
|
|
|
|
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%)
|
2026-01-22 08:33:00 +01:00
|
|
|
|
breakeven_buffer_pips=300, # +$3 über BE (300 points × 0.01 = $3 für Gold)
|
2026-01-16 11:36:46 +01:00
|
|
|
|
|
|
|
|
|
|
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
|
2026-01-22 08:33:00 +01:00
|
|
|
|
atr_multiplier=1.5, # Erhöht von 1.0 auf 1.5 für mehr Spielraum
|
2026-01-16 11:36:46 +01:00
|
|
|
|
|
|
|
|
|
|
time_based_breakeven=True, # Time-based BE
|
|
|
|
|
|
hours_to_breakeven=4.0,
|
|
|
|
|
|
|
2026-01-22 08:33:00 +01:00
|
|
|
|
min_distance_points=500, # Min $5 Abstand (500 × 0.01 = $5 für Gold)
|
|
|
|
|
|
|
2026-01-16 11:36:46 +01:00
|
|
|
|
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)")
|
|
|
|
|
|
"""
|