构建MQL5自优化智能交易系统(EA)(第四部分):动态头寸规模调整
电子和数字计算机自20世纪50年代就已问世,但金融市场已存在数个世纪。人类交易者曾在没有先进计算工具的情况下取得成功,这为设计现代交易软件带来了挑战。我们究竟应该充分利用全部计算能力,还是遵循成功的人类交易原则?本文主张在简单性与现代技术之间取得平衡。尽管如今拥有先进工具,但许多交易者在没有MQL5 API等高性能软件的复杂去中心化系统中依然取得了成功。
人类日常决策过程中的许多方面,很难将其具有目的性地传达给计算机。例如,在交易时,常听到有人说:“我对自己的决策很有信心,所以增加了手数”。我们如何指示交易应用程序也这样做,即如果对交易“有信心”,就增加头寸规模?
我希望读者能立刻明白,若不向系统中引入复杂性来衡量计算机“感觉”到的“信心”程度,就无法实现这一目标。一种方法是构建概率模型来量化交易的“信心”水平。在本文中,我们将构建一个简单的逻辑回归模型来衡量交易的信心,使我们的应用程序能够独立调整头寸规模。
我们将重点关注约翰·博林格(John Bollinger)最初提出的布林带策略。我们的目标是优化该策略,解决其不足之处,同时不丢失核心思想。
我们的交易应用程序将致力于实现以下目标:
- 如果模型对交易有信心,则以更大的手数追加一笔交易
- 如果模型对交易信心不足,则以较小的手数进行一笔交易
在我们对原始交易策略的回测中,约翰·博林格提出的策略共进行了493笔交易。在所有进行的交易中,62%的交易是盈利的。尽管盈利交易的比例较为可观,但还不足以产生一个盈利的交易策略。在回测期间,我们亏损了-813美元,夏普比率为-0.33。而我们优化后的算法总共进行了495笔交易,其中63%的交易是盈利的。在相同的时间段内,回测结束时的总利润大幅增加至2427美元,夏普比率稳定在0.74。
本文的目标并非贬低深度神经网络(DNN)或强化学习算法等先进计算工具的强大功能。相反,我对这些技术带来的可能性深感兴奋。然而,必须认识到,单纯为了复杂而增加复杂性,并不一定会带来更好的结果。
我理解作为算法交易社区新成员所面临的挑战。我自己也曾经历过这样的阶段,满怀雄心壮志,却不知从何入手。面对眼前琳琅满目的工具、技术和选项,很容易感到不知所措。
本文旨在为初学者提供一份指引图。通过从简单入手,您可以建立信心,进而独自应对更复杂的问题,并对它们的应用有更深刻的理解。我们通过保留约翰·博林格提出的原始简单交易规则,并补充复杂性以模拟人类决策过程,而非单纯为了复杂而增加复杂性,从而得出了本文中展示的结果。
交易策略概述

图例1:我们的布林带(Bollinger Band)策略运行示意图
我们的交易策略基于约翰·布林格提出的交易信号。该策略的原始规则为:当价格水平突破上轨布林带时卖出,当价格水平跌破下轨布林带时买入。
一般来说,我们可以将这些规则扩展为退出条件。也就是说,每当价格水平出现在最上轨布林带上方时,我们将平掉所有可能存在的多头头寸(即已买入的持仓),同时开立空头头寸(即卖出)。这些规则组合足以构建一个能够自主判断何时开仓和平仓的自动化管理系统。
我们将在M15时间框架下,对英镑兑美元(GBPUSD)货币对在2022年1月1日至2024年12月30日期间的表现进行策略测试。
MQL5入门指南
要在MQL5中开启编程,我们首先要定义系统常量,例如要交易的货币对、使用的手数以及其他不希望用户修改的常量。
//+------------------------------------------------------------------+ //| GBPUSD BB Breakout Benchmark.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 BB_SHIFT 0 // Our bollinger band should not be shifted #define SYMBOL "GBPUSD" // The intended pair for our trading system #define BB_PRICE PRICE_CLOSE // The price our bollinger band should work on #define LOT 0.1 // Our intended lot size
接下来,让我们加载交易库。
//+------------------------------------------------------------------+ //| Dependencies | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> CTrade Trade;
交易策略的某些方面可由终端用户自行控制,例如用于技术指标计算的时间框架,以及布林带指标的计算周期。
//+------------------------------------------------------------------+ //| User inputs | //+------------------------------------------------------------------+ input group "Technical Indicators" input ENUM_TIMEFRAMES TF = PERIOD_M15; // Intended time frame input int BB_PERIOD = 30; // The period for our bollinger bands input double BB_SD = 2.0; // The standard deviation for our bollinger bands
我们还需要定义在整个程序中使用的全局变量。
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Technical indicators | //+------------------------------------------------------------------+ int bb_handler; double bb_u[],bb_m[],bb_l[]; //+------------------------------------------------------------------+ //| System variables | //+------------------------------------------------------------------+ int state; double o,h,l,c,bid,ask;
当我们的交易应用程序首次加载时,我们将调用初始化函数。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Setup our system if(!setup()) return(INIT_FAILED); //--- return(INIT_SUCCEEDED); }
如果我们的应用程序不再使用,我们将释放那些无需使用的技术指标。
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Release resources we no longer need release(); }
在接收到更新的价格信息后,我们需要存储新的价格数据并进行处理,以做出交易决策。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Update our system variables update(); } //+------------------------------------------------------------------+
此函数负责设置我们的技术指标。
//+------------------------------------------------------------------+ //| Custom functions | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Setup our technical indicators and other variables | //+------------------------------------------------------------------+ bool setup(void) { //--- Setup our system bb_handler = iBands(SYMBOL,TF,BB_PERIOD,BB_SHIFT,BB_SD,BB_PRICE); state = 0; //--- Validate our system has been setup correctly if((bb_handler != INVALID_HANDLE) && (Symbol() == SYMBOL)) return(true); //--- Something went wrong! return(false); }
如果我们不再使用交易应用程序,应当释放与所选技术指标相关联的内存。
//+------------------------------------------------------------------+ //| Release the resources we no longer need | //+------------------------------------------------------------------+ void release(void) { //--- Free up system resources for our end user IndicatorRelease(bb_handler); }
当我们从市场接收到更新的价格信息时,会更新全局变量,若当前没有持仓头寸,则检查是否存在有效的交易机会。
//+------------------------------------------------------------------+ //| Update our system variables | //+------------------------------------------------------------------+ void update(void) { static datetime timestamp; datetime current_time = iTime(Symbol(),PERIOD_CURRENT,0); if(timestamp != current_time) { timestamp = current_time; //--- Update our system CopyBuffer(bb_handler,0,1,1,bb_m); CopyBuffer(bb_handler,1,1,1,bb_u); CopyBuffer(bb_handler,2,1,1,bb_l); Comment("U: ",bb_u[0],"\nM: ",bb_m[0],"\nL: ",bb_l[0]); //--- Market prices o = iOpen(SYMBOL,PERIOD_CURRENT,1); c = iClose(SYMBOL,PERIOD_CURRENT,1); h = iHigh(SYMBOL,PERIOD_CURRENT,1); l = iLow(SYMBOL,PERIOD_CURRENT,1); bid = SymbolInfoDouble(SYMBOL,SYMBOL_BID); ask = SymbolInfoDouble(SYMBOL,SYMBOL_ASK); //--- Should we reset our system state? if(PositionsTotal() == 0) { state = 0; find_setup(); } if(PositionsTotal() == 1) { manage_setup(); } } }
我们用于寻找交易入场点的规则,是约翰·布林格最初提出的规则。
//+------------------------------------------------------------------+ //| Find an oppurtunity to trade | //+------------------------------------------------------------------+ void find_setup(void) { //--- Check if we have breached the bollinger bands if(c > bb_u[0]) { Trade.Sell(LOT,SYMBOL,bid); state = -1; return; } if(c < bb_l[0]) { Trade.Buy(LOT,SYMBOL,ask); state = 1; } }
正如我们之前所述,约翰·布林格提出的规则也可用于制定离场规则,从而精准地界定何时平仓。
//+------------------------------------------------------------------+ //| Manage our open trades | //+------------------------------------------------------------------+ void manage_setup(void) { if(((c < bb_l[0]) && (state == -1))||((c > bb_u[0]) && (state == 1))) Trade.PositionClose(SYMBOL); } //+------------------------------------------------------------------+
首先,我们将选择M15作为目标时间框架。这些较低级别的时间框架非常适合像我们这样的超短线交易(剥头皮)策略,此类策略旨在利用金融市场中每日形成的形态模式来获利。我们选择的交易品种是英镑兑美元货币对,测试时间范围将从2022年1月1日开始,至2024年12月30日结束。

图例2:为我们的回测选择时间框架
接下来,我们将对测试参数进行精细调整。选择“随机延迟”选项,可以检验我们的交易系统在市场条件不稳定时的可靠性。此外,我还选择了“基于真实点差的逐点模拟”,因为这能为我们提供最贴近真实市场数据的模拟环境。在这种建模模式下,我们的MetaTrader 5交易终端将获取经纪商当天发送的所有实时点差数据。根据您的网络速度,这一过程可能会比较耗时。不过,最终结果很可能非常接近真实情况。

图例3:为我们的测试选择回测条件
最后,我们将定义一些设置,以控制应用程序的运行行为。请注意,在第二次测试中,我们将通过系统变量保持图例4中选择的设置不变。因此,我们不会让第二个版本的应用程序相对于当前正在测试的这个版本获得不公平的优势。

图例4:本次单次回测中,我们EA的输入参数
我们当前算法生成的盈利曲线具有内在的不稳定性。它不可预测地经历了快速增长和过度亏损的时期。我们当前的交易策略版本,大部分时间都在从亏损期中恢复,而非积累利润和偶尔亏损交易。这远非理想状态。测试结束时,我们的算法只让我们损失了本金。显然,在考虑使用这个算法之前,我们还有更多的工作要做。

图例5:当前原始交易策略版本生成的资金曲线
当我们仔细审视回测结果时,发现系统盈利交易占比可观,所有交易中有63%实现了盈利。但问题在于,我们的盈利金额几乎只有亏损金额的一半。由于我们不希望更改原始交易规则,因此新目标是将平均盈利增长推向更高水平,同时确保亏损交易的增长幅度更小。这一微妙的平衡策略,将为我们带来期望的结果。

图例6:原始交易策略版本生成结果的详细分析
改进初始结果
显而易见,初始结果并不尽如人意。然而,我们知道,发明布林带并提出这些交易规则的人类交易员,无论以何种标准衡量,都是一位成功的交易者。那么,约翰·布林格制定的规则与我们通过算法遵循这些规则所获得的结果之间,究竟存在哪些差距呢?

图例7:布林带发明者约翰·布林格
差距的部分原因可能在于人类对这些规则的应用方式。随着时间的推移,布林格可能逐渐形成了对市场条件的直觉,了解他的策略在何种条件下表现优异,以及在何种条件下容易失败。而我们当前的应用在每笔交易中始终承担相同的风险,对所有交易机会一视同仁。然而,人们可以根据自己对未来的预期和信心水平,灵活地调整风险承受能力。
真人交易员旨在在认为最有可能获利时承担风险,而非僵化地遵循既定规则。我们希望在原始策略的基础上,为计算机增加一层灵活性。实现这一目标,或许能解释预期结果与实际结果之间的差距。因此,我们将引入复杂性,试图让机器更接近专业真人交易员的日常操作,而非仅仅试图直接预测未来价格水平。
我们可以构建一个逻辑回归模型,为应用赋予一种“信心”感。该模型的参数将使用我们从MetaTrader 5终端获取的历史市场数据进行优化。我们原生的MQL5实现意味着,只要某个时间框架上有足够的数据,我们的EA就可以在该时间框架上运行。
逻辑回归模型可能是我们今天能构建的最简单的模型。逻辑模型有多种形式,然而,我们今天要讨论的形式仅适用于对两类进行建模。希望对超过两类进行分类的读者,应参考阅读更多关于逻辑模型的文献。
为了实现期望的改进,并使应用决策过程更接近人类的决策过程,我们将对当前版本的交易系统进行几项重要修改:
| 调整建议 | 设计目的 |
|---|---|
| 新增系统常量 | 我们需要创建新的系统常量,以适配想要构建的概率模型以及所有其他新增的系统组件。 |
| 补充技术分析 | 同时运用两种策略,能够为我们的系统解锁更高的盈利水平。我们还将寻求随机振荡器(Stochastic Oscillator)的确认信号,然后再开仓交易,以此提高交易盈利的概率。 |
| 新增用户输入项 | 为了让用户能够控制系统的新增部分,我们需要创建新的用户输入项,用以控制正在实现的新功能。 |
| 自定义函数修改 | 我们迄今为止构建的自定义函数需要进行修订和扩展,以适配应用程序将要执行的所有新变量和新任务。 |
开始
要开始构建修订后的交易应用程序版本,我们首先需要创建新的系统常量,以确保算法在所有版本中测试结果的一致性。
//+------------------------------------------------------------------+ //| GBPUSD BB Breakout Benchmark.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 BB_SHIFT 0 // Our bollinger band should not be shifted #define SYMBOL "GBPUSD" // The intended pair for our trading system #define BB_PRICE PRICE_CLOSE // The price our bollinger band should work on #define BB_PERIOD 90 // The period for our bollinger bands #define BB_SD 2.0 // The standard deviation for our bollinger bands #define LOT 0.1 // Our intended lot size #define TF PERIOD_M15 // Our intended time frame #define ATR_MULTIPLE 20 // ATR Multiple #define ATR_PERIOD 14 // ATR Period #define K_PERIOD 12 // Stochastic K period #define D_PERIOD 20 // Stochastic D period #define STO_SMOOTHING 12 // Stochastic smoothing #define LOGISTIC_MODEL_PARAMS 5 // Total inputs to our logistic model
此外,我们希望用户能够控制逻辑回归模型的功能。“fetch”(数据获取量)输入项用于确定构建模型时应使用多少数据。需要注意的是,通常来说,用户想要使用的时间框架越高(即时间跨度越大),我们能获取到的数据就越少。另一方面,“look_ahead”(前瞻期)用于确定模型应尝试预测未来多长的时间范围。
//+------------------------------------------------------------------+ //| User inputs | //+------------------------------------------------------------------+ input int fetch = 5; // How many historical bars of data should we fetch? input int look_ahead = 10; // How far ahead into the future should we forecast?
此外,我们需要在应用程序中引入新的全局变量。这些变量将作为新技术指标以及逻辑回归模型各动态组件的句柄来使用。
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Technical indicators | //+------------------------------------------------------------------+ int bb_handler,atr_handler,stoch_handler; double bb_u[],bb_m[],bb_l[],atr[],stoch[]; double logistic_prediction; double learning_rate = 5E-3; vector open_price = vector::Zeros(fetch); vector open_price_old = vector::Zeros(fetch); vector close_price = vector::Zeros(fetch); vector close_price_old = vector::Zeros(fetch); vector high_price = vector::Zeros(fetch); vector high_price_old = vector::Zeros(fetch); vector low_price = vector::Zeros(fetch); vector low_price_old = vector::Zeros(fetch); vector target = vector::Zeros(fetch); vector coef = vector::Zeros(LOGISTIC_MODEL_PARAMS); double max_forecast = 0; double min_forecast = 0; double baseline_forecast = 0;
我们交易系统大多数的其他部分将保持不变,只有少数几个函数需要扩展,还有一些新函数需要定义。首先要编辑的是初始化函数。在准备开始交易前,我们还有额外的步骤需要完成。我们需要设置平均真实波幅(ATR)指标和随机指标模型,此外,还必须定义“setup_logistic_model()”(设置逻辑回归模型)函数。
//+------------------------------------------------------------------+ //| Setup our technical indicators and other variables | //+------------------------------------------------------------------+ bool setup(void) { //--- Setup our system bb_handler = iBands(SYMBOL,TF,BB_PERIOD,BB_SHIFT,BB_SD,BB_PRICE); atr_handler = iATR(SYMBOL,TF,ATR_PERIOD); stoch_handler = iStochastic(SYMBOL,TF,K_PERIOD,D_PERIOD,STO_SMOOTHING,MODE_EMA,STO_LOWHIGH); state = 0; higher_state = 0; setup_logistic_model(); //--- Validate our system has been setup correctly if((bb_handler != INVALID_HANDLE) && (Symbol() == SYMBOL)) return(true); //--- Something went wrong! return(false); }
我们的逻辑回归模型接收一组输入数据,并根据当前自变量x的值,预测目标变量属于默认类别的概率(概率值介于0到1之间)。该模型使用如图例8所示的S型函数(sigmoid function)来计算这些概率。

假设我们想要解决以下问题:“已知一个人的体重和身高,那么其是男性的概率是多少?”在这个示例问题中,“是男性”为默认类别。概率高于0.5意味着认为该人是男性,而概率低于0.5则意味着假定该人为女性。这是逻辑回归模型最简单的版本。逻辑回归模型也有能对超过2个目标进行分类的版本,但我们今天暂不讨论。
图例8中所示的通用S型函数会将任意x值转换为一个介于0到1之间的输出值,如图例9所示。

图例9:S型函数的转换效果可视化
我们可以仔细校准S型函数,使其对训练数据中属于类别1的所有观测值产生接近1的估计值,对训练数据中属于类别0的所有观测值产生接近0的估计值。这种算法被称为最大似然估计。我们可以使用一种更简单的算法——梯度下降法来近似得到这些结果。
在如下提供的代码中,我们首先准备输入数据。我们计算开盘价、最高价、最低价和收盘价的变化,将这些作为模型的输入。之后,我们记录未来价格的相应变化。如果价格下跌,我们将其记录为类别0。类别0是我们的默认类别。预测值高于临界值意味着我们的模型预计未来价格会下跌。同样,预测值低于临界值意味着默认类别不成立,或者就我们的情况而言,模型预计价格会上涨。通常,临界值首选0.5。
在给数据打上标签后,我们将所有模型系数初始化为0,然后用这些不太理想的系数进行第一次预测。每次预测后,我们根据预测值与真实标签之间的差异来修正系数。对获取的每一根K线都重复这一过程。
最后,我之前提到过,临界值通常首选0.5。然而,金融市场向来以“不听话”著称,绝非规规矩矩的环境。对于我们交易者而言,传统方法得出的概率并无多大用处,因此我对传统算法进行了扩展,并进一步校准。
我增加了一个额外的步骤,即先记录模型预测的最大和最小概率,然后计算最优临界值。接着,我们将模型给出的真实预测范围一分为二,以确定临界值。鉴于金融市场可能存在噪声,我们的模型可能难以有效学习,因此需要发挥创意,找到新的模型解读方式。这一动态临界值将有助于模型排除我们的固有的偏见而做出决策。

图例10:动态设置临界值的方法可视化
因此,在我们的案例中,高于动态临界值的概率将被解释为默认类别,意味着模型认为我们应该“卖出”。对于低于动态临界值的预测,情况则相反。
//+------------------------------------------------------------------+ //| Setup our logistic regression model | //+------------------------------------------------------------------+ void setup_logistic_model(void) { open_price.CopyRates(SYMBOL,TF,COPY_RATES_OPEN,(fetch + look_ahead),fetch); open_price_old.CopyRates(SYMBOL,TF,COPY_RATES_OPEN,(fetch + (look_ahead * 2)),fetch); high_price.CopyRates(SYMBOL,TF,COPY_RATES_HIGH,(fetch + look_ahead),fetch); high_price_old.CopyRates(SYMBOL,TF,COPY_RATES_HIGH,(fetch + (look_ahead * 2)),fetch); low_price.CopyRates(SYMBOL,TF,COPY_RATES_LOW,(fetch + look_ahead),fetch); low_price_old.CopyRates(SYMBOL,TF,COPY_RATES_LOW,(fetch + (look_ahead * 2)),fetch); close_price.CopyRates(SYMBOL,TF,COPY_RATES_CLOSE,(fetch + look_ahead),fetch); close_price_old.CopyRates(SYMBOL,TF,COPY_RATES_CLOSE,(fetch + (look_ahead * 2)),fetch); open_price = open_price - open_price_old; high_price = high_price - high_price_old; low_price = low_price - low_price_old; close_price = close_price - close_price_old; CopyBuffer(atr_handler,0,0,fetch,atr); for(int i = (fetch + look_ahead); i > look_ahead; i--) { if(iClose(SYMBOL,TF,i) > iClose(SYMBOL,TF,i - look_ahead)) target[i-look_ahead-1] = 0; if(iClose(SYMBOL,TF,i) < iClose(SYMBOL,TF,i - look_ahead)) target[i-look_ahead-1] = 1; } //Fitting our coefficients coef[0] = 0; coef[1] = 0; coef[2] = 0; coef[3] = 0; coef[4] = 0; for(int i =0; i < fetch; i++) { double prediction = 1 / (1 + MathExp(-(coef[0] + (coef[1] * open_price[i]) + (coef[2] * high_price[i]) + (coef[3] * low_price[i]) + (coef[4] * close_price[i])))); coef[0] = coef[0] + (learning_rate * (target[i] - prediction)) * prediction * (1 - prediction) * 1.0; coef[1] = coef[1] + (learning_rate * (target[i] - prediction)) * prediction * (1 - prediction) * open_price[i]; coef[2] = coef[2] + (learning_rate * (target[i] - prediction)) * prediction * (1 - prediction) * high_price[i]; coef[3] = coef[3] + (learning_rate * (target[i] - prediction)) * prediction * (1 - prediction) * low_price[i]; coef[4] = coef[4] + (learning_rate * (target[i] - prediction)) * prediction * (1 - prediction) * close_price[i]; } for(int i =0; i < fetch; i++) { double prediction = 1 / (1 + MathExp(-(coef[0] + (coef[1] * open_price[i]) + (coef[2] * high_price[i]) + (coef[3] * low_price[i]) + (coef[4] * close_price[i])))); if(i == 0) { max_forecast = prediction; min_forecast = prediction; } max_forecast = (prediction > max_forecast) ? (prediction) : max_forecast; min_forecast = (prediction < min_forecast) ? (prediction) : min_forecast; } baseline_forecast = ((max_forecast + min_forecast) / 2); Print(coef); Print("Baseline: ",baseline_forecast); }
如果我们不需使用EA,那么还需要释放一些额外的技术指标。
//+------------------------------------------------------------------+ //| Release the resources we no longer need | //+------------------------------------------------------------------+ void release(void) { //--- Free up system resources for our end user IndicatorRelease(bb_handler); IndicatorRelease(atr_handler); IndicatorRelease(stoch_handler); }
我们开仓的条件基本保持不变,但若模型预测结果与约翰·布林格提出的交易规则相契合,我们将抓住这一良机加大投入,并仅在此条件下指示应用程序承担更高风险。
//+------------------------------------------------------------------+ //| Find an oppurtunity to trade | //+------------------------------------------------------------------+ void find_setup(void) { double open_input = iOpen(SYMBOL,TF,0) - iOpen(SYMBOL,TF,look_ahead); double close_input = iClose(SYMBOL,TF,0) - iClose(SYMBOL,TF,look_ahead); double high_input = iHigh(SYMBOL,TF,0) - iHigh(SYMBOL,TF,look_ahead); double low_input = iLow(SYMBOL,TF,0) - iLow(SYMBOL,TF,look_ahead); double prediction = 1 / (1 + MathExp(-(coef[0] + (coef[1] * open_input) + (coef[2] * high_input) + (coef[3] * low_input) + (coef[4] * close_input)))); Print("Odds: ",prediction - baseline_forecast); //--- Check if we have breached the bollinger bands if((c > bb_u[0]) && (stoch[0] < 50)) { Trade.Sell(LOT,SYMBOL,bid); state = -1; if(((prediction - baseline_forecast) > 0)) { Trade.Sell((LOT * 2),SYMBOL,bid); Trade.Sell((LOT * 2),SYMBOL,bid); state = -1; } return; } if((c < bb_l[0]) && (stoch[0] > 50)) { Trade.Buy(LOT,SYMBOL,ask); state = 1; if(((prediction - baseline_forecast) < 0)) { Trade.Buy((LOT * 2),SYMBOL,ask); Trade.Buy((LOT * 2),SYMBOL,ask); state = 1; } return; } }
此外,我们希望设置一个止损机制:当交易处于盈利状态时,止损位将随之动态上移;如果交易未盈利,则止损位保持不变。这样能确保我们在盈利时降低风险,这正是人类交易者始终采用的明智策略。
//+------------------------------------------------------------------+ //| Manage our open positions | //+------------------------------------------------------------------+ void manage_setup(void) { if(((c < bb_l[0]) && (state == -1))||((c > bb_u[0]) && (state == 1))) Trade.PositionClose(SYMBOL); //--- Update the stop loss for(int i = PositionsTotal() -1; i >= 0; i--) { string symbol = PositionGetSymbol(i); if(_Symbol == symbol) { double position_size = PositionGetDouble(POSITION_VOLUME); double risk_factor = 1; if(position_size == (LOT * 2)) risk_factor = 2; double atr_stop = atr[0] * ATR_MULTIPLE * risk_factor; ulong ticket = PositionGetInteger(POSITION_TICKET); double position_price = PositionGetDouble(POSITION_PRICE_OPEN); long type = PositionGetInteger(POSITION_TYPE); double current_take_profit = PositionGetDouble(POSITION_TP); double current_stop_loss = PositionGetDouble(POSITION_SL); if(type == POSITION_TYPE_BUY) { double atr_stop_loss = (bid - (atr_stop)); double atr_take_profit = (bid + (atr_stop)); if((current_stop_loss < atr_stop_loss) || (current_stop_loss == 0)) { Trade.PositionModify(ticket,atr_stop_loss,current_take_profit); } } else+ if(type == POSITION_TYPE_SELL) { double atr_stop_loss = (ask + (atr_stop)); double atr_take_profit = (ask - (atr_stop)); if((current_stop_loss > atr_stop_loss) || (current_stop_loss == 0)) { Trade.PositionModify(ticket,atr_stop_loss,current_take_profit); } } } } } //+------------------------------------------------------------------+
控制回测持续时间和时间框架的设置将保持不变,此处我们唯一需要更改的变量是所选用的EA。我们已选定本文前一部分中重构完成的修订版应用程序。请确保在选用新版本应用程序时,其他设置保持不变。

图例11:为第二次回测选择时间框架和周期,以评估所选设置的有效性
与往常一样,请务必选择与经纪商协议相符的杠杆设置。杠杆设置指定错误可能导致您对交易应用程序的盈利能力产生不切实际的预期。更糟糕的是,您可能会发现难以复现回测中获得的结果,特别是当真实账户的杠杆设置与回测中的杠杆设置不匹配时。这是回测过程中常被忽视的错误来源,因此请务必谨慎操作。

图例12:回测对启动回测时所选的设置非常敏感。请确保首次设置即正确无误
现在,我们将定义交易应用程序应获取多少数据来估算逻辑回归模型的参数,以及模型的预测范围。请勿尝试获取超过经纪商提供的数据量。否则,应用程序将无法按预期运行!此外,请设置与您风险承受能力相符的预测范围。
例如,您可能希望训练应用程序预测未来2000步。然而,您必须牢记,在M15时间框架下,预测未来2000步相当于预测约20天。如果您作为交易者,在实际交易时并不会如此长远地预测未来,那么请不要强迫应用程序这样做。请记住,我们的目标是构建一个能够模拟您作为交易者日常操作的应用程序。

图例13:控制交易应用程序和逻辑回归模型行为的参数
现在,我们来到了测试中最具信息量的部分。我们的新系统实现了平均79美元的利润。最初,我们预计平均利润为45美元。因此,当前预期利润(79美元)与先前预期利润(45美元)之间的差额为34美元。这34美元的差额相当于原始预期利润增长了约75%。
同时,我们的新预期损失为-122美元,而初始预期损失为-81美元。差额为41美元,相当于平均损失规模增长了约50%。因此,我们成功地实现了目标!
我们的新设置确保了利润的增长速度高于损失。这也是我们成功地修正夏普比率和预期收益的原因。在不改变算法规则或回测周期的情况下,我们的初始交易策略累计亏损791美元,而新系统累计盈利2274美元。

图例14:理想状态下,我们期望损失的增长率为0,但现实情况并不尽如人意
现在,当观察算法生成的资金曲线时,可以清晰地看到,我们的算法比最初时更加稳定。所有交易策略都会经历回撤期。然而,我们关注的是策略从损失中恢复并最终保留利润的能力。策略若过于规避风险,则可能几乎无法盈利;相反,策略若过于偏好风险,则可能迅速损失所有利润。因此,我们成功地在中间找到了平衡点。

图例15:新版本交易算法生成的资金曲线比初始结果更理想
结论
控制交易应用程序所承担的风险量,对于确保盈利和可持续的交易至关重要。本文展示了如何设计应用程序,以便在应用程序检测到交易有很高盈利可能性时,自动增加交易量。否则,如果预期交易可能无法成功,我们的应用程序将承担尽可能小的风险。这种动态的头寸规模调整对于盈利交易至关重要,因为它确保了我们充分利用每一个机会,并负责任地管理我们的风险水平。通过共同构建一个概率逻辑模型,我们学到了一种可能的方法,可以根据应用程序对当前市场的了解,来选择最佳的头寸规模。 | 附件文件 | 描述 |
|---|---|
| 英镑兑美元布林带突破基准 | 这是我们交易应用程序的初始版本,首次测试时并未实现盈利。 |
| 英镑兑美元布林带突破基准V2 | 基于相同交易规则的优化算法,会在检测到具有较高的获胜概率时,智能地增加我们的持仓头寸规模。 |
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/16925
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
开发回放系统(第 76 部分):新 Chart Trade(三)
在训练中激活神经元的函数:快速收敛的关键?
循环孤雌生殖算法(CPA)
开发多币种 EA 交易(第 20 部分):整理自动项目优化阶段的输送机(一)