使用Python和MQL5进行多品种分析(第三部分):三角汇率·进阶篇
(2/3)· 当白银双报价与欧美汇率构成三角形,隐藏情绪如何从普通报价里被拆出来
「用 SL/TP 位置判定多空并自动平移止损」
这段逻辑先读取当前持仓的止盈价,再用止损与止盈的相对位置区分买卖盘:若 SL 低于 TP,说明是 Buy 仓;反之 SL 高于 TP,则是 Sell 仓。 对于 Buy 仓,当 bid 减去 sl_width 后仍然大于原 SL,就把止损上移到 bid - sl_width、止盈同步推到 bid + sl_width,实现随价保护。Sell 仓的判定镜像处理:ask + sl_width 小于原 SL 时,止损下移到 ask + sl_width、止盈落到 ask - sl_width。 sl_width 是你要自己定义的缓冲宽度,单位与报价一致;XAGUSD 这类贵金属点值小,sl_width 设 0.1 与设 0.5 会导致修改触发频率差数倍,建议先在策略测试器用 2023 年 1—6 月数据跑一遍看成交次数。 外汇与贵金属杠杆高,自动改单可能在快速行情中连续触发,实盘前务必用模拟盘验证滑点表现。
class="type">class="kw">double current_tp = PositionGetDouble(POSITION_TP); class=class="str">"cmt">//--- Buy setup if(current_sl < current_tp) { if((bid - sl_width) > current_sl) Trade.PositionModify(SYMBOL_ONE,(bid - sl_width),(bid + sl_width)); } class=class="str">"cmt">//--- Sell setup if(current_sl > current_tp) { if((ask + sl_width) < current_sl) Trade.PositionModify(SYMBOL_ONE,(ask + sl_width),(ask - sl_width)); }
◍ EA 骨架里的跨品种向量与手数设定
这段初始化代码把欧元兑美元、白银美元价、白银欧元价三个序列塞进 vector 容器,用 H1 周期拉 24 根 K 线做后续比值计算。手数不是写死,而是取交易品种最小成交量乘 10,比如 XAUUSD 最小 0.01 手时实际下单 0.1 手,跟账户合约规格走。 止损宽度直接绑 _Point:sl_width = 3e2 * _Point,对多数外汇对就是 300 点,贵金属点值不同要自己重算。OnInit 只调一个 setup(),真正的指标装配在别处,这里先留空壳。 OnTick 里只调 new_quotes_received(),把报价事件和外部逻辑解耦。想验证就开 MT5 建个 EA,把这段贴进文件,编译后看初始化是否过、vector 能否在 H1 上取到 24 根。外汇和贵金属杠杆高,最小手数乘 10 可能超出小微账户风控,先开模拟盘跑。
class="macro">#define FETCH class="num">24 class=class="str">"cmt">//--- How many bars of data should we fetch? class="macro">#define TF_1 PERIOD_H1 class=class="str">"cmt">//--- Our intended time frame class="macro">#define VOLUME SymbolInfoDouble(SYMBOL_ONE,SYMBOL_VOLUME_MIN) * class="num">10 class=class="str">"cmt">//--- Our trading volume class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Global variables | class=class="str">"cmt">//+------------------------------------------------------------------+ vector eurusd,xagusd,xageur; class="type">class="kw">double eurusd_growth,xagusd_growth,xageur_growth,bid,ask; class="type">class="kw">double sl_width = class="num">3e2 * _Point; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Libraries | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Trade\Trade.mqh> CTrade Trade; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- Setup our technical indicators setup(); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">//--- } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- New prices have been quoted new_quotes_received(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom functions | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+
三品种联动下的报价刷新与信号触发
跨品种套利类 EA 的核心,是只在新 K 线成型时才重算状态,避免每 tick 重复跑逻辑。下面这段用静态时间戳比对 iTime 返回值,时间变了才调用 update(),能明显降低 CPU 占用。 初始化阶段用 SymbolSelect 把三个品种(如 XAGUSD、XAGEUR、EURUSD)加进市场观察窗,否则后续 CopyRates 可能取不到数据。update() 里对每品种取 TF_1 周期最近 1 根+前 FETCH-1 根的收盘价,用当前收盘除以前一根得出 growth 比值——growth<1 代表该品种当前段走弱,>1 为走强。 信号判定很直接:无持仓时,若 EURUSD 与 XAGUSD 的 growth 均小于 1 且 XAGEUR 大于 1,倾向认为白银相对欧元高估,触发 SYMBOL_ONE 的 Sell;反向条件则倾向 Buy。注意原文里 XAGUSD 拼写作 Grwoth 属笔误,实盘照抄会编译报错。 外汇与贵金属杠杆高、跳空频繁,上述 growth 阈值仅描述代码逻辑,实际胜率随品种波动 regime 变化,可能失效。建议开 MT5 把 SYMBOL_ONE/Two/Three 改成真实符号,先用策略测试器跑 2023 年 XAGUSD 数据验证 growth 计算是否符合预期。
class=class="str">"cmt">//| Updates system variables accordingly | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void new_quotes_received(class="type">void) { class="kw">static class="type">class="kw">datetime time_stamp; class="type">class="kw">datetime time = iTime(SYMBOL_ONE,TF_1,class="num">0); if(time_stamp != time) { time_stamp = time; update(); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Setup our technical indicators and select the symbols we need | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void setup(class="type">void) { class=class="str">"cmt">//--- Select the symbols we need SymbolSelect(SYMBOL_ONE,true); SymbolSelect(SYMBOL_TWO,true); SymbolSelect(SYMBOL_THREE,true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Update our system setup | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void update(class="type">void) { class=class="str">"cmt">//--- Fetch updated prices xagusd.CopyRates(SYMBOL_ONE,TF_1,COPY_RATES_CLOSE,class="num">1,FETCH); xageur.CopyRates(SYMBOL_TWO,TF_1,COPY_RATES_CLOSE,class="num">1,FETCH); eurusd.CopyRates(SYMBOL_THREE,TF_1,COPY_RATES_CLOSE,class="num">1,FETCH); class=class="str">"cmt">//--- Calculate the growth in market prices eurusd_growth = eurusd[class="num">0] / eurusd[FETCH - class="num">1]; xageur_growth = xageur[class="num">0] / xageur[FETCH - class="num">1]; xagusd_growth = xagusd[class="num">0] / xagusd[FETCH - class="num">1]; class=class="str">"cmt">//--- Update system variables SymbolSelect(SYMBOL_ONE,true); bid = SymbolInfoDouble(SYMBOL_ONE,SYMBOL_BID); ask = SymbolInfoDouble(SYMBOL_ONE,SYMBOL_ASK); class=class="str">"cmt">//--- Check if we need to setup a new position if(PositionsTotal() == class="num">0) find_setup(); class=class="str">"cmt">//--- Check if we need to manage our positions if(PositionsTotal() > class="num">0) manage_setup(); class=class="str">"cmt">//--- Give feedback on the market growth Comment("EURUSD Growth: ",eurusd_growth,"\nXAGEUR Growth: ",xageur_growth,"\nXAGUSD Grwoth: ",xagusd_growth); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Find setup | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void find_setup(class="type">void) { class=class="str">"cmt">//--- Check if the current market setup matches our expectations for selling if((eurusd_growth < class="num">1) && (xageur_growth > class="num">1) && (xagusd_growth < class="num">1)) { Trade.Sell(VOLUME,SYMBOL_ONE,bid,(ask + sl_width),(ask - sl_width),""); } class=class="str">"cmt">//--- Check if the current market setup matches our expectations for buying if((eurusd_growth > class="num">1) && (xageur_growth < class="num">1) && (xagusd_growth > class="num">1))
「持仓过程的动态止损挪移」
开仓之后并不是丢给市场不管,管理函数会在每轮 tick 里重新选中指定品种的持仓,读取它当前的 SL 与 TP 数值。 对于多单,当实时买价减去缓冲宽度(bid - sl_width)高于原止损时,说明价格已经朝有利方向走出了足够空间,此时调用 PositionModify 把止损上移到该位置、止盈同步外推到 bid + sl_width,实现浮盈保护。 空单逻辑对称:若 ask + sl_width 低于原止损,则把止损下压到 ask + sl_width、止盈收至 ask - sl_width。外汇与贵金属杠杆高,这种随价移动止损能缩小回撤概率,但无法消除极端滑点风险。 代码末尾用 #undef 注销 TF_1、SYMBOL_ONE 等宏,避免多文件编译时的宏污染;你在 MT5 里复用时若报错“宏未定义”,先确认这些常量在包含段已重新 #define。
class="type">void manage_setup(class="type">void) { class=class="str">"cmt">//--- Select our open position if(PositionSelect(SYMBOL_ONE)) { class="type">class="kw">double current_sl = PositionGetDouble(POSITION_SL); class="type">class="kw">double current_tp = PositionGetDouble(POSITION_TP); class=class="str">"cmt">//--- Buy setup if(current_sl < current_tp) { if((bid - sl_width) > current_sl) Trade.PositionModify(SYMBOL_ONE,(bid - sl_width),(bid + sl_width)); } class=class="str">"cmt">//--- Sell setup if(current_sl > current_tp) { if((ask + sl_width) < current_sl) Trade.PositionModify(SYMBOL_ONE,(ask + sl_width),(ask - sl_width)); } } } class="macro">#undef TF_1 class="macro">#undef SYMBOL_ONE class="macro">#undef SYMBOL_TWO class="macro">#undef SYMBOL_THREE class="macro">#undef VOLUME class="macro">#undef FETCH
◍ 给初始策略加两道过滤阀
原始策略在盈利与连续亏损之间来回摆,这种周期性振荡大多来自市场噪音和单一市场的盲判。要压住这种不稳定,两条路最直接:用技术指标做附加确认来延迟决策、过滤杂波,再给每个跟踪品种单独建统计模型。 给每个市场建独立模型,意义不只是预测方向。模型在波动结构生变时给出的置信下滑,本身就是一个减仓信号——不确定期把仓位规模缩下来,比硬扛回撤更划算。外汇与贵金属杠杆高,这类缩减动作本质是风控,不是择时神技。 建模前先抓历史报价。一开始你并不知道哪组输入能出最优模型,所以先把特征铺开:平方、立方、平方根、增长率、对数增长、三角变换全写进去,再往后逐步剔。下面这段脚本就是一次性导出 XAGUSD / XAGEUR / EURUSD 三角套汇的 3000 根 K 线及 40 余类派生特征到 CSV,开 MT5 挂脚本即可跑。 别把正态当圣经 派生特征里写了 Growth 也写了 Log Growth,真实价格序列厚尾偏态常见,线性增长喂进去容易过拟合,对数项往往更耐造。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ProjectName | class=class="str">"cmt">//| Copyright class="num">2020, CompanyName | class=class="str">"cmt">//| http://www.companyname.net | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property script_show_inputs class=class="str">"cmt">//--- File name class="type">class="kw">string file_name = "XAGEUR XAGUSD EURUSD Triangular Exchange Rates.csv"; class=class="str">"cmt">//--- Amount of data requested input class="type">int size = class="num">3000; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Our script execution | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//---Write to file class="type">int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,","); for(class="type">int i=size;i>=class="num">1;i--) { if(i == size) { FileWrite(file_handle,"Time","XAGUSD Open","XAGUSD High","XAGUSD Low","XAGUSD Close","XAGEUR Open","XAGEUR High","XAGEUR Low","XAGEUR Close","EURUSD Open","EURUSD High","EURUSD Low","EURUSD Close","Open Squared","High Squared","Low Squared","Close Squared","Open Cubed","High Cubed","Low Cubed","Close Cubed","Open Squre Root","High Square Root","Low Square Root","Close Square Root","Open Growth","High Growth","Low Grwoth","Close Growth","O / H","O / L","O / C","H / L","Log Open Growth","Log High Grwoth","Log Low Growth","Log Close Grwoth","Sin H / L","Cos O / C"); } else { FileWrite(file_handle, iTime("XAGUSD",PERIOD_CURRENT,i), iOpen("XAGUSD",PERIOD_CURRENT,i),
把银价和欧元交叉盘喂给特征矩阵
上面这段调用链直接把 XAGUSD、XAGEUR、EURUSD 三个品种在当前周期上的 OHLC 拉进同一组特征里,序号 i 就是回看的第几根 K 线。注意 XAGEUR 那一组只取了 Open/High/Low/Close 四个基础价,没有像 XAGUSD 那样继续做平方、立方和平方根变换。 对 XAGUSD 来说,除了原始开高低收,还叠了二次、三次幂和平方根共 12 列:MathPow(...,2)、MathPow(...,3)、MathSqrt(...) 各 4 个。这种非线性展开会让特征维度从 3 个品种的 12 个基础值涨到 28 列,模型可能更容易抓到贵金属对汇率的非线性扰动。 在 MT5 里把这段贴进 iCustom 或脚本的特征构造循环,先把 i 设成 0~199 跑一遍,看 XAGEUR 缺失高阶项时样本矩阵是否还能对齐。外汇和贵金属杠杆高、跳空频繁,这类特征工程只是给后续分析提供输入,实盘信号仍需自行验证风险。
iHigh("XAGUSD",PERIOD_CURRENT,i), iLow("XAGUSD",PERIOD_CURRENT,i), iClose("XAGUSD",PERIOD_CURRENT,i), iOpen("XAGEUR",PERIOD_CURRENT,i), iHigh("XAGEUR",PERIOD_CURRENT,i), iLow("XAGEUR",PERIOD_CURRENT,i), iClose("XAGEUR",PERIOD_CURRENT,i), iOpen("EURUSD",PERIOD_CURRENT,i), iHigh("EURUSD",PERIOD_CURRENT,i), iLow("EURUSD",PERIOD_CURRENT,i), iClose("EURUSD",PERIOD_CURRENT,i), MathPow(iOpen("XAGUSD",PERIOD_CURRENT,i),class="num">2), MathPow(iHigh("XAGUSD",PERIOD_CURRENT,i),class="num">2), MathPow(iLow("XAGUSD",PERIOD_CURRENT,i),class="num">2), MathPow(iClose("XAGUSD",PERIOD_CURRENT,i),class="num">2), MathPow(iOpen("XAGUSD",PERIOD_CURRENT,i),class="num">3), MathPow(iHigh("XAGUSD",PERIOD_CURRENT,i),class="num">3), MathPow(iLow("XAGUSD",PERIOD_CURRENT,i),class="num">3), MathPow(iClose("XAGUSD",PERIOD_CURRENT,i),class="num">3), MathSqrt(iOpen("XAGUSD",PERIOD_CURRENT,i)), MathSqrt(iHigh("XAGUSD",PERIOD_CURRENT,i)), MathSqrt(iLow("XAGUSD",PERIOD_CURRENT,i)), MathSqrt(iClose("XAGUSD",PERIOD_CURRENT,i)),