动量弹球交易策略·进阶篇
🎯

动量弹球交易策略·进阶篇

(2/3)· 从信号模块到人工交易指标,MQL5 面向对象简化法如何落地弹球规则

偏理论 第 2/3 篇
很多开发者照着书里的动量弹球规则写 EA,却在 D1 与 H1 周期切换时把方向判断逻辑写散了。用结构代替类封装信号,既能留给过程化习惯的人平滑上手空间,也不丢策略本身的边界判定力。

◍ 把动量弹球信号做成可改参数的模块

原书里 LBR/RSI 用的是死数值:RSI 周期 3、超买 70、超卖 30。我们把它全部 input 化,方便在 MT5 里直接拖参数做实验,不用改代码重编译。 平仓逻辑给了三种让用户选:跟踪止损、次日第一个日柱收盘平、突破建仓日极值平。‘早晨’原文没定义,我们绑成 24 小时周期里每天第一个日柱,和多数策略规则一致,避免歧义。 fe_Get_Entry_Signal 的参数连接和返回值类型要和之前 Raschke/Connors 策略统一。EA 调用时用静态变量跨 tick 存水平,指标调用则把 t_Time 传负值来清零静态量,这是区分两种调用源的关键。 每隔 24 小时在已关闭 D1 柱上读 LBR/RSI,若落在 30–70 中性区就禁交易。H1 首柱偏移默认 10 点,同时用于挂单和止损距离。下面这段声明是模块骨架,复制进 MQ5 就能改。

MQL5 / C++
enum ENUM_EXIT_MODE {      class=class="str">"cmt">// 退场方法列表
   CLOSE_ON_SL_TRAIL,         class=class="str">"cmt">// 只通过跟踪止损
   CLOSE_ON_NEW_1ST_CLOSE, class=class="str">"cmt">// 通过在后面一天的第一个柱平仓
   CLOSE_ON_DAY_BREAK       class=class="str">"cmt">// 通过突破建仓日价格极值平仓
};
class=class="str">"cmt">// user settings
class="kw">input ENUM_APPLIED_PRICE  TS_MomPin_Applied_Price = PRICE_CLOSE;      class=class="str">"cmt">// 动量弹球: 用于计算 ROC 的价格
class="kw">input class="type">uint    TS_MomPin_RSI_Period = class="num">3;                            class=class="str">"cmt">// 动量弹球: RSI 周期数
class="kw">input class="type">class="kw">double  TS_MomPin_RSI_Overbought = class="num">70;                       class=class="str">"cmt">// 动量弹球: RSI 超卖水平
class="kw">input class="type">class="kw">double  TS_MomPin_RSI_Oversold = class="num">30;                         class=class="str">"cmt">// 动量弹球: RSI 超买水平
class="kw">input class="type">uint    TS_MomPin_Entry_Offset = class="num">10;                         class=class="str">"cmt">// 动量弹球: 入场水平距离 H1 边界的偏移(点数)
class="kw">input class="type">uint    TS_MomPin_Exit_Offset = class="num">10;                          class=class="str">"cmt">// 动量弹球: 出场水平距离 H1 边界的偏移(点数)
class="kw">input ENUM_EXIT_MODE   TS_MomPin_Exit_Mode = CLOSE_ON_SL_TRAIL;    class=class="str">"cmt">// 动量弹球: 获利仓位的平仓方法
ENUM_ENTRY_SIGNAL fe_Get_Entry_Signal(       class=class="str">"cmt">// 两个柱形模式分析 (D1 + H1)
  class="type">class="kw">datetime   t_Time,                         class=class="str">"cmt">// 当前时间
  class="type">class="kw">double&    d_Entry_Level,                  class=class="str">"cmt">// 进场水平 (与变量的连接)
  class="type">class="kw">double&    d_SL,                           class=class="str">"cmt">// 止损水平 (与变量的连接)
  class="type">class="kw">double&    d_TP,                           class=class="str">"cmt">// 获利水平 (与变量的连接)
  class="type">class="kw">double&    d_Range_High,                   class=class="str">"cmt">// 范围内第一个小时柱的最高价 (与变量的连接)
  class="type">class="kw">double&    d_Range_Low                     class=class="str">"cmt">// 范围内第一个小时柱的最低价 (与变量的连接)
) {
  class=class="str">"cmt">// function body
}
class="kw">static ENUM_ENTRY_SIGNAL se_Trade_Direction = ENTRY_UNKNOWN;  class=class="str">"cmt">// 今天的交易方向
class="kw">static class="type">class="kw">double
  class=class="str">"cmt">// 用于在分时之间保存计算的水平的变量
  sd_Entry_Level = class="num">0,
  sd_SL = class="num">0, sd_TP = class="num">0,
  sd_Range_High = class="num">0, sd_Range_Low = class="num">0
;
if(t_Time < class="num">0) {                                           class=class="str">"cmt">// 只用于在指标中调用

日线 LBR_RSI 句柄与昨日柱的惰性初始化

这段逻辑把进场位、止损、止盈和区间高低全部先归零,方向标记为 ENTRY_UNKNOWN,等于每次新分析前把旧状态清空。随后用 static 句柄缓存 LBR_RSI 指标,只在第一次进函数时通过 iCustom 向日线周期(PERIOD_D1)要句柄,避免重复申请。 指标参数直接吃全局变量:TS_MomPin_Applied_Price、TS_MomPin_RSI_Period、TS_MomPin_RSI_Overbought、TS_MomPin_RSI_Oversold。若句柄仍是 INVALID_HANDLE,按 Log_Level 决定是否打印错误码并返回 ENTRY_INTERNAL_ERROR,这一步能帮你快速定位自定义指标路径写错或周期不支持。 真正取数靠 CopyTime 拉前两根日柱时间,要求返回值不小于 2;不足就报错退出。static 变量 st_Prev_Day 记录已分析过的日期,只有 ta_Bar_Time[0] 比它新才重算,所以同一根日线内反复调用不会重复跑 CopyBuffer。 CopyBuffer 取的是缓冲区编号 4、长度 1 的 LBR_RSI 值,若大于 100 或小于 0 视为脏数据返回 ENTRY_UNKNOWN。外汇与贵金属波动大,这类自定义动量指标在极端跳空时可能返回异常值,实盘前务必在 MT5 用历史日线手动验证返回值边界。

MQL5 / C++
sd_Entry_Level = sd_SL = sd_TP = sd_Range_High = sd_Range_Low = class="num">0;
se_Trade_Direction = ENTRY_UNKNOWN;
}
class=class="str">"cmt">// 默认使用之前保存的水平来做进场/出场:
  d_Entry_Level = sd_Entry_Level; d_SL = sd_SL; d_TP = sd_TP; d_Range_High = sd_Range_High; d_Range_Low = sd_Range_Low;
class="kw">static class="type">int si_Indicator_Handle = INVALID_HANDLE;
if(si_Indicator_Handle == INVALID_HANDLE) {
  class=class="str">"cmt">// 在第一次调用函数时取得指标句柄:
  si_Indicator_Handle = iCustom(_Symbol, PERIOD_D1, "LBR_RSI",
    TS_MomPin_Applied_Price,
    TS_MomPin_RSI_Period,
    TS_MomPin_RSI_Overbought,
    TS_MomPin_RSI_Oversold
  );
  
  if(si_Indicator_Handle == INVALID_HANDLE) { class=class="str">"cmt">// 没有得到指标句柄
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: 收取 LBR_RSI 指标句柄出错代码 #%u", __FUNCTION__, _LastError);
    class="kw">return(ENTRY_INTERNAL_ERROR);
  }
}
class="kw">static class="type">int si_Indicator_Handle = INVALID_HANDLE;
if(si_Indicator_Handle == INVALID_HANDLE) {
  class=class="str">"cmt">// receiving indicator handle at first function call:
  si_Indicator_Handle = iCustom(_Symbol, PERIOD_D1, "LBR_RSI",
    TS_MomPin_Applied_Price,
    TS_MomPin_RSI_Period,
    TS_MomPin_RSI_Overbought,
    TS_MomPin_RSI_Oversold
  );
  
  if(si_Indicator_Handle == INVALID_HANDLE) {     class=class="str">"cmt">// 没有取得指标句柄
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: 指标句柄接收错误 LBR_RSI #%u", __FUNCTION__, _LastError);
    class="kw">return(ENTRY_INTERNAL_ERROR);
  }
}
class=class="str">"cmt">// 查找前一天日柱的时间:
class="type">class="kw">datetime ta_Bar_Time[];
if(CopyTime(_Symbol, PERIOD_D1, fabs(t_Time), class="num">2, ta_Bar_Time) < class="num">2) {
  if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyTime: 错误 #%u", __FUNCTION__, _LastError);
  class="kw">return(ENTRY_INTERNAL_ERROR);
}
class=class="str">"cmt">// 如果这是今天第一次调用,对前一天的分析:
class="kw">static class="type">class="kw">datetime st_Prev_Day = class="num">0;
if(t_Time < class="num">0) st_Prev_Day = class="num">0;                    class=class="str">"cmt">// only for call from indicator
if(st_Prev_Day < ta_Bar_Time[class="num">0]) {
  class=class="str">"cmt">// 之前一天参数清零:
  se_Trade_Direction = ENTRY_UNKNOWN;
  d_Entry_Level = sd_Entry_Level = d_SL = sd_SL = d_TP = sd_TP = d_Range_High = sd_Range_High = d_Range_Low = sd_Range_Low = class="num">0;
  
  class=class="str">"cmt">// 取得前一天的 LBR/RSI 指标值:
  class="type">class="kw">double da_Indicator_Value[];
  if(class="num">1 > CopyBuffer(si_Indicator_Handle, class="num">4, ta_Bar_Time[class="num">0], class="num">1, da_Indicator_Value)) {
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyBuffer: 错误 #%u", __FUNCTION__, _LastError);
    class="kw">return(ENTRY_INTERNAL_ERROR);
  }
  
  class=class="str">"cmt">// 如果 LBR/RSI 指标值有错误:
  if(da_Indicator_Value[class="num">0] > class="num">100. || da_Indicator_Value[class="num">0] < class="num">0.) {
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: 指标缓冲区值出错 (%f)", __FUNCTION__, da_Indicator_Value[class="num">0]);
    class="kw">return(ENTRY_UNKNOWN);
  }
  
  st_Prev_Day = ta_Bar_Time[class="num">0];                  class=class="str">"cmt">// 重试次数计数

「用H1首根柱锁定当日进场与止损」

这段逻辑把「方向判定」和「价位计算」拆成了两步:先由 RSI 类指标值给出 ENTRY_BUY / ENTRY_SELL / ENTRY_NONE,再在 H1 周期上取最近 24 根小时柱,回找「今天第一根 H1 柱」的高、低点作为区间边界。若 H1 首根尚未走完(已成型柱数不足 3 根),函数直接返回 ENTRY_UNKNOWN,避免用不完整数据下单。 进场价与初始止损都靠偏移量算出来:买入时 sd_Entry_Level = 首根最高价 + _Point*TS_MomPin_Entry_Offset,卖出时取首根最低价减去该偏移;止损则对称地挂在区间另一侧。外汇与贵金属杠杆高,这种基于固定点差偏移的挂单在跳空时可能滑到更远价位,实盘前应在 MT5 用历史日跑一遍验证。 平仓侦测函数 fe_Get_Exit_Signal 用静态数组缓存前一天 D1 数据,并通过 t_Current_Time 正负号区分是指标调用还是 EA 调用——负数入参会被取反并重置计数器。复制下面这段核心片段,改 TS_MomPin_Entry_Offset 参数就能直接看不同偏移对触发频率的影响。

MQL5 / C++
class=class="str">"cmt">// 记录今天的交易方向:
if(da_Indicator_Value[class="num">0] > TS_MomPin_RSI_Overbought) se_Trade_Direction = ENTRY_SELL;
else se_Trade_Direction = da_Indicator_Value[class="num">0] > TS_MomPin_RSI_Oversold ?ENTRY_NONE : ENTRY_BUY;

class=class="str">"cmt">// 记到记录中:
if(Log_Level == LOG_LEVEL_DEBUG) PrintFormat("%s: 交易方向 %s: %s. LBR/RSI: (%.2f)",
  __FUNCTION__,
  TimeToString(ta_Bar_Time[class="num">1], TIME_DATE),
  StringSubstr(EnumToString(se_Trade_Direction), class="num">6),
  da_Indicator_Value[class="num">0]
);
class=class="str">"cmt">// 今天没有搜索信号
if(se_Trade_Direction == ENTRY_NONE) class="kw">return(ENTRY_NONE);
class=class="str">"cmt">// 分析今天第一个H1上的小时柱,除非已经做过了:
if(sd_Entry_Level == class="num">0.) {
  class=class="str">"cmt">// to receive data of last class="num">24 bars H1:
  class="type">MqlRates oa_H1_Rates[];
  class="type">int i_Price_Bars = CopyRates(_Symbol, PERIOD_H1, fabs(t_Time), class="num">24, oa_H1_Rates);
  if(i_Price_Bars == WRONG_VALUE) {                    class=class="str">"cmt">// 处理 CopyRates 函数错误
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyRates: 错误 #%u", __FUNCTION__, _LastError);
    class="kw">return(ENTRY_INTERNAL_ERROR);
  }
  class=class="str">"cmt">// 在24个柱中找到今天的第一个柱,并且记录最高价和最低价:
  class="type">int i_Bar = i_Price_Bars;
  class="kw">while(i_Bar-- > class="num">0) {
    if(oa_H1_Rates[i_Bar].time < ta_Bar_Time[class="num">1]) class="kw">break;      class=class="str">"cmt">// 前一天在 H1 上的最后一个柱
    class=class="str">"cmt">// H1第一个柱的范围边界:
    sd_Range_High = d_Range_High = oa_H1_Rates[i_Bar].high;
    sd_Range_Low = d_Range_Low = oa_H1_Rates[i_Bar].low;
  }
  class=class="str">"cmt">// H1 第一个柱还没有关闭:
  if(i_Price_Bars - i_Bar < class="num">3) class="kw">return(ENTRY_UNKNOWN);
  class=class="str">"cmt">// 计算交易水平:
  class=class="str">"cmt">// 进入市场的水平:  
  d_Entry_Level = _Point * TS_MomPin_Entry_Offset;        class=class="str">"cmt">// 辅助计算
  sd_Entry_Level = d_Entry_Level = se_Trade_Direction == ENTRY_SELL ?d_Range_Low - d_Entry_Level : d_Range_High + d_Entry_Level;
  class=class="str">"cmt">// 初始止损水平:  
  d_SL = _Point * TS_MomPin_Exit_Offset;                  class=class="str">"cmt">// 辅助计算
  sd_SL = d_SL = se_Trade_Direction == ENTRY_BUY ?d_Range_Low - d_SL : d_Range_High + d_SL;
}

◍ 日内极值突破的离场判定

这段逻辑处理的是建仓后如何按日线级别规则平仓,核心在 CLOSE_ON_NEW_1ST_CLOSE 与 CLOSE_ON_DAY_BREAK 两种模式。前者要求在建仓日的后一天第一根柱收盘时检查信号,若 fe_Get_Entry_Signal 返回值小于 ENTRY_UNKNOWN 就全平,否则继续持有。 CLOSE_ON_DAY_BREAK 更依赖价格行为:先用 t_Current_Time % 86400 > 36000 过滤掉日内前 10 小时(36000 秒)的波动,再用 CopyRates 拉取两根 D1 柱。若持仓为多头且前一日高点低于当前日高点,视为无突破、不平仓;反向则倾向触发 EXIT_ALL。 注意 86400 是一天的秒数,36000 约等于 10 小时,这两个硬参数直接决定离场灵敏度。外汇与贵金属波动剧烈,这类突破平仓逻辑可能在大跳空时滑点放大,实盘前务必在 MT5 策略测试器用历史数据跑一遍。

MQL5 / C++
  d_SL, d_TP,
  d_Range_High,  d_Range_Low
  ;

  if(e_Trade_Direction < class="num">1) {         class=class="str">"cmt">// 没有仓位,一切清零
    si_Price_Bars = class="num">0;
  }

  class="kw">switch(e_Exit_Mode) {
    case CLOSE_ON_SL_TRAIL:            class=class="str">"cmt">// 只有跟踪止损
          class="kw">return(EXIT_NONE);
          
    case CLOSE_ON_NEW_1ST_CLOSE:     class=class="str">"cmt">// 在后面一天第一个柱时平仓
          if((t_Current_Time - t_Current_Time % class="num">86400)
            ==
            (t_Entry_Time - t_Current_Time % class="num">86400)
          ) class="kw">return(EXIT_NONE);       class=class="str">"cmt">// 建仓日尚未结束
          
          if(fe_Get_Entry_Signal(t_Current_Time, d_Curr_Entry_Level, d_SL, d_TP, d_Range_High, d_Range_Low)
            < ENTRY_UNKNOWN
          ) {
            if(Log_Level > LOG_LEVEL_ERR) PrintFormat("%s: 后一天的第一个柱关闭了", __FUNCTION__);
            class="kw">return(EXIT_ALL);
          }
          class="kw">return(EXIT_NONE);         class=class="str">"cmt">// 不做平仓
          
    case CLOSE_ON_DAY_BREAK:         class=class="str">"cmt">// 根据突破建仓日价格极值平仓
          if((t_Current_Time - t_Current_Time % class="num">86400)
            ==
            (t_Entry_Time - t_Current_Time % class="num">86400)
          ) class="kw">return(EXIT_NONE);       class=class="str">"cmt">// 建仓日尚未结束
          
          if(t_Current_Time % class="num">86400 > class="num">36000) class="kw">return(EXIT_ALL); class=class="str">"cmt">// 时间超出
          
          if(si_Price_Bars < class="num">1) {
            si_Price_Bars = CopyRates(_Symbol, PERIOD_D1, t_Current_Time, class="num">2, soa_Prev_D1_Rate);
            if(si_Price_Bars == WRONG_VALUE) { class=class="str">"cmt">// 处理 CopyRates 函数错误
              if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyRates: 错误 #%u", __FUNCTION__, _LastError);
              class="kw">return(EXIT_UNKNOWN);
            }
            
            if(e_Trade_Direction == ENTRY_BUY) {
              if(soa_Prev_D1_Rate[class="num">1].high < soa_Prev_D1_Rate[class="num">0].high) class="kw">return(EXIT_NONE);       class=class="str">"cmt">// 没有突破
              

用前一日高低点卡死出场信号

这段逻辑把「昨天 D1 高低点是否被今日 D1 高低点突破」当成平仓触发器,而不是用指标金叉死叉。昨日 high 被今日 high 上破,返回 EXIT_BUY 平掉多仓;昨日 low 未被今日 low 下破时直接 EXIT_NONE 不动,只有真下破才返回 EXIT_SELL 平空。 枚举 ENUM_EXIT_SIGNAL 给了五个状态:UNKNOWN 是初始未标记,BUY/SELL 分别对应平多平空,ALL 是一刀切全平,NONE 是本次循环不操作。实盘里外汇和贵金属波动受消息面干扰大,突破可能是假突破,这种纯高低点出场在高波动时段可能频繁触发,建议拿 MT5 策略测试器跑 2023 年 XAUUSD 的 D1 数据看回撤。 逐行看代码:Log_Level 控制只打印高于错误级的日志,PrintFormat 把函数名和前后高低点都转成带 _Digits 精度的字符串;else 分支里先判断昨日 low 是否大于今日 low,若是说明没向下突破直接放行。最后函数兜底返回 EXIT_UNKNOWN,防止漏状态。

MQL5 / C++
if(Log_Level > LOG_LEVEL_ERR) PrintFormat("%s: 价格突破了昨天的最高价: %s > %s", __FUNCTION__, DoubleToString(soa_Prev_D1_Rate[class="num">1].high, _Digits), DoubleToString(soa_Prev_D1_Rate[class="num">0].high, _Digits));
class="kw">return(EXIT_BUY);
} else {
if(soa_Prev_D1_Rate[class="num">1].low > soa_Prev_D1_Rate[class="num">0].low) class="kw">return(EXIT_NONE); class=class="str">"cmt">// 没有突破

if(Log_Level > LOG_LEVEL_ERR) PrintFormat("%s: 价格突破了昨天的最低价: %s < %s", __FUNCTION__, DoubleToString(soa_Prev_D1_Rate[class="num">1].low, _Digits), DoubleToString(soa_Prev_D1_Rate[class="num">0].low, _Digits));
class="kw">return(EXIT_SELL);
}

class="kw">return(EXIT_NONE); class=class="str">"cmt">// for each
}

class="kw">return(EXIT_UNKNOWN);
}
enum ENUM_EXIT_SIGNAL { class=class="str">"cmt">// 出场信号的列表
EXIT_UNKNOWN, class=class="str">"cmt">// 没有标记
EXIT_BUY, class=class="str">"cmt">// 关闭买入仓位
EXIT_SELL, class=class="str">"cmt">// 关闭卖出仓位
EXIT_ALL, class=class="str">"cmt">// 全部平仓
EXIT_NONE class=class="str">"cmt">// 不做平仓
};

「把信号模块塞进图表做人工交易指标」

把动量弹球信号模块直接做成指标,比写 EA 更轻:它不自动下单,只在图表上画出挂单位、止损位和固定获利位,并给手机推通知。指标额外把每天第一根小时柱用 DRAW_FILLING 填充,肉眼就能看出为什么这些水平这样摆,弥补策略测试器报告里看不到的视觉信息。 通知走三条通道:终端弹窗带声音、邮件、以及 MT5 推送。实操时建议只开推送,避免欧美盘重叠时弹窗刷屏。外汇和贵金属波动剧烈,这类水平仅作参考,触发概率不等于必胜。 缓冲区要开 6 个、绘图 4 类:1 个填充缓冲画首小时柱,3 个线缓冲画入场/获利/止损,其中入场线用 DRAW_COLOR_LINE 随方向变蓝或粉,另两条单色即可。初始化里把点数转成品种价格,能省掉主循环成千上万次重复换算。 主循环里先用 go_Brownie 结构判新柱,只在昨天和今天柱上算;切换周期要清残留缓冲,否则虚柱会被旧数据填实。模块给信号后先算获利:t_Curr_D1_Bar = Time[i_Current_TF_Bar] - Time[i_Current_TF_Bar] % 86400 拿到当天起点。若价格没触挂单就跳过后序,触单后才画止损获利直到一条被交叉。 挂单未明时 if (i_Period_Bar >= rates_total - 1) break 直接截断,避免在当前未结束日空转。完整源码在 TS_Momentum_Pinball.mq5,f_Do_Alert 推送函数与前篇一致未改,接上就能用。

MQL5 / C++
class="macro">#class="kw">property indicator_chart_window
class="macro">#class="kw">property indicator_buffers  class="num">6
class="macro">#class="kw">property indicator_plots    class="num">4
class="macro">#class="kw">property indicator_label1  "一天中的第一个小时"
class="macro">#class="kw">property indicator_type1   DRAW_FILLING
class="macro">#class="kw">property indicator_color1  C&class="macro">#x27;class="num">255,class="num">208,class="num">234&class="macro">#x27;, C&class="macro">#x27;class="num">179,class="num">217,class="num">255&class="macro">#x27;
class="macro">#class="kw">property indicator_width1  class="num">1
class="macro">#class="kw">property indicator_label2  "进场水平"
class="macro">#class="kw">property indicator_type2   DRAW_COLOR_LINE
class="macro">#class="kw">property indicator_style2  STYLE_DASHDOT
class="macro">#class="kw">property indicator_color2  clrDodgerBlue, clrDeepPink
class="macro">#class="kw">property indicator_width2  class="num">2
class="macro">#class="kw">property indicator_label3  "止损"
class="macro">#class="kw">property indicator_type3   DRAW_LINE
class="macro">#class="kw">property indicator_style3  STYLE_DASHDOTDOT
class="macro">#class="kw">property indicator_color3  clrCrimson
class="macro">#class="kw">property indicator_width3  class="num">1
class="macro">#class="kw">property indicator_label4  "获利"
class="macro">#class="kw">property indicator_type4   DRAW_LINE
class="macro">#class="kw">property indicator_color4  clrGreen
class="macro">#class="kw">property indicator_width4  class="num">1
enum ENUM_LOG_LEVEL {  class=class="str">"cmt">// 记录的级别
  LOG_LEVEL_NONE,      class=class="str">"cmt">// 禁止记录
  LOG_LEVEL_ERR,       class=class="str">"cmt">// 只记录错误
  LOG_LEVEL_INFO,      class=class="str">"cmt">// 记录错误和EA注释
  LOG_LEVEL_DEBUG      class=class="str">"cmt">// 全部记录,没有例外
};
enum ENUM_ENTRY_SIGNAL {  class=class="str">"cmt">// 入场信号列表
  ENTRY_BUY,           class=class="str">"cmt">// 买入信号
  ENTRY_SELL,          class=class="str">"cmt">// 卖出信号
  ENTRY_NONE,          class=class="str">"cmt">// 没有信号
  ENTRY_UNKNOWN,       class=class="str">"cmt">// 状态不确定
  ENTRY_INTERNAL_ERROR class=class="str">"cmt">// 内部函数错误
};
enum ENUM_EXIT_SIGNAL {  class=class="str">"cmt">// 退出信号列表
  EXIT_UNKNOWN,        class=class="str">"cmt">// 没有识别
把信号诊断交给小布盯盘
这些 LBR/RSI 超买超卖边界与挂单触发位的诊断,小布盯盘的 AIGC 已内置,打开对应品种页即可看到,你只管核对结构里的数值映射有没有偏。

常见问题

作者在 ROC 上叠 RSI 周期特征并加信号边界,是为了消掉泰勒策略里人工改方向造成的规则混乱,D1 超卖低于 30 才算买入日候选。
本篇刻意去掉类,结构和过程化差异最小,足以覆盖弹球策略的字段与函数需求,但继承与封装粒度自然比完整 OOP 弱。
规则是在高于该 H1 柱最高点处挂买,止损设到该柱最低价;若扫损则在同价位重挂,属日内边界框定做法。
可以,品种页的 AIGC 层会按 D1 的 LBR/RSI 与 H1 极值推算挂单与止损水平,省去手动划线的重复劳动。
点差与跳空频率不同,外汇贵金属属高风险品类,历史边界收益倾向失效,需以近期样本重验触发概率。