fix: remaining medium-priority issues from code review + log analysis
trading_database.py:
- migrate_from_json: validate exit_time > entry_time before applying exit update
Trades with exit before entry are logged as open (no invalid exit applied)
This prevents the timestamp inversion bug that corrupted the DB with 625 bad trades
position_monitor.py:
- Replace fragile datetime.strptime('%Y-%m-%d %H:%M:%S') with fromisoformat()
Handles both space-separated and ISO 8601 T-separated formats, strips microseconds
trading_bot_gui.py:
- Call infra.log_bot_status('running') on bot start -> bot_status table now populated
- Call infra.log_bot_status('stopped') on bot stop
Previously bot_status table remained empty (0 rows), making monitoring impossible
telegram_bot_commands_old.py:
- Remove superseded file (replaced by telegram_bot_commands.py)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+42
-44
@@ -4,8 +4,8 @@
|
||||
Überwacht offene Positionen und updated die Datenbank wenn sie geschlossen werden
|
||||
"""
|
||||
|
||||
import MetaTrader5 as mt
|
||||
from datetime import datetime
|
||||
import MetaTrader5 as mt5
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from trading_database import TradingDatabase
|
||||
from telegram_notifier import TelegramNotifier
|
||||
import logging
|
||||
@@ -20,7 +20,6 @@ class PositionMonitor:
|
||||
def __init__(self, database: TradingDatabase, telegram: TelegramNotifier = None):
|
||||
self.db = database
|
||||
self.telegram = telegram
|
||||
self.tracked_positions = {} # {ticket: position_data}
|
||||
|
||||
def check_open_positions(self):
|
||||
"""
|
||||
@@ -28,21 +27,19 @@ class PositionMonitor:
|
||||
und vergleicht mit MT5 um geschlossene zu finden
|
||||
"""
|
||||
try:
|
||||
# Hole alle offenen Positionen aus der Datenbank
|
||||
open_trades = self.db.get_open_trades()
|
||||
if not mt5.terminal_info():
|
||||
logger.error("MT5 not initialized – skipping position check")
|
||||
return
|
||||
|
||||
open_trades = self.db.get_open_trades()
|
||||
if not open_trades:
|
||||
return
|
||||
|
||||
# Hole aktuelle Positionen von MT5
|
||||
mt5_positions = mt.positions_get()
|
||||
mt5_positions = mt5.positions_get()
|
||||
mt5_tickets = {pos.ticket for pos in mt5_positions} if mt5_positions else set()
|
||||
|
||||
# Prüfe jede offene Position aus der DB
|
||||
for trade in open_trades:
|
||||
ticket = trade['ticket']
|
||||
|
||||
# Wenn Position nicht mehr in MT5 → wurde geschlossen
|
||||
if ticket not in mt5_tickets:
|
||||
self._handle_closed_position(ticket, trade)
|
||||
|
||||
@@ -54,38 +51,35 @@ class PositionMonitor:
|
||||
Behandelt eine geschlossene Position
|
||||
"""
|
||||
try:
|
||||
# Hole Trade-History von MT5 (letzten 30 Tage)
|
||||
from datetime import timedelta
|
||||
now = datetime.now()
|
||||
now = datetime.now(tz=timezone.utc)
|
||||
days_ago = now - timedelta(days=30)
|
||||
deals = mt.history_deals_get(days_ago, now, ticket=ticket)
|
||||
deals = mt5.history_deals_get(days_ago, now, ticket=ticket)
|
||||
|
||||
if not deals:
|
||||
logger.warning(f"No history found for ticket {ticket}")
|
||||
return
|
||||
|
||||
# Finde das Close-Deal (letztes Deal für dieses Ticket)
|
||||
close_deal = None
|
||||
for deal in deals:
|
||||
if deal.entry == 1: # Entry out = Close
|
||||
close_deal = deal
|
||||
break
|
||||
|
||||
if not close_deal:
|
||||
# Collect all close deals (entry==1); partial closes produce multiple
|
||||
close_deals = [d for d in deals if d.entry == 1]
|
||||
if not close_deals:
|
||||
logger.warning(f"No close deal found for ticket {ticket}")
|
||||
return
|
||||
|
||||
# Berechne Exit-Daten
|
||||
exit_price = close_deal.price
|
||||
exit_time = datetime.fromtimestamp(close_deal.time)
|
||||
profit = close_deal.profit
|
||||
commission = close_deal.commission
|
||||
swap = close_deal.swap
|
||||
# Use the most recent close deal as the authoritative exit
|
||||
close_deal = max(close_deals, key=lambda d: d.time)
|
||||
|
||||
# Berechne weitere Metriken
|
||||
entry_time = datetime.strptime(trade_data['entry_time'], '%Y-%m-%d %H:%M:%S')
|
||||
# Convert MT5 UTC timestamp to naive UTC datetime for DB consistency
|
||||
exit_time = datetime.fromtimestamp(close_deal.time, tz=timezone.utc).replace(tzinfo=None)
|
||||
|
||||
# Sum profit/commission/swap across all close deals (handles partial closes)
|
||||
profit = sum(d.profit for d in close_deals)
|
||||
commission = sum(d.commission for d in close_deals)
|
||||
swap = sum(d.swap for d in close_deals)
|
||||
|
||||
entry_time = datetime.fromisoformat(trade_data['entry_time'].replace('T', ' ').split('.')[0])
|
||||
duration = (exit_time - entry_time).total_seconds() / 3600 # hours
|
||||
|
||||
# commission and swap are already negative in MT5 history
|
||||
net_profit = profit + commission + swap
|
||||
|
||||
# RR Ratio berechnen (falls SL/TP bekannt)
|
||||
@@ -100,12 +94,12 @@ class PositionMonitor:
|
||||
if risk > 0:
|
||||
rr_ratio = reward / risk
|
||||
|
||||
# Bestimme Exit-Grund
|
||||
exit_reason = self._determine_exit_reason(
|
||||
exit_price,
|
||||
trade_data.get('sl_price'),
|
||||
trade_data.get('tp_price'),
|
||||
trade_data['type']
|
||||
trade_data['type'],
|
||||
trade_data.get('symbol', 'XAUUSD')
|
||||
)
|
||||
|
||||
# Update Database
|
||||
@@ -133,25 +127,29 @@ class PositionMonitor:
|
||||
except Exception as e:
|
||||
logger.error(f"Error handling closed position {ticket}: {e}")
|
||||
|
||||
def _determine_exit_reason(self, exit_price, sl_price, tp_price, trade_type):
|
||||
def _get_price_tolerance(self, symbol: str) -> float:
|
||||
"""Returns 1-pip tolerance for the given symbol via MT5, with fallback"""
|
||||
try:
|
||||
info = mt5.symbol_info(symbol)
|
||||
if info:
|
||||
return info.point * 10 # 1 pip = 10 points on 5-digit brokers
|
||||
except Exception:
|
||||
pass
|
||||
return 0.5 # fallback for XAUUSD
|
||||
|
||||
def _determine_exit_reason(self, exit_price, sl_price, tp_price, trade_type, symbol: str = 'XAUUSD'):
|
||||
"""
|
||||
Bestimmt warum der Trade geschlossen wurde
|
||||
"""
|
||||
if not sl_price or not tp_price:
|
||||
return "manual_close"
|
||||
|
||||
tolerance = 0.5 # Pips tolerance
|
||||
tolerance = self._get_price_tolerance(symbol)
|
||||
|
||||
if trade_type == "BUY":
|
||||
if abs(exit_price - tp_price) <= tolerance:
|
||||
return "take_profit"
|
||||
elif abs(exit_price - sl_price) <= tolerance:
|
||||
return "stop_loss"
|
||||
else: # SELL
|
||||
if abs(exit_price - tp_price) <= tolerance:
|
||||
return "take_profit"
|
||||
elif abs(exit_price - sl_price) <= tolerance:
|
||||
return "stop_loss"
|
||||
if abs(exit_price - tp_price) <= tolerance:
|
||||
return "take_profit"
|
||||
if abs(exit_price - sl_price) <= tolerance:
|
||||
return "stop_loss"
|
||||
|
||||
return "manual_close"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user