[Add]新增完整海龟策略的例子examples/TurtleStrategy

This commit is contained in:
vn.py 2018-11-11 11:45:27 +08:00
parent aaa4b80a7c
commit 3d1048848b
4 changed files with 972 additions and 0 deletions

View File

@ -0,0 +1,172 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import sys\n",
"reload(sys)\n",
"\n",
"\n",
"%matplotlib inline\n",
"\n",
"from datetime import datetime\n",
"import matplotlib.pyplot as plt\n",
"\n",
"from turtleEngine import BacktestingEngine"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"l = ['IF99'] #, 'CU99', 'I99', 'TA99']\n",
"engine = BacktestingEngine()\n",
"engine.setPeriod(datetime(2015, 1, 1), datetime(2018, 11, 9))\n",
"engine.initPortfolio(l)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"11:39:12.298000:IF99数据加载完成总数据量940\n",
"11:39:12.299000:全部数据加载完成\n"
]
}
],
"source": [
"engine.loadData()\n",
"engine.runBacktesting()\n",
"engine.calculateResult()"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2015-06-08 00:00:00\n"
]
},
{
"ename": "UnicodeDecodeError",
"evalue": "'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mUnicodeDecodeError\u001b[0m Traceback (most recent call last)",
"\u001b[1;32m<ipython-input-4-74c05e0f545f>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;32mprint\u001b[0m \u001b[0mdt\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mtrade\u001b[0m \u001b[1;32min\u001b[0m \u001b[0ml\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 4\u001b[1;33m \u001b[1;32mprint\u001b[0m \u001b[0mtrade\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[1;32mC:\\Github\\vnpy\\examples\\TurtleStrategy\\turtleEngine.py\u001b[0m in \u001b[0;36m__str__\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m 134\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0moffset\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mencode\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'UTF-8'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 135\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mvolume\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 136\u001b[1;33m self.price)\n\u001b[0m\u001b[0;32m 137\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 138\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
"\u001b[1;31mUnicodeDecodeError\u001b[0m: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)"
]
}
],
"source": [
"for dt, l in engine.tradeDict.items():\n",
" print dt\n",
" for trade in l:\n",
" print trade"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": false
},
"outputs": [],
"source": [
"for result in engine.resultList:\n",
" print result.date, result.totalPnl"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"l = [result.totalPnl for result in engine.resultList]\n",
"equity = np.cumsum(l)\n",
"plt.plot(equity)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"for data in engine.dataDict.values():\n",
" print data"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print trade."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.14"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@ -0,0 +1,265 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import sys\n",
"reload(sys)\n",
"\n",
"\n",
"%matplotlib inline\n",
"\n",
"from datetime import datetime\n",
"import matplotlib.pyplot as plt\n",
"\n",
"from turtleEngine import BacktestingEngine"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"l = ['IF99'] #, 'CU99', 'I99', 'TA99']\n",
"engine = BacktestingEngine()\n",
"engine.setPeriod(datetime(2015, 1, 1), datetime(2018, 11, 9))\n",
"engine.initPortfolio(l)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"11:39:12.298000:IF99数据加载完成总数据量940\n",
"11:39:12.299000:全部数据加载完成\n"
]
}
],
"source": [
"engine.loadData()\n",
"engine.runBacktesting()\n",
"engine.calculateResult()"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2015-06-08 00:00:00\n",
"IF99 多 开仓 5370.6182 1\n",
"IF99 多 开仓 5370.6182 1\n",
"2015-06-17 00:00:00\n",
"IF99 空 平仓 4994.5359289007665 1\n",
"IF99 空 平仓 4994.5359289007665 1\n",
"2015-10-12 00:00:00\n",
"IF99 多 开仓 3341.0982 1\n",
"2015-10-26 00:00:00\n",
"IF99 多 开仓 3424.5987371468213 1\n",
"2015-11-04 00:00:00\n",
"IF99 多 开仓 3508.099274293643 1\n",
"2015-11-05 00:00:00\n",
"IF99 多 开仓 3591.5998114404642 1\n",
"2015-11-23 00:00:00\n",
"IF99 空 平仓 3603.8898 4\n",
"2015-12-21 00:00:00\n",
"IF99 多 开仓 3786.5235 1\n",
"2015-12-23 00:00:00\n",
"IF99 多 开仓 3834.280588057014 1\n",
"2015-12-28 00:00:00\n",
"IF99 空 平仓 3643.2522358289566 2\n",
"2016-03-07 00:00:00\n",
"IF99 多 开仓 3027.3054 1\n",
"IF99 多 开仓 3074.3983619185256 1\n",
"2016-03-18 00:00:00\n",
"IF99 多 开仓 3121.4913238370505 1\n",
"2016-03-21 00:00:00\n",
"IF99 多 开仓 3168.584285755576 1\n",
"2016-04-20 00:00:00\n",
"IF99 空 平仓 3105.8006 4\n",
"2016-07-07 00:00:00\n",
"IF99 多 开仓 3166.5061 1\n",
"2016-07-11 00:00:00\n",
"IF99 多 开仓 3194.9341606344988 1\n",
"IF99 多 开仓 3192.2685 1\n",
"2016-07-12 00:00:00\n",
"IF99 多 开仓 3223.3622212689975 1\n",
"2016-07-27 00:00:00\n",
"IF99 空 平仓 3173.5677 4\n",
"2016-08-11 00:00:00\n",
"IF99 多 开仓 3245.6871 1\n",
"2016-08-12 00:00:00\n",
"IF99 多 开仓 3268.4782362989677 1\n",
"IF99 多 开仓 3274.5309 1\n",
"2016-08-15 00:00:00\n",
"IF99 多 开仓 3291.269372597935 1\n",
"2016-08-25 00:00:00\n",
"IF99 空 平仓 3251.8034639734847 4\n",
"2016-10-19 00:00:00\n",
"IF99 多 开仓 3294.1809 1\n",
"2016-10-24 00:00:00\n",
"IF99 多 开仓 3313.0011487826814 1\n",
"IF99 多 开仓 3331.821397565363 1\n",
"IF99 多 开仓 3350.6416463480446 1\n",
"2016-12-07 00:00:00\n",
"IF99 空 平仓 3432.9777 4\n",
"2017-03-24 00:00:00\n",
"IF99 多 开仓 3468.6291 1\n",
"2017-03-30 00:00:00\n",
"IF99 空 平仓 3406.5561141656326 1\n",
"2017-04-05 00:00:00\n",
"IF99 多 开仓 3474.3432 1\n",
"IF99 多 开仓 3491.1258202647214 1\n",
"IF99 多 开仓 3474.3432 1\n",
"IF99 多 开仓 3491.1258202647214 1\n",
"2017-04-17 00:00:00\n",
"IF99 空 平仓 3440.7779594705567 3\n",
"IF99 空 平仓 3440.7779594705567 1\n",
"2017-05-25 00:00:00\n",
"IF99 多 开仓 3433.5201 1\n",
"IF99 多 开仓 3449.826458335901 1\n",
"IF99 多 开仓 3466.1328166718017 1\n",
"2017-05-26 00:00:00\n",
"IF99 多 开仓 3482.4391750077025 1\n",
"2017-06-15 00:00:00\n",
"IF99 空 平仓 3498.115614786368 4\n",
"2017-06-22 00:00:00\n",
"IF99 多 开仓 3579.0624 1\n",
"2017-06-26 00:00:00\n",
"IF99 多 开仓 3597.53568462007 1\n",
"IF99 多 开仓 3616.0089692401393 1\n",
"IF99 多 开仓 3634.4822538602093 1\n",
"2017-08-11 00:00:00\n",
"IF99 空 平仓 3656.9743 4\n",
"2018-01-02 00:00:00\n",
"IF99 多 开仓 4105.3543 1\n",
"2018-01-03 00:00:00\n",
"IF99 多 开仓 4132.444330290369 1\n",
"2018-01-08 00:00:00\n",
"IF99 多 开仓 4159.534360580738 1\n",
"2018-01-09 00:00:00\n",
"IF99 多 开仓 4186.624390871108 1\n",
"2018-02-01 00:00:00\n",
"IF99 空 平仓 4253.1416 4\n",
"2018-07-24 00:00:00\n",
"IF99 多 开仓 3507.8183 1\n",
"IF99 多 开仓 3547.101357149508 1\n",
"2018-08-02 00:00:00\n",
"IF99 空 平仓 3389.9691285514746 2\n",
"2018-09-21 00:00:00\n",
"IF99 多 开仓 3407.2856 1\n",
"2018-09-26 00:00:00\n",
"IF99 多 开仓 3438.7157642602238 1\n",
"2018-10-08 00:00:00\n",
"IF99 空 平仓 3312.9951072193303 2\n"
]
}
],
"source": [
"for dt, l in engine.tradeDict.items():\n",
" print dt\n",
" for trade in l:\n",
" print trade.vtSymbol, trade.direction, trade.offset, trade.price, trade.volume"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": false
},
"outputs": [],
"source": [
"for result in engine.resultList:\n",
" print result.date, result.totalPnl"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"l = [result.totalPnl for result in engine.resultList]\n",
"equity = np.cumsum(l)\n",
"plt.plot(equity)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"for data in engine.dataDict.values():\n",
" print data"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print trade."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.14"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@ -0,0 +1,199 @@
# encoding: UTF-8
from datetime import datetime
from pymongo import MongoClient
from collections import OrderedDict, defaultdict
from vnpy.trader.vtObject import VtBarData
from vnpy.trader.vtConstant import DIRECTION_LONG, DIRECTION_SHORT
from turtleStrategy import TurtlePortfolio
DAILY_DB_NAME = 'VnTrader_Daily_Db'
########################################################################
class BacktestingEngine(object):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
self.portfolio = None
self.vtSymbolList = []
self.startDt = None
self.endDt = None
self.currentDt = None
self.dataDict = OrderedDict()
self.tradeDict = OrderedDict()
self.result = None
self.resultList = []
#----------------------------------------------------------------------
def setPeriod(self, startDt, endDt):
""""""
self.startDt = startDt
self.endDt = endDt
#----------------------------------------------------------------------
def initPortfolio(self, vtSymbolList):
""""""
self.vtSymbolList = vtSymbolList
self.portfolio = TurtlePortfolio(self)
self.portfolio.init(vtSymbolList)
#----------------------------------------------------------------------
def loadData(self):
""""""
mc = MongoClient()
db = mc[DAILY_DB_NAME]
for vtSymbol in self.vtSymbolList:
flt = {'datetime':{'$gte':self.startDt,
'$lte':self.endDt}}
collection = db[vtSymbol]
cursor = collection.find(flt).sort('datetime')
for d in cursor:
bar = VtBarData()
bar.__dict__ = d
barDict = self.dataDict.setdefault(bar.datetime, OrderedDict())
barDict[bar.vtSymbol] = bar
self.writeLog(u'%s数据加载完成,总数据量:%s' %(vtSymbol, cursor.count()))
self.writeLog(u'全部数据加载完成')
#----------------------------------------------------------------------
def runBacktesting(self):
""""""
for dt, barDict in self.dataDict.items():
self.currentDt = dt
result = DailyResult(dt)
result.updatePos(self.portfolio.posDict)
for bar in barDict.values():
self.portfolio.onBar(bar)
result.updateBar(bar)
if self.result:
result.updatePreviousClose(self.result.closeDict)
self.resultList.append(result)
self.result = result
#----------------------------------------------------------------------
def calculateResult(self):
""""""
for result in self.resultList:
result.calculatePnl()
#----------------------------------------------------------------------
def sendOrder(self, vtSymbol, direction, offset, price, volume):
""""""
trade = TradeData(vtSymbol, direction, offset, price, volume)
l = self.tradeDict.setdefault(self.currentDt, [])
l.append(trade)
self.result.updateTrade(trade)
#----------------------------------------------------------------------
def writeLog(self, content):
""""""
print '%s:%s' %(datetime.now().strftime('%H:%M:%S.%f'), content)
########################################################################
class TradeData(object):
""""""
#----------------------------------------------------------------------
def __init__(self, vtSymbol, direction, offset, price, volume):
"""Constructor"""
self.vtSymbol = vtSymbol
self.direction = direction
self.offset = offset
self.price = price
self.volume = volume
########################################################################
class DailyResult(object):
"""每日的成交记录"""
#----------------------------------------------------------------------
def __init__(self, date):
"""Constructor"""
self.date = date
self.closeDict = {} # 收盘价字典
self.previousCloseDict = {} # 昨收盘字典
self.tradeDict = defaultdict(list) # 成交字典
self.posDict = {} # 持仓字典(开盘时)
self.tradingPnl = 0
self.holdingPnl = 0
self.totalPnl = 0
#----------------------------------------------------------------------
def updateTrade(self, trade):
"""更新交易"""
l = self.tradeDict[trade.vtSymbol]
l.append(trade)
#----------------------------------------------------------------------
def updatePos(self, d):
"""更新昨持仓"""
self.posDict.update(d)
#----------------------------------------------------------------------
def updateBar(self, bar):
"""更新K线"""
self.closeDict[bar.vtSymbol] = bar.close
#----------------------------------------------------------------------
def updatePreviousClose(self, d):
"""更新昨收盘"""
self.previousCloseDict.update(d)
#----------------------------------------------------------------------
def calculateTradingPnl(self):
"""计算当日交易盈亏"""
for vtSymbol, l in self.tradeDict.items():
close = self.closeDict[vtSymbol]
for trade in l:
if trade.direction == DIRECTION_LONG:
side = 1
else:
side = -1
pnl = (close - trade.price) * trade.volume * side
self.tradingPnl += pnl
#----------------------------------------------------------------------
def calculateHoldingPnl(self):
"""计算当日持仓盈亏"""
for vtSymbol, pos in self.posDict.items():
previousClose = self.previousCloseDict.get(vtSymbol, 0)
close = self.closeDict[vtSymbol]
pnl = (close - previousClose) * pos
self.holdingPnl += pnl
#----------------------------------------------------------------------
def calculatePnl(self):
"""计算总盈亏"""
self.calculateHoldingPnl()
self.calculateTradingPnl()
self.totalPnl = self.holdingPnl + self.tradingPnl

View File

@ -0,0 +1,336 @@
# encoding: UTF-8
from collections import defaultdict
from vnpy.trader.vtConstant import (DIRECTION_LONG, DIRECTION_SHORT,
OFFSET_OPEN, OFFSET_CLOSE)
from vnpy.trader.vtUtility import ArrayManager
MAX_PRODUCT_POS = 4
MAX_DIRECTION_POS = 10
########################################################################
class TurtleResult(object):
"""一次完整的开平交易"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
self.pos = 0
self.entry = 0 # 开仓均价
self.exit = 0 # 平仓均价
self.pnl = 0 # 盈亏
#----------------------------------------------------------------------
def open(self, price, change):
"""开仓或者加仓"""
cost = self.pos * self.entry # 计算之前的开仓成本
cost += change * price # 加上新仓位的成本
self.pos += change # 加上新仓位的数量
self.entry = cost / self.pos # 计算新的平均开仓成本
#----------------------------------------------------------------------
def close(self, price):
"""平仓"""
self.exit = price
self.pnl = self.pos * (self.exit - self.entry)
########################################################################
class TurtleSignal(object):
"""海龟信号"""
#----------------------------------------------------------------------
def __init__(self, portfolio, vtSymbol,
entryWindow, exitWindow, atrWindow,
profitCheck=False):
"""Constructor"""
self.portfolio = portfolio # 投资组合
self.vtSymbol = vtSymbol # 合约代码
self.entryWindow = entryWindow # 入场通道周期数
self.exitWindow = exitWindow # 出场通道周期数
self.atrWindow = atrWindow # 计算ATR周期数
self.profitCheck = profitCheck # 是否检查上一笔盈利
self.am = ArrayManager() # K线容器
self.atrVolatility = 0 # ATR波动率
self.entryUp = 0 # 入场通道
self.entryDown = 0
self.exitUp = 0 # 出场通道
self.exitDown = 0
self.longEntry1 = 0 # 多头入场位
self.longEntry2 = 0
self.longEntry3 = 0
self.longEntry4 = 0
self.longStop = 0 # 多头止损位
self.shortEntry1 = 0 # 空头入场位
self.shortEntry2 = 0
self.shortEntry3 = 0
self.shortEntry4 = 0
self.shortStop = 0 # 空头止损位
self.pos = 0 # 信号持仓
self.result = None # 当前的交易
self.resultList = [] # 交易列表
#----------------------------------------------------------------------
def onBar(self, bar):
""""""
self.am.updateBar(bar)
if not self.am.inited:
return
self.generateSignal(bar)
self.calculateIndicator()
#----------------------------------------------------------------------
def generateSignal(self, bar):
"""
判断交易信号
要注意在任何一个数据点buy/sell/short/cover只允许执行一类动作
"""
# 如果指标尚未初始化,则忽略
if not self.longEntry1:
return
# 优先检查平仓
if self.pos > 0:
longExit = max(self.longStop, self.exitDown)
if bar.low <= longExit:
self.sell(longExit)
return
elif self.pos < 0:
shortExit = min(self.shortStop, self.exitUp)
if bar.high >= shortExit:
self.cover(shortExit)
return
# 没有仓位或者持有多头仓位的时候,可以做多(加仓)
if self.pos >= 0:
trade = False
if bar.high >= self.longEntry1 and self.pos < 1:
self.buy(self.longEntry1, 1)
trade = True
if bar.high >= self.longEntry2 and self.pos < 2:
self.buy(self.longEntry2, 1)
trade = True
if bar.high >= self.longEntry3 and self.pos < 3:
self.buy(self.longEntry3, 1)
trade = True
if bar.high >= self.longEntry4 and self.pos < 4:
self.buy(self.longEntry4, 1)
trade = True
if trade:
return
# 没有仓位或者持有空头仓位的时候,可以做空(加仓)
elif self.pos <= 0:
if bar.low <= self.shortEntry1 and self.pos > -1:
self.short(self.shortEntry1, 1)
if bar.low <= self.shortEntry2 and self.pos > -2:
self.short(self.shortEntry2, 1)
if bar.low <= self.shortEntry3 and self.pos > -3:
self.short(self.shortEntry3, 1)
if bar.low <= self.shortEntry4 and self.pos > -4:
self.short(self.shortEntry4, 1)
#----------------------------------------------------------------------
def calculateIndicator(self):
"""计算技术指标"""
self.entryUp, self.entryDown = self.am.donchian(self.entryWindow)
self.exitUp, self.exitDown = self.am.donchian(self.exitWindow)
if not self.pos:
self.atrVolatility = self.am.atr(self.atrWindow)
self.longEntry1 = self.entryUp
self.longEntry2 = self.entryUp + self.atrVolatility * 0.5
self.longEntry3 = self.entryUp + self.atrVolatility * 1
self.longEntry4 = self.entryUp + self.atrVolatility * 1.5
self.shortEntry1 = self.entryDown
self.shortEntry2 = self.entryDown - self.atrVolatility * 0.5
self.shortEntry3 = self.entryDown - self.atrVolatility * 1
self.shortEntry4 = self.entryDown - self.atrVolatility * 1.5
#----------------------------------------------------------------------
def newSignal(self, direction, offset, price, volume):
""""""
self.portfolio.newSignal(self, direction, offset, price, volume)
#----------------------------------------------------------------------
def buy(self, price, volume):
"""买入开仓"""
self.open(price, volume)
self.newSignal(DIRECTION_LONG, OFFSET_OPEN, price, volume)
# 以最后一次加仓价格加上两倍N计算止损
self.longStop = price - self.atrVolatility * 2
#----------------------------------------------------------------------
def sell(self, price):
"""卖出平仓"""
volume = abs(self.pos)
self.close(price)
self.newSignal(DIRECTION_SHORT, OFFSET_CLOSE, price, volume)
#----------------------------------------------------------------------
def short(self, price, volume):
"""卖出开仓"""
self.open(price, -volume)
self.newSignal(DIRECTION_SHORT, OFFSET_OPEN, price, volume)
# 以最后一次加仓价格加上两倍N计算止损
self.shortStop = price + self.atrVolatility * 2
#----------------------------------------------------------------------
def cover(self, price):
"""买入平仓"""
volume = abs(self.pos)
self.close(price)
self.newSignal(DIRECTION_LONG, OFFSET_CLOSE, price, volume)
#----------------------------------------------------------------------
def open(self, price, change):
"""开仓"""
self.pos += change
if not self.result:
self.result = TurtleResult()
self.result.open(price, change)
#----------------------------------------------------------------------
def close(self, price):
"""平仓"""
self.pos = 0
self.result.close(price)
self.resultList.append(self.result)
self.result = None
#----------------------------------------------------------------------
def lastPnl(self):
"""上一笔交易的盈亏"""
if not self.resultList:
return 0
result = self.resultList[-1]
return result.pnl
########################################################################
class TurtlePortfolio(object):
"""海龟组合"""
#----------------------------------------------------------------------
def __init__(self, engine):
"""Constructor"""
self.engine = engine
self.signalDict = defaultdict(list)
self.posDict = {} # 每个品种的持仓情况
self.totalLong = 0 # 总的多头持仓
self.totalShort = 0 # 总的空头持仓
#----------------------------------------------------------------------
def init(self, vtSymbolList):
""""""
for vtSymbol in vtSymbolList:
signal1 = TurtleSignal(self, vtSymbol, 20, 10, 20, True)
signal2 = TurtleSignal(self, vtSymbol, 55, 20, 20, False)
l = self.signalDict[vtSymbol]
l.append(signal1)
l.append(signal2)
self.posDict[vtSymbol] = 0
#----------------------------------------------------------------------
def onBar(self, bar):
""""""
for signal in self.signalDict[bar.vtSymbol]:
signal.onBar(bar)
#----------------------------------------------------------------------
def newSignal(self, signal, direction, offset, price, volume):
"""对交易信号进行过滤,符合条件的才发单执行"""
pos = self.posDict[signal.vtSymbol]
# 开仓
if offset == OFFSET_OPEN:
# 检查上一次是否为盈利
if signal.profitCheck:
pnl = signal.lastPnl()
if pnl > 0:
return
# 买入
if direction == DIRECTION_LONG:
# 组合持仓不能超过上限
if self.totalLong >= MAX_DIRECTION_POS:
return
# 单品种持仓不能超过上限
if self.posDict[signal.vtSymbol] >= MAX_PRODUCT_POS:
return
# 卖出
else:
if self.totalShort <= -MAX_DIRECTION_POS:
return
if self.posDict[signal.vtSymbol] <= -MAX_PRODUCT_POS:
return
# 平仓
else:
if direction == DIRECTION_LONG:
# 必须有空头持仓
if pos >= 0:
return
# 平仓数量不能超过空头持仓
volume = min(volume, abs(pos))
else:
if pos <= 0:
return
volume = min(volume, abs(pos))
self.sendOrder(signal.vtSymbol, direction, offset, price, volume)
#----------------------------------------------------------------------
def sendOrder(self, vtSymbol, direction, offset, price, volume):
""""""
# 计算合约持仓
if direction == DIRECTION_LONG:
self.posDict[vtSymbol] += volume
else:
self.posDict[vtSymbol] -= volume
# 计算总持仓
self.totalLong = 0
self.totalShort = 0
for pos in self.posDict.values():
if pos > 0:
self.totalLong += pos
elif pos < 0:
self.totalShort += pos
# 向回测引擎中发单记录
self.engine.sendOrder(vtSymbol, direction, offset, price, volume)