Files
mt5_python_ea_suite/scripts/daily_optimize.py
T
silencesdg e1691c3c41 feat: 适应度门槛95+swing_point策略+多项改进
- 新增适应度门槛: min_backtest_fitness=95, 适应度<95暂停开仓
- 新增 SwingPointRetest 策略替代 Turtle
- 新增 monday_reset.py 周重置脚本
- exit_rules: 拖尾止损相对回撤模式
- market_state: 趋势检测优化
- position: 一票制并发锁+合约规格缓存
- optimize: Optuna 替代 DEAP 遗传算法
- realtime_trader: 适应度门槛+同向递增
- weights: 动态权重管理
- cron_optimize: PYTHONPATH 修复
- .gitignore: 排除生成文件
2026-05-21 20:26:55 +08:00

297 lines
10 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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())