PyAlgoTrade 0.20 中文文档(一)(1)https://developer.aliyun.com/article/1524140
交易
让我们用一个简单的策略继续,这次模拟实际交易。这个想法非常简单:
- 如果调整后的收盘价高于 SMA(15),我们就进入多头头寸(我们放置一个市价买单)。
- 如果已经存在多头头寸,并且调整后的收盘价低于 SMA(15),我们就退出多头头寸(我们放置一个卖市价单)。
from __future__ import print_function from pyalgotrade import strategy from pyalgotrade.barfeed import quandlfeed from pyalgotrade.technical import ma class MyStrategy(strategy.BacktestingStrategy): def __init__(self, feed, instrument, smaPeriod): super(MyStrategy, self).__init__(feed, 1000) self.__position = None self.__instrument = instrument # We'll use adjusted close values instead of regular close values. self.setUseAdjustedValues(True) self.__sma = ma.SMA(feed[instrument].getPriceDataSeries(), smaPeriod) def onEnterOk(self, position): execInfo = position.getEntryOrder().getExecutionInfo() self.info("BUY at $%.2f" % (execInfo.getPrice())) def onEnterCanceled(self, position): self.__position = None def onExitOk(self, position): execInfo = position.getExitOrder().getExecutionInfo() self.info("SELL at $%.2f" % (execInfo.getPrice())) self.__position = None def onExitCanceled(self, position): # If the exit was canceled, re-submit it. self.__position.exitMarket() def onBars(self, bars): # Wait for enough bars to be available to calculate a SMA. if self.__sma[-1] is None: return bar = bars[self.__instrument] # If a position was not opened, check if we should enter a long position. if self.__position is None: if bar.getPrice() > self.__sma[-1]: # Enter a buy market order for 10 shares. The order is good till canceled. self.__position = self.enterLong(self.__instrument, 10, True) # Check if we have to exit the position. elif bar.getPrice() < self.__sma[-1] and not self.__position.exitActive(): self.__position.exitMarket() def run_strategy(smaPeriod): # Load the bar feed from the CSV file feed = quandlfeed.Feed() feed.addBarsFromCSV("orcl", "WIKI-ORCL-2000-quandl.csv") # Evaluate the strategy with the feed. myStrategy = MyStrategy(feed, "orcl", smaPeriod) myStrategy.run() print("Final portfolio value: $%.2f" % myStrategy.getBroker().getEquity()) run_strategy(15)
如果你运行脚本,你应该会看到类似于这样的东西:
2000-01-26 00:00:00 strategy [INFO] BUY at $25.84 2000-01-28 00:00:00 strategy [INFO] SELL at $23.45 2000-02-03 00:00:00 strategy [INFO] BUY at $25.22 2000-02-22 00:00:00 strategy [INFO] SELL at $26.92 2000-02-23 00:00:00 strategy [INFO] BUY at $27.41 2000-03-31 00:00:00 strategy [INFO] SELL at $36.51 2000-04-07 00:00:00 strategy [INFO] BUY at $38.11 2000-04-12 00:00:00 strategy [INFO] SELL at $35.49 2000-04-19 00:00:00 strategy [INFO] BUY at $35.80 2000-04-20 00:00:00 strategy [INFO] SELL at $33.61 2000-04-28 00:00:00 strategy [INFO] BUY at $35.74 2000-05-05 00:00:00 strategy [INFO] SELL at $33.70 2000-05-08 00:00:00 strategy [INFO] BUY at $34.29 2000-05-09 00:00:00 strategy [INFO] SELL at $33.55 2000-05-16 00:00:00 strategy [INFO] BUY at $35.35 2000-05-19 00:00:00 strategy [INFO] SELL at $32.78 2000-05-31 00:00:00 strategy [INFO] BUY at $33.35 2000-06-23 00:00:00 strategy [INFO] SELL at $36.80 2000-06-27 00:00:00 strategy [INFO] BUY at $37.51 2000-06-28 00:00:00 strategy [INFO] SELL at $37.37 2000-06-29 00:00:00 strategy [INFO] BUY at $37.37 2000-06-30 00:00:00 strategy [INFO] SELL at $36.60 2000-07-03 00:00:00 strategy [INFO] BUY at $36.94 2000-07-05 00:00:00 strategy [INFO] SELL at $34.97 2000-07-21 00:00:00 strategy [INFO] BUY at $35.26 2000-07-24 00:00:00 strategy [INFO] SELL at $35.12 2000-07-26 00:00:00 strategy [INFO] BUY at $34.06 2000-07-28 00:00:00 strategy [INFO] SELL at $34.21 2000-08-01 00:00:00 strategy [INFO] BUY at $34.24 2000-08-02 00:00:00 strategy [INFO] SELL at $33.24 2000-08-04 00:00:00 strategy [INFO] BUY at $35.66 2000-09-11 00:00:00 strategy [INFO] SELL at $39.19 2000-09-29 00:00:00 strategy [INFO] BUY at $37.05 2000-10-02 00:00:00 strategy [INFO] SELL at $36.31 2000-10-20 00:00:00 strategy [INFO] BUY at $32.90 2000-10-31 00:00:00 strategy [INFO] SELL at $29.72 2000-11-20 00:00:00 strategy [INFO] BUY at $22.14 2000-11-21 00:00:00 strategy [INFO] SELL at $22.59 2000-12-01 00:00:00 strategy [INFO] BUY at $24.02 2000-12-15 00:00:00 strategy [INFO] SELL at $26.81 2000-12-18 00:00:00 strategy [INFO] BUY at $27.32 2000-12-21 00:00:00 strategy [INFO] SELL at $25.33 2000-12-22 00:00:00 strategy [INFO] BUY at $27.67 Final portfolio value: $974.87
但是,如果我们使用 30 作为 SMA 周期,而不是 15?那会产生更好的结果还是更差的结果?我们当然可以做这样的事情:
for i in range(10, 30): run_strategy(i)
我们会发现,使用 SMA(20)可以获得更好的结果:
Final portfolio value: $1071.03
如果我们只需要尝试有限的参数值集合,那么这是可以的。但是如果我们必须测试一个具有多个参数的策略,那么串行方法肯定不会随着策略变得更加复杂而扩展。
优化
满足优化器组件。这个想法非常简单:
- 有一个负责的服务器:
- 提供运行策略的条形图。
- 提供运行策略的参数。
- 记录每个工作人员的策略结果。
- 有多个负责的工作人员:
- 使用服务器提供的条形图和参数运行策略。
为了说明这一点,我们将使用一个称为RSI2的策略,它需要以下参数:
- 用于趋势识别的 SMA 周期。我们将其称为 entrySMA,范围在 150 到 250 之间。
- 退出点使用较小的 SMA 周期。我们将其称为 exitSMA,范围在 5 到 15 之间。
- 用于进入短头寸/多头头寸的 RSI 周期。我们将其称为 rsiPeriod,范围在 2 到 10 之间。
- 用于多头头寸进入的 RSI 超卖阈值。我们将其称为 overSoldThreshold,范围在 5 到 25 之间。
- 用于短头寸进入的 RSI 超买阈值。我们将其称为 overBoughtThreshold,范围在 75 到 95 之间。
如果我的数学没错的话,这些是 4409559 个不同的组合。
对于一个参数集测试该策略大约需要 0.16 秒。如果我串行执行所有组合,那么评估它们并找到最佳参数组合将需要大约 8.5 天的时间。那是一个很长的时间,但如果我可以让十台 8 核计算机来做这个工作,那么总时间将缩短到约 2.5 小时。
长话短说,我们需要并行化。
让我们从下载“IBM”的 3 年每日柱开始:
python -m "pyalgotrade.tools.quandl" --source-code="WIKI" --table-code="IBM" --from-year=2009 --to-year=2011 --storage=. --force-download --frequency=daily
将此代码保存为 rsi2.py:
from pyalgotrade import strategy from pyalgotrade.technical import ma from pyalgotrade.technical import rsi from pyalgotrade.technical import cross class RSI2(strategy.BacktestingStrategy): def __init__(self, feed, instrument, entrySMA, exitSMA, rsiPeriod, overBoughtThreshold, overSoldThreshold): super(RSI2, self).__init__(feed) self.__instrument = instrument # We'll use adjusted close values, if available, instead of regular close values. if feed.barsHaveAdjClose(): self.setUseAdjustedValues(True) self.__priceDS = feed[instrument].getPriceDataSeries() self.__entrySMA = ma.SMA(self.__priceDS, entrySMA) self.__exitSMA = ma.SMA(self.__priceDS, exitSMA) self.__rsi = rsi.RSI(self.__priceDS, rsiPeriod) self.__overBoughtThreshold = overBoughtThreshold self.__overSoldThreshold = overSoldThreshold self.__longPos = None self.__shortPos = None def getEntrySMA(self): return self.__entrySMA def getExitSMA(self): return self.__exitSMA def getRSI(self): return self.__rsi def onEnterCanceled(self, position): if self.__longPos == position: self.__longPos = None elif self.__shortPos == position: self.__shortPos = None else: assert(False) def onExitOk(self, position): if self.__longPos == position: self.__longPos = None elif self.__shortPos == position: self.__shortPos = None else: assert(False) def onExitCanceled(self, position): # If the exit was canceled, re-submit it. position.exitMarket() def onBars(self, bars): # Wait for enough bars to be available to calculate SMA and RSI. if self.__exitSMA[-1] is None or self.__entrySMA[-1] is None or self.__rsi[-1] is None: return bar = bars[self.__instrument] if self.__longPos is not None: if self.exitLongSignal(): self.__longPos.exitMarket() elif self.__shortPos is not None: if self.exitShortSignal(): self.__shortPos.exitMarket() else: if self.enterLongSignal(bar): shares = int(self.getBroker().getCash() * 0.9 / bars[self.__instrument].getPrice()) self.__longPos = self.enterLong(self.__instrument, shares, True) elif self.enterShortSignal(bar): shares = int(self.getBroker().getCash() * 0.9 / bars[self.__instrument].getPrice()) self.__shortPos = self.enterShort(self.__instrument, shares, True) def enterLongSignal(self, bar): return bar.getPrice() > self.__entrySMA[-1] and self.__rsi[-1] <= self.__overSoldThreshold def exitLongSignal(self): return cross.cross_above(self.__priceDS, self.__exitSMA) and not self.__longPos.exitActive() def enterShortSignal(self, bar): return bar.getPrice() < self.__entrySMA[-1] and self.__rsi[-1] >= self.__overBoughtThreshold def exitShortSignal(self): return cross.cross_below(self.__priceDS, self.__exitSMA) and not self.__shortPos.exitActive()
这是服务器脚本:
import itertools from pyalgotrade.optimizer import server from pyalgotrade.barfeed import quandlfeed def parameters_generator(): instrument = ["ibm"] entrySMA = range(150, 251) exitSMA = range(5, 16) rsiPeriod = range(2, 11) overBoughtThreshold = range(75, 96) overSoldThreshold = range(5, 26) return itertools.product(instrument, entrySMA, exitSMA, rsiPeriod, overBoughtThreshold, overSoldThreshold) # The if __name__ == '__main__' part is necessary if running on Windows. if __name__ == '__main__': # Load the bar feed from the CSV files. feed = quandlfeed.Feed() feed.addBarsFromCSV("ibm", "WIKI-IBM-2009-quandl.csv") feed.addBarsFromCSV("ibm", "WIKI-IBM-2010-quandl.csv") feed.addBarsFromCSV("ibm", "WIKI-IBM-2011-quandl.csv") # Run the server. server.serve(feed, parameters_generator(), "localhost", 5000)
服务器代码正在做 3 件事:
- 声明一个生成器函数,产生策略的不同参数组合。
- 使用我们下载的 CSV 文件加载源数据。
- 运行服务器,它将在端口 5000 上等待传入连接。
这是工作脚本,使用 pyalgotrade.optimizer.worker 模块并行运行由服务器提供数据的策略:
from pyalgotrade.optimizer import worker import rsi2 # The if __name__ == '__main__' part is necessary if running on Windows. if __name__ == '__main__': worker.run(rsi2.RSI2, "localhost", 5000, workerName="localworker")
当你运行服务器和客户端时,你会在服务器控制台上看到类似这样的东西:
2017-07-21 22:56:51,944 pyalgotrade.optimizer.server [INFO] Starting server 2017-07-21 22:56:51,944 pyalgotrade.optimizer.xmlrpcserver [INFO] Loading bars 2017-07-21 22:56:52,609 pyalgotrade.optimizer.xmlrpcserver [INFO] Started serving 2017-07-21 22:58:50,073 pyalgotrade.optimizer.xmlrpcserver [INFO] Best result so far 1261295.07089 with parameters ('ibm', 150, 5, 2, 83, 24) . .
以及在工作节点控制台上看到类似这样的东西:
2017-07-21 22:56:57,884 localworker [INFO] Started running 2017-07-21 22:56:57,884 localworker [INFO] Started running 2017-07-21 22:56:58,439 localworker [INFO] Running strategy with parameters ('ibm', 150, 5, 2, 84, 15) 2017-07-21 22:56:58,498 localworker [INFO] Running strategy with parameters ('ibm', 150, 5, 2, 94, 5) 2017-07-21 22:56:58,918 localworker [INFO] Result 1137855.88871 2017-07-21 22:56:58,918 localworker [INFO] Running strategy with parameters ('ibm', 150, 5, 2, 84, 14) 2017-07-21 22:56:58,996 localworker [INFO] Result 1027761.85581 2017-07-21 22:56:58,997 localworker [INFO] Running strategy with parameters ('ibm', 150, 5, 2, 93, 25) 2017-07-21 22:56:59,427 localworker [INFO] Result 1092194.67448 2017-07-21 22:57:00,016 localworker [INFO] Result 1260766.64479 . .
请注意,你应该运行 只有一个服务器和一个或多个工作节点。
如果你只想在自己的桌面上并行运行策略,你可以像这样利用 pyalgotrade.optimizer.local 模块:
import itertools from pyalgotrade.optimizer import local from pyalgotrade.barfeed import quandlfeed import rsi2 def parameters_generator(): instrument = ["ibm"] entrySMA = range(150, 251) exitSMA = range(5, 16) rsiPeriod = range(2, 11) overBoughtThreshold = range(75, 96) overSoldThreshold = range(5, 26) return itertools.product(instrument, entrySMA, exitSMA, rsiPeriod, overBoughtThreshold, overSoldThreshold) # The if __name__ == '__main__' part is necessary if running on Windows. if __name__ == '__main__': # Load the bar feed from the CSV files. feed = quandlfeed.Feed() feed.addBarsFromCSV("ibm", "WIKI-IBM-2009-quandl.csv") feed.addBarsFromCSV("ibm", "WIKI-IBM-2010-quandl.csv") feed.addBarsFromCSV("ibm", "WIKI-IBM-2011-quandl.csv") local.run(rsi2.RSI2, feed, parameters_generator())
该代码正在做 3 件事:
- 声明一个生成器函数,产生不同的参数组合。
- 使用我们下载的 CSV 文件加载源数据。
- 使用 pyalgotrade.optimizer.local 模块并行运行策略,找到最佳结果。
当你运行此代码时,你应该看到类似这样的东西:
2017-07-21 22:59:26,921 pyalgotrade.optimizer.local [INFO] Starting server 2017-07-21 22:59:26,922 pyalgotrade.optimizer.xmlrpcserver [INFO] Loading bars 2017-07-21 22:59:26,922 pyalgotrade.optimizer.local [INFO] Starting workers 2017-07-21 22:59:27,642 pyalgotrade.optimizer.xmlrpcserver [INFO] Started serving 2017-07-21 23:01:14,306 pyalgotrade.optimizer.xmlrpcserver [INFO] Best result so far 1261295.07089 with parameters ('ibm', 150, 5, 2, 83, 24) . .
绘图
PyAlgoTrade 可以很容易地绘制策略执行。
将此保存为 sma_crossover.py:
from pyalgotrade import strategy from pyalgotrade.technical import ma from pyalgotrade.technical import cross class SMACrossOver(strategy.BacktestingStrategy): def __init__(self, feed, instrument, smaPeriod): super(SMACrossOver, self).__init__(feed) self.__instrument = instrument self.__position = None # We'll use adjusted close values instead of regular close values. self.setUseAdjustedValues(True) self.__prices = feed[instrument].getPriceDataSeries() self.__sma = ma.SMA(self.__prices, smaPeriod) def getSMA(self): return self.__sma def onEnterCanceled(self, position): self.__position = None def onExitOk(self, position): self.__position = None def onExitCanceled(self, position): # If the exit was canceled, re-submit it. self.__position.exitMarket() def onBars(self, bars): # If a position was not opened, check if we should enter a long position. if self.__position is None: if cross.cross_above(self.__prices, self.__sma) > 0: shares = int(self.getBroker().getCash() * 0.9 / bars[self.__instrument].getPrice()) # Enter a buy market order. The order is good till canceled. self.__position = self.enterLong(self.__instrument, shares, True) # Check if we have to exit the position. elif not self.__position.exitActive() and cross.cross_below(self.__prices, self.__sma) > 0: self.__position.exitMarket()
PyAlgoTrade 0.20 中文文档(一)(3)https://developer.aliyun.com/article/1524142