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.getPriceHisory({'instruments': 'EUR_USD',
#api.getPriceHisory({'instrument': 'EUR_USD',
#'granularity': 'D',
#'candleFormat': 'midpoint',
#'count': '50'})

View File

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

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

View File

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

View File

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

View File

@ -10,6 +10,7 @@ vtSymbol直接使用symbol
import os
import json
from copy import copy
from vnctpmd import MdApi
from vnctptd import TdApi
@ -436,6 +437,8 @@ class CtpTdApi(TdApi):
self.frontID = EMPTY_INT # 前置机编号
self.sessionID = EMPTY_INT # 会话编号
self.posDict = {} # 缓存持仓数据的字典
#----------------------------------------------------------------------
def onFrontConnected(self):
"""服务器连接"""
@ -624,33 +627,47 @@ class CtpTdApi(TdApi):
#----------------------------------------------------------------------
def onRspQryInvestorPosition(self, data, error, n, last):
"""持仓查询回报"""
# 获取缓存字典中的持仓对象,若无则创建并初始化
positionName = '.'.join([data['InstrumentID'], data['PosiDirection']])
if positionName in self.posDict:
pos = self.posDict[positionName]
else:
pos = VtPositionData()
self.posDict[positionName] = pos
pos.gatewayName = self.gatewayName
# 保存代码
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:
pos.frozen = data['LongFrozen']
elif pos.direction == DIRECTION_SHORT:
pos.frozen = data['ShortFrozen']
# 持仓量
if data['Position']:
pos.position = data['Position']
if data['YdPosition']:
pos.ydPosition = data['YdPosition']
# 持仓均价
if 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):

View File

@ -177,6 +177,7 @@ class OandaApi(object):
#----------------------------------------------------------------------
def exit(self):
"""退出接口"""
if self.active:
self.active = False
self.reqThread.join()