使用Python和MQL5进行多交易品种分析(第一部分):纳斯达克集成电路制造商·进阶篇
(2/3)·均匀分配资本的老办法在波动市里不够看,这篇用十万行M1数据跑出五只集成电路股的算法仓位
先看清数据里的相关与波动
拿到一组资产回报率后,第一件事不是急着建模,而是用探索性手段把变量间的关系摊开看。对 returns 矩阵直接算相关系数并画热力图,肉眼就能判断有没有强到能榨出线性组合的配对——实测下来多数格子里的数值偏弱,暂时找不到可依赖的线性结构。 光看相关性还不够,大样本里非线性或错位的关联常被热力图掩盖。用成对散点图(pairplot)把每两个变量的分布打散看,能压低这种漏看的概率;但当前这批图同样没暴露出容易捕捉的显性关系。 把各标的回报率时序画出来,NVIDIA 的曲线振幅明显最宽,说明其样本内波动最剧烈。再换成箱线图看市场回报率整体分布,须注意外汇与贵金属市场同样存在高波动高风险,箱线中位线清晰地落在 0 附近,即平均市场回报率为 0,这对后续信号阈值设定是直接约束。
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空。外汇/贵金属若套同法,杠杆与隔夜成本会让权重含义漂移,属高风险操作,仅作方法验证。
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 改。
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 观察平仓触发阈值。
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) {