Files
mt5_python_ea_suite/scripts/daily_optimize.py
T

297 lines
10 KiB
Python
Raw Normal View History

#!/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, fitness)"""
from execution.optimize import run_optimizer as _run
logger.info("🧬 开始遗传算法优化...")
best_params, fitness = _run()
logger.info(f"✅ 优化完成 适应度={fitness:.2f}")
return best_params, fitness
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, fitness: float = 0.0):
"""将优化结果写回 config.py"""
content = CONFIG_PATH.read_text(encoding="utf-8")
# ═══ RISK_CONFIG — 手动设定,不进优化器 ═══
# optimizer.py PARAM_SPACE 已移除风控基因,此 map 清空)
risk_map = {
}
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"),
# SwingPointRetestStrategy
"swing_point_left_bars": ("swing_point", "left_bars"),
"swing_point_right_bars": ("swing_point", "right_bars"),
"swing_point_tolerance_pct": ("swing_point", "tolerance_pct"),
"swing_point_num_swings": ("swing_point", "num_swings"),
# 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_SwingPointRetestStrategy": "swing_point",
"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
)
# ═══ LAST_OPTIMIZATION_FITNESS ═══
content = re.sub(
r'LAST_OPTIMIZATION_FITNESS\s*=\s*[\d.\-e]+',
f'LAST_OPTIMIZATION_FITNESS = {fitness:.2f}',
content
)
CONFIG_PATH.write_text(content, encoding="utf-8")
logger.info("✏️ 配置已更新")
def restart_ea():
"""杀掉旧 EA → 清日志 → 启动新 EA(应用优化后的配置)"""
import subprocess
logger.info("🔄 重启 EA...")
# 杀旧进程,等锁释放再启动新的
subprocess.run(["pkill", "-f", "python.*run/realtime.py"], capture_output=True)
import time; time.sleep(2)
subprocess.run(["pkill", "-9", "-f", "python.*run/realtime.py"], capture_output=True)
# 确认旧进程已死 + 锁已释放(轮询最多等 5 秒)
lock_file = PROJECT_DIR / ".ea.lock"
import fcntl as _fcntl
for _ in range(10):
time.sleep(0.5)
try:
fd = os.open(str(lock_file), os.O_RDONLY)
_fcntl.flock(fd, _fcntl.LOCK_EX | _fcntl.LOCK_NB)
os.close(fd) # 立即释放,只是测试
break
except (BlockingIOError, OSError):
pass
else:
logger.warning("⚠️ 旧进程锁未释放,强制启动(旧进程可能僵死)")
# 清空旧交易记录
for f in PROJECT_DIR.glob("realtime_trades_*"):
f.unlink(missing_ok=True)
# 清日志
log_file = PROJECT_DIR / "logs" / "strategy.log"
log_file.write_text("")
# 启动新 EA(后台)
log = open(log_file, "a")
proc = subprocess.Popen(
[sys.executable, "run/realtime.py"],
cwd=str(PROJECT_DIR),
stdout=log, stderr=subprocess.STDOUT,
)
pid_file = PROJECT_DIR / ".ea_pid"
pid_file.write_text(str(proc.pid))
logger.info(f"✅ EA 已重启 PID={proc.pid}")
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, fitness = run_optimizer()
except Exception as e:
logger.error(f"优化失败: {e}")
import traceback
traceback.print_exc()
return 1
# 3. 写入 config.py(含适应度)
update_config(best_params, fitness)
# 4. ★ EA 通过 config.reload() 自动热加载,无需重启
logger.info("✅ 每日优化流程完成 (EA 将在下个周期自动读取新配置)")
return 0
if __name__ == "__main__":
sys.exit(main())