text: # encoding: UTF-8
  
  """
  基于King Keltner通道的交易策略，适合用在股指上，
  展示了OCO委托和5分钟K线聚合的方法。
  
  注意事项：
  1. 作者不对交易盈利做任何保证，策略代码仅供参考
  2. 本策略需要用到talib，没有安装的用户请先参考www.vnpy.org上的教程安装
  3. 将IF0000_1min.csv用ctaHistoryData.py导入MongoDB后，直接运行本文件即可回测策略
  """
  
  from __future__ import division
  
  from trader.vtObject import VtBarData
  from language.chinese.vt_constant import EMPTY_STRING
  from app.ctaStrategy.ctaTemplate import (CtaTemplate,
                                                  BarManager,
                                                  ArrayManager)
  
  
  ########################################################################
  class KkStrategy(CtaTemplate):
      """基于King Keltner通道的交易策略"""
      className = 'KkStrategy'
      author = u'用Python的交易员'
  
      # 策略参数
      kkLength = 11           # 计算通道中值的窗口数
      kkDev = 1.6             # 计算通道宽度的偏差
      trailingPrcnt = 0.8     # 移动止损
      initDays = 10           # 初始化数据所用的天数
      fixedSize = 1           # 每次交易的数量
  
      # 策略变量
      kkUp = 0                            # KK通道上轨
      kkDown = 0                          # KK通道下轨
      intraTradeHigh = 0                  # 持仓期内的最高点
      intraTradeLow = 0                   # 持仓期内的最低点
  
      buyOrderIDList = []                 # OCO委托买入开仓的委托号
      shortOrderIDList = []               # OCO委托卖出开仓的委托号
      orderList = []                      # 保存委托代码的列表
  
      # 参数列表，保存了参数的名称
      paramList = ['name',
                   'className',
                   'author',
                   'vtSymbol',
                   'kkLength',
                   'kkDev']
  
      # 变量列表，保存了变量的名称
      varList = ['inited',
                 'trading',
                 'pos',
                 'kkUp',
                 'kkDown']
  
      #----------------------------------------------------------------------
      def __init__(self, ctaEngine, setting):
          """Constructor"""
          super(KkStrategy, self).__init__(ctaEngine, setting)
  
          self.bm = BarManager(self.onBar, 5, self.onFiveBar)     # 创建K线合成器对象
          self.am = ArrayManager()
  
      #----------------------------------------------------------------------
      def onInit(self):
          """初始化策略（必须由用户继承实现）"""
          self.writeCtaLog(u'%s策略初始化' % self.name)
  
          # 载入历史数据，并采用回放计算的方式初始化策略数值
          initData = self.loadBar(self.initDays)
          for bar in initData:
              self.onBar(bar)
  
          self.putEvent()
  
      #----------------------------------------------------------------------
      def onStart(self):
          """启动策略（必须由用户继承实现）"""
          self.writeCtaLog(u'%s策略启动' % self.name)
          self.putEvent()
  
      #----------------------------------------------------------------------
      def onStop(self):
          """停止策略（必须由用户继承实现）"""
          self.writeCtaLog(u'%s策略停止' % self.name)
          self.putEvent()
  
      #----------------------------------------------------------------------
      def onTick(self, tick):
          """收到行情TICK推送（必须由用户继承实现）"""
          self.bm.updateTick(tick)
  
      #----------------------------------------------------------------------
      def onBar(self, bar):
          """收到Bar推送（必须由用户继承实现）"""
          self.bm.updateBar(bar)
  
      #----------------------------------------------------------------------
      def onFiveBar(self, bar):
          """收到5分钟K线"""
          # 撤销之前发出的尚未成交的委托（包括限价单和停止单）
          for orderID in self.orderList:
              self.cancelOrder(orderID)
          self.orderList = []
  
          # 保存K线数据
          am = self.am
          am.updateBar(bar)
          if not am.inited:
              return
  
          # 计算指标数值
          self.kkUp, self.kkDown = am.keltner(self.kkLength, self.kkDev)
  
          # 判断是否要进行交易
  
          # 当前无仓位，发送OCO开仓委托
          if self.pos == 0:
              self.intraTradeHigh = bar.high
              self.intraTradeLow = bar.low
              self.sendOcoOrder(self.kkUp, self.kkDown, self.fixedSize)
  
          # 持有多头仓位
          elif self.pos > 0:
              self.intraTradeHigh = max(self.intraTradeHigh, bar.high)
              self.intraTradeLow = bar.low
  
              l = self.sell(self.intraTradeHigh * (1 - self.trailingPrcnt / 100),
                            abs(self.pos), True)
              self.orderList.extend(l)
  
          # 持有空头仓位
          elif self.pos < 0:
              self.intraTradeHigh = bar.high
              self.intraTradeLow = min(self.intraTradeLow, bar.low)
  
              l = self.cover(self.intraTradeLow * (1 + self.trailingPrcnt / 100),
                             abs(self.pos), True)
              self.orderList.extend(l)
  
          # 发出状态更新事件
          self.putEvent()
  
      #----------------------------------------------------------------------
      def onOrder(self, order):
          """收到委托变化推送（必须由用户继承实现）"""
          pass
  
      #----------------------------------------------------------------------
      def onTrade(self, trade):
          if self.pos != 0:
              # 多头开仓成交后，撤消空头委托
              if self.pos > 0:
                  for shortOrderID in self.shortOrderIDList:
                      self.cancelOrder(shortOrderID)
              # 反之同样
              elif self.pos < 0:
                  for buyOrderID in self.buyOrderIDList:
                      self.cancelOrder(buyOrderID)
  
              # 移除委托号
              for orderID in (self.buyOrderIDList + self.shortOrderIDList):
                  if orderID in self.orderList:
                      self.orderList.remove(orderID)
  
          # 发出状态更新事件
          self.putEvent()
  
      #----------------------------------------------------------------------
      def sendOcoOrder(self, buyPrice, shortPrice, volume):
          """
          发送OCO委托
  
          OCO(One Cancel Other)委托：
          1. 主要用于实现区间突破入场
          2. 包含两个方向相反的停止单
          3. 一个方向的停止单成交后会立即撤消另一个方向的
          """
          # 发送双边的停止单委托，并记录委托号
          self.buyOrderIDList = self.buy(buyPrice, volume, True)
          self.shortOrderIDList = self.short(shortPrice, volume, True)
  
          # 将委托号记录到列表中
          self.orderList.extend(self.buyOrderIDList)
          self.orderList.extend(self.shortOrderIDList)
  
      #----------------------------------------------------------------------
      def onStopOrder(self, so):
          """停止单推送"""
          pass
  