2016-07-02 03:12:44 +00:00
|
|
|
|
# encoding: UTF-8
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
本文件中实现了CTA策略引擎,针对CTA类型的策略,抽象简化了部分底层接口的功能。
|
|
|
|
|
|
|
|
|
|
关于平今和平昨规则:
|
|
|
|
|
1. 普通的平仓OFFSET_CLOSET等于平昨OFFSET_CLOSEYESTERDAY
|
|
|
|
|
2. 只有上期所的品种需要考虑平今和平昨的区别
|
|
|
|
|
3. 当上期所的期货有今仓时,调用Sell和Cover会使用OFFSET_CLOSETODAY,否则
|
|
|
|
|
会使用OFFSET_CLOSE
|
|
|
|
|
4. 以上设计意味着如果Sell和Cover的数量超过今日持仓量时,会导致出错(即用户
|
|
|
|
|
希望通过一个指令同时平今和平昨)
|
|
|
|
|
5. 采用以上设计的原因是考虑到vn.trader的用户主要是对TB、MC和金字塔类的平台
|
|
|
|
|
感到功能不足的用户(即希望更高频的交易),交易策略不应该出现4中所述的情况
|
|
|
|
|
6. 对于想要实现4中所述情况的用户,需要实现一个策略信号引擎和交易委托引擎分开
|
|
|
|
|
的定制化统结构(没错,得自己写)
|
2016-11-08 02:48:40 +00:00
|
|
|
|
|
|
|
|
|
Modified by IncenseLee(李来佳)
|
|
|
|
|
1、增加单一策略里,多个vtSymbol的配置。
|
|
|
|
|
|
2016-07-02 03:12:44 +00:00
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
import os
|
2017-05-03 06:19:25 +00:00
|
|
|
|
import traceback
|
2016-07-02 03:12:44 +00:00
|
|
|
|
from collections import OrderedDict
|
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
|
|
|
|
|
|
from ctaBase import *
|
2017-04-28 14:10:07 +00:00
|
|
|
|
from strategy import STRATEGY_CLASS
|
2016-07-02 03:12:44 +00:00
|
|
|
|
from eventEngine import *
|
|
|
|
|
from vtConstant import *
|
|
|
|
|
from vtGateway import VtSubscribeReq, VtOrderReq, VtCancelOrderReq, VtLogData
|
|
|
|
|
from vtFunction import todayDate
|
2016-07-03 16:59:48 +00:00
|
|
|
|
import logging
|
2016-07-02 03:12:44 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
########################################################################
|
|
|
|
|
class CtaEngine(object):
|
|
|
|
|
"""CTA策略引擎"""
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
|
|
|
|
# 策略配置文件
|
2016-07-02 03:12:44 +00:00
|
|
|
|
settingFileName = 'CTA_setting.json'
|
2017-04-28 14:10:07 +00:00
|
|
|
|
path = os.path.abspath(os.path.dirname(__file__))
|
|
|
|
|
settingFileName = os.path.join(path, settingFileName)
|
2016-07-02 03:12:44 +00:00
|
|
|
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
|
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 = {}
|
|
|
|
|
|
2017-04-28 14:10:07 +00:00
|
|
|
|
# 成交号集合,用来过滤已经收到过的成交推送
|
|
|
|
|
self.tradeSet = set()
|
|
|
|
|
|
2016-11-30 06:26:08 +00:00
|
|
|
|
# 引擎类型为实盘
|
|
|
|
|
self.engineType = ENGINETYPE_TRADING
|
|
|
|
|
|
|
|
|
|
# tick缓存
|
|
|
|
|
self.tickDict = {}
|
|
|
|
|
|
2017-05-05 03:40:01 +00:00
|
|
|
|
# 未能订阅的symbols
|
|
|
|
|
self.pendingSubcribeSymbols = {}
|
|
|
|
|
|
2016-07-02 03:12:44 +00:00
|
|
|
|
# 注册事件监听
|
|
|
|
|
self.registerEvent()
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
2016-07-02 03:12:44 +00:00
|
|
|
|
def sendOrder(self, vtSymbol, orderType, price, volume, strategy):
|
|
|
|
|
"""发单"""
|
|
|
|
|
contract = self.mainEngine.getContract(vtSymbol)
|
|
|
|
|
|
|
|
|
|
req = VtOrderReq()
|
2016-07-03 16:59:48 +00:00
|
|
|
|
req.symbol = contract.symbol # 合约代码
|
|
|
|
|
req.exchange = contract.exchange # 交易所
|
2017-04-28 14:10:07 +00:00
|
|
|
|
req.price = self.roundToPriceTick(contract.priceTick, price) # 价格
|
2016-07-03 16:59:48 +00:00
|
|
|
|
req.volume = volume # 数量
|
|
|
|
|
|
2016-12-04 12:44:55 +00:00
|
|
|
|
if strategy:
|
|
|
|
|
req.productClass = strategy.productClass
|
|
|
|
|
req.currency = strategy.currency
|
|
|
|
|
else:
|
|
|
|
|
req.productClass = ''
|
|
|
|
|
req.currency = ''
|
|
|
|
|
|
|
|
|
|
|
2016-07-02 03:12:44 +00:00
|
|
|
|
# 设计为CTA引擎发出的委托只允许使用限价单
|
2016-07-03 16:59:48 +00:00
|
|
|
|
req.priceType = PRICETYPE_LIMITPRICE # 价格类型
|
2016-07-02 03:12:44 +00:00
|
|
|
|
|
|
|
|
|
# CTA委托类型映射
|
|
|
|
|
if orderType == CTAORDER_BUY:
|
2016-07-03 16:59:48 +00:00
|
|
|
|
req.direction = DIRECTION_LONG # 合约方向
|
|
|
|
|
req.offset = OFFSET_OPEN # 开/平
|
2016-07-02 03:12:44 +00:00
|
|
|
|
elif orderType == CTAORDER_SELL:
|
|
|
|
|
req.direction = DIRECTION_SHORT
|
|
|
|
|
|
|
|
|
|
# 只有上期所才要考虑平今平昨
|
|
|
|
|
if contract.exchange != EXCHANGE_SHFE:
|
|
|
|
|
req.offset = OFFSET_CLOSE
|
|
|
|
|
else:
|
|
|
|
|
# 获取持仓缓存数据
|
|
|
|
|
posBuffer = self.posBufferDict.get(vtSymbol, None)
|
|
|
|
|
# 如果获取持仓缓存失败,则默认平昨
|
|
|
|
|
if not posBuffer:
|
|
|
|
|
req.offset = OFFSET_CLOSE
|
2016-11-30 06:26:08 +00:00
|
|
|
|
|
|
|
|
|
# modified by IncenseLee 2016/11/08,改为优先平昨仓
|
|
|
|
|
elif posBuffer.longYd :
|
|
|
|
|
req.offset = OFFSET_CLOSE
|
|
|
|
|
else:
|
|
|
|
|
req.offset = OFFSET_CLOSETODAY
|
|
|
|
|
|
2016-07-02 03:12:44 +00:00
|
|
|
|
# 否则如果有多头今仓,则使用平今
|
2016-11-30 06:26:08 +00:00
|
|
|
|
#elif posBuffer.longToday:
|
|
|
|
|
# req.offset= OFFSET_CLOSETODAY
|
2016-07-02 03:12:44 +00:00
|
|
|
|
# 其他情况使用平昨
|
2016-11-30 06:26:08 +00:00
|
|
|
|
#else:
|
|
|
|
|
# req.offset = OFFSET_CLOSE
|
2016-07-02 03:12:44 +00:00
|
|
|
|
|
|
|
|
|
elif orderType == CTAORDER_SHORT:
|
|
|
|
|
req.direction = DIRECTION_SHORT
|
|
|
|
|
req.offset = OFFSET_OPEN
|
|
|
|
|
|
|
|
|
|
elif orderType == CTAORDER_COVER:
|
|
|
|
|
req.direction = DIRECTION_LONG
|
|
|
|
|
|
|
|
|
|
# 只有上期所才要考虑平今平昨
|
|
|
|
|
if contract.exchange != EXCHANGE_SHFE:
|
|
|
|
|
req.offset = OFFSET_CLOSE
|
|
|
|
|
else:
|
|
|
|
|
# 获取持仓缓存数据
|
|
|
|
|
posBuffer = self.posBufferDict.get(vtSymbol, None)
|
|
|
|
|
# 如果获取持仓缓存失败,则默认平昨
|
|
|
|
|
if not posBuffer:
|
|
|
|
|
req.offset = OFFSET_CLOSE
|
2016-11-30 06:26:08 +00:00
|
|
|
|
|
|
|
|
|
#modified by IncenseLee 2016/11/08,改为优先平昨仓
|
|
|
|
|
elif posBuffer.shortYd:
|
|
|
|
|
req.offset = OFFSET_CLOSE
|
|
|
|
|
else:
|
|
|
|
|
req.offset = OFFSET_CLOSETODAY
|
|
|
|
|
|
2016-07-02 03:12:44 +00:00
|
|
|
|
# 否则如果有空头今仓,则使用平今
|
2016-11-30 06:26:08 +00:00
|
|
|
|
#elif posBuffer.shortToday:
|
|
|
|
|
# req.offset= OFFSET_CLOSETODAY
|
2016-07-02 03:12:44 +00:00
|
|
|
|
# 其他情况使用平昨
|
2016-11-30 06:26:08 +00:00
|
|
|
|
#else:
|
|
|
|
|
# req.offset = OFFSET_CLOSE
|
2016-07-02 03:12:44 +00:00
|
|
|
|
|
|
|
|
|
vtOrderID = self.mainEngine.sendOrder(req, contract.gatewayName) # 发单
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
2016-12-04 12:44:55 +00:00
|
|
|
|
if strategy:
|
|
|
|
|
self.orderStrategyDict[vtOrderID] = strategy # 保存vtOrderID和策略的映射关系
|
2016-07-02 03:12:44 +00:00
|
|
|
|
|
2016-12-04 12:44:55 +00:00
|
|
|
|
self.writeCtaLog(u'策略%s发送委托,%s, %s,%s,%s@%s'
|
2016-10-14 07:36:54 +00:00
|
|
|
|
%(strategy.name, vtSymbol, req.offset, req.direction, volume, price))
|
2016-12-04 12:44:55 +00:00
|
|
|
|
else:
|
|
|
|
|
self.writeCtaLog(u'%s发送委托,%s, %s,%s,%s@%s'
|
|
|
|
|
% ('CtaEngine', vtSymbol, req.offset, req.direction, volume, price))
|
2016-07-02 03:12:44 +00:00
|
|
|
|
return vtOrderID
|
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# ----------------------------------------------------------------------
|
2016-07-02 03:12:44 +00:00
|
|
|
|
def cancelOrder(self, vtOrderID):
|
|
|
|
|
"""撤单"""
|
|
|
|
|
# 查询报单对象
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# 1.调用主引擎接口,查询委托单对象
|
2016-07-02 03:12:44 +00:00
|
|
|
|
order = self.mainEngine.getOrder(vtOrderID)
|
|
|
|
|
|
|
|
|
|
# 如果查询成功
|
|
|
|
|
if order:
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# 2.检查是否报单(委托单)还有效,只有有效时才发出撤单指令
|
|
|
|
|
orderFinished = (order.status == STATUS_ALLTRADED or order.status == STATUS_CANCELLED)
|
2016-07-02 03:12:44 +00:00
|
|
|
|
if not orderFinished:
|
|
|
|
|
req = VtCancelOrderReq()
|
|
|
|
|
req.symbol = order.symbol
|
|
|
|
|
req.exchange = order.exchange
|
|
|
|
|
req.frontID = order.frontID
|
|
|
|
|
req.sessionID = order.sessionID
|
|
|
|
|
req.orderID = order.orderID
|
2016-07-03 16:59:48 +00:00
|
|
|
|
self.mainEngine.cancelOrder(req, order.gatewayName)
|
|
|
|
|
else:
|
|
|
|
|
if order.status == STATUS_ALLTRADED:
|
|
|
|
|
self.writeCtaLog(u'委托单({0}已执行,无法撤销'.format(vtOrderID))
|
|
|
|
|
if order.status == STATUS_CANCELLED:
|
|
|
|
|
self.writeCtaLog(u'委托单({0}已撤销,无法再次撤销'.format(vtOrderID))
|
|
|
|
|
# 查询不成功
|
|
|
|
|
else:
|
|
|
|
|
self.writeCtaLog(u'委托单({0}不存在'.format(vtOrderID))
|
2016-07-02 03:12:44 +00:00
|
|
|
|
|
2016-08-04 07:13:40 +00:00
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
|
def cancelOrders(self, symbol, offset=EMPTY_STRING):
|
|
|
|
|
"""撤销所有单"""
|
|
|
|
|
# Symbol参数:指定合约的撤单;
|
|
|
|
|
# OFFSET参数:指定Offset的撤单,缺省不填写时,为所有
|
|
|
|
|
|
|
|
|
|
l = self.mainEngine.getAllWorkingOrders()
|
|
|
|
|
|
2016-10-14 07:36:54 +00:00
|
|
|
|
self.writeCtaLog(u'从所有订单{0}中撤销{1}'.format(len(l), symbol))
|
2016-08-04 07:13:40 +00:00
|
|
|
|
|
|
|
|
|
for order in l:
|
2016-11-30 06:26:08 +00:00
|
|
|
|
|
|
|
|
|
if symbol == EMPTY_STRING:
|
|
|
|
|
symbolCond = True
|
|
|
|
|
else:
|
|
|
|
|
symbolCond = order.symbol == symbol
|
|
|
|
|
|
2016-08-04 07:13:40 +00:00
|
|
|
|
if offset == EMPTY_STRING:
|
|
|
|
|
offsetCond = True
|
|
|
|
|
else:
|
|
|
|
|
offsetCond = order.offset == offset
|
|
|
|
|
|
2016-11-30 06:26:08 +00:00
|
|
|
|
if symbolCond and offsetCond:
|
2016-08-04 07:13:40 +00:00
|
|
|
|
req = VtCancelOrderReq()
|
|
|
|
|
req.symbol = order.symbol
|
|
|
|
|
req.exchange = order.exchange
|
|
|
|
|
req.frontID = order.frontID
|
|
|
|
|
req.sessionID = order.sessionID
|
|
|
|
|
req.orderID = order.orderID
|
|
|
|
|
self.writeCtaLog(u'撤单:{0}/{1},{2}{3}手'
|
|
|
|
|
.format(order.symbol, order.orderID, order.offset, order.totalVolume-order.tradedVolume))
|
|
|
|
|
self.mainEngine.cancelOrder(req, order.gatewayName)
|
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# ----------------------------------------------------------------------
|
2016-07-02 03:12:44 +00:00
|
|
|
|
def sendStopOrder(self, vtSymbol, orderType, price, volume, strategy):
|
|
|
|
|
"""发停止单(本地实现)"""
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
|
|
|
|
# 1.生成本地停止单ID
|
2016-07-02 03:12:44 +00:00
|
|
|
|
self.stopOrderCount += 1
|
|
|
|
|
stopOrderID = STOPORDERPREFIX + str(self.stopOrderCount)
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
|
|
|
|
# 2.创建停止单对象
|
2016-07-02 03:12:44 +00:00
|
|
|
|
so = StopOrder()
|
2016-07-03 16:59:48 +00:00
|
|
|
|
so.vtSymbol = vtSymbol # 代码
|
|
|
|
|
so.orderType = orderType # 停止单类型
|
|
|
|
|
so.price = price # 价格
|
|
|
|
|
so.volume = volume # 数量
|
|
|
|
|
so.strategy = strategy # 来源策略
|
|
|
|
|
so.stopOrderID = stopOrderID # Id
|
|
|
|
|
so.status = STOPORDER_WAITING # 状态
|
2016-07-02 03:12:44 +00:00
|
|
|
|
|
|
|
|
|
if orderType == CTAORDER_BUY:
|
|
|
|
|
so.direction = DIRECTION_LONG
|
|
|
|
|
so.offset = OFFSET_OPEN
|
|
|
|
|
elif orderType == CTAORDER_SELL:
|
|
|
|
|
so.direction = DIRECTION_SHORT
|
|
|
|
|
so.offset = OFFSET_CLOSE
|
|
|
|
|
elif orderType == CTAORDER_SHORT:
|
|
|
|
|
so.direction = DIRECTION_SHORT
|
|
|
|
|
so.offset = OFFSET_OPEN
|
|
|
|
|
elif orderType == CTAORDER_COVER:
|
|
|
|
|
so.direction = DIRECTION_LONG
|
|
|
|
|
so.offset = OFFSET_CLOSE
|
|
|
|
|
|
|
|
|
|
# 保存stopOrder对象到字典中
|
2016-07-03 16:59:48 +00:00
|
|
|
|
self.stopOrderDict[stopOrderID] = so # 字典中不会删除
|
|
|
|
|
self.workingStopOrderDict[stopOrderID] = so # 字典中会删除
|
|
|
|
|
|
|
|
|
|
self.writeCtaLog(u'发停止单成功,'
|
|
|
|
|
u'Id:{0},Symbol:{1},Type:{2},Price:{3},Volume:{4}'
|
|
|
|
|
u'.'.format(stopOrderID, vtSymbol, orderType, price, volume))
|
2016-07-02 03:12:44 +00:00
|
|
|
|
|
|
|
|
|
return stopOrderID
|
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# ----------------------------------------------------------------------
|
2016-07-02 03:12:44 +00:00
|
|
|
|
def cancelStopOrder(self, stopOrderID):
|
2016-07-03 16:59:48 +00:00
|
|
|
|
"""撤销停止单
|
|
|
|
|
Incense Li modified 20160124:
|
|
|
|
|
增加返回True 和 False
|
|
|
|
|
"""
|
|
|
|
|
# 1.检查停止单是否存在
|
2016-07-02 03:12:44 +00:00
|
|
|
|
if stopOrderID in self.workingStopOrderDict:
|
|
|
|
|
so = self.workingStopOrderDict[stopOrderID]
|
2016-07-03 16:59:48 +00:00
|
|
|
|
so.status = STOPORDER_CANCELLED # STOPORDER_WAITING =》STOPORDER_CANCELLED
|
|
|
|
|
del self.workingStopOrderDict[stopOrderID] # 删除
|
|
|
|
|
self.writeCtaLog(u'撤销停止单:{0}成功.'.format(stopOrderID))
|
|
|
|
|
return True
|
|
|
|
|
else:
|
|
|
|
|
self.writeCtaLog(u'撤销停止单:{0}失败,不存在Id.'.format(stopOrderID))
|
|
|
|
|
return False
|
2016-07-02 03:12:44 +00:00
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# ----------------------------------------------------------------------
|
2016-07-02 03:12:44 +00:00
|
|
|
|
def processStopOrder(self, tick):
|
|
|
|
|
"""收到行情后处理本地停止单(检查是否要立即发出)"""
|
|
|
|
|
vtSymbol = tick.vtSymbol
|
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# 1.首先检查是否有策略交易该合约
|
2016-07-02 03:12:44 +00:00
|
|
|
|
if vtSymbol in self.tickStrategyDict:
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# 2.遍历等待中的停止单,检查是否会被触发
|
2016-07-02 03:12:44 +00:00
|
|
|
|
for so in self.workingStopOrderDict.values():
|
|
|
|
|
if so.vtSymbol == vtSymbol:
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# 3. 触发标识判断
|
|
|
|
|
longTriggered = so.direction == DIRECTION_LONG and tick.lastPrice >= so.price # 多头停止单被触发
|
2017-04-13 15:54:24 +00:00
|
|
|
|
shortTriggered = so.direction == DIRECTION_SHORT and tick.lastPrice <= so.price # 空头停止单被触发
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
|
|
|
|
# 4.触发处理
|
2016-07-02 03:12:44 +00:00
|
|
|
|
if longTriggered or shortTriggered:
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
|
|
|
|
# 5.设定价格,买入和卖出分别以涨停跌停价发单(模拟市价单)
|
|
|
|
|
if so.direction == DIRECTION_LONG:
|
2016-07-02 03:12:44 +00:00
|
|
|
|
price = tick.upperLimit
|
|
|
|
|
else:
|
|
|
|
|
price = tick.lowerLimit
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
|
|
|
|
# 6.更新停止单状态,触发
|
2016-07-02 03:12:44 +00:00
|
|
|
|
so.status = STOPORDER_TRIGGERED
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
|
|
|
|
# 7.发单
|
2016-07-02 03:12:44 +00:00
|
|
|
|
self.sendOrder(so.vtSymbol, so.orderType, price, so.volume, so.strategy)
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
|
|
|
|
# 8.删除停止单
|
2016-07-02 03:12:44 +00:00
|
|
|
|
del self.workingStopOrderDict[so.stopOrderID]
|
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# ----------------------------------------------------------------------
|
2016-07-02 03:12:44 +00:00
|
|
|
|
def procecssTickEvent(self, event):
|
2016-07-03 16:59:48 +00:00
|
|
|
|
"""处理行情推送事件"""
|
|
|
|
|
|
|
|
|
|
# 1. 获取事件的Tick数据
|
2016-07-02 03:12:44 +00:00
|
|
|
|
tick = event.dict_['data']
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
2017-05-05 03:40:01 +00:00
|
|
|
|
# 移除待订阅的合约清单
|
|
|
|
|
if tick.vtSymbol in self.pendingSubcribeSymbols:
|
|
|
|
|
self.writeCtaLog(u'已成功订阅{0},从待订阅清单中移除'.format(tick.vtSymbol))
|
|
|
|
|
del self.pendingSubcribeSymbols[tick.vtSymbol]
|
|
|
|
|
|
2016-11-30 06:26:08 +00:00
|
|
|
|
# 缓存最新tick
|
|
|
|
|
self.tickDict[tick.vtSymbol] = tick
|
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# 2.收到tick行情后,优先处理本地停止单(检查是否要立即发出)
|
2016-07-02 03:12:44 +00:00
|
|
|
|
self.processStopOrder(tick)
|
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# 3.推送tick到对应的策略对象进行处理
|
2016-07-02 03:12:44 +00:00
|
|
|
|
if tick.vtSymbol in self.tickStrategyDict:
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
|
|
|
|
# 4.将vtTickData数据转化为ctaTickData
|
2016-07-02 03:12:44 +00:00
|
|
|
|
ctaTick = CtaTickData()
|
|
|
|
|
d = ctaTick.__dict__
|
|
|
|
|
for key in d.keys():
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
2016-07-02 03:12:44 +00:00
|
|
|
|
if key != 'datetime':
|
|
|
|
|
d[key] = tick.__getattribute__(key)
|
2017-04-28 14:10:07 +00:00
|
|
|
|
# 添加datetime字段
|
2016-07-02 03:12:44 +00:00
|
|
|
|
ctaTick.datetime = datetime.strptime(' '.join([tick.date, tick.time]), '%Y%m%d %H:%M:%S.%f')
|
2016-10-14 07:36:54 +00:00
|
|
|
|
|
2016-10-27 08:16:22 +00:00
|
|
|
|
# 5.添加datetime字段
|
|
|
|
|
if ctaTick.datetime.hour >= 20:
|
|
|
|
|
dt = datetime.now()
|
2016-10-14 07:36:54 +00:00
|
|
|
|
today = dt.strftime('%Y%m%d')
|
2016-10-27 08:16:22 +00:00
|
|
|
|
ctaTick.datetime = datetime.strptime(' '.join([today, tick.time]), '%Y%m%d %H:%M:%S.%f')
|
|
|
|
|
ctaTick.date = today
|
|
|
|
|
|
2016-07-02 03:12:44 +00:00
|
|
|
|
# 逐个推送到策略实例中
|
|
|
|
|
l = self.tickStrategyDict[tick.vtSymbol]
|
|
|
|
|
for strategy in l:
|
2017-04-28 14:10:07 +00:00
|
|
|
|
self.callStrategyFunc(strategy, strategy.onTick, ctaTick)
|
2016-07-02 03:12:44 +00:00
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# ----------------------------------------------------------------------
|
2016-07-02 03:12:44 +00:00
|
|
|
|
def processOrderEvent(self, event):
|
2016-07-03 16:59:48 +00:00
|
|
|
|
"""处理委托推送事件"""
|
|
|
|
|
# 1.获取事件的Order数据
|
2016-07-02 03:12:44 +00:00
|
|
|
|
order = event.dict_['data']
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
2016-08-04 07:13:40 +00:00
|
|
|
|
# order.vtOrderID 在gateway中,已经格式化为 gatewayName.vtOrderID
|
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# 2.判断order是否在映射字典中
|
2016-07-02 03:12:44 +00:00
|
|
|
|
if order.vtOrderID in self.orderStrategyDict:
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# 3.提取对应的策略
|
|
|
|
|
strategy = self.orderStrategyDict[order.vtOrderID]
|
|
|
|
|
# 4.触发策略的委托推送事件方法
|
2016-07-02 03:12:44 +00:00
|
|
|
|
strategy.onOrder(order)
|
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# ----------------------------------------------------------------------
|
2016-07-02 03:12:44 +00:00
|
|
|
|
def processTradeEvent(self, event):
|
2016-07-03 16:59:48 +00:00
|
|
|
|
"""处理成交推送事件"""
|
|
|
|
|
|
|
|
|
|
# 1.获取事件的Trade数据
|
2016-07-02 03:12:44 +00:00
|
|
|
|
trade = event.dict_['data']
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
2017-04-28 14:10:07 +00:00
|
|
|
|
# 过滤已经收到过的成交回报
|
|
|
|
|
if trade.vtTradeID in self.tradeSet:
|
|
|
|
|
return
|
|
|
|
|
self.tradeSet.add(trade.vtTradeID)
|
|
|
|
|
|
|
|
|
|
# 将成交推送到策略对象中
|
2016-07-02 03:12:44 +00:00
|
|
|
|
if trade.vtOrderID in self.orderStrategyDict:
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
|
|
|
|
# 3.提取对应的策略
|
2016-07-02 03:12:44 +00:00
|
|
|
|
strategy = self.orderStrategyDict[trade.vtOrderID]
|
|
|
|
|
|
2017-05-07 10:54:24 +00:00
|
|
|
|
# 计算策略持仓 ( canceled by IncenseLee )
|
|
|
|
|
#if trade.direction == DIRECTION_LONG:
|
|
|
|
|
# strategy.pos += trade.volume
|
|
|
|
|
#else:
|
|
|
|
|
# strategy.pos -= trade.volume
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
2017-04-28 14:10:07 +00:00
|
|
|
|
self.callStrategyFunc(strategy, strategy.onTrade, trade)
|
2016-07-02 03:12:44 +00:00
|
|
|
|
|
|
|
|
|
# 更新持仓缓存数据
|
|
|
|
|
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']
|
|
|
|
|
|
|
|
|
|
# 更新持仓缓存数据
|
2017-04-28 14:10:07 +00:00
|
|
|
|
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)
|
2016-07-02 03:12:44 +00:00
|
|
|
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
|
def registerEvent(self):
|
|
|
|
|
"""注册事件监听"""
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
|
|
|
|
# 注册行情数据推送(Tick数据到达)的响应事件
|
2016-07-02 03:12:44 +00:00
|
|
|
|
self.eventEngine.register(EVENT_TICK, self.procecssTickEvent)
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
|
|
|
|
# 注册订单推送的响应事件
|
2016-07-02 03:12:44 +00:00
|
|
|
|
self.eventEngine.register(EVENT_ORDER, self.processOrderEvent)
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
|
|
|
|
# 注册成交推送(交易)的响应时间
|
2016-07-02 03:12:44 +00:00
|
|
|
|
self.eventEngine.register(EVENT_TRADE, self.processTradeEvent)
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
|
|
|
|
# 注册持仓更新事件
|
2016-07-02 03:12:44 +00:00
|
|
|
|
self.eventEngine.register(EVENT_POSITION, self.processPositionEvent)
|
2017-05-05 11:48:00 +00:00
|
|
|
|
|
|
|
|
|
# 账号更新事件(借用账号更新事件,来检查是否有未订阅的合约信息)
|
|
|
|
|
self.eventEngine.register(EVENT_ACCOUNT, self.checkUnsubscribedSymbols)
|
2016-11-08 02:48:40 +00:00
|
|
|
|
|
|
|
|
|
# 注册定时器事件
|
|
|
|
|
self.eventEngine.register(EVENT_TIMER, self.processTimerEvent)
|
|
|
|
|
|
2016-12-04 12:44:55 +00:00
|
|
|
|
# 注册强制止损事件
|
|
|
|
|
self.eventEngine.register(EVENT_ACCOUNT_LOSS, self.processAccoutLossEvent)
|
|
|
|
|
|
|
|
|
|
def processAccoutLossEvent(self,event):
|
|
|
|
|
"""处理止损时间"""
|
|
|
|
|
balance = event.dict_['data']
|
|
|
|
|
self.writeCtaLog(u'净值{0}低于止损线,执行强制止损'.format(balance))
|
|
|
|
|
self.mainEngine.writeLog(u'净值{0}低于止损线,执行强制止损'.format(balance))
|
|
|
|
|
|
|
|
|
|
self.cancelOrders(symbol=EMPTY_STRING)
|
|
|
|
|
|
|
|
|
|
for posBuffer in self.posBufferDict.values():
|
|
|
|
|
|
|
|
|
|
if posBuffer.shortYd > 0:
|
|
|
|
|
self.writeCtaLog(u'{0}合约持有昨空单{1}手,强平'.format(posBuffer.vtSymbol,posBuffer.shortYd))
|
|
|
|
|
tick = self.tickDict.get(posBuffer.vtSymbol, None)
|
|
|
|
|
|
|
|
|
|
if not tick:
|
|
|
|
|
self.writeCtaLog(u'找不对{0}的最新Tick数据'.format(posBuffer.vtSymbol))
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
self.sendOrder(posBuffer.vtSymbol, orderType=CTAORDER_COVER, price=tick.upperLimit, volume=posBuffer.shortYd,strategy=None)
|
|
|
|
|
|
|
|
|
|
if posBuffer.shortToday > 0:
|
|
|
|
|
self.writeCtaLog(u'{0}合约持有今空单{1}手,强平'.format(posBuffer.vtSymbol,posBuffer.shortToday))
|
|
|
|
|
tick = self.tickDict.get(posBuffer.vtSymbol, None)
|
|
|
|
|
|
|
|
|
|
if not tick:
|
|
|
|
|
self.writeCtaLog(u'找不对{0}的最新Tick数据'.format(posBuffer.vtSymbol))
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
self.sendOrder(posBuffer.vtSymbol, orderType=CTAORDER_COVER, price=tick.upperLimit, volume=posBuffer.shortToday,strategy=None)
|
|
|
|
|
|
|
|
|
|
if posBuffer.longYd > 0:
|
|
|
|
|
self.writeCtaLog(u'{0}合约持有昨多单{1}手,强平'.format(posBuffer.vtSymbol,posBuffer.longYd))
|
|
|
|
|
tick = self.tickDict.get(posBuffer.vtSymbol, None)
|
|
|
|
|
|
|
|
|
|
if not tick:
|
|
|
|
|
self.writeCtaLog(u'找不对{0}的最新Tick数据'.format(posBuffer.vtSymbol))
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
self.sendOrder(posBuffer.vtSymbol, orderType=CTAORDER_SELL, price=tick.lowerLimit, volume=posBuffer.longYd,strategy=None)
|
|
|
|
|
|
|
|
|
|
if posBuffer.longToday > 0:
|
|
|
|
|
self.writeCtaLog(u'{0}合约持有今多单{1}手,强平'.format(posBuffer.vtSymbol,posBuffer.longToday))
|
|
|
|
|
tick = self.tickDict.get(posBuffer.vtSymbol, None)
|
|
|
|
|
|
|
|
|
|
if not tick:
|
|
|
|
|
self.writeCtaLog(u'找不对{0}的最新Tick数据'.format(posBuffer.vtSymbol))
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
self.sendOrder(posBuffer.vtSymbol, orderType=CTAORDER_SELL, price=tick.lowerLimit, volume=posBuffer.longToday,strategy=None)
|
|
|
|
|
|
|
|
|
|
|
2016-11-08 02:48:40 +00:00
|
|
|
|
def processTimerEvent(self, event):
|
|
|
|
|
"""定时器事件"""
|
|
|
|
|
|
|
|
|
|
# 触发每个策略的定时接口
|
|
|
|
|
for strategy in self.strategyDict.values():
|
|
|
|
|
strategy.onTimer()
|
|
|
|
|
|
2016-07-02 03:12:44 +00:00
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# ----------------------------------------------------------------------
|
2016-07-02 03:12:44 +00:00
|
|
|
|
def insertData(self, dbName, collectionName, data):
|
|
|
|
|
"""插入数据到数据库(这里的data可以是CtaTickData或者CtaBarData)"""
|
|
|
|
|
self.mainEngine.dbInsert(dbName, collectionName, data.__dict__)
|
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# ----------------------------------------------------------------------
|
2016-07-02 03:12:44 +00:00
|
|
|
|
def loadBar(self, dbName, collectionName, days):
|
|
|
|
|
"""从数据库中读取Bar数据,startDate是datetime对象"""
|
|
|
|
|
startDate = self.today - timedelta(days)
|
|
|
|
|
|
|
|
|
|
d = {'datetime':{'$gte':startDate}}
|
2017-04-28 14:10:07 +00:00
|
|
|
|
barData = self.mainEngine.dbQuery(dbName, collectionName, d)
|
2016-07-02 03:12:44 +00:00
|
|
|
|
|
|
|
|
|
l = []
|
2017-04-28 14:10:07 +00:00
|
|
|
|
for d in barData:
|
2016-07-02 03:12:44 +00:00
|
|
|
|
bar = CtaBarData()
|
|
|
|
|
bar.__dict__ = d
|
|
|
|
|
l.append(bar)
|
|
|
|
|
|
|
|
|
|
return l
|
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# ----------------------------------------------------------------------
|
2016-07-02 03:12:44 +00:00
|
|
|
|
def loadTick(self, dbName, collectionName, days):
|
|
|
|
|
"""从数据库中读取Tick数据,startDate是datetime对象"""
|
|
|
|
|
startDate = self.today - timedelta(days)
|
|
|
|
|
|
|
|
|
|
d = {'datetime':{'$gte':startDate}}
|
2017-04-28 14:10:07 +00:00
|
|
|
|
tickData = self.mainEngine.dbQuery(dbName, collectionName, d)
|
2016-07-02 03:12:44 +00:00
|
|
|
|
|
|
|
|
|
l = []
|
2017-04-28 14:10:07 +00:00
|
|
|
|
for d in tickData:
|
2016-07-02 03:12:44 +00:00
|
|
|
|
tick = CtaTickData()
|
|
|
|
|
tick.__dict__ = d
|
|
|
|
|
l.append(tick)
|
|
|
|
|
|
|
|
|
|
return l
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
2016-07-02 03:12:44 +00:00
|
|
|
|
def writeCtaLog(self, content):
|
|
|
|
|
"""快速发出CTA模块日志事件"""
|
|
|
|
|
log = VtLogData()
|
|
|
|
|
log.logContent = content
|
|
|
|
|
event = Event(type_=EVENT_CTA_LOG)
|
|
|
|
|
event.dict_['data'] = log
|
2016-07-03 16:59:48 +00:00
|
|
|
|
self.eventEngine.put(event)
|
|
|
|
|
|
|
|
|
|
# 写入本地log日志
|
|
|
|
|
logging.info(content)
|
2016-07-02 03:12:44 +00:00
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# ----------------------------------------------------------------------
|
2016-07-02 03:12:44 +00:00
|
|
|
|
def loadStrategy(self, setting):
|
|
|
|
|
"""载入策略"""
|
|
|
|
|
try:
|
|
|
|
|
name = setting['name']
|
|
|
|
|
className = setting['className']
|
2016-11-30 06:26:08 +00:00
|
|
|
|
except Exception as e:
|
2016-07-02 03:12:44 +00:00
|
|
|
|
self.writeCtaLog(u'载入策略出错:%s' %e)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 获取策略类
|
|
|
|
|
strategyClass = STRATEGY_CLASS.get(className, None)
|
|
|
|
|
if not strategyClass:
|
|
|
|
|
self.writeCtaLog(u'找不到策略类:%s' %className)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 防止策略重名
|
|
|
|
|
if name in self.strategyDict:
|
|
|
|
|
self.writeCtaLog(u'策略实例重名:%s' %name)
|
|
|
|
|
else:
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# 1.创建策略对象
|
2016-07-02 03:12:44 +00:00
|
|
|
|
strategy = strategyClass(self, setting)
|
|
|
|
|
self.strategyDict[name] = strategy
|
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# 2.保存Tick映射关系(symbol <==> Strategy[] )
|
2016-11-08 02:48:40 +00:00
|
|
|
|
# modifid by Incenselee 支持多个Symbol的订阅
|
|
|
|
|
symbols = strategy.vtSymbol.split(';')
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
2016-11-08 02:48:40 +00:00
|
|
|
|
for symbol in symbols:
|
2017-05-05 03:40:01 +00:00
|
|
|
|
self.writeCtaLog(u'添加合约{0}与策略的匹配目录'.format(symbol))
|
2016-11-08 02:48:40 +00:00
|
|
|
|
if symbol in self.tickStrategyDict:
|
|
|
|
|
l = self.tickStrategyDict[symbol]
|
|
|
|
|
else:
|
|
|
|
|
l = []
|
|
|
|
|
self.tickStrategyDict[symbol] = l
|
|
|
|
|
l.append(strategy)
|
|
|
|
|
|
|
|
|
|
# 3.订阅合约
|
2017-05-05 03:40:01 +00:00
|
|
|
|
self.writeCtaLog(u'向gateway订阅合约{0}'.format(symbol))
|
|
|
|
|
self.subscribe(strategy=strategy, symbol=symbol)
|
|
|
|
|
|
|
|
|
|
def subscribe(self, strategy, symbol):
|
|
|
|
|
"""订阅合约,不成功时,加入到待订阅列表"""
|
|
|
|
|
contract = self.mainEngine.getContract(symbol)
|
|
|
|
|
|
|
|
|
|
if contract:
|
|
|
|
|
# 4.构造订阅请求包
|
|
|
|
|
req = VtSubscribeReq()
|
|
|
|
|
req.symbol = contract.symbol
|
|
|
|
|
req.exchange = contract.exchange
|
|
|
|
|
|
|
|
|
|
# 对于IB接口订阅行情时所需的货币和产品类型,从策略属性中获取
|
|
|
|
|
req.currency = strategy.currency
|
|
|
|
|
req.productClass = strategy.productClass
|
|
|
|
|
|
|
|
|
|
# 5.调用主引擎的订阅接口
|
|
|
|
|
self.mainEngine.subscribe(req, contract.gatewayName)
|
|
|
|
|
else:
|
|
|
|
|
print u'Warning, can not find {0} in contracts'.format(symbol)
|
|
|
|
|
self.writeCtaLog(u'交易合约{}无法找到,添加到待订阅列表'.format (symbol))
|
|
|
|
|
self.pendingSubcribeSymbols[symbol]=strategy
|
|
|
|
|
|
|
|
|
|
def checkUnsubscribedSymbols(self, event):
|
|
|
|
|
"""持仓更新信息时,检查未提交的合约"""
|
|
|
|
|
for symbol in self.pendingSubcribeSymbols.keys():
|
|
|
|
|
contract = self.mainEngine.getContract(symbol)
|
|
|
|
|
if contract:
|
|
|
|
|
self.writeCtaLog(u'重新提交合约{0}订阅请求'.format(symbol))
|
|
|
|
|
strategy = self.pendingSubcribeSymbols[symbol]
|
|
|
|
|
self.subscribe(strategy=strategy, symbol=symbol)
|
2016-07-02 03:12:44 +00:00
|
|
|
|
|
|
|
|
|
#----------------------------------------------------------------------
|
2016-08-04 07:13:40 +00:00
|
|
|
|
def initStrategy(self, name, force = False):
|
2016-07-02 03:12:44 +00:00
|
|
|
|
"""初始化策略"""
|
|
|
|
|
if name in self.strategyDict:
|
|
|
|
|
strategy = self.strategyDict[name]
|
|
|
|
|
|
2016-08-04 07:13:40 +00:00
|
|
|
|
if not strategy.inited or force == True:
|
2017-04-28 14:10:07 +00:00
|
|
|
|
self.callStrategyFunc(strategy, strategy.onInit,force)
|
|
|
|
|
#strategy.onInit(force=force)
|
2016-10-14 07:36:54 +00:00
|
|
|
|
#strategy.inited = True
|
2016-07-02 03:12:44 +00:00
|
|
|
|
else:
|
|
|
|
|
self.writeCtaLog(u'请勿重复初始化策略实例:%s' %name)
|
|
|
|
|
else:
|
|
|
|
|
self.writeCtaLog(u'策略实例不存在:%s' %name)
|
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# ---------------------------------------------------------------------
|
2016-07-02 03:12:44 +00:00
|
|
|
|
def startStrategy(self, name):
|
|
|
|
|
"""启动策略"""
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
|
|
|
|
# 1.判断策略名称是否存在字典中
|
2016-07-02 03:12:44 +00:00
|
|
|
|
if name in self.strategyDict:
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
|
|
|
|
# 2.提取策略
|
2016-07-02 03:12:44 +00:00
|
|
|
|
strategy = self.strategyDict[name]
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
|
|
|
|
# 3.判断策略是否运行
|
2016-07-02 03:12:44 +00:00
|
|
|
|
if strategy.inited and not strategy.trading:
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# 4.设置运行状态
|
2016-07-02 03:12:44 +00:00
|
|
|
|
strategy.trading = True
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# 5.启动策略
|
2017-04-28 14:10:07 +00:00
|
|
|
|
self.callStrategyFunc(strategy, strategy.onStart)
|
2016-07-02 03:12:44 +00:00
|
|
|
|
else:
|
|
|
|
|
self.writeCtaLog(u'策略实例不存在:%s' %name)
|
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# ----------------------------------------------------------------------
|
2016-07-02 03:12:44 +00:00
|
|
|
|
def stopStrategy(self, name):
|
2016-07-03 16:59:48 +00:00
|
|
|
|
"""停止策略运行"""
|
|
|
|
|
|
|
|
|
|
# 1.判断策略名称是否存在字典中
|
2016-07-02 03:12:44 +00:00
|
|
|
|
if name in self.strategyDict:
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# 2.提取策略
|
2016-07-02 03:12:44 +00:00
|
|
|
|
strategy = self.strategyDict[name]
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
|
|
|
|
# 3.停止交易
|
2016-07-02 03:12:44 +00:00
|
|
|
|
if strategy.trading:
|
2016-07-03 16:59:48 +00:00
|
|
|
|
|
|
|
|
|
# 4.设置交易状态为False
|
2016-07-02 03:12:44 +00:00
|
|
|
|
strategy.trading = False
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# 5.调用策略的停止方法
|
2017-04-28 14:10:07 +00:00
|
|
|
|
self.callStrategyFunc(strategy, strategy.onStop)
|
2016-07-02 03:12:44 +00:00
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# 6.对该策略发出的所有限价单进行撤单
|
2016-07-02 03:12:44 +00:00
|
|
|
|
for vtOrderID, s in self.orderStrategyDict.items():
|
|
|
|
|
if s is strategy:
|
|
|
|
|
self.cancelOrder(vtOrderID)
|
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# 7.对该策略发出的所有本地停止单撤单
|
2016-07-02 03:12:44 +00:00
|
|
|
|
for stopOrderID, so in self.workingStopOrderDict.items():
|
|
|
|
|
if so.strategy is strategy:
|
|
|
|
|
self.cancelStopOrder(stopOrderID)
|
|
|
|
|
else:
|
|
|
|
|
self.writeCtaLog(u'策略实例不存在:%s' %name)
|
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# ----------------------------------------------------------------------
|
2016-07-02 03:12:44 +00:00
|
|
|
|
def saveSetting(self):
|
|
|
|
|
"""保存策略配置"""
|
|
|
|
|
with open(self.settingFileName, 'w') as f:
|
|
|
|
|
l = []
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# 逐一循环:name,策略实例名称,strategy,策略
|
2016-07-02 03:12:44 +00:00
|
|
|
|
for strategy in self.strategyDict.values():
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# 配置串
|
2016-07-02 03:12:44 +00:00
|
|
|
|
setting = {}
|
|
|
|
|
for param in strategy.paramList:
|
|
|
|
|
setting[param] = strategy.__getattribute__(param)
|
|
|
|
|
l.append(setting)
|
|
|
|
|
|
|
|
|
|
jsonL = json.dumps(l, indent=4)
|
|
|
|
|
f.write(jsonL)
|
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# ----------------------------------------------------------------------
|
2016-07-02 03:12:44 +00:00
|
|
|
|
def loadSetting(self):
|
|
|
|
|
"""读取策略配置"""
|
|
|
|
|
with open(self.settingFileName) as f:
|
|
|
|
|
l = json.load(f)
|
|
|
|
|
|
|
|
|
|
for setting in l:
|
|
|
|
|
self.loadStrategy(setting)
|
|
|
|
|
|
2017-04-28 14:10:07 +00:00
|
|
|
|
self.loadPosition()
|
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# ----------------------------------------------------------------------
|
2016-07-02 03:12:44 +00:00
|
|
|
|
def getStrategyVar(self, name):
|
|
|
|
|
"""获取策略当前的变量字典"""
|
|
|
|
|
if name in self.strategyDict:
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# 获取策略实例
|
2016-07-02 03:12:44 +00:00
|
|
|
|
strategy = self.strategyDict[name]
|
|
|
|
|
varDict = OrderedDict()
|
|
|
|
|
|
|
|
|
|
for key in strategy.varList:
|
2016-10-26 10:26:03 +00:00
|
|
|
|
if hasattr(strategy,key):
|
|
|
|
|
varDict[key] = strategy.__getattribute__(key)
|
2016-07-02 03:12:44 +00:00
|
|
|
|
|
|
|
|
|
return varDict
|
|
|
|
|
else:
|
|
|
|
|
self.writeCtaLog(u'策略实例不存在:' + name)
|
|
|
|
|
return None
|
|
|
|
|
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# ----------------------------------------------------------------------
|
2016-07-02 03:12:44 +00:00
|
|
|
|
def getStrategyParam(self, name):
|
|
|
|
|
"""获取策略的参数字典"""
|
|
|
|
|
if name in self.strategyDict:
|
2016-07-03 16:59:48 +00:00
|
|
|
|
# 获取策略实例
|
2016-07-02 03:12:44 +00:00
|
|
|
|
strategy = self.strategyDict[name]
|
|
|
|
|
paramDict = OrderedDict()
|
|
|
|
|
|
2016-10-26 10:26:03 +00:00
|
|
|
|
for key in strategy.paramList:
|
|
|
|
|
if hasattr(strategy, key):
|
|
|
|
|
paramDict[key] = strategy.__getattribute__(key)
|
2016-07-02 03:12:44 +00:00
|
|
|
|
|
|
|
|
|
return paramDict
|
|
|
|
|
else:
|
|
|
|
|
self.writeCtaLog(u'策略实例不存在:' + name)
|
|
|
|
|
return None
|
|
|
|
|
|
2016-10-19 13:16:10 +00:00
|
|
|
|
# ----------------------------------------------------------------------
|
2016-07-02 03:12:44 +00:00
|
|
|
|
def putStrategyEvent(self, name):
|
|
|
|
|
"""触发策略状态变化事件(通常用于通知GUI更新)"""
|
|
|
|
|
event = Event(EVENT_CTA_STRATEGY+name)
|
|
|
|
|
self.eventEngine.put(event)
|
2016-10-19 13:16:10 +00:00
|
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
2017-04-28 14:10:07 +00:00
|
|
|
|
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([u'策略%s触发异常已停止' % strategy.name,
|
|
|
|
|
traceback.format_exc()])
|
|
|
|
|
self.writeCtaLog(content)
|
|
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
|
def savePosition(self):
|
|
|
|
|
"""保存所有策略的持仓情况到数据库"""
|
|
|
|
|
for strategy in 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.writeCtaLog(content)
|
|
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
|
def loadPosition(self):
|
|
|
|
|
"""从数据库载入策略的持仓情况"""
|
|
|
|
|
for strategy in 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
|
|
|
|
|
|
2017-05-05 03:40:01 +00:00
|
|
|
|
# ----------------------------------------------------------------------
|
2016-10-19 13:16:10 +00:00
|
|
|
|
def getAccountInfo(self):
|
|
|
|
|
"""获取账号的实时权益、可用资金、仓位比例
|
|
|
|
|
Added by Incenselee
|
|
|
|
|
暂不支持多接口同时运行哦
|
|
|
|
|
"""
|
|
|
|
|
return self.mainEngine.getAccountInfo()
|
|
|
|
|
|
2016-10-26 10:26:03 +00:00
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
|
def saveStrategyData(self):
|
|
|
|
|
"""保存策略的数据"""
|
|
|
|
|
|
|
|
|
|
# 1.判断策略名称是否存在字典中
|
|
|
|
|
for key in self.strategyDict.keys():
|
|
|
|
|
|
|
|
|
|
# 2.提取策略
|
|
|
|
|
strategy = self.strategyDict[key]
|
|
|
|
|
|
|
|
|
|
if strategy is None:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# 3.判断策略是否运行
|
|
|
|
|
if strategy.inited and strategy.trading:
|
|
|
|
|
|
|
|
|
|
# 5.保存策略数据
|
|
|
|
|
strategy.saveBar()
|
2016-07-02 03:12:44 +00:00
|
|
|
|
|
2016-11-08 02:48:40 +00:00
|
|
|
|
def clearData(self):
|
|
|
|
|
"""清空运行数据"""
|
|
|
|
|
self.orderStrategyDict = {}
|
|
|
|
|
self.workingStopOrderDict = {}
|
|
|
|
|
self.posBufferDict = {}
|
|
|
|
|
self.stopOrderDict = {}
|
2016-07-02 03:12:44 +00:00
|
|
|
|
|
|
|
|
|
########################################################################
|
|
|
|
|
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
|