diff --git a/examples/TurtleStrategy/.ipynb_checkpoints/run-checkpoint.ipynb b/examples/TurtleStrategy/.ipynb_checkpoints/run-checkpoint.ipynb new file mode 100644 index 00000000..38a3e42a --- /dev/null +++ b/examples/TurtleStrategy/.ipynb_checkpoints/run-checkpoint.ipynb @@ -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\u001b[0m in \u001b[0;36m\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 +} diff --git a/examples/TurtleStrategy/run.ipynb b/examples/TurtleStrategy/run.ipynb new file mode 100644 index 00000000..70eb452e --- /dev/null +++ b/examples/TurtleStrategy/run.ipynb @@ -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 +} diff --git a/examples/TurtleStrategy/turtleEngine.py b/examples/TurtleStrategy/turtleEngine.py new file mode 100644 index 00000000..2455d72a --- /dev/null +++ b/examples/TurtleStrategy/turtleEngine.py @@ -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 + \ No newline at end of file diff --git a/examples/TurtleStrategy/turtleStrategy.py b/examples/TurtleStrategy/turtleStrategy.py new file mode 100644 index 00000000..a6ce811e --- /dev/null +++ b/examples/TurtleStrategy/turtleStrategy.py @@ -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) + \ No newline at end of file