English Deutsch 日本語
preview
重构经典策略(第十四部分):高胜率交易形态

重构经典策略(第十四部分):高胜率交易形态

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

在前期讨论中,我们分析了如何通过固定双移动平均线指标的周期参数,重构均线交叉策略。实践表明,这种方式能有效控制交易策略的滞后性。随后我们发现:通过将一条均线应用于开盘价、另一条应用于收盘价,显著提升了策略对价格波动的敏感度。这一创新框架提供了传统策略无法实现的稳定性保障。尚未阅读前文的读者可点击此处回顾。

今天,我们将进一步探索:是否还能从“重新构想”的均线交叉策略中榨出更多收益。通过精准建模该策略与欧元对美元(EURUSD)市场之间的关系,我们有望识别策略在哪些市场情况下如鱼得水,又在哪些市场情况下寸步难行。我们的目标是打造一套会“知难而退”的策略,一旦侦测到不利的市场环境便主动停止交易。


交易策略概述

交易圈普遍认同的原则——交易者应当主动寻求高胜率交易形态。然而,对于"何为高胜率形态"这一本质问题,业界始终缺乏标准化定义。我们如何通过实证方法量化特定交易形态的胜率?询问不同的人,会得到不同的识别与利用方式。

本文提出了一套算法框架,旨在摒弃传统定义方式,构建基于实证数据的数值化形态识别体系,让策略能自动、持续地识别并从交易中获利。 

我们的目标是为特定交易策略与所选交易品种之间的“关系”建模。实现目标的第一步是从MetaTrader 5终端获取完整市场数据与策略全部参数,一并作为输入。
随后,我们将拟合一个统计模型,用于判断策略即将产生的信号是盈利还是亏损。 

模型给出的概率,即为该信号的“成功概率”。借此,我们得以用更科学、实证的方式探讨“高胜率交易形态”,一切基于证据与市场数据。

该框架使交易策略知晓目标,只在预期有利时才入场。我们开始规划范“算法交易”的必要组件:策略先估计自身行为产生的最可能结果,再决定是否投入使用。这可视为以监督方式强化学习意识。


MQL5入门指南

我们当前的任务是学习策略与交易品种之间的关系。为达成目标,我们将获取四大价格(开盘价、最高价、最低价、收盘价)的变动,以及双均线的变化。 

请注意,标注数据需要双均线原始值和收盘价。最终,我们将数据存为10列CSV之后,再通过建模学习策略与该交易品种的关系,同时将这一结果与相同模型的性能进行对比,试图直接预测市场价格。

这样就能告知我们哪个目标更易学习,并且无论哪个目标更易预测,都能指示价格走势。

/+-------------------------------------------------------------------+
//|                                                      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

//--- Define our moving average indicator
#define MA_PERIOD 3                 //--- Moving Average Period
#define MA_TYPE   MODE_SMA          //--- Type of moving average we have
#define  HORIZON 10

//--- Our handlers for our indicators
int ma_handle,ma_o_handle;

//--- Data structures to store the readings from our indicators
double ma_reading[],ma_o_reading[];

//--- File name
string file_name = Symbol() + " Reward Modelling.csv";

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

//+------------------------------------------------------------------+
//| Our script execution                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
   int fetch = size + (HORIZON * 2);
//---Setup our technical indicators
   ma_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_CLOSE);
   ma_o_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_OPEN);

//---Set the values as series
   CopyBuffer(ma_handle,0,0,fetch,ma_reading);
   ArraySetAsSeries(ma_reading,true);
   CopyBuffer(ma_o_handle,0,0,fetch,ma_o_reading);
   ArraySetAsSeries(ma_o_reading,true);

//---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","True Close","True MA C","True MA O","Open","High","Low","Close","MA Close 2","MA Open 2");
        }

      else
        {
         FileWrite(file_handle,
                   iTime(_Symbol,PERIOD_CURRENT,i),
                   iClose(_Symbol,PERIOD_CURRENT,i),
                   ma_reading[i],
                   ma_o_reading[i],
                   iOpen(_Symbol,PERIOD_CURRENT,i)   - iOpen(_Symbol,PERIOD_CURRENT,(i + HORIZON)),
                   iHigh(_Symbol,PERIOD_CURRENT,i)   - iHigh(_Symbol,PERIOD_CURRENT,(i + HORIZON)),
                   iLow(_Symbol,PERIOD_CURRENT,i)    - iLow(_Symbol,PERIOD_CURRENT,(i + HORIZON)),
                   iClose(_Symbol,PERIOD_CURRENT,i)  - iClose(_Symbol,PERIOD_CURRENT,(i + HORIZON)),
                   ma_reading[i] - ma_reading[(i + HORIZON)],
                   ma_o_reading[i] - ma_o_reading[(i + HORIZON)]
                  );
        }
     }
//--- Close the file
   FileClose(file_handle);
  }
//+------------------------------------------------------------------+


分析数据

在开始之前,让我们在Jupyter Notebook中导入市场数据以开展策略性能的量化分析。

import pandas as pd

定义预测利润或亏损的时间范围。

HORIZON = 10

读取数据并新增所需列。第一列Target——传统市场回报,此处为欧元兑美元10日收益。Class列——看涨日为1,其余为0。Action列——策略触发动作:1为买入,-1为卖出。Reward列 ——Target列与Action列的元素乘积,仅当策略与市场同向时为正。

  • Action = -1 且 Target < 0(策略做空且后市下跌)
  • Action = 1 且 Target > 0(策略做多且后市上涨)
其他任何组合均产生负收益,即策略行为错误。如果分类器能预测收益是否为正,我们便拥有了可靠的工具,以便避开不利的市场情况,并且仅在胜算概率最高时入场。末列Trade Signal默认为 0,仅当收益为正时设置为1。初始时,所有五列均设置为0。

data = pd.read_csv("..\EURUSD Reward Modelling.csv")

data['Target'] = 0
data['Class']  = 0
data['Action'] = 0
data['Reward'] = 0
data['Trade Signal'] = 0

现在,我们计算经典目标变量——欧元兑美元收盘价的10日变动率。

data['Target'] = data['True Close'].shift(-HORIZON) - data['True Close']
data.dropna(inplace=True)

我们需要标注类别,以便在图表中快速区分看涨日和看跌日。

data.loc[data['Target'] > 0,'Class'] = 1

现在,让我们来填充策略将采取的具体交易行动。

data.loc[data['True MA C'] > data['True MA O'],'Action'] = 1
data.loc[data['True MA C'] < data['True MA O'],'Action'] = -1

以及该策略因执行这些交易行为所产生的盈亏情况。

data['Reward'] = data['Target'] * data['Action']

现在,让我们来填充交易信号,该信号将指示我们何时应采取行动或保持观望。

data.loc[((data['Target'] < 0) & (data['Action'] == -1)),'Trade Signal'] = 1
data.loc[((data['Target'] > 0) & (data['Action'] == 1)),'Trade Signal'] = 1

我们查阅一下数据。很快就能发现,要准确区分市场走势极具挑战性。橙色圆点代表我们本应采取的交易信号,而蓝色圆点则代表我们本应忽略的信号。在这张散点图中,橙色和蓝色圆点相互重叠,这一数学现象向读者表明:盈利与亏损的交易信号可能在几乎相同的条件下形成。我们的统计模型或许能够识别出人类难以察觉或需要耗费大量精力才能发现的相似性与差异性。

import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

sns.scatterplot(data=data,x='MA Close 2',y='MA Open 2',hue='Trade Signal')
plt.title('Analyzing How Well Moving Average Cross Overs Separate The Market')
plt.grid()

图例1:我们的移动平均线交叉策略似乎难以有效区分价格走势

让我们来快速评估一下,相较于传统目标,我们的新目标是否更容易预测。 

from sklearn.linear_model import RidgeClassifier
from sklearn.model_selection import TimeSeriesSplit,cross_val_score

线性模型在获取可靠且低成本的近似解方面具有独特优势。让我们创建一个时间序列分割对象,以便对线性分类器进行交叉验证。

tscv = TimeSeriesSplit(n_splits=5,gap=HORIZON)
model = RidgeClassifier()
scores = []

首先对传统目标变量进行交叉验证,最后再对我们旨在估算策略盈亏的新目标变量进行交叉验证。由于我们执行的是分类任务,请务必将评分方法设置为"准确率"。

scores.append(np.mean(np.abs(cross_val_score(model,data.iloc[:,4:-5],data.loc[:,'Class'],cv=tscv,scoring='accuracy'))))
scores.append(np.mean(np.abs(cross_val_score(model,data.iloc[:,4:-5],data.loc[:,'Trade Signal'],cv=tscv,scoring='accuracy'))))

通过创建结果K线图可以快速地发现:预测策略盈亏的模型表现优于直接预测市场的模型。直接预测市场的模型准确率低于50%阈值,而我们的新目标变量帮助我们突破了50%基准线——实话实说,其仅以微弱优势超出该界限。

sns.barplot(scores,color='black')
plt.axhline(np.max(scores),linestyle=':',color='red')
plt.title('Forcasting Market Returns vs Forecasting Strategy Reward')
plt.ylabel('Percentage Accuracy Levels %')
plt.xlabel('0: Market Return Forecast | 1: Strategy Profit/Loss Forecast')

图例2:线性模型表明,预测策略产生的盈亏可能比直接预测市场走势更为有利

我们可以精确计算性能提升的百分比,结果显示:通过预测策略与市场的关系(而非直接预测市场),我们的准确率提升了7.6%。

scores = (((scores / scores[0]) - 1) * 100)

scores[1]

7.595993322203687

删除所有与回测期重叠的数据,确保回测结果能真实模拟实际市场环境。

#Drop all the data that overlaps with your backtest period
data = data.iloc[:-((365 * 4) + (30 * 5) + 17),:]
data

图例3:请确保数据框中的日期范围与我们将要进行回测的日期区间无重叠

线性模型让我们确信:预测策略产生的盈亏,可能比直接预测价格对我们更有利。然而,我们将在交易回测中使用更具灵活性的学习器,以确保模型能捕捉尽可能多的有效信息。

from sklearn.ensemble import GradientBoostingRegressor

model = GradientBoostingRegressor()

标注输入特征和目标变量。

X = ['Open','High','Low','Close','MA Close 2','MA Open 2']
y = 'Trade Signal'

拟合模型。

model.fit(data.loc[:,X],data.loc[:,y])

准备将模型导出至ONNX。

import onnx
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType

定义模型的输入大小。

initial_types = [("float input",FloatTensorType([1,6]))]

保存模型。

onnx_proto = convert_sklearn(model,initial_types=initial_types,target_opset=12)
onnx.save(onnx_proto,"EURUSD Reward Model.onnx")


MQL5入门指南

我们现已准备好启动交易应用程序的开发工作。为评估对统计模型训练流程的改进效果,我们将构建两个版本的策略。这两个版本的交易算法将在完全相同的条件下进行回测。让我们先明确这些特定的条件,避免重复说明相同的信息。 

首个关键参数是交易品种——正如前文所述,本案例中我们将交易欧元兑美元货币对。该货币对将在日线图时间框架下进行交易,回测周期为5年,从2020年1月1日至2025年4月1日。这一较长的回测周期将为我们提供充分的数据来检验应用程序的有效性。需要提醒读者的是,如图例3所示,我们已将2019年12月29日之后的所有市场数据从数据集中移除。

图例4:两种交易策略版本回测所需的日期范围

最后,我们将市场模拟条件设置为贴近真实交易的不可预测性。为此,我们选择采用随机延迟,并基于真实tick数据逐笔回放,以获得高度逼真的市场环境复现。 

图例5:用于模拟市场条件的关键参数设置至关重要

现在让我们开始构建可回测的应用程序,以评估对训练流程改进方案的有效性。首先,我们将测量未采用新建模方法(用于识别高胜率交易形态)时策略的表现。从本质上讲,第一个版本的交易策略仅在移动平均线交叉发生时执行主观交易规则。这将为我们提供基准性能,用于与后续提出的收益建模策略进行对比。在开始之前,我们首先导入交易函数库。

//+------------------------------------------------------------------+
//|                                             Reward Modelling.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"

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

定义实用的系统常量,这些常量需与我们在MQL5脚本和Python脚本中使用的其他常量保持一致。

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define MA_PERIOD 3                //--- Moving Average Period
#define MA_TYPE   MODE_SMA         //--- Type of moving average we have
#define HORIZON 10                 //--- How far into the future we should forecast

设置我们的全局变量和技术指标。

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int fetch = HORIZON + 1;

//+------------------------------------------------------------------+
//| Technical indicators                                             |
//+------------------------------------------------------------------+
int ma_handle,ma_o_handle;
double ma_reading[],ma_o_reading[];
int position_timer;

每个事件处理器均已与对应的处理方法绑定,当处理器被触发时将自动调用该方法。这种设计模式可使代码库更易于维护和扩展,如果您后续有新想法,则无需大规模重构。如果直接在事件处理器内部编写逻辑,那么开发者在修改前需逐行检查大量代码以确保不会破坏现有功能;而采用我们的设计模式时,开发者只需在现有方法调用链上方叠加自定义函数,即可轻松扩展应用功能。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   if(!setup())
      return(INIT_FAILED);
//---
   return(INIT_SUCCEEDED);
  }
  
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   release();
  }
  
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   update();
  }
//+------------------------------------------------------------------+

现在我们将逐一分析各个方法。首先需要实现的功能是加载技术指标并重置持仓计时器。该持仓计时器的作用是确保每笔交易的持仓时长严格遵循我们预设的系统常量。

//+------------------------------------------------------------------+
//| Setup the system                                                 |
//+------------------------------------------------------------------+
bool setup(void)
  {
//---Setup our technical indicators
   ma_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_CLOSE);
   ma_o_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_OPEN);
   position_timer = 0;

   return(true);
  }

释放方法仅用于释放未使用的技术指标资源,在MQL5中养成主动清理资源的良好编程习惯至关重要。

//+------------------------------------------------------------------+
//| Release system variables we are no longer using                  |
//+------------------------------------------------------------------+
void release(void)
  {
   IndicatorRelease(ma_handle);
   IndicatorRelease(ma_o_handle);
   return;
  }

更新方法负责获取当前价格数据,并将其复制到技术指标缓冲区中。此外,该方法还会持续跟踪当前持仓的持续时间,确保在预设的时间点准时平仓。

//+------------------------------------------------------------------+
//| Update system parameters                                         |
//+------------------------------------------------------------------+
void update(void)
  {
   //--- Time stamps
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),PERIOD_D1,0);

   //--- We are on a new day
   if(time_stamp != current_time)
     {
      time_stamp = current_time;
      if(PositionsTotal() == 0)
        {
         //--- Copy indicator values
         CopyBuffer(ma_handle,0,0,fetch,ma_reading);
         CopyBuffer(ma_o_handle,0,0,fetch,ma_o_reading);
         //---Set the values as series
         ArraySetAsSeries(ma_reading,true);
         ArraySetAsSeries(ma_o_reading,true);
         find_setup();
         position_timer = 0;
        }

      //--- Forecasts are only valid for HORIZON days
      if(PositionsTotal() > 0)
        {
         position_timer += 1;
        }

      //--- Otherwise close the position
      if(position_timer == HORIZON)
         Trade.PositionClose(Symbol());
     }
     return;
  }

最后,轮到寻找交易信号函数。当移动平均线发生交叉时,系统将识别交易信号。如果开盘价上穿收盘价,将生成做空信号。反之,则生成做多信号。

//+------------------------------------------------------------------+
//| Find a trading oppurtunity                                       |
//+------------------------------------------------------------------+
void find_setup(void)
  {
         double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID) , ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
         double vol = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);

         vector ma_o,ma_c;

         ma_o.CopyIndicatorBuffer(ma_o_handle,0,0,1);
         ma_c.CopyIndicatorBuffer(ma_handle,0,0,1);

         if(ma_o[0] > ma_c[0])
           {
            Trade.Sell(vol,Symbol(),ask,0,0,"");
           }

         if(ma_o[0] < ma_c[0])
           {
            Trade.Buy(vol,Symbol(),bid,0,0,"");
           }
     return;
  }    

请不要忘记释放先前声明的系统常量。

//+------------------------------------------------------------------+
//| Undefine system constatns                                        |
//+------------------------------------------------------------------+
#undef HORIZON
#undef MA_PERIOD
#undef MA_TYPE

我们已经在前文中详细说明了本次回测将使用的参数配置。当前目标是通过纯策略表现验证有效性——即排除我们自主研发的统计建模技术干扰。仅需加载智能交易系统(EA),随后使用图例4和图例5中讨论的参数启动回测。

图例6:建立基准对比

我们手工执行的交易策略虽能盈利,但仅能呈现微利状态。平均单笔盈利与亏损差额仅0.9美元,且仅51%的交易实现盈利,这一表现差强人意。夏普比率为0.62,如果我们对系统进行全面优化或有望提升。

图例7:手工交易策略表现的深度分析

通过分析该版本策略生成的资金曲线与权益曲线,其缺陷立刻呈现出来。策略表现极不稳定且波动剧烈。事实上,在回测进行至第4年(2024年2月)时,账户净值几乎回撤至回测起始水平。从2020年末至2024年的4年间,该策略持续陷入亏损交易循环,始终未能突破困境。作为算法交易者,此类表现完全无法接受。 

图例8:可视化手工交易策略的盈亏曲线


EA性能提升方案

现在,通过赋予程序人类决策思维模式来提升策略表现——即在执行交易前模拟评估行为后果的能力。 

我们首先将ONNX模型作为资源文件,从系统目录导入至应用程序中。 

//+------------------------------------------------------------------+
//| System resources                                                 |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD Reward Model.onnx" as uchar onnx_proto[];

我们还需要几个附加的全局变量来调用模型。主要需要用于表示模型句柄以及控制获取数据量的变量。

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
long onnx_model;
int fetch = HORIZON + 1;

接下来,我们需要在交易信号函数中完成ONNX模型的部署配置。在以下代码示例中,我们刻意省略了应用两个版本之间未变更的代码段。我们只需从缓冲区创建ONNX模型,验证模型并相应地指定其输入和输出大小即可。如果上述任一环节失败,系统将立即终止初始化流程并释放资源。

//+------------------------------------------------------------------+
//| Setup the system                                                 |
//+------------------------------------------------------------------+
bool setup(void)
  {
//---Omitted code that hasn't changed

//--- Setup the ONNX model
   onnx_model = OnnxCreateFromBuffer(onnx_proto,ONNX_DEFAULT);

//--- Validate the ONNX model
   if(onnx_model == INVALID_HANDLE)
     {
      Comment("Failed to create ONNX model");
      return(false);
     }

//--- Register the ONNX model I/O parameters
   ulong input_shape[] = {1,6};
   ulong output_shape[] = {1,1};

   if(!OnnxSetInputShape(onnx_model,0,input_shape))
     {
      Comment("Failed to set input shape");
      return(false);
     }

   if(!OnnxSetOutputShape(onnx_model,0,output_shape))
     {
      Comment("Failed to set output shape");
      return(false);
     }

   return(true);
  }

此外,在应用程序退出时,必须释放所有不再使用的系统资源。

//+------------------------------------------------------------------+
//| Release system variables we are no longer using                  |
//+------------------------------------------------------------------+
void release(void)
  {
//--- Omitted code segments that haven't changed
   OnnxRelease(onnx_model);
   return;
  }

应用程序需先调用交易模型生成预测值,再据此决定是否执行交易。当模型输出值大于0.5时,表明算法判定策略信号具备盈利潜力,允许开仓交易。反之,如果预测值小于等于0.5,则暂停交易直至市场状况有所改善。

//+------------------------------------------------------------------+
//| Find a trading oppurtunity                                       |
//+------------------------------------------------------------------+
void find_setup(void)
  {
//--- Skipped parts of the code base that haven't changed

//--- Prepare the model's inputs
   vectorf model_input(6);
   model_input[0] = (float)(iOpen(_Symbol,PERIOD_CURRENT,0)   - iOpen(_Symbol,PERIOD_CURRENT,(HORIZON)));
   model_input[1] = (float)(iHigh(_Symbol,PERIOD_CURRENT,0)   - iHigh(_Symbol,PERIOD_CURRENT,(HORIZON)));
   model_input[2] = (float)(iLow(_Symbol,PERIOD_CURRENT,0)    - iLow(_Symbol,PERIOD_CURRENT,(HORIZON)));
   model_input[3] = (float)(iClose(_Symbol,PERIOD_CURRENT,0)  - iClose(_Symbol,PERIOD_CURRENT,(HORIZON)));
   model_input[4] = (float)(ma_reading[0] - ma_reading[(HORIZON)]);
   model_input[5] = (float)(ma_o_reading[0] - ma_o_reading[(HORIZON)]);

//--- Prepare the model's output
   vectorf model_output(1);

//--- We failed to run the model
   if(!OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,model_input,model_output))
      Comment("Failed to obtain a forecast");

//--- Everything went fine
   else
     {
      Comment("Forecast: ",model_output[0]);

      //--- Our model forecasts that our strategy is likely to be profitable
      if(model_output[0] > 0.5)
        {
         if(ma_o[0] > ma_c[0])
           {
            Trade.Sell(vol,Symbol(),ask,0,0,"");
           }

         if(ma_o[0] < ma_c[0])
           {
            Trade.Buy(vol,Symbol(),bid,0,0,"");
           }
        }
     }
   return;
  }

使用图例4和图例5中指定的参数配置,在回测环境中运行应用程序,以便公平对比两种策略的性能表现。

回测输入

图例9:基于盈亏建模优化版交易策略的二次回测结果

夏普比率从手工策略基准的0.62跃升至当前版本的1.07,增幅达到72%。采用新算法定义的"高胜率交易形态"策略后,净利润从117.13增加至162.75,提升38%。亏损交易比例从48.78%降至40.48%,减少17个百分点。最后,总交易次数从164次降至126次,这就意味着新算法仅用76%的交易量就实现了38%的利润增长,表明我们的效率有了显著提升——在承担更少风险的同时获得更高的回报。

图例10:新版交易策略性能全景分析

为了便于对比分析,我将优化版策略的资金曲线与原始版本曲线同框呈现(原始版曲线置于下方),读者无需反复滚动页面即可直观比较两者差异。由此可见,在首个回测年度尚未结束时,原始策略已濒临盈亏平衡点,而新优化版策略在该阶段仍保持稳健收益。 

值得注意的是,两种策略在2022年5月至12月期间均表现疲软。这可能反映出该时段市场存在系统性风险,我们需要通过更有效的方式加以应对。

图例11:具备行动后果评估能力的新优化版策略盈亏曲线

图例12:为便于与新版结果对比,重制原始策略资金曲线


结论

阅读完本文后,读者将掌握一种创新方法——通过监督式统计模型实现算法交易的系统化路径。现已具备构建私有交易策略与市场动态关联模型核心能力的读者,可以精准捕捉两者间的非线性关系。相较于直接手工预测市场的传统方法(本文已论证其局限性),该方法为读者构建了差异化的竞争力。 

文件名  文件描述
Reward Modelling Benchmark.mq5 这是我们交易策略的传统版本,交易前无后果评估。其对所有交易机会采取均等权重分配,并默认每笔交易必须盈利。
Reward Modelling.mq5 这是我们交易策略的优化改进版本,在交易前尝试评估行为后果。
EURUSD Reward Model.onnx ONNX统计模型用于评估策略生成信号的盈利概率。
Reward Modelling.ipynb Jupyter Notebook用于分析历史市场数据并拟合统计模型。

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

交易中的神经网络:层次化双塔变换器(Hidformer) 交易中的神经网络:层次化双塔变换器(Hidformer)
我们邀请您来领略层次化双塔变换器(Hidmer)框架,其专为时间序列预测和数据分析而开发。框架作者提出了若干变换器架构改进方案,其成果提高了预测准确性、并降低了计算资源消耗。
使用MQL5经济日历进行交易(第七部分):基于资源型新闻事件分析的策略测试准备 使用MQL5经济日历进行交易(第七部分):基于资源型新闻事件分析的策略测试准备
在本文中,我们通过将经济日历数据作为非实盘分析资源嵌入到MQL5交易系统中,为策略测试做好准备。我们实现了按时间、货币和影响程度加载和筛选事件的功能,并在策略测试器中验证其有效性。这使得基于新闻事件的策略能够进行高效的回测。
交易中的资本管理和带有数据库的交易者家庭会计程序 交易中的资本管理和带有数据库的交易者家庭会计程序
交易者如何管理资金?交易者和投资者如何跟踪支出、收入、资产和负债?我不仅要向你介绍会计软件;我将向您展示一个工具,它可能会成为您在波涛汹涌的交易海洋中可靠的金融导航器。
从新手到专家:对K线进行编程 从新手到专家:对K线进行编程
在本文中,我们将迈出 MQL5 编程的第一步,即使是完全零基础的初学者也能上手。我们将向您展示,如何将熟悉的 K线形态 转换为一个功能完备的自定义指标。K线形态之所以有价值,是因为它们反映了真实的价格行为,并预示着市场的转变。与其手动扫描图表——这种方法容易出错且效率低下——我们将讨论如何通过一个指标来自动化这个过程,该指标会自动识别并标记出这些形态。在此过程中,我们将探讨一些关键概念,例如索引、时间序列、平均真实波幅(用于在多变的市场波动性中提高准确性),以及如何开发一个可自定义、可复用的 K线形态库,以便在未来的项目中使用。