mirror of
https://github.com/silencesdg/mt5_python_ea_suite.git
synced 2026-07-29 03:37:43 +00:00
feat: 一票制并发锁 + MT5硬止损兜底
- 一票制: _pending_long/_pending_short 计数器防同一周期内多信号穿透 - 硬止损: MT5下单时附带sl/tp,倍率1.5x(止损)/1.3x(止盈),比EA软止损更宽 - config.py: 新增 hard_sl_multiplier/hard_tp_multiplier - 全部 send_order 链路(abc/remote/live/server/dryrun/backtest) 支持 sl/tp 参数 - 对冲模块: 彻底移除 - 日志: 去重+30轮摘要
This commit is contained in:
Executable
+255
@@ -0,0 +1,255 @@
|
||||
#!/usr/bin/env python3
|
||||
"""每日自动优化 — 遗传算法跑参数 → 写入config → 重启实盘EA
|
||||
|
||||
通过 cron 调用: python3 scripts/daily_optimize.py
|
||||
"""
|
||||
|
||||
import sys, os, json, re, time, signal, shutil
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_DIR = Path(__file__).resolve().parent.parent
|
||||
os.chdir(str(PROJECT_DIR))
|
||||
sys.path.insert(0, str(PROJECT_DIR))
|
||||
|
||||
from logger import setup_logger
|
||||
setup_logger("INFO")
|
||||
from logger import logger
|
||||
|
||||
CONFIG_PATH = PROJECT_DIR / "config.py"
|
||||
BACKUP_DIR = PROJECT_DIR / "config_backups"
|
||||
RESTART_SIGNAL = PROJECT_DIR / ".restart_signal"
|
||||
|
||||
|
||||
def run_optimizer():
|
||||
"""运行遗传算法优化,返回 best_params dict"""
|
||||
from execution.optimize import run_optimizer as _run
|
||||
logger.info("🧬 开始遗传算法优化...")
|
||||
best_params, fitness = _run()
|
||||
logger.info(f"✅ 优化完成 适应度={fitness:.2f}")
|
||||
return best_params
|
||||
|
||||
|
||||
def backup_config():
|
||||
"""备份当前 config.py"""
|
||||
BACKUP_DIR.mkdir(exist_ok=True)
|
||||
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
dst = BACKUP_DIR / f"config_{ts}.py"
|
||||
shutil.copy(CONFIG_PATH, dst)
|
||||
logger.info(f"📦 已备份配置: {dst}")
|
||||
|
||||
|
||||
def update_config(best_params: dict):
|
||||
"""将优化结果写回 config.py"""
|
||||
content = CONFIG_PATH.read_text(encoding="utf-8")
|
||||
|
||||
# ═══ RISK_CONFIG ═══
|
||||
risk_map = {
|
||||
"stop_loss_pct": "stop_loss_pct",
|
||||
"profit_retracement_pct": "profit_retracement_pct",
|
||||
"min_profit_for_trailing": "min_profit_for_trailing",
|
||||
"take_profit_pct": "take_profit_pct",
|
||||
"max_holding_minutes": "max_holding_minutes",
|
||||
"min_profit_for_time_exit": "min_profit_for_time_exit",
|
||||
}
|
||||
for opt_key, cfg_key in risk_map.items():
|
||||
if opt_key in best_params:
|
||||
val = best_params[opt_key]
|
||||
content = re.sub(
|
||||
rf'("{cfg_key}":\s*)[\d.\-e]+',
|
||||
rf'\g<1>{val}',
|
||||
content
|
||||
)
|
||||
|
||||
# ═══ SIGNAL_THRESHOLDS ═══
|
||||
if "buy_threshold" in best_params:
|
||||
content = re.sub(
|
||||
r'("buy_threshold":\s*)[\d.\-e]+',
|
||||
rf'\g<1>{best_params["buy_threshold"]}',
|
||||
content
|
||||
)
|
||||
if "sell_threshold" in best_params:
|
||||
content = re.sub(
|
||||
r'("sell_threshold":\s*)[\d.\-e]+',
|
||||
rf'\g<1>{best_params["sell_threshold"]}',
|
||||
content
|
||||
)
|
||||
|
||||
# ═══ MARKET_STATE_CONFIG ═══
|
||||
market_map = {
|
||||
"market_trend_period": "trend_period",
|
||||
"market_retracement_tolerance": "retracement_tolerance",
|
||||
"market_volume_period": "volume_period",
|
||||
"market_volume_ma_period": "volume_ma_period",
|
||||
}
|
||||
for opt_key, cfg_key in market_map.items():
|
||||
if opt_key in best_params:
|
||||
val = int(best_params[opt_key]) if "period" in opt_key else best_params[opt_key]
|
||||
content = re.sub(
|
||||
rf'("{cfg_key}":\s*)[\d.\-e]+',
|
||||
rf'\g<1>{val}',
|
||||
content
|
||||
)
|
||||
|
||||
# ═══ STRATEGY_CONFIG (strategy params) ═══
|
||||
strategy_param_map = {
|
||||
# MACrossStrategy
|
||||
"ma_cross_short_window": ("ma_cross", "short_window"),
|
||||
"ma_cross_long_window": ("ma_cross", "long_window"),
|
||||
# RSIStrategy
|
||||
"rsi_period": ("rsi", "period"),
|
||||
"rsi_overbought": ("rsi", "overbought"),
|
||||
"rsi_oversold": ("rsi", "oversold"),
|
||||
# BollingerStrategy
|
||||
"bollinger_period": ("bollinger", "period"),
|
||||
"bollinger_std_dev": ("bollinger", "std_dev"),
|
||||
# MACDStrategy
|
||||
"macd_fast_ema": ("macd", "fast_ema"),
|
||||
"macd_slow_ema": ("macd", "slow_ema"),
|
||||
"macd_signal_period": ("macd", "signal_period"),
|
||||
# MeanReversionStrategy
|
||||
"mean_reversion_period": ("mean_reversion", "period"),
|
||||
"mean_reversion_std_dev": ("mean_reversion", "std_dev"),
|
||||
# MomentumBreakoutStrategy
|
||||
"momentum_breakout_period": ("momentum_breakout", "period"),
|
||||
"momentum_breakout_momentum_period": ("momentum_breakout", "momentum_period"),
|
||||
# KDJStrategy
|
||||
"kdj_period": ("kdj", "period"),
|
||||
# TurtleStrategy
|
||||
"turtle_period": ("turtle", "period"),
|
||||
# DailyBreakoutStrategy
|
||||
"daily_breakout_bars_count": ("daily_breakout", "bars_count"),
|
||||
# WaveTheoryStrategy
|
||||
"wave_ema_short": ("wave_theory", "ema_short"),
|
||||
"wave_ema_medium": ("wave_theory", "ema_medium"),
|
||||
"wave_ema_long": ("wave_theory", "ema_long"),
|
||||
"wave_period": ("wave_theory", "wave_period"),
|
||||
"wave_range_period": ("wave_theory", "range_period"),
|
||||
"wave_adx_period": ("wave_theory", "adx_period"),
|
||||
"wave_momentum_period": ("wave_theory", "momentum_period"),
|
||||
"wave_range_threshold": ("wave_theory", "range_threshold"),
|
||||
"wave_adx_threshold": ("wave_theory", "adx_threshold"),
|
||||
}
|
||||
for opt_key, (section, key) in strategy_param_map.items():
|
||||
if opt_key in best_params:
|
||||
val = best_params[opt_key]
|
||||
if isinstance(val, float) and abs(val - round(val)) < 1e-6:
|
||||
val = int(round(val))
|
||||
content = re.sub(
|
||||
rf'("{key}":\s*)[\d.\-e]+',
|
||||
rf'\g<1>{val}',
|
||||
content,
|
||||
count=1,
|
||||
)
|
||||
|
||||
# ═══ DEFAULT_WEIGHTS ═══
|
||||
weight_map = {
|
||||
"weight_MACrossStrategy": "ma_cross",
|
||||
"weight_RSIStrategy": "rsi",
|
||||
"weight_BollingerStrategy": "bollinger",
|
||||
"weight_MeanReversionStrategy": "mean_reversion",
|
||||
"weight_MomentumBreakoutStrategy": "momentum_breakout",
|
||||
"weight_MACDStrategy": "macd",
|
||||
"weight_KDJStrategy": "kdj",
|
||||
"weight_TurtleStrategy": "turtle",
|
||||
"weight_DailyBreakoutStrategy": "daily_breakout",
|
||||
"weight_WaveTheoryStrategy": "wave_theory",
|
||||
}
|
||||
for opt_key, cfg_key in weight_map.items():
|
||||
if opt_key in best_params:
|
||||
val = best_params[opt_key]
|
||||
content = re.sub(
|
||||
rf'("{cfg_key}":\s*)[\d.\-e]+',
|
||||
rf'\g<1>{val}',
|
||||
content
|
||||
)
|
||||
|
||||
# ═══ TREND_INDICATOR_WEIGHTS ═══
|
||||
trend_weight_map = {
|
||||
"trend_price_breakout_weight": "price_breakout",
|
||||
"trend_volume_confirmation_weight": "volume_confirmation",
|
||||
"trend_momentum_oscillator_weight": "momentum_oscillator",
|
||||
"trend_moving_average_weight": "moving_average",
|
||||
}
|
||||
for opt_key, cfg_key in trend_weight_map.items():
|
||||
if opt_key in best_params:
|
||||
val = best_params[opt_key]
|
||||
content = re.sub(
|
||||
rf'("{cfg_key}":\s*)[\d.\-e]+',
|
||||
rf'\g<1>{val}',
|
||||
content
|
||||
)
|
||||
|
||||
# ═══ TREND_THRESHOLDS ═══
|
||||
trend_thresh_map = {
|
||||
"trend_strong_threshold": "strong_trend",
|
||||
"trend_weak_threshold": "weak_trend",
|
||||
"trend_volume_spike": "volume_spike",
|
||||
"trend_oversold": "oversold",
|
||||
"trend_overbought": "overbought",
|
||||
}
|
||||
for opt_key, cfg_key in trend_thresh_map.items():
|
||||
if opt_key in best_params:
|
||||
val = best_params[opt_key]
|
||||
if "sold" in opt_key or "bought" in opt_key:
|
||||
val = int(round(val))
|
||||
content = re.sub(
|
||||
rf'("{cfg_key}":\s*)[\d.\-e]+',
|
||||
rf'\g<1>{val}',
|
||||
content
|
||||
)
|
||||
|
||||
# ═══ CONFIDENCE_THRESHOLDS ═══
|
||||
conf_map = {
|
||||
"confidence_high": "high_confidence",
|
||||
"confidence_medium": "medium_confidence",
|
||||
}
|
||||
for opt_key, cfg_key in conf_map.items():
|
||||
if opt_key in best_params:
|
||||
val = best_params[opt_key]
|
||||
content = re.sub(
|
||||
rf'("{cfg_key}":\s*)[\d.\-e]+',
|
||||
rf'\g<1>{val}',
|
||||
content
|
||||
)
|
||||
|
||||
CONFIG_PATH.write_text(content, encoding="utf-8")
|
||||
logger.info("✏️ 配置已更新")
|
||||
|
||||
|
||||
def restart_ea():
|
||||
"""通过信号文件通知 restart_ea.sh 重启"""
|
||||
RESTART_SIGNAL.write_text(datetime.now().isoformat())
|
||||
logger.info("📡 已发送重启信号")
|
||||
|
||||
|
||||
def main():
|
||||
logger.info("=" * 60)
|
||||
logger.info("📅 每日自动优化启动")
|
||||
logger.info(f"时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
logger.info("=" * 60)
|
||||
|
||||
# 1. 备份当前配置
|
||||
backup_config()
|
||||
|
||||
# 2. 运行优化
|
||||
try:
|
||||
best_params = run_optimizer()
|
||||
except Exception as e:
|
||||
logger.error(f"优化失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return 1
|
||||
|
||||
# 3. 写入 config.py
|
||||
update_config(best_params)
|
||||
|
||||
# 4. 触发重启
|
||||
restart_ea()
|
||||
|
||||
logger.info("✅ 每日优化流程完成")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
# 重启 EA:杀旧进程 → 应用新配置 → 启动新进程
|
||||
# 由 daily_optimize.py 或 cron 调用
|
||||
|
||||
set -e
|
||||
PROJECT_DIR="/home/songkl/mt5_python_ea_suite"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 重启 EA..."
|
||||
|
||||
# 1. 杀掉所有 realtime.py 进程
|
||||
pkill -f "python.*run/realtime.py" 2>/dev/null || true
|
||||
sleep 2
|
||||
|
||||
# 强制清理残留
|
||||
pkill -9 -f "python.*run/realtime.py" 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
# 2. 清空旧交易记录(新配置从零开始)
|
||||
rm -f realtime_trades_*.json realtime_trades_*.csv
|
||||
|
||||
# 3. 清空日志
|
||||
> logs/strategy.log
|
||||
|
||||
# 4. 启动新 EA
|
||||
nohup python3 run/realtime.py >> /dev/null 2>&1 &
|
||||
NEW_PID=$!
|
||||
echo $NEW_PID > .ea_pid
|
||||
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] EA 已重启 PID=$NEW_PID"
|
||||
Reference in New Issue
Block a user