剥头皮(scalp)交易策略代码与回测

QuantPoint3周前MT5策略17

本章我们来介绍一个简单的剥头皮策略,本策略是自己理解了剥头皮策略的核心之后,自己写出的第一个交易策略。目前并未经过严格实测,因为本人目前采用的是趋势跟踪策略。但此策略是博主mt5自动化交易的开端,所以有着极其重要的地位。


下面是此剥头皮策略的核心思想:找到动能强劲的k线,在下一个k线小幅回调后,沿着强劲k线挂突破单,止盈止损采用固定点数,在沿着有利的方向运行一段距离后,启用移动跟踪止损。

  • 什么是动能强劲的k线:1.k线足够大;2.如果是阳线,则其上影线很短;如果是阴线则反之;

  • 什么是小幅回调:如果强劲k线是阳线,下一根k线回调到强劲k线高点以下一定距离(如果10pips);

  • 什么是移动追踪止损:如果做多,价格沿着多的方向走出30pips,则激活移动止损。从此之后,止损一直保持距离价格15pips的距离


下面是剥头皮策略实现代码

//+------------------------------------------------------------------+
//|   定义输入参数                                                     |
//+------------------------------------------------------------------+
input group "====单k线突破scalp策略======"
input double tradelots=0.01;                            //交易手数
input ENUM_TIMEFRAMES tradeFrame = PERIOD_M5;    //交易周期
input int InitSl = 150;                             //初始止损
input int InitTp = 150;                            //初始止盈
input int TriggerPoints = 30;                      //移动止损激活pips,0=不启用
input int TriggerSl = 15;                           //止损点数
input int pullbackPoints = 10;                     //回调多少点以上再入场
input int Magic = 223456;

input group "====过滤器====="
input double minBarPoints = 90;                   //待突破k线最小pips,0=不启用
input int maxPinbar = 5;                           //做多上影线不能超过k线的几分之一,0=不启用
input int startMiniutes = 600;                     //开始交易分钟数
input int durationMiniutes = 240;                  //交易持续时间,0=不启用时间过滤

上述代码参数均有说明,我们增加了一个时间过滤器,在不活跃的时段,禁止交易。


//+------------------------------------------------------------------+
//| 全局变量                                                          |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
CTrade trade;
CPositionInfo pos;
COrderInfo ord;
MqlRates g_bar;   //保存信号k线信息

定义一些交易中的全局变量,以便程序中使用,g_bar存储强势k线的信息。其它几个变量是操作订单的变量。


在OnInit函数中,我们设置好交易的魔幻数字

trade.SetExpertMagicNumber(Magic);
trade.SetDeviationInPoints(5);


在OnTick函数中,实现整个逻辑

static int signal = 0; //信号:1-做多;-1:做空
   
   static datetime prevTime = 0;
   datetime currentTime = iTime(_Symbol, tradeFrame, 0);
   
   if(TriggerPoints>0) {
      trainStop();
   }
   
   int total = 0;
   for(int i=PositionsTotal()-1; i>=0; i--) {
      if(!pos.SelectByIndex(i) || pos.Symbol()!=_Symbol || pos.Magic()!=Magic) continue;
      total++;
   }
   
   if(total>0) {
      return;
   }
   
   if(signal != 0) {
      double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      if(ask-bid>TriggerSl) return;
      if(g_bar.close>g_bar.open) {
         if(g_bar.high-ask>pullbackPoints*_Point) {
            double entry = g_bar.high+_Point;
            double sl = entry-InitSl*_Point;
            double tp = entry+InitTp*_Point;
            trade.BuyStop(tradelots, entry, _Symbol, sl, tp, ORDER_TIME_SPECIFIED, currentTime+PeriodSeconds(tradeFrame)-1, __FILE__);
            signal = 0;
         }
      }
      if(g_bar.close<g_bar.open) {
         if(bid-g_bar.low>pullbackPoints*_Point) {
            double entry = g_bar.low+_Point;
            double sl = entry+InitSl*_Point;
            double tp = entry-InitTp*_Point;
            trade.SellStop(tradelots, entry, _Symbol, sl, tp, ORDER_TIME_SPECIFIED, currentTime+PeriodSeconds(tradeFrame)-1, __FILE__);
            signal = 0;
         }
      }
   }

   
   if(prevTime==currentTime) return;
   prevTime = currentTime;
   
   signal = 0;
   
   if(durationMiniutes>0) {   //检查是否在交易时间内
      MqlDateTime dt;
      TimeToStruct(currentTime, dt);
      if(dt.min+dt.hour*60<startMiniutes || dt.min+dt.hour*60>startMiniutes+durationMiniutes) return;
   }
   
   
   //获取前一根k线信息
   MqlRates rates[1];
   if(CopyRates(_Symbol, tradeFrame, 1, 1, rates)!=1) return;
   
   //检查是否满足信号k线要求
   if(minBarPoints>0 && rates[0].high-rates[0].low<minBarPoints*_Point) return;
   
   if(rates[0].open<rates[0].close) {//阳线
      if(maxPinbar>0 && (rates[0].high-rates[0].close)*maxPinbar>rates[0].high-rates[0].low) return;
      g_bar = rates[0];
      signal = 1;
   } else if(rates[0].open>rates[0].close) {
      if(maxPinbar>0 && (rates[0].close-rates[0].low)*maxPinbar>rates[0].high-rates[0].low) return;
      g_bar = rates[0];
      signal = -1;
   }

有几个特别说明的地方:

  1. 只在当前k结束后,下一根k线开始才去检查是否是强劲k线;

  2. 同时只有一个持仓订单

  3. 进行了交易时间过滤

  4. 移动止损在每个tick上检查,以便可以及时追踪止损


下面是ontick中调用的移动止损追踪函数:

void trainStop() {
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double tp,sl;
   for(int i=PositionsTotal()-1;i>=0; i--) {
      if(pos.SelectByIndex(i)) {
         if(pos.Magic()!=Magic || pos.Symbol()!=_Symbol) return;
         ulong ticket = pos.Ticket();
         
         if(pos.PositionType()==POSITION_TYPE_BUY) {
            if(bid-pos.PriceOpen()>TriggerPoints*_Point) {
               tp = NormalizeDouble(pos.TakeProfit(), _Digits);
               sl = NormalizeDouble(bid-(TriggerSl*_Point), _Digits);
               if(sl>pos.StopLoss() && sl>0) {
                  trade.PositionModify(ticket, sl, tp);
               }
            }
         }
         
         if(pos.PositionType()==POSITION_TYPE_SELL) {
            if(ask<pos.PriceOpen()-(TriggerPoints*_Point)) {
               tp = NormalizeDouble(pos.TakeProfit(), _Digits);
               sl = NormalizeDouble(ask+(TriggerSl*_Point),_Digits);
               if(sl<pos.StopLoss() && sl>0) {
                  trade.PositionModify(ticket, sl, tp);
               }
            }
         }
      }
   }
}



下面是回测的参数与结果:

image.png

image.png


image.png


从上述回测结果可以看出,剥头皮策略的特点

  1. 胜率很高,回测中达到了89%;

  2. 盈亏比差,平均盈利0.79平均亏损4.10

  3. 交易频次高,每天进行数次交易

图中靠着大量交易,8个月利润达到了惊人的150%。但这个只是回测数据,真实情况如何,还需要在实盘中测试。

另外,上述经纪商的点差很小,且没有佣金。如果你换一个点差较大且有佣金的经纪商,可能没有利润,甚至最终结果是亏损也不奇怪。



如果需要完整代码的,请联系本人免费索取。为避免纠纷,这里只介绍主要逻辑。

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。