[策略] 双均线量化交易策略及代码


[策略] 双均线量化交易策略及代码


双均线策略,通过建立m天移动平均线,n天移动平均线,则两条均线必有交点。若m>n,n天平均线“上穿越”m天均线则为买入点,反之为卖出点。该策略基于不同天数均线的交叉点,抓住股票的强势和弱势时刻,进行交易。

均线嘛,都是均线

对于每一个交易日,都可以计算出前N天的移动平均值,然后把这些移动平均值连起来,成为一条线,就叫做N日移动平均线。

比如前5个交易日的收盘价分别为10,9,9,10,11元,那么,5日的移动平均股价为9.8元。同理,如果下一个交易日的收盘价为12,那么在下一次计算移动平均值的时候,需要计算9,9,10,11,12元的平均值,也就是10.2元。

将这平均值连起来,就是均线。

如下图所示,收盘价是蓝线,橙色的线表示5日的移动平均线。



可以看到股票价格的波动比5天均线的波动要大,这是因为5天均线取的是前5个交易日的均值,相当于做了一个平滑。

双均线

顾名思义就是两条天数不同的移动平均线,比如说,一条是5天的移动平均线,另一条是10天的移动平均线。如图,蓝色的是5天均线,黄色的是10天均线。



金叉和死叉

由时间短的均线(如上图蓝色的线)在下方向上穿越时间长一点的均线(如上图黄色的线),为“金叉”,反之为“死叉”。
好了,现在可以构建一个简单的策略:我们认为,双均线金叉的时候,表明股票很强势,反之很弱势,我们就在强势的时候买一个好了,弱势的时候卖掉好了。

说了这么多,下面我们开始实战!

有时候我们也许会根据自己的需要对一些现有的策略进行改造,比如说,我想对均线进行加权呢?我想改造一个指数均线呢?

那我们得自己实现一下均线函数。方法不难,获得前N天的收盘价,然后计算一个算术平均数就可以了,各位读者可以先自己进行尝试,也可以参考回测代码块5(里面有代码和注释)

接下来,如果你想挑战更高难度,可以试一下计算指数移动平均的函数。

指数移动平均和算术平均或者加权平均的主要区别在于指数移动平均需要进行一个迭代,因此这可能是个有点挑战的地方:



其中pi表示前一天的收盘价,且

写出来没?如果没写出来的话可以参考回测代码块3和4(里面有代码和注释)

怎么样?是不是有点挑战性?如果写出这样的子函数,那么在主程序里面只要改一下函数输入参数,就可以轻松的在不同的参数之间来回切换,比较收益。

多股票

除了改为指数移动平均线以外,小编还加入多股票实现方法。

在交易日前,同时对多个股票进行判断,哪只金叉了就买入,死叉了就卖出,按日进行判断和交易。最大可以同时持有N只股票,用于实现这个策略的主函数可以参考回测代码块1和2。

好啦,看看小编选了5个股票的回测结果吧。从2005开始回测至今,你会发现在大部分时间里面,策略的收益比基准收益高,是不是很棒?各位读者可以自己尝试修改参数,看看参数应该如何选取。在这里,小编提供一个同时持有股票书N的选择的小tips,如果风险承受能力强的话可以少选一点,如果想分散风险,可以多选一些股票,但是,分到每个股票的资金最好不要少于两万,因为手续费是有最低限制的。

小结

我们这里是量化课堂的第三部分,主要给大家提供几个大范围的量化思路。本篇为第一讲,举了一个技术指标的例子。比较简单。有关技术指标的文章,百度上一搜一大把,我们平台里也有很多相关的帖子,python公开库ta-lib里也收录了很多指标,大家想进一步研究可以实现一下。也可以结合其他选股策略,或者根据需要制订资金等分的份数或者控制仓位等方法提高策略的收益。

好了,今天对双均线多股票选股策略的介绍就到这里了。



【策略代码】

本帖隐藏的内容

  1. #双均线策略
  2. # 2015-01-01 到 2016-03-08, ¥2000000, 每天
  3.  
  4. '''
  5. ================================================================================
  6. 总体回测前
  7. ================================================================================
  8. '''
  9. #总体回测前要做的事情
  10. def initialize(context):
  11.     set_params()    #1设置策参数
  12.     set_variables() #2设置中间变量
  13.     set_backtest()  #3设置回测条件
  14.    
  15. #1
  16. #设置策略参数
  17. def set_params():
  18.     g.tc=15  # 调仓频率
  19.     g.N=4 #持仓数目
  20.     g.security = ["000001.XSHE","000002.XSHE","000006.XSHE","000007.XSHE","000009.XSHE"]#设置股票池
  21.  
  22. #2
  23. #设置中间变量
  24. def set_variables():
  25.     return
  26.  
  27. #3
  28. #设置回测条件
  29. def set_backtest():
  30.     set_option('use_real_price', True) #用真实价格交易
  31.     log.set_level('order', 'error')
  32.  
  33.  
  34.  
  35.  
  36.  
  37.  
  38. '''
  39. ================================================================================
  40. 每天开盘前
  41. ================================================================================
  42. '''
  43. #每天开盘前要做的事情
  44. def before_trading_start(context):
  45.     set_slip_fee(context)
  46.  
  47. #4
  48. # 根据不同的时间段设置滑点与手续费
  49. def set_slip_fee(context):
  50.     # 将滑点设置为0
  51.     set_slippage(FixedSlippage(0))
  52.     # 根据不同的时间段设置手续费
  53.     dt=context.current_dt
  54.    
  55.     if dt>datetime.datetime(2013,1, 1):
  56.         set_commission(PerTrade(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))
  57.         
  58.     elif dt>datetime.datetime(2011,1, 1):
  59.         set_commission(PerTrade(buy_cost=0.001, sell_cost=0.002, min_cost=5))
  60.             
  61.     elif dt>datetime.datetime(2009,1, 1):
  62.         set_commission(PerTrade(buy_cost=0.002, sell_cost=0.003, min_cost=5))
  63.                
  64.     else:
  65.         set_commission(PerTrade(buy_cost=0.003, sell_cost=0.004, min_cost=5))
  66.  
  67.  
  68.  
  69.  
  70.  
  71.  
  72. '''
  73. ================================================================================
  74. 每天交易时
  75. ================================================================================
  76. '''
  77. def handle_data(context, data):
  78.     # 将总资金等分为g.N份,为每只股票配资
  79.     capital_unit = context.portfolio.portfolio_value/g.N
  80.     toSell = signal_stock_sell(context,data)
  81.     toBuy = signal_stock_buy(context,data)
  82.     # 执行卖出操作以腾出资金
  83.     for i in range(len(g.security)):
  84.         if toSell[i]==1:
  85.             order_target_value(g.security[i],0)
  86.     # 执行买入操作
  87.     for i in range(len(g.security)):
  88.         if toBuy[i]==1:
  89.             order_target_value(g.security[i],capital_unit)  
  90.     if not (1 in toBuy) or (1 in toSell):
  91.         # log.info("今日无操作")
  92.         send_message("今日无操作")
  93.  
  94.  
  95. #5
  96. #获得卖出信号
  97. #输入:context, data
  98. #输出:sell - list
  99. def signal_stock_sell(context,data):
  100.     sell = [0]*len(g.security)
  101.     for i in range(len(g.security)):
  102.     # 算出今天和昨天的两个指数移动均线的值,我们这里假设长线是60天,短线是1天(前一天的收盘价)
  103.         (ema_long_pre,ema_long_now) = get_EMA(g.security[i],60,data)
  104.         (ema_short_pre,ema_short_now) = get_EMA(g.security[i],1,data)
  105.         # 如果短均线从上往下穿越长均线,则为死叉信号,标记卖出
  106.         if ema_short_now < ema_long_now and ema_short_pre > ema_long_pre and context.portfolio.positions[g.security[i]].sellable_amount > 0:
  107.             sell[i]=1
  108.     return sell
  109.         
  110.  
  111. #6
  112. #获得买入信号
  113. #输入:context, data
  114. #输出:buy - list
  115. def signal_stock_buy(context,data):
  116.     buy = [0]*len(g.security)
  117.     for i in range(len(g.security)):
  118.     # 算出今天和昨天的两个指数移动均线的值,我们这里假设长线是60天,短线是1天(前一天的收盘价)
  119.         (ema_long_pre,ema_long_now) = get_EMA(g.security[i],60,data)
  120.         (ema_short_pre,ema_short_now) = get_EMA(g.security[i],1,data)
  121.         # 如果短均线从下往上穿越长均线,则为金叉信号,标记买入
  122.         if ema_short_now > ema_long_now and ema_short_pre < ema_long_pre and context.portfolio.positions[g.security[i]].sellable_amount == 0 :
  123.             buy[i]=1
  124.     return buy
  125.  
  126.  
  127. #7
  128. # 计算移动平均线数据
  129. # 输入:股票代码-字符串,移动平均线天数-整数
  130. # 输出:算术平均值-浮点数
  131. def get_MA(security_code,days):
  132.     # 获得前days天的数据,详见API
  133.     a=attribute_history(security_code, days, '1d', ('close'))
  134.     # 定义一个局部变量sum,用于求和
  135.     sum=0
  136.     # 对前days天的收盘价进行求和
  137.     for i in range(1,days+1):
  138.         sum+=a['close'][-i]
  139.     # 求和之后除以天数就可以的得到算术平均值啦
  140.     return sum/days
  141.  
  142. #8
  143. # 计算指数移动平均线数据
  144. # 输入:股票代码-字符串,移动指数平均线天数-整数,data
  145. # 输出:今天和昨天的移动指数平均数-浮点数
  146. def get_EMA(security_code,days,data):
  147.     # 如果只有一天的话,前一天的收盘价就是移动平均
  148.     if days==1:
  149.     # 获得前两天的收盘价数据,一个作为上一期的移动平均值,后一个作为当期的移动平均值
  150.         t = attribute_history(security_code, 2, '1d', ('close'))
  151.         return t['close'][-2],t['close'][-1]
  152.     else:
  153.     # 如果全局变量g.EMAs不存在的话,创建一个字典类型的变量,用来记录已经计算出来的EMA值
  154.         if 'EMAs' not in dir(g):
  155.             g.EMAs={}
  156.         # 字典的关键字用股票编码和天数连接起来唯一确定,以免不同股票或者不同天数的指数移动平均弄在一起了
  157.         key="%s%d" %(security_code,days)
  158.         # 如果关键字存在,说明之前已经计算过EMA了,直接迭代即可
  159.         if key in g.EMAs:
  160.             #计算alpha值
  161.             alpha=(days-1.0)/(days+1.0)
  162.             # 获得前一天的EMA(这个是保存下来的了)
  163.             EMA_pre=g.EMAs[key]
  164.             # EMA迭代计算
  165.             EMA_now=EMA_pre*alpha+data[security_code].close*(1.0-alpha)
  166.             # 写入新的EMA值
  167.             g.EMAs[key]=EMA_now
  168.             # 给用户返回昨天和今天的两个EMA值
  169.             return (EMA_pre,EMA_now)
  170.         # 如果关键字不存在,说明之前没有计算过这个EMA,因此要初始化
  171.         else:
  172.             # 获得days天的移动平均
  173.             ma=get_MA(security_code,days)
  174.             # 如果滑动平均存在(不返回NaN)的话,那么我们已经有足够数据可以对这个EMA初始化了
  175.             if not(isnan(ma)):
  176.                 g.EMAs[key]=ma
  177.                 # 因为刚刚初始化,所以前一期的EMA还不存在
  178.                 return (float("nan"),ma)
  179.             else:
  180.                 # 移动平均数据不足days天,只好返回NaN值
  181.                 return (float("nan"),float("nan"))
  182.  
  183.  
  184.  
  185.  
  186.  
  187. '''
  188. ================================================================================
  189. 每天收盘后
  190. ================================================================================
  191. '''
  192. # 每日收盘后要做的事情(本策略中不需要)
  193. def after_trading_end(context):
  194.     return
  195.  
  196.  
复制代码

相关资源

发表评论

点 击 提 交