请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  量化策略 帖子:3364704 新帖:25

Trademinator 3:交易机器的崛起

K线达人发表于:4 月 17 日 17:02回复(1)

序言

很久以前,有一个论坛 (MQL5) 发布了两篇文章:"《遗传算法 - 很简单!》(作者 joo)及《Dr. Tradelove...》(作者是我)。在第一篇文章中,作者为我们提供了一款优化您所需任何内容的强大工具,其中就包括交易策略 - 一种通过 MQL5 语言实施的遗传算法。

利用此算法,我在第二篇文章中就尝试着根据它来开发一个自优化的 EA 交易。此文以下述任务的公式化描述作为结尾:要创建不仅可以选择某特定交易系统的最佳参数、亦可选择所有已制定策略中最佳策略的 EA 交易(当然是自优化型)。我们来看看是否可能,如果可能,又该怎样做到。

交易机器人相关纪事

首先,我们制定自优化 EA 交易的一般要求。

它要能够(基于历史数据):

  • 从所述对象中选择最佳策略
  • 选择最佳金融工具
  • 选择带杠杆率校正的交易的最佳存款数额
  • 选择选定策略中指标的最佳参数

而且,在真实交易中,它还要能够:

  • 建仓和平仓
  • 选择仓位大小
  • 就是否需要新的优化做出决定

下图所示为提议 EA 交易的原理图。


带有界限的详图,位于随附文件 Scheme_en 中。

要记住,掌握无限是不可能的,我们在 EA 交易逻辑中引入限制。我们认为(重要):

  1. EA 交易要在 新柱出现(在我们选择的任何时间表)时做出交易决定。
  2. 根据但不限于 p.1,该 EA 交易只会根据指标信号关闭交易,而不是利用获利、止损以及相应的追踪止损。
  3. 开始新优化的条件:余额亏损高于该水平初始化期间的预设值。请注意,这只是我个人的条件,你们当中的每一位都可以选择自己特定的条件。
  4. 一个适应度函数模型会根据历史情况进行交易并实现模型化余额最大化,前提是模拟交易余额相对亏损低于某特定预设水平。这里也要注意,这只是我个人的适应度函数,而您可以选择自己的特定适应度函数。
  5. 我们把指标缓冲区待优化参数的数量——但三个常规参数(策略、工具及存款额)除外,限制为 5 个。该限制逻辑上服从于内置技术指标的指标缓冲区最大数量。如果您要对使用带有大量指标缓冲区的自定义指标的策略进行描述,则只需将 main.mq5 文件中的 OptParamCount 变量更改为想要的数量。

现在,要求已规定,限制亦已选定,您可以查看实施所有这些内容的代码了。

我们从一切得以运行的函数开始。

void OnTick()
{
  if(isNewBars()==true)
  {
    trig=false;
    switch(strat)
    {
      case  0: {trig=NeedCloseMA()   ; break;};                      //The number of case strings must be equal to the number of strategies
      case  1: {trig=NeedCloseSAR()  ; break;};
      case  2: {trig=NeedCloseStoch(); break;};
      default: {trig=NeedCloseMA()   ; break;};
    }
    if(trig==true)
    {
      if(GetRelDD()>maxDD)                                           //If a balance drawdown is above the max allowed value:
      {
        GA();                                                        //Call the genetic optimization function
        GetTrainResults();                                           //Get the optimized parameters
        maxBalance=AccountInfoDouble(ACCOUNT_BALANCE);               //Now count the drawdown not from the balance maximum...
                                                                     //...but from the current balance
      }
    }
    switch(strat)
    {
      case  0: {trig=NeedOpenMA()   ; break;};                       //The number of case strings must be equal to the number of strategies
      case  1: {trig=NeedOpenSAR()  ; break;};
      case  2: {trig=NeedOpenStoch(); break;};
      default: {trig=NeedOpenMA()   ; break;};
    }
    Print(TimeToString(TimeCurrent()),";","Main:OnTick:isNewBars(true)",
          ";","strat=",strat);
  }
}

这里是什么?如图中所示,我们查看每个价格变动,而不管是否存在新柱。如果有新柱,那么,在知道现在选定哪个策略的情况下,我们调用其特定函数,以检查是否有未平仓位,必要时予以平仓。假设现在的最佳突破策略为 SAR,则会分别调用 NeedCloseSAR 函数:

bool NeedCloseSAR()
{
  CopyBuffer(SAR,0,0,count,SARBuffer);
  CopyOpen(s,tf,0,count,o);
  Print(TimeToString(TimeCurrent()),";","StrategySAR:NeedCloseSAR",
        ";","SAR[0]=",SARBuffer[0],";","SAR[1]=",SARBuffer[1],";","Open[0]=",o[0],";","Open[1]=",o[1]);
  if((SARBuffer[0]>o[0]&&SARBuffer[1]<o[1])||
     (SARBuffer[0]<o[0]&&SARBuffer[1]>o[1]))
  {
    if(PositionsTotal()>0)
    {
      ClosePosition();
      return(true);
    }
  }
  return(false);
}

任何平仓函数都必须是布尔型,并在平仓时返回 true。这就允许 OnTick() 函数的下一个代码块来决定是否需要新的优化:

    if(trig==true)
    {
      if(GetRelDD()>maxDD)                                           //If the balance drawdown is above the max allowed one:
      {
        GA();                                                        //Call the genetic optimization function
        GetTrainResults();                                           //Get optimized parameters
        maxBalance=AccountInfoDouble(ACCOUNT_BALANCE);                   //Now count the drawdown not from the balance maximum...
                                                                     //...but from the current balance
      }
    }

获取当前余额亏损,并与最大允许亏损进行对比。如果它超过了最大值,则运行新的优化 (GA())。GA() 函数反过来又会调用 EA 交易的核心 - GAModule.mqh 模块的适应度函数 FitnessFunction(int chromos):

void FitnessFunction(int chromos)                                    //A fitness function for the genetic optimizer:...
                                                                     //...selects a strategy, symbol, deposit share,...
                                                                     //...parameters of indicator buffers;...
                                                                     //...you can optimize whatever you need, but...
                                                                     //...watch carefully the number of genes
{
  double ff=0.0;                                                     //The fitness function
  strat=(int)MathRound(Colony[GeneCount-2][chromos]*StratCount);     //GA selects a strategy
 //For EA testing mode use the following code...
  z=(int)MathRound(Colony[GeneCount-1][chromos]*3);                  //GA selects a symbol
  switch(z)
  {
    case  0: {s="EURUSD"; break;};
    case  1: {s="GBPUSD"; break;};
    case  2: {s="USDCHF"; break;};
    case  3: {s="USDJPY"; break;};
    default: {s="EURUSD"; break;};
  }
//..for real mode, comment the previous code and uncomment the following one (symbols are selected in the MarketWatch window)
/*
  z=(int)MathRound(Colony[GeneCount-1][chromos]*(SymbolsTotal(true)-1));//GA selects a symbol
  s=SymbolName(z,true);
*/
  optF=Colony[GeneCount][chromos];                                   //GA selects a deposit share
  switch(strat)
  {
    case  0: {ff=FFMA(   Colony[1][chromos],                         //The number of case strings must be equal to the number of strategies
                         Colony[2][chromos],
                         Colony[3][chromos],
                         Colony[4][chromos],
                         Colony[5][chromos]); break;};
    case  1: {ff=FFSAR(  Colony[1][chromos],
                         Colony[2][chromos],
                         Colony[3][chromos],
                         Colony[4][chromos],
                         Colony[5][chromos]); break;};
    case  2: {ff=FFStoch(Colony[1][chromos],
                         Colony[2][chromos],
                         Colony[3][chromos],
                         Colony[4][chromos],
                         Colony[5][chromos]); break;};
    default: {ff=FFMA(   Colony[1][chromos],
                         Colony[2][chromos],
                         Colony[3][chromos],
                         Colony[4][chromos],
                         Colony[5][chromos]); break;};
  }
  AmountStartsFF++;
  Colony[0][chromos]=ff;
  Print(TimeToString(TimeCurrent()),";","GAModule:FitnessFunction",
        ";","strat=",strat,";","s=",s,";","optF=",optF,
        ";",Colony[1][chromos],";",Colony[2][chromos],";",Colony[3][chromos],";",Colony[4][chromos],";",Colony[5][chromos]);
}

根据当前选定的策略,具体到某特定策略的适应度函数计算模块会被调用。比如说,GA 选择了一个随机指标,则 FFStoch () 会被调用,而指标缓冲区的最优化参数会被传递给它:

double FFStoch(double par1,double par2,double par3,double par4,double par5)
{
  int    b;
  bool   FFtrig=false;                                               //Is there an open position?
  string dir="";                                                     //Direction of the open position
  double OpenPrice;                                                  //Position Open price
  double t=cap;                                                      //Current balance
  double maxt=t;                                                     //Maximum balance
  double aDD=0.0;                                                    //Absolute drawdown
  double rDD=0.000001;                                               //Relative drawdown
  Stoch=iStochastic(s,tf,(int)MathRound(par1*MaxStochPeriod)+1,
                         (int)MathRound(par2*MaxStochPeriod)+1,
                         (int)MathRound(par3*MaxStochPeriod)+1,MODE_SMA,STO_CLOSECLOSE);
  StochTopLimit   =par4*100.0;
  StochBottomLimit=par5*100.0;
  dig=MathPow(10.0,(double)SymbolInfoInteger(s,SYMBOL_DIGITS));
  leverage=AccountInfoInteger(ACCOUNT_LEVERAGE);
  contractSize=SymbolInfoDouble(s,SYMBOL_TRADE_CONTRACT_SIZE);
  b=MathMin(Bars(s,tf)-1-count-MaxMAPeriod,depth);
  for(from=b;from>=1;from--)                                         //Where to start copying of history
  {
    CopyBuffer(Stoch,0,from,count,StochBufferMain);
    CopyBuffer(Stoch,1,from,count,StochBufferSignal);
    if((StochBufferMain[0]>StochBufferSignal[0]&&StochBufferMain[1]<StochBufferSignal[1])||
       (StochBufferMain[0]<StochBufferSignal[0]&&StochBufferMain[1]>StochBufferSignal[1]))
    {
      if(FFtrig==true)
      {
        if(dir=="BUY")
        {
          CopyOpen(s,tf,from,count,o);
          if(t>0) t=t+t*optF*leverage*(o[1]-OpenPrice)*dig/contractSize; else t=0;
          if(t>maxt) {maxt=t; aDD=0;} else if((maxt-t)>aDD) aDD=maxt-t;
          if((maxt>0)&&(aDD/maxt>rDD)) rDD=aDD/maxt;
        }
        if(dir=="SELL")
        {
          CopyOpen(s,tf,from,count,o);
          if(t>0) t=t+t*optF*leverage*(OpenPrice-o[1])*dig/contractSize; else t=0;
          if(t>maxt) {maxt=t; aDD=0;} else if((maxt-t)>aDD) aDD=maxt-t;
          if((maxt>0)&&(aDD/maxt>rDD)) rDD=aDD/maxt;
        }
        FFtrig=false;
      }
   }
    if(StochBufferMain[0]>StochBufferSignal[0]&&StochBufferMain[1]<StochBufferSignal[1]&&StochBufferMain[1]>StochTopLimit)
    {
      CopyOpen(s,tf,from,count,o);
      OpenPrice=o[1];
      dir="SELL";
      FFtrig=true;
    }
    if(StochBufferMain[0]<StochBufferSignal[0]&&StochBufferMain[1]>StochBufferSignal[1]&&StochBufferMain[1]<StochBottomLimit)
    {
      CopyOpen(s,tf,from,count,o);
      OpenPrice=o[1];
      dir="BUY";
      FFtrig=true;
    }
  }
  Print(TimeToString(TimeCurrent()),";","StrategyStoch:FFStoch",
        ";","K=",(int)MathRound(par1*MaxStochPeriod)+1,";","D=",(int)MathRound(par2*MaxStochPeriod)+1,
        ";","Slow=",(int)MathRound(par3*MaxStochPeriod)+1,";","TopLimit=",StochTopLimit,";","BottomLimit=",StochBottomLimit,
        ";","rDD=",rDD,";","Cap=",t);
  if(rDD<=trainDD) return(t); else return(0.0);
}

该随机指标的适应度函数会向主函数返回一个模拟的余额,而主函数则会将其传递给遗传算法。GA 会在某个时间点决定结束优化,而我们则会利用 GetTrainResults() 函数返回策略的最佳当前值(比如移动平均线)、交易品种,基础程序指标缓冲区的存款份额与参数,以及为进一步真实交易创建指标:

void GetTrainResults()                                               //Get the best parameters
{
  strat=(int)MathRound(Chromosome[GeneCount-2]*StratCount);          //Remember the best strategy
//For EA testing mode use the following code...
  z=(int)MathRound(Chromosome[GeneCount-1]*3);                       //Remember the best symbol
  switch(z)
  {
    case  0: {s="EURUSD"; break;};
    case  1: {s="GBPUSD"; break;};
    case  2: {s="USDCHF"; break;};
    case  3: {s="USDJPY"; break;};
    default: {s="EURUSD"; break;};
  }
//...for real mode, comment the previous code and uncomment the following one (symbols are selected in the MarketWatch window)
/*
  z=(int)MathRound(Chromosome[GeneCount-1]*(SymbolsTotal(true)-1));  //Remember the best symbol
  s=SymbolName(z,true);
*/
  optF=Chromosome[GeneCount];                                        //Remember the best deposit share
  switch(strat)
  {
    case  0: {GTRMA(   Chromosome[1],                                //The number of case strings must be equal to the number of strategies
                       Chromosome[2],
                       Chromosome[3],
                       Chromosome[4],
                       Chromosome[5]) ; break;};
    case  1: {GTRSAR(  Chromosome[1],
                       Chromosome[2],
                       Chromosome[3],
                       Chromosome[4],
                       Chromosome[5]) ; break;};
    case  2: {GTRStoch(Chromosome[1],
                       Chromosome[2],
                       Chromosome[3],
                       Chromosome[4],
                       Chromosome[5]) ; break;};
    default: {GTRMA(   Chromosome[1],
                       Chromosome[2],
                       Chromosome[3],
                       Chromosome[4],
                       Chromosome[5]) ; break;};
  }
  Print(TimeToString(TimeCurrent()),";","GAModule:GetTrainResults",
        ";","strat=",strat,";","s=",s,";","optF=",optF,
        ";",Chromosome[1],";",Chromosome[2],";",Chromosome[3],";",Chromosome[4],";",Chromosome[5]);
}

void GTRMA(double par1,double par2,double par3,double par4,double par5)
{
  MAshort=iMA(s,tf,(int)MathRound(par1*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
  MAlong =iMA(s,tf,(int)MathRound(par2*MaxMAPeriod)+1,0,MODE_SMA,PRICE_OPEN);
  CopyBuffer(MAshort,0,from,count,ShortBuffer);
  CopyBuffer(MAlong, 0,from,count,LongBuffer );
  Print(TimeToString(TimeCurrent()),";","StrategyMA:GTRMA",
        ";","MAL=",(int)MathRound(par2*MaxMAPeriod)+1,";","MAS=",(int)MathRound(par1*MaxMAPeriod)+1);
}

现在,它完全返回到了一切都运行 (OnTick()) 的地方:在了解现在什么策略最佳的情况下,它会检查是否到了进入市场的时机:

bool NeedOpenMA()
{
  CopyBuffer(MAshort,0,0,count,ShortBuffer);
  CopyBuffer(MAlong, 0,0,count,LongBuffer );
  Print(TimeToString(TimeCurrent()),";","StrategyMA:NeedOpenMA",
        ";","LB[0]=",LongBuffer[0],";","LB[1]=",LongBuffer[1],";","SB[0]=",ShortBuffer[0],";","SB[1]=",ShortBuffer[1]);
  if(LongBuffer[0]>LongBuffer[1]&&ShortBuffer[0]>LongBuffer[0]&&ShortBuffer[1]<LongBuffer[1])
  {
    request.type=ORDER_TYPE_SELL;
    OpenPosition();
    return(false);
  }
  if(LongBuffer[0]<LongBuffer[1]&&ShortBuffer[0]<LongBuffer[0]&&ShortBuffer[1]>LongBuffer[1])
  {
    request.type=ORDER_TYPE_BUY;
    OpenPosition();
    return(false);
  }
  return(true);
}

此循环关闭。

我们来看看其工作方式。这里有一份 2011 年有关 1 小时时间表的报告,有 4 个主货币对:EURUSD、GBPUSD、USDCHF、USDJPY:

策略测试报告
InstaForex-Server (Build 567)
设置
EA:
交易品种: EURUSD
期段: H1 (2011.01.01 - 2011.12.31)
输入参数: trainDD=0.50000000
maxDD=0.20000000
经纪: InstaForex Companies Group
货币: USD
初始存入: 10 000.00
杠杆率: 1:100
结果
历史质量: 100%
柱: 6197 价格变动: 1321631
总净利润: -538.74 毛利: 3 535.51 净损失: -4 074.25
获利系数: 0.87 预计获利: -89.79 预付款水平: 85.71%
回收系数: -0.08 夏普比率: 0.07 OnTester 结果: 0
余额亏损:
余额亏损绝对值: 4 074.25 余额亏损最大值: 4 074.25 (40.74%) 余额亏损相对值: 40.74% (4 074.25)
市值亏损:
市值亏损绝对值: 4 889.56 市值亏损最大值: 6 690.90 (50.53%) 市值亏损相对值: 50.53% (6 690.90)
总交易: 6 短线交易(获利%) 6 (16.67%) 长线交易(获利%) 0 (0.00%)
总交易: 12 盈利交易(总交易的%): 1 (16.67%) 亏损交易(总交易的%): 5 (83.33%)
最大获利交易: 3 535.51 最大亏损交易: -1 325.40
平均获利交易: 3 535.51 平均亏损交易: -814.85
最大连续盈利: 1 (3 535.51) 最大连续亏损: 5 (-4 074.25)
最大连续盈利(次数): 3 535.51 (1) 最大连续亏损(次数): -4 074.25 (5)
平均连续盈利: 1 平均连续亏损: 5


 

订单
开盘时间 订单 交易品种 类型 交易量 价格 S / L T / P 时间 状态 备注
2011.01.03 01:002USDCHF28.21 / 28.210.93212011.01.03 01:00已填充
2011.01.03 03:003USDCHF28.21 / 28.210.93652011.01.03 03:00已填充
2011.01.03 06:004USDCHF24.47 / 24.470.93522011.01.03 06:00已填充
2011.01.03 09:005USDCHF24.47 / 24.470.93722011.01.03 09:00已填充
2011.01.03 13:006USDCHF22.99 / 22.990.93522011.01.03 13:00已填充
2011.01.03 16:007USDCHF22.99 / 22.990.93752011.01.03 16:00已填充
2011.01.03 18:008USDJPY72.09 / 72.0981.572011.01.03 18:00已填充
2011.01.03 21:009USDJPY72.09 / 72.0981.662011.01.03 21:00已填充
2011.01.04 01:0010USDJPY64.54 / 64.5481.672011.01.04 01:00已填充
2011.01.04 02:0011USDJPY64.54 / 64.5481.782011.01.04 02:00已填充
2011.10.20 21:0012USDCHF56.30 / 56.300.89642011.10.20 21:00已填充
2011.10.21 12:0013USDCHF56.30 / 56.300.89082011.10.21 12:00已填充
成交
时间 成交 交易品种 类型 方向 交易量 价格 订单 手续费 互换 盈利 余额 备注
2011.01.01 00:001余额0.000.0010 000.0010 000.00
2011.01.03 01:002USDCHF28.210.932120.000.000.0010 000.00
2011.01.03 03:003USDCHF28.210.936530.000.00-1 325.408 674.60
2011.01.03 06:004USDCHF24.470.935240.000.000.008 674.60
2011.01.03 09:005USDCHF24.470.937250.000.00-522.198 152.41
2011.01.03 13:006USDCHF22.990.935260.000.000.008 152.41
2011.01.03 16:007USDCHF22.990.937570.000.00-564.027 588.39
2011.01.03 18:008USDJPY72.0981.5780.000.000.007 588.39
2011.01.03 21:009USDJPY72.0981.6690.000.00-794.536 793.86
2011.01.04 01:0010USDJPY64.5481.67100.000.000.006 793.86
2011.01.04 02:0011USDJPY64.5481.78110.000.00-868.115 925.75
2011.10.20 21:0012USDCHF56.300.8964120.000.000.005 925.75
2011.10.21 12:0013USDCHF56.300.8908130.00-3.783 539.299 461.26
0.00 -3.78 -534.96 9 461.26
Copyright 2001-2011, MetaQuotes Software Corp.

我们来解释一下图表上标注的区域(阐释节选自日志分析):

  1. EA 交易启动后,遗传算法选择了突破策略 SAR,条件是基于 USDCHF、交易中的存款份额为 28%。之后,交易至 1 月 3 日晚,亏损余额 20% 以上,并开始重新优化。
  2. 然后,EA 交易决定基于 USDJPY 突破 SAR 交易,但使用的是全部存款 (98%)。自然,它不能长线交易,也因此在 1 月 4 日上午开始其第三次优化。
  3. 这一次,它决定基于 USDCHF 再次针对整个存款交易移动平均线的黄金与死亡交叉。而且,它一直等待第一个死亡交叉,直到 10 月 20 日,将其卖出了最高值,并赢回了它的全部损失。从那之后到年底,该 EA 交易未能发现进入市场的有利时机。

后面还有吗?

还有后面吗?EA 交易的下一代会是什么样?制定策略的 EA 交易以及其中选择最佳策略。而且更进一步,它还可以管理资金、购买更强大的硬件、通道等等...

风险警告:

如此简短的陈述,不足以完整揭示基于预付款的外汇货币交易的所有风险及其它重大内容。您要清楚交易的本质,以及您暴露于风险的程度。鉴于您的经验、目标、金融资源及其它相关环境,您要认真考虑交易是否适合于您。

外汇市场中不仅有利可图,也存在着巨大的风险。就预付款交易而言,相对较小的汇价波动可对交易者的账户造成重大影响,进而导致初始存款以及为维持开仓而存入账户的任何款项全部损失。您不要存入自己不能接受其损失的款额。决定交易之前,请确保您了解所有风险,并考虑了自己的经验水平。如有必要,请寻求独立顾问建议。

许可:

UGAlib.mqh 模块由 Andrey Dik aka joo 根据 BSD 许可研制和发布。

本文随附的 EA 交易与辅助模块,均由作者 Roman Rich 根据 BSD 许可创建和发布。许可文本请见 Lic.txt 文件。

全部回复

0/140

量化课程

    移动端课程