fix: enhanced_trailing_stop, dynamic_threshold_optimizer, signal_cache
enhanced_trailing_stop.py: - mt -> mt5 (all occurrences) - Add pandas + timezone imports at file top - Fix UTC bug: datetime.fromtimestamp(..., tz=timezone.utc).replace(tzinfo=None) - Fetch symbol_info once per call, reuse for point (was called twice) - cleanup_closed_positions: handle None from positions_get() - Remove pandas import from inside function body dynamic_threshold_optimizer.py: - Fix SQL injection: replace f-string session filter with parameterized query (?) - Add logging module, replace all print() with logger calls - Use context manager (with sqlite3.connect()) to prevent connection leak on exception - save_thresholds_to_config: add try/except with logger.error signal_cache.py: - Fix bare except -> except Exception in _cleanup_old_entries Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,10 +12,13 @@ FEATURES:
|
||||
|
||||
import sqlite3
|
||||
import pandas as pd
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, Optional, Tuple
|
||||
import json
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DynamicThresholdOptimizer:
|
||||
"""
|
||||
@@ -58,10 +61,9 @@ class DynamicThresholdOptimizer:
|
||||
'overlap': 70
|
||||
}
|
||||
|
||||
print(f"✅ Dynamic Threshold Optimizer initialized")
|
||||
print(f" Lookback: {lookback_trades} trades")
|
||||
print(f" Target Win Rate: {target_win_rate*100:.1f}%")
|
||||
print(f" Range: {min_threshold}% - {max_threshold}%")
|
||||
logger.info(f"Dynamic Threshold Optimizer initialized — "
|
||||
f"lookback={lookback_trades}, target_wr={target_win_rate*100:.0f}%, "
|
||||
f"range={min_threshold}%-{max_threshold}%")
|
||||
|
||||
def get_recent_performance(self, session: Optional[str] = None) -> Dict:
|
||||
"""
|
||||
@@ -74,10 +76,7 @@ class DynamicThresholdOptimizer:
|
||||
Dict mit Performance-Metriken
|
||||
"""
|
||||
try:
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
|
||||
# Query für letzte N Trades
|
||||
query = f"""
|
||||
base_query = """
|
||||
SELECT
|
||||
confidence,
|
||||
session,
|
||||
@@ -86,14 +85,15 @@ class DynamicThresholdOptimizer:
|
||||
FROM trades
|
||||
WHERE status = 'closed'
|
||||
"""
|
||||
|
||||
params: list = []
|
||||
if session:
|
||||
query += f" AND session = '{session}'"
|
||||
base_query += " AND session = ?"
|
||||
params.append(session)
|
||||
base_query += " ORDER BY exit_time DESC LIMIT ?"
|
||||
params.append(self.lookback_trades)
|
||||
|
||||
query += f" ORDER BY exit_time DESC LIMIT {self.lookback_trades}"
|
||||
|
||||
df = pd.read_sql_query(query, conn)
|
||||
conn.close()
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
df = pd.read_sql_query(base_query, conn, params=params)
|
||||
|
||||
if df.empty:
|
||||
return {
|
||||
@@ -105,10 +105,10 @@ class DynamicThresholdOptimizer:
|
||||
}
|
||||
|
||||
trades = len(df)
|
||||
wins = df['win'].sum()
|
||||
wins = int(df['win'].sum())
|
||||
win_rate = wins / trades if trades > 0 else 0.0
|
||||
avg_confidence = df['confidence'].mean()
|
||||
total_profit = df['net_profit'].sum()
|
||||
avg_confidence = float(df['confidence'].mean())
|
||||
total_profit = float(df['net_profit'].sum())
|
||||
|
||||
return {
|
||||
'trades': trades,
|
||||
@@ -121,7 +121,7 @@ class DynamicThresholdOptimizer:
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error getting performance: {e}")
|
||||
logger.error(f"Error getting performance: {e}")
|
||||
return {
|
||||
'trades': 0,
|
||||
'win_rate': 0.0,
|
||||
@@ -322,10 +322,12 @@ class DynamicThresholdOptimizer:
|
||||
}
|
||||
}
|
||||
|
||||
with open(config_file, 'w') as f:
|
||||
json.dump(config, f, indent=2)
|
||||
|
||||
print(f"✅ Thresholds saved to: {config_file}")
|
||||
try:
|
||||
with open(config_file, 'w') as f:
|
||||
json.dump(config, f, indent=2)
|
||||
logger.info(f"Thresholds saved to: {config_file}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to save thresholds: {e}")
|
||||
|
||||
|
||||
# ==========================================
|
||||
@@ -344,9 +346,9 @@ def auto_optimize_thresholds(optimizer: DynamicThresholdOptimizer,
|
||||
Returns:
|
||||
Optimization Results
|
||||
"""
|
||||
print(f"\n{'='*70}")
|
||||
print(f"🔄 AUTO-OPTIMIZATION STARTED - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
print(f"{'='*70}\n")
|
||||
logger.info(f"\n{'='*70}")
|
||||
logger.info(f"🔄 AUTO-OPTIMIZATION STARTED - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
logger.info(f"{'='*70}\n")
|
||||
|
||||
results = optimizer.optimize_all_sessions()
|
||||
|
||||
@@ -357,16 +359,16 @@ def auto_optimize_thresholds(optimizer: DynamicThresholdOptimizer,
|
||||
|
||||
change_emoji = "🔽" if info['change'] < 0 else ("🔼" if info['change'] > 0 else "➡️")
|
||||
|
||||
print(f"{session.upper():8s}: {info['old_threshold']}% → {info['new_threshold']}% "
|
||||
logger.info(f"{session.upper():8s}: {info['old_threshold']}% → {info['new_threshold']}% "
|
||||
f"{change_emoji} | WR: {info['win_rate']*100:.1f}% ({info['recent_trades']} trades)")
|
||||
|
||||
if apply_changes:
|
||||
optimizer.save_thresholds_to_config()
|
||||
print("\n✅ Changes applied and saved!")
|
||||
logger.info("\n✅ Changes applied and saved!")
|
||||
else:
|
||||
print("\n⚠️ Dry-run mode - changes NOT applied")
|
||||
logger.info("\n⚠️ Dry-run mode - changes NOT applied")
|
||||
|
||||
print(f"\n{'='*70}\n")
|
||||
logger.info(f"\n{'='*70}\n")
|
||||
|
||||
return results
|
||||
|
||||
@@ -434,4 +436,4 @@ print("✅ Auto-optimization scheduled (daily at midnight)")
|
||||
if __name__ == "__main__":
|
||||
# Test
|
||||
optimizer = DynamicThresholdOptimizer()
|
||||
print(optimizer.generate_report())
|
||||
logger.info(optimizer.generate_report())
|
||||
|
||||
+18
-15
@@ -11,8 +11,9 @@ IMPROVEMENTS:
|
||||
5. Multi-tier Profit Locking
|
||||
"""
|
||||
|
||||
import MetaTrader5 as mt
|
||||
from datetime import datetime, timedelta
|
||||
import MetaTrader5 as mt5
|
||||
import pandas as pd
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Tuple, Optional, Dict
|
||||
import logging
|
||||
|
||||
@@ -132,15 +133,18 @@ class EnhancedTrailingStopManager:
|
||||
entry_price = position.price_open
|
||||
current_sl = position.sl
|
||||
tp = position.tp
|
||||
entry_time = datetime.fromtimestamp(position.time)
|
||||
entry_time = datetime.fromtimestamp(position.time, tz=timezone.utc).replace(tzinfo=None)
|
||||
|
||||
# Current Price
|
||||
symbol_info = mt.symbol_info_tick(position.symbol)
|
||||
if not symbol_info:
|
||||
# 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 = symbol_info.bid if position_type == 0 else symbol_info.ask
|
||||
point = mt.symbol_info(position.symbol).point
|
||||
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
|
||||
@@ -318,7 +322,7 @@ class EnhancedTrailingStopManager:
|
||||
"""
|
||||
try:
|
||||
request = {
|
||||
"action": mt.TRADE_ACTION_SLTP,
|
||||
"action": mt5.TRADE_ACTION_SLTP,
|
||||
"position": position.ticket,
|
||||
"symbol": position.symbol,
|
||||
"sl": new_sl,
|
||||
@@ -327,9 +331,9 @@ class EnhancedTrailingStopManager:
|
||||
"comment": "Enhanced Trailing"
|
||||
}
|
||||
|
||||
result = mt.order_send(request)
|
||||
result = mt5.order_send(request)
|
||||
|
||||
if result.retcode == mt.TRADE_RETCODE_DONE:
|
||||
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}")
|
||||
@@ -345,7 +349,7 @@ class EnhancedTrailingStopManager:
|
||||
|
||||
def cleanup_closed_positions(self):
|
||||
"""Entfernt geschlossene Positions aus Tier-Tracking"""
|
||||
open_tickets = {pos.ticket for pos in mt.positions_get()}
|
||||
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:
|
||||
@@ -387,7 +391,7 @@ def create_enhanced_position_monitor(
|
||||
- Time-based Breakeven
|
||||
"""
|
||||
try:
|
||||
positions = mt.positions_get(symbol=symbol)
|
||||
positions = mt5.positions_get(symbol=symbol)
|
||||
|
||||
if not positions:
|
||||
return
|
||||
@@ -398,9 +402,8 @@ def create_enhanced_position_monitor(
|
||||
# Get current ATR
|
||||
atr_value = None
|
||||
try:
|
||||
rates = mt.copy_rates_from_pos(symbol, mt.TIMEFRAME_M5, 0, 20)
|
||||
rates = mt5.copy_rates_from_pos(symbol, mt5.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'],
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ class SignalCache:
|
||||
age_hours = (now - timestamp).total_seconds() / 3600
|
||||
if age_hours > MAX_CACHE_AGE_HOURS:
|
||||
to_remove.append(ticket)
|
||||
except:
|
||||
except Exception:
|
||||
to_remove.append(ticket)
|
||||
|
||||
for ticket in to_remove:
|
||||
|
||||
Reference in New Issue
Block a user