使用Python和MQL5进行多交易品种分析(第一部分):纳斯达克集成电路制造商·进阶篇
📊

使用Python和MQL5进行多交易品种分析(第一部分):纳斯达克集成电路制造商·进阶篇

(2/3)·均匀分配资本的老办法在波动市里不够看,这篇用十万行M1数据跑出五只集成电路股的算法仓位

实战向 第 2/3 篇
把资金平摊到几只关联股票上就当分散风险,是很多人的默认动作。可芯片股之间相关性弱、波动幅度差很大,无脑均配只会让回撤失控。本篇用数据告诉你哪些该加仓、哪些该反手卖出。

先看清数据里的相关与波动

拿到一组资产回报率后,第一件事不是急着建模,而是用探索性手段把变量间的关系摊开看。对 returns 矩阵直接算相关系数并画热力图,肉眼就能判断有没有强到能榨出线性组合的配对——实测下来多数格子里的数值偏弱,暂时找不到可依赖的线性结构。 光看相关性还不够,大样本里非线性或错位的关联常被热力图掩盖。用成对散点图(pairplot)把每两个变量的分布打散看,能压低这种漏看的概率;但当前这批图同样没暴露出容易捕捉的显性关系。 把各标的回报率时序画出来,NVIDIA 的曲线振幅明显最宽,说明其样本内波动最剧烈。再换成箱线图看市场回报率整体分布,须注意外汇与贵金属市场同样存在高波动高风险,箱线中位线清晰地落在 0 附近,即平均市场回报率为 0,这对后续信号阈值设定是直接约束。

MQL5 / C++
class="macro">#Let&class="macro">#x27;s analyze if there is any correlation in the data
sns.heatmap(returns.corr(),annot=True)
class="macro">#Let&class="macro">#x27;s create pair plots of our data
sns.pairplot(returns)
<span class="preprocessor">class="macro">#Lets </span>also visualize our returns
returns.plot()
class="macro">#Let&class="macro">#x27;s try creating box-plots 
sns.boxplot(returns)

◍ 用几何均值与L1约束求组合权重

做组合权重分配时,先给五类资产随机撒一组加起来为1的初值,同时挂一个列表把每次迭代的目标值存下来,方便后面看算法是不是真在收敛。 目标函数别用算术平均。资产收益有正有负,算术平均容易把组合回报算成0,失去优化意义。这里改用几何平均数:把(1+组合收益)连乘后开N次方再减1,且在回传给优化器前乘-1,把最大化转成最小化问题,SLSQP跑起来更稳。 约束只有一条——权重绝对值之和必须等于1,也就是L1范数为1。SciPy里等式约束要求函数输出为0,所以写的是 sum(abs(x))-1。每只权重上下界锁在[-1,1],允许净空头暴露。 一次实跑结果:status True,fun=0.00243086,x=[0.3931,0.1138,-0.0599,0.0774,-0.3557],迭代23次、函数评估158次。验证 np.sum(np.abs(x)) 正好回 1.0,约束没破。 把系数乘10再整除1,得到 [3,1,-1,0,-4]:Broadcom开3多、Cisco开1多、Comcast开1空、Intel不动、NVIDIA开4空。外汇/贵金属若套同法,杠杆与隔夜成本会让权重含义漂移,属高风险操作,仅作方法验证。

MQL5 / C++
class="macro">#Define random weights that add up to class="num">1
weights = np.array([class="num">1,class="num">0.5,class="num">0,class="num">0.5,-class="num">1])
class="macro">#Create a data structure to store the progress of the algorithm
evaluation_history = []
class="macro">#Let us now get ready to maximize our returns
class="macro">#First we need to define the cost function
def cost_function(x):
    class="macro">#First we need to calculate the portfolio returns with the suggested weights
    portfolio_returns = np.dot(returns,x)
    geom_mean       = ((np.prod( class="num">1 + portfolio_returns ) ** (class="num">1.0/class="num">99999.0)) - class="num">1)
    class="macro">#Let&class="macro">#x27;s keep track of how our algorithm is performing
    evaluation_history.append(-geom_mean)
    class="kw">return(-geom_mean)
class="macro">#Now we need to define our constraints
def l1_norm_constraint(x):
    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_constraint})
class="macro">#Now we need to define the bounds for our weights
bounds = [(-class="num">1,class="num">1)] * class="num">5
class="macro">#Perform the optimization
results = minimize(cost_function,weights,method="SLSQP",bounds=bounds,constraints=constraints)
结果
optimal_weights = results.x
optimal_weights
optima_y = min(evaluation_history)
optima_x = evaluation_history.index(optima_y)
inputs = np.arange(class="num">0,len(evaluation_history))
plt.scatter(inputs,evaluation_history)
plt.plot(optima_x,optima_y,&class="macro">#x27;s&class="macro">#x27;,class="type">color=&class="macro">#x27;r&class="macro">#x27;)
plt.axvline(x=optima_x,ls=&class="macro">#x27;--&class="macro">#x27;,class="type">color=&class="macro">#x27;red&class="macro">#x27;)
plt.axhline(y=optima_y,ls=&class="macro">#x27;--&class="macro">#x27;,class="type">color=&class="macro">#x27;red&class="macro">#x27;)
plt.title("Maximizing Returns")
class="macro">#Validate the weights add up to class="num">1
np.sum(np.abs(optimal_weights))
class="macro">#Here&class="macro">#x27;s an intuitive way of understanding the data
class="macro">#If we can only open class="num">10 positions, our best bet may be
#class="num">3 buy positions in Broadcom
#class="num">1 buy position in Cisco
#class="num">1 sell position sell position in Comcast
class="macro">#No positions in Intel
#class="num">4 sell postions in NVIDIA(optimal_weights * class="num">10) class=class="str">"cmt">// class="num">1

「把多品种均值回归写成 MT5 EA 骨架」

这套组合策略在 MT5 里落地的第一步,是先把 5 个标的的全局状态钉死。代码里 stocks[5] 写死了 AVGO.NAS、CSCO.NAS、CMCSA.NAS、INTC.NAS、NVDA.NAS,optimal_weights[5] 给的是 {3,1,-1,0,-4}——正权重偏多、负权重偏空、0 表示观望,初始化时若任一品种选不上就直接 return false 终止,避免半套组合裸跑。 价格每次 tick 进来,先写全局 bid/ask,再跑机会检查与利润兑现。兑现函数会扫一遍池子里的品种,有仓且浮利超过用户设的目标才平仓,没超就跳过;这种「只盯已持仓品种」的写法,能把不必要的平仓信号挡在门外。 开仓侧靠 check_buy / check_sell 做门槛:多头要价破布林上轨、RSI>70、高周期走多、且当前仓数不超权重分配;空头则 check_sell 把权重乘 -1 后,要求价破布林下轨、RSI<30、高周期允许做空。外汇与贵金属品种若套用同逻辑,杠杆与跳空会让胜率明显低于美股盘后回测,属高风险用法,参数须重测。 optimize_portfolio 吃(品种,权重)两个参,权重正则补多仓到满足、负则反手补空仓。下面这段全局与库声明是 EA 能跑起来的底,逐行拆一下方便你直接抄进 MetaEditor 改。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| NASDAQ IC AI.mq5 |
class=class="str">"cmt">//| Gamuchirai Zororo Ndawana |
class=class="str">"cmt">//| [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Gamuchirai Zororo Ndawana"
class="macro">#class="kw">property link "[MQL5官方文档]
class="macro">#class="kw">property version "class="num">1.00"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Global variables |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int rsi_handler,bb_handler; class=class="str">"cmt">// RSI与布林带指标句柄,后续取缓冲用
 class="type">class="kw">double bid,ask; class=class="str">"cmt">// 全局存最新买价卖价,供各函数调用
class="type">int optimal_weights[class="num">5] = {class="num">3,class="num">1,-class="num">1,class="num">0,-class="num">4}; class=class="str">"cmt">// 5品种目标权重:正多负空0观望
class="type">class="kw">string stocks[class="num">5] = {"AVGO.NAS","CSCO.NAS","CMCSA.NAS","INTC.NAS","NVDA.NAS"}; class=class="str">"cmt">// 标的数组
vector current_close = vector::Zeros(class="num">1); class=class="str">"cmt">// 收盘价向量占位
vector rsi_buffer = vector::Zeros(class="num">1); class=class="str">"cmt">// RSI值向量占位
vector bb_high_buffer = vector::Zeros(class="num">1); class=class="str">"cmt">// 布林上轨向量占位
vector bb_mid_buffer = vector::Zeros(class="num">1); class=class="str">"cmt">// 布林中轨向量占位
vector bb_low_buffer = vector::Zeros(class="num">1); class=class="str">"cmt">// 布林下轨向量占位
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Libraries |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Trade/Trade.mqh> class=class="str">"cmt">// 导入交易库管仓位
CTrade Trade; class=class="str">"cmt">// 交易对象实例
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| User inputs |
class=class="str">"cmt">//+------------------------------------------------------------------+

把盈利落袋的逻辑写进 OnTick

这套多品种 EA 的收口动作很直接:每跳价一来,先刷行情、再扫信号、最后查持仓盈利。盈利目标由外部参数 profit_target 控制,默认 1.0,意味着浮盈超过该值就倾向平仓,外汇与贵金属品种需自行把该值换算成点数或金额,相关杠杆风险较高。 核心平仓函数 check_profits 写死了遍历前 5 个符号(stocks[0]~stocks[4]),用 SymbolSelect 确保品种可见,PositionSelect 确认有仓,再读 POSITION_PROFIT 比 profit_target。实测若 profit_target=1.0 且账户货币与品种报价货币不同,比较的是账户净值货币下的绝对利润,不是点数。 初始化 OnInit 只做符号校验,失败返回 INIT_FAILED 阻止启动;OnDeinit 调 release_resources 释放资源。想验证就开 MT5 把 stocks 数组换成 XAUUSD、EURUSD 等,改 profit_target=10 观察平仓触发阈值。

MQL5 / C++
class="kw">input class="type">class="kw">double profit_target =  class="num">1.0; class=class="str">"cmt">//At this profit level, our position will be closed
class="kw">input class="type">int     rsi_period    =  class="num">20; class=class="str">"cmt">//Adjust the RSI period
class="kw">input class="type">int     bb_period     =  class="num">20; class=class="str">"cmt">//Adjust the Bollinger Bands period
class="kw">input class="type">class="kw">double trade_size     =  class="num">0.3; class=class="str">"cmt">//How big should our trades be?
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Validate that all the symbols we need are available
   if(!validate_symbol())
     {
       class="kw">return(INIT_FAILED);
     }
class=class="str">"cmt">//--- Everything went fine
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
class=class="str">"cmt">//--- Release resources we no longer need
   release_resources();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//--- Update market data
   update_market_data();
class=class="str">"cmt">//--- Check for a trade oppurtunity in each symbol
   check_trade_symbols();
class=class="str">"cmt">//--- Check if we have an oppurtunity to take ourt profits
   check_profits();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check for opportunities to collect our profits                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void check_profits(class="type">void)
  {
   for(class="type">int i =class="num">0; i < class="num">5; i++)
     {
       if(SymbolSelect(stocks[i],true))
         {
          if(PositionSelect(stocks[i]))
            {
               if(PositionGetDouble(POSITION_PROFIT) > profit_target)
                 {
                  Trade.PositionClose(stocks[i]);
                 }
            }
         }
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Update markte data                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void update_market_data(class="type">void)
  {
把跨品种诊断交给小布
这些个股相关性与权重诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到五只芯片股的真实联动,你只管定自己的风险容忍度。

常见问题

业务侧重点不同,Broadcom做通信芯片、NVIDIA做GPU、Intel覆盖CPU等,需求周期错位,收益率叠加后无明显共移,弱相关是样本统计结果。
目前内置的是相关性和波动诊断视图,权重优化需自行用Python+SciPy求解;小布负责把盘口联动可视化,重复盯数据的事交给它更省心。
MT5 Python库分次拉取即可,单机pandas处理百万行以内无压力,重点在百分比收益转换和箱线图排查异常点。
负值即算法提示卖出该标的,正值对应买入规模,突破0轴就是方向切换信号,比固定比例持仓更贴合波动市。