text: # encoding: UTF-8
  
  '''
  本文件中实现了STUB策略引擎，针对STUB类型的策略，抽象简化了部分底层接口的功能。
  
  关于平今和平昨规则：
  1. 普通的平仓OFFSET_CLOSET等于平昨OFFSET_CLOSEYESTERDAY
  2. 只有上期所的品种需要考虑平今和平昨的区别
  3. 当上期所的期货有今仓时，调用Sell和Cover会使用OFFSET_CLOSETODAY，否则
     会使用OFFSET_CLOSE
  4. 以上设计意味着如果Sell和Cover的数量超过今日持仓量时，会导致出错（即用户
     希望通过一个指令同时平今和平昨）
  5. 采用以上设计的原因是考虑到vn.trader的用户主要是对TB、MC和金字塔类的平台
     感到功能不足的用户（即希望更高频的交易），交易策略不应该出现4中所述的情况
  6. 对于想要实现4中所述情况的用户，需要实现一个策略信号引擎和交易委托引擎分开
     的定制化统结构（没错，得自己写）
  '''
  
  
  
  import json
  import os
  import traceback
  from collections import OrderedDict
  from datetime import datetime, timedelta
  
  from vnpy.event import Event
  from vnpy.trader.vtEvent import *
  from vnpy.trader.language.vtConstant import *
  from vnpy.trader.main.vtObject import VtTickData, VtBarData
  from vnpy.trader.vtGateway import VtSubscribeReq, VtOrderReq, VtCancelOrderReq, VtLogData
  from vnpy.trader.main.vtFunction import todayDate
  
  from vnpy.trader.app.stubStrategy.stubBase import *
  from vnpy.trader.app.stubStrategy.strategy import STRATEGY_CLASS
  
  
  
  
  ########################################################################
  class StrategyManager(object):
      """策略引擎管理"""
      settingFileName = 'ST_setting.json'
      path = os.path.abspath(os.path.dirname(__file__))
      settingFileName = os.path.join(path, settingFileName)
  
      #----------------------------------------------------------------------
      def __init__(self, mainEngine, eventEngine):
          """Constructor"""
          self.mainEngine = mainEngine
          self.eventEngine = eventEngine
  
          # 当前日期
          self.today = todayDate()
  
          # 保存策略实例的字典
          # key为策略名称，value为策略实例，注意策略名称不允许重复
          self.strategyDict = {}
  
          # 保存vtSymbol和策略实例映射的字典（用于推送tick数据）
          # 由于可能多个strategy交易同一个vtSymbol，因此key为vtSymbol
          # value为包含所有相关strategy对象的list
          self.tickStrategyDict = {}
  
          # 保存vtOrderID和strategy对象映射的字典（用于推送order和trade数据）
          # key为vtOrderID，value为strategy对象
          self.orderStrategyDict = {}
  
          # 本地停止单编号计数
          self.stopOrderCount = 0
          # stopOrderID = STOPORDERPREFIX + str(stopOrderCount)
  
          # 本地停止单字典
          # key为stopOrderID，value为stopOrder对象
          self.stopOrderDict = {}             # 停止单撤销后不会从本字典中删除
          self.workingStopOrderDict = {}      # 停止单撤销后会从本字典中删除
  
          # 持仓缓存字典
          # key为vtSymbol，value为PositionBuffer对象
          self.posBufferDict = {}
  
          # 成交号集合，用来过滤已经收到过的成交推送
          self.tradeSet = set()
  
          # 引擎类型为实盘
          self.engineType = ENGINETYPE_TRADING
  
          # 注册事件监听
          self.registerEvent()
  
      #----------------------------------------------------------------------
      def registerEvent(self):
          """注册事件监听"""
          self.eventEngine.register(EVENT_TICK, self.processTickEvent)
          self.eventEngine.register(EVENT_ORDER, self.processOrderEvent)
          self.eventEngine.register(EVENT_TRADE, self.processTradeEvent)
          self.eventEngine.register(EVENT_POSITION, self.processPositionEvent)
  
      #----------------------------------------------------------------------
      def sendOrder(self, vtSymbol, orderType, price, volume, strategy):
          """发单"""
  
  
          self.writeStubLog('策略%s发送委托，%s，%s，%s@%s'
                           %(strategy.name, vtSymbol, req.direction, volume, price))
  
          return vtOrderID
  
      #----------------------------------------------------------------------
      def cancelOrder(self, vtOrderID):
          """撤单"""
          pass
  
      #----------------------------------------------------------------------
      def sendStopOrder(self, vtSymbol, orderType, price, volume, strategy):
          """发停止单（本地实现）"""
          pass
  
          return stopOrderID
  
      #----------------------------------------------------------------------
      def cancelStopOrder(self, stopOrderID):
          """撤销停止单"""
          pass
  
      #----------------------------------------------------------------------
      def processStopOrder(self, tick):
          """收到行情后处理本地停止单（检查是否要立即发出）"""
          pass
  
      #----------------------------------------------------------------------
      def processTickEvent(self, event):
          """处理行情推送"""
          tick = event.dict_['data']
          # 收到tick行情后，先处理本地停止单（检查是否要立即发出）
          self.processStopOrder(tick)
  
          # 推送tick到对应的策略实例进行处理
          if tick.vtSymbol in self.tickStrategyDict:
              # 添加datetime字段
              if not tick.datetime:
                  tick.datetime = datetime.strptime(' '.join([tick.date, tick.time]), '%Y%m%d %H:%M:%S.%f')
  
              # 逐个推送到策略实例中
              l = self.tickStrategyDict[tick.vtSymbol]
              for strategy in l:
                  self.callStrategyFunc(strategy, strategy.onTick, tick)
  
      #----------------------------------------------------------------------
      def processOrderEvent(self, event):
          """处理委托推送"""
          order = event.dict_['data']
  
          if order.vtOrderID in self.orderStrategyDict:
              strategy = self.orderStrategyDict[order.vtOrderID]
              self.callStrategyFunc(strategy, strategy.onOrder, order)
  
      #----------------------------------------------------------------------
      def processTradeEvent(self, event):
          """处理成交推送"""
          trade = event.dict_['data']
  
          # 过滤已经收到过的成交回报
          if trade.vtTradeID in self.tradeSet:
              return
          self.tradeSet.add(trade.vtTradeID)
  
          # 将成交推送到策略对象中
          if trade.vtOrderID in self.orderStrategyDict:
              strategy = self.orderStrategyDict[trade.vtOrderID]
  
              # 计算策略持仓
              if trade.direction == DIRECTION_LONG:
                  strategy.pos += trade.volume
              else:
                  strategy.pos -= trade.volume
  
              self.callStrategyFunc(strategy, strategy.onTrade, trade)
  
          # 更新持仓缓存数据
          if trade.vtSymbol in self.tickStrategyDict:
              posBuffer = self.posBufferDict.get(trade.vtSymbol, None)
              if not posBuffer:
                  posBuffer = PositionBuffer()
                  posBuffer.vtSymbol = trade.vtSymbol
                  self.posBufferDict[trade.vtSymbol] = posBuffer
              posBuffer.updateTradeData(trade)
  
      #----------------------------------------------------------------------
      def processPositionEvent(self, event):
          """处理持仓推送"""
          pos = event.dict_['data']
  
          # 更新持仓缓存数据
          if pos.vtSymbol in self.tickStrategyDict:
              posBuffer = self.posBufferDict.get(pos.vtSymbol, None)
              if not posBuffer:
                  posBuffer = PositionBuffer()
                  posBuffer.vtSymbol = pos.vtSymbol
                  self.posBufferDict[pos.vtSymbol] = posBuffer
              posBuffer.updatePositionData(pos)
  
  
  
      #----------------------------------------------------------------------
      def insertData(self, dbName, collectionName, data):
          """插入数据到数据库（这里的data可以是VtTickData或者VtBarData）"""
          self.mainEngine.dbInsert(dbName, collectionName, data.__dict__)
  
      #----------------------------------------------------------------------
      def loadBar(self, dbName, collectionName, days):
          """从数据库中读取Bar数据，startDate是datetime对象"""
          startDate = self.today - timedelta(days)
  
          d = {'datetime':{'$gte':startDate}}
          barData = self.mainEngine.dbQuery(dbName, collectionName, d)
  
          l = []
          for d in barData:
              bar = VtBarData()
              bar.__dict__ = d
              l.append(bar)
          return l
  
      #----------------------------------------------------------------------
      def loadTick(self, dbName, collectionName, days):
          """从数据库中读取Tick数据，startDate是datetime对象"""
          startDate = self.today - timedelta(days)
  
          d = {'datetime':{'$gte':startDate}}
          tickData = self.mainEngine.dbQuery(dbName, collectionName, d)
  
          l = []
          for d in tickData:
              tick = VtTickData()
              tick.__dict__ = d
              l.append(tick)
          return l
  
      #----------------------------------------------------------------------
      def writeStubLog(self, content):
          """快速发出STUB模块日志事件"""
          log = VtLogData()
          log.logContent = content
          event = Event(type_=EVENT_STUB_LOG)
          event.dict_['data'] = log
          self.eventEngine.put(event)
  
      #----------------------------------------------------------------------
      def loadStrategy(self, setting):
          """载入策略"""
          try:
              name = setting['name']
              className = setting['className']
          except Exception as e:
              self.writeStubLog('载入策略出错：%s' %e)
              return
  
          # 获取策略类
          strategyClass = STRATEGY_CLASS.get(className, None)
          if not strategyClass:
              self.writeStubLog('找不到策略类：%s' %className)
              return
  
          # 防止策略重名
          if name in self.strategyDict:
              self.writeStubLog('策略实例重名：%s' %name)
          else:
              # 创建策略实例
              strategy = strategyClass(self, setting)
              self.strategyDict[name] = strategy
  
              # 保存Tick映射关系
              if strategy.vtSymbol in self.tickStrategyDict:
                  l = self.tickStrategyDict[strategy.vtSymbol]
              else:
                  l = []
                  self.tickStrategyDict[strategy.vtSymbol] = l
              l.append(strategy)
  
              # 订阅合约
              contract = self.mainEngine.getContract(strategy.vtSymbol)
              if contract:
                  req = VtSubscribeReq()
                  req.symbol = contract.symbol
                  req.exchange = contract.exchange
  
                  # 对于IB接口订阅行情时所需的货币和产品类型，从策略属性中获取
                  req.currency = strategy.currency
                  req.productClass = strategy.productClass
  
                  self.mainEngine.subscribe(req, contract.gatewayName)
              else:
                  self.writeStubLog('%s的交易合约%s无法找到' %(name, strategy.vtSymbol))
  
      #----------------------------------------------------------------------
      def initStrategy(self, name):
          """初始化策略"""
          if name in self.strategyDict:
              strategy = self.strategyDict[name]
  
              if not strategy.inited:
                  strategy.inited = True
                  self.callStrategyFunc(strategy, strategy.onInit)
              else:
                  self.writeStubLog('请勿重复初始化策略实例：%s' %name)
          else:
              self.writeStubLog('策略实例不存在：%s' %name)
  
      #---------------------------------------------------------------------
      def startStrategy(self, name):
          """启动策略"""
          if name in self.strategyDict:
              strategy = self.strategyDict[name]
  
              if strategy.inited and not strategy.trading:
                  strategy.trading = True
                  self.callStrategyFunc(strategy, strategy.onStart)
          else:
              self.writeStubLog('策略实例不存在：%s' %name)
  
      #----------------------------------------------------------------------
      def stopStrategy(self, name):
          """停止策略"""
          if name in self.strategyDict:
              strategy = self.strategyDict[name]
  
              if strategy.trading:
                  strategy.trading = False
                  self.callStrategyFunc(strategy, strategy.onStop)
  
                  # 对该策略发出的所有限价单进行撤单
                  for vtOrderID, s in list(self.orderStrategyDict.items()):
                      if s is strategy:
                          self.cancelOrder(vtOrderID)
  
                  # 对该策略发出的所有本地停止单撤单
                  for stopOrderID, so in list(self.workingStopOrderDict.items()):
                      if so.strategy is strategy:
                          self.cancelStopOrder(stopOrderID)
          else:
              self.writeStubLog('策略实例不存在：%s' %name)
  
      #----------------------------------------------------------------------
      def saveSetting(self):
          """保存策略配置"""
          with open(self.settingFileName, 'w') as f:
              l = []
  
              for strategy in list(self.strategyDict.values()):
                  setting = {}
                  for param in strategy.paramList:
                      setting[param] = strategy.__getattribute__(param)
                  l.append(setting)
  
              jsonL = json.dumps(l, indent=4)
              f.write(jsonL)
  
      #----------------------------------------------------------------------
      def loadSetting(self):
          """读取策略配置"""
          with open(self.settingFileName) as f:
              l = json.load(f)
  
              for setting in l:
                  self.loadStrategy(setting)
  
          self.loadPosition()
  
      #----------------------------------------------------------------------
      def getStrategyVar(self, name):
          """获取策略当前的变量字典"""
          if name in self.strategyDict:
              strategy = self.strategyDict[name]
              varDict = OrderedDict()
  
              for key in strategy.varList:
                  varDict[key] = strategy.__getattribute__(key)
  
              return varDict
          else:
              self.writeStubLog('策略实例不存在：' + name)
              return None
  
      #----------------------------------------------------------------------
      def getStrategyParam(self, name):
          """获取策略的参数字典"""
          if name in self.strategyDict:
              strategy = self.strategyDict[name]
              paramDict = OrderedDict()
  
              for key in strategy.paramList:
                  paramDict[key] = strategy.__getattribute__(key)
  
              return paramDict
          else:
              self.writeStubLog('策略实例不存在：' + name)
              return None
  
      #----------------------------------------------------------------------
      def putStrategyEvent(self, name):
          """触发策略状态变化事件（通常用于通知GUI更新）"""
          event = Event(EVENT_STUB_STRATEGY+name)
          self.eventEngine.put(event)
  
      #----------------------------------------------------------------------
      def callStrategyFunc(self, strategy, func, params=None):
          """调用策略的函数，若触发异常则捕捉"""
          try:
              if params:
                  func(params)
              else:
                  func()
          except Exception:
              # 停止策略，修改状态为未初始化
              strategy.trading = False
              strategy.inited = False
  
              # 发出日志
              content = '\n'.join(['策略%s触发异常已停止' %strategy.name,
                                  traceback.format_exc()])
              self.writeStubLog(content)
  
      #----------------------------------------------------------------------
      def savePosition(self):
          """保存所有策略的持仓情况到数据库"""
          for strategy in list(self.strategyDict.values()):
              flt = {'name': strategy.name,
                     'vtSymbol': strategy.vtSymbol}
  
              d = {'name': strategy.name,
                   'vtSymbol': strategy.vtSymbol,
                   'pos': strategy.pos}
  
              self.mainEngine.dbUpdate(POSITION_DB_NAME, strategy.className,
                                       d, flt, True)
  
              content = '策略%s持仓保存成功' %strategy.name
              self.writeStubLog(content)
  
      #----------------------------------------------------------------------
      def loadPosition(self):
          """从数据库载入策略的持仓情况"""
          for strategy in list(self.strategyDict.values()):
              flt = {'name': strategy.name,
                     'vtSymbol': strategy.vtSymbol}
              posData = self.mainEngine.dbQuery(POSITION_DB_NAME, strategy.className, flt)
  
              for d in posData:
                  strategy.pos = d['pos']
  
      #----------------------------------------------------------------------
      def roundToPriceTick(self, priceTick, price):
          """取整价格到合约最小价格变动"""
          if not priceTick:
              return price
  
          newPrice = round(price/priceTick, 0) * priceTick
          return newPrice
  
      #----------------------------------------------------------------------
      def stop(self):
          """停止"""
          pass
  
  
  ########################################################################
  class PositionBuffer(object):
      """持仓缓存信息（本地维护的持仓数据）"""
  
      #----------------------------------------------------------------------
      def __init__(self):
          """Constructor"""
          self.vtSymbol = EMPTY_STRING
  
          # 多头
          self.longPosition = EMPTY_INT
          self.longToday = EMPTY_INT
          self.longYd = EMPTY_INT
  
          # 空头
          self.shortPosition = EMPTY_INT
          self.shortToday = EMPTY_INT
          self.shortYd = EMPTY_INT
  
      #----------------------------------------------------------------------
      def updatePositionData(self, pos):
          """更新持仓数据"""
          if pos.direction == DIRECTION_LONG:
              self.longPosition = pos.position
              self.longYd = pos.ydPosition
              self.longToday = self.longPosition - self.longYd
          else:
              self.shortPosition = pos.position
              self.shortYd = pos.ydPosition
              self.shortToday = self.shortPosition - self.shortYd
  
      #----------------------------------------------------------------------
      def updateTradeData(self, trade):
          """更新成交数据"""
          if trade.direction == DIRECTION_LONG:
              # 多方开仓，则对应多头的持仓和今仓增加
              if trade.offset == OFFSET_OPEN:
                  self.longPosition += trade.volume
                  self.longToday += trade.volume
              # 多方平今，对应空头的持仓和今仓减少
              elif trade.offset == OFFSET_CLOSETODAY:
                  self.shortPosition -= trade.volume
                  self.shortToday -= trade.volume
              # 多方平昨，对应空头的持仓和昨仓减少
              else:
                  self.shortPosition -= trade.volume
                  self.shortYd -= trade.volume
          else:
              # 空头和多头相同
              if trade.offset == OFFSET_OPEN:
                  self.shortPosition += trade.volume
                  self.shortToday += trade.volume
              elif trade.offset == OFFSET_CLOSETODAY:
                  self.longPosition -= trade.volume
                  self.longToday -= trade.volume
              else:
                  self.longPosition -= trade.volume
                  self.longYd -= trade.volume
  
  
  
  
  
  
  