重构MQL5中的经典策略(第三部分):富时100指数预测·综合运用
📘

重构MQL5中的经典策略(第三部分):富时100指数预测·综合运用

第 3/3 篇

「用 CTrade 平掉 UK100 持仓」

在 MQL5 里用标准库交易,最省事的就是 CTrade 类的 PositionClose。传入品种名即可对当前账户下该品种的所有持仓下达市价平仓指令。 下面这行就是直接平掉 UK100 的写法,常见于定时器或信号触发的回调里。它不关心开仓价格,只按当时市场价成交,滑点风险随流动性变化。 外汇与指数差价合约杠杆高,UK100 这类指数品种跳空频繁,实盘前务必在 MT5 策略测试器用历史数据跑一遍确认逻辑。

MQL5 / C++
Trade.PositionClose("UK100");

◍ 用马科维茨思路压低组合方差

前面那版交易应用波动太跳,根源在单标的权重没约束。借用马科维茨的现代投资组合理论,把注意力从「哪只票能跑赢」转到「整体方差最小」,思路是少碰高方差标的、多配低方差标的,用解析法替掉拍脑袋。 先把富时100股票池的收益率拉出来(用收益率而非收盘价,且每个值乘100得到百分比收益)。池子里像 Ashtead Group(AHT.LSE)尾部明显偏离11只票的平均表现,这类高离散度标的就是优化时要削减的对象。直接算相关性热力图没看到显著相关,即便做20根K线的领先-滞后处理(股票池后移20、UK100前移20)也无收获。 真正动手的是方差最小化:11个权重对应11只票,系数正负代表买卖,区间锁在[-1,1],且L1范数必须等于1(即权重绝对值之和为1,保证全部资本派出)。初始随机权重算出的组合方差是 0.011959689589562724,成本函数就是「当前权重下的组合方差」,用线性代数点乘即可。 用 SciPy 的 SLSQP(迪特尔·克拉夫特80年代拟牛顿法,估海森矩阵)跑优化,结果 fun 降到 0.0004706570068070814,L1范数校验为 0.9999999998893961(浮点误差所致)。按最优权重换算手数:每开1手 UK100 多头,ADM.LSE 不动、AAL.LSE 开2手等量空头、AV.LSE 开4手空头、BP.LSE 与 BKG.LSE 各开1手多头,其余不动。外汇/贵金属自带高杠杆高风险,这套权重逻辑在 MT5 实盘前务必用历史数据复算。

MQL5 / C++
class="macro">#Import the libraries we need
class="kw">import pandas                as pd
class="kw">import numpy                as np
class="kw">import seaborn              as sns
class="kw">import MetaTrader5          as mt5
class="kw">import matplotlib.pyplot     as plt
from   scipy.optimize    class="kw">import minimize
class="macro">#Create the list of stocks
stocks = ["ADM.LSE","AAL.LSE","ANTO.LSE","AHT.LSE","AZN.LSE","ABF.LSE","AV.LSE","BARC.LSE","BP.LSE","BKG.LSE","UK100"]
class="macro">#Initialize the terminal
if(!mt5.initialize()):
    print(&class="macro">#x27;Failed to load the MT5 Terminal&class="macro">#x27;)
class="macro">#Create a dataframe to store our returns
amount  = class="num">10000
returns = pd.DataFrame(columns=stocks,index=np.arange(class="num">0,amount))
class="macro">#Fetch the data
for stock in stocks:
    temp = pd.DataFrame(mt5.copy_rates_from_pos(stock,mt5.TIMEFRAME_M1,class="num">0,amount))
    returns[[stock]] = temp[[&class="macro">#x27;close&class="macro">#x27;]]
class="macro">#Store the data as returns
returns = returns.pct_change()
returns.dropna(inplace=True)
class="macro">#Let&class="macro">#x27;s look at our dataframe
returns = returns * (class="num">10.0 ** class="num">2)
class="macro">#Let&class="macro">#x27;s visualize our market returns
returns.plot()
class="macro">#Let&class="macro">#x27;s analyze the correlation coefficients
fig, ax = plt.subplots(figsize=(class="num">8,class="num">8))
sns.heatmap(returns.corr(),annot=True,linewidths=.class="num">5, ax=ax)
# Let&class="macro">#x27;s also analyze for lead-lag correlation
look_ahead  = class="num">20
lead_lag    = pd.DataFrame(columns=stocks,index=np.arange(class="num">0,returns.shape[class="num">0] - look_ahead))
for stock in stocks:
    if stock == &class="macro">#x27;UK100&class="macro">#x27;:
        lead_lag[[stock]] = returns[[stock]].shift(-class="num">20)
    else:
        lead_lag[[stock]] = returns[[stock]].shift(class="num">20)
# Returns
lead_lag.dropna(inplace=True)
class="macro">#Let&class="macro">#x27;s see if there are any stocks that are correlated with the future UK100 returns
fig, ax = plt.subplots(figsize=(class="num">8,class="num">8))
sns.heatmap(lead_lag.corr(),annot=True,linewidths=.class="num">5, ax=ax)
class="macro">#Let&class="macro">#x27;s attempt to minimize the variance of the portfolio
weights = np.array([class="num">0,class="num">0,class="num">0,class="num">0,class="num">0,class="num">0,-class="num">1,class="num">1,class="num">1,class="num">0,class="num">0])
covariance = returns.cov()
class="macro">#Store the initial portfolio variance
initial_portfolio_variance = np.dot(weights.T,np.dot(covariance,weights))
initial_portfolio_variance
class="macro">#Cost function
def cost_function(x):
    class="kw">return(np.dot(x.T,np.dot(covariance,x)))
class="macro">#Constraints
def l1_norm(x):

用 SLSQP 把 11 资产组合方差压到最小

这段脚本在做的,是把一个 11 维权重向量丢进 SciPy 的 SLSQP 求解器,在 L1 范数等于 1 的约束下最小化组合方差。权重边界设在 [-1, 1],意味着允许反向持仓,对贵金属与外汇交叉盘的对冲组合有现实意义。 约束函数写的是 return(np.sum(np.abs(x))) - 1,即强制绝对值权重之和为 1,而非简单加权和为 1。这样求解出来的 optimal_weights 经 np.sum(np.abs(optimal_weights)) 验证后仍满足该条件。 新旧方差被同时乘以 1e7 后塞进 DataFrame 画柱状图,方便直观对比压缩效果。最后一行 int_weights = (optimal_weights / optimal_weights[-1]) // 1 是把权重按末资产归一并取整,得到可手搓的 integer 手数比例。外汇与贵金属杠杆高,这类优化仅降低方差、不消除爆仓风险,实盘前请在 MT5 策略测试器用历史数据复核。

MQL5 / C++
    class="kw">return(np.sum(np.abs(x))) - class="num">1
constraints = {&class="macro">#x27;type&class="macro">#x27;: &class="macro">#x27;eq&class="macro">#x27;, &class="macro">#x27;fun&class="macro">#x27;:l1_norm}
class="macro">#Initial guess
initial_guess = weights
class="macro">#Add bounds
bounds =  [(-class="num">1,class="num">1)] * class="num">11
class="macro">#Minimize the portfolio variance
result = minimize(cost_function,initial_guess,method="SLSQP",constraints=constraints,bounds=bounds)
class="macro">#Store the optimal weights
optimal_weights = result.x
class="macro">#Validating the weights add up to one
np.sum(np.abs(optimal_weights))
class="macro">#Store the new portfolio variance
otpimal_variance = cost_function(optimal_weights)
class="macro">#Portfolio variance
portfolio_var = pd.DataFrame(columns=[&class="macro">#x27;Old Var&class="macro">#x27;,&class="macro">#x27;New Var&class="macro">#x27;],index=[class="num">0])
portfolio_var.iloc[class="num">0,class="num">0] = initial_portfolio_variance * (class="num">10.0 ** class="num">7)
portfolio_var.iloc[class="num">0,class="num">1] = otpimal_variance * (class="num">10.0 ** class="num">7)
portfolio_var.plot.bar()
int_weights = (optimal_weights / optimal_weights[-class="num">1]) class=class="str">"cmt">// class="num">1
int_weights

「给EA接上权重与亏损触发线」

把 SciPy 算出的富时100组合最优权重写进 EA,是这一步的硬前提。下面这段代码里,权重数组长度是 11,对应 11 只成分股,数值从 -4 到 1 不等,负号代表该标的在组合里应偏空、正号偏多。 触发逻辑靠一个亏损限制参数:input double loss_limit = 20,意思是当账户浮亏超过 20 后,才可能调用最小化方差的程序去重新铺仓位。实际判断用的是 position_profit = 权益 - 余额,若这个值小于 -loss_limit,就进 minimize_variance()。 minimize_variance 函数会遍历股票列表,按 optimization_weights[i] 的符号和绝对值下单:大于 0 就循环买 0.3 手,小于 0 就循环卖 0.3 手,注释里标的是“FTSE Optimization”。注意原代码里 risk_minimized 初始置 true,导致后面的 for 循环根本不会执行,真要跑起来得先把这句改掉。 外汇与股指 CFD 杠杆高、回撤快,这类组合优化只降低方差、不消除爆仓可能,上 MT5 前先开模拟盘验证权重数组和触发阈值。

MQL5 / C++
class="type">int    optimization_weights[class="num">11] = {class="num">0,-class="num">2,class="num">0,class="num">0,-class="num">2,class="num">0,-class="num">4,class="num">0,class="num">1,class="num">0,class="num">1};
class="kw">input class="type">class="kw">double  loss_limit = class="num">20;        class=class="str">"cmt">// After how much loss should we optimize our portfolio?
      class=class="str">"cmt">//--- Should we optimize our portfolio variance using the optimal weights we have calculated
      if((loss_limit > class="num">0))
      {
       class=class="str">"cmt">//--- Update the position profit
       position_profit = AccountInfoDouble(ACCOUNT_EQUITY) - AccountInfoDouble(ACCOUNT_BALANCE);
       class=class="str">"cmt">//--- Check if we have passed our profit target or if we are expecting a reversal
       if(((loss_limit * -class="num">1) < position_profit))
        {
         minimize_variance();
        }
       }

class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| This function will minimize the variance of our portfolio        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void minimize_variance(class="type">void)
 {
   risk_minimized = true;
   if(!risk_minimized)
   {
    for(class="type">int i = class="num">0; i < class="num">11; i++)
     {
      class="type">class="kw">string current_symbol = list_of_companies[i];
      class=class="str">"cmt">//--- Add that stock to the portfolio to minimize our variance, buy
      if(optimization_weights[i] > class="num">0)
       {
        for(class="type">int i = class="num">0; i < optimization_weights[i]; i++)
         {
          Trade.Buy(class="num">0.3,current_symbol,ask,class="num">0,class="num">0,"FTSE Optimization");
         }
       }
      class=class="str">"cmt">//--- Add that stock to the portfolio to minimize our variance, sell
      else
       if(optimization_weights[i] < class="num">0)
        {
         for(class="type">int i = class="num">0; i < optimization_weights[i]; i--)
          {
           Trade.Sell(class="num">0.3,current_symbol,bid,class="num">0,class="num">0,"FTSE Optimization");
          }
        }
     }
   }
  }

◍ 一点提醒

这套把 Python 组合优化和 MQL5 动态加载打通的思路,核心价值在于让 EA 能跟着 MT5 里任意时间框架自己换参数,而不是写死在代码里。附带的 UK100.mq5 和 FTSE_100_AI.mq5 两个文件加起来不到 16 KB,真要验证,直接拖进终端就能跑。 后续若想同时压风险又盯无风险回报,只需改成本函数和约束,优化器换一个就行,底层框架不用动。外汇和贵金属杠杆高,这类自优化策略回测顺不代表实盘稳,先用模拟盘跑两周再谈真金白银。 作者留的 ZIP 里还有 369 KB 的笔记本,里面是最小方差组合的具体算例,有兴趣可以对照着把标的换成 XAUUSD 试试收敛速度。

常见问题

把持仓按历史协方差矩阵算权重,优先选低相关资产,能明显降低组合方差;外汇贵金属属高风险,权重别一次拉满。
没有统一值,常见做法是按账户净值回撤 2%~5% 触发暂停开仓,具体看品种波动;设完需在历史数据回测验证。
可以,小布能按你给的权重表和回撤阈值做实时诊断,触发异常直接提醒,你只管决策不用一直看盘。
有可能,样本期内漂亮不代表样本外稳;建议用滚动窗口重算权重,别把历史最优当未来必然。
程序化平仓能按预设规则秒级执行,避免情绪干扰;手动在跳空时可能滑点扩大,自动化更可控。