Merge pull request #1 from vnpy/master

更新合并
This commit is contained in:
lyic 2016-04-02 17:01:02 +08:00
commit 113ebb6527
8 changed files with 21191 additions and 40 deletions

View File

@ -19,7 +19,7 @@ if __name__ == '__main__':
#api.getPrices({'instruments': 'EUR_USD'}) #api.getPrices({'instruments': 'EUR_USD'})
# 获取历史数据,失败 # 获取历史数据,失败
#api.getPriceHisory({'instruments': 'EUR_USD', #api.getPriceHisory({'instrument': 'EUR_USD',
#'granularity': 'D', #'granularity': 'D',
#'candleFormat': 'midpoint', #'candleFormat': 'midpoint',
#'count': '50'}) #'count': '50'})

View File

@ -87,7 +87,7 @@ class OandaApi(object):
self.initFunctionSetting(FUNCTIONCODE_GETPRICES, {'path': '/v1/prices', self.initFunctionSetting(FUNCTIONCODE_GETPRICES, {'path': '/v1/prices',
'method': 'GET'}) 'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_GETPRICEHISTORY, {'path': 'v1/candles', self.initFunctionSetting(FUNCTIONCODE_GETPRICEHISTORY, {'path': '/v1/candles',
'method': 'GET'}) 'method': 'GET'})
self.initFunctionSetting(FUNCTIONCODE_GETACCOUNTS, {'path': '/v1/accounts', self.initFunctionSetting(FUNCTIONCODE_GETACCOUNTS, {'path': '/v1/accounts',
@ -177,8 +177,9 @@ class OandaApi(object):
#---------------------------------------------------------------------- #----------------------------------------------------------------------
def exit(self): def exit(self):
"""退出接口""" """退出接口"""
self.active = False if self.active:
self.reqThread.join() self.active = False
self.reqThread.join()
#---------------------------------------------------------------------- #----------------------------------------------------------------------
def initFunctionSetting(self, code, setting): def initFunctionSetting(self, code, setting):

File diff suppressed because it is too large Load Diff

View File

@ -45,6 +45,8 @@ class BacktestingEngine(object):
self.mode = self.BAR_MODE # 回测模式默认为K线 self.mode = self.BAR_MODE # 回测模式默认为K线
self.slippage = 0 # 回测时假设的滑点 self.slippage = 0 # 回测时假设的滑点
self.rate = 0 # 回测时假设的佣金比例(适用于百分比佣金)
self.size = 1 # 合约大小默认为1
self.dbClient = None # 数据库客户端 self.dbClient = None # 数据库客户端
self.dbCursor = None # 数据库指针 self.dbCursor = None # 数据库指针
@ -285,8 +287,10 @@ class BacktestingEngine(object):
# 3. 则在实际中的成交价会是100而不是105因为委托发出时市场的最优价格是100 # 3. 则在实际中的成交价会是100而不是105因为委托发出时市场的最优价格是100
if buyCross: if buyCross:
trade.price = min(order.price, bestCrossPrice) trade.price = min(order.price, bestCrossPrice)
self.strategy.pos += order.totalVolume
else: else:
trade.price = max(order.price, bestCrossPrice) trade.price = max(order.price, bestCrossPrice)
self.strategy.pos -= order.totalVolume
trade.volume = order.totalVolume trade.volume = order.totalVolume
trade.tradeTime = str(self.dt) trade.tradeTime = str(self.dt)
@ -310,9 +314,11 @@ class BacktestingEngine(object):
if self.mode == self.BAR_MODE: if self.mode == self.BAR_MODE:
buyCrossPrice = self.bar.high # 若买入方向停止单价格低于该价格,则会成交 buyCrossPrice = self.bar.high # 若买入方向停止单价格低于该价格,则会成交
sellCrossPrice = self.bar.low # 若卖出方向限价单价格高于该价格,则会成交 sellCrossPrice = self.bar.low # 若卖出方向限价单价格高于该价格,则会成交
bestCrossPrice = self.bar.open # 最优成交价,买入停止单不能低于,卖出停止单不能高于
else: else:
buyCrossPrice = self.tick.lastPrice buyCrossPrice = self.tick.lastPrice
sellCrossPrice = self.tick.lastPrice sellCrossPrice = self.tick.lastPrice
bestCrossPrice = self.tick.lastPrice
# 遍历停止单字典中的所有停止单 # 遍历停止单字典中的所有停止单
for stopOrderID, so in self.workingStopOrderDict.items(): for stopOrderID, so in self.workingStopOrderDict.items():
@ -330,6 +336,13 @@ class BacktestingEngine(object):
trade.tradeID = tradeID trade.tradeID = tradeID
trade.vtTradeID = tradeID trade.vtTradeID = tradeID
if buyCross:
self.strategy.pos += so.volume
trade.price = max(bestCrossPrice, so.price)
else:
self.strategy.pos -= so.volume
trade.price = min(bestCrossPrice, so.price)
self.limitOrderCount += 1 self.limitOrderCount += 1
orderID = str(self.limitOrderCount) orderID = str(self.limitOrderCount)
trade.orderID = orderID trade.orderID = orderID
@ -337,7 +350,6 @@ class BacktestingEngine(object):
trade.direction = so.direction trade.direction = so.direction
trade.offset = so.offset trade.offset = so.offset
trade.price = so.price
trade.volume = so.volume trade.volume = so.volume
trade.tradeTime = str(self.dt) trade.tradeTime = str(self.dt)
trade.dt = self.dt trade.dt = self.dt
@ -405,6 +417,9 @@ class BacktestingEngine(object):
longTrade = [] # 未平仓的多头交易 longTrade = [] # 未平仓的多头交易
shortTrade = [] # 未平仓的空头交易 shortTrade = [] # 未平仓的空头交易
# 计算滑点,一个来回包括两次
totalSlippage = self.slippage * 2
for trade in self.tradeDict.values(): for trade in self.tradeDict.values():
# 多头交易 # 多头交易
if trade.direction == DIRECTION_LONG: if trade.direction == DIRECTION_LONG:
@ -414,8 +429,11 @@ class BacktestingEngine(object):
# 当前多头交易为平空 # 当前多头交易为平空
else: else:
entryTrade = shortTrade.pop(0) entryTrade = shortTrade.pop(0)
# 滑点对于交易而言永远是不利的方向,因此每笔交易开平需要减去两倍的滑点 # 计算比例佣金
pnl = (trade.price - entryTrade.price - self.slippage * 2) * trade.volume * (-1) commission = (trade.price+entryTrade.price) * self.rate
# 计算盈亏
pnl = ((trade.price - entryTrade.price)*(-1) - totalSlippage - commission) \
* trade.volume * self.size
pnlDict[trade.dt] = pnl pnlDict[trade.dt] = pnl
# 空头交易 # 空头交易
else: else:
@ -425,7 +443,11 @@ class BacktestingEngine(object):
# 当前空头交易为平多 # 当前空头交易为平多
else: else:
entryTrade = longTrade.pop(0) entryTrade = longTrade.pop(0)
pnl = (trade.price - entryTrade.price - self.slippage * 2) * trade.volume # 计算比例佣金
commission = (trade.price+entryTrade.price) * self.rate
# 计算盈亏
pnl = ((trade.price - entryTrade.price) - totalSlippage - commission) \
* trade.volume * self.size
pnlDict[trade.dt] = pnl pnlDict[trade.dt] = pnl
# 然后基于每笔交易的结果,我们可以计算具体的盈亏曲线和最大回撤等 # 然后基于每笔交易的结果,我们可以计算具体的盈亏曲线和最大回撤等
@ -449,6 +471,14 @@ class BacktestingEngine(object):
maxCapitalList.append(maxCapital) maxCapitalList.append(maxCapital)
drawdownList.append(drawdown) drawdownList.append(drawdown)
# 输出
self.output('-' * 50)
self.output(u'第一笔交易时间:%s' % timeList[0])
self.output(u'最后一笔交易时间:%s' % timeList[-1])
self.output(u'总交易次数:%s' % len(pnlList))
self.output(u'总盈亏:%s' % capitalList[-1])
self.output(u'最大回撤: %s' % min(drawdownList))
# 绘图 # 绘图
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
@ -464,13 +494,7 @@ class BacktestingEngine(object):
pPnl.set_ylabel("pnl") pPnl.set_ylabel("pnl")
pPnl.hist(pnlList, bins=20) pPnl.hist(pnlList, bins=20)
# 输出 plt.show()
self.output('-' * 50)
self.output(u'第一笔交易时间:%s' % timeList[0])
self.output(u'最后一笔交易时间:%s' % timeList[-1])
self.output(u'总交易次数:%s' % len(pnlList))
self.output(u'总盈亏:%s' % capitalList[-1])
self.output(u'最大回撤: %s' % min(drawdownList))
#---------------------------------------------------------------------- #----------------------------------------------------------------------
def putStrategyEvent(self, name): def putStrategyEvent(self, name):
@ -482,6 +506,17 @@ class BacktestingEngine(object):
"""设置滑点""" """设置滑点"""
self.slippage = slippage self.slippage = slippage
#----------------------------------------------------------------------
def setSize(self, size):
"""设置合约大小"""
self.size = size
#----------------------------------------------------------------------
def setRate(self, rate):
"""设置佣金比例"""
self.rate = rate
if __name__ == '__main__': if __name__ == '__main__':
# 以下内容是一段回测脚本的演示,用户可以根据自己的需求修改 # 以下内容是一段回测脚本的演示,用户可以根据自己的需求修改
@ -495,15 +530,17 @@ if __name__ == '__main__':
# 设置引擎的回测模式为K线 # 设置引擎的回测模式为K线
engine.setBacktestingMode(engine.BAR_MODE) engine.setBacktestingMode(engine.BAR_MODE)
# 设置滑点
engine.setSlippage(0.2) # 股指1跳
# 设置回测用的数据起始日期 # 设置回测用的数据起始日期
engine.setStartDate('20100416') engine.setStartDate('20120101')
# 载入历史数据到引擎中 # 载入历史数据到引擎中
engine.loadHistoryData(MINUTE_DB_NAME, 'IF0000') engine.loadHistoryData(MINUTE_DB_NAME, 'IF0000')
# 设置产品相关参数
engine.setSlippage(0.2) # 股指1跳
engine.setRate(0.3/10000) # 万0.3
engine.setSize(300) # 股指合约大小
# 在引擎中创建策略对象 # 在引擎中创建策略对象
engine.initStrategy(DoubleEmaDemo, {}) engine.initStrategy(DoubleEmaDemo, {})

View File

@ -28,6 +28,7 @@ STOPORDER_TRIGGERED = u'已触发'
STOPORDERPREFIX = 'CtaStopOrder.' STOPORDERPREFIX = 'CtaStopOrder.'
# 数据库名称 # 数据库名称
SETTING_DB_NAME = 'VnTrader_Setting_Db'
TICK_DB_NAME = 'VtTrader_Tick_Db' TICK_DB_NAME = 'VtTrader_Tick_Db'
DAILY_DB_NAME = 'VtTrader_Daily_Db' DAILY_DB_NAME = 'VtTrader_Daily_Db'
MINUTE_DB_NAME = 'VtTrader_1Min_Db' MINUTE_DB_NAME = 'VtTrader_1Min_Db'

View File

@ -285,4 +285,3 @@ class OrderManagementDemo(CtaTemplate):
"""收到成交推送(必须由用户继承实现)""" """收到成交推送(必须由用户继承实现)"""
# 对于无需做细粒度委托控制的策略可以忽略onOrder # 对于无需做细粒度委托控制的策略可以忽略onOrder
pass pass

View File

@ -10,6 +10,7 @@ vtSymbol直接使用symbol
import os import os
import json import json
from copy import copy
from vnctpmd import MdApi from vnctpmd import MdApi
from vnctptd import TdApi from vnctptd import TdApi
@ -436,6 +437,8 @@ class CtpTdApi(TdApi):
self.frontID = EMPTY_INT # 前置机编号 self.frontID = EMPTY_INT # 前置机编号
self.sessionID = EMPTY_INT # 会话编号 self.sessionID = EMPTY_INT # 会话编号
self.posDict = {} # 缓存持仓数据的字典
#---------------------------------------------------------------------- #----------------------------------------------------------------------
def onFrontConnected(self): def onFrontConnected(self):
"""服务器连接""" """服务器连接"""
@ -624,33 +627,47 @@ class CtpTdApi(TdApi):
#---------------------------------------------------------------------- #----------------------------------------------------------------------
def onRspQryInvestorPosition(self, data, error, n, last): def onRspQryInvestorPosition(self, data, error, n, last):
"""持仓查询回报""" """持仓查询回报"""
pos = VtPositionData() # 获取缓存字典中的持仓对象,若无则创建并初始化
pos.gatewayName = self.gatewayName positionName = '.'.join([data['InstrumentID'], data['PosiDirection']])
# 保存代码 if positionName in self.posDict:
pos.symbol = data['InstrumentID'] pos = self.posDict[positionName]
pos.vtSymbol = pos.symbol # 这里因为data中没有ExchangeID这个字段 else:
pos = VtPositionData()
self.posDict[positionName] = pos
# 方向和持仓冻结数量 pos.gatewayName = self.gatewayName
pos.direction = posiDirectionMapReverse.get(data['PosiDirection'], '')
# 保存代码
pos.symbol = data['InstrumentID']
pos.vtSymbol = pos.symbol # 这里因为data中没有ExchangeID这个字段
# 方向
pos.direction = posiDirectionMapReverse.get(data['PosiDirection'], '')
# VT系统持仓名
pos.vtPositionName = '.'.join([pos.vtSymbol, pos.direction])
# 持仓冻结数量
if pos.direction == DIRECTION_NET or pos.direction == DIRECTION_LONG: if pos.direction == DIRECTION_NET or pos.direction == DIRECTION_LONG:
pos.frozen = data['LongFrozen'] pos.frozen = data['LongFrozen']
elif pos.direction == DIRECTION_SHORT: elif pos.direction == DIRECTION_SHORT:
pos.frozen = data['ShortFrozen'] pos.frozen = data['ShortFrozen']
# 持仓量 # 持仓量
pos.position = data['Position'] if data['Position']:
pos.ydPosition = data['YdPosition'] pos.position = data['Position']
if data['YdPosition']:
pos.ydPosition = data['YdPosition']
# 持仓均价 # 持仓均价
if pos.position: if pos.position:
pos.price = data['PositionCost'] / pos.position pos.price = data['PositionCost'] / pos.position
# VT系统持仓名
pos.vtPositionName = '.'.join([pos.vtSymbol, pos.direction])
# 推送 # 推送
self.gateway.onPosition(pos) newpos = copy(pos)
self.gateway.onPosition(newpos)
#---------------------------------------------------------------------- #----------------------------------------------------------------------
def onRspQryTradingAccount(self, data, error, n, last): def onRspQryTradingAccount(self, data, error, n, last):

View File

@ -177,8 +177,9 @@ class OandaApi(object):
#---------------------------------------------------------------------- #----------------------------------------------------------------------
def exit(self): def exit(self):
"""退出接口""" """退出接口"""
self.active = False if self.active:
self.reqThread.join() self.active = False
self.reqThread.join()
#---------------------------------------------------------------------- #----------------------------------------------------------------------
def initFunctionSetting(self, code, setting): def initFunctionSetting(self, code, setting):