mirror of
https://github.com/silencesdg/mt5_python_ea_suite.git
synced 2026-07-28 03:07:48 +00:00
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: 排除生成文件
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#!/bin/bash
|
||||
# cron 环境修复:设置正确的 PYTHONPATH 后运行优化器
|
||||
export PYTHONPATH="/home/songkl/.hermes/profiles/bot1/home/.local/lib/python3.13/site-packages:$PYTHONPATH"
|
||||
# 每日自动优化 + 重启 EA
|
||||
# 设置 PYTHONPATH 确保 cron 环境下能找到 user-site packages(如 deap, moocore 等)
|
||||
export PYTHONPATH="$HOME/.local/lib/python$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')/site-packages:$PYTHONPATH"
|
||||
cd /home/songkl/mt5_python_ea_suite
|
||||
exec /usr/bin/python3 scripts/daily_optimize.py "$@"
|
||||
exec python3 scripts/daily_optimize.py "$@"
|
||||
|
||||
+39
-23
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""每日自动优化 — 遗传算法跑参数 → 写入config → 重启实盘EA
|
||||
"""每日自动优化 — 遗传算法跑参数 → 写入config → EA热加载生效(无需重启)
|
||||
|
||||
通过 cron 调用: python3 scripts/daily_optimize.py
|
||||
"""
|
||||
@@ -22,12 +22,12 @@ RESTART_SIGNAL = PROJECT_DIR / ".restart_signal"
|
||||
|
||||
|
||||
def run_optimizer():
|
||||
"""运行遗传算法优化,返回 best_params dict"""
|
||||
"""运行遗传算法优化,返回 (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
|
||||
return best_params, fitness
|
||||
|
||||
|
||||
def backup_config():
|
||||
@@ -39,18 +39,13 @@ def backup_config():
|
||||
logger.info(f"📦 已备份配置: {dst}")
|
||||
|
||||
|
||||
def update_config(best_params: dict):
|
||||
def update_config(best_params: dict, fitness: float = 0.0):
|
||||
"""将优化结果写回 config.py"""
|
||||
content = CONFIG_PATH.read_text(encoding="utf-8")
|
||||
|
||||
# ═══ RISK_CONFIG ═══
|
||||
# ═══ RISK_CONFIG — 手动设定,不进优化器 ═══
|
||||
# (optimizer.py PARAM_SPACE 已移除风控基因,此 map 清空)
|
||||
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:
|
||||
@@ -115,8 +110,11 @@ def update_config(best_params: dict):
|
||||
"momentum_breakout_momentum_period": ("momentum_breakout", "momentum_period"),
|
||||
# KDJStrategy
|
||||
"kdj_period": ("kdj", "period"),
|
||||
# TurtleStrategy
|
||||
"turtle_period": ("turtle", "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
|
||||
@@ -151,7 +149,7 @@ def update_config(best_params: dict):
|
||||
"weight_MomentumBreakoutStrategy": "momentum_breakout",
|
||||
"weight_MACDStrategy": "macd",
|
||||
"weight_KDJStrategy": "kdj",
|
||||
"weight_TurtleStrategy": "turtle",
|
||||
"weight_SwingPointRetestStrategy": "swing_point",
|
||||
"weight_DailyBreakoutStrategy": "daily_breakout",
|
||||
"weight_WaveTheoryStrategy": "wave_theory",
|
||||
}
|
||||
@@ -213,6 +211,13 @@ def update_config(best_params: dict):
|
||||
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("✏️ 配置已更新")
|
||||
|
||||
@@ -222,11 +227,24 @@ def restart_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)
|
||||
time.sleep(1)
|
||||
# 确认旧进程已死 + 锁已释放(轮询最多等 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_*"):
|
||||
@@ -259,20 +277,18 @@ def main():
|
||||
|
||||
# 2. 运行优化
|
||||
try:
|
||||
best_params = run_optimizer()
|
||||
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)
|
||||
# 3. 写入 config.py(含适应度)
|
||||
update_config(best_params, fitness)
|
||||
|
||||
# 4. 触发重启
|
||||
restart_ea()
|
||||
|
||||
logger.info("✅ 每日优化流程完成")
|
||||
# 4. ★ EA 通过 config.reload() 自动热加载,无需重启
|
||||
logger.info("✅ 每日优化流程完成 (EA 将在下个周期自动读取新配置)")
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
"""开市后全平持仓 + 清峰值 + 重启 EA"""
|
||||
import requests, json, time, sys, os, subprocess
|
||||
|
||||
API = "http://192.168.1.5:5555/api"
|
||||
SYMBOL = "XAUUSDz"
|
||||
PROJECT_DIR = "/home/songkl/mt5_python_ea_suite"
|
||||
|
||||
def log(msg):
|
||||
print(f"[{time.strftime('%H:%M:%S')}] {msg}")
|
||||
|
||||
# 1. 等市场开市(重试最多30次,间隔10秒)
|
||||
log("等待市场开市...")
|
||||
for i in range(30):
|
||||
try:
|
||||
r = requests.get(f"{API}/account", timeout=5)
|
||||
if r.status_code == 200:
|
||||
acct = r.json()
|
||||
if acct.get("trade_allowed"):
|
||||
log("✅ 市场已开市")
|
||||
break
|
||||
except:
|
||||
pass
|
||||
time.sleep(10)
|
||||
else:
|
||||
log("❌ 等待超时,市场未开市")
|
||||
sys.exit(1)
|
||||
|
||||
# 2. 获取所有持仓并平仓
|
||||
log("获取持仓...")
|
||||
r = requests.get(f"{API}/positions/{SYMBOL}")
|
||||
positions = r.json().get("positions", [])
|
||||
log(f"共 {len(positions)} 单")
|
||||
|
||||
total_pnl = 0
|
||||
for p in positions:
|
||||
resp = requests.post(f"{API}/close",
|
||||
json={"ticket": p["ticket"], "symbol": SYMBOL, "volume": p["volume"]})
|
||||
ok = resp.json().get("success", False)
|
||||
total_pnl += p["profit"]
|
||||
log(f"{'✅' if ok else '❌'} {p['ticket']} ${p['profit']:+.2f}")
|
||||
log(f"总盈亏: ${total_pnl:+.2f}")
|
||||
|
||||
# 3. 清峰值数据
|
||||
peak_file = os.path.join(PROJECT_DIR, "position_peaks.json")
|
||||
if os.path.exists(peak_file):
|
||||
os.remove(peak_file)
|
||||
log("✅ 峰值数据已清除")
|
||||
|
||||
# 4. 杀掉旧 EA,启动新 EA
|
||||
subprocess.run(["pkill", "-9", "-f", "python.*run/realtime.py"], capture_output=True)
|
||||
time.sleep(2)
|
||||
|
||||
log_file = os.path.join(PROJECT_DIR, "logs", "strategy.log")
|
||||
with open(log_file, "a") as f:
|
||||
proc = subprocess.Popen(
|
||||
[sys.executable, "run/realtime.py"],
|
||||
cwd=PROJECT_DIR,
|
||||
stdout=f, stderr=subprocess.STDOUT,
|
||||
)
|
||||
log(f"✅ EA 已重启 PID={proc.pid}")
|
||||
Reference in New Issue
Block a user