|
|
//+------------------------------------------------------------------+
//| 恒鑫EA量化-究极强化版.mq5 |
//+------------------------------------------------------------------+
#property description "声明:"
#property description "1.本EA只作为交易辅助工具,不作任何盈利保证;"
#property description "2.投资风险,入市需谨慎;"
#property description "3.用本软件参与一切非法行为,与作者和本软件无关;"
#property description "4.EA属于虚拟产品一旦发货正常加载使用不支持退货退款。"
#property description "5.QQ群:788238290"
//#property version "终极版本"
#property strict
//#resource "bg.bmp" // 背景图片资源(编译进ex5,发别人也能看到)
#include <Trade\Trade.mqh>
// --- Embedded USD high-impact backtest news start ---
// Auto-generated by generate_backtest_news_include.py
// Source: https://www.mql5.com/en/economic-calendar/content
// Filters: USD + high importance + exact datetime
// Range: 2025-01-01 .. 2026-08-08
// Generated: 2026-04-03 03:28:49 UTC
#define GOLDKING_BACKTEST_USD_HIGH_NEWS_COUNT 615
#define GOLDKING_BACKTEST_USD_HIGH_NEWS_RANGE_LABEL "2025-01-01~2026-04-03"
#define GOLDKING_BACKTEST_USD_HIGH_NEWS_RANGE_START D'2025.01.01 00:00:00'
#define GOLDKING_BACKTEST_USD_HIGH_NEWS_RANGE_END D'2026.04.03 23:59:59'
datetime GOLDKING_BACKTEST_USD_HIGH_NEWS_TIMES[GOLDKING_BACKTEST_USD_HIGH_NEWS_COUNT]=
{
D'2025.01.02 13:30:00',
D'2025.01.02 14:45:00',
D'2025.01.02 16:00:00',
D'2025.01.03 15:00:00',
D'2025.01.03 15:00:00',
D'2025.01.06 14:45:00',
D'2025.01.07 15:00:00',
D'2025.01.07 15:00:00',
D'2025.01.07 15:00:00',
D'2025.01.07 18:00:00',
D'2025.01.08 13:15:00',
D'2025.01.08 13:30:00',
D'2025.01.08 15:30:00',
D'2025.01.08 18:00:00',
D'2025.01.10 13:30:00',
D'2025.01.10 13:30:00',
D'2025.01.10 13:30:00',
D'2025.01.14 13:30:00',
D'2025.01.15 13:30:00',
D'2025.01.15 13:30:00',
D'2025.01.15 13:30:00',
D'2025.01.15 13:30:00',
D'2025.01.15 13:30:00',
D'2025.01.15 15:30:00',
D'2025.01.16 13:30:00',
D'2025.01.16 13:30:00',
D'2025.01.16 13:30:00',
D'2025.01.16 13:30:00',
D'2025.01.23 13:30:00',
D'2025.01.23 17:00:00',
D'2025.01.24 15:00:00',
D'2025.01.27 15:00:00',
D'2025.01.28 13:30:00',
D'2025.01.28 15:00:00',
D'2025.01.29 15:30:00',
D'2025.01.29 19:00:00',
D'2025.01.29 19:30:00',
D'2025.01.30 13:30:00',
D'2025.01.30 13:30:00',
D'2025.01.30 13:30:00',
D'2025.01.31 13:30:00',
D'2025.01.31 13:30:00',
D'2025.01.31 14:45:00',
D'2025.02.03 14:45:00',
D'2025.02.03 15:00:00',
D'2025.02.03 15:00:00',
D'2025.02.04 15:00:00',
D'2025.02.05 13:15:00',
D'2025.02.05 14:45:00',
D'2025.02.05 15:00:00',
D'2025.02.05 15:00:00',
D'2025.02.05 15:30:00',
D'2025.02.06 13:30:00',
D'2025.02.07 13:30:00',
D'2025.02.07 13:30:00',
D'2025.02.07 13:30:00',
D'2025.02.11 15:00:00',
D'2025.02.12 13:30:00',
D'2025.02.12 13:30:00',
D'2025.02.12 13:30:00',
D'2025.02.12 13:30:00',
D'2025.02.12 13:30:00',
D'2025.02.12 15:00:00',
D'2025.02.12 15:30:00',
D'2025.02.12 18:00:00',
D'2025.02.13 13:30:00',
D'2025.02.13 13:30:00',
D'2025.02.13 18:00:00',
D'2025.02.14 13:30:00',
D'2025.02.14 13:30:00',
D'2025.02.20 13:30:00',
D'2025.02.20 13:30:00',
D'2025.02.20 17:00:00',
D'2025.02.21 15:00:00',
D'2025.02.25 15:00:00',
D'2025.02.26 15:00:00',
D'2025.02.26 15:30:00',
D'2025.02.27 13:30:00',
D'2025.02.27 13:30:00',
D'2025.02.27 13:30:00',
D'2025.02.27 13:30:00',
D'2025.02.28 13:30:00',
D'2025.02.28 13:30:00',
D'2025.02.28 14:45:00',
D'2025.03.03 14:45:00',
D'2025.03.03 15:00:00',
D'2025.03.03 15:00:00',
D'2025.03.05 13:15:00',
D'2025.03.05 14:45:00',
D'2025.03.05 15:00:00',
D'2025.03.05 15:00:00',
D'2025.03.05 15:30:00',
D'2025.03.06 13:30:00',
D'2025.03.07 13:30:00',
D'2025.03.07 13:30:00',
D'2025.03.07 13:30:00',
D'2025.03.07 17:30:00',
D'2025.03.11 14:00:00',
D'2025.03.12 12:30:00',
D'2025.03.12 12:30:00',
D'2025.03.12 12:30:00',
D'2025.03.12 12:30:00',
D'2025.03.12 12:30:00',
D'2025.03.12 14:30:00',
D'2025.03.12 17:00:00',
D'2025.03.13 12:30:00',
D'2025.03.13 12:30:00',
D'2025.03.13 17:00:00',
D'2025.03.17 12:30:00',
D'2025.03.17 12:30:00',
D'2025.03.19 14:30:00',
D'2025.03.19 18:00:00',
D'2025.03.19 18:30:00',
D'2025.03.20 12:30:00',
D'2025.03.20 12:30:00',
D'2025.03.20 14:00:00',
D'2025.03.25 14:00:00',
D'2025.03.25 14:00:00',
D'2025.03.26 12:30:00',
D'2025.03.26 14:30:00',
D'2025.03.27 12:30:00',
D'2025.03.27 12:30:00',
D'2025.03.27 12:30:00',
D'2025.03.28 12:30:00',
D'2025.03.28 12:30:00',
D'2025.03.31 13:45:00',
D'2025.04.01 13:45:00',
D'2025.04.01 14:00:00',
D'2025.04.01 14:00:00',
D'2025.04.01 14:00:00',
D'2025.04.02 12:15:00',
D'2025.04.02 14:30:00',
D'2025.04.03 12:30:00',
D'2025.04.03 13:45:00',
D'2025.04.03 14:00:00',
D'2025.04.03 14:00:00',
D'2025.04.04 12:30:00',
D'2025.04.04 12:30:00',
D'2025.04.04 12:30:00',
D'2025.04.04 15:25:00',
D'2025.04.09 14:30:00',
D'2025.04.09 17:00:00',
D'2025.04.10 12:30:00',
D'2025.04.10 12:30:00',
D'2025.04.10 12:30:00',
D'2025.04.10 12:30:00',
D'2025.04.10 12:30:00',
D'2025.04.10 12:30:00',
D'2025.04.10 17:00:00',
D'2025.04.11 12:30:00',
D'2025.04.16 12:30:00',
D'2025.04.16 12:30:00',
D'2025.04.16 14:30:00',
D'2025.04.16 17:30:00',
D'2025.04.17 12:30:00',
D'2025.04.17 12:30:00',
D'2025.04.23 14:00:00',
D'2025.04.23 14:30:00',
D'2025.04.24 12:30:00',
D'2025.04.24 12:30:00',
D'2025.04.24 14:00:00',
D'2025.04.29 14:00:00',
D'2025.04.29 14:00:00',
D'2025.04.30 12:15:00',
D'2025.04.30 12:30:00',
D'2025.04.30 12:30:00',
D'2025.04.30 13:45:00',
D'2025.04.30 14:00:00',
D'2025.04.30 14:00:00',
D'2025.04.30 14:30:00',
D'2025.05.01 12:30:00',
D'2025.05.01 13:45:00',
D'2025.05.01 14:00:00',
D'2025.05.01 14:00:00',
D'2025.05.02 12:30:00',
D'2025.05.02 12:30:00',
D'2025.05.02 12:30:00',
D'2025.05.05 13:45:00',
D'2025.05.05 14:00:00',
D'2025.05.05 14:00:00',
D'2025.05.06 17:00:00',
D'2025.05.07 14:30:00',
D'2025.05.07 18:00:00',
D'2025.05.07 18:30:00',
D'2025.05.08 12:30:00',
D'2025.05.08 17:00:00',
D'2025.05.13 12:30:00',
D'2025.05.13 12:30:00',
D'2025.05.13 12:30:00',
D'2025.05.13 12:30:00',
D'2025.05.13 12:30:00',
D'2025.05.14 14:30:00',
D'2025.05.15 12:30:00',
D'2025.05.15 12:30:00',
D'2025.05.15 12:30:00',
D'2025.05.15 12:30:00',
D'2025.05.15 12:30:00',
D'2025.05.15 12:40:00',
D'2025.05.21 14:30:00',
D'2025.05.22 12:30:00',
D'2025.05.22 14:00:00',
D'2025.05.23 14:00:00',
D'2025.05.25 18:40:00',
D'2025.05.27 12:30:00',
D'2025.05.27 14:00:00',
D'2025.05.29 12:30:00',
D'2025.05.29 12:30:00',
D'2025.05.29 12:30:00',
D'2025.05.29 16:00:00',
D'2025.05.30 12:30:00',
D'2025.05.30 12:30:00',
D'2025.05.30 13:45:00',
D'2025.06.02 13:45:00',
D'2025.06.02 14:00:00',
D'2025.06.02 14:00:00',
D'2025.06.02 17:00:00',
D'2025.06.03 14:00:00',
D'2025.06.04 12:15:00',
D'2025.06.04 13:45:00',
D'2025.06.04 14:00:00',
D'2025.06.04 14:00:00',
D'2025.06.04 14:30:00',
D'2025.06.05 12:30:00',
D'2025.06.06 12:30:00',
D'2025.06.06 12:30:00',
D'2025.06.06 12:30:00',
D'2025.06.11 12:30:00',
D'2025.06.11 12:30:00',
D'2025.06.11 12:30:00',
D'2025.06.11 12:30:00',
D'2025.06.11 12:30:00',
D'2025.06.11 14:30:00',
D'2025.06.11 17:00:00',
D'2025.06.12 12:30:00',
D'2025.06.12 12:30:00',
D'2025.06.12 17:00:00',
D'2025.06.17 12:30:00',
D'2025.06.17 12:30:00',
D'2025.06.18 12:30:00',
D'2025.06.18 14:30:00',
D'2025.06.18 18:00:00',
D'2025.06.18 18:30:00',
D'2025.06.20 12:30:00',
D'2025.06.23 14:00:00',
D'2025.06.24 14:00:00',
D'2025.06.24 14:00:00',
D'2025.06.25 14:00:00',
D'2025.06.25 14:00:00',
D'2025.06.25 14:30:00',
D'2025.06.26 12:30:00',
D'2025.06.26 12:30:00',
D'2025.06.26 12:30:00',
D'2025.06.26 12:30:00',
D'2025.06.27 12:30:00',
D'2025.06.27 12:30:00',
D'2025.06.30 13:45:00',
D'2025.07.01 13:30:00',
D'2025.07.01 13:45:00',
D'2025.07.01 14:00:00',
D'2025.07.01 14:00:00',
D'2025.07.01 14:00:00',
D'2025.07.02 12:15:00',
D'2025.07.02 14:30:00',
D'2025.07.03 12:30:00',
D'2025.07.03 12:30:00',
D'2025.07.03 12:30:00',
D'2025.07.03 12:30:00',
D'2025.07.03 13:45:00',
D'2025.07.03 14:00:00',
D'2025.07.03 14:00:00',
D'2025.07.09 14:30:00',
D'2025.07.09 17:00:00',
D'2025.07.10 12:30:00',
D'2025.07.10 17:00:00',
D'2025.07.15 12:30:00',
D'2025.07.15 12:30:00',
D'2025.07.15 12:30:00',
D'2025.07.15 12:30:00',
D'2025.07.15 12:30:00',
D'2025.07.16 12:30:00',
D'2025.07.16 14:30:00',
D'2025.07.17 12:30:00',
D'2025.07.17 12:30:00',
D'2025.07.17 12:30:00',
D'2025.07.17 12:30:00',
D'2025.07.22 12:30:00',
D'2025.07.23 14:00:00',
D'2025.07.23 14:30:00',
D'2025.07.24 12:30:00',
D'2025.07.24 14:00:00',
D'2025.07.25 12:30:00',
D'2025.07.29 14:00:00',
D'2025.07.29 14:00:00',
D'2025.07.30 12:15:00',
D'2025.07.30 12:30:00',
D'2025.07.30 12:30:00',
D'2025.07.30 14:30:00',
D'2025.07.30 18:00:00',
D'2025.07.30 18:30:00',
D'2025.07.31 12:30:00',
D'2025.07.31 12:30:00',
D'2025.07.31 12:30:00',
D'2025.07.31 13:45:00',
D'2025.08.01 12:30:00',
D'2025.08.01 12:30:00',
D'2025.08.01 12:30:00',
D'2025.08.01 13:45:00',
D'2025.08.01 14:00:00',
D'2025.08.01 14:00:00',
D'2025.08.05 13:45:00',
D'2025.08.05 14:00:00',
D'2025.08.05 14:00:00',
D'2025.08.06 14:30:00',
D'2025.08.06 17:00:00',
D'2025.08.07 12:30:00',
D'2025.08.07 17:00:00',
D'2025.08.12 12:30:00',
D'2025.08.12 12:30:00',
D'2025.08.12 12:30:00',
D'2025.08.12 12:30:00',
D'2025.08.12 12:30:00',
D'2025.08.13 14:30:00',
D'2025.08.14 12:30:00',
D'2025.08.14 12:30:00',
D'2025.08.15 12:30:00',
D'2025.08.15 12:30:00',
D'2025.08.20 14:30:00',
D'2025.08.21 12:30:00',
D'2025.08.21 12:30:00',
D'2025.08.21 14:00:00',
D'2025.08.22 14:00:00',
D'2025.08.25 14:00:00',
D'2025.08.26 12:30:00',
D'2025.08.26 14:00:00',
D'2025.08.27 14:30:00',
D'2025.08.28 12:30:00',
D'2025.08.28 12:30:00',
D'2025.08.28 12:30:00',
D'2025.08.29 12:30:00',
D'2025.08.29 12:30:00',
D'2025.08.29 13:45:00',
D'2025.09.02 13:45:00',
D'2025.09.02 14:00:00',
D'2025.09.02 14:00:00',
D'2025.09.03 14:00:00',
D'2025.09.04 12:15:00',
D'2025.09.04 12:30:00',
D'2025.09.04 13:45:00',
D'2025.09.04 14:00:00',
D'2025.09.04 14:00:00',
D'2025.09.04 16:00:00',
D'2025.09.05 12:30:00',
D'2025.09.05 12:30:00',
D'2025.09.05 12:30:00',
D'2025.09.10 12:30:00',
D'2025.09.10 14:30:00',
D'2025.09.10 17:00:00',
D'2025.09.11 12:30:00',
D'2025.09.11 12:30:00',
D'2025.09.11 12:30:00',
D'2025.09.11 12:30:00',
D'2025.09.11 12:30:00',
D'2025.09.11 12:30:00',
D'2025.09.11 17:00:00',
D'2025.09.16 12:30:00',
D'2025.09.16 12:30:00',
D'2025.09.17 14:30:00',
D'2025.09.17 18:00:00',
D'2025.09.17 18:30:00',
D'2025.09.18 12:30:00',
D'2025.09.18 12:30:00',
D'2025.09.23 14:00:00',
D'2025.09.23 16:35:00',
D'2025.09.24 14:00:00',
D'2025.09.24 14:30:00',
D'2025.09.25 12:30:00',
D'2025.09.25 12:30:00',
D'2025.09.25 12:30:00',
D'2025.09.25 12:30:00',
D'2025.09.26 12:30:00',
D'2025.09.26 12:30:00',
D'2025.09.30 13:45:00',
D'2025.09.30 14:00:00',
D'2025.09.30 14:00:00',
D'2025.10.01 12:15:00',
D'2025.10.01 13:45:00',
D'2025.10.01 14:00:00',
D'2025.10.01 14:00:00',
D'2025.10.01 14:30:00',
D'2025.10.03 13:45:00',
D'2025.10.05 14:00:00',
D'2025.10.05 14:00:00',
D'2025.10.08 14:30:00',
D'2025.10.08 17:00:00',
D'2025.10.09 12:30:00',
D'2025.10.09 17:00:00',
D'2025.10.14 16:20:00',
D'2025.10.16 12:30:00',
D'2025.10.16 16:00:00',
D'2025.10.22 14:30:00',
D'2025.10.23 14:00:00',
D'2025.10.24 12:30:00',
D'2025.10.24 12:30:00',
D'2025.10.24 12:30:00',
D'2025.10.24 12:30:00',
D'2025.10.24 12:30:00',
D'2025.10.28 14:00:00',
D'2025.10.29 14:30:00',
D'2025.10.29 18:00:00',
D'2025.10.29 18:30:00',
D'2025.10.31 13:45:00',
D'2025.11.03 14:45:00',
D'2025.11.03 15:00:00',
D'2025.11.03 15:00:00',
D'2025.11.05 13:15:00',
D'2025.11.05 14:45:00',
D'2025.11.05 15:00:00',
D'2025.11.05 15:00:00',
D'2025.11.05 15:30:00',
D'2025.11.12 18:00:00',
D'2025.11.13 17:00:00',
D'2025.11.13 18:00:00',
D'2025.11.19 15:30:00',
D'2025.11.20 12:30:00',
D'2025.11.20 12:30:00',
D'2025.11.20 12:30:00',
D'2025.11.20 13:30:00',
D'2025.11.20 13:30:00',
D'2025.11.20 15:00:00',
D'2025.11.25 12:30:00',
D'2025.11.25 12:30:00',
D'2025.11.25 12:30:00',
D'2025.11.25 15:00:00',
D'2025.11.26 12:30:00',
D'2025.11.26 13:30:00',
D'2025.11.26 14:45:00',
D'2025.11.26 15:30:00',
D'2025.12.01 14:45:00',
D'2025.12.01 15:00:00',
D'2025.12.01 15:00:00',
D'2025.12.02 01:00:00',
D'2025.12.03 13:15:00',
D'2025.12.03 14:45:00',
D'2025.12.03 15:00:00',
D'2025.12.03 15:00:00',
D'2025.12.03 15:30:00',
D'2025.12.04 13:30:00',
D'2025.12.05 15:00:00',
D'2025.12.05 15:00:00',
D'2025.12.09 15:00:00',
D'2025.12.09 18:00:00',
D'2025.12.10 15:30:00',
D'2025.12.10 19:00:00',
D'2025.12.10 19:30:00',
D'2025.12.11 13:30:00',
D'2025.12.11 18:00:00',
D'2025.12.16 13:30:00',
D'2025.12.16 13:30:00',
D'2025.12.16 13:30:00',
D'2025.12.16 13:30:00',
D'2025.12.16 13:30:00',
D'2025.12.17 15:30:00',
D'2025.12.18 13:30:00',
D'2025.12.18 13:30:00',
D'2025.12.18 13:30:00',
D'2025.12.18 13:30:00',
D'2025.12.18 13:30:00',
D'2025.12.18 13:30:00',
D'2025.12.18 13:30:00',
D'2025.12.19 15:00:00',
D'2025.12.23 13:30:00',
D'2025.12.23 13:30:00',
D'2025.12.23 13:30:00',
D'2025.12.23 15:00:00',
D'2025.12.24 13:30:00',
D'2025.12.24 15:30:00',
D'2025.12.29 22:00:00',
D'2025.12.30 14:45:00',
D'2025.12.31 13:30:00',
D'2026.01.02 14:45:00',
D'2026.01.05 15:00:00',
D'2026.01.05 15:00:00',
D'2026.01.06 14:45:00',
D'2026.01.07 13:15:00',
D'2026.01.07 15:00:00',
D'2026.01.07 15:00:00',
D'2026.01.07 15:00:00',
D'2026.01.07 15:30:00',
D'2026.01.08 13:30:00',
D'2026.01.09 13:30:00',
D'2026.01.09 13:30:00',
D'2026.01.09 13:30:00',
D'2026.01.12 18:00:00',
D'2026.01.13 13:30:00',
D'2026.01.13 13:30:00',
D'2026.01.13 13:30:00',
D'2026.01.13 13:30:00',
D'2026.01.13 13:30:00',
D'2026.01.13 15:00:00',
D'2026.01.13 18:00:00',
D'2026.01.14 13:30:00',
D'2026.01.14 13:30:00',
D'2026.01.14 13:30:00',
D'2026.01.14 15:30:00',
D'2026.01.15 13:30:00',
D'2026.01.15 13:30:00',
D'2026.01.22 13:30:00',
D'2026.01.22 13:30:00',
D'2026.01.22 13:30:00',
D'2026.01.22 15:00:00',
D'2026.01.22 15:00:00',
D'2026.01.22 17:00:00',
D'2026.01.26 13:30:00',
D'2026.01.27 15:00:00',
D'2026.01.28 15:30:00',
D'2026.01.28 19:00:00',
D'2026.01.28 19:30:00',
D'2026.01.29 13:30:00',
D'2026.01.30 13:30:00',
D'2026.01.30 14:45:00',
D'2026.02.02 15:00:00',
D'2026.02.02 15:00:00',
D'2026.02.04 13:15:00',
D'2026.02.04 15:00:00',
D'2026.02.04 15:00:00',
D'2026.02.04 15:30:00',
D'2026.02.05 13:30:00',
D'2026.02.05 15:00:00',
D'2026.02.10 13:30:00',
D'2026.02.10 13:30:00',
D'2026.02.11 13:30:00',
D'2026.02.11 13:30:00',
D'2026.02.11 13:30:00',
D'2026.02.11 15:30:00',
D'2026.02.11 18:00:00',
D'2026.02.12 13:30:00',
D'2026.02.12 15:00:00',
D'2026.02.12 18:00:00',
D'2026.02.13 13:30:00',
D'2026.02.13 13:30:00',
D'2026.02.13 13:30:00',
D'2026.02.13 13:30:00',
D'2026.02.13 13:30:00',
D'2026.02.18 13:30:00',
D'2026.02.19 13:30:00',
D'2026.02.19 13:30:00',
D'2026.02.19 17:00:00',
D'2026.02.20 13:30:00',
D'2026.02.20 13:30:00',
D'2026.02.20 13:30:00',
D'2026.02.20 13:30:00',
D'2026.02.20 15:00:00',
D'2026.02.24 15:00:00',
D'2026.02.25 15:30:00',
D'2026.02.26 13:30:00',
D'2026.02.27 13:30:00',
D'2026.02.27 14:45:00',
D'2026.03.02 14:45:00',
D'2026.03.02 15:00:00',
D'2026.03.02 15:00:00',
D'2026.03.04 13:15:00',
D'2026.03.04 14:45:00',
D'2026.03.04 15:00:00',
D'2026.03.04 15:00:00',
D'2026.03.04 15:30:00',
D'2026.03.05 13:30:00',
D'2026.03.06 13:30:00',
D'2026.03.06 13:30:00',
D'2026.03.06 13:30:00',
D'2026.03.06 13:30:00',
D'2026.03.06 13:30:00',
D'2026.03.10 14:00:00',
D'2026.03.11 12:30:00',
D'2026.03.11 12:30:00',
D'2026.03.11 12:30:00',
D'2026.03.11 12:30:00',
D'2026.03.11 12:30:00',
D'2026.03.11 14:30:00',
D'2026.03.11 17:00:00',
D'2026.03.12 12:30:00',
D'2026.03.12 17:00:00',
D'2026.03.13 12:30:00',
D'2026.03.13 12:30:00',
D'2026.03.13 12:30:00',
D'2026.03.13 12:30:00',
D'2026.03.13 12:30:00',
D'2026.03.13 14:00:00',
D'2026.03.18 12:30:00',
D'2026.03.18 14:30:00',
D'2026.03.18 18:00:00',
D'2026.03.18 18:30:00',
D'2026.03.19 12:30:00',
D'2026.03.19 12:30:00',
D'2026.03.19 14:00:00',
D'2026.03.21 17:30:00',
D'2026.03.25 14:30:00',
D'2026.03.26 12:30:00',
D'2026.03.30 14:30:00',
D'2026.03.31 13:45:00',
D'2026.03.31 14:00:00',
D'2026.03.31 14:00:00',
D'2026.04.01 12:15:00',
D'2026.04.01 12:30:00',
D'2026.04.01 12:30:00',
D'2026.04.01 13:45:00',
D'2026.04.01 14:00:00',
D'2026.04.01 14:00:00',
D'2026.04.01 14:30:00',
D'2026.04.02 12:30:00',
D'2026.04.03 12:30:00',
D'2026.04.03 12:30:00',
D'2026.04.03 12:30:00',
D'2026.04.03 13:45:00',
D'2026.04.03 14:00:00',
D'2026.04.03 14:00:00'
};
string GOLDKING_BACKTEST_USD_HIGH_NEWS_NAMES[GOLDKING_BACKTEST_USD_HIGH_NEWS_COUNT]=
{
"Initial Jobless Claims",
"S& Global Manufacturing PMI",
"EIA Crude Oil Stocks Change",
"ISM Manufacturing PMI",
"ISM Manufacturing Prices Paid",
"S& Global Services PMI",
"ISM Non-Manufacturing PMI",
"ISM Non-Manufacturing Prices Paid",
"JOLTS Job Openings",
"10-Year Note Auction",
"ADP Nonfarm Employment Change",
"Initial Jobless Claims",
"EIA Crude Oil Stocks Change",
"30-Year Bond Auction",
"Average Hourly Earnings m/m",
"Nonfarm Payrolls",
"Unemployment Rate",
" PI m/m",
"CPI",
"CPI m/m",
"CPI y/y",
"Core CPI m/m",
"Core CPI n.s.a. m/m",
"EIA Crude Oil Stocks Change",
"Core Retail Sales m/m",
"Initial Jobless Claims",
" hiladelphia Fed Manufacturing Index",
"Retail Sales m/m",
"Initial Jobless Claims",
"EIA Crude Oil Stocks Change",
"Existing Home Sales",
"New Home Sales",
"Durable Goods Orders m/m",
"CB Consumer Confidence Index",
"EIA Crude Oil Stocks Change",
"Fed Interest Rate Decision",
"FOMC Press Conference",
"GDP Sales q/q",
"GDP q/q",
"Initial Jobless Claims",
"Core PCE Price Index m/m",
"Core PCE Price Index y/y",
"MNI Chicago Business Barometer",
"S& Global Manufacturing PMI",
"ISM Manufacturing PMI",
"ISM Manufacturing Prices Paid",
"JOLTS Job Openings",
"ADP Nonfarm Employment Change",
"S& Global Services PMI",
"ISM Non-Manufacturing PMI",
"ISM Non-Manufacturing Prices Paid",
"EIA Crude Oil Stocks Change",
"Initial Jobless Claims",
"Average Hourly Earnings m/m",
"Nonfarm Payrolls",
"Unemployment Rate",
"Fed Chair Powell Testimony",
"CPI",
"CPI m/m",
"CPI y/y",
"Core CPI m/m",
"Core CPI n.s.a. m/m",
"Fed Chair Powell Testimony",
"EIA Crude Oil Stocks Change",
"10-Year Note Auction",
"Initial Jobless Claims",
" PI m/m",
"30-Year Bond Auction",
"Core Retail Sales m/m",
"Retail Sales m/m",
"Initial Jobless Claims",
" hiladelphia Fed Manufacturing Index",
"EIA Crude Oil Stocks Change",
"Existing Home Sales",
"CB Consumer Confidence Index",
"New Home Sales",
"EIA Crude Oil Stocks Change",
"Durable Goods Orders m/m",
"GDP Sales q/q",
"GDP q/q",
"Initial Jobless Claims",
"Core PCE Price Index m/m",
"Core PCE Price Index y/y",
"MNI Chicago Business Barometer",
"S& Global Manufacturing PMI",
"ISM Manufacturing PMI",
"ISM Manufacturing Prices Paid",
"ADP Nonfarm Employment Change",
"S& Global Services PMI",
"ISM Non-Manufacturing PMI",
"ISM Non-Manufacturing Prices Paid",
"EIA Crude Oil Stocks Change",
"Initial Jobless Claims",
"Average Hourly Earnings m/m",
"Nonfarm Payrolls",
"Unemployment Rate",
"Fed Chair Powell Speech",
"JOLTS Job Openings",
"CPI",
"CPI m/m",
"CPI y/y",
"Core CPI m/m",
"Core CPI n.s.a. m/m",
"EIA Crude Oil Stocks Change",
"10-Year Note Auction",
"Initial Jobless Claims",
"PPI m/m",
"30-Year Bond Auction",
"Core Retail Sales m/m",
"Retail Sales m/m",
"EIA Crude Oil Stocks Change",
"Fed Interest Rate Decision",
"FOMC Press Conference",
"Initial Jobless Claims",
"Philadelphia Fed Manufacturing Index",
"Existing Home Sales",
"CB Consumer Confidence Index",
"New Home Sales",
"Durable Goods Orders m/m",
"EIA Crude Oil Stocks Change",
"GDP Sales q/q",
"GDP q/q",
"Initial Jobless Claims",
"Core PCE Price Index m/m",
"Core PCE Price Index y/y",
"MNI Chicago Business Barometer",
"S&P Global Manufacturing PMI",
"ISM Manufacturing PMI",
"ISM Manufacturing Prices Paid",
"JOLTS Job Openings",
"ADP Nonfarm Employment Change",
"EIA Crude Oil Stocks Change",
"Initial Jobless Claims",
"S&P Global Services PMI",
"ISM Non-Manufacturing PMI",
"ISM Non-Manufacturing Prices Paid",
"Average Hourly Earnings m/m",
"Nonfarm Payrolls",
"Unemployment Rate",
"Fed Chair Powell Speech",
"EIA Crude Oil Stocks Change",
"10-Year Note Auction",
"CPI",
"CPI m/m",
"CPI y/y",
"Core CPI m/m",
"Core CPI n.s.a. m/m",
"Initial Jobless Claims",
"30-Year Bond Auction",
"PPI m/m",
"Core Retail Sales m/m",
"Retail Sales m/m",
"EIA Crude Oil Stocks Change",
"Fed Chair Powell Speech",
"Initial Jobless Claims",
"Philadelphia Fed Manufacturing Index",
"New Home Sales",
"EIA Crude Oil Stocks Change",
"Durable Goods Orders m/m",
"Initial Jobless Claims",
"Existing Home Sales",
"CB Consumer Confidence Index",
"JOLTS Job Openings",
"ADP Nonfarm Employment Change",
"GDP Sales q/q",
"GDP q/q",
"MNI Chicago Business Barometer",
"Core PCE Price Index m/m",
"Core PCE Price Index y/y",
"EIA Crude Oil Stocks Change",
"Initial Jobless Claims",
"S&P Global Manufacturing PMI",
"ISM Manufacturing PMI",
"ISM Manufacturing Prices Paid",
"Average Hourly Earnings m/m",
"Nonfarm Payrolls",
"Unemployment Rate",
"S&P Global Services PMI",
"ISM Non-Manufacturing PMI",
"ISM Non-Manufacturing Prices Paid",
"10-Year Note Auction",
"EIA Crude Oil Stocks Change",
"Fed Interest Rate Decision",
"FOMC Press Conference",
"Initial Jobless Claims",
"30-Year Bond Auction",
"CPI",
"CPI m/m",
"CPI y/y",
"Core CPI m/m",
"Core CPI n.s.a. m/m",
"EIA Crude Oil Stocks Change",
"Core Retail Sales m/m",
"Initial Jobless Claims",
"PPI m/m",
"Philadelphia Fed Manufacturing Index",
"Retail Sales m/m",
"Fed Chair Powell Speech",
"EIA Crude Oil Stocks Change",
"Initial Jobless Claims",
"Existing Home Sales",
"New Home Sales",
"Fed Chair Powell Speech",
"Durable Goods Orders m/m",
"CB Consumer Confidence Index",
"GDP Sales q/q",
"GDP q/q",
"Initial Jobless Claims",
"EIA Crude Oil Stocks Change",
"Core PCE Price Index m/m",
"Core PCE Price Index y/y",
"MNI Chicago Business Barometer",
"S&P Global Manufacturing PMI",
"ISM Manufacturing PMI",
"ISM Manufacturing Prices Paid",
"Fed Chair Powell Speech",
"JOLTS Job Openings",
"ADP Nonfarm Employment Change",
"S&P Global Services PMI",
"ISM Non-Manufacturing PMI",
"ISM Non-Manufacturing Prices Paid",
"EIA Crude Oil Stocks Change",
"Initial Jobless Claims",
"Average Hourly Earnings m/m",
"Nonfarm Payrolls",
"Unemployment Rate",
"CPI",
"CPI m/m",
"CPI y/y",
"Core CPI m/m",
"Core CPI n.s.a. m/m",
"EIA Crude Oil Stocks Change",
"10-Year Note Auction",
"Initial Jobless Claims",
"PPI m/m",
"30-Year Bond Auction",
"Core Retail Sales m/m",
"Retail Sales m/m",
"Initial Jobless Claims",
"EIA Crude Oil Stocks Change",
"Fed Interest Rate Decision",
"FOMC Press Conference",
"Philadelphia Fed Manufacturing Index",
"Existing Home Sales",
"CB Consumer Confidence Index",
"Fed Chair Powell Testimony",
"Fed Chair Powell Testimony",
"New Home Sales",
"EIA Crude Oil Stocks Change",
"Durable Goods Orders m/m",
"GDP Sales q/q",
"GDP q/q",
"Initial Jobless Claims",
"Core PCE Price Index m/m",
"Core PCE Price Index y/y",
"MNI Chicago Business Barometer",
"Fed Chair Powell Speech",
"S&P Global Manufacturing PMI",
"ISM Manufacturing PMI",
"ISM Manufacturing Prices Paid",
"JOLTS Job Openings",
"ADP Nonfarm Employment Change",
"EIA Crude Oil Stocks Change",
"Average Hourly Earnings m/m",
"Initial Jobless Claims",
"Nonfarm Payrolls",
"Unemployment Rate",
"S&P Global Services PMI",
"ISM Non-Manufacturing PMI",
"ISM Non-Manufacturing Prices Paid",
"EIA Crude Oil Stocks Change",
"10-Year Note Auction",
"Initial Jobless Claims",
"30-Year Bond Auction",
"CPI",
"CPI m/m",
"CPI y/y",
"Core CPI m/m",
"Core CPI n.s.a. m/m",
"PPI m/m",
"EIA Crude Oil Stocks Change",
"Core Retail Sales m/m",
"Initial Jobless Claims",
"Philadelphia Fed Manufacturing Index",
"Retail Sales m/m",
"Fed Chair Powell Speech",
"Existing Home Sales",
"EIA Crude Oil Stocks Change",
"Initial Jobless Claims",
"New Home Sales",
"Durable Goods Orders m/m",
"CB Consumer Confidence Index",
"JOLTS Job Openings",
"ADP Nonfarm Employment Change",
"GDP Sales q/q",
"GDP q/q",
"EIA Crude Oil Stocks Change",
"Fed Interest Rate Decision",
"FOMC Press Conference",
"Core PCE Price Index m/m",
"Core PCE Price Index y/y",
"Initial Jobless Claims",
"MNI Chicago Business Barometer",
"Average Hourly Earnings m/m",
"Nonfarm Payrolls",
"Unemployment Rate",
"S&P Global Manufacturing PMI",
"ISM Manufacturing PMI",
"ISM Manufacturing Prices Paid",
"S&P Global Services PMI",
"ISM Non-Manufacturing PMI",
"ISM Non-Manufacturing Prices Paid",
"EIA Crude Oil Stocks Change",
"10-Year Note Auction",
"Initial Jobless Claims",
"30-Year Bond Auction",
"CPI",
"CPI m/m",
"CPI y/y",
"Core CPI m/m",
"Core CPI n.s.a. m/m",
"EIA Crude Oil Stocks Change",
"Initial Jobless Claims",
"PPI m/m",
"Core Retail Sales m/m",
"Retail Sales m/m",
"EIA Crude Oil Stocks Change",
"Initial Jobless Claims",
"Philadelphia Fed Manufacturing Index",
"Existing Home Sales",
"Fed Chair Powell Speech",
"New Home Sales",
"Durable Goods Orders m/m",
"CB Consumer Confidence Index",
"EIA Crude Oil Stocks Change",
"GDP Sales q/q",
"GDP q/q",
"Initial Jobless Claims",
"Core PCE Price Index m/m",
"Core PCE Price Index y/y",
"MNI Chicago Business Barometer",
"S&P Global Manufacturing PMI",
"ISM Manufacturing PMI",
"ISM Manufacturing Prices Paid",
"JOLTS Job Openings",
"ADP Nonfarm Employment Change",
"Initial Jobless Claims",
"S&P Global Services PMI",
"ISM Non-Manufacturing PMI",
"ISM Non-Manufacturing Prices Paid",
"EIA Crude Oil Stocks Change",
"Average Hourly Earnings m/m",
"Nonfarm Payrolls",
"Unemployment Rate",
"PPI m/m",
"EIA Crude Oil Stocks Change",
"10-Year Note Auction",
"CPI",
"CPI m/m",
"CPI y/y",
"Core CPI m/m",
"Core CPI n.s.a. m/m",
"Initial Jobless Claims",
"30-Year Bond Auction",
"Core Retail Sales m/m",
"Retail Sales m/m",
"EIA Crude Oil Stocks Change",
"Fed Interest Rate Decision",
"FOMC Press Conference",
"Initial Jobless Claims",
"Philadelphia Fed Manufacturing Index",
"Existing Home Sales",
"Fed Chair Powell Speech",
"New Home Sales",
"EIA Crude Oil Stocks Change",
"Durable Goods Orders m/m",
"GDP Sales q/q",
"GDP q/q",
"Initial Jobless Claims",
"Core PCE Price Index m/m",
"Core PCE Price Index y/y",
"MNI Chicago Business Barometer",
"CB Consumer Confidence Index",
"JOLTS Job Openings",
"ADP Nonfarm Employment Change",
"S&P Global Manufacturing PMI",
"ISM Manufacturing PMI",
"ISM Manufacturing Prices Paid",
"EIA Crude Oil Stocks Change",
"S&P Global Services PMI",
"ISM Non-Manufacturing PMI",
"ISM Non-Manufacturing Prices Paid",
"EIA Crude Oil Stocks Change",
"10-Year Note Auction",
"Fed Chair Powell Speech",
"30-Year Bond Auction",
"Fed Chair Powell Speech",
"Philadelphia Fed Manufacturing Index",
"EIA Crude Oil Stocks Change",
"EIA Crude Oil Stocks Change",
"Existing Home Sales",
"CPI",
"CPI m/m",
"CPI y/y",
"Core CPI m/m",
"Core CPI n.s.a. m/m",
"CB Consumer Confidence Index",
"EIA Crude Oil Stocks Change",
"Fed Interest Rate Decision",
"FOMC Press Conference",
"MNI Chicago Business Barometer",
"S&P Global Manufacturing PMI",
"ISM Manufacturing PMI",
"ISM Manufacturing Prices Paid",
"ADP Nonfarm Employment Change",
"S&P Global Services PMI",
"ISM Non-Manufacturing PMI",
"ISM Non-Manufacturing Prices Paid",
"EIA Crude Oil Stocks Change",
"10-Year Note Auction",
"EIA Crude Oil Stocks Change",
"30-Year Bond Auction",
"EIA Crude Oil Stocks Change",
"Average Hourly Earnings m/m",
"Nonfarm Payrolls",
"Unemployment Rate",
"Initial Jobless Claims",
"Philadelphia Fed Manufacturing Index",
"Existing Home Sales",
"Core Retail Sales m/m",
"PPI m/m",
"Retail Sales m/m",
"CB Consumer Confidence Index",
"Durable Goods Orders m/m",
"Initial Jobless Claims",
"MNI Chicago Business Barometer",
"EIA Crude Oil Stocks Change",
"S&P Global Manufacturing PMI",
"ISM Manufacturing PMI",
"ISM Manufacturing Prices Paid",
"Fed Chair Powell Speech",
"ADP Nonfarm Employment Change",
"S&P Global Services PMI",
"ISM Non-Manufacturing PMI",
"ISM Non-Manufacturing Prices Paid",
"EIA Crude Oil Stocks Change",
"Initial Jobless Claims",
"Core PCE Price Index m/m",
"Core PCE Price Index y/y",
"JOLTS Job Openings",
"10-Year Note Auction",
"EIA Crude Oil Stocks Change",
"Fed Interest Rate Decision",
"FOMC Press Conference",
"Initial Jobless Claims",
"30-Year Bond Auction",
"Average Hourly Earnings m/m",
"Core Retail Sales m/m",
"Nonfarm Payrolls",
"Retail Sales m/m",
"Unemployment Rate",
"EIA Crude Oil Stocks Change",
"CPI",
"CPI m/m",
"CPI y/y",
"Core CPI m/m",
"Core CPI n.s.a. m/m",
"Initial Jobless Claims",
"Philadelphia Fed Manufacturing Index",
"Existing Home Sales",
"Durable Goods Orders m/m",
"GDP Sales q/q",
"GDP q/q",
"CB Consumer Confidence Index",
"Initial Jobless Claims",
"EIA Crude Oil Stocks Change",
"EIA Crude Oil Stocks Change",
"MNI Chicago Business Barometer",
"Initial Jobless Claims",
"S&P Global Manufacturing PMI",
"ISM Manufacturing PMI",
"ISM Manufacturing Prices Paid",
"S&P Global Services PMI",
"ADP Nonfarm Employment Change",
"ISM Non-Manufacturing PMI",
"ISM Non-Manufacturing Prices Paid",
"JOLTS Job Openings",
"EIA Crude Oil Stocks Change",
"Initial Jobless Claims",
"Average Hourly Earnings m/m",
"Nonfarm Payrolls",
"Unemployment Rate",
"10-Year Note Auction",
"CPI",
"CPI m/m",
"CPI y/y",
"Core CPI m/m",
"Core CPI n.s.a. m/m",
"New Home Sales",
"30-Year Bond Auction",
"Core Retail Sales m/m",
"PPI m/m",
"Retail Sales m/m",
"EIA Crude Oil Stocks Change",
"Initial Jobless Claims",
"Philadelphia Fed Manufacturing Index",
"GDP Sales q/q",
"GDP q/q",
"Initial Jobless Claims",
"Core PCE Price Index m/m",
"Core PCE Price Index y/y",
"EIA Crude Oil Stocks Change",
"Durable Goods Orders m/m",
"CB Consumer Confidence Index",
"EIA Crude Oil Stocks Change",
"Fed Interest Rate Decision",
"FOMC Press Conference",
"Initial Jobless Claims",
"PPI m/m",
"MNI Chicago Business Barometer",
"ISM Manufacturing PMI",
"ISM Manufacturing Prices Paid",
"ADP Nonfarm Employment Change",
"ISM Non-Manufacturing PMI",
"ISM Non-Manufacturing Prices Paid",
"EIA Crude Oil Stocks Change",
"Initial Jobless Claims",
"JOLTS Job Openings",
"Core Retail Sales m/m",
"Retail Sales m/m",
"Average Hourly Earnings m/m",
"Nonfarm Payrolls",
"Unemployment Rate",
"EIA Crude Oil Stocks Change",
"10-Year Note Auction",
"Initial Jobless Claims",
"Existing Home Sales",
"30-Year Bond Auction",
"CPI",
"CPI m/m",
"CPI y/y",
"Core CPI m/m",
"Core CPI n.s.a. m/m",
"Durable Goods Orders m/m",
"Initial Jobless Claims",
"Philadelphia Fed Manufacturing Index",
"EIA Crude Oil Stocks Change",
"Core PCE Price Index m/m",
"Core PCE Price Index y/y",
"GDP Sales q/q",
"GDP q/q",
"New Home Sales",
"CB Consumer Confidence Index",
"EIA Crude Oil Stocks Change",
"Initial Jobless Claims",
"PPI m/m",
"MNI Chicago Business Barometer",
"S&P Global Manufacturing PMI",
"ISM Manufacturing PMI",
"ISM Manufacturing Prices Paid",
"ADP Nonfarm Employment Change",
"S&P Global Services PMI",
"ISM Non-Manufacturing PMI",
"ISM Non-Manufacturing Prices Paid",
"EIA Crude Oil Stocks Change",
"Initial Jobless Claims",
"Average Hourly Earnings m/m",
"Core Retail Sales m/m",
"Nonfarm Payrolls",
"Retail Sales m/m",
"Unemployment Rate",
"Existing Home Sales",
"CPI",
"CPI m/m",
"CPI y/y",
"Core CPI m/m",
"Core CPI n.s.a. m/m",
"EIA Crude Oil Stocks Change",
"10-Year Note Auction",
"Initial Jobless Claims",
"30-Year Bond Auction",
"Core PCE Price Index m/m",
"Core PCE Price Index y/y",
"Durable Goods Orders m/m",
"GDP Sales q/q",
"GDP q/q",
"JOLTS Job Openings",
"PPI m/m",
"EIA Crude Oil Stocks Change",
"Fed Interest Rate Decision",
"FOMC Press Conference",
"Initial Jobless Claims",
"Philadelphia Fed Manufacturing Index",
"New Home Sales",
"Fed Chair Powell Speech",
"EIA Crude Oil Stocks Change",
"Initial Jobless Claims",
"Fed Chair Powell Speech",
"MNI Chicago Business Barometer",
"CB Consumer Confidence Index",
"JOLTS Job Openings",
"ADP Nonfarm Employment Change",
"Core Retail Sales m/m",
"Retail Sales m/m",
"S&P Global Manufacturing PMI",
"ISM Manufacturing PMI",
"ISM Manufacturing Prices Paid",
"EIA Crude Oil Stocks Change",
"Initial Jobless Claims",
"Average Hourly Earnings m/m",
"Nonfarm Payrolls",
"Unemployment Rate",
"S&P Global Services PMI",
"ISM Non-Manufacturing PMI",
"ISM Non-Manufacturing Prices Paid"
};
// --- Embedded USD high-impact backtest news end ---
//+------------------------------------------------------------------+
//| 本EA仅支持马丁加仓模式 |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| 画线模式枚举 |
//+------------------------------------------------------------------+
enum ENUM_LINE_MODE
{
LINE_BODY = 0, // 0
LINE_WHOLE = 1 // 1
};
//+------------------------------------------------------------------+
//| ATR过滤模式枚举 |
//+------------------------------------------------------------------+
enum ENUM_ATR_MODE
{
ATR_NONE = 0, // 0
ATR_BODY = 1, // 1
ATR_WHOLE = 2 // 2
};
//+------------------------------------------------------------------+
//| 授权参数(不显示在参数页面) |
//+------------------------------------------------------------------+
string g_authAccount = ""; // 授权账号(英文逗号隔开 空=不限)
string g_licenseKey = "V8-2026"; // 授权密钥
datetime g_expiryDate = D'2099.08.30'; // 授权到期日
//+------------------------------------------------------------------+
//| 输入参数 |
//+------------------------------------------------------------------+
input group "════════════ 基础设置 ══════════════"
input ENUM_TIMEFRAMES InpPeriod = PERIOD_M1; // 交易周期
input bool InpEnableDualGroup = true; // 双组模式(关=单组A)
input int InpABOpenGapBars = 0; // AB组开仓间隔(K线数)
input group "════════════ 马丁加仓模式 ════════════"
input int InpAddMode = 1; // 加仓模式:0=固定间隔,1=阶梯间隔
input int InpAddDistance = 500; // 固定加仓间隔(点) [模式0使用]
input string InpStageAddDistances = "500,550,600,650,700"; // 阶梯间隔序列(点,逗号分隔) [模式1使用]
input string InpLotSequence = "0.25,0.3,0.5,0.8,1.1"; // 自定义加仓手数
input double InpLossAmount = 10000; // 止损金额
input bool InpUseAddTracking = true; // 启用追踪加仓
input int InpAddTriggerPoints = 30; // 突破挂单距离点
input int InpAddTrailPoints = 18; // 挂单追踪步长
input int InpTrailStep = 20; // 移动止盈步长
input int InpFirstTrailActive = 120; // 马丁首单:移动止盈激活点数
input int InpAddTrailActive = 70; // 马丁加仓后:移动止盈激活点数
input group "════════════ 过滤条件 ══════════════"
input ENUM_LINE_MODE InpLineMode = LINE_BODY; // 画线模式
input ENUM_ATR_MODE InpATRMode = ATR_BODY; // ATR过滤模式
input int InpEMAPeriod = 10; // EMA周期
input int InpATRPeriod = 14; // ATR周期
input double InpATRMultiple = 1.3; // ATR倍数
input bool InpUseCCIRSIFilter = false; // 启用CCI+RSI风控(上下轨外不开首单)
input int InpRSIPeriod = 14; // RSI周期
input double InpRSIHighStop = 70.0; // RSI上轨
input double InpRSILowStop = 30.0; // RSI下轨
input int InpCCIPeriod = 14; // CCI周期
input double InpCCIBlockUpper = 100.0; // CCI上轨
input double InpCCIBlockLower = -100.0; // CCI下轨
input bool InpUseRSIDirectionFilter = true; // 启用5分钟RSI方向过滤
input ENUM_TIMEFRAMES InpRSIDirectionTF = PERIOD_M5; // RSI方向过滤周期
input int InpRSIDirectionPeriod = 14; // RSI方向过滤周期
input double InpRSIBuyMax = 52.0; // RSI做多阈值
input double InpRSISellMin = 48.0; // RSI做空阈值
input double InpFractalProtectionAmount =50000; // 分型保护金额(浮亏达到此金额平仓)
input double InpNetProtectionDistance = 1000; // 结余保护距离
input bool InpUseNetProtection = false; // 结余保护开关
input int InpMaxLosses = 99; // 连续止损次数触发暂停
input int InpPauseMinutes = 15; // 暂停时长(分钟)
input int InpCloseRetryMs = 2000; // 平仓重试间隔(毫秒)
input int InpCloseMaxRetries = 5; // 平仓最大重试次数
input int InpMagicNumber = 20268888; // 幻数
input int InpMaxSlippage = 60; // 最大滑点(点)
input string InpComment = "XAUUSD"; // 订单注释
input string InpNoTradeWindow = "="; // 禁止时段(北京时间),"-"=禁用
input group "════════════ 界面设置 ══════════════"
input bool InpUsePanel = true; // 启用面板
input bool InpUseButtons = true; // 启用按钮
input int InpUIWidth = 350; // 面板宽度
input int InpUIX = 10; // 左边距
input int InpUIY = 20; // 上边距
input bool InpUseChartTheme = true; // 赛博朋克图表配色
//+------------------------------------------------------------------+
//| 交易时段控制 (Session) 移植自 金麒麟king |
//+------------------------------------------------------------------+
input group "════════════ 交易时段控制 ════════════"
input bool EnableTradingSessionWindow = true; // 启用工作时间段控制
input string SS_EA_StartTime = "00:00"; // EA开始时间
input string SS_EA_StopTime = "24:00"; // EA结束时间
input bool EnableDailyWrapUpPhase = false; // 启用日内收尾阶段
input string DailyWrapUpStartTime = "20:00"; // 收尾开始(盘面/服务器时间)
input string DailyWrapUpStopTime = "24:00"; // 收尾结束(盘面/服务器时间)
input bool EnableDailyProfitTarget = false; // 启用每日盈利目标
input double DailyProfitTarget = 888.88; // 每日盈利目标,达到后封盘
input bool EnableHighImpactNewsPause = true; // 启用高影响新闻暂停
input int NewsLeadMinutes = 60; // 新闻前等待平仓分钟
input int NewsCooldownMinutes = 60; // 新闻后暂停分钟
//+------------------------------------------------------------------+
//| 状态 |
//+------------------------------------------------------------------+
enum EA_STATE
{
STATE_WAIT_FIRST_CANDLE, // 等第一根K线收盘
STATE_WAIT_SIGNAL, // 等信号
STATE_IN_TRADE, // 持仓中
STATE_CLOSING, // 异步平仓中
STATE_WAIT_AFTER_CLOSE // 平仓后等K线收盘
};
//+------------------------------------------------------------------+
//| 全局变量 |
//+------------------------------------------------------------------+
CTrade trade;
EA_STATE g_state = STATE_WAIT_FIRST_CANDLE;
#define GROUP_COUNT 2
string g_groupNames[GROUP_COUNT] = {"A", "B"};
int g_groupMagics[GROUP_COUNT];
bool g_groupClosing[GROUP_COUNT] = {false, false};
int g_ctxGroup = 0; // 当前上下文组(0=A,1=B)
datetime g_groupLastOpenBarTime[GROUP_COUNT] = {0, 0}; // 记录每组最近开仓K线时间
struct GROUP_RUNTIME
{
int direction;
int orderTickets[50];
double orderLots[50];
double orderPrices[50];
int orderCount;
double totalLots;
double avgPrice;
double entryPrice;
double SL;
double trailLine;
bool trailActive;
bool isFirstOrder;
string closeReason;
double closeProfit;
int closeRetries;
bool autoTradingWarned;
datetime closeRequestTime;
ulong closeTickets[50];
int closeTicketCount;
bool addTrackingActive;
double addExtremePrice;
double addTriggerPrice;
datetime closeBarTime; // 平仓时的K线时间(防同K线重开)
bool kLineTraded; // 组内K线已交易标记
bool isOpening; // 该组是否正在开仓(防止并发)
};
GROUP_RUNTIME g_groupRt[GROUP_COUNT];
// 启动时记录的时间
datetime g_startBarTime = 0;
datetime g_prevBarTime = 0;
bool g_firstBarChecked = false;
// 标记K线数据
double g_kOpen = 0;
double g_kHigh = 0;
double g_kLow = 0;
double g_kClose = 0;
double g_kATR = 0;
double g_kCCI = 0;
double g_kRSI = 0;
double g_kRSIDirection = 0;
datetime g_candleTime = 0;
// EMA单均线方向
int g_bias = 0; // 1=多头排列 -1=空头排列 0=不明
bool g_emaReady = false; // 单均线就绪
double g_emaVal = 0; // 单EMA当前值
bool g_atrOk = false;
// 当前持仓方向
int g_direction = 0;
// 加仓模式持仓数组
int g_orderTickets[50];
double g_orderLots[50];
double g_orderPrices[50];
int g_orderCount = 0;
double g_totalLots = 0;
double g_avgPrice = 0;
// 自定义手数序列数组
double g_lotSequence[50];
int g_maxOrders = 0;
// 阶梯加仓间隔数组
double g_stageDistances[50]; // 存储解析后的阶梯间隔
int g_stageCount = 0; // 间隔个数
// 止盈相关
double g_entryPrice = 0;
double g_SL = 0;
double g_trailLine = 0;
bool g_trailActive = false;
bool g_isFirstOrder = true;
// 每K线一次限制
bool g_kLineTraded = false; // [FIX1] 工作变量,per-group持久化在GROUP_RUNTIME中
datetime g_signalUsedBarTime = 0; // 任意组开仓后记录K线时间,同K线禁止另一组开仓
datetime g_closeBarTime = 0;
// 异步平仓相关
string g_closeReason = ""; // 平仓原因
double g_closeProfit = 0; // 平仓时记录的盈亏
int g_closeRetries = 0; // 已重试次数
bool g_autoTradingWarned = false; // 防止10027重复警告
datetime g_closeRequestTime = 0; // 上次平仓请求时间
ulong g_closeTickets[50]; // 待平仓单号
int g_closeTicketCount = 0; // 待平仓单数
// 指标句柄
int g_atrHandle = INVALID_HANDLE;
int g_emaHandle = INVALID_HANDLE; // 单EMA句柄
int g_cciHandle = INVALID_HANDLE;
int g_rsiHandle = INVALID_HANDLE;
int g_rsiDirectionHandle = INVALID_HANDLE;
// 画线
string g_lineHighName = "EMA2_High";
string g_lineLowName = "EMA2_Low";
string g_lineSLName = "EMA2_SL_A";
string g_lineTrailName = "EMA2_Trail_A";
string g_lineAvgName = "EMA2_Avg_A";
// 今日统计
int g_todayWins = 0;
int g_todayLosses = 0;
double g_todayProfit = 0;
datetime g_todayDate = 0;
// 累计统计(跨EA重启保留)
int g_stat_totalTrades = 0; // 总交易次数
int g_stat_wins = 0; // 盈利次数
int g_stat_losses = 0; // 亏损次数
double g_stat_totalProfit = 0; // 累计盈利金额
double g_stat_totalLoss = 0; // 累计亏损金额
double g_stat_maxProfit = 0; // 最大单笔盈利
double g_stat_maxLoss = 0; // 最大单笔亏损
double g_stat_maxDrawdown = 0; // 最大回撤
bool g_isAuthorized = false;
string g_gvPrefix = "EMA2_";
// 连续止损暂停变量
int g_consecutiveLosses = 0; // 连续止损次数
datetime g_pauseUntilTime = 0; // 暂停截止时间
// 结余保护变量
bool g_permanentlyStopped = false; // 永久停止标志
double g_maxBalance = 0; // 最大结余(只升不降)
// 今日最大浮盈浮亏
double g_todayMaxProfit = 0; // 今日最大浮盈
double g_todayMaxLoss = 0; // 今日最大浮亏
double g_equityPeak = 0; // 权益峰值(用于计算回撤)
double g_maxDrawdown = 0; // 当前运行最大回撤
// UI对象前缀
string g_uiPrefix = "HX_UI_";
// 面板对象名
string g_panelBg = "HX_UI_PanelBG";
string g_panelTitle = "HX_UI_PanelTitle";
string g_plAcct = "HX_UI_Acct";
string g_plStatus = "HX_UI_Status";
string g_plPnL = "HX_UI_PnL";
string g_plToday = "HX_UI_Today";
string g_plPos = "HX_UI_Pos";
string g_plLots = "HX_UI_Lots";
string g_plConsec = "HX_UI_Consec";
string g_plPause = "HX_UI_Pause";
string g_plNetProt = "HX_UI_NetProt";
string g_plStopped = "HX_UI_Stopped";
string g_plNoTrade = "HX_UI_NoTrade";
string g_plRange1 = "HX_UI_Range1";
string g_plRange2 = "HX_UI_Range2";
string g_plAuth = "HX_UI_Auth";
// 分隔线 + 辅助
string g_plSep1 = "HX_UI_Sep1";
string g_plSep2 = "HX_UI_Sep2";
string g_plSep3 = "HX_UI_Sep3";
string g_plSep4 = "HX_UI_Sep4";
string g_plSep5 = "HX_UI_Sep5";
string g_plAuthVal = "HX_UI_AuthVal";
string g_plDetail = "HX_UI_Detail";
string g_plCountdown = "HX_UI_Countdown";
string g_plBalVal = "HX_UI_BalVal";
string g_plAcctVal = "HX_UI_AcctVal";
string g_plFloatVal = "HX_UI_FloatVal";
string g_plTodayVal = "HX_UI_TodayVal";
string g_plPosVal = "HX_UI_PosVal";
string g_plIndi1 = "HX_UI_Indi1";
string g_plIndi2 = "HX_UI_Indi2";
// --- 交易时段控制面板标签 ---
string g_plSessHdr = "HX_UI_SessHdr";
string g_plSess = "HX_UI_Sess";
string g_plSessVal = "HX_UI_SessVal";
string g_plWrap = "HX_UI_Wrap";
string g_plWrapVal = "HX_UI_WrapVal";
string g_plGoal = "HX_UI_Goal";
string g_plGoalVal = "HX_UI_GoalVal";
string g_plNews = "HX_UI_News";
string g_plNewsVal = "HX_UI_NewsVal";
string g_plSep6 = "HX_UI_Sep6";
string g_plSep7 = "HX_UI_Sep7";
string g_plTopStrip = "HX_UI_TopStrip";
string g_plTitleGlow = "HX_UI_TitleGlow";
string g_plBotStrip = "HX_UI_BotStrip";
string g_plSessBar = "HX_UI_SessBar";
// 按钮对象名
string g_btnCloseAll = "HX_UI_BtnCloseAll";
string g_btnCloseA = "HX_UI_BtnCloseA";
string g_btnCloseB = "HX_UI_BtnCloseB";
string g_btnCloseLong = "HX_UI_BtnCloseLong";
string g_btnCloseShort = "HX_UI_BtnCloseShort";
//+==================================================================+
//| 交易时段控制模块 (Session Module) |
//| 移植自 金麒麟king,已适配 恒鑫双组(A/B)幻数模型 |
//+==================================================================+
struct SS_NewsWindowState
{
bool enabled;
bool calendar_available;
bool using_embedded_history;
bool has_active_window;
bool in_pre_window;
bool in_post_window;
bool has_upcoming_event;
bool block_entries;
datetime server_now;
datetime event_time;
datetime window_start;
datetime resume_time;
string currency;
string event_name;
string error_text;
};
struct SS_NewsEvalAccumulator
{
bool any_success;
string first_error;
datetime active_block_start;
datetime active_block_end;
datetime active_future_time;
string active_future_currency;
string active_future_name;
datetime active_past_time;
string active_past_currency;
string active_past_name;
datetime next_upcoming_time;
string next_upcoming_currency;
string next_upcoming_name;
};
SS_NewsWindowState g_ss_news;
datetime g_ss_news_cache_minute = 0;
string g_ss_today_key = "";
bool g_ss_daily_target_locked = false;
double g_ss_daily_target_hit = 0.0;
string g_ss_lastCloseReason = "";
bool g_ss_blockEntries = false;
//--- 时间/时段判定 ---
bool SS_IsTestingMode()
{
return((bool)MQLInfoInteger(MQL_TESTER));
}
datetime SS_ReferenceNow()
{
return(SS_IsTestingMode() ? TimeCurrent() : TimeLocal());
}
datetime SS_ReferenceNewsNow()
{
if(SS_IsTestingMode())
return(TimeCurrent());
datetime t=TimeTradeServer();
if(t>0) return(t);
t=TimeCurrent();
if(t>0) return(t);
return(TimeLocal());
}
string SS_CleanTimeString(const string value)
{
string result=value;
StringReplace(result," ","");
StringTrimLeft(result);
StringTrimRight(result);
if(result=="24:00") result="23:59:59";
return(result);
}
datetime SS_TodayAt(const string time_text,const datetime now_value)
{
string t=SS_CleanTimeString(time_text);
if(StringLen(t)==5) t+=":00";
return(StringToTime(TimeToString(now_value,TIME_DATE)+" "+t));
}
bool SS_IsInWindow(const string start_text,const string stop_text,const datetime now_value)
{
const datetime s=SS_TodayAt(start_text,now_value);
const datetime e=SS_TodayAt(stop_text,now_value);
if(s<=e) return(now_value>=s && now_value<=e);
return(now_value>=s || now_value<=e);
}
bool SS_IsAfterSessionStop(const datetime now_value)
{
if(SS_IsInWindow(SS_EA_StartTime,SS_EA_StopTime,now_value))
return(false);
const datetime s=SS_TodayAt(SS_EA_StartTime,now_value);
const datetime e=SS_TodayAt(SS_EA_StopTime,now_value);
if(s<=e) return(now_value>e);
return(now_value>e && now_value<s);
}
bool SS_IsTradingSessionOpen(const datetime now_value)
{
if(!EnableTradingSessionWindow) return(true);
return(SS_IsInWindow(SS_EA_StartTime,SS_EA_StopTime,now_value));
}
bool SS_IsTradingSessionAfterStop(const datetime now_value)
{
if(!EnableTradingSessionWindow) return(false);
return(SS_IsAfterSessionStop(now_value));
}
bool SS_IsDailyWrapUpWindow(const datetime now_value)
{
if(!EnableDailyWrapUpPhase) return(false);
return(SS_IsInWindow(DailyWrapUpStartTime,DailyWrapUpStopTime,now_value));
}
//--- 持仓/盈亏 (跨A/B两组幻数) ---
bool SS_HasAnyPosition()
{
for(int i=PositionsTotal()-1;i>=0;--i)
{
ulong tk=PositionGetTicket(i);
if(tk==0) continue;
if(!PositionSelectByTicket(tk)) continue;
if(PositionGetString(POSITION_SYMBOL)!=_Symbol) continue;
long mg=PositionGetInteger(POSITION_MAGIC);
if(mg==g_groupMagics[0] || mg==g_groupMagics[1])
return(true);
}
return(false);
}
double SS_FloatingProfit()
{
double p=0.0;
for(int i=PositionsTotal()-1;i>=0;--i)
{
ulong tk=PositionGetTicket(i);
if(tk==0) continue;
if(!PositionSelectByTicket(tk)) continue;
if(PositionGetString(POSITION_SYMBOL)!=_Symbol) continue;
long mg=PositionGetInteger(POSITION_MAGIC);
if(mg==g_groupMagics[0] || mg==g_groupMagics[1])
p+=PositionGetDouble(POSITION_PROFIT)+PositionGetDouble(POSITION_SWAP);
}
return(p);
}
double SS_TodayClosedProfit(const datetime now_value)
{
double total=0.0;
datetime from_time=SS_TodayAt("00:00",now_value);
if(!HistorySelect(from_time,TimeCurrent()))
return(0.0);
const int deals=(int)HistoryDealsTotal();
for(int i=0;i<deals;++i)
{
ulong dt=HistoryDealGetTicket(i);
if(dt==0) continue;
ENUM_DEAL_ENTRY entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(dt,DEAL_ENTRY);
if(entry!=DEAL_ENTRY_OUT && entry!=DEAL_ENTRY_OUT_BY && entry!=DEAL_ENTRY_INOUT)
continue;
if(HistoryDealGetString(dt,DEAL_SYMBOL)!=_Symbol) continue;
long mg=(long)HistoryDealGetInteger(dt,DEAL_MAGIC);
if(mg!=g_groupMagics[0] && mg!=g_groupMagics[1]) continue;
total+=HistoryDealGetDouble(dt,DEAL_PROFIT);
total+=HistoryDealGetDouble(dt,DEAL_SWAP);
total+=HistoryDealGetDouble(dt,DEAL_COMMISSION);
}
return(total);
}
double SS_TodayProgressProfit(const datetime now_value)
{
return(SS_TodayClosedProfit(now_value)+SS_FloatingProfit());
}
void SS_RefreshDailyLocks(const datetime now_value)
{
const string day_key=TimeToString(now_value,TIME_DATE);
if(day_key!=g_ss_today_key)
{
g_ss_today_key=day_key;
g_ss_daily_target_locked=false;
g_ss_daily_target_hit=0.0;
}
if(!EnableDailyProfitTarget || DailyProfitTarget<=0.0)
{
g_ss_daily_target_locked=false;
g_ss_daily_target_hit=0.0;
return;
}
if(g_ss_daily_target_locked) return;
const double prog=SS_TodayProgressProfit(now_value);
if(prog>=DailyProfitTarget)
{
g_ss_daily_target_locked=true;
g_ss_daily_target_hit=prog;
}
}
//--- 新闻状态 ---
void SS_ResetNewsState(SS_NewsWindowState &st)
{
st.enabled=false; st.calendar_available=false; st.using_embedded_history=false;
st.has_active_window=false; st.in_pre_window=false; st.in_post_window=false;
st.has_upcoming_event=false; st.block_entries=false;
st.server_now=0; st.event_time=0; st.window_start=0; st.resume_time=0;
st.currency=""; st.event_name=""; st.error_text="";
}
void SS_ResetEval(SS_NewsEvalAccumulator &ev)
{
ev.any_success=false; ev.first_error="";
ev.active_block_start=0; ev.active_block_end=0;
ev.active_future_time=0; ev.active_future_currency=""; ev.active_future_name="";
ev.active_past_time=0; ev.active_past_currency=""; ev.active_past_name="";
ev.next_upcoming_time=0; ev.next_upcoming_currency=""; ev.next_upcoming_name="";
}
void SS_AppendUnique(string &items[],const string value)
{
string item=value;
StringTrimLeft(item); StringTrimRight(item);
if(item=="") return;
for(int i=0;i<ArraySize(items);++i)
if(items==item) return;
const int sz=ArraySize(items);
ArrayResize(items,sz+1);
items[sz]=item;
}
void SS_BuildCurrencies(string &cur[])
{
ArrayResize(cur,0);
SS_AppendUnique(cur,SymbolInfoString(_Symbol,SYMBOL_CURRENCY_PROFIT));
SS_AppendUnique(cur,SymbolInfoString(_Symbol,SYMBOL_CURRENCY_BASE));
SS_AppendUnique(cur,SymbolInfoString(_Symbol,SYMBOL_CURRENCY_MARGIN));
}
string SS_CalendarErrorText(const int code)
{
switch(code)
{
case 0: return("");
case 5400: return("经济日历返回过多数据");
case 5401: return("经济日历请求超时");
case 5402: return("经济日历无可用数据");
default: return("经济日历错误 "+IntegerToString(code));
}
}
void SS_ConsiderEvent(const datetime now_server,const datetime event_time,const string currency,const string event_name,const int lead_s,const int cool_s,SS_NewsEvalAccumulator &ev)
{
if(event_time<=0) return;
const datetime ws=event_time-lead_s;
const datetime we=event_time+cool_s;
if(now_server>=ws && now_server<we)
{
if(ev.active_block_start==0 || ws<ev.active_block_start) ev.active_block_start=ws;
if(we>ev.active_block_end) ev.active_block_end=we;
if(event_time>=now_server)
{
if(ev.active_future_time==0 || event_time<ev.active_future_time)
{ ev.active_future_time=event_time; ev.active_future_currency=currency; ev.active_future_name=event_name; }
}
else
{
if(ev.active_past_time==0 || event_time>ev.active_past_time)
{ ev.active_past_time=event_time; ev.active_past_currency=currency; ev.active_past_name=event_name; }
}
return;
}
if(event_time>now_server && (ev.next_upcoming_time==0 || event_time<ev.next_upcoming_time))
{ ev.next_upcoming_time=event_time; ev.next_upcoming_currency=currency; ev.next_upcoming_name=event_name; }
}
void SS_ApplyEval(const SS_NewsEvalAccumulator &ev)
{
g_ss_news.calendar_available=ev.any_success;
if(!ev.any_success)
{
g_ss_news.error_text=(ev.first_error!="" ? ev.first_error : "经济日历不可用,已忽略");
return;
}
if(ev.active_block_start!=0)
{
g_ss_news.has_active_window=true;
g_ss_news.block_entries=true;
g_ss_news.window_start=ev.active_block_start;
g_ss_news.resume_time=ev.active_block_end;
g_ss_news.in_pre_window=(ev.active_future_time!=0);
g_ss_news.in_post_window=(ev.active_past_time!=0) || !g_ss_news.in_pre_window;
if(g_ss_news.in_pre_window)
{ g_ss_news.event_time=ev.active_future_time; g_ss_news.currency=ev.active_future_currency; g_ss_news.event_name=ev.active_future_name; }
else
{ g_ss_news.event_time=ev.active_past_time; g_ss_news.currency=ev.active_past_currency; g_ss_news.event_name=ev.active_past_name; }
return;
}
if(ev.next_upcoming_time!=0)
{
g_ss_news.has_upcoming_event=true;
g_ss_news.event_time=ev.next_upcoming_time;
g_ss_news.currency=ev.next_upcoming_currency;
g_ss_news.event_name=ev.next_upcoming_name;
}
}
void SS_RefreshEmbeddedNews(const datetime now_server)
{
g_ss_news.using_embedded_history=true;
SS_NewsEvalAccumulator ev; SS_ResetEval(ev);
ev.any_success=(GOLDKING_BACKTEST_USD_HIGH_NEWS_COUNT>0);
if(!ev.any_success)
{ ev.first_error="回测内置新闻为空"; SS_ApplyEval(ev); return; }
const int lead_s=MathMax(0,NewsLeadMinutes)*60;
const int cool_s=MathMax(0,NewsCooldownMinutes)*60;
for(int i=0;i<GOLDKING_BACKTEST_USD_HIGH_NEWS_COUNT;++i)
SS_ConsiderEvent(now_server,GOLDKING_BACKTEST_USD_HIGH_NEWS_TIMES,"USD",GOLDKING_BACKTEST_USD_HIGH_NEWS_NAMES,lead_s,cool_s,ev);
SS_ApplyEval(ev);
}
void SS_RefreshNews(const bool force)
{
const datetime now_server=SS_ReferenceNewsNow();
const datetime minute_key=now_server-now_server%60;
if(!force && g_ss_news_cache_minute==minute_key && g_ss_news.server_now!=0)
{ g_ss_news.server_now=now_server; return; }
g_ss_news_cache_minute=minute_key;
SS_ResetNewsState(g_ss_news);
g_ss_news.enabled=EnableHighImpactNewsPause;
g_ss_news.server_now=now_server;
if(!EnableHighImpactNewsPause) return;
if(SS_IsTestingMode())
{ SS_RefreshEmbeddedNews(now_server); return; }
string cur[]; SS_BuildCurrencies(cur);
if(ArraySize(cur)==0)
{ g_ss_news.error_text="新闻过滤无可用货币"; return; }
const int lead_s=MathMax(0,NewsLeadMinutes)*60;
const int cool_s=MathMax(0,NewsCooldownMinutes)*60;
const datetime q_from=now_server-cool_s;
const datetime q_to=now_server+86400;
SS_NewsEvalAccumulator ev; SS_ResetEval(ev);
for(int c=0;c<ArraySize(cur);++c)
{
MqlCalendarValue values[];
ResetLastError();
const int count=CalendarValueHistory(values,q_from,q_to,NULL,cur[c]);
const int err=GetLastError();
if(count<0)
{
if(ev.first_error=="" && err!=0) ev.first_error=SS_CalendarErrorText(err);
continue;
}
ev.any_success=true;
for(int i=0;i<count;++i)
{
if(values.event_id==0 || values.time<=0) continue;
MqlCalendarEvent evt={};
if(!CalendarEventById(values.event_id,evt)) continue;
if(evt.importance!=CALENDAR_IMPORTANCE_HIGH) continue;
if(evt.time_mode!=CALENDAR_TIMEMODE_DATETIME) continue;
SS_ConsiderEvent(now_server,values.time,cur[c],evt.name,lead_s,cool_s,ev);
}
}
SS_ApplyEval(ev);
}
//--- 面板状态文案 ---
//--- 按字符预算裁剪(中文按2、其余按1计宽), 超出加省略号 ---
string SS_Clip(const string text,const int max_units)
{
int units=0;
int len=StringLen(text);
string out="";
for(int i=0;i<len;++i)
{
ushort ch=StringGetCharacter(text,i);
int w=(ch>127)?2:1;
if(units+w>max_units)
{
out+="…";
break;
}
out+=ShortToString(ch);
units+=w;
}
return(out);
}
//--- 估算文本像素宽度(中文按字号、英文数字按约0.55字号) ---
int SS_TextPixels(const string text,const int font_size)
{
int len=StringLen(text);
double w=0.0;
for(int i=0;i<len;++i)
{
ushort ch=StringGetCharacter(text,i);
w+=(ch>127)?(double)font_size*1.35 double)font_size*0.62;
}
return((int)MathRound(w));
}
//--- 按字符宽度预算把长文本折成多行(中文2/其余1), 返回行数 ---
int SS_WrapToLines(const string text,const int max_units,string &out_lines[],const int max_lines)
{
int len=StringLen(text);
int line=0;
int units=0;
string cur="";
for(int i=0;i<len && line<max_lines;++i)
{
ushort ch=StringGetCharacter(text,i);
int cw=(ch>127)?2:1;
if(units+cw>max_units)
{
out_lines[line]=cur;
line++;
cur="";
units=0;
if(line>=max_lines)
{
// 剩余内容截断到末行加省略号
out_lines[line-1]=SS_Clip(out_lines[line-1],max_units);
return(line);
}
}
cur+=ShortToString(ch);
units+=cw;
}
if(cur!="" && line<max_lines)
{
out_lines[line]=cur;
line++;
}
return(line);
}
string SS_FormatSignedMoney(const double v)
{
return((v>0.0?"+":"")+DoubleToString(v,2));
}
string SS_FormatMoment(const datetime when,const datetime now_value)
{
if(when<=0) return("--");
MqlDateTime s={},n={};
TimeToStruct(when,s); TimeToStruct(now_value,n);
if(s.year==n.year && s.mon==n.mon && s.day==n.day)
return(StringFormat("%02d:%02d",s.hour,s.min));
return(StringFormat("%02d-%02d %02d:%02d",s.mon,s.day,s.hour,s.min));
}
string SS_SessionStateText(const datetime now_value)
{
if(!EnableTradingSessionWindow) return("已关闭");
if(SS_IsTradingSessionAfterStop(now_value))
return(SS_HasAnyPosition()?"结束已到,待平仓":"今日已收工");
if(!SS_IsTradingSessionOpen(now_value)) return("未到工作时间");
return("工作时段内");
}
string SS_WrapUpStateText()
{
if(!EnableDailyWrapUpPhase) return("已关闭");
const string slot=DailyWrapUpStartTime+"-"+DailyWrapUpStopTime;
if(!SS_IsDailyWrapUpWindow(g_ss_news.server_now)) return(slot+" 未触发");
return(SS_HasAnyPosition()?slot+" 待平仓":slot+" 已收尾");
}
string SS_GoalStateText(const double prog)
{
if(!EnableDailyProfitTarget) return("已关闭");
if(DailyProfitTarget<=0.0) return("金额<=0");
if(g_ss_daily_target_locked)
return("已达 "+SS_FormatSignedMoney(MathMax(prog,g_ss_daily_target_hit))+" / +"+DoubleToString(DailyProfitTarget,2));
return("进度 "+SS_FormatSignedMoney(prog)+" / +"+DoubleToString(DailyProfitTarget,2));
}
string SS_NewsStateText()
{
const string src=(g_ss_news.using_embedded_history?"回测内置":"实时日历");
if(!EnableHighImpactNewsPause) return("已关闭");
if(!g_ss_news.calendar_available)
return(src+" | "+(g_ss_news.error_text==""?"新闻源不可用":g_ss_news.error_text));
const string et="服"+SS_FormatMoment(g_ss_news.event_time,g_ss_news.server_now);
const string ep=(g_ss_news.currency==""?"":g_ss_news.currency+" ");
const string es=(g_ss_news.event_name==""?"":" "+g_ss_news.event_name);
if(g_ss_news.block_entries)
{
if(g_ss_news.in_pre_window)
return(src+" | "+(SS_HasAnyPosition()?"前"+IntegerToString(NewsLeadMinutes/60)+"h待平仓":"前"+IntegerToString(NewsLeadMinutes/60)+"h暂停")+" | "+ep+et+es);
return(src+" | "+(SS_HasAnyPosition()?"后"+IntegerToString(NewsCooldownMinutes/60)+"h待平仓":"后"+IntegerToString(NewsCooldownMinutes/60)+"h冷静")+" | "+ep+et+es);
}
if(g_ss_news.has_upcoming_event)
return(src+" | 下个高影响 | "+ep+et+es);
if(g_ss_news.using_embedded_history)
{
if(g_ss_news.server_now<GOLDKING_BACKTEST_USD_HIGH_NEWS_RANGE_START || g_ss_news.server_now>GOLDKING_BACKTEST_USD_HIGH_NEWS_RANGE_END)
return("回测内置 | 范围外 "+GOLDKING_BACKTEST_USD_HIGH_NEWS_RANGE_LABEL);
return("回测内置 | 未触发");
}
return("未触发");
}
//--- 综合门控: 返回是否禁止开新仓, 并输出是否需要强制平仓 + 原因 ---
bool SS_ShouldBlockEntries(const datetime now_value,bool &force_close_out,string &reason_out)
{
force_close_out=false;
reason_out="";
// 1) 每日盈利目标封盘
if(g_ss_daily_target_locked)
{
force_close_out=true;
reason_out="今日盈利达标封盘";
return(true);
}
// 2) 高影响新闻窗口
if(g_ss_news.block_entries)
{
force_close_out=true; // 新闻窗口内等待全部平仓
reason_out=(g_ss_news.in_pre_window?"高影响新闻前,暂停并平仓":"高影响新闻后冷静期");
return(true);
}
// 3) 日内收尾阶段
if(SS_IsDailyWrapUpWindow(now_value))
{
force_close_out=true;
reason_out="日内收尾阶段";
return(true);
}
// 4) 工作时段结束后
if(SS_IsTradingSessionAfterStop(now_value))
{
force_close_out=true;
reason_out="已过工作结束时间";
return(true);
}
// 5) 未到工作时段开始 (仅禁开, 不强平)
if(!SS_IsTradingSessionOpen(now_value))
{
reason_out="未到工作时段";
return(true);
}
return(false);
}
// 供 OnTick 调用: 刷新时段/新闻/目标状态, 必要时强平所有组, 返回是否禁止开仓
bool SS_ProcessAndGate()
{
const datetime now_value=SS_ReferenceNow();
SS_RefreshDailyLocks(now_value);
SS_RefreshNews(false);
bool force_close=false;
string reason="";
bool block=SS_ShouldBlockEntries(now_value,force_close,reason);
if(force_close && SS_HasAnyPosition())
{
if(reason!=g_ss_lastCloseReason)
{
Print("【时段控制】",reason,",强制平仓所有组");
g_ss_lastCloseReason=reason;
}
for(int g=0;g<GROUP_COUNT;g++)
{
LoadGroupRuntime(g);
if(HasGroupPosition(g) || g_groupClosing[g])
DoClose("时段控制:"+reason);
SaveGroupRuntime(g);
}
SetGroupContext(0);
}
else if(!force_close)
{
g_ss_lastCloseReason="";
}
return(block);
}
// 禁止时段(北京时间)
bool g_inNoTradeWindow = false;
datetime g_noTradeStartBarTime = 0;
// 马丁追踪加仓相关变量
bool g_addTrackingActive = false; // 追踪加仓是否激活
double g_addExtremePrice = 0; // 极端价(做空=最高,做多=最低)
double g_addTriggerPrice = 0; // 挂单线价格
string g_lineAddName = "EMA_AddTrigger_A"; // 挂单线名称
// 分型保护变量(按组)
bool g_fractalPending[GROUP_COUNT] = {false, false}; // 分型触发待观察标记
//+------------------------------------------------------------------+
//| 解析阶梯间隔序列 |
//+------------------------------------------------------------------+
void ParseStageDistances(string seqStr, double &dists[], int &count)
{
ArrayInitialize(dists, 0);
count = 0;
string parts[];
int n = StringSplit(seqStr, ',', parts);
for(int i = 0; i < n; i++)
{
string s = parts;
StringReplace(s, " ", "");
if(StringLen(s) == 0) continue;
double val = StringToDouble(s);
if(val > 0)
{
dists[count] = val;
count++;
if(count >= 50) break;
}
}
}
//+------------------------------------------------------------------+
//| 获取当前加仓间隔(根据模式和订单数) |
//+------------------------------------------------------------------+
double GetCurrentAddDistance()
{
if(InpAddMode == 0) // 固定间隔
return InpAddDistance;
else // 阶梯间隔
{
// g_orderCount 是当前持仓订单数(含首单),第N次加仓对应的索引为 N-1 = g_orderCount - 1
int idx = g_orderCount - 1; // 因为首单时 g_orderCount=1, 首次加仓应取序列第0个
if(idx < 0) idx = 0;
if(idx >= g_stageCount)
idx = g_stageCount - 1;
if(g_stageCount == 0) return InpAddDistance; // 容错
return g_stageDistances[idx];
}
}
//+------------------------------------------------------------------+
//| 双组上下文 |
//+------------------------------------------------------------------+
int CurrentGroupMagic()
{
return g_groupMagics[g_ctxGroup];
}
string CurrentGroupTag()
{
return g_groupNames[g_ctxGroup];
}
string CurrentGroupComment()
{
return InpComment + "_" + CurrentGroupTag();
}
void SetGroupContext(int group)
{
if(group < 0 || group >= GROUP_COUNT) return;
g_ctxGroup = group;
trade.SetExpertMagicNumber(CurrentGroupMagic());
g_lineSLName = "EMA2_SL_" + CurrentGroupTag();
g_lineTrailName = "EMA2_Trail_" + CurrentGroupTag();
g_lineAvgName = "EMA2_Avg_" + CurrentGroupTag();
g_lineAddName = "EMA2_AddTrigger_" + CurrentGroupTag();
}
bool HasGroupPosition(int group)
{
int oldGroup = g_ctxGroup;
SetGroupContext(group);
bool found = false;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong tk = PositionGetTicket(i);
if(!PositionSelectByTicket(tk)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if(PositionGetInteger(POSITION_MAGIC) != CurrentGroupMagic()) continue;
found = true;
break;
}
SetGroupContext(oldGroup);
return found;
}
bool HasAnyGroupPosition()
{
for(int g = 0; g < GROUP_COUNT; g++)
{
if(HasGroupPosition(g)) return true;
}
return false;
}
int MaxWorkingGroups()
{
return InpEnableDualGroup ? GROUP_COUNT : 1;
}
bool PassABOpenGapForGroup(int targetGroup)
{
// 单组模式不需要AB间隔限制
if(!InpEnableDualGroup) return true;
// 仅当另一组当前有持仓/平仓流程中时,才限制AB开仓间隔
datetime refOpenBar = 0;
for(int g = 0; g < GROUP_COUNT; g++)
{
if(g == targetGroup) continue;
if(!HasGroupPosition(g) && !g_groupClosing[g]) continue;
if(g_groupLastOpenBarTime[g] > refOpenBar)
refOpenBar = g_groupLastOpenBarTime[g];
}
if(refOpenBar <= 0) return true;
int shift = iBarShift(_Symbol, InpPeriod, refOpenBar, false);
if(shift < 0) return true;
int minShift = MathMax(0, InpABOpenGapBars) + 1;
return (shift >= minShift);
}
int FindFreeGroup()
{
int busy = 0;
for(int g = 0; g < GROUP_COUNT; g++)
{
if(g_groupClosing[g] || HasGroupPosition(g)) busy++;
}
if(busy >= MaxWorkingGroups()) return -1;
for(int g = 0; g < GROUP_COUNT; g++)
{
if(g >= MaxWorkingGroups()) break;
if(g_groupClosing[g]) continue;
if(!HasGroupPosition(g)) return g;
}
return -1;
}
void ResetGroupRuntime(int group)
{
if(group < 0 || group >= GROUP_COUNT) return;
g_groupRt[group].direction = 0;
ArrayInitialize(g_groupRt[group].orderTickets, 0);
ArrayInitialize(g_groupRt[group].orderLots, 0);
ArrayInitialize(g_groupRt[group].orderPrices, 0);
g_groupRt[group].orderCount = 0;
g_groupRt[group].totalLots = 0;
g_groupRt[group].avgPrice = 0;
g_groupRt[group].entryPrice = 0;
g_groupRt[group].SL = 0;
g_groupRt[group].trailLine = 0;
g_groupRt[group].trailActive = false;
g_groupRt[group].isFirstOrder = true;
g_groupRt[group].closeReason = "";
g_groupRt[group].closeProfit = 0;
g_groupRt[group].closeRetries = 0;
g_groupRt[group].autoTradingWarned = false;
g_groupRt[group].closeRequestTime = 0;
ArrayInitialize(g_groupRt[group].closeTickets, 0);
g_groupRt[group].closeTicketCount = 0;
g_groupRt[group].addTrackingActive = false;
g_groupRt[group].addExtremePrice = 0;
g_groupRt[group].addTriggerPrice = 0;
g_groupRt[group].closeBarTime = 0;
g_groupRt[group].isOpening = false;
}
void LoadGroupRuntime(int group)
{
SetGroupContext(group);
g_direction = g_groupRt[group].direction;
ArrayCopy(g_orderTickets, g_groupRt[group].orderTickets);
ArrayCopy(g_orderLots, g_groupRt[group].orderLots);
ArrayCopy(g_orderPrices, g_groupRt[group].orderPrices);
g_orderCount = g_groupRt[group].orderCount;
g_totalLots = g_groupRt[group].totalLots;
g_avgPrice = g_groupRt[group].avgPrice;
g_entryPrice = g_groupRt[group].entryPrice;
g_SL = g_groupRt[group].SL;
g_trailLine = g_groupRt[group].trailLine;
g_trailActive = g_groupRt[group].trailActive;
g_isFirstOrder = g_groupRt[group].isFirstOrder;
g_closeReason = g_groupRt[group].closeReason;
g_closeProfit = g_groupRt[group].closeProfit;
g_closeRetries = g_groupRt[group].closeRetries;
g_autoTradingWarned = g_groupRt[group].autoTradingWarned;
g_closeRequestTime = g_groupRt[group].closeRequestTime;
ArrayCopy(g_closeTickets, g_groupRt[group].closeTickets);
g_closeTicketCount = g_groupRt[group].closeTicketCount;
g_addTrackingActive = g_groupRt[group].addTrackingActive;
g_addExtremePrice = g_groupRt[group].addExtremePrice;
g_addTriggerPrice = g_groupRt[group].addTriggerPrice;
g_closeBarTime = g_groupRt[group].closeBarTime;
g_kLineTraded = g_groupRt[group].kLineTraded;
}
void SaveGroupRuntime(int group)
{
g_groupRt[group].direction = g_direction;
ArrayCopy(g_groupRt[group].orderTickets, g_orderTickets);
ArrayCopy(g_groupRt[group].orderLots, g_orderLots);
ArrayCopy(g_groupRt[group].orderPrices, g_orderPrices);
g_groupRt[group].orderCount = g_orderCount;
g_groupRt[group].totalLots = g_totalLots;
g_groupRt[group].avgPrice = g_avgPrice;
g_groupRt[group].entryPrice = g_entryPrice;
g_groupRt[group].SL = g_SL;
g_groupRt[group].trailLine = g_trailLine;
g_groupRt[group].trailActive = g_trailActive;
g_groupRt[group].isFirstOrder = g_isFirstOrder;
g_groupRt[group].closeReason = g_closeReason;
g_groupRt[group].closeProfit = g_closeProfit;
g_groupRt[group].closeRetries = g_closeRetries;
g_groupRt[group].autoTradingWarned = g_autoTradingWarned;
g_groupRt[group].closeRequestTime = g_closeRequestTime;
ArrayCopy(g_groupRt[group].closeTickets, g_closeTickets);
g_groupRt[group].closeTicketCount = g_closeTicketCount;
g_groupRt[group].addTrackingActive = g_addTrackingActive;
g_groupRt[group].addExtremePrice = g_addExtremePrice;
g_groupRt[group].addTriggerPrice = g_addTriggerPrice;
g_groupRt[group].closeBarTime = g_closeBarTime;
g_groupRt[group].kLineTraded = g_kLineTraded;
}
//+------------------------------------------------------------------+
//| 持久化 |
//+------------------------------------------------------------------+
void SaveState()
{
GlobalVariableSet(g_gvPrefix + "state", (double)g_state);
GlobalVariableSet(g_gvPrefix + "prevBarTime", (double)g_prevBarTime);
// closeBarTime按组保存
GlobalVariableSet(g_gvPrefix + "closeBarTime_0", (double)g_groupRt[0].closeBarTime);
GlobalVariableSet(g_gvPrefix + "closeBarTime_1", (double)g_groupRt[1].closeBarTime);
GlobalVariableSet(g_gvPrefix + "firstBarChecked", g_firstBarChecked ? 1.0 : 0.0);
GlobalVariableSet(g_gvPrefix + "kOpen", g_kOpen);
GlobalVariableSet(g_gvPrefix + "kHigh", g_kHigh);
GlobalVariableSet(g_gvPrefix + "kLow", g_kLow);
GlobalVariableSet(g_gvPrefix + "kClose", g_kClose);
GlobalVariableSet(g_gvPrefix + "kATR", g_kATR);
GlobalVariableSet(g_gvPrefix + "candleTime", (double)g_candleTime);
GlobalVariableSet(g_gvPrefix + "bias", (double)g_bias);
GlobalVariableSet(g_gvPrefix + "emaReady", g_emaReady ? 1.0 : 0.0);
GlobalVariableSet(g_gvPrefix + "atrOk", g_atrOk ? 1.0 : 0.0);
GlobalVariableSet(g_gvPrefix + "direction", (double)g_direction);
// kLineTraded 按组保存,signalUsedBarTime 保持全局(跨组互斥)
GlobalVariableSet(g_gvPrefix + "entryPrice", g_entryPrice);
GlobalVariableSet(g_gvPrefix + "SL", g_SL);
GlobalVariableSet(g_gvPrefix + "trailLine", g_trailLine);
GlobalVariableSet(g_gvPrefix + "trailActive", g_trailActive ? 1.0 : 0.0);
GlobalVariableSet(g_gvPrefix + "isFirstOrder", g_isFirstOrder ? 1.0 : 0.0);
GlobalVariableSet(g_gvPrefix + "orderCount", (double)g_orderCount);
GlobalVariableSet(g_gvPrefix + "totalLots", g_totalLots);
GlobalVariableSet(g_gvPrefix + "avgPrice", g_avgPrice);
GlobalVariableSet(g_gvPrefix + "todayWins", (double)g_todayWins);
GlobalVariableSet(g_gvPrefix + "todayLosses", (double)g_todayLosses);
GlobalVariableSet(g_gvPrefix + "todayProfit", g_todayProfit);
GlobalVariableSet(g_gvPrefix + "todayDate", (double)g_todayDate);
// 连续止损暂停变量持久化
GlobalVariableSet(g_gvPrefix + "consecutiveLosses", (double)g_consecutiveLosses);
GlobalVariableSet(g_gvPrefix + "pauseUntilTime", (double)g_pauseUntilTime);
// 结余保护变量持久化
GlobalVariableSet(g_gvPrefix + "permanentlyStopped", g_permanentlyStopped ? 1.0 : 0.0);
GlobalVariableSet(g_gvPrefix + "maxBalance", g_maxBalance);
// 今日最大浮盈浮亏持久化
GlobalVariableSet(g_gvPrefix + "todayMaxProfit", g_todayMaxProfit);
GlobalVariableSet(g_gvPrefix + "todayMaxLoss", g_todayMaxLoss);
// 脉冲模式变量持久化
// 脉冲模式变量已删除
// 追踪加仓变量持久化
GlobalVariableSet(g_gvPrefix + "addTrackingActive", g_addTrackingActive ? 1.0 : 0.0);
GlobalVariableSet(g_gvPrefix + "addExtremePrice", g_addExtremePrice);
GlobalVariableSet(g_gvPrefix + "addTriggerPrice", g_addTriggerPrice);
// 禁止时段变量持久化
GlobalVariableSet(g_gvPrefix + "inNoTradeWindow", g_inNoTradeWindow ? 1.0 : 0.0);
// 分型保护变量持久化(按组)
GlobalVariableSet(g_gvPrefix + "fractalPending_0", g_fractalPending[0] ? 1.0 : 0.0);
GlobalVariableSet(g_gvPrefix + "fractalPending_1", g_fractalPending[1] ? 1.0 : 0.0);
}
void LoadState()
{
if(!GlobalVariableCheck(g_gvPrefix + "state")) return;
if(GlobalVariableCheck(g_gvPrefix + "inNoTradeWindow"))
g_inNoTradeWindow = (GlobalVariableGet(g_gvPrefix + "inNoTradeWindow") != 0);
g_state = (EA_STATE)(int)GlobalVariableGet(g_gvPrefix + "state");
g_prevBarTime = (datetime)GlobalVariableGet(g_gvPrefix + "prevBarTime");
// closeBarTime按组加载
if(GlobalVariableCheck(g_gvPrefix + "closeBarTime_0"))
g_groupRt[0].closeBarTime = (datetime)GlobalVariableGet(g_gvPrefix + "closeBarTime_0");
else
g_groupRt[0].closeBarTime = 0;
if(GlobalVariableCheck(g_gvPrefix + "closeBarTime_1"))
g_groupRt[1].closeBarTime = (datetime)GlobalVariableGet(g_gvPrefix + "closeBarTime_1");
else
g_groupRt[1].closeBarTime = 0;
g_firstBarChecked = (GlobalVariableGet(g_gvPrefix + "firstBarChecked") != 0);
g_kOpen = GlobalVariableGet(g_gvPrefix + "kOpen");
g_kHigh = GlobalVariableGet(g_gvPrefix + "kHigh");
g_kLow = GlobalVariableGet(g_gvPrefix + "kLow");
g_kClose = GlobalVariableGet(g_gvPrefix + "kClose");
g_kATR = GlobalVariableGet(g_gvPrefix + "kATR");
g_candleTime = (datetime)GlobalVariableGet(g_gvPrefix + "candleTime");
g_bias = (int)GlobalVariableGet(g_gvPrefix + "bias");
g_emaReady = (GlobalVariableGet(g_gvPrefix + "emaReady") != 0);
g_atrOk = (GlobalVariableGet(g_gvPrefix + "atrOk") != 0);
g_direction = (int)GlobalVariableGet(g_gvPrefix + "direction");
// kLineTraded 按组保存
// signalUsedBarTime 保持全局
g_entryPrice = GlobalVariableGet(g_gvPrefix + "entryPrice");
g_SL = GlobalVariableGet(g_gvPrefix + "SL");
g_trailLine = GlobalVariableGet(g_gvPrefix + "trailLine");
g_trailActive = (GlobalVariableGet(g_gvPrefix + "trailActive") != 0);
g_isFirstOrder = (GlobalVariableGet(g_gvPrefix + "isFirstOrder") != 0);
g_orderCount = (int)GlobalVariableGet(g_gvPrefix + "orderCount");
g_totalLots = GlobalVariableGet(g_gvPrefix + "totalLots");
g_avgPrice = GlobalVariableGet(g_gvPrefix + "avgPrice");
g_todayWins = (int)GlobalVariableGet(g_gvPrefix + "todayWins");
g_todayLosses = (int)GlobalVariableGet(g_gvPrefix + "todayLosses");
g_todayProfit = GlobalVariableGet(g_gvPrefix + "todayProfit");
g_todayDate = (datetime)GlobalVariableGet(g_gvPrefix + "todayDate");
// 连续止损暂停变量恢复
if(GlobalVariableCheck(g_gvPrefix + "consecutiveLosses"))
g_consecutiveLosses = (int)GlobalVariableGet(g_gvPrefix + "consecutiveLosses");
if(GlobalVariableCheck(g_gvPrefix + "pauseUntilTime"))
g_pauseUntilTime = (datetime)GlobalVariableGet(g_gvPrefix + "pauseUntilTime");
// 结余保护变量恢复
if(GlobalVariableCheck(g_gvPrefix + "permanentlyStopped"))
g_permanentlyStopped = (GlobalVariableGet(g_gvPrefix + "permanentlyStopped") != 0);
if(GlobalVariableCheck(g_gvPrefix + "maxBalance"))
g_maxBalance = GlobalVariableGet(g_gvPrefix + "maxBalance");
// 今日最大浮盈浮亏恢复
if(GlobalVariableCheck(g_gvPrefix + "todayMaxProfit"))
g_todayMaxProfit = GlobalVariableGet(g_gvPrefix + "todayMaxProfit");
if(GlobalVariableCheck(g_gvPrefix + "todayMaxLoss"))
g_todayMaxLoss = GlobalVariableGet(g_gvPrefix + "todayMaxLoss");
// 脉冲模式变量恢复
// 追踪加仓变量恢复
if(GlobalVariableCheck(g_gvPrefix + "addTrackingActive"))
g_addTrackingActive = (GlobalVariableGet(g_gvPrefix + "addTrackingActive") != 0);
if(GlobalVariableCheck(g_gvPrefix + "addExtremePrice"))
g_addExtremePrice = GlobalVariableGet(g_gvPrefix + "addExtremePrice");
if(GlobalVariableCheck(g_gvPrefix + "addTriggerPrice"))
g_addTriggerPrice = GlobalVariableGet(g_gvPrefix + "addTriggerPrice");
// 分型保护变量恢复(按组)
if(GlobalVariableCheck(g_gvPrefix + "fractalPending_0"))
g_fractalPending[0] = (GlobalVariableGet(g_gvPrefix + "fractalPending_0") != 0);
if(GlobalVariableCheck(g_gvPrefix + "fractalPending_1"))
g_fractalPending[1] = (GlobalVariableGet(g_gvPrefix + "fractalPending_1") != 0);
// 跨日重置统计
MqlDateTime dt;
TimeToStruct(TimeLocal(), dt);
datetime today = StringToTime(StringFormat("%04d.%02d.%02d", dt.year, dt.mon, dt.day));
if(g_todayDate > 0 && today > g_todayDate)
{
g_todayWins = 0; g_todayLosses = 0; g_todayProfit = 0; g_todayDate = today;
g_todayMaxProfit = 0; g_todayMaxLoss = 0; // 同时清除最大浮盈浮亏
}
else if(g_todayDate == 0)
{
g_todayDate = today; // 首次运行或GV不存在,只设日期不重置
}
// 异步平仓恢复:如果重启时还在STATE_CLOSING,检查持仓是否已平
if(g_state == STATE_CLOSING)
{
Print("【恢复】检测到异步平仓状态,重新检查持仓...");
// 不做任何操作,OnTick中的STATE_CLOSING会处理
}
}
//+------------------------------------------------------------------+
//| 保存累计统计数据(跨EA重启保留) |
//+------------------------------------------------------------------+
void SaveStatistics()
{
GlobalVariableSet(g_gvPrefix + "stat_totalTrades", (double)g_stat_totalTrades);
GlobalVariableSet(g_gvPrefix + "stat_wins", (double)g_stat_wins);
GlobalVariableSet(g_gvPrefix + "stat_losses", (double)g_stat_losses);
GlobalVariableSet(g_gvPrefix + "stat_totalProfit", g_stat_totalProfit);
GlobalVariableSet(g_gvPrefix + "stat_totalLoss", g_stat_totalLoss);
GlobalVariableSet(g_gvPrefix + "stat_maxProfit", g_stat_maxProfit);
GlobalVariableSet(g_gvPrefix + "stat_maxLoss", g_stat_maxLoss);
GlobalVariableSet(g_gvPrefix + "stat_maxDrawdown", g_stat_maxDrawdown);
}
//+------------------------------------------------------------------+
//| 加载累计统计数据 |
//+------------------------------------------------------------------+
void LoadStatistics()
{
if(GlobalVariableCheck(g_gvPrefix + "stat_totalTrades"))
{
g_stat_totalTrades = (int)GlobalVariableGet(g_gvPrefix + "stat_totalTrades");
g_stat_wins = (int)GlobalVariableGet(g_gvPrefix + "stat_wins");
g_stat_losses = (int)GlobalVariableGet(g_gvPrefix + "stat_losses");
g_stat_totalProfit = GlobalVariableGet(g_gvPrefix + "stat_totalProfit");
g_stat_totalLoss = GlobalVariableGet(g_gvPrefix + "stat_totalLoss");
g_stat_maxProfit = GlobalVariableGet(g_gvPrefix + "stat_maxProfit");
g_stat_maxLoss = GlobalVariableGet(g_gvPrefix + "stat_maxLoss");
if(GlobalVariableCheck(g_gvPrefix + "stat_maxDrawdown"))
g_stat_maxDrawdown = GlobalVariableGet(g_gvPrefix + "stat_maxDrawdown");
Print("【统计数据恢复】总交易:", g_stat_totalTrades,
" 盈:", g_stat_wins, " 亏:", g_stat_losses,
" 净盈亏 ", DoubleToString(g_stat_totalProfit - g_stat_totalLoss, 2),
" 最大回撤 ", DoubleToString(g_stat_maxDrawdown, 2));
}
}
//+------------------------------------------------------------------+
//| 重绘所有线(切换周期/参数后恢复) |
//+------------------------------------------------------------------+
void RedrawLines()
{
// 检查是否有区间数据
if(g_kHigh > 0 && g_kLow > 0)
{
DrawRangeLines();
Print("【重绘】区间线已恢复 高:", g_kHigh, " 低:", g_kLow);
}
// 检查是否有止损数据
if(g_SL > 0)
{
DrawSLLine();
Print("【重绘】止损线已恢复:", g_SL);
}
// 检查是否有追踪止盈线
if(g_trailLine > 0 && g_trailActive)
{
DrawTrailLine();
Print("【重绘】追踪线已恢复:", g_trailLine);
}
// 检查是否有均价线
if(g_avgPrice > 0)
{
DrawAvgLine();
Print("【重绘】均价线已恢复:", g_avgPrice);
}
// 检查是否有追踪加仓挂单线
if(g_addTriggerPrice > 0 && g_addTrackingActive)
{
DrawAddTriggerLine();
Print("【重绘】挂单线已恢复:", g_addTriggerPrice);
}
}
//+------------------------------------------------------------------+
//| 清除运行时状态全局变量(保留统计数据) |
//+------------------------------------------------------------------+
void ClearRuntimeGlobals()
{
// 清除运行时状态的全局变量,但保留统计数据(stat_xxx)
string prefix = g_gvPrefix;
int total = GlobalVariablesTotal();
// 从后往前遍历,因为删除会改变索引
for(int i = total - 1; i >= 0; i--)
{
string name = GlobalVariableName(i);
// 只删除非统计数据、非今日统计的前缀匹配变量
if(StringFind(name, prefix) == 0 && StringFind(name, "stat_") < 0 && StringFind(name, "today") < 0)
{
GlobalVariableDel(name);
}
}
Print("【清理】运行时状态已清除,统计数据和今日统计已保留");
}
//+------------------------------------------------------------------+
//| 授权 |
//+------------------------------------------------------------------+
bool CheckAuthorization()
{
// 1. 检查授权账户(支持多个账号,用英文逗号分隔)
if(g_authAccount != "")
{
long accountLogin = AccountInfoInteger(ACCOUNT_LOGIN);
string accountStr = IntegerToString(accountLogin);
// 将授权账号字符串按逗号分割成数组
string authList[];
int count = StringSplit(g_authAccount, ',', authList);
bool authorized = false;
for(int i = 0; i < count; i++)
{
// 去除可能的前后空格
StringTrimLeft(authList);
StringTrimRight(authList);
if(authList == accountStr)
{
authorized = true;
break;
}
}
if(!authorized)
{
Alert("【授权失败】账户未授权!\n当前账户: ", accountStr, "\n授权账户: ", g_authAccount);
Print("【授权失败】当前账户:", accountStr, " 授权账户:", g_authAccount);
return false;
}
}
// 2. 检查授权密钥
if(g_licenseKey != "" && g_licenseKey != "TRIAL")
{
bool keyOk = (g_licenseKey == "V2-2026") || (g_licenseKey == "V6-2026") ||
(g_licenseKey == "V8-2026") || (g_licenseKey == "V9-2026");
if(!keyOk)
{
Alert("【授权失败】密钥无效!\n当前密钥: ", g_licenseKey);
Print("【授权失败】密钥无效:", g_licenseKey);
return false;
}
}
// 3. 检查授权到期时间(服务器时间)
datetime serverTime = TimeTradeServer();
datetime localTime = TimeLocal();
int offsetHours = (int)((serverTime - localTime) / 3600);
datetime expiryUTC = g_expiryDate - offsetHours * 3600;
if(serverTime >= expiryUTC)
{
Alert("【授权失败】授权已到期!\n服务器时间: ", TimeToString(serverTime, TIME_DATE),
"\n到期日: ", TimeToString(expiryUTC, TIME_DATE));
Print("【授权失败】授权已到期,服务器时间:", TimeToString(serverTime, TIME_DATE),
" 到期日:", TimeToString(expiryUTC, TIME_DATE));
return false;
}
Print("【授权成功】账户:", AccountInfoInteger(ACCOUNT_LOGIN),
" 密钥:", g_licenseKey,
" 到期:", TimeToString(expiryUTC, TIME_DATE),
" 剩余:", (int)((expiryUTC - serverTime) / 86400), "天");
return true;
}
//+------------------------------------------------------------------+
//| 授权到期检测 + 强制平仓停止 |
//+------------------------------------------------------------------+
void StopIfExpired()
{
// 如果已经永久停止,跳过
if(g_permanentlyStopped) return;
datetime serverTime = TimeTradeServer();
datetime localTime = TimeLocal();
int offsetHours = (int)((serverTime - localTime) / 3600);
datetime expiryUTC = g_expiryDate - offsetHours * 3600;
if(serverTime < expiryUTC) return; // 未到期
// ========== 授权已到期,执行强制停止 ==========
Print("【授权到期】服务器时间已超过授权到期日,强制平仓并停止EA");
Alert("【授权到期】服务器时间已超过授权到期日!\n所有持仓将被平掉,EA将停止工作。\n到期日: ", TimeToString(expiryUTC, TIME_DATE));
// 关闭所有A组持仓
LoadGroupRuntime(0);
DoClose("授权到期强制平仓");
SaveGroupRuntime(0);
// 关闭所有B组持仓
LoadGroupRuntime(1);
DoClose("授权到期强制平仓");
SaveGroupRuntime(1);
// 标记永久停止(OnTick中的IsPermanentlyStopped()会拦截后续所有交易)
g_permanentlyStopped = true;
Print("【授权到期】所有持仓已平,EA已永久停止");
}
//+------------------------------------------------------------------+
//| 获取当前运行中K线的时间 |
//+------------------------------------------------------------------+
datetime GetCurrentBarTime()
{
MqlRates rates[];
ArraySetAsSeries(rates, true);
if(CopyRates(_Symbol, InpPeriod, 0, 1, rates) < 1) return 0;
return rates[0].time;
}
//+------------------------------------------------------------------+
//| 解析手数序列字符串(逗号分隔) |
//+------------------------------------------------------------------+
void ParseLotSequence(string seqStr, double &lots[], int &count)
{
ArrayInitialize(lots, 0);
count = 0;
string parts[];
int n = StringSplit(seqStr, ',', parts);
for(int i = 0; i < n; i++)
{
string s = parts;
StringReplace(s, " ", "");
if(StringLen(s) == 0) continue;
double lot = StringToDouble(s);
if(lot > 0)
{
lots[count] = lot;
count++;
if(count >= 50) break;
}
}
}
//+------------------------------------------------------------------+
//| 获取上一根已收盘K线数据(rates[1]) |
//+------------------------------------------------------------------+
bool GetLastClosedCandle(double &open, double &high, double &low, double &close,
double &atrVal, double &emaVal, double &cciVal, double &rsiVal,
double &rsiDirectionVal,
datetime &time)
{
MqlRates rates[];
ArraySetAsSeries(rates, true);
if(CopyRates(_Symbol, InpPeriod, 0, 2, rates) < 2) return false;
open = rates[1].open;
high = rates[1].high;
low = rates[1].low;
close = rates[1].close;
time = rates[1].time;
// ATR
atrVal = 0;
if(g_atrHandle != INVALID_HANDLE)
{
double buf[];
ArraySetAsSeries(buf, true);
if(CopyBuffer(g_atrHandle, 0, 0, 2, buf) >= 2)
atrVal = buf[1];
}
// 单EMA
emaVal = 0;
if(g_emaHandle != INVALID_HANDLE)
{
double buf[];
ArraySetAsSeries(buf, true);
if(CopyBuffer(g_emaHandle, 0, 0, 2, buf) >= 2)
emaVal = buf[1];
}
// CCI
cciVal = 0;
if(g_cciHandle != INVALID_HANDLE)
{
double buf[];
ArraySetAsSeries(buf, true);
if(CopyBuffer(g_cciHandle, 0, 0, 2, buf) >= 2)
cciVal = buf[1];
}
// RSI
rsiVal = 0;
if(g_rsiHandle != INVALID_HANDLE)
{
double buf[];
ArraySetAsSeries(buf, true);
if(CopyBuffer(g_rsiHandle, 0, 0, 2, buf) >= 2)
rsiVal = buf[1];
}
// RSI方向过滤
rsiDirectionVal = 0;
if(g_rsiDirectionHandle != INVALID_HANDLE)
{
double buf[];
ArraySetAsSeries(buf, true);
if(CopyBuffer(g_rsiDirectionHandle, 0, 0, 2, buf) >= 2)
rsiDirectionVal = buf[1];
}
return true;
}
//+------------------------------------------------------------------+
//| 画线 |
//+------------------------------------------------------------------+
void DeleteAllLines()
{
ObjectDelete(0, g_lineHighName);
ObjectDelete(0, g_lineLowName);
int oldGroup = g_ctxGroup;
for(int g = 0; g < GROUP_COUNT; g++)
{
SetGroupContext(g);
ObjectDelete(0, g_lineSLName);
ObjectDelete(0, g_lineTrailName);
ObjectDelete(0, g_lineAvgName);
ObjectDelete(0, g_lineAddName);
}
SetGroupContext(oldGroup);
}
void DeleteTradeLines()
{
ObjectDelete(0, g_lineSLName);
ObjectDelete(0, g_lineTrailName);
ObjectDelete(0, g_lineAvgName);
ObjectDelete(0, g_lineAddName);
}
void DrawRangeLines()
{
// 脉冲模式不画线
// 脉冲模式已移除
double upper = 0, lower = 0;
if(InpLineMode == LINE_BODY)
{
if(g_kOpen <= 0 || g_kClose <= 0) return;
upper = MathMax(g_kOpen, g_kClose);
lower = MathMin(g_kOpen, g_kClose);
}
else
{
if(g_kHigh <= 0 || g_kLow <= 0) return;
upper = g_kHigh;
lower = g_kLow;
}
ObjectDelete(0, g_lineHighName);
ObjectCreate(0, g_lineHighName, OBJ_HLINE, 0, 0, upper);
ObjectSetInteger(0, g_lineHighName, OBJPROP_COLOR, clrBlue);
ObjectSetInteger(0, g_lineHighName, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, g_lineHighName, OBJPROP_WIDTH, 2);
ObjectDelete(0, g_lineLowName);
ObjectCreate(0, g_lineLowName, OBJ_HLINE, 0, 0, lower);
ObjectSetInteger(0, g_lineLowName, OBJPROP_COLOR, clrBlue);
ObjectSetInteger(0, g_lineLowName, OBJPROP_STYLE, STYLE_SOLID);
ObjectSetInteger(0, g_lineLowName, OBJPROP_WIDTH, 2);
}
// 获取当前组的标记线颜色
color GetGroupColor()
{
return (g_ctxGroup == 0) ? clrOrange : clrMagenta; // A组橙色,B组紫色
}
// 获取当前组的标签文字
string GetGroupLabel()
{
return (g_ctxGroup == 0) ? "A" : "B";
}
void DrawSLLine()
{
if(g_SL <= 0) { ObjectDelete(0, g_lineSLName); return; }
ObjectDelete(0, g_lineSLName);
ObjectCreate(0, g_lineSLName, OBJ_HLINE, 0, 0, g_SL);
ObjectSetInteger(0, g_lineSLName, OBJPROP_COLOR, GetGroupColor());
ObjectSetInteger(0, g_lineSLName, OBJPROP_STYLE, STYLE_DASH);
ObjectSetInteger(0, g_lineSLName, OBJPROP_WIDTH, 2);
ObjectSetString(0, g_lineSLName, OBJPROP_TEXT, GetGroupLabel() + "止损");
}
void DrawTrailLine()
{
if(!g_trailActive || g_trailLine <= 0 || g_entryPrice <= 0)
{
ObjectDelete(0, g_lineTrailName);
return;
}
// g_trailLine是浮盈点数,需转换为实际价格
double trailPrice = (g_direction == 1) ?
g_entryPrice + g_trailLine * _Point :
g_entryPrice - g_trailLine * _Point;
trailPrice = NormalizeDouble(trailPrice, _Digits);
ObjectDelete(0, g_lineTrailName);
ObjectCreate(0, g_lineTrailName, OBJ_HLINE, 0, 0, trailPrice);
ObjectSetInteger(0, g_lineTrailName, OBJPROP_COLOR, GetGroupColor());
ObjectSetInteger(0, g_lineTrailName, OBJPROP_STYLE, STYLE_DASH);
ObjectSetInteger(0, g_lineTrailName, OBJPROP_WIDTH, 2);
ObjectSetString(0, g_lineTrailName, OBJPROP_TEXT, GetGroupLabel() + "追踪");
}
void DrawAvgLine()
{
if(g_avgPrice <= 0) return;
ObjectDelete(0, g_lineAvgName);
ObjectCreate(0, g_lineAvgName, OBJ_HLINE, 0, 0, g_avgPrice);
ObjectSetInteger(0, g_lineAvgName, OBJPROP_COLOR, GetGroupColor());
ObjectSetInteger(0, g_lineAvgName, OBJPROP_STYLE, STYLE_DASH);
ObjectSetInteger(0, g_lineAvgName, OBJPROP_WIDTH, 2);
ObjectSetString(0, g_lineAvgName, OBJPROP_TEXT, GetGroupLabel() + "均价");
}
//+------------------------------------------------------------------+
//| 画挂单线(紫色实线) |
//+------------------------------------------------------------------+
void DrawAddTriggerLine()
{
if(g_addTriggerPrice <= 0) return;
ObjectDelete(0, g_lineAddName);
ObjectCreate(0, g_lineAddName, OBJ_HLINE, 0, 0, g_addTriggerPrice);
ObjectSetInteger(0, g_lineAddName, OBJPROP_COLOR, GetGroupColor());
ObjectSetInteger(0, g_lineAddName, OBJPROP_STYLE, STYLE_DASH);
ObjectSetInteger(0, g_lineAddName, OBJPROP_WIDTH, 2);
ObjectSetString(0, g_lineAddName, OBJPROP_TEXT, GetGroupLabel() + "加仓");
}
//+------------------------------------------------------------------+
//| 查找本EA持仓 |
//+------------------------------------------------------------------+
int FindMyTicket()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong tk = PositionGetTicket(i);
if(!PositionSelectByTicket(tk)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if(PositionGetInteger(POSITION_MAGIC) != CurrentGroupMagic()) continue;
return (int)tk;
}
return 0;
}
//+------------------------------------------------------------------+
//| 检查是否还有本EA的持仓 |
//+------------------------------------------------------------------+
bool HasMyPosition()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong tk = PositionGetTicket(i);
if(!PositionSelectByTicket(tk)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if(PositionGetInteger(POSITION_MAGIC) != CurrentGroupMagic()) continue;
return true;
}
return false;
}
//+------------------------------------------------------------------+
//| 统一处理交易错误 |
//| 返回 false 表示交易失败(常规错误),返回 true 表示已处理特殊错误并停止 |
//+------------------------------------------------------------------+
bool HandleTradeError(uint retcode, string operation)
{
if(retcode == 10027) // 自动交易被客户端禁用
{
if(!g_autoTradingWarned) // 只警告一次,不刷屏
{
g_autoTradingWarned = true;
Print("╔══════════════════════════════════════════════════════════╗");
Print("║ ⚠️ 自动交易被禁用!请检查 MT5 右上角【EA交易】按钮是否为绿色 ║");
Print("║ EA 已暂停运行,等待您恢复自动交易后自动继续 ║");
Print("╚══════════════════════════════════════════════════════════╝");
}
return true;
}
else if(retcode == 10019 || retcode == 10025) // 无持仓 / 无持仓可用于平
{
Print("【" + operation + "】当前无持仓(可能已平仓),忽略");
return true; // 已处理,不是错误
}
else if(retcode == 10014) // 无效持仓单号
{
Print("【" + operation + "】无效持仓单号,跳过");
return true; // 已处理,不是错误
}
else if(retcode == 10006) // 无持仓(平仓类)
{
Print("【" + operation + "】无持仓,跳过");
return true;
}
return false; // 其他错误,返回 false 让调用方处理
}
//+------------------------------------------------------------------+
//| 恢复单组持仓状态(A/B) - 按时间排序修复版 |
//+------------------------------------------------------------------+
bool RestoreGroupPositions(int group)
{
Print("【诊断】RestoreGroupPositions 被调用,组=", group);
SetGroupContext(group);
ResetGroupRuntime(group);
LoadGroupRuntime(group);
ArrayInitialize(g_orderTickets,0);
ArrayInitialize(g_orderLots,0);
ArrayInitialize(g_orderPrices,0);
ArrayInitialize(g_closeTickets,0);
g_orderCount=0;
g_totalLots=0;
g_avgPrice=0;
g_direction=0;
g_entryPrice=0;
g_SL=0;
g_trailLine=0;
g_trailActive=false;
g_isFirstOrder=true;
g_closeTicketCount=0;
g_closeRetries=0;
g_groupClosing[group]=false;
ResetAddTracking();
// ---- 新增:临时结构体用于排序 ----
struct PosInfo { datetime time; int ticket; double lots; double price; int type; };
PosInfo temp[];
ArrayResize(temp, 0);
for(int i=PositionsTotal()-1;i>=0;i--)
{
ulong tk=PositionGetTicket(i);
if(!PositionSelectByTicket(tk)) continue;
if(PositionGetString(POSITION_SYMBOL)!=_Symbol) continue;
if(PositionGetInteger(POSITION_MAGIC)!=CurrentGroupMagic()) continue;
int size = ArraySize(temp);
ArrayResize(temp, size+1);
temp[size].time = (datetime)PositionGetInteger(POSITION_TIME);
temp[size].ticket = (int)tk;
temp[size].lots = PositionGetDouble(POSITION_VOLUME);
temp[size].price = PositionGetDouble(POSITION_PRICE_OPEN);
temp[size].type = (PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)?1:-1;
}
if(ArraySize(temp)==0) return false; // 无持仓
// ---- 按开仓时间升序排序(默认升序) ----
ArraySort(temp);
ArrayReverse(temp);
string sortCheck = "【排序验证】排序后订单顺序(时间升序): ";
for(int idx=0; idx<ArraySize(temp); idx++) {
sortCheck += StringFormat("[%d]时间:%s 价格:%.2f ",
idx,
TimeToString(temp[idx].time),
temp[idx].price);
}
Print(sortCheck);
// ---- 按排序后的顺序填充数组 ----
int count=0;
for(int i=0; i<ArraySize(temp); i++)
{
g_orderTickets[count] = temp.ticket;
g_orderLots[count] = temp.lots;
g_orderPrices[count] = temp.price;
if(g_direction==0) g_direction = temp.type;
count++;
if(count>=50) break;
}
g_orderCount = count;
g_entryPrice = g_orderPrices[0]; // 首单价格
g_SL = 0; // 重启后止损需重新计算(可由后续逻辑设定)
// 计算总手数和均价
double sumLots=0, sumPrice=0;
for(int i=0;i<g_orderCount;i++)
{
sumLots += g_orderLots;
sumPrice += g_orderLots*g_orderPrices;
}
g_totalLots = sumLots;
g_avgPrice = (sumLots>0)?(sumPrice/sumLots):0;
g_isFirstOrder = (g_orderCount<=1);
string arrayCheck = "【数组验证】g_orderPrices填充后顺序: ";
for(int idx=0; idx<g_orderCount; idx++) {
arrayCheck += StringFormat("价格[%d]:%.2f ", idx, g_orderPrices[idx]);
}
Print(arrayCheck);
// 更新各组最新开仓时间(取最晚的时间)
datetime latestPosTime = 0;
for(int i=0;i<ArraySize(temp);i++)
if(temp.time > latestPosTime) latestPosTime = temp.time;
g_groupLastOpenBarTime[group] = latestPosTime;
// 画线
DrawAvgLine();
DrawSLLine();
Print("[GroupRestore]",CurrentGroupTag()," Count:",g_orderCount," Dir:",(g_direction==1?"Long":"Short"));
SaveGroupRuntime(group);
return true;
}
//+------------------------------------------------------------------+
//| 初始化 |
//+------------------------------------------------------------------+
int OnInit()
{
g_groupMagics[0] = InpMagicNumber * 10 + 1;
g_groupMagics[1] = InpMagicNumber * 10 + 2;
g_groupLastOpenBarTime[0] = 0;
g_groupLastOpenBarTime[1] = 0;
ResetGroupRuntime(0);
ResetGroupRuntime(1);
SetGroupContext(0);
trade.SetDeviationInPoints(InpMaxSlippage);
trade.SetTypeFilling(ORDER_FILLING_FOK);
trade.SetAsyncMode(true);
g_atrHandle = iATR(_Symbol, InpPeriod, InpATRPeriod);
if(g_atrHandle == INVALID_HANDLE) Print("【警告】ATR创建失败");
g_emaHandle = iMA(_Symbol, InpPeriod, InpEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(g_emaHandle == INVALID_HANDLE) Print("【警告】EMA指标创建失败");
g_cciHandle = iCCI(_Symbol, InpPeriod, InpCCIPeriod, PRICE_TYPICAL);
if(g_cciHandle == INVALID_HANDLE) Print("【警告】CCI指标创建失败");
g_rsiHandle = iRSI(_Symbol, InpPeriod, InpRSIPeriod, PRICE_CLOSE);
if(g_rsiHandle == INVALID_HANDLE) Print("【警告】RSI指标创建失败");
g_rsiDirectionHandle = iRSI(_Symbol, InpRSIDirectionTF, InpRSIDirectionPeriod, PRICE_CLOSE);
if(g_rsiDirectionHandle == INVALID_HANDLE) Print("【警告】RSI方向过滤指标创建失败");
// 解析阶梯间隔
ParseStageDistances(InpStageAddDistances, g_stageDistances, g_stageCount);
if(g_stageCount == 0 && InpAddMode == 1)
Print("【警告】阶梯间隔序列解析为空,将使用固定间隔值作为后备");
g_isAuthorized = CheckAuthorization();
g_emaReady = false;
// 启动时检查授权是否已到期
StopIfExpired();
if(g_permanentlyStopped)
{
Print("【授权到期】EA已停止工作,请联系更新授权");
return INIT_FAILED;
}
// 初始化自定义手数序列
ParseLotSequence(InpLotSequence, g_lotSequence, g_maxOrders);
if(g_maxOrders == 0)
{
Print("【错误】手数序列未设置");
return INIT_FAILED;
}
Print("【手数序列】共", g_maxOrders, "仓,首仓:", g_lotSequence[0], ",末仓:", g_lotSequence[g_maxOrders-1]);
if(InpAddMode == 1)
{
string distStr = "";
for(int i=0;i<g_stageCount;i++)
distStr += (i==0?"":",") + DoubleToString(g_stageDistances,0);
Print("【阶梯间隔】共", g_stageCount, "档:", distStr);
}
else
Print("【固定间隔】", InpAddDistance, "点");
// 初始化异步平仓数组(按当前组上下文)
ArrayInitialize(g_closeTickets, 0);
g_closeTicketCount = 0;
g_closeRetries = 0;
g_groupClosing[0] = false;
g_groupClosing[1] = false;
// 尝试恢复状态
LoadState();
// A/B 持仓恢复(先恢复,再画线,避免用旧GV值画线)
bool hasAnyPosition = false;
bool groupHasPos[2] = {false, false};
for(int g = 0; g < GROUP_COUNT; g++)
{
if(RestoreGroupPositions(g))
{
hasAnyPosition = true;
groupHasPos[g] = true;
}
}
// 重绘画线:有持仓的组画线,无持仓的组清除残留线
for(int g = 0; g < GROUP_COUNT; g++)
{
SetGroupContext(g);
if(groupHasPos[g])
RedrawLines();
else
DeleteTradeLines();
}
SetGroupContext(0);
// 脉冲模式已移除
// 双组模式:全局状态仅负责信号等待,不再用单组IN_TRADE阻塞
g_state = STATE_WAIT_FIRST_CANDLE;
datetime curBar = GetCurrentBarTime();
if(curBar > 0)
g_prevBarTime = curBar; // 强制等待当前K线收盘后再进入信号检测
if(hasAnyPosition)
Print("【双均线2.0启动】检测到组持仓,继续监控并允许空闲组接信号");
else
Print("【双均线2.0启动】等待当前K线收盘...");
LoadStatistics(); // 恢复累计统计数据
// 初始化交易时段控制模块(移植自金麒麟king)
SS_ResetNewsState(g_ss_news);
g_ss_today_key = "";
g_ss_daily_target_locked = false;
g_ss_daily_target_hit = 0.0;
g_ss_blockEntries = false;
SS_RefreshNews(true);
Print("【时段控制】已启用 | 工作时段:", EnableTradingSessionWindow?(SS_EA_StartTime+"-"+SS_EA_StopTime):"关",
" | 收尾:", EnableDailyWrapUpPhase?(DailyWrapUpStartTime+"-"+DailyWrapUpStopTime):"关",
" | 日盈利目标:", EnableDailyProfitTarget?DoubleToString(DailyProfitTarget,2):"关",
" | 新闻暂停:", EnableHighImpactNewsPause?"开":"关");
// 初始化图表主题和UI
SetChartTheme();
UpdatePanel();
Print("【V8启动】UI初始化完成");
Print("【禁止时段】参数: ", InpNoTradeWindow, " 北京时间");
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| 卸载 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// === 原因分流 ===
if(reason == REASON_PARAMETERS || reason == REASON_CHARTCHANGE)
{
// 切换周期或改参数:保存状态,不清运行时
SaveState();
DeleteUIObjects();
Print("【V8暂停】切换周期/参数,状态已保存");
}
else if(reason == REASON_REMOVE || reason == REASON_CLOSE)
{
// 完全卸载:删所有东西,清运行时,保留统计数据
DeleteAllLines();
DeleteUIObjects();
ClearRuntimeGlobals();
Print("【V8停止】");
}
// === 指标句柄:所有情况下都必须释放,防止句柄泄漏 ===
// 注意:OnInit 里句柄为0时 iATR/iMA 返回 INVALID_HANDLE,
// 这里释放后置为 INVALID_HANDLE,下次 OnInit 会重新创建
if(g_atrHandle != INVALID_HANDLE) { IndicatorRelease(g_atrHandle); g_atrHandle = INVALID_HANDLE; }
if(g_emaHandle != INVALID_HANDLE) { IndicatorRelease(g_emaHandle); g_emaHandle = INVALID_HANDLE; }
if(g_cciHandle != INVALID_HANDLE) { IndicatorRelease(g_cciHandle); g_cciHandle = INVALID_HANDLE; }
if(g_rsiHandle != INVALID_HANDLE) { IndicatorRelease(g_rsiHandle); g_rsiHandle = INVALID_HANDLE; }
if(g_rsiDirectionHandle != INVALID_HANDLE) { IndicatorRelease(g_rsiDirectionHandle); g_rsiDirectionHandle = INVALID_HANDLE; }
}
//+------------------------------------------------------------------+
//| 图表事件(窗口大小变化时重居中背景图) |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
// 按钮点击处理
if(id == CHARTEVENT_OBJECT_CLICK &&
(sparam == g_btnCloseAll || sparam == g_btnCloseA || sparam == g_btnCloseB ||
sparam == g_btnCloseLong || sparam == g_btnCloseShort))
{
if(sparam == g_btnCloseAll)
{
bool hasAny = false;
for(int g = 0; g < GROUP_COUNT; g++)
{
if(!HasGroupPosition(g)) continue;
hasAny = true;
LoadGroupRuntime(g);
DoClose("用户点击全部平仓");
SaveGroupRuntime(g);
}
if(hasAny)
{
ChartRedraw();
Print("【用户点击】全部平仓按钮");
}
else
{
Print("【用户点击】全部平仓按钮 - 无持仓,忽略");
}
}
else if(sparam == g_btnCloseLong)
{
DoCloseByDirection(1, "用户点击多开平仓");
ChartRedraw();
}
else if(sparam == g_btnCloseShort)
{
DoCloseByDirection(-1, "用户点击空开平仓");
ChartRedraw();
}
else
{
int targetGroup = (sparam == g_btnCloseA) ? 0 : 1;
string tag = (targetGroup == 0) ? "A" : "B";
if(HasGroupPosition(targetGroup))
{
LoadGroupRuntime(targetGroup);
DoClose("用户点击" + tag + "组平仓");
SaveGroupRuntime(targetGroup);
Print("【用户点击】" + tag + "组平仓按钮");
}
else
{
Print("【用户点击】" + tag + "组平仓按钮 - 该组无持仓,忽略");
}
}
SetGroupContext(0);
ObjectSetInteger(0, sparam, OBJPROP_STATE, false);
ChartRedraw(0);
}
}
//+------------------------------------------------------------------+
//| 交易事务通知(加速异步平仓检测) |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
const MqlTradeRequest& request,
const MqlTradeResult& result)
{
// 双组模式由OnTick统一轮询处理平仓完成,此处不做单组状态门控
if(trans.type != TRADE_TRANSACTION_DEAL_ADD) return;
}
//+------------------------------------------------------------------+
//| 标记K线(统一处理) |
//+------------------------------------------------------------------+
void MarkCandle(double open, double high, double low, double close,
double atrVal, double emaVal, double cciVal, double rsiVal,
double rsiDirectionVal,
datetime time)
{
g_candleTime = time;
// 新K线时重置信号互斥(允许新K线开仓)
if(g_signalUsedBarTime > 0 && time > g_signalUsedBarTime)
g_signalUsedBarTime = 0;
// 新K线时重置所有组的 kLineTraded,允许各组在新K线重新开仓
for(int g = 0; g < GROUP_COUNT; g++)
g_groupRt[g].kLineTraded = false;
g_kOpen = NormalizeDouble(open, _Digits);
g_kHigh = NormalizeDouble(high, _Digits);
g_kLow = NormalizeDouble(low, _Digits);
g_kClose = NormalizeDouble(close, _Digits);
g_kATR = atrVal;
g_kCCI = cciVal;
g_kRSI = rsiVal;
g_kRSIDirection = rsiDirectionVal;
// 单EMA方向判断:收盘价 vs EMA
g_bias = 0;
g_emaReady = false;
g_emaVal = emaVal;
// 单均线版本:无差值检查
g_emaReady = (emaVal > 0);
if(emaVal > 0)
{
// 多头方向:收盘价 > EMA
if(close > emaVal)
g_bias = 1;
// 空头方向:收盘价 < EMA
else if(close < emaVal)
g_bias = -1;
else
g_bias = 0;
}
// ATR条件
g_atrOk = false;
if(InpATRMode != ATR_NONE)
{
if(g_kATR > 0)
{
double comparePts = 0;
if(InpATRMode == ATR_BODY)
comparePts = MathAbs(g_kClose - g_kOpen) / _Point;
else if(InpATRMode == ATR_WHOLE)
comparePts = (g_kHigh - g_kLow) / _Point;
double atrPts = g_kATR / _Point;
double mult = (InpATRMultiple <= 0) ? 1.0 : InpATRMultiple;
g_atrOk = (comparePts > atrPts * mult);
}
}
else
{
g_atrOk = true;
}
DrawRangeLines();
// 首根K线收盘后,切换到信号等待状态
if(g_state == STATE_WAIT_FIRST_CANDLE)
g_state = STATE_WAIT_SIGNAL;
SaveState();
}
//+------------------------------------------------------------------+
//| CCI/RSI 开仓过滤 |
//+------------------------------------------------------------------+
bool PassesMomentumFilters(int direction)
{
if(InpUseCCIRSIFilter)
{
if(g_cciHandle == INVALID_HANDLE || g_rsiHandle == INVALID_HANDLE)
{
Print("【开仓拦截】CCI/RSI 指标句柄无效,跳过开仓");
return false;
}
if(g_kCCI > InpCCIBlockUpper || g_kCCI < InpCCIBlockLower)
{
Print("【开仓拦截】CCI位于上下轨外 CCI:", DoubleToString(g_kCCI, 2),
" 允许区间:[", DoubleToString(InpCCIBlockLower, 0), ",", DoubleToString(InpCCIBlockUpper, 0), "]");
return false;
}
if(g_kRSI > InpRSIHighStop || g_kRSI < InpRSILowStop)
{
Print("【开仓拦截】RSI位于上下轨外 RSI:", DoubleToString(g_kRSI, 2),
" 允许区间:", DoubleToString(InpRSILowStop, 0), "-", DoubleToString(InpRSIHighStop, 0));
return false;
}
}
if(InpUseRSIDirectionFilter)
{
if(g_rsiDirectionHandle == INVALID_HANDLE)
{
Print("【开仓拦截】RSI方向过滤句柄无效,跳过开仓");
return false;
}
if(direction == 1 && g_kRSIDirection >= InpRSIBuyMax)
{
Print("【开仓拦截】5分钟RSI不满足开多阈值 RSI:", DoubleToString(g_kRSIDirection, 2),
" 需 <", DoubleToString(InpRSIBuyMax, 1));
return false;
}
if(direction == -1 && g_kRSIDirection <= InpRSISellMin)
{
Print("【开仓拦截】5分钟RSI不满足开空阈值 RSI:", DoubleToString(g_kRSIDirection, 2),
" 需 >", DoubleToString(InpRSISellMin, 1));
return false;
}
}
return true;
}
//+------------------------------------------------------------------+
//| 信号处理 |
//+------------------------------------------------------------------+
void ProcessSignal()
{
if(g_state!=STATE_WAIT_SIGNAL)return;
if(!g_isAuthorized)return;
if(IsPaused())return;
if(IsPermanentlyStopped())return;
if(g_ss_blockEntries)return; // 交易时段控制:禁开时不进场
// 禁止时段
if(CheckNoTradeWindow()){
g_inNoTradeWindow=true;
g_noTradeStartBarTime=g_candleTime;
Print("[Blocked] No trade window active");
return;
}
CheckBreakout();
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| 开仓(加仓模式首单) |
//+------------------------------------------------------------------+
bool DoOpenMartingale(int direction)
{
if(g_state != STATE_WAIT_SIGNAL) return false;
if(!g_isAuthorized) return false;
if(g_maxOrders <= 0) return false;
// ===== 互斥锁:防止同一组同时开仓 =====
if(g_groupRt[g_ctxGroup].isOpening) return false;
g_groupRt[g_ctxGroup].isOpening = true;
// 最后释放锁(无论成功与否)
bool success = false;
int tk = 0;
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double price = (direction == 1) ? ask : bid;
double lots = g_lotSequence[0];
// 再次检查持仓(防止在获取锁期间被其他组干扰)
if(HasMyPosition())
{
Print("【开仓拦截】该组已有持仓,跳过首单开仓");
g_groupRt[g_ctxGroup].isOpening = false;
return false;
}
// 同步开仓
trade.SetAsyncMode(false);
bool ok = (direction == 1) ?
trade.Buy(lots, _Symbol, price, 0, 0, CurrentGroupComment()) :
trade.Sell(lots, _Symbol, price, 0, 0, CurrentGroupComment());
trade.SetAsyncMode(true);
if(!ok)
{
uint retcode = trade.ResultRetcode();
if(HandleTradeError(retcode, "开仓")) { g_groupRt[g_ctxGroup].isOpening = false; return false; }
Print("【开仓失败】", retcode);
g_groupRt[g_ctxGroup].isOpening = false;
return false;
}
// 获取成交单号
ulong resultDeal = trade.ResultDeal();
if(resultDeal > 0 && PositionSelectByTicket(resultDeal))
{
if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == CurrentGroupMagic())
tk = (int)resultDeal;
}
// 如果无法获取,则通过遍历查找(兜底)
if(tk == 0)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong t = PositionGetTicket(i);
if(!PositionSelectByTicket(t)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if(PositionGetInteger(POSITION_MAGIC) != CurrentGroupMagic()) continue;
int posType = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ? 1 : -1;
if(posType == direction) { tk = (int)t; break; }
}
}
if(tk == 0)
{
Print("【开仓失败】找不到持仓,但订单可能已提交,请检查");
g_groupRt[g_ctxGroup].isOpening = false;
return false;
}
// ===== 立即更新数组(不再依赖 Sleep 后的遍历) =====
ArrayInitialize(g_orderTickets, 0);
ArrayInitialize(g_orderLots, 0);
ArrayInitialize(g_orderPrices, 0);
g_orderTickets[0] = tk;
g_orderLots[0] = lots;
g_orderPrices[0] = price;
g_orderCount = 1;
g_totalLots = lots;
g_avgPrice = price;
g_direction = direction;
g_entryPrice = price;
g_SL = 0;
g_trailActive = false;
g_trailLine = 0;
g_isFirstOrder = true;
DrawAvgLine();
Print("【马丁首单】", (direction == 1 ? "多" : "空"),
" 手数:", lots, " 价格:", price);
g_groupLastOpenBarTime[g_ctxGroup] = GetCurrentBarTime();
SaveState();
// 释放锁
g_groupRt[g_ctxGroup].isOpening = false;
return true;
}
//+------------------------------------------------------------------+
//| 加仓(加仓模式) |
//+------------------------------------------------------------------+
bool DoAddOrder()
{
// 马丁模式已设为默认
if(g_orderCount <= 0) return false;
if(g_orderCount >= g_maxOrders)
{
Print("【加仓停止】已达到仓数上限:", g_maxOrders, "仓");
return false;
}
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double newLots = g_lotSequence[g_orderCount];
if(newLots < 0.01)
{
Print("【加仓停止】手数错误:", newLots);
return false;
}
double price = (g_direction == 1) ? ask : bid;
// 加仓也用同步模式,确保仓位创建后再继续
trade.SetAsyncMode(false);
bool ok = (g_direction == 1) ?
trade.Buy(newLots, _Symbol, price, 0, 0, CurrentGroupComment()) :
trade.Sell(newLots, _Symbol, price, 0, 0, CurrentGroupComment());
trade.SetAsyncMode(true);
if(!ok)
{
uint retcode = trade.ResultRetcode();
if(HandleTradeError(retcode, "加仓")) return false;
Print("【加仓失败】", retcode);
return false;
}
Sleep(300);
// [FIX3] 精确匹配:优先用经纪商返回的成交单号,兜底遍历找最接近价格+排除已跟踪票
ulong resultDeal = trade.ResultDeal();
int tk = 0;
if(resultDeal > 0)
{
// 优先使用经纪商返回的成交单号
if(PositionSelectByTicket(resultDeal))
{
if(PositionGetString(POSITION_SYMBOL) == _Symbol)
if(PositionGetInteger(POSITION_MAGIC) == CurrentGroupMagic())
tk = (int)resultDeal;
}
}
if(tk == 0)
{
// 兜底:遍历找当前组+同方向+价格最接近发单价+不在g_orderTickets中的票
double targetPrice = (g_direction == 1) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID);
double bestDiff = 1e10;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong t = PositionGetTicket(i);
if(!PositionSelectByTicket(t)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if(PositionGetInteger(POSITION_MAGIC) != CurrentGroupMagic()) continue;
int posType = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ? 1 : -1;
if(posType != g_direction) continue;
// 跳过已在 g_orderTickets 中的票,防止重复跟踪
bool alreadyTracked = false;
for(int j = 0; j < g_orderCount; j++)
if((int)g_orderTickets[j] == (int)t) { alreadyTracked = true; break; }
if(alreadyTracked) continue;
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double diff = MathAbs(openPrice - targetPrice);
if(diff < bestDiff) { bestDiff = diff; tk = (int)t; }
}
}
if(tk == 0) { Print("【加仓失败】找不到持仓"); return false; }
g_orderTickets[g_orderCount] = tk;
g_orderLots[g_orderCount] = newLots;
g_orderPrices[g_orderCount] = price;
g_orderCount++;
g_isFirstOrder = false;
// 计算总手数和均价
double mainLots = 0;
double mainSum = 0;
for(int i = 0; i < g_orderCount; i++)
{
mainLots += g_orderLots;
mainSum += g_orderPrices * g_orderLots;
}
if(mainLots > 0)
g_avgPrice = mainSum / mainLots;
g_totalLots = mainLots;
DrawAvgLine();
SaveState();
Print("【加仓】手数:", newLots, " 累计:", g_totalLots, " 均价:", g_avgPrice);
return true;
}
//+------------------------------------------------------------------+
//| 检查加仓条件(原逻辑,不启用追踪时使用) |
//+------------------------------------------------------------------+
void CheckAddOrder()
{
// 马丁模式已设为默认
if(g_orderCount <= 0) return;
if(g_orderCount >= g_maxOrders) return;
// 启用追踪加仓时,走新逻辑
if(InpUseAddTracking)
{
CheckAddOrderWithTracking();
return;
}
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double diff = 0;
if(g_direction == 1)
diff = (g_orderPrices[0] - ask) / _Point;
else
diff = (bid - g_orderPrices[0]) / _Point;
// 获取当前应使用的加仓间隔(阶梯或固定)
double addDist = GetCurrentAddDistance();
double needDiff = addDist * g_orderCount;
if(diff >= needDiff)
{
Print("【满足加仓条件】当前亏损:", DoubleToString(diff, 0),
"点 需要:", DoubleToString(needDiff, 0), "点");
DoAddOrder();
}
return;
}
//+------------------------------------------------------------------+
//| 检查加仓条件(追踪挂单模式) |
//+------------------------------------------------------------------+
void CheckAddOrderWithTracking()
{
// 马丁模式已设为默认
if(g_orderCount <= 0) return;
if(g_orderCount >= g_maxOrders) return;
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double point = _Point;
// 计算正常加仓价(以上一次加仓价为基准)
double lastAddPrice = g_orderPrices[g_orderCount - 1]; // 上一次加仓价
double addDist = GetCurrentAddDistance(); // 获取当前间隔
double normalAddPrice;
if(g_direction == 1) // 多单,价格跌了才加仓
{
normalAddPrice = lastAddPrice - addDist * _Point; // 上一次价格 - 间隔
}
else // 空单,价格涨了才加仓
{
normalAddPrice = lastAddPrice + addDist * _Point; // 上一次价格 + 间隔
}
// 计算触发门槛价 = 正常加仓价 ± 30点(逆势方向再突破30点)
double triggerThreshold;
if(g_direction == 1) // 多单,价格要跌得更低
{
triggerThreshold = normalAddPrice - InpAddTriggerPoints * point;
}
else // 空单,价格要涨得更高
{
triggerThreshold = normalAddPrice + InpAddTriggerPoints * point;
}
// 检查是否触发追踪
if(g_direction == 1) // 多单,价格跌了才加仓
{
// 价格跌破触发门槛 → 激活追踪或更新极端价
if(bid <= triggerThreshold)
{
if(!g_addTrackingActive)
{
// 首次触发,记录极端价(最低价)
g_addTrackingActive = true;
g_addExtremePrice = bid;
g_addTriggerPrice = g_addExtremePrice + InpAddTrailPoints * point; // 挂单线在极端价上方
DrawAddTriggerLine();
Print("【追踪加仓激活】多单 上次加仓价:", DoubleToString(lastAddPrice, _Digits),
" 触发价:", DoubleToString(triggerThreshold, _Digits),
" 挂单线:", DoubleToString(g_addTriggerPrice, _Digits));
return; // [FIX2] 激活后立即返回,防止同tick价格快速反弹触发两次加仓
}
else
{
// 更新极端价(只降不升,记录更低的低点)
if(bid < g_addExtremePrice)
{
g_addExtremePrice = bid;
g_addTriggerPrice = g_addExtremePrice + InpAddTrailPoints * point; // 挂单线跟着往下移
DrawAddTriggerLine();
}
}
}
}
else // 空单,价格涨了才加仓
{
// 价格涨破触发门槛 → 激活追踪或更新极端价
if(ask >= triggerThreshold)
{
if(!g_addTrackingActive)
{
// 首次触发,记录极端价(最高价)
g_addTrackingActive = true;
g_addExtremePrice = ask;
g_addTriggerPrice = g_addExtremePrice - InpAddTrailPoints * point; // 挂单线在极端价下方
DrawAddTriggerLine();
Print("【追踪加仓激活】空单 上次加仓价:", DoubleToString(lastAddPrice, _Digits),
" 触发价:", DoubleToString(triggerThreshold, _Digits),
" 挂单线:", DoubleToString(g_addTriggerPrice, _Digits));
return; // [FIX2] 激活后立即返回,防止同tick价格快速反弹触发两次加仓
}
else
{
// 更新极端价(只升不降,记录更高的高点)
if(ask > g_addExtremePrice)
{
g_addExtremePrice = ask;
g_addTriggerPrice = g_addExtremePrice - InpAddTrailPoints * point; // 挂单线跟着往上移
DrawAddTriggerLine();
}
}
}
}
// 触碰挂单线判断独立检查(不受门槛条件限制)
if(g_addTrackingActive)
{
if(g_direction == 1 && bid >= g_addTriggerPrice) // 多单:价格反弹触碰挂单线
{
Print("【追踪加仓触发】多单 触碰挂单线:", DoubleToString(g_addTriggerPrice, _Digits));
DoAddOrder();
ResetAddTracking();
}
else if(g_direction == -1 && ask <= g_addTriggerPrice) // 空单:价格回落触碰挂单线
{
Print("【追踪加仓触发】空单 触碰挂单线:", DoubleToString(g_addTriggerPrice, _Digits));
DoAddOrder();
ResetAddTracking();
}
}
}
//+------------------------------------------------------------------+
//| 重置追踪加仓状态 |
//+------------------------------------------------------------------+
void ResetAddTracking()
{
g_addTrackingActive = false;
g_addExtremePrice = 0;
g_addTriggerPrice = 0;
ObjectDelete(0, g_lineAddName);
}
//+------------------------------------------------------------------+
//| 检测突破 |
//+------------------------------------------------------------------+
void CheckBreakout()
{
if(g_state != STATE_WAIT_SIGNAL) return;
if(g_kLineTraded) return;
// 任意组开仓后同K线禁止另一组开仓
if(g_signalUsedBarTime > 0 && g_signalUsedBarTime == g_candleTime) return;
// 防止同组在同一根K线平仓后立即开仓
if(g_closeBarTime > 0 && g_closeBarTime == g_candleTime) return;
if(!g_isAuthorized) return;
// ===== 禁止时段:禁止开仓 =====
if(g_inNoTradeWindow)
{
Print("【开仓拦截】禁止时段中,不开仓");
return;
}
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double upper = 0, lower = 0;
if(InpLineMode == LINE_BODY)
{
if(g_kOpen <= 0 || g_kClose <= 0) return;
upper = MathMax(g_kOpen, g_kClose);
lower = MathMin(g_kOpen, g_kClose);
}
else
{
if(g_kHigh <= 0 || g_kLow <= 0) return;
upper = g_kHigh;
lower = g_kLow;
}
// ===== EMA方向过滤:价格在均线下方只做空,价格在均线上方只做多 =====
// 多单方向:只在均线之上开多
if(ask > upper && g_bias == -1)
{
Print("【开仓拦截】价格在均线下方,不允许多单 g_bias:", g_bias);
return;
}
// 空单方向:只在均线之下开空
if(bid < lower && g_bias == 1)
{
Print("【开仓拦截】价格在均线上方,不允许空单 g_bias:", g_bias);
return;
}
// 多单:突破区间高价
if(ask > upper)
{
Print("【开仓诊断】检测到向上突破 ask:", ask, " upper:", upper,
" ATR模式:", InpATRMode, " atrOk:", g_atrOk);
if(InpATRMode != ATR_NONE && !g_atrOk)
{
// ATR不满足,不标记g_kLineTraded,让空单仍有机会
Print("【开仓诊断】多单被ATR过滤 atrOk:", g_atrOk);
}
else
{
if(!PassesMomentumFilters(1))
return;
Print("【开仓诊断】多单全部通过,执行开仓");
bool ok = DoOpenMartingale(1);
if(ok) { g_kLineTraded = true; g_signalUsedBarTime = g_candleTime; }
}
return;
}
// 空单:跌破区间低价
if(bid < lower)
{
Print("【开仓诊断】检测到向下突破 bid:", bid, " lower:", lower,
" ATR模式:", InpATRMode, " atrOk:", g_atrOk);
if(InpATRMode != ATR_NONE && !g_atrOk)
{
// ATR不满足,不标记g_kLineTraded,让多单仍有机会
Print("【开仓诊断】空单被ATR过滤 atrOk:", g_atrOk);
}
else
{
if(!PassesMomentumFilters(-1))
return;
Print("【开仓诊断】空单全部通过,执行开仓");
bool ok = DoOpenMartingale(-1);
if(ok) { g_kLineTraded = true; g_signalUsedBarTime = g_candleTime; }
}
return;
}
}
//+------------------------------------------------------------------+
//| 计算总浮盈(加仓模式用) |
//+------------------------------------------------------------------+
double CalcTotalProfit()
{
double profit = 0;
// 计算所有持仓的浮盈
for(int i = 0; i < g_orderCount; i++)
{
if(g_orderTickets <= 0) continue;
if(!PositionSelectByTicket(g_orderTickets))
{
g_orderTickets = 0;
continue;
}
profit += PositionGetDouble(POSITION_PROFIT);
profit += PositionGetDouble(POSITION_SWAP);
}
// 更新今日最大浮盈浮亏
if(profit > g_todayMaxProfit) g_todayMaxProfit = profit;
if(profit < g_todayMaxLoss) g_todayMaxLoss = profit;
return profit;
}
void MonitorTradeMartingale()
{
bool hasPos = false;
for(int i = 0; i < g_orderCount; i++)
{
if(g_orderTickets <= 0) continue;
if(PositionSelectByTicket(g_orderTickets))
{
hasPos = true;
break;
}
}
if(!hasPos)
{
// 先用HasMyPosition扫描市场,确认是否真的无持仓
// (防止g_orderTickets不同步导致误判)
if(HasMyPosition())
{
Print("【持仓恢复】", CurrentGroupTag(), " g_orderTickets丢失,从市场恢复持仓跟踪");
RestoreGroupPositions(g_ctxGroup);
// 恢复后继续正常监控(不return,让后续逻辑执行)
}
else
{
DoClose("持仓消失");
return;
}
}
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double cur = (g_direction == 1) ? bid : ask;
double totalProfit = CalcTotalProfit();
// ===== 分型保护平仓(优先级1) =====
if(g_fractalPending[g_ctxGroup])
{
// 浮亏绝对值达到保护金额 -> 平仓
if(totalProfit <= -InpFractalProtectionAmount)
{
Print("【分型保护触发】", CurrentGroupTag(), "组 浮亏:", DoubleToString(totalProfit, 2), " <= -", InpFractalProtectionAmount);
DoClose("分型保护平仓");
g_fractalPending[g_ctxGroup] = false;
return;
}
}
// ===== 马丁强制止损(优先级2,与分型保护并行) =====
if(totalProfit <= -InpLossAmount)
{
DoClose("统一止损");
g_fractalPending[g_ctxGroup] = false; // 清除分型标记
}
double basePrice = g_isFirstOrder ? g_orderPrices[0] : g_avgPrice;
double pts = (g_direction == 1) ?
(cur - basePrice) / _Point : (basePrice - cur) / _Point;
// 移动止盈模式(仅保留移动止盈)
// 马丁模式:首单用150点激活,加仓后用80点激活
int trailActivePts = g_isFirstOrder ? InpFirstTrailActive : InpAddTrailActive;
double trailCheckPts = pts;
bool trailJustActivated = false;
if(!g_trailActive && trailCheckPts >= trailActivePts)
{
g_trailActive = true;
g_trailLine = trailCheckPts - InpTrailStep;
trailJustActivated = true;
Print("【移动止盈激活】", (g_isFirstOrder ? "首单" : "加仓单"),
" 浮盈", DoubleToString(trailCheckPts, 0), "点,止盈线", DoubleToString(g_trailLine, 0), "点");
}
if(g_trailActive)
{
if(!trailJustActivated && trailCheckPts - InpTrailStep > g_trailLine)
{
g_trailLine = trailCheckPts - InpTrailStep;
}
if(!trailJustActivated && trailCheckPts <= g_trailLine)
{
DoClose("追踪止盈");
g_fractalPending[g_ctxGroup] = false; // 移动止盈平仓后清除分型标记
return;
}
}
CheckAddOrder();
}
void DoClose(string reason)
{
// 先收集所有需要平仓的ticket(必须在CalcTotalProfit之前,因为CalcTotalProfit会清零选不上的ticket)
ArrayInitialize(g_closeTickets, 0);
g_closeTicketCount = 0;
for(int i = 0; i < g_orderCount; i++)
{
if(g_orderTickets <= 0) continue;
g_closeTickets[g_closeTicketCount] = (ulong)g_orderTickets;
g_closeTicketCount++;
}
// 如果从 g_orderTickets 收集为空,说明 g_orderCount 可能已不同步
// 从全局持仓列表中恢复属于当前组的真实持仓
if(g_closeTicketCount == 0 && g_orderCount > 0)
{
Print("[Close] g_orderCount=", g_orderCount, " but tickets empty, recovering from market...");
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong tk = PositionGetTicket(i);
if(!PositionSelectByTicket(tk)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
if(PositionGetInteger(POSITION_MAGIC) != CurrentGroupMagic()) continue;
g_closeTickets[g_closeTicketCount] = tk;
g_closeTicketCount++;
if(g_closeTicketCount >= 50) break;
}
if(g_closeTicketCount > 0)
Print("[Close] Recovered ", g_closeTicketCount, " positions from market");
}
if(g_closeTicketCount == 0)
{
Print("[Close] No positions to close, clear group state directly");
// 没有持仓需要平,直接调用 AfterClose 清理状态
AfterClose();
return;
}
// 收集完ticket后再计算盈亏
double profit = CalcTotalProfit();
g_closeReason = reason;
g_closeProfit = profit;
g_closeRetries = 0;
g_closeRequestTime = TimeCurrent();
g_groupClosing[g_ctxGroup] = true;
for(int i = 0; i < g_closeTicketCount; i++)
trade.PositionClose(g_closeTickets);
Print("[Async Close]", reason, " Profit ", DoubleToString(profit,2), " Sent:", g_closeTicketCount);
SaveState();
}
//+------------------------------------------------------------------+
//| 异步平仓(批量发送,平仓更快) |
//+------------------------------------------------------------------+
void CheckCloseCompleted()
{
if(!g_groupClosing[g_ctxGroup]) return;
// 直接查 g_closeTickets 是否还有持仓在市,不用 g_orderTickets
// 因为 CalcTotalProfit 可能已经清零了 g_orderTickets 中的无效票
int remaining = 0;
for(int i = 0; i < g_closeTicketCount; i++)
{
if(g_closeTickets <= 0) continue;
if(PositionSelectByTicket(g_closeTickets))
remaining++;
}
if(remaining == 0)
{
Print("[Async Close Done]", g_closeReason, " Profit ", DoubleToString(g_closeProfit, 2));
if(g_closeProfit > 0)
{
g_todayWins++;
g_consecutiveLosses = 0;
}
else if(g_closeProfit < 0)
{
g_todayLosses++;
g_consecutiveLosses++;
if(g_consecutiveLosses >= InpMaxLosses)
{
g_pauseUntilTime = TimeCurrent() + InpPauseMinutes * 60;
g_consecutiveLosses = 0;
Print("[Consecutive Loss] Paused for", InpPauseMinutes, "min");
}
}
g_todayProfit += g_closeProfit;
AfterClose();
return;
}
uint elapsed = (uint)(TimeCurrent() - g_closeRequestTime) * 1000;
uint retryInterval = 200;
if(elapsed >= retryInterval)
{
g_closeRetries++;
if(g_closeRetries >= 10)
{
Print("[Force Close] Retrying with larger slippage, remaining:", remaining);
trade.SetDeviationInPoints(1000);
for(int i = 0; i < g_closeTicketCount; i++)
{
if(g_closeTickets <= 0) continue;
trade.PositionClose(g_closeTickets);
}
trade.SetDeviationInPoints(InpMaxSlippage);
}
else
{
Print("[Async Retry]", g_closeRetries, " remaining:", remaining);
for(int i = 0; i < g_closeTicketCount; i++)
{
if(g_closeTickets <= 0) continue;
trade.PositionClose(g_closeTickets);
}
}
g_closeRequestTime = TimeCurrent();
SaveState();
}
}
//+------------------------------------------------------------------+
//| 结余保护检查(保护值随最大结余提升) |
//+------------------------------------------------------------------+
void CheckNetProtection()
{
if(!InpUseNetProtection) return;
double currentBalance = AccountInfoDouble(ACCOUNT_BALANCE);
// 初始化:首次运行或重启后无记录
if(g_maxBalance == 0)
{
g_maxBalance = currentBalance;
Print("【结余保护初始化】初始结余$", DoubleToString(g_maxBalance, 2),
" 保护值$", DoubleToString(g_maxBalance - InpNetProtectionDistance, 2));
SaveState();
return;
}
// 更新最大结余(只升不降)
if(currentBalance > g_maxBalance)
{
g_maxBalance = currentBalance;
Print("【结余保护提升】最大结余$", DoubleToString(g_maxBalance, 2),
" → 保护值$", DoubleToString(g_maxBalance - InpNetProtectionDistance, 2));
SaveState();
}
// 计算当前保护值 = 最大结余 - 保护距离
double protectionValue = g_maxBalance - InpNetProtectionDistance;
// 检查是否触发保护(只在无持仓时检查,不影响持仓中的EA)
if(!HasAnyGroupPosition() && currentBalance < protectionValue)
{
g_permanentlyStopped = true;
Print("【结余保护触发】当前结余$", DoubleToString(currentBalance, 2),
" < 保护值$", DoubleToString(protectionValue, 2),
"(最大结余$", DoubleToString(g_maxBalance, 2),
" - 距离$", DoubleToString(InpNetProtectionDistance, 2), ")",
",EA永久停止");
SaveState();
}
}
//+------------------------------------------------------------------+
//| 计算当前保护值 |
//| 公式: 最大结余 - 保护距离 |
//+------------------------------------------------------------------+
double CalcProtectionValue()
{
if(g_maxBalance == 0) return 0;
return g_maxBalance - InpNetProtectionDistance;
}
//+------------------------------------------------------------------+
//| 检查暂停状态 |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| 获取当前北京时间(UTC+8) |
//+------------------------------------------------------------------+
datetime GetBeijingTime()
{
return TimeCurrent() + 8 * 3600; // UTC+8
}
//+------------------------------------------------------------------+
//| 禁止时段检测 |
//| 参数 InpNoTradeWindow = "HH:MM-HH:MM" 北京时间 |
//| 例:InpNoTradeWindow="05:00-06:30" |
//| 05:00起禁止开仓,06:30后恢复正常 |
//+------------------------------------------------------------------+
bool CheckNoTradeWindow()
{
// 如果参数为空或"-",则不禁用
if(InpNoTradeWindow == "" || InpNoTradeWindow == "-")
return false;
// 解析参数格式:start-end,如"05:30-06:30"
int sepPos = StringFind(InpNoTradeWindow, "-");
if(sepPos < 0) return false;
// 提取并清理起始时间字符串(去除空格)
string rawStart = StringSubstr(InpNoTradeWindow, 0, sepPos);
StringTrimLeft(rawStart);
StringTrimRight(rawStart);
// 提取结束时间字符串
string rawEnd = StringSubstr(InpNoTradeWindow, sepPos + 1);
StringTrimLeft(rawEnd);
StringTrimRight(rawEnd);
// 解析起始时间 HH:MM
int colonPos1 = StringFind(rawStart, ":");
if(colonPos1 < 0) return false;
int startH = (int)StringToInteger(StringSubstr(rawStart, 0, colonPos1));
int startM = (int)StringToInteger(StringSubstr(rawStart, colonPos1 + 1));
// 解析结束时间 HH:MM
int colonPos2 = StringFind(rawEnd, ":");
if(colonPos2 < 0) return false;
int endH = (int)StringToInteger(StringSubstr(rawEnd, 0, colonPos2));
int endM = (int)StringToInteger(StringSubstr(rawEnd, colonPos2 + 1));
// 获取当前北京时间的时分
datetime beijingNow = GetBeijingTime();
MqlDateTime mdt;
TimeToStruct(beijingNow, mdt);
int nowH = mdt.hour;
int nowM = mdt.min;
// 转为分钟数便于比较
int nowMin = nowH * 60 + nowM;
int startMin = startH * 60 + startM;
int endMin = endH * 60 + endM;
if(startMin < endMin)
{
// 普通区间:05:30-06:30
return (nowMin >= startMin && nowMin < endMin);
}
else
{
// 跨天区间:21:30-04:00
return (nowMin >= startMin || nowMin < endMin);
}
}
bool IsPaused()
{
if(g_pauseUntilTime > 0 && TimeCurrent() < g_pauseUntilTime)
{
// 不每个Tick打印,避免日志刷屏
return true;
}
// 暂停结束,清除标志
if(g_pauseUntilTime > 0 && TimeCurrent() >= g_pauseUntilTime)
{
g_pauseUntilTime = 0;
Print("【暂停结束】恢复交易");
SaveState();
}
return false;
}
//+------------------------------------------------------------------+
//| 检查永久停止状态 |
//+------------------------------------------------------------------+
bool IsPermanentlyStopped()
{
if(g_permanentlyStopped)
{
Print("【永久停止】结余保护已触发,不再交易");
return true;
}
return false;
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| 平仓后处理 |
//+------------------------------------------------------------------+
void AfterClose()
{
DeleteTradeLines();
g_direction = 0;
g_entryPrice = 0; g_SL = 0; g_trailLine = 0;
g_trailActive = false; g_isFirstOrder = true;
g_orderCount = 0; g_totalLots = 0; g_avgPrice = 0;
ArrayInitialize(g_orderTickets, 0);
ArrayInitialize(g_orderLots, 0);
ArrayInitialize(g_orderPrices, 0);
// 清除分型保护标记
g_fractalPending[g_ctxGroup] = false;
// 重置追踪加仓变量
ResetAddTracking();
// 清理异步平仓状态
ArrayInitialize(g_closeTickets, 0);
g_closeTicketCount = 0;
g_closeRetries = 0;
g_closeReason = "";
g_autoTradingWarned = false; // 平仓后重置10027警告标志
// 记录平仓时的已收盘K线时间(防止同K线重开)
// 注意:g_candleTime是已收盘K线时间,barTime是当前K线时间
// 用g_candleTime才能正确匹配CheckBreakout中的判断
g_closeBarTime = g_candleTime;
// 更新结余保护(平仓后检查)
CheckNetProtection();
// 更新累计统计
g_stat_totalTrades++;
if(g_closeProfit > 0)
{
g_stat_wins++;
g_stat_totalProfit += g_closeProfit;
if(g_closeProfit > g_stat_maxProfit) g_stat_maxProfit = g_closeProfit;
}
else if(g_closeProfit < 0)
{
g_stat_losses++;
g_stat_totalLoss -= g_closeProfit; // g_closeProfit是负数,减去变加
if(g_closeProfit < g_stat_maxLoss) g_stat_maxLoss = g_closeProfit;
}
SaveStatistics();
Print("【统计更新】总交易:", g_stat_totalTrades,
" 盈:", g_stat_wins, " 亏:", g_stat_losses,
" 胜率:", (g_stat_totalTrades > 0 ? DoubleToString(100.0 * g_stat_wins / g_stat_totalTrades, 1) : "0"), "%",
" 净盈亏 ", DoubleToString(g_stat_totalProfit - g_stat_totalLoss, 2));
g_groupClosing[g_ctxGroup] = false;
// 不在这里重置g_kLineTraded,让g_closeBarTime检查来防止同K线重开
SaveState();
Print("【组平仓完成】", CurrentGroupTag(), "组 平仓K线时间:", g_closeBarTime);
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| 赛博朋克图表配色 |
//+------------------------------------------------------------------+
void SetChartTheme()
{
if(!InpUseChartTheme) return;
ChartSetInteger(0, CHART_COLOR_BACKGROUND, C'6,6,8'); // 纯黑背景
ChartSetInteger(0, CHART_COLOR_FOREGROUND, C'212,175,55'); // 金色前景(轴标签)
ChartSetInteger(0, CHART_COLOR_GRID, C'26,22,12'); // 暗金网格
ChartSetInteger(0, CHART_COLOR_CHART_LINE, C'212,175,55'); // 收盘价线 金
ChartSetInteger(0, CHART_COLOR_CANDLE_BULL, C'46,204,113'); // 阳线 盈利绿
ChartSetInteger(0, CHART_COLOR_CANDLE_BEAR, C'231,76,60'); // 阴线 亏损红
ChartSetInteger(0, CHART_COLOR_CHART_UP, C'46,204,113'); // 柱状图上 绿
ChartSetInteger(0, CHART_COLOR_CHART_DOWN, C'231,76,60'); // 柱状图下 红
ChartSetInteger(0, CHART_COLOR_VOLUME, C'160,128,48'); // 成交量 暗金
ChartSetInteger(0, CHART_COLOR_BID, C'231,76,60'); // 卖价 红
ChartSetInteger(0, CHART_COLOR_ASK, C'46,204,113'); // 买价 绿
ChartSetInteger(0, CHART_COLOR_STOP_LEVEL, C'240,208,120'); // 止损线 亮金
ChartRedraw(0);
}
//| UI (赛博朋克风格) |
//+------------------------------------------------------------------+
void CreatePanelBG(int panelHeight)
{
// 纯黑背景 + 暗金边框(匹配图片风格)
if(ObjectFind(0, g_panelBg) == -1)
ObjectCreate(0, g_panelBg, OBJ_RECTANGLE_LABEL, 0, 0, 0);
ObjectSetInteger(0, g_panelBg, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, g_panelBg, OBJPROP_XDISTANCE, InpUIX);
ObjectSetInteger(0, g_panelBg, OBJPROP_YDISTANCE, InpUIY);
ObjectSetInteger(0, g_panelBg, OBJPROP_XSIZE, InpUIWidth);
ObjectSetInteger(0, g_panelBg, OBJPROP_YSIZE, panelHeight);
ObjectSetInteger(0, g_panelBg, OBJPROP_BGCOLOR, C'5,5,5'); // 纯黑底
ObjectSetInteger(0, g_panelBg, OBJPROP_COLOR, C'160,128,48'); // 暗金边框
ObjectSetInteger(0, g_panelBg, OBJPROP_BORDER_TYPE, BORDER_FLAT);
ObjectSetInteger(0, g_panelBg, OBJPROP_WIDTH, 1);
ObjectSetInteger(0, g_panelBg, OBJPROP_BACK, false);
ObjectSetInteger(0, g_panelBg, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, g_panelBg, OBJPROP_HIDDEN, true);
}
void CreatePanelLabel(string name, int x, int y, string text, color clr, int size = 10, string font = "Microsoft YaHei")
{
if(ObjectFind(0, name) == -1)
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, size);
ObjectSetString(0, name, OBJPROP_TEXT, text);
ObjectSetString(0, name, OBJPROP_FONT, font);
ObjectSetInteger(0, name, OBJPROP_BACK, false);
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
}
//--- 面板绘制辅助: 顶端色条(标题装饰) ---
void CreatePanelStrip(string name, int x, int y, int w, int h, color bg)
{
if(ObjectFind(0, name) == -1)
ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
ObjectSetInteger(0, name, OBJPROP_XSIZE, w);
ObjectSetInteger(0, name, OBJPROP_YSIZE, h);
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bg);
ObjectSetInteger(0, name, OBJPROP_COLOR, bg);
ObjectSetInteger(0, name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
ObjectSetInteger(0, name, OBJPROP_BACK, false);
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
}
void UpdatePanelLabel(string name, string text)
{
if(ObjectFind(0, name) >= 0)
ObjectSetString(0, name, OBJPROP_TEXT, text);
}
void CreateCyberButton(string name, int x, int y, int w, int h, string text, color bg, color fg)
{
if(ObjectFind(0, name) == -1)
ObjectCreate(0, name, OBJ_BUTTON, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
ObjectSetInteger(0, name, OBJPROP_XSIZE, w);
ObjectSetInteger(0, name, OBJPROP_YSIZE, h);
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bg);
ObjectSetInteger(0, name, OBJPROP_COLOR, fg);
ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, C'80,80,80');
ObjectSetString(0, name, OBJPROP_TEXT, text);
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 10);
ObjectSetString(0, name, OBJPROP_FONT, "Microsoft YaHei");
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
}
void UpdateButtons()
{
// 按钮现在由 UpdatePanel 在面板内部直接定位绘制
// 保留此函数兼容旧调用
}
void DeleteUIObjects()
{
ObjectsDeleteAll(0, g_uiPrefix);
ObjectDelete(0, g_panelBg);
ObjectDelete(0, g_btnCloseAll);
ObjectDelete(0, g_btnCloseA);
ObjectDelete(0, g_btnCloseB);
ObjectDelete(0, g_btnCloseLong);
ObjectDelete(0, g_btnCloseShort);
}
//+------------------------------------------------------------------+
//| 获取当前账户浮亏/浮盈(本EA的Magic) |
//+------------------------------------------------------------------+
double GetFloatingProfit()
{
double profit = 0;
int total = PositionsTotal();
for(int i = total - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(PositionSelectByTicket(ticket))
{
if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
PositionGetInteger(POSITION_MAGIC) == InpMagicNumber)
{
profit += PositionGetDouble(POSITION_PROFIT);
}
}
}
return profit;
}
//+------------------------------------------------------------------+
//| 获取持仓信息字符串 |
//+------------------------------------------------------------------+
string GetPositionInfo()
{
int total = PositionsTotal();
for(int i = total - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(PositionSelectByTicket(ticket))
{
if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
PositionGetInteger(POSITION_MAGIC) == InpMagicNumber)
{
string dir = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY ? "多" : "空";
return StringFormat("◆ 持仓: %s %.2f手 @%.2f",
dir,
PositionGetDouble(POSITION_VOLUME),
PositionGetDouble(POSITION_PRICE_OPEN));
}
}
}
return "◆ 持仓: 无";
}
//+------------------------------------------------------------------+
//| 获取总持仓手数 |
//+------------------------------------------------------------------+
double GetTotalLots()
{
double lots = 0;
int total = PositionsTotal();
for(int i = total - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0) continue;
if(PositionSelectByTicket(ticket))
{
if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
PositionGetInteger(POSITION_MAGIC) == InpMagicNumber)
{
lots += PositionGetDouble(POSITION_VOLUME);
}
}
}
return lots;
}
//+------------------------------------------------------------------+
//| 获取多单持仓信息(均价+手数) |
//+------------------------------------------------------------------+
void GetLongPositionInfo(double &avgPrice, double &lots)
{
double sumLots = 0, sumPrice = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong tk = PositionGetTicket(i);
if(tk == 0) continue;
if(!PositionSelectByTicket(tk)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
long mg = PositionGetInteger(POSITION_MAGIC);
if(mg != g_groupMagics[0] && mg != g_groupMagics[1]) continue;
if(PositionGetInteger(POSITION_TYPE) != POSITION_TYPE_BUY) continue;
double vol = PositionGetDouble(POSITION_VOLUME);
double price = PositionGetDouble(POSITION_PRICE_OPEN);
sumLots += vol;
sumPrice += vol * price;
}
lots = sumLots;
avgPrice = (sumLots > 0) ? sumPrice / sumLots : 0;
}
//+------------------------------------------------------------------+
//| 获取空单持仓信息(均价+手数) |
//+------------------------------------------------------------------+
void GetShortPositionInfo(double &avgPrice, double &lots)
{
double sumLots = 0, sumPrice = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong tk = PositionGetTicket(i);
if(tk == 0) continue;
if(!PositionSelectByTicket(tk)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
long mg = PositionGetInteger(POSITION_MAGIC);
if(mg != g_groupMagics[0] && mg != g_groupMagics[1]) continue;
if(PositionGetInteger(POSITION_TYPE) != POSITION_TYPE_SELL) continue;
double vol = PositionGetDouble(POSITION_VOLUME);
double price = PositionGetDouble(POSITION_PRICE_OPEN);
sumLots += vol;
sumPrice += vol * price;
}
lots = sumLots;
avgPrice = (sumLots > 0) ? sumPrice / sumLots : 0;
}
//+------------------------------------------------------------------+
//| 按方向平仓(平掉所有多单或空单) |
//+------------------------------------------------------------------+
void DoCloseByDirection(int direction, string reason)
{
ENUM_POSITION_TYPE targetType = (direction == 1) ? POSITION_TYPE_BUY : POSITION_TYPE_SELL;
int closedCount = 0;
double closedProfit = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong tk = PositionGetTicket(i);
if(tk == 0) continue;
if(!PositionSelectByTicket(tk)) continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
long mg = PositionGetInteger(POSITION_MAGIC);
if(mg != g_groupMagics[0] && mg != g_groupMagics[1]) continue;
if(PositionGetInteger(POSITION_TYPE) != targetType) continue;
closedProfit += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
trade.PositionClose(tk);
closedCount++;
}
if(closedCount > 0)
{
Print("【用户点击】", reason, " 已平掉", closedCount, "个", (direction==1?"多单":"空单"),
" 盈亏 ", DoubleToString(closedProfit, 2));
if(closedProfit > 0)
{
g_todayWins++;
g_todayProfit += closedProfit;
g_consecutiveLosses = 0;
}
else if(closedProfit < 0)
{
g_todayLosses++;
g_todayProfit += closedProfit;
g_consecutiveLosses++;
if(g_consecutiveLosses >= InpMaxLosses)
{
g_pauseUntilTime = TimeCurrent() + InpPauseMinutes * 60;
g_consecutiveLosses = 0;
Print("[Consecutive Loss] Paused for", InpPauseMinutes, "min");
}
}
g_stat_totalTrades++;
if(closedProfit > 0)
{
g_stat_wins++;
g_stat_totalProfit += closedProfit;
if(closedProfit > g_stat_maxProfit) g_stat_maxProfit = closedProfit;
}
else if(closedProfit < 0)
{
g_stat_losses++;
g_stat_totalLoss -= closedProfit;
if(closedProfit < g_stat_maxLoss) g_stat_maxLoss = closedProfit;
}
SaveStatistics();
SaveState();
}
}
//+------------------------------------------------------------------+
//| 面板绘制辅助: 分区标题(带左侧强调竖条) |
//+------------------------------------------------------------------+
void PanelSectionHeader(string labelName,string barName,int x,int y,int w,string text,color txtClr,color barClr)
{
CreatePanelStrip(barName, x - 8, y + 1, 3, 12, barClr); // 左侧强调条
CreatePanelLabel(labelName, x, y, text, txtClr, 10, "Microsoft YaHei");
}
//+------------------------------------------------------------------+
//| 清除面板上所有动态文本(避免条件行残留重叠) |
//+------------------------------------------------------------------+
void ClearPanelDynamicLabels()
{
// 删除所有以 HX_UI_ 开头且为 OBJ_LABEL 的对象, 背景/色条/按钮保留由各自函数重建
int total = ObjectsTotal(0, -1, OBJ_LABEL);
for(int i = total - 1; i >= 0; --i)
{
string nm = ObjectName(0, i, -1, OBJ_LABEL);
if(StringFind(nm, g_uiPrefix) == 0)
ObjectDelete(0, nm);
}
}
//+------------------------------------------------------------------+
//| 更新面板 (究极强化版 · 深空青蓝主题) |
//+------------------------------------------------------------------+
void UpdatePanel()
{
if(!InpUsePanel) return;
// 每次刷新前清掉旧的动态文本
ClearPanelDynamicLabels();
// ========== 数据准备 ==========
double floatPnL = SS_FloatingProfit();
double todayClosed = SS_TodayClosedProfit(TimeCurrent());
long account = AccountInfoInteger(ACCOUNT_LOGIN);
int todayTotal = g_todayWins + g_todayLosses;
double winRate = (todayTotal > 0) ? (100.0 * g_todayWins / todayTotal) : 0;
double longLots = 0, longAvg = 0;
double shortLots = 0, shortAvg = 0;
GetLongPositionInfo(longAvg, longLots);
GetShortPositionInfo(shortAvg, shortLots);
int daysLeft = 0;
if(g_isAuthorized && !g_permanentlyStopped)
{
datetime serverTime = TimeTradeServer();
datetime localTime = TimeLocal();
int offsetHours = (int)((serverTime - localTime) / 3600);
datetime expiryUTC = g_expiryDate - offsetHours * 3600;
daysLeft = (int)((expiryUTC - serverTime) / 86400);
if(daysLeft < 0) daysLeft = 0;
}
// ========== 颜色主题(匹配图片:黑底+红绿金白) ==========
color cTitle = clrWhite; // 标题白
color cAuth = C'240,208,120'; // 授权亮金
color cLabel = C'180,180,180'; // 标签灰
color cRed = C'231,76,60'; // 亏损红
color cGreen = C'46,204,113'; // 盈利绿
color cGold = C'255,215,0'; // 胜率金
color cWhite = clrWhite;
color cBorder = C'160,128,48'; // 暗金分隔线
color cBtnRed = C'60,20,20'; // 多平按钮底
color cBtnGreen = C'20,60,20'; // 空平按钮底
color cBtnGold = C'60,50,20'; // 全平按钮底
string uiFont = "Microsoft YaHei";
string monoFont = "Consolas";
// ========== 布局 ==========
int W = InpUIWidth;
int x0 = InpUIX;
int px = x0 + 12; // 标签列
int vx = x0 + 160; // 数值列
int lh = 18; // 行高
// 计算总高度
int H = 32 + 10; // 标题区 + 间距
H += lh * 6; // 基础6行
if(longLots > 0) H += lh;
if(shortLots > 0) H += lh;
H += 10; // 按钮前间距
if(InpUseButtons) H += 32 * 3 + 6;
H += 10; // 底部留白
// 创建背景
CreatePanelBG(H);
int py = InpUIY + 6;
// ═════ 标题 ═════
CreatePanelLabel(g_panelTitle, px, py, "恒鑫EA量化-究极强化版", cTitle, 11, uiFont);
py += 16;
CreatePanelLabel(g_plAuth, px, py, "授权剩余:" + IntegerToString(daysLeft) + "天", cAuth, 9, uiFont);
py += 22;
// 分隔线
CreatePanelLabel(g_plSep1, px, py, "────────────────────────", cBorder, 7, monoFont);
py += 14;
// ═════ 数据行(匹配图片文字和颜色) ═════
// 浮亏 (负值红色)
color floatClr = (floatPnL >= 0) ? cGreen : cRed;
string floatStr = (floatPnL >= 0) ?
"+$" + DoubleToString(floatPnL, 2) : "-$" + DoubleToString(MathAbs(floatPnL), 2);
CreatePanelLabel(g_plToday, px, py, "浮亏:", cLabel, 10, uiFont);
CreatePanelLabel(g_plFloatVal, vx, py, floatStr, floatClr, 10, monoFont);
py += lh;
// 当日已平仓净盈利 (正值绿色)
color todayClr = (todayClosed >= 0) ? cGreen : cRed;
string todayStr = (todayClosed >= 0) ?
"+$" + DoubleToString(todayClosed, 2) : "-$" + DoubleToString(MathAbs(todayClosed), 2);
CreatePanelLabel(g_plPnL, px, py, "当日已平仓净盈利:", cLabel, 10, uiFont);
CreatePanelLabel(g_plBalVal, vx, py, todayStr, todayClr, 10, monoFont);
py += lh;
// 胜率 (绿色/金色)
CreatePanelLabel(g_plStatus, px, py, "胜率:", cLabel, 10, uiFont);
CreatePanelLabel(g_plAuthVal, vx, py, DoubleToString(winRate, 1) + "%", cGold, 10, monoFont);
py += lh;
// 盈利次数 / 亏损次数
CreatePanelLabel(g_plPos, px, py, "盈利次数:", cLabel, 10, uiFont);
string wlStr = IntegerToString(g_stat_wins) + " 亏损次数:" + IntegerToString(g_stat_losses);
CreatePanelLabel(g_plPosVal, vx, py, wlStr, cWhite, 10, monoFont);
py += lh;
// 最大回撤 (红色)
CreatePanelLabel(g_plConsec, px, py, "最大回撤:", cLabel, 10, uiFont);
CreatePanelLabel(g_plPause, vx, py, "$" + DoubleToString(g_stat_maxDrawdown, 2), cRed, 10, monoFont);
py += lh;
// 多单持仓均价
if(longLots > 0)
{
CreatePanelLabel(g_plNetProt, px, py, "多单持仓均价:", cLabel, 10, uiFont);
CreatePanelLabel(g_plStopped, vx, py, DoubleToString(longAvg, _Digits), cWhite, 10, monoFont);
py += lh;
}
// 空单持仓均价
if(shortLots > 0)
{
CreatePanelLabel(g_plNoTrade, px, py, "空单持仓均价:", cLabel, 10, uiFont);
CreatePanelLabel(g_plRange1, vx, py, DoubleToString(shortAvg, _Digits), cWhite, 10, monoFont);
py += lh;
}
// 持仓总手数
double totalLots = longLots + shortLots;
if(totalLots > 0)
{
CreatePanelLabel(g_plRange2, px, py, "持仓总手数:", cLabel, 10, uiFont);
CreatePanelLabel(g_plDetail, vx, py, DoubleToString(totalLots, 2), cWhite, 10, monoFont);
py += lh;
}
py += 8;
// ═════ 按钮(匹配图片:多开平仓/空开平仓/全部平仓) ═════
if(InpUseButtons)
{
int btnW = W - 24;
int btnH = 30;
int btnX = x0 + 12;
// 多开平仓 - 红色系
CreateCyberButton(g_btnCloseLong, btnX, py, btnW, btnH, "多开平仓", cBtnRed, cRed);
py += btnH + 3;
// 空开平仓 - 绿色系
CreateCyberButton(g_btnCloseShort, btnX, py, btnW, btnH, "空开平仓", cBtnGreen, cGreen);
py += btnH + 3;
// 全部平仓 - 金色系
CreateCyberButton(g_btnCloseAll, btnX, py, btnW, btnH, "全部平仓", cBtnGold, cGold);
}
ChartRedraw(0);
}
//+------------------------------------------------------------------+
//| 主循环 |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| 顶底分型识别(单均线4.0版) |
//| 顶分型: K1.High在K2区间内, K3.High在K2区间内, K3.Low < K2.Low |
//| 底分型: K1.Low在K2区间内, K3.Low在K2区间内, K3.High > K2.High |
//| 触发条件: 需趋势确认(顶分型平多需bias!=-1, 底分型平空需bias!=-1) |
//+------------------------------------------------------------------+
bool IsTopFractal(double &k1H, double &k1L, double &k2H, double &k2L, double &k3H, double &k3L, double &k3C)
{
// 新定义: K1.High在K2区间内, K3.High在K2区间内, K3.Low < K2.Low
if(k1H < k2L || k1H > k2H) return false;
if(k3H < k2L || k3H > k2H) return false;
if(k3L >= k2L) return false;
return true;
}
bool IsBottomFractal(double &k1H, double &k1L, double &k2H, double &k2L, double &k3H, double &k3L, double &k3C)
{
// 新定义: K1.Low在K2区间内, K3.Low在K2区间内, K3.High > K2.High
if(k1L < k2L || k1L > k2H) return false;
if(k3L < k2L || k3L > k2H) return false;
if(k3H <= k2H) return false;
return true;
}
void OnTick()
{
// === 全局保护检查(任何状态都要检查)===
if(IsPermanentlyStopped()) return;
// ===== 禁止时段检测(北京时间)=====
bool inNoTrade = CheckNoTradeWindow();
// 进入禁止时段:立即平仓所有组
if(inNoTrade && !g_inNoTradeWindow)
{
g_inNoTradeWindow = true;
datetime nowBeijing = GetBeijingTime();
MqlDateTime mdt;
TimeToStruct(nowBeijing, mdt);
string timeStr = StringFormat("%02d:%02d", mdt.hour, mdt.min);
Print("【禁止时段开始】北京时间:", timeStr, " 禁止开仓,已平仓所有组");
Alert("【禁止时段开始】北京时间:", timeStr, " 禁止时段:", InpNoTradeWindow, " 已平仓所有持仓!");
for(int ntg = 0; ntg < GROUP_COUNT; ntg++)
{
LoadGroupRuntime(ntg);
if(HasGroupPosition(ntg) || g_groupClosing[ntg])
{
DoClose("禁止时段强制平仓");
SaveGroupRuntime(ntg);
}
}
}
// 退出禁止时段:解除封锁
else if(!inNoTrade && g_inNoTradeWindow)
{
g_inNoTradeWindow = false;
Print("【禁止时段结束】恢复正常交易");
}
if(IsPaused()) return;
// 授权到期检测(服务器时间,无法绕过)
StopIfExpired();
// 实时更新结余保护(最大结余追踪)
CheckNetProtection();
// 更新最大回撤
double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
if(currentEquity > g_equityPeak) g_equityPeak = currentEquity;
double currentDD = g_equityPeak - currentEquity;
if(currentDD > g_maxDrawdown) g_maxDrawdown = currentDD;
if(currentDD > g_stat_maxDrawdown) g_stat_maxDrawdown = currentDD;
// ===== 交易时段控制: 刷新状态,必要时强平,并决定是否禁开 =====
g_ss_blockEntries = SS_ProcessAndGate();
// 自动交易恢复后,重置警告标志,下一个Tick自动继续运行
g_autoTradingWarned = false;
datetime barTime = GetCurrentBarTime();
if(barTime == 0) return;
if(g_prevBarTime == 0)
{
g_prevBarTime = barTime;
UpdatePanel();
return;
}
bool newBar = (barTime != g_prevBarTime);
if(newBar)
{
double open, high, low, close, atrVal, emaVal, cciVal, rsiVal, rsiDirectionVal;
datetime time;
if(GetLastClosedCandle(open, high, low, close, atrVal, emaVal, cciVal, rsiVal, rsiDirectionVal, time))
{
MarkCandle(open, high, low, close, atrVal, emaVal, cciVal, rsiVal, rsiDirectionVal, time);
// ===== 分型检测(按持仓状态) =====
MqlRates rates3[];
ArraySetAsSeries(rates3, true);
if(CopyRates(_Symbol, InpPeriod, 1, 3, rates3) >= 3)
{
double k1H = rates3[2].high, k1L = rates3[2].low;
double k2H = rates3[1].high, k2L = rates3[1].low;
double k3H = rates3[0].high, k3L = rates3[0].low, k3C = rates3[0].close;
for(int fg = 0; fg < GROUP_COUNT; fg++)
{
LoadGroupRuntime(fg);
if(HasGroupPosition(fg) && !g_fractalPending[fg]) // 持仓且未触发分型
{
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
if(posType == POSITION_TYPE_BUY)
{
// 顶分型触发标记
if(IsTopFractal(k1H, k1L, k2H, k2L, k3H, k3L, k3C))
{
g_fractalPending[fg] = true;
Print("【顶分型触发】", CurrentGroupTag(), "组 K2高:", DoubleToString(k2H,_Digits), " 标记待观察");
//Alert("【顶分型触发】", CurrentGroupTag(), "组多单待观察!");
}
}
else if(posType == POSITION_TYPE_SELL)
{
// 底分型触发标记
if(IsBottomFractal(k1H, k1L, k2H, k2L, k3H, k3L, k3C))
{
g_fractalPending[fg] = true;
Print("【底分型触发】", CurrentGroupTag(), "组 K2低:", DoubleToString(k2L,_Digits), " 标记待观察");
//Alert("【底分型触发】", CurrentGroupTag(), "组空单待观察!");
}
}
}
SaveGroupRuntime(fg);
}
}
}
g_prevBarTime = barTime;
// 脉冲模式已移除
SaveState();
}
// 脉冲模式已移除
// 逐组处理:先异步平仓,再持仓监控
for(int g = 0; g < GROUP_COUNT; g++)
{
LoadGroupRuntime(g);
if(g_groupClosing[g])
{
CheckCloseCompleted();
}
else if(HasMyPosition())
{
MonitorTradeMartingale();
}
SaveGroupRuntime(g);
}
// 仅在信号状态下给空闲组开仓:同一信号最多开1组 (时段控制禁开时跳过)
if(g_state == STATE_WAIT_SIGNAL && !g_ss_blockEntries)
{
int freeGroup = FindFreeGroup();
if(freeGroup >= 0 && PassABOpenGapForGroup(freeGroup))
{
LoadGroupRuntime(freeGroup);
CheckBreakout();
SaveGroupRuntime(freeGroup);
}
}
SetGroupContext(0);
UpdatePanel();
}
//+------------------------------------------------------------------+
|
|