commit
113ebb6527
@ -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'})
|
||||||
|
@ -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
@ -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, {})
|
||||||
|
|
||||||
|
@ -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'
|
||||||
|
@ -285,4 +285,3 @@ class OrderManagementDemo(CtaTemplate):
|
|||||||
"""收到成交推送(必须由用户继承实现)"""
|
"""收到成交推送(必须由用户继承实现)"""
|
||||||
# 对于无需做细粒度委托控制的策略,可以忽略onOrder
|
# 对于无需做细粒度委托控制的策略,可以忽略onOrder
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
Loading…
Reference in New Issue
Block a user