English Русский Deutsch 日本語
preview
使用Python和MQL5进行多品种分析(第三部分):三角汇率

使用Python和MQL5进行多品种分析(第三部分):三角汇率

MetaTrader 5示例 |
585 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

金融市场本质上充满噪音。交易者常常因虚假交易信号而面临不必要的资金回撤,这些信号诱使交易者过早开仓。针对这一问题,已经出现了许多不同的交易策略和准则。这些交易准则本质上都是教导交易者在行动前要等待,转而寻求其他确认信号或额外的强势迹象。

这些规则通常没有明确规定必须在何时找到确认信号,有时可能会让交易者感到遗憾,因为遵循这些经验法则的交易者往往会在大多数最终决定入场的交易中错过良好的入场点位。

显而易见,我们迫切地需要尝试制定交易策略,以尽可能接近实时地衡量市场强度。如果我们尝试利用相互关联的市场,并寻找有意义的可预测模式,那么有可能实现这一目标。我们可以持续关注我们认为可预测的跨市场模式,而不是在每笔感兴趣的交易中潜在地损失点数,从而随着时间的推移累积损失。

对于对跨市场模式这一思想流派可能不太熟悉的读者,我们将简要介绍这一主题,以便您理解为什么有些交易者会告诉您这些跨市场模式通常是可预测的,并且如果您能正确地把握,使得它们有可能成为稳健的策略。

世界上约90%的大宗商品以美元计价。但是,有些大宗商品交易如此频繁,以至于它们常常同时以多种货币报价。 

贵金属类,例如白银,通常同时以美元和欧元报价。今天我们将以白银为例进行说明。您可能会发现,其经纪商同时提供(XAGUSD)和白银兑欧元(XAGEUR)计价的价格报价。

如果我们想交易白银兑美元,并且知道它与白银兑欧元存在的根本性联系,那么如何将这种理解转换成优势,以接受或拒绝提供给我们的交易机会,而无需无限期地等待确认信号?

通过考虑欧元兑美元(EURUSD)之间的现行汇率,我们或许能够形成一种三角交易策略,该策略能够发现白银兑美元、欧元计价的价格以及欧元兑美元的合理汇率之间形成的可预测模式。我们的目标是为读者提供一种对噪音具有稳健性的交易策略,该策略能够帮助您从MetaTrader 5终端接收到的普通市场报价中揭示“隐藏”的市场情绪。 


我们的交易策略概述

在深入探讨我们的交易策略细节之前,让我们首先确保每位读者都对交易对中的基础货币和报价货币有基础性的理解。如果我们以欧元兑美元为例,欧元(EUR)就是我们的基础货币。交易对中左侧的第一个缩写代表基础货币。随着图表上显示的汇率偏离0,基础货币的价值正在增加。因此,如果我们观察欧元兑美元汇率的图表,并且图表趋势向上,这就表明在外汇现货市场上,我们需要卖出更多的美元才能赚取1欧元。

图1:理解交易对中基础货币与报价货币的区别

另一方面,所列出的第二个符号是报价货币。随着图表上显示的汇率越接近0,报价货币的价值在上升。这意味着,我们所需卖出的美元变少,就能获得1欧元。

图2:聚焦报价货币

现在,在确保读者能够理解的情况下,我们可以来探讨特定的交易设置。我们关注的是3个不同的市场,但目标仅交易其中1个。我们的目标是白银兑美元。如果想要了解白银兑美元可能的趋势走向,我们的策略将首先从检查欧元兑美元汇率开始。

如果欧元兑美元汇率正在上升,那么欧元(报价货币)正在升值,而美元(基础货币)正在贬值。可以预期,一种同时以欧元和美元计价的商品,在欧元购买力增强的情况下,以欧元计价可能会变得更便宜,而在美元贬值的情况下,以美元计价可能会变得更贵。

希望这个思维模式能让那些对这种交易策略完全陌生的读者了解,在某些假设下,3个市场可能会如何“镜像”彼此。 

通过同时分析这3个市场的表现,我们本质上可以创建一个策略,该策略可能并不总是需要等待确认信号。如果我们对这些市场相互关联性质的假设成立,那么就应该有一个可行的框架,该框架也可以应用于读者感兴趣交易的其他市场。 

该交易策略的可视化思维导图如图4所示。一般来说,如果欧元兑美元汇率正在下跌,我们会预期以欧元计价的商品会变得更贵,而以美元计价的商品会变得更便宜。我们的策略将检查欧元兑美元的汇率、以欧元计价的白银价格和以美元计价的白银价格,寻找这种特定的模式。如果发现这种跨市场模式,我们将做空白银的美元价格。

图3:可视化做空白银兑美元的规则

当我们想要做多白银的美元价格时,我们以相反的方式应用相同的规则。我们希望观察到外汇市场的强势,支持在欧洲和美国对该商品定价中观察到的价格走势。否则,那些没有跨市场模式支撑的走势可能是脆弱的,很容易逆转。如果我们对市场行为的假设是正确的,那么策略应该是合理的。并且,如果跨市场分析对您的个人投资组合有意义,那么它可能会替代“等待确认”的需求。

图4:可视化做多白银兑美元的交易规则

请注意,实际上,很可能有无数复杂的因素推动白银价格上涨或下跌。我们只是尝试使用更简单的关系来总结这些复杂的联系。


回测期概述

我们将从2023年11月1日到2025年1月1日对我们的策略进行测试。使用我们现有的从2022年11月1日到2023年10月底的剩余数据训练我们的应用。在未来的文章版本中,我们将用计算机可以独立学习的模型,来替代我们对这些市场相互关联性质的简单模型。因此,尽管在本次讨论中我们不会用训练分区,但在未来的讨论中会使用。

截图2

图5:了解本次讨论所涉及的回测时间段


MQL5入门指南

我们的策略要求同时关注三个市场。因此,我们首先创建系统常量,以跟踪持有未平仓头寸的各个市场。这使我们能够轻松在这几个市场之间切换,并比较它们的表现。

//+------------------------------------------------------------------+
//|                                               Baseline Model.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System Constants                                                 |
//+------------------------------------------------------------------+
#define SYMBOL_ONE   "XAGUSD"                                                       //--- Our primary   symbol, the price of Silver in USD
#define SYMBOL_TWO   "XAGEUR"                                                       //--- Our secondary symbol, the price of Silver in EUR
#define SYMBOL_THREE "EURUSD"                                                       //--- Our EURUSD exchange rate.
#define FETCH        24                                                             //--- How many bars of data should we fetch?
#define TF_1         PERIOD_H1                                                      //--- Our intended time frame
#define VOLUME       SymbolInfoDouble(SYMBOL_ONE,SYMBOL_VOLUME_MIN) * 10            //--- Our trading volume

我们将使用MQL5中的向量类型来获取市场数据,并快速地进行转换处理。

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
vector eurusd,xagusd,xageur;
double eurusd_growth,xagusd_growth,xageur_growth,bid,ask;
double sl_width = 3e2 * _Point;

交易库允许我们开立并管理交易头寸,在今天的操作中需要用到。

//+------------------------------------------------------------------+
//| Libraries                                                        |
//+------------------------------------------------------------------+
#include  <Trade\Trade.mqh>
CTrade Trade;

MQL5语言旨在助力我们高效开展交易。你能想到的每一个在市场中发生的事件,都对应着特定的事件映射。接收到新价格就是一个事件。当该事件被触发时,就会调用OnTick()事件处理器。将执行此事件处理器内的函数。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Setup our technical indicators
   setup();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- New prices have been quoted
   new_quotes_received();
  }
//+------------------------------------------------------------------+

每当接收到新的报价时,我们首先需要检查是否已形成了一根完整的K线。如果已经形成新的K线,我们将更新系统变量,并尝试寻找交易机会。 

//+------------------------------------------------------------------+
//| Custom functions                                                 |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Updates system variables accordingly                             |
//+------------------------------------------------------------------+
void new_quotes_received(void)
  {
   static datetime time_stamp;
   datetime time = iTime(SYMBOL_ONE,TF_1,0);

   if(time_stamp != time)
     {
      time_stamp = time;
      update();
     }
  }

当系统首次加载时,我们希望确保所有所需的市场均已被激活且随时可用。

//+------------------------------------------------------------------+
//| Setup our technical indicators and select the symbols we need    |
//+------------------------------------------------------------------+
void setup(void)
  {
//--- Select the symbols we need
   SymbolSelect(SYMBOL_ONE,true);
   SymbolSelect(SYMBOL_TWO,true);
   SymbolSelect(SYMBOL_THREE,true);
  }

一旦形成了新的K线,我们将从跟踪的每个市场中读取更新后的价格。我们可以通过将市场的当前价值除以其过去价值,来衡量市场的增长情况。如果除法运算得出的数值小于1,则表明市场价值正在缩水。否则,说明市场价值正在上升。

//+------------------------------------------------------------------+
//| Update our system setup                                          |
//+------------------------------------------------------------------+
void update(void)
  {
//--- Fetch updated prices
   xagusd.CopyRates(SYMBOL_ONE,TF_1,COPY_RATES_CLOSE,1,FETCH);
   xageur.CopyRates(SYMBOL_TWO,TF_1,COPY_RATES_CLOSE,1,FETCH);
   eurusd.CopyRates(SYMBOL_THREE,TF_1,COPY_RATES_CLOSE,1,FETCH);

//--- Calculate the growth in market prices
   eurusd_growth = eurusd[0] / eurusd[FETCH - 1];
   xageur_growth = xageur[0] / xageur[FETCH - 1];
   xagusd_growth = xagusd[0] / xagusd[FETCH - 1];

//--- Update system variables
   SymbolSelect(SYMBOL_ONE,true);

   bid = SymbolInfoDouble(SYMBOL_ONE,SYMBOL_BID);
   ask = SymbolInfoDouble(SYMBOL_ONE,SYMBOL_ASK);

//--- Check if we need to setup a new position
   if(PositionsTotal() == 0)
      find_setup();

//--- Check if we need to manage our positions
   if(PositionsTotal() > 0)
      manage_setup();

//--- Give feedback on the market growth
   Comment("EURUSD Growth: ",eurusd_growth,"\nXAGEUR Growth: ",xageur_growth,"\nXAGUSD Grwoth: ",xagusd_growth);
  }

我们的交易设置可想象为一种特定状态配置,期望市场彼此间呈现出这种相对状态。我们本质上希望看到,白银兑美元市场的走势分别得到欧元兑美元和白银兑欧元市场走势的支撑。以下所规定的规则,等同于在图4和图5中给出的直观图示。 

//+------------------------------------------------------------------+
//| Find setup                                                       |
//+------------------------------------------------------------------+
void find_setup(void)
  {
   //--- Check if the current market setup matches our expectations for selling
   if((eurusd_growth < 1) && (xageur_growth > 1) && (xagusd_growth < 1))
     {
      Trade.Sell(VOLUME,SYMBOL_ONE,bid,(ask + sl_width),(ask - sl_width),"");
     }

   //--- Check if the current market setup matches our expectations for buying
   if((eurusd_growth > 1) && (xageur_growth < 1) && (xagusd_growth > 1))
     {
      Trade.Buy(VOLUME,SYMBOL_ONE,ask,(bid - sl_width),(bid + sl_width),"");
     }
  }

开仓交易后,将通过追踪止损机制进行监控,以帮助我们锁定利润。

//+------------------------------------------------------------------+
//| Manage setup                                                     |
//+------------------------------------------------------------------+
void manage_setup(void)
  {
   //--- Select our open position
   if(PositionSelect(SYMBOL_ONE))
     {
      double current_sl = PositionGetDouble(POSITION_SL);
      double current_tp = PositionGetDouble(POSITION_TP);

      //--- Buy setup
      if(current_sl < current_tp)
        {
         if((bid - sl_width) > current_sl)
            Trade.PositionModify(SYMBOL_ONE,(bid - sl_width),(bid + sl_width));
        }

      //--- Sell setup
      if(current_sl > current_tp)
        {
         if((ask + sl_width) < current_sl)
            Trade.PositionModify(SYMBOL_ONE,(ask + sl_width),(ask - sl_width));
        }
     }
  }
最后,我们将取消定义先前已定义的系统常数。 
//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef TF_1
#undef SYMBOL_ONE
#undef SYMBOL_TWO
#undef SYMBOL_THREE
#undef VOLUME
#undef FETCH

当我们把系统的所有组件整合在一起后,应用就大功告成了。

//+------------------------------------------------------------------+
//|                                               Baseline Model.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System Constants                                                 |
//+------------------------------------------------------------------+
#define SYMBOL_ONE   "XAGUSD"                                                       //--- Our primary   symbol, the price of Silver in USD
#define SYMBOL_TWO   "XAGEUR"                                                       //--- Our secondary symbol, the price of Silver in EUR
#define SYMBOL_THREE "EURUSD"                                                       //--- Our EURUSD exchange rate.
#define FETCH        24                                                             //--- How many bars of data should we fetch?
#define TF_1         PERIOD_H1                                                      //--- Our intended time frame
#define VOLUME       SymbolInfoDouble(SYMBOL_ONE,SYMBOL_VOLUME_MIN) * 10            //--- Our trading volume

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
vector eurusd,xagusd,xageur;
double eurusd_growth,xagusd_growth,xageur_growth,bid,ask;
double sl_width = 3e2 * _Point;

//+------------------------------------------------------------------+
//| Libraries                                                        |
//+------------------------------------------------------------------+
#include  <Trade\Trade.mqh>
CTrade Trade;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Setup our technical indicators
   setup();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- New prices have been quoted
   new_quotes_received();
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Custom functions                                                 |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Updates system variables accordingly                             |
//+------------------------------------------------------------------+
void new_quotes_received(void)
  {
   static datetime time_stamp;
   datetime time = iTime(SYMBOL_ONE,TF_1,0);

   if(time_stamp != time)
     {
      time_stamp = time;
      update();
     }
  }

//+------------------------------------------------------------------+
//| Setup our technical indicators and select the symbols we need    |
//+------------------------------------------------------------------+
void setup(void)
  {
//--- Select the symbols we need
   SymbolSelect(SYMBOL_ONE,true);
   SymbolSelect(SYMBOL_TWO,true);
   SymbolSelect(SYMBOL_THREE,true);
  }

//+------------------------------------------------------------------+
//| Update our system setup                                          |
//+------------------------------------------------------------------+
void update(void)
  {
//--- Fetch updated prices
   xagusd.CopyRates(SYMBOL_ONE,TF_1,COPY_RATES_CLOSE,1,FETCH);
   xageur.CopyRates(SYMBOL_TWO,TF_1,COPY_RATES_CLOSE,1,FETCH);
   eurusd.CopyRates(SYMBOL_THREE,TF_1,COPY_RATES_CLOSE,1,FETCH);

//--- Calculate the growth in market prices
   eurusd_growth = eurusd[0] / eurusd[FETCH - 1];
   xageur_growth = xageur[0] / xageur[FETCH - 1];
   xagusd_growth = xagusd[0] / xagusd[FETCH - 1];

//--- Update system variables
   SymbolSelect(SYMBOL_ONE,true);

   bid = SymbolInfoDouble(SYMBOL_ONE,SYMBOL_BID);
   ask = SymbolInfoDouble(SYMBOL_ONE,SYMBOL_ASK);

//--- Check if we need to setup a new position
   if(PositionsTotal() == 0)
      find_setup();

//--- Check if we need to manage our positions
   if(PositionsTotal() > 0)
      manage_setup();

//--- Give feedback on the market growth
   Comment("EURUSD Growth: ",eurusd_growth,"\nXAGEUR Growth: ",xageur_growth,"\nXAGUSD Grwoth: ",xagusd_growth);
  }

//+------------------------------------------------------------------+
//| Find setup                                                       |
//+------------------------------------------------------------------+
void find_setup(void)
  {
  
   //--- Check if the current market setup matches our expectations for selling
   if((eurusd_growth < 1) && (xageur_growth > 1) && (xagusd_growth < 1))
     {
      Trade.Sell(VOLUME,SYMBOL_ONE,bid,(ask + sl_width),(ask - sl_width),"");
     }

   //--- Check if the current market setup matches our expectations for buying
   if((eurusd_growth > 1) && (xageur_growth < 1) && (xagusd_growth > 1))
     {
      Trade.Buy(VOLUME,SYMBOL_ONE,ask,(bid - sl_width),(bid + sl_width),"");
     }
  }

//+------------------------------------------------------------------+
//| Manage setup                                                     |
//+------------------------------------------------------------------+
void manage_setup(void)
  {
   //--- Select our open position
   if(PositionSelect(SYMBOL_ONE))
     {
      double current_sl = PositionGetDouble(POSITION_SL);
      double current_tp = PositionGetDouble(POSITION_TP);

      //--- Buy setup
      if(current_sl < current_tp)
        {
         if((bid - sl_width) > current_sl)
            Trade.PositionModify(SYMBOL_ONE,(bid - sl_width),(bid + sl_width));
        }

      //--- Sell setup
      if(current_sl > current_tp)
        {
         if((ask + sl_width) < current_sl)
            Trade.PositionModify(SYMBOL_ONE,(ask + sl_width),(ask - sl_width));
        }
     }
  }

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef TF_1
#undef SYMBOL_ONE
#undef SYMBOL_TWO
#undef SYMBOL_THREE
#undef VOLUME
#undef FETCH

在讨论伊始,我们明确指出,本次回测将从2023年11月1日开始,至2025年1月结束。我们将采用H1(1小时)时间框架进行测试。相较于日线图等更高时间框架,H1时间框架有望为我们提供更多交易机会;同时,与M1(1分钟)等更小时间框架相比,却不会因市场噪音过多而让我们感到困惑。

图6:我们用于白银兑美元策略回测的日期范围

我们的交易条件设置将力求模拟真实交易体验。我们希望基于真实tick数据,为每个tick设置随机延迟。这样就能可靠地模拟过去的市场状况。

图7:采用真实tick数据的交易条件,是我们能获得的最接近真实场景的设置

策略生成的资金曲线呈现出盈利爆发期与长期亏损期交替出现的特征。该策略虽然能够盈利,但当前形式下表现不稳定。鉴于我们的策略在盈利期与亏损期之间持续“振荡”,因此,学会识别这种周期性行为或许有助于我们使策略更加稳定。

图8:我们的交易策略生成的资金曲线

使用我们的交易策略进行回测表明,系统本可以带来更高的潜在利润。我们利用三个市场成功交易的目标已触手可及。我们的交易规则可能需要修订,甚至可能需接受计算机根据数据生成的规则的挑战。这或许是解决我们对算法稳定性担忧的良方。 

图9:我们的交易策略性能的详细分析

提升初始表现

就我们的初始策略而言,仍有大量的改进空间。我们将尝试纠正初始策略的不稳定行为,并消除从盈利期到持续亏损期的周期性振荡。我们简要总结为从交易策略中获得更好的结果而对系统所做出的更改:

拟议改进设计目的
整合技术指标以提供附加确认通过在MetaTrader 5终端中使用技术指标提供的附加确认,我们可以过滤掉大量市场噪音,从而减少决策延迟。
为所跟踪的每个市场构建独立的统计模型拥有市场模型有助于我们预测市场方向或波动性的变化,甚至有助于在随后不确定的时期缩减仓位规模。

为开展工作,我们首先需要获取历史市场报价数据,以便训练模型。在使用统计模型时,我们一开始并不确定哪组输入变量将生成最优模型。因此,一般而言,最优做法是尽可能多地考虑不同的特征,然后再逐步缩小选择范围。 

//+------------------------------------------------------------------+
//|                                                      ProjectName |
//|                                      Copyright 2020, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs

//--- File name
string file_name = "XAGEUR XAGUSD EURUSD Triangular Exchange Rates.csv";

//--- Amount of data requested
input int size = 3000;

//+------------------------------------------------------------------+
//| Our script execution                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
//---Write to file
   int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");

   for(int i=size;i>=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), 
                   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),2),
                   MathPow(iHigh("XAGUSD",PERIOD_CURRENT,i),2),
                   MathPow(iLow("XAGUSD",PERIOD_CURRENT,i),2),
                   MathPow(iClose("XAGUSD",PERIOD_CURRENT,i),2),
                   MathPow(iOpen("XAGUSD",PERIOD_CURRENT,i),3),
                   MathPow(iHigh("XAGUSD",PERIOD_CURRENT,i),3),
                   MathPow(iLow("XAGUSD",PERIOD_CURRENT,i),3),
                   MathPow(iClose("XAGUSD",PERIOD_CURRENT,i),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)),
                   (iOpen("XAGUSD",PERIOD_CURRENT,i) / iOpen("XAGUSD",PERIOD_CURRENT,i+1)),
                   (iHigh("XAGUSD",PERIOD_CURRENT,i) / iHigh("XAGUSD",PERIOD_CURRENT,i+1)),
                   (iLow("XAGUSD",PERIOD_CURRENT,i) / iLow("XAGUSD",PERIOD_CURRENT,i+1)),
                   (iClose("XAGUSD",PERIOD_CURRENT,i) / iClose("XAGUSD",PERIOD_CURRENT,i+1)),
                   (iOpen("XAGUSD",PERIOD_CURRENT,i) / iHigh("XAGUSD",PERIOD_CURRENT,i+1)),
                   (iOpen("XAGUSD",PERIOD_CURRENT,i) / iLow("XAGUSD",PERIOD_CURRENT,i+1)),
                   (iOpen("XAGUSD",PERIOD_CURRENT,i) / iClose("XAGUSD",PERIOD_CURRENT,i+1)),
                   (iHigh("XAGUSD",PERIOD_CURRENT,i) / iLow("XAGUSD",PERIOD_CURRENT,i+1)),
                   MathLog10(iOpen("XAGUSD",PERIOD_CURRENT,i) / iOpen("XAGUSD",PERIOD_CURRENT,i+1)),
                   MathLog10(iHigh("XAGUSD",PERIOD_CURRENT,i) / iHigh("XAGUSD",PERIOD_CURRENT,i+1)),
                   MathLog10(iLow("XAGUSD",PERIOD_CURRENT,i) / iLow("XAGUSD",PERIOD_CURRENT,i+1)),
                   MathLog10(iClose("XAGUSD",PERIOD_CURRENT,i) / iClose("XAGUSD",PERIOD_CURRENT,i+1)),
                   (MathSin(iHigh("XAGUSD",PERIOD_CURRENT,i) / iLow("XAGUSD",PERIOD_CURRENT,i))),
                   (MathCos(iOpen("XAGUSD",PERIOD_CURRENT,i) / iClose("XAGUSD",PERIOD_CURRENT,i)))
                   );
        }
     }
//--- Close the file
   FileClose(file_handle);
  }
//+------------------------------------------------------------------+


在Python中分析我们的数据

一旦收集好训练数据,我们就可以开始构建数据的统计模型了。首先,我们将导入所需的Python库。

#Import libraries we need
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

现在,我们要标注数据。回想一下,我们交易的是H1(1小时)时间框架,因此我们将标注设定为24小时(1个交易日)内的价格水平变化。

#Clean up the data
LOOK_AHEAD = 24
data = pd.read_csv("../XAGEUR XAGUSD EURUSD Triangular Exchange Rates.csv")
data["Target"] = data["XAGUSD Close"].shift(-LOOK_AHEAD) - data["XAGUSD Close"]
data.dropna(inplace=True)
data.reset_index(inplace=True,drop=True)

这是我们共同遵循的整个流程中最为关键的一步。我们的数据最初会包含与回测时间段重叠的市场报价。对于那些希望如实评估自身交易策略价值的从业者而言,这种情况并不理想。

因此,我们将删除所有与预定回测时间段重叠的市场数据。回想一下,在图6中,我们的回测时间段明确是从2023年11月1日开始的;而在图10中,我们的训练数据截止于2023年10月31日。

#Drop the dates corresponding to our backtest
_    = data.iloc[-((24 * 365) - 918):,:]
#Keep the dates before our backtest
data = data.iloc[:-((24 * 365) - 918),:]
data

图10:请确保您用于统计模型的训练数据不会泄露后续信息

如果我们观察白银兑美元和白银兑欧元市场的增长情况,会发现随着时间推移,这两个市场之间的价差会扩大和缩小。这样可能表明这两个市场之间存在套利机会。否则,如果不存在套利机会,那么从一开始到如今,红色和绿色曲线本应完美重叠,而不会出现任何的偏离。显然,实际情况并非如此。两个市场明显会有一段时间出现背离,随后才会得到修正。

plt.title("Comparing XAGUSD & XAGEUR Growth")
plt.plot((data['XAGUSD Close'] / data.loc[0,"XAGUSD Close"]) / (data['XAGUSD Close'].max() - data['XAGUSD Close'].min()),color="red")
plt.plot((data['XAGEUR Close'] / data.loc[0,"XAGEUR Close"]) / (data['XAGEUR Close'].max() - data['XAGEUR Close'].min()),color="green")
plt.ylabel("Commodity Growth")
plt.xlabel("Time")
plt.legend(["XAGUSD","XAGEUR"])
plt.grid()

图11:可视化两个金融市场数据叠加,以揭示可供我们利用的潜在套利机会

标注我们的输入和目标变量 

X = data.iloc[:,1:-1].columns
y = "Target"

准备将梯度提升树(Gradient Boosting Tree)拟合到我们的数据上,并将其导出为ONNX格式。梯度提升树以其能够检测给定数据集中内部交互作用的能力而闻名。我们希望借助其强大的模式识别能力来优化我们的交易策略。

import onnx
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorTypea
from sklearn.ensemble import GradientBoostingRegressor

现在,让我们为每个我们关注的市场分别构建专门的统计模型。希望这能帮助我们过滤掉虚假突破信号。将每个模型拟合到数据上,并导出为ONNX格式,以便我们随后将这三个模型全部导入到MetaTrader 5应用中。 

让我们首先构建一个针对白银兑美元市场模型。

model = GradientBoostingRegressor()
model.fit(data.loc[:,["XAGUSD Open","XAGUSD High","XAGUSD Low","XAGUSD Close"]],data.loc[:,y])
initial_types = [("float_input",FloatTensorType([1,4]))]
xagusd_model_proto = convert_sklearn(model,initial_types=initial_types,target_opset=12)
onnx.save(xagusd_model_proto,"../XAGUSD State Model.onnx")

接下来,将跟进白银兑欧元市场。

model = GradientBoostingRegressor()
model.fit(data.loc[:,["XAGEUR Open","XAGEUR High","XAGEUR Low","XAGEUR Close"]],data.loc[:,"XAGEUR Target"])
initial_types = [("float_input",FloatTensorType([1,4]))]
xageur_model_proto = convert_sklearn(model,initial_types=initial_types,target_opset=12)
onnx.save(xageur_model_proto,"../XAGEUR State Model.onnx")

最后,导出我们的欧元兑美元市场统计模型。

model = GradientBoostingRegressor()
model.fit(data.loc[:,["EURUSD Open","EURUSD High","EURUSD Low","EURUSD Close"]],data.loc[:,"EURUSD Target"])
initial_types = [("float_input",FloatTensorType([1,4]))]
eurusd_model_proto = convert_sklearn(model,initial_types=initial_types,target_opset=12)
onnx.save(eurusd_model_proto,"../EURUSD State Model.onnx")

我们的系统将对三个模型同时支持的市场走势赋予了更高的权重。我们希望首先观察到图3和图4中描述的市场模式正在形成,随后我们的三个模型预测这些模式不会迅速消失,而是会随时间持续存在。这将为我们提供合理的信心依据,相信我们预期将出现的市场走势具备足够的支撑力度。


在MQL5中实现我们的改进

现在,我们可以开始改进原始版本的交易策略了。首先,我们将在策略工具库中添加更多技术指标,以便为计算机提供更精准的指引和趋势识别能力。移动平均线(MA)交叉策略是完成这一任务的优良选择。然而,我们将采用该策略更灵敏的版本,以最大限度地减少交易信号的滞后性。对这一版本的移动平均线交叉策略感兴趣的读者,可点击此处了解更多的信息。

#define XAGUSD_MA_PERIOD 8

将我们刚刚构建的ONNX模型作为系统资源加载。

//+------------------------------------------------------------------+
//| System resources                                                 |
//+------------------------------------------------------------------+
#resource "\\Files\\XAGUSD State Model.onnx" as  uchar xagusd_onnx_buffer[]
#resource "\\Files\\XAGEUR State Model.onnx" as  uchar xageur_onnx_buffer[]
#resource "\\Files\\EURUSD State Model.onnx" as  uchar eurusd_onnx_buffer[]

我们需要引入新的全局变量,这些变量既要对应新增的移动平均线指标,也要涵盖完成ONNX模型所需的各个独立组件。

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
vector  eurusd,xagusd,xageur;
double  eurusd_growth,xagusd_growth,xageur_growth,bid,ask;
double  sl_width = 3e2 * _Point;
int     xagusd_f_ma_handler,xagusd_s_ma_handler;
double  xagusd_f[],xagusd_s[];
vectorf model_output = vectorf::Zeros(1);
long    onnx_model;
vectorf xageur_model_output = vectorf::Zeros(1);
long    xageur_onnx_model;
vectorf eurusd_model_output = vectorf::Zeros(1);
long    eurusd_onnx_model;

我们的一些函数需要进行重构,以满足新的需求。首先,当我们的交易应用程序不再使用时,需要安全释放已分配给ONNX模型以及两个移动平均线指标所占用的资源。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   OnnxRelease(onnx_model);
   OnnxRelease(xageur_onnx_model);
   OnnxRelease(eurusd_onnx_model);
   IndicatorRelease(xagusd_f_ma_handler);
   IndicatorRelease(xagusd_s_ma_handler);
   Print("System deinitialized");
  }

此外,设置流程还必须适配ONNX模型和技术指标的要求。我们必须验证所需指标和模型是否已正确加载并配置。否则将中断初始化流程,并向用户报告具体错误原因。

//+------------------------------------------------------------------+
//| Setup our technical indicators and select the symbols we need    |
//+------------------------------------------------------------------+
bool setup(void)
  {
//--- Select the symbols we need
   SymbolSelect(SYMBOL_ONE,true);
   SymbolSelect(SYMBOL_TWO,true);
   SymbolSelect(SYMBOL_THREE,true);

//--- Setup the moving averages
   xagusd_f_ma_handler = iMA(SYMBOL_ONE,TF_1,XAGUSD_MA_PERIOD,0,MODE_SMA,PRICE_OPEN);
   xagusd_s_ma_handler = iMA(SYMBOL_ONE,TF_1,XAGUSD_MA_PERIOD,0,MODE_SMA,PRICE_CLOSE);

   if((xagusd_f_ma_handler == INVALID_HANDLE) || (xagusd_s_ma_handler == INVALID_HANDLE))
     {
      Comment("Failed to load our technical indicators correctly. ", GetLastError());
      return(false);
     }

//--- Setup our statistical models
   onnx_model        = OnnxCreateFromBuffer(xagusd_onnx_buffer,ONNX_DEFAULT);
   xageur_onnx_model = OnnxCreateFromBuffer(xageur_onnx_buffer,ONNX_DEFAULT);
   eurusd_onnx_model = OnnxCreateFromBuffer(eurusd_onnx_buffer,ONNX_DEFAULT);

   if(onnx_model == INVALID_HANDLE)
     {
      Comment("Failed to create our XAGUSD ONNX model correctly. ",GetLastError());
      return(false);
     }

   if(xageur_onnx_model == INVALID_HANDLE)
     {
      Comment("Failed to create our XAGEUR ONNX model correctly. ",GetLastError());
      return(false);
     }

   if(eurusd_onnx_model == INVALID_HANDLE)
     {
      Comment("Failed to create our EURUSD ONNX model correctly. ",GetLastError());
      return(false);
     }

   ulong input_shape[] = {1,4};
   ulong output_shape[] = {1,1};

   if(!(OnnxSetInputShape(onnx_model,0,input_shape)))
     {
      Comment("Failed to specify XAGUSD model input shape. ",GetLastError());
      return(false);
     }


   if(!(OnnxSetInputShape(xageur_onnx_model,0,input_shape)))
     {
      Comment("Failed to specify XAGEUR model input shape. ",GetLastError());
      return(false);
     }


   if(!(OnnxSetInputShape(eurusd_onnx_model,0,input_shape)))
     {
      Comment("Failed to specify EURUSD model input shape. ",GetLastError());
      return(false);
     }

   if(!(OnnxSetOutputShape(onnx_model,0,output_shape)))
     {
      Comment("Failed to specify XAGUSD model output shape. ",GetLastError());
      return(false);
     }

   if(!(OnnxSetOutputShape(xageur_onnx_model,0,output_shape)))
     {
      Comment("Failed to specify XAGEUR model output shape. ",GetLastError());
      return(false);
     }

   if(!(OnnxSetOutputShape(eurusd_onnx_model,0,output_shape)))
     {
      Comment("Failed to specify EURUSD model output shape. ",GetLastError());
      return(false);
     }

   Print("System initialized succefully");

//--- If we have gotten this far, everything went fine.
   return(true);
  }

我们还需要一个专用函数,用于从ONNX模型中获取预测结果。该函数将首先准备ONNX模型所需的输入数据,然后调用OnnxRun函数从模型中获取预测结果。

//+------------------------------------------------------------------+
//| Fetch a prediction from our model                                |
//+------------------------------------------------------------------+
void model_predict(void)
  {
   vectorf model_inputs =  { (float) iOpen(SYMBOL_ONE,TF_1,1), (float) iClose(SYMBOL_ONE,TF_1,1)};
   OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,model_inputs,model_output);
   Print(StringFormat("Model forecast: %d",model_output));
  }

我们寻找交易机会的流程也必须修订。首先要做的第一步是从我们的模型中获取预测结果,完成后,我们再从移动平均线交叉信号中寻求进一步确认。需要注意的是,我们采用的特定版本移动平均线交叉策略,是将一条移动平均线应用于开盘价,另一条应用于收盘价,且两条移动平均线采用相同的周期。当应用于开盘价的移动平均线位于上方时,我们将其解读为做空信号。如果并非如此,则视为做多信号。

因此,我们的最终条件是,梯度提升回归模型预测的价格走势需与我们观察到的两个信号均保持一致。如果满足此条件,我们将认为这是一个高概率交易机会,并将交易手数翻倍。否则,将采取保守的交易策略。  

//+------------------------------------------------------------------+
//| Find setup                                                       |
//+------------------------------------------------------------------+
void find_setup(void)
  {
   model_predict();

//--- Check if the current market setup matches our expectations for selling
   if((eurusd_growth < 1) && (xageur_growth > 1) && (xagusd_growth <  1))
     {
      if(xagusd_s[0] < xagusd_f[0])
        {
         if(model_output[0] < 0)
           {
            //--- If all our systems align, we have a high probability trade setup
            Trade.Sell(VOLUME * 2,SYMBOL_ONE,bid,(ask + sl_width),(ask - sl_width),"");
           }
         //--- Otherwise, we should trade conservatively
         Trade.Sell(VOLUME,SYMBOL_ONE,bid,(ask + sl_width),(ask - sl_width),"");
        }
     }

//--- Check if the current market setup matches our expectations for buying
   if((eurusd_growth > 1) && (xageur_growth < 1) && (xagusd_growth > 1))
     {
      if(xagusd_s[0] > xagusd_f[0])
        {
         if(model_output[0] > 0)
           {
            Trade.Buy(VOLUME * 2,SYMBOL_ONE,ask,(bid - sl_width),(bid + sl_width),"");
           }

         Trade.Buy(VOLUME,SYMBOL_ONE,ask,(bid - sl_width),(bid + sl_width),"");
        }
     }
  }

现在,让我们重复之前做过的相同测试,但这次将使用经过优化后的交易策略版本。请注意,我们已确保回测日期与用于训练统计模型的日期不存在重叠。 

图12:两次测试均采用相同的测试周期此外,我们的统计模型此前均未接触过这些数据。

正如预期那样,所有测试设置将保持不变,以确保对两种交易策略进行公平比较。

图13:理想情况下,两次测试应保持相同的参数设置

现在,让我们分析得到的测试结果。在初始回测中,我们获得的夏普比率为0.14,而优化后的策略夏普比率达到了1.85。这一显著提升表明,我们在承担可控的额外风险的同时,成功实现了更高的盈利能力。夏普比率较低通常意味着在低收益情况下存在高波动性。 

此外,我们的平均每笔交易亏损从约115美元降至约109美元,而平均每笔交易盈利则从188美元增长至213美元。这对我们而言是有利的。我们的总盈利也从策略第一版的395美元增长至当前版本的1449美元。所有这些改进,都是在交易次数少于手动配置策略版本的情况下实现的。 

图14:我们的交易策略在白银兑美元市场历史表现的详细汇总

我们对系统所做的改进,纠正了初始版本策略中观察到的账户余额不稳定的波动问题。根据回测结果,平均而言,我们的新策略在每笔交易中的盈利倾向高于亏损倾向。这使得新策略的盈亏曲线低谷期较原始高风险版本中累积的亏损深度更浅。

图15:可视化优化后交易策略生成的盈亏曲线

结论

阅读本文后,读者有望掌握一种用于交易相互关联市场的算法策略。读者将了解如何融合自身领域专业知识,实现并精准交易“三角”套利。通过运用统计模型,读者能够更精准地定位所需的具体交易机会。利用特定市场的相互关联特性,我们可实时构建反映真实市场强度的指标。

附件文件名称描述
基准模型我们最初版本的三角交易策略。
第二版优化后且更具盈利能力的交易策略版本。
欧元兑美元状态模型我们针对欧元兑美元市场构建的统计模型。
白银兑欧元状态模型我们针对白银兑欧元市场构建的统计模型。
白银兑美元状态模型我们针对白银兑美元市场构建的统计模型。
三角汇率分析我们用于分析市场数据并构建市场统计模型的Jupyter笔记本电脑。

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/17258

利用 Python 实现价格走势离散方法 利用 Python 实现价格走势离散方法
我们将考察使用 Python + MQL5 来离散价格的方法。在本文中,我将分享我开发 Python 函数库的实践经验,其以多种方式实现柱线形成 — 从经典的交易量和范围柱线,到更奇特的方法,如 Renko 和 Kagi。我们将研究三线突破蜡烛和范围柱线,分析它们的统计数据,并尝试定义如何将价格以离散化表示。
MQL5 交易策略自动化(第十部分):开发趋势盘整动量策略 MQL5 交易策略自动化(第十部分):开发趋势盘整动量策略
在本文中,我们将基于MQL5开发趋势盘整动量策略EA。我们将结合双移动平均线交叉与 RSI 和 CCI 动量过滤器来生成交易信号。我们还将对EA进行回测,以及为提升其在真实交易环境下的表现而进行的优化。
市场模拟(第二部分):跨期订单(二) 市场模拟(第二部分):跨期订单(二)
与上一篇文章中所做的不同,这里我们将使用 EA 交易来测试选择选项。虽然这还不是最终的解决方案,但目前已经足够了。在本文的帮助下,您将能够理解如何实现一种可能的解决方案。
MQL5 交易工具包(第 5 部分):使用仓位函数扩展历史管理 EX5 库 MQL5 交易工具包(第 5 部分):使用仓位函数扩展历史管理 EX5 库
了解如何创建可导出的 EX5 函数,以高效查询和保存历史仓位数据。在本分步指南中,我们将通过开发检索最近平仓的关键属性的模块来扩展历史管理 EX5 库。这些属性包括净利润、交易持续时间、基于点的止损、止盈、利润值以及其他各种重要细节。