S&P 500交易策略在MQL5中的实现(适合初学者)·进阶篇
📊

S&P 500交易策略在MQL5中的实现(适合初学者)·进阶篇

(2/3)·当AI信号飘忽时,用CCI/RSI/WPR/MA四条硬规则给模型兜底

偏理论进阶 第 2/3 篇
很多初学者把AI预测直接当入场令,回测漂亮实盘翻车。标普500成分股权重漂移,单看模型容易踩空。先立住经典技术过滤,再让AI补位,才是这套混合思路的底线。

◍ 用多指标矩阵搭线性回归的初始化骨架

在 MT5 用 MQL5 做跨品种线性回归预测,第一步是把输入特征和目标品种的历史数据装进 matrix / vector 容器。下面这段全局声明先把 US_500 作为目标品种,并预留 CCI、RSI、WPR、MA 四个指标句柄与缓冲区,训练标志默认全 false。 初始化函数 OnInit 里只做两件事:用 iRSI、iMA(EMA 模式)、iWPR、iCCI 绑定 target_symbol 的 PERIOD_CURRENT 数据,再用 SymbolInfoDouble 取当前图表品种的最小成交量。注意指标周期 rsi_period、ma_period 等是外部参数,没填默认会编译报错。 model_initialize 才是拟合入口:先把 model_being_trained 置 true,再按注释约定——输入数据起始位 inupt_start = 1 + look_ahead,输出起始位 output_start = 1,保证输入比输出旧、不含最新 bar。随后依次调 fetch_input_data、fetch_output_data、prepare_data(0)、fit_linear_model,最后 model_ready 翻转标志。 别把正态当圣经:这段只搭了数据流向,prepare_data 里的归一化若没处理量纲,US_500 和外汇品种混训时梯度可能爆掉,建议开 MT5 把 desired_symbols 改成同资产类别先跑通。

MQL5 / C++
matrix input_matrix = matrix::Ones(fetch,(ArraySize(desired_symbols))-class="num">1);
matrix output_matrix = matrix::Ones(fetch,class="num">1);
vector initiall_input_values = vector::Ones((ArraySize(desired_symbols)));
class="type">class="kw">double initiall_output_value = class="num">0.0;
matrix b;
class="type">class="kw">string target_symbol = "US_500";
vector symbol_history = vector::Zeros(fetch);
class="type">bool model_initialized = class="kw">false;
class="type">bool model_being_trained = class="kw">false;
class="type">class="kw">double model_forecast = class="num">0;
class="type">int cci_handler,rsi_handler,wpr_handler,ma_handler;
class="type">class="kw">double cci_buffer[],rsi_buffer[],wpr_buffer[],ma_buffer[];

class="type">int OnInit()
  {
  rsi_handler = iRSI(target_symbol,PERIOD_CURRENT,rsi_period,PRICE_CLOSE);
  ma_handler = iMA(target_symbol,PERIOD_CURRENT,ma_period,class="num">0,MODE_EMA,PRICE_CLOSE);
  wpr_handler = iWPR(target_symbol,PERIOD_CURRENT,wpr_period);
  cci_handler = iCCI(target_symbol,PERIOD_CURRENT,cci_period,PRICE_CLOSE);
  minimum_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
  class="kw">return(INIT_SUCCEEDED);
  }

class="type">void model_initialize(class="type">void)
  {
  model_being_trained = true;
  inupt_start = class="num">1+look_ahead;
  output_start = class="num">1;
  fetch_input_data(inupt_start,fetch);
  fetch_output_data(output_start,fetch);
  prepare_data(class="num">0);
  fit_linear_model();
  model_ready();
  }

class="type">void fetch_input_data(class="type">int f_input_start,class="type">int f_fetch)
  {

「抓取与归一化训练矩阵的实操细节」

在 MT5 用多标的收盘序列训练线性模型时,输入矩阵的第一列必须恒为 1,用来承载截距项。代码里用 matrix::Ones(f_fetch, 13) 先铺满 1,再 Reshape 成 f_fetch 行 13 列,随后从列 1 开始填各品种数据,列 0 最后用 vector::Ones(f_fetch) 覆盖,这个顺序不能反。 输出矩阵则固定为 f_fetch 行 1 列,直接把目标品种 COPY_RATES_CLOSE 拷进列 0。注意输入用 ArraySize(desired_symbols)-1 算宽度、输出只有 1 列,若 desired_symbols 含 14 个品种,输入矩阵实际宽 13 列再加截距列,总共 14 列——和 Reshape 的 13 参数对不上时会报维度错,开 MT5 跑前先核对符号数组长度。 归一化只在 f_flag==0 时做一次:把每列初值存下,之后整列除以该初值,使首行变 1,输出也按同样逻辑缩放成价格相对涨幅。后续实时推断直接除初始值即可,不必重算。外汇与贵金属价差跳变频繁,这类归一化对高波动品种可能放大噪声,建议先用历史数据回测再上实盘。

MQL5 / C++
input_matrix = matrix::Ones(f_fetch,(ArraySize(desired_symbols))-class="num">1);
input_matrix.Reshape(f_fetch,class="num">13);
for(class="type">int i=class="num">1; i <= ArraySize(desired_symbols);i++)
  {
  symbol_history.CopyRates(desired_symbols[i-class="num">1],PERIOD_CURRENT,COPY_RATES_CLOSE,f_input_start,f_fetch);
  input_matrix.Col(symbol_history,i);
  }
vector intercept = vector::Ones(f_fetch);
input_matrix.Col(intercept,class="num">0);

output_matrix = matrix::Ones(f_fetch,class="num">1);
output_matrix.Reshape(f_fetch,class="num">1);
symbol_history.CopyRates(target_symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,f_output_start,f_fetch);
output_matrix.Col(symbol_history,class="num">0);

用首值做基准把输入列压成相对比例

把多列行情数据归一化,最省事的办法是以第 0 行的值为基准,整列同除该值。这样每列首个元素必然变成 1,后续数值小于 1 表示相对起点走弱,大于 1 表示走强。 代码里先存下各列初始值 initiall_input_values,再把整列拷到 temp_vector 后除以基准,写回矩阵。输出列同理:temp_vector_output 除以 initiall_output_value 后,1.023 代表涨了约 2.3%,0.8732 代表跌了约 22.67%,一眼能读出相对涨跌幅度。 预测阶段若 f_flag==1,只把最新一行的输入列分别除以之前算好的 initiall_input_values,就得到可直接送模型的相对化特征。外汇与贵金属波动剧烈,这种比例缩放不改变方向概率,实盘仍属高风险,须用 MT5 打印矩阵自行核对首值是否为 1。 模型就绪后 model_ready 把 model_initialized 置 true、model_being_trained 置 false,系统才被放行交易;model_predict 开头先打印取数日志,说明预测前必须保证实时行情已按同一基准归一。

MQL5 / C++
initiall_input_values[i] = input_matrix[class="num">0][i+class="num">1];
class=class="str">"cmt">//Now temporarily copy the column, so that we can normalize the entire column at once
vector temp_vector = input_matrix.Col(i+class="num">1);
class=class="str">"cmt">//Now divide that entire column by its initial value, if done correctly the first value of each column should be class="num">1
temp_vector = temp_vector / initiall_input_values[i];
class=class="str">"cmt">//Now reinsert the normalized column
input_matrix.Col(temp_vector,i+class="num">1);
}
class=class="str">"cmt">//Print the initial class="kw">input values
Print("Our initial class="kw">input values for each column ");
Print(initiall_input_values);
class=class="str">"cmt">//Scale the output
class=class="str">"cmt">//Store the initial output value
initiall_output_value = output_matrix[class="num">0][class="num">0];
class=class="str">"cmt">//Make a temporary copy of the entire output column
vector temp_vector_output = output_matrix.Col(class="num">0);
class=class="str">"cmt">//Divide the entire column by its initial value, so that the first value in the column is one
class=class="str">"cmt">//This means that any time the value is less than class="num">1, price fell
class=class="str">"cmt">//And every time the value is greater than class="num">1, price rose.
class=class="str">"cmt">//A value of class="num">1.023... means price increased by class="num">2.3%
class=class="str">"cmt">//A value of class="num">0.8732.. would mean price fell by class="num">22.67%
temp_vector_output = temp_vector_output / initiall_output_value;
class=class="str">"cmt">//Reinsert the column back into the output matrix
output_matrix.Col(temp_vector_output,class="num">0);
class=class="str">"cmt">//Shpw the scaled data
Print("Data has been normlised and the output has been scaled:");
Print("Input matrix: ");
Print(input_matrix);
Print("Output matrix: ");
Print(output_matrix);
 }
class=class="str">"cmt">//Normalize the data class="kw">using the initial values we have allready calculated
 if(f_flag == class="num">1)
  {
   class=class="str">"cmt">//Divide each of the class="kw">input values by its corresponding class="kw">input value
   Print("Preparing to normalize the data for prediction");
   for(class="type">int i = class="num">0; i < ArraySize(desired_symbols);i++)
     {
      input_matrix[class="num">0][i+class="num">1] = input_matrix[class="num">0][i+class="num">1]/initiall_input_values[i];
     }
   Print("Input data being used for prediction");
   Print(input_matrix);
  }
 }
class=class="str">"cmt">//This function will allow our program to class="kw">switch states and begin forecasting and looking for trades
class="type">void model_ready(class="type">void)
  {
class=class="str">"cmt">//Our model is ready.
class=class="str">"cmt">//We need to class="kw">switch these flags to give the system access to trading functionality
  Print("Giving the system permission to begin trading");
  model_initialized = true;
  model_being_trained = class="kw">false;
  }
class=class="str">"cmt">//This function will obtain a prediction from our model
class="type">class="kw">double model_predict(class="type">void)
  {
class=class="str">"cmt">//First we have to fetch current market data
  Print("Obtaining a forecast from our model");

◍ 从伪逆拟合到下单信号的链路

线性回归模型用伪逆解(OLS)估算参数,本质是最小化训练集上的残差平方和(RSS)。代码里 b = input_matrix.PInv().MatMul(output_matrix) 一行就完成了系数求解,但注意:这组 b 只保证在训练数据上误差最小,对未见过行情的泛化能力并未验证,实盘外汇/贵金属波动下可能明显偏移。 拟合完成后,update_technical_indicators() 负责把 RSI、CCI、WPR、MA 这四类指标的近 10 根缓冲拉进数组,并用 ArraySetAsSeries 把时序倒排,保证 buffer[0] 永远是最新值。任何漏掉 AsSeries 的设置,都会让后续矩阵错位、预测变成用未来数据回看。 find_entry() 里直接读 bearish_sentiment() 的布尔结果来触发 Trade.Sell(minimum_volume * lot_multiple, _Symbol, bid_price)。这条卖单逻辑没有任何止盈止损包裹,贵金属隔夜跳空时风险偏高,建议你在 MT5 里跑之前先给 Trade 对象挂上 SL/TP 再测。

MQL5 / C++
  fetch_input_data(class="num">0,class="num">1);
class=class="str">"cmt">//Now we need to normalize our data class="kw">using the values we calculated when we initialized the model
  prepare_data(class="num">1);
class=class="str">"cmt">//Now we can class="kw">return our model&class="macro">#x27;s forecast
  class="kw">return((b[class="num">0][class="num">0] +
        (b[class="num">1][class="num">0]*input_matrix[class="num">0][class="num">0]) +
        (b[class="num">2][class="num">0]*input_matrix[class="num">0][class="num">1]) +
        (b[class="num">3][class="num">0]*input_matrix[class="num">0][class="num">2]) +
        (b[class="num">4][class="num">0]*input_matrix[class="num">0][class="num">3]) +
        (b[class="num">5][class="num">0]*input_matrix[class="num">0][class="num">4]) +
        (b[class="num">6][class="num">0]*input_matrix[class="num">0][class="num">5]) +
        (b[class="num">7][class="num">0]*input_matrix[class="num">0][class="num">6]) +
        (b[class="num">8][class="num">0]*input_matrix[class="num">0][class="num">7]) +
        (b[class="num">9][class="num">0]*input_matrix[class="num">0][class="num">8]) +
        (b[class="num">10][class="num">0]*input_matrix[class="num">0][class="num">9]) +
        (b[class="num">11][class="num">0]*input_matrix[class="num">0][class="num">10])+
        (b[class="num">12][class="num">0]*input_matrix[class="num">0][class="num">11])));
  }
class=class="str">"cmt">//This function will fit our linear model class="kw">using the pseudo inverse solution
class="type">void fit_linear_model(class="type">void)
  {
  class=class="str">"cmt">/*
    伪逆解通过最小化误差(残差平方和,RSS)来找到一组值,将我们的训练数据输入映射到输出。
这些系数值可以通过线性代数轻松估算,但这些系数仅能最小化我们在训练数据上的误差,而不是在未见过的数据上!
  */
  Print("Attempting to find OLS solutions.");
class=class="str">"cmt">//Now we can estimate our model parameters
  b = input_matrix.PInv().MatMul(output_matrix);
class=class="str">"cmt">//Let&class="macro">#x27;s see our model parameters
  Print("Our model parameters ");
  Print(b);
  }
class=class="str">"cmt">//Update our technical indicator values
class="type">void update_technical_indicators()
  {
class=class="str">"cmt">//Get market data
  ask_price = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
  bid_price = SymbolInfoDouble(_Symbol,SYMBOL_BID);
class=class="str">"cmt">//Copy indicator buffers
  CopyBuffer(rsi_handler,class="num">0,class="num">0,class="num">10,rsi_buffer);
  CopyBuffer(cci_handler,class="num">0,class="num">0,class="num">10,cci_buffer);
  CopyBuffer(wpr_handler,class="num">0,class="num">0,class="num">10,wpr_buffer);
  CopyBuffer(ma_handler,class="num">0,class="num">0,class="num">10,ma_buffer);
class=class="str">"cmt">//Set the arrays as series
  ArraySetAsSeries(rsi_buffer,true);
  ArraySetAsSeries(cci_buffer,true);
  ArraySetAsSeries(wpr_buffer,true);
  ArraySetAsSeries(ma_buffer,true);
  }
class=class="str">"cmt">//Let&class="macro">#x27;s try find an entry signal
class="type">void find_entry(class="type">void)
  {
class=class="str">"cmt">//Our sell setup
  if(bearish_sentiment())
    {
      Trade.Sell(minimum_volume * lot_multiple,_Symbol,bid_price);
    }
class=class="str">"cmt">//Our buy setup

「多空共振函数与持仓止损刷新」

空头共振函数 bearish_sentiment 里写死了五层过滤:收盘价低于均线、CCI 小于 0、RSI 小于 50、WPR 小于 -50、模型预测值小于 1。五个条件用 && 串起来,任一不满足就返回 false,实盘里这种硬性串联会显著压低触发频率,但也能过滤掉大部分震荡假信号。 多头一侧 bullish_sentiment 把阈值镜像翻转:收盘价站上均线、CCI 大于 0、RSI 大于 50、WPR 大于 -50、模型预测大于 1。注意原文注释里把 buy setup 错写成了 'For a sell setup',读代码时别被注释带偏,看 return 里的逻辑才是真的。 update_stoploss 负责遍历持仓并刷新止损。它从 PositionsTotal()-1 倒序循环,先用 PositionGetSymbol 取币种,再拿 _Symbol 做比对,只处理当前图表品种。拿到 ticket、开仓价、持仓类型、当前 SL 后,后续才计算新止损——这部分截断在取数,真正的 SL 重算逻辑要看完整 EA。 外汇和贵金属杠杆高、滑点跳空频繁,这类多指标共振在 EURUSD 15M 上可能把信号压缩到日均不到 3 次,参数阈值建议先在策略测试器跑历史再上模拟盘。

MQL5 / C++
else
    if(bullish_sentiment())
      {
       Trade.Buy(minimum_volume * lot_multiple,_Symbol,ask_price);
      }
}
class=class="str">"cmt">//This function tells us if all our tools align for a sell setup
class="type">bool bearish_sentiment(class="type">void)
  {
  class=class="str">"cmt">/*
      For a sell setup, we want the following conditions to be satisfied:
      class="num">1) CCI reading should be less than class="num">0
      class="num">2) RSI reading should be less than class="num">50
      class="num">3) WPR reading should be less than -class="num">50
      class="num">4) Our model forecast should be less than class="num">1
      class="num">5) MA reading should be below the moving average
   */
   class="kw">return((iClose(target_symbol,PERIOD_CURRENT,class="num">0) < ma_buffer[class="num">0]) &&
              (cci_buffer[class="num">0] < class="num">0) &&
              (rsi_buffer[class="num">0] < class="num">50) &&
              (wpr_buffer[class="num">0] < -class="num">50) &&
              (model_forecast < class="num">1));
  }
class=class="str">"cmt">//This function tells us if all our tools align for a buy setup
class="type">bool bullish_sentiment(class="type">void)
  {
  class=class="str">"cmt">/*
      For a sell setup, we want the following conditions to be satisfied:
      class="num">1) CCI reading should be greater than class="num">0
      class="num">2) RSI reading should be greater than class="num">50
      class="num">3) WPR reading should be greater than -class="num">50
      class="num">4) Our model forecast should be greater than class="num">1
      class="num">5) MA reading should be above the moving average
   */
   class="kw">return((iClose(target_symbol,PERIOD_CURRENT,class="num">0) > ma_buffer[class="num">0]) &&
              (cci_buffer[class="num">0] > class="num">0) &&
              (rsi_buffer[class="num">0] > class="num">50) &&
              (wpr_buffer[class="num">0] > -class="num">50) &&
              (model_forecast > class="num">1));
  }
class=class="str">"cmt">//+----------------------------------------------------------------------+
class=class="str">"cmt">//|This function is responsible for calculating our SL & TP values         |
class=class="str">"cmt">//+----------------------------------------------------------------------+
class="type">void update_stoploss(class="type">void)
  {
class=class="str">"cmt">//First we iterate over the total number of open positions
   for(class="type">int i = PositionsTotal() -class="num">1; i >= class="num">0; i--)
     {
      class=class="str">"cmt">//Then we fetch the name of the symbol of the open position
      class="type">class="kw">string symbol = PositionGetSymbol(i);
      class=class="str">"cmt">//Before going any furhter we need to ensure that the symbol of the position matches the symbol we&class="macro">#x27;re trading
      if(_Symbol == symbol)
        {
         class=class="str">"cmt">//Now we get information about the position
         class="type">class="kw">ulong ticket = PositionGetInteger(POSITION_TICKET); class=class="str">"cmt">//Position Ticket
         class="type">class="kw">double position_price = PositionGetDouble(POSITION_PRICE_OPEN); class=class="str">"cmt">//Position Open Price
         class="type">long type = PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">//Position Type
         class="type">class="kw">double current_stop_loss = PositionGetDouble(POSITION_SL); class=class="str">"cmt">//Current Stop loss value
把多品种扫描交给小布
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到CCI与RSI的多标的联立状态,你只管判断AI输出是否过过滤线。

常见问题

按市值加权,大市值公司如苹果微软对指数影响更重,权重会随股价变动而漂移。
文中把CCI围绕0的摆动视作成交量支持与否的代理,高于0视为买盘有量支撑,低于0反之,用来剔除无量假信号。
卖出要求RSI小于50、WPR小于-50,两者共同表达卖压占优,再叠CCI小于0与MA压制收盘价确认。
可以,AIGC模块已内置多品种CCI/RSI/WPR/MA同屏,省去自己写MQL5轮询的重复劳动。
本篇把原则用在指标数组与AI输入拼接上,关于基础铺垫见《标普500交易策略在MQL5中的实现·基础篇》。