commit
113ebb6527
@ -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'})
|
||||
@ -102,4 +102,4 @@ if __name__ == '__main__':
|
||||
#api.getAutochartist({'instrument': 'EUR_USD'})
|
||||
|
||||
# 阻塞
|
||||
input()
|
||||
input()
|
||||
|
@ -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,8 +177,9 @@ class OandaApi(object):
|
||||
#----------------------------------------------------------------------
|
||||
def exit(self):
|
||||
"""退出接口"""
|
||||
self.active = False
|
||||
self.reqThread.join()
|
||||
if self.active:
|
||||
self.active = False
|
||||
self.reqThread.join()
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def initFunctionSetting(self, code, setting):
|
||||
@ -605,4 +606,4 @@ class OandaApi(object):
|
||||
if not self.active:
|
||||
break
|
||||
else:
|
||||
self.onError(error, -1)
|
||||
self.onError(error, -1)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
||||
@ -463,14 +493,8 @@ class BacktestingEngine(object):
|
||||
pPnl = plt.subplot(3, 1, 3)
|
||||
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):
|
||||
@ -481,6 +505,17 @@ class BacktestingEngine(object):
|
||||
def setSlippage(self, slippage):
|
||||
"""设置滑点"""
|
||||
self.slippage = slippage
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def setSize(self, size):
|
||||
"""设置合约大小"""
|
||||
self.size = size
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def setRate(self, rate):
|
||||
"""设置佣金比例"""
|
||||
self.rate = rate
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
@ -494,16 +529,18 @@ 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, {})
|
||||
|
||||
|
@ -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'
|
||||
|
@ -285,4 +285,3 @@ class OrderManagementDemo(CtaTemplate):
|
||||
"""收到成交推送(必须由用户继承实现)"""
|
||||
# 对于无需做细粒度委托控制的策略,可以忽略onOrder
|
||||
pass
|
||||
|
||||
|
@ -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):
|
||||
"""持仓查询回报"""
|
||||
pos = VtPositionData()
|
||||
pos.gatewayName = self.gatewayName
|
||||
# 获取缓存字典中的持仓对象,若无则创建并初始化
|
||||
positionName = '.'.join([data['InstrumentID'], data['PosiDirection']])
|
||||
|
||||
# 保存代码
|
||||
pos.symbol = data['InstrumentID']
|
||||
pos.vtSymbol = pos.symbol # 这里因为data中没有ExchangeID这个字段
|
||||
if positionName in self.posDict:
|
||||
pos = self.posDict[positionName]
|
||||
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:
|
||||
pos.frozen = data['LongFrozen']
|
||||
elif pos.direction == DIRECTION_SHORT:
|
||||
pos.frozen = data['ShortFrozen']
|
||||
|
||||
# 持仓量
|
||||
pos.position = data['Position']
|
||||
pos.ydPosition = data['YdPosition']
|
||||
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):
|
||||
|
@ -177,8 +177,9 @@ class OandaApi(object):
|
||||
#----------------------------------------------------------------------
|
||||
def exit(self):
|
||||
"""退出接口"""
|
||||
self.active = False
|
||||
self.reqThread.join()
|
||||
if self.active:
|
||||
self.active = False
|
||||
self.reqThread.join()
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def initFunctionSetting(self, code, setting):
|
||||
|
Loading…
Reference in New Issue
Block a user