all changes done over the last 2 weeks
This commit is contained in:
@@ -0,0 +1,575 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
🎯 Advanced Position Management Module
|
||||
Performance Optimization Features:
|
||||
1. Adaptive Position Sizing
|
||||
2. Trailing Stop-Loss
|
||||
3. Partial Take Profit
|
||||
"""
|
||||
|
||||
import MetaTrader5 as mt
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Tuple, Optional, Dict
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 1. ADAPTIVE POSITION SIZING
|
||||
# ==========================================
|
||||
|
||||
class AdaptivePositionSizer:
|
||||
"""
|
||||
Passt Position Size basierend auf Signal Confidence an
|
||||
|
||||
Bessere Signals → Größere Positionen
|
||||
Schwächere Signals → Kleinere Positionen
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
base_risk: float = 0.01,
|
||||
high_confidence_threshold: float = 80.0,
|
||||
medium_confidence_threshold: float = 70.0,
|
||||
high_multiplier: float = 1.5,
|
||||
medium_multiplier: float = 1.0,
|
||||
low_multiplier: float = 0.5):
|
||||
"""
|
||||
Args:
|
||||
base_risk: Basis-Risk pro Trade (default 1%)
|
||||
high_confidence_threshold: Ab diesem Wert gilt Signal als "high confidence"
|
||||
medium_confidence_threshold: Ab diesem Wert gilt Signal als "medium confidence"
|
||||
high_multiplier: Risk-Multiplikator für high confidence (1.5x = 1.5%)
|
||||
medium_multiplier: Risk-Multiplikator für medium confidence (1.0x = 1.0%)
|
||||
low_multiplier: Risk-Multiplikator für low confidence (0.5x = 0.5%)
|
||||
"""
|
||||
self.base_risk = base_risk
|
||||
self.high_threshold = high_confidence_threshold
|
||||
self.medium_threshold = medium_confidence_threshold
|
||||
self.high_mult = high_multiplier
|
||||
self.medium_mult = medium_multiplier
|
||||
self.low_mult = low_multiplier
|
||||
|
||||
def calculate_risk_for_confidence(self, confidence: float) -> float:
|
||||
"""
|
||||
Berechnet angepasstes Risk basierend auf Confidence
|
||||
|
||||
Args:
|
||||
confidence: Signal Confidence (0-100)
|
||||
|
||||
Returns:
|
||||
Angepasstes Risk (z.B. 0.015 für 1.5%)
|
||||
"""
|
||||
if confidence >= self.high_threshold:
|
||||
multiplier = self.high_mult
|
||||
category = "HIGH"
|
||||
elif confidence >= self.medium_threshold:
|
||||
multiplier = self.medium_mult
|
||||
category = "MEDIUM"
|
||||
else:
|
||||
multiplier = self.low_mult
|
||||
category = "LOW"
|
||||
|
||||
adjusted_risk = self.base_risk * multiplier
|
||||
|
||||
logger.info(f"📊 Adaptive Position Sizing:")
|
||||
logger.info(f" Confidence: {confidence:.1f}% ({category})")
|
||||
logger.info(f" Base Risk: {self.base_risk*100:.1f}%")
|
||||
logger.info(f" Multiplier: {multiplier}x")
|
||||
logger.info(f" Adjusted Risk: {adjusted_risk*100:.1f}%")
|
||||
|
||||
return adjusted_risk
|
||||
|
||||
def calculate_position_size(self,
|
||||
confidence: float,
|
||||
balance: float,
|
||||
stop_loss_distance: float,
|
||||
symbol: str = "XAUUSD") -> float:
|
||||
"""
|
||||
Berechnet Position Size mit adaptivem Risk
|
||||
|
||||
Args:
|
||||
confidence: Signal Confidence
|
||||
balance: Account Balance
|
||||
stop_loss_distance: Distanz zum Stop Loss in Pips
|
||||
symbol: Trading Symbol
|
||||
|
||||
Returns:
|
||||
Lot Size (Volume)
|
||||
"""
|
||||
# Adaptive Risk
|
||||
adjusted_risk = self.calculate_risk_for_confidence(confidence)
|
||||
risk_amount = balance * adjusted_risk
|
||||
|
||||
# Symbol Info
|
||||
symbol_info = mt.symbol_info(symbol)
|
||||
if not symbol_info:
|
||||
logger.error(f"Symbol info not available for {symbol}")
|
||||
return 0.01 # Minimum
|
||||
|
||||
# Pip Value berechnen
|
||||
point = symbol_info.point
|
||||
tick_value = symbol_info.trade_tick_value
|
||||
tick_size = symbol_info.trade_tick_size
|
||||
pip_value = (tick_value / tick_size) * point
|
||||
|
||||
# Volume berechnen
|
||||
volume = risk_amount / (stop_loss_distance * pip_value)
|
||||
|
||||
# Auf erlaubte Schritte runden
|
||||
volume_min = symbol_info.volume_min
|
||||
volume_max = symbol_info.volume_max
|
||||
volume_step = symbol_info.volume_step
|
||||
|
||||
volume = round(volume / volume_step) * volume_step
|
||||
volume = max(volume_min, min(volume_max, volume))
|
||||
|
||||
logger.info(f"💰 Position Size: {volume:.2f} lots")
|
||||
logger.info(f" Risk Amount: ${risk_amount:.2f}")
|
||||
logger.info(f" SL Distance: {stop_loss_distance:.2f} pips")
|
||||
|
||||
return volume
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 2. TRAILING STOP-LOSS
|
||||
# ==========================================
|
||||
|
||||
class TrailingStopManager:
|
||||
"""
|
||||
Verwaltet Trailing Stop-Loss für laufende Positionen
|
||||
|
||||
Bewegt Stop-Loss mit Profit mit:
|
||||
- Break-Even bei 50% des Weges zu TP
|
||||
- Lock 50% Profit bei 75% des Weges zu TP
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
breakeven_trigger_pct: float = 0.5,
|
||||
profit_lock_trigger_pct: float = 0.75,
|
||||
profit_lock_amount_pct: float = 0.5,
|
||||
min_distance_points: int = 100):
|
||||
"""
|
||||
Args:
|
||||
breakeven_trigger_pct: Bei wie viel % des TP-Wegs auf Break-Even
|
||||
profit_lock_trigger_pct: Bei wie viel % des TP-Wegs Profit locken
|
||||
profit_lock_amount_pct: Wie viel % vom Profit locken
|
||||
min_distance_points: Minimum Distanz in Points (Anti-Stop-Hunting)
|
||||
"""
|
||||
self.breakeven_trigger = breakeven_trigger_pct
|
||||
self.profit_lock_trigger = profit_lock_trigger_pct
|
||||
self.profit_lock_amount = profit_lock_amount_pct
|
||||
self.min_distance = min_distance_points
|
||||
|
||||
def should_update_trailing_stop(self, position) -> Tuple[bool, Optional[float], str]:
|
||||
"""
|
||||
Prüft ob Trailing Stop aktualisiert werden sollte
|
||||
|
||||
Args:
|
||||
position: MT5 Position Object
|
||||
|
||||
Returns:
|
||||
(should_update, new_sl_price, reason)
|
||||
"""
|
||||
try:
|
||||
# Position Info
|
||||
ticket = position.ticket
|
||||
position_type = position.type # 0=BUY, 1=SELL
|
||||
entry_price = position.price_open
|
||||
current_sl = position.sl
|
||||
tp = position.tp
|
||||
|
||||
# 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
|
||||
|
||||
# TP Distance
|
||||
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 to TP
|
||||
progress_pct = current_distance / tp_distance
|
||||
|
||||
# Check Break-Even Trigger
|
||||
if progress_pct >= self.breakeven_trigger:
|
||||
new_sl = entry_price
|
||||
|
||||
# Verify minimum distance
|
||||
if position_type == 0: # BUY
|
||||
sl_distance_points = (current_price - new_sl) / mt.symbol_info(position.symbol).point
|
||||
else: # SELL
|
||||
sl_distance_points = (new_sl - current_price) / mt.symbol_info(position.symbol).point
|
||||
|
||||
if sl_distance_points < self.min_distance:
|
||||
return False, None, f"Distance too small: {sl_distance_points:.0f} points"
|
||||
|
||||
# Don't move SL backwards
|
||||
if position_type == 0: # BUY
|
||||
if current_sl > 0 and new_sl <= current_sl:
|
||||
return False, None, "Would move SL backwards"
|
||||
else: # SELL
|
||||
if current_sl > 0 and new_sl >= current_sl:
|
||||
return False, None, "Would move SL backwards"
|
||||
|
||||
return True, new_sl, f"Break-Even at {progress_pct*100:.1f}% progress"
|
||||
|
||||
# Check Profit Lock Trigger
|
||||
if progress_pct >= self.profit_lock_trigger:
|
||||
if position_type == 0: # BUY
|
||||
locked_profit = tp_distance * self.profit_lock_amount
|
||||
new_sl = entry_price + locked_profit
|
||||
else: # SELL
|
||||
locked_profit = tp_distance * self.profit_lock_amount
|
||||
new_sl = entry_price - locked_profit
|
||||
|
||||
# Verify minimum distance
|
||||
if position_type == 0: # BUY
|
||||
sl_distance_points = (current_price - new_sl) / mt.symbol_info(position.symbol).point
|
||||
else: # SELL
|
||||
sl_distance_points = (new_sl - current_price) / mt.symbol_info(position.symbol).point
|
||||
|
||||
if sl_distance_points < self.min_distance:
|
||||
return False, None, f"Distance too small: {sl_distance_points:.0f} points"
|
||||
|
||||
# Don't move SL backwards
|
||||
if position_type == 0: # BUY
|
||||
if current_sl > 0 and new_sl <= current_sl:
|
||||
return False, None, "Would move SL backwards"
|
||||
else: # SELL
|
||||
if current_sl > 0 and new_sl >= current_sl:
|
||||
return False, None, "Would move SL backwards"
|
||||
|
||||
return True, new_sl, f"Locking {self.profit_lock_amount*100:.0f}% profit at {progress_pct*100:.1f}% progress"
|
||||
|
||||
return False, None, f"Progress {progress_pct*100:.1f}% < trigger {self.breakeven_trigger*100:.0f}%"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking trailing stop: {e}")
|
||||
return False, None, str(e)
|
||||
|
||||
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": "Trailing Stop"
|
||||
}
|
||||
|
||||
result = mt.order_send(request)
|
||||
|
||||
if result.retcode == mt.TRADE_RETCODE_DONE:
|
||||
logger.info(f"✅ Trailing Stop updated for #{position.ticket}")
|
||||
logger.info(f" Old SL: {position.sl:.5f}")
|
||||
logger.info(f" New SL: {new_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
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 3. PARTIAL TAKE PROFIT
|
||||
# ==========================================
|
||||
|
||||
class PartialTakeProfitManager:
|
||||
"""
|
||||
Verwaltet Partial Take Profit
|
||||
|
||||
Schließt Teil der Position bei TP1, lässt Rest laufen bis TP2
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
tp1_risk_ratio: float = 1.5,
|
||||
tp2_risk_ratio: float = 2.5,
|
||||
partial_close_pct: float = 0.5):
|
||||
"""
|
||||
Args:
|
||||
tp1_risk_ratio: TP1 bei diesem Risk-Reward (1.5 = 1.5x Risk)
|
||||
tp2_risk_ratio: TP2 bei diesem Risk-Reward (2.5 = 2.5x Risk)
|
||||
partial_close_pct: Wie viel % bei TP1 schließen (0.5 = 50%)
|
||||
"""
|
||||
self.tp1_ratio = tp1_risk_ratio
|
||||
self.tp2_ratio = tp2_risk_ratio
|
||||
self.partial_pct = partial_close_pct
|
||||
|
||||
def calculate_partial_tp_levels(self,
|
||||
entry_price: float,
|
||||
sl_price: float,
|
||||
position_type: int) -> Tuple[float, float]:
|
||||
"""
|
||||
Berechnet TP1 und TP2 Levels
|
||||
|
||||
Args:
|
||||
entry_price: Entry Preis
|
||||
sl_price: Stop Loss Preis
|
||||
position_type: 0=BUY, 1=SELL
|
||||
|
||||
Returns:
|
||||
(tp1_price, tp2_price)
|
||||
"""
|
||||
if position_type == 0: # BUY
|
||||
risk = entry_price - sl_price
|
||||
tp1 = entry_price + (risk * self.tp1_ratio)
|
||||
tp2 = entry_price + (risk * self.tp2_ratio)
|
||||
else: # SELL
|
||||
risk = sl_price - entry_price
|
||||
tp1 = entry_price - (risk * self.tp1_ratio)
|
||||
tp2 = entry_price - (risk * self.tp2_ratio)
|
||||
|
||||
logger.info(f"📊 Partial TP Levels:")
|
||||
logger.info(f" Entry: {entry_price:.5f}")
|
||||
logger.info(f" SL: {sl_price:.5f}")
|
||||
logger.info(f" Risk: {abs(risk):.5f}")
|
||||
logger.info(f" TP1 ({self.tp1_ratio}R): {tp1:.5f}")
|
||||
logger.info(f" TP2 ({self.tp2_ratio}R): {tp2:.5f}")
|
||||
|
||||
return tp1, tp2
|
||||
|
||||
def should_close_partial(self, position, tp1_price: float) -> Tuple[bool, str]:
|
||||
"""
|
||||
Prüft ob Partial Close ausgeführt werden soll
|
||||
|
||||
Args:
|
||||
position: MT5 Position
|
||||
tp1_price: TP1 Preis Level
|
||||
|
||||
Returns:
|
||||
(should_close, reason)
|
||||
"""
|
||||
try:
|
||||
# Current Price
|
||||
symbol_info = mt.symbol_info_tick(position.symbol)
|
||||
if not symbol_info:
|
||||
return False, "No symbol info"
|
||||
|
||||
current_price = symbol_info.bid if position.type == 0 else symbol_info.ask
|
||||
|
||||
# Check if TP1 hit
|
||||
if position.type == 0: # BUY
|
||||
if current_price >= tp1_price:
|
||||
return True, f"TP1 hit: Price {current_price:.5f} >= TP1 {tp1_price:.5f}"
|
||||
else: # SELL
|
||||
if current_price <= tp1_price:
|
||||
return True, f"TP1 hit: Price {current_price:.5f} <= TP1 {tp1_price:.5f}"
|
||||
|
||||
return False, f"TP1 not reached yet"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking partial close: {e}")
|
||||
return False, str(e)
|
||||
|
||||
def close_partial_position(self, position, close_pct: float = None) -> bool:
|
||||
"""
|
||||
Schließt Teil der Position
|
||||
|
||||
Args:
|
||||
position: MT5 Position
|
||||
close_pct: Prozent zum Schließen (default: self.partial_pct)
|
||||
|
||||
Returns:
|
||||
Success
|
||||
"""
|
||||
try:
|
||||
if close_pct is None:
|
||||
close_pct = self.partial_pct
|
||||
|
||||
# Calculate volume to close
|
||||
close_volume = round(position.volume * close_pct, 2)
|
||||
|
||||
# Minimum volume check
|
||||
symbol_info = mt.symbol_info(position.symbol)
|
||||
if close_volume < symbol_info.volume_min:
|
||||
logger.warning(f"Close volume {close_volume} < minimum {symbol_info.volume_min}")
|
||||
return False
|
||||
|
||||
# Close request
|
||||
close_type = mt.ORDER_TYPE_SELL if position.type == 0 else mt.ORDER_TYPE_BUY
|
||||
close_price = mt.symbol_info_tick(position.symbol).bid if position.type == 0 else mt.symbol_info_tick(position.symbol).ask
|
||||
|
||||
request = {
|
||||
"action": mt.TRADE_ACTION_DEAL,
|
||||
"position": position.ticket,
|
||||
"symbol": position.symbol,
|
||||
"volume": close_volume,
|
||||
"type": close_type,
|
||||
"price": close_price,
|
||||
"deviation": 20,
|
||||
"magic": 234000,
|
||||
"comment": f"Partial TP1 ({close_pct*100:.0f}%)",
|
||||
"type_time": mt.ORDER_TIME_GTC,
|
||||
"type_filling": mt.ORDER_FILLING_IOC,
|
||||
}
|
||||
|
||||
result = mt.order_send(request)
|
||||
|
||||
if result.retcode == mt.TRADE_RETCODE_DONE:
|
||||
logger.info(f"✅ Partial close executed for #{position.ticket}")
|
||||
logger.info(f" Closed: {close_volume:.2f} lots ({close_pct*100:.0f}%)")
|
||||
logger.info(f" Remaining: {position.volume - close_volume:.2f} lots")
|
||||
logger.info(f" Profit: ${result.profit:.2f}")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"❌ Partial close failed: {result.comment}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error closing partial position: {e}")
|
||||
return False
|
||||
|
||||
|
||||
# ==========================================
|
||||
# INTEGRATED MANAGER
|
||||
# ==========================================
|
||||
|
||||
class AdvancedPositionManager:
|
||||
"""
|
||||
Integrierter Manager für alle Advanced Features
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
enable_adaptive_sizing: bool = True,
|
||||
enable_trailing_stop: bool = True,
|
||||
enable_partial_tp: bool = True):
|
||||
"""
|
||||
Args:
|
||||
enable_adaptive_sizing: Adaptive Position Sizing aktivieren
|
||||
enable_trailing_stop: Trailing Stop aktivieren
|
||||
enable_partial_tp: Partial TP aktivieren
|
||||
"""
|
||||
self.adaptive_sizing = AdaptivePositionSizer() if enable_adaptive_sizing else None
|
||||
self.trailing_stop = TrailingStopManager() if enable_trailing_stop else None
|
||||
self.partial_tp = PartialTakeProfitManager() if enable_partial_tp else None
|
||||
|
||||
# Track partial closes (avoid duplicate partial closes)
|
||||
self.partial_closed_positions = set()
|
||||
|
||||
logger.info("🎯 Advanced Position Manager initialized")
|
||||
logger.info(f" Adaptive Sizing: {'✅' if enable_adaptive_sizing else '❌'}")
|
||||
logger.info(f" Trailing Stop: {'✅' if enable_trailing_stop else '❌'}")
|
||||
logger.info(f" Partial TP: {'✅' if enable_partial_tp else '❌'}")
|
||||
|
||||
def check_and_update_positions(self, symbol: str = "XAUUSD"):
|
||||
"""
|
||||
Prüft alle offenen Positionen und aktualisiert Trailing Stops / Partial TPs
|
||||
|
||||
Args:
|
||||
symbol: Symbol zum Checken
|
||||
"""
|
||||
try:
|
||||
positions = mt.positions_get(symbol=symbol)
|
||||
|
||||
if not positions:
|
||||
return
|
||||
|
||||
logger.info(f"\n🔍 Checking {len(positions)} position(s) for {symbol}...")
|
||||
|
||||
for position in positions:
|
||||
# Trailing Stop Check
|
||||
if self.trailing_stop:
|
||||
should_update, new_sl, reason = self.trailing_stop.should_update_trailing_stop(position)
|
||||
|
||||
if should_update:
|
||||
logger.info(f"📈 Trailing Stop Trigger for #{position.ticket}: {reason}")
|
||||
self.trailing_stop.update_stop_loss(position, new_sl)
|
||||
else:
|
||||
logger.debug(f"⏸️ No trailing stop update: {reason}")
|
||||
|
||||
# Partial TP Check (only if not already partially closed)
|
||||
if self.partial_tp and position.ticket not in self.partial_closed_positions:
|
||||
# Calculate TP1 from current position
|
||||
tp1, tp2 = self.partial_tp.calculate_partial_tp_levels(
|
||||
position.price_open,
|
||||
position.sl,
|
||||
position.type
|
||||
)
|
||||
|
||||
should_close, reason = self.partial_tp.should_close_partial(position, tp1)
|
||||
|
||||
if should_close:
|
||||
logger.info(f"🎯 Partial TP Trigger for #{position.ticket}: {reason}")
|
||||
if self.partial_tp.close_partial_position(position):
|
||||
self.partial_closed_positions.add(position.ticket)
|
||||
else:
|
||||
logger.debug(f"⏸️ No partial close: {reason}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking positions: {e}")
|
||||
|
||||
|
||||
# ==========================================
|
||||
# USAGE EXAMPLE
|
||||
# ==========================================
|
||||
|
||||
"""
|
||||
INTEGRATION IN NOTEBOOK:
|
||||
|
||||
# Cell: Advanced Position Management Setup
|
||||
|
||||
from advanced_position_management import AdvancedPositionManager, AdaptivePositionSizer
|
||||
|
||||
# Initialize Manager
|
||||
adv_position_mgr = AdvancedPositionManager(
|
||||
enable_adaptive_sizing=True,
|
||||
enable_trailing_stop=True,
|
||||
enable_partial_tp=True
|
||||
)
|
||||
|
||||
print("✅ Advanced Position Management activated!")
|
||||
|
||||
|
||||
# Cell: In execute_trade_v2_adaptive()
|
||||
|
||||
# BEFORE (old):
|
||||
volume = 0.01 # Fixed
|
||||
|
||||
# AFTER (with Adaptive Sizing):
|
||||
if adv_position_mgr.adaptive_sizing:
|
||||
volume = adv_position_mgr.adaptive_sizing.calculate_position_size(
|
||||
confidence=confidence,
|
||||
balance=account_info.balance,
|
||||
stop_loss_distance=adjusted_atr_mult * atr * 10000, # Convert to pips
|
||||
symbol=symbol
|
||||
)
|
||||
else:
|
||||
volume = 0.01
|
||||
|
||||
|
||||
# Cell: Add to Scheduler (for Trailing Stop + Partial TP)
|
||||
|
||||
scheduler.add_job(
|
||||
func=lambda: adv_position_mgr.check_and_update_positions(symbol),
|
||||
trigger='interval',
|
||||
minutes=1,
|
||||
id='advanced_position_management'
|
||||
)
|
||||
|
||||
print("✅ Advanced Position Management scheduler added!")
|
||||
"""
|
||||
Reference in New Issue
Block a user