美文网首页
股票量化分析入门——PyAlgoTrade 中文文档

股票量化分析入门——PyAlgoTrade 中文文档

作者: coolwind | 来源:发表于2018-06-11 16:37 被阅读295次

    因为要学习,所以没找到官方手册的中文版。所以翻译一下,翻译的过程自己能够仔细阅读,翻译的结果也能给大家做一下参考。翻译的对应的版本是0.18,对应的原文地址在这里

    分隔线以下为原文的翻译


    教程

    编写本教程的目的是提供 PyAlgoTrade 的快速入门。就像介绍里所说的, PyAlgoTrade 的目标是帮助你对股票交易的策略进行回测。或许你自己有一个交易的策略,并且你希望能够在历史数据上使用这个策略做模拟交易,一遍查看这个策略的效果。使用 PyAlgoTrade 只需要做很少的工作,就可以帮助你实现这个目标。

    在继续之前我需要向 Pablo Jorge 表达我衷心的谢意,他帮助我校对了最初的设计和文档。

    这个教程是在 UNIX 环境之下制作的,但是其中的步骤要用于 Windows 环境也很简单。

    PyAlgoTrade 有6个主要的概念:

    • 策略
      交易的逻辑就是顶级在策略类中。包含何时买入、何时卖出等。

    • 数据源
      这个一个抽象的数据提供源。例如,你可以使用 CSV 数据源,它可以从 CSV(每一行代表一条数据,行内使用逗号分隔的文件)文件将历史数据提供给一个交易策略。数据源不限于历史价格数据。例如,Twitter的数据源可以将 Tiwtter 的事件整合进交易决策中。

    • 经纪商
      经纪商负责执行交易指令。

    • 数据序列(DataSeries)
      数据序列是一个用户管理时间序列数据的抽象类。

    • 技术指标(Technicals)
      这里有一系列的过滤工具用户在数据序列之上进行计算。例如 SMA(简单移动平均),RSI(相对强弱指数)等。这些过滤工具都集成为数据序列的装饰器。

    • 优化器(Optimizer)
      这里有一系列的类,可以帮助你将回测分发到不同的计算机上,或者交给同一台计算机的多个线程,也可以分到多台计算机的多个线程。他们让水平分割计算量变得十分简单。

    说了这么多,我们首先需要做的就是在一些数据上测试我们的交易策略。让我们使用2000年 Oracle 的股票价格数据,这些数据我们可以通过如下的命令下载到:

    python -c "from pyalgotrade.tools import yahoofinance; yahoofinance.download_daily_bars('orcl', 2000, 'orcl-2000.csv')"
    

    pyalgotrade.tools.yahoofinance 包从哑无财经下载 CSV 格式的数据。orcl-2000.csv 文件看起来像这个样子:

    Date,Open,High,Low,Close,Volume,Adj Close
    2000-12-29,30.87,31.31,28.69,29.06,31655500,28.35
    2000-12-28,30.56,31.12,30.37,31.06,25055600,30.30
    2000-12-27,30.37,31.06,29.37,30.69,26441700,29.94
    .
    .
    2000-01-04,115.50,118.62,105.00,107.69,116850000,26.26
    2000-01-03,124.62,125.19,111.62,118.12,98122000,28.81</pre>
    

    让我们从一个简单的策略开始,它的功能仅仅是打印出收盘价格,代码如下:

    from pyalgotrade import strategy
    from pyalgotrade.barfeed import yahoofeed
    
    class MyStrategy(strategy.BacktestingStrategy):
        def __init__(self, feed, instrument):
            super(MyStrategy, self).__init__(feed)
            self.__instrument = instrument
    
        def onBars(self, bars):
            bar = bars[self.__instrument]
            self.info(bar.getClose())
    
    #从 CSV 文件加载yahoo数据源
    feed = yahoofeed.Feed()
    feed.addBarsFromCSV("orcl", "orcl-2000.csv")
    
    # 在数据源的记录中逐条执行策略。
    myStrategy = MyStrategy(feed, "orcl")
    myStrategy.run()
    

    以上代码主要做了如下三件事:

    1. 申明一个新的策略。其中之定义了一个方法 onBars,这个方法将使用数据源的每一条数据作为参数,逐条调用。
    2. 从 CSV 文件加载数据源。
    3. 使用数据源提供给的每一条数据运行所定义的策略。

    如果你运行这个脚本,你将会看到顺序排列的收盘价:

    2000-01-03 00:00:00 strategy [INFO] 118.12
    2000-01-04 00:00:00 strategy [INFO] 107.69
    2000-01-05 00:00:00 strategy [INFO] 102.0
    .
    .
    .
    2000-12-27 00:00:00 strategy [INFO] 30.69
    2000-12-28 00:00:00 strategy [INFO] 31.06
    2000-12-29 00:00:00 strategy [INFO] 29.06
    

    让我们前进一小步,将策略调整为打印收盘价的简单移动平均值,从而演示如何使用技术指标:

    from pyalgotrade import strategy
    from pyalgotrade.barfeed import yahoofeed
    from pyalgotrade.technical import ma
    
    class MyStrategy(strategy.BacktestingStrategy):
        def __init__(self, feed, instrument):
            super(MyStrategy, self).__init__(feed)
            # 我们希望以15作为周期在收盘加上计算 SMA 。
            self.__sma = ma.SMA(feed[instrument].getCloseDataSeries(), 15)
            self.__instrument = instrument
    
        def onBars(self, bars):
            bar = bars[self.__instrument]
            self.info("%s  %s" % (bar.getClose(), self.__sma[-1]))
    
    # 从 CSV 文件加载雅虎的数据源
    feed = yahoofeed.Feed()
    feed.addBarsFromCSV("orcl", "orcl-2000.csv")
    
    # 使用数据源中的数据逐条运行策略。
    myStrategy = MyStrategy(feed, "orcl")
    myStrategy.run()
    

    这和之前的那个示例很像,除了:
    如果你运行这个脚本你将看到收盘价和对应的移动平均值,但是前14条记录的移动平均值是没有的。这是因为我们需要至少15条记录来得出移动平均值:

    2000-01-03 00:00:00 strategy [INFO] 118.12 None
    2000-01-04 00:00:00 strategy [INFO] 107.69 None
    2000-01-05 00:00:00 strategy [INFO] 102.0 None
    2000-01-06 00:00:00 strategy [INFO] 96.0 None
    2000-01-07 00:00:00 strategy [INFO] 103.37 None
    2000-01-10 00:00:00 strategy [INFO] 115.75 None
    2000-01-11 00:00:00 strategy [INFO] 112.37 None
    2000-01-12 00:00:00 strategy [INFO] 105.62 None
    2000-01-13 00:00:00 strategy [INFO] 105.06 None
    2000-01-14 00:00:00 strategy [INFO] 106.81 None
    2000-01-18 00:00:00 strategy [INFO] 111.25 None
    2000-01-19 00:00:00 strategy [INFO] 57.13 None
    2000-01-20 00:00:00 strategy [INFO] 59.25 None
    2000-01-21 00:00:00 strategy [INFO] 59.69 None
    2000-01-24 00:00:00 strategy [INFO] 54.19 94.2866666667
    2000-01-25 00:00:00 strategy [INFO] 56.44 90.1746666667
    .
    .
    .
    2000-12-27 00:00:00 strategy [INFO] 30.69 29.9866666667
    2000-12-28 00:00:00 strategy [INFO] 31.06 30.0446666667
    2000-12-29 00:00:00 strategy [INFO] 29.06 30.0946666667
    

    当值在给定的时间区间上无法被计算出来的时候,所有的技术指标都会返回 None。

    关于技术指标一个很重要的点是他们可以组合使用。这是因为它们都被设计为了数据序列。例如,基于收盘价计算 RSI,再基于RSI 计算 SMA,要实现上述操作十分简单,代码如下:

    from pyalgotrade import strategy
    from pyalgotrade.barfeed import yahoofeed
    from pyalgotrade.technical import ma
    from pyalgotrade.technical import rsi
    
    class MyStrategy(strategy.BacktestingStrategy):
        def __init__(self, feed, instrument):
            super(MyStrategy, self).__init__(feed)
            self.__rsi = rsi.RSI(feed[instrument].getCloseDataSeries(), 14)
            self.__sma = ma.SMA(self.__rsi, 15)
            self.__instrument = instrument
    
        def onBars(self, bars):
            bar = bars[self.__instrument]
            self.info("%s  %s  %s" % (bar.getClose(), self.__rsi[-1], self.__sma[-1]))
    
    # 从 CSV 文件加载雅虎数据源
    feed = yahoofeed.Feed()
    feed.addBarsFromCSV("orcl", "orcl-2000.csv")
    
    # 使用数据源中的数据逐条运行策略。
    myStrategy = MyStrategy(feed, "orcl")
    myStrategy.run()
    

    如果以运行这个脚本,将看到好几个值,如下边这样:

    2000-01-03 00:00:00 strategy [INFO] 118.12 None None
    2000-01-04 00:00:00 strategy [INFO] 107.69 None None
    2000-01-05 00:00:00 strategy [INFO] 102.0 None None
    2000-01-06 00:00:00 strategy [INFO] 96.0 None None
    2000-01-07 00:00:00 strategy [INFO] 103.37 None None
    2000-01-10 00:00:00 strategy [INFO] 115.75 None None
    2000-01-11 00:00:00 strategy [INFO] 112.37 None None
    2000-01-12 00:00:00 strategy [INFO] 105.62 None None
    2000-01-13 00:00:00 strategy [INFO] 105.06 None None
    2000-01-14 00:00:00 strategy [INFO] 106.81 None None
    2000-01-18 00:00:00 strategy [INFO] 111.25 None None
    2000-01-19 00:00:00 strategy [INFO] 57.13 None None
    2000-01-20 00:00:00 strategy [INFO] 59.25 None None
    2000-01-21 00:00:00 strategy [INFO] 59.69 None None
    2000-01-24 00:00:00 strategy [INFO] 54.19 23.5673530141 None
    2000-01-25 00:00:00 strategy [INFO] 56.44 25.0687519877 None
    2000-01-26 00:00:00 strategy [INFO] 55.06 24.7476577095 None
    2000-01-27 00:00:00 strategy [INFO] 51.81 23.9690136517 None
    2000-01-28 00:00:00 strategy [INFO] 47.38 22.9108539956 None
    2000-01-31 00:00:00 strategy [INFO] 49.95 24.980004823 None
    2000-02-01 00:00:00 strategy [INFO] 54.0 28.2484181864 None
    2000-02-02 00:00:00 strategy [INFO] 54.31 28.505177315 None
    2000-02-03 00:00:00 strategy [INFO] 56.69 30.5596770599 None
    2000-02-04 00:00:00 strategy [INFO] 57.81 31.5564353751 None
    2000-02-07 00:00:00 strategy [INFO] 59.94 33.5111056589 None
    2000-02-08 00:00:00 strategy [INFO] 59.56 33.3282358994 None
    2000-02-09 00:00:00 strategy [INFO] 59.94 33.7177605915 None
    2000-02-10 00:00:00 strategy [INFO] 62.31 36.2205441255 None
    2000-02-11 00:00:00 strategy [INFO] 59.69 34.6623493641 29.0368892505
    2000-02-14 00:00:00 strategy [INFO] 62.19 37.4284445543 29.9609620198
    .
    .
    .
    2000-12-27 00:00:00 strategy [INFO] 30.69 51.3196802735 49.8506368511
    2000-12-28 00:00:00 strategy [INFO] 31.06 52.1646203455 49.997518354
    2000-12-29 00:00:00 strategy [INFO] 29.06 47.3776678335 50.0790646925
    

    交易

    让我们将策略在推进一小步,这次我们模拟真实的交易。这个主意很简单:

    from pyalgotrade import strategy
    from pyalgotrade.barfeed import yahoofeed
    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
            # 我们使用已调整收盘价代替常规的收盘价.
            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):
            # 如果退出被取消了,则再次提交它
            self.__position.exitMarket()
    
        def onBars(self, bars):
            # 等待足够的数据条数,以便计算SMA。
            if self.__sma[-1] is None:
                return
    
            bar = bars[self.__instrument]
            # 如果一个 position 还没打开,检查是否应该进入一个 long position。
            if self.__position is None:
                if bar.getPrice() > self.__sma[-1]:
                    # 计入一个买单,买入10手。只要不取消就一直尝试。
                    self.__position = self.enterLong(self.__instrument, 10, True)
            # 检查我们是否必须退出这个 position.
            elif bar.getPrice() < self.__sma[-1] and not self.__position.exitActive():
                self.__position.exitMarket()
    
    def run_strategy(smaPeriod):
        # 从 CSV 文件加载雅虎数据源
        feed = yahoofeed.Feed()
        feed.addBarsFromCSV("orcl", "orcl-2000.csv")
    
        # 使用数据源计算策略
        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 $27.26
    2000-01-28 00:00:00 strategy [INFO] SELL at $24.74
    2000-02-03 00:00:00 strategy [INFO] BUY at $26.60
    2000-02-22 00:00:00 strategy [INFO] SELL at $28.40
    2000-02-23 00:00:00 strategy [INFO] BUY at $28.91
    2000-03-31 00:00:00 strategy [INFO] SELL at $38.51
    2000-04-07 00:00:00 strategy [INFO] BUY at $40.19
    2000-04-12 00:00:00 strategy [INFO] SELL at $37.44
    2000-04-19 00:00:00 strategy [INFO] BUY at $37.76
    2000-04-20 00:00:00 strategy [INFO] SELL at $35.45
    2000-04-28 00:00:00 strategy [INFO] BUY at $37.70
    2000-05-05 00:00:00 strategy [INFO] SELL at $35.54
    2000-05-08 00:00:00 strategy [INFO] BUY at $36.17
    2000-05-09 00:00:00 strategy [INFO] SELL at $35.39
    2000-05-16 00:00:00 strategy [INFO] BUY at $37.28
    2000-05-19 00:00:00 strategy [INFO] SELL at $34.58
    2000-05-31 00:00:00 strategy [INFO] BUY at $35.18
    2000-06-23 00:00:00 strategy [INFO] SELL at $38.81
    2000-06-27 00:00:00 strategy [INFO] BUY at $39.56
    2000-06-28 00:00:00 strategy [INFO] SELL at $39.42
    2000-06-29 00:00:00 strategy [INFO] BUY at $39.41
    2000-06-30 00:00:00 strategy [INFO] SELL at $38.60
    2000-07-03 00:00:00 strategy [INFO] BUY at $38.96
    2000-07-05 00:00:00 strategy [INFO] SELL at $36.89
    2000-07-21 00:00:00 strategy [INFO] BUY at $37.19
    2000-07-24 00:00:00 strategy [INFO] SELL at $37.04
    2000-07-26 00:00:00 strategy [INFO] BUY at $35.93
    2000-07-28 00:00:00 strategy [INFO] SELL at $36.08
    2000-08-01 00:00:00 strategy [INFO] BUY at $36.11
    2000-08-02 00:00:00 strategy [INFO] SELL at $35.06
    2000-08-04 00:00:00 strategy [INFO] BUY at $37.61
    2000-09-11 00:00:00 strategy [INFO] SELL at $41.34
    2000-09-29 00:00:00 strategy [INFO] BUY at $39.07
    2000-10-02 00:00:00 strategy [INFO] SELL at $38.30
    2000-10-20 00:00:00 strategy [INFO] BUY at $34.71
    2000-10-31 00:00:00 strategy [INFO] SELL at $31.34
    2000-11-20 00:00:00 strategy [INFO] BUY at $23.35
    2000-11-21 00:00:00 strategy [INFO] SELL at $23.83
    2000-12-01 00:00:00 strategy [INFO] BUY at $25.33
    2000-12-21 00:00:00 strategy [INFO] SELL at $26.72
    2000-12-22 00:00:00 strategy [INFO] BUY at $29.17
    Final portfolio value: $979.44
    

    如果我们把计算移动平均的区间从15天调整为30天会怎样?这会让我们获得跟好的收益么?我们可以立即验证我们这个想法,如下:

    for i in range(10, 30):
        run_strategy(i)
    

    然后我们会发现使用SMA(20)我们可以获得最佳的收益:

    Final portfolio value: $1075.38
    

    如果我们只是尝试有限的几个参数调整,这样是可以的。但是如果我们要测试很多的参数,这样串行执行方法的规模会随着策略的复杂而增长。

    优化

    让我们来看一下优化器组件。它的实现思路也很简单:

    为了演示它,我们将使用 RSI2 策略 (http://stockcharts.com/school/doku.php?id=chart_school:trading_strategies:rsi2) 它需要如下的参数:

    • 一个 SMA 指标作为趋势的指标。我们称这个为 entrySMA 并且他的范围在 150至250之间。
    • 一个较小的 SMA 指标作为判断是否退出的依据。我们称这个为 exitSMA 它的范围在 5至15之间。
    • 一个 RSI 指标判断是否进入长或短的 positions。我们称之为 rsiPeriod 并且它的的范围在 2至10之间。
    • 一个 RSI 指标作为超卖入口的确定指标,判断是否执行 long position。我们称之为 overSoldThreshold 并且它的范围在 5至25之间。
    • 一个 RSI 指标作为判断超买的依据,确定是否执行 short position 。我们称之为 overBoughtThreshold 并且它的范围在 75至95之间。

    如果我的数学知识还行,那么这里有 4409559 种不同的组合。

    使用其中一种组合测试这个策略耗时0.16秒。如果我顺序测试所有的组合,将需要8.5天来找出里边的最优参数。这是一个很长的时间,但是如果我使用10台8核的计算机来做这个事情,那么总时间将下降为2.5小时。

    缩短运行时间, 我们需要使用并行计算.

    让我们从下载3年的‘道琼斯工业指数’的日数据开始:

    python -c "from pyalgotrade.tools import yahoofinance; yahoofinance.download_daily_bars('dia', 2009, 'dia-2009.csv')"
    python -c "from pyalgotrade.tools import yahoofinance; yahoofinance.download_daily_bars('dia', 2010, 'dia-2010.csv')"
    python -c "from pyalgotrade.tools import yahoofinance; yahoofinance.download_daily_bars('dia', 2011, 'dia-2011.csv')"
    

    把以下代码放入 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.barfeed import yahoofeed
    from pyalgotrade.optimizer import server
    
    def parameters_generator():
        instrument = ["dia"]
        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 feed from the CSV files.
        feed = yahoofeed.Feed()
        feed.addBarsFromCSV("dia", "dia-2009.csv")
        feed.addBarsFromCSV("dia", "dia-2010.csv")
        feed.addBarsFromCSV("dia", "dia-2011.csv")
    
        # Run the server.
        server.serve(feed, parameters_generator(), "localhost", 5000)
    

    服务器的代码作了3件事情:
    这是使用 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")
    

    当你运行服务器和客户端,你将会在服务器的控制台看到类似的输出:

    2014-05-03 15:04:01,083 server [INFO] Loading bars
    2014-05-03 15:04:01,348 server [INFO] Waiting for workers
    2014-05-03 15:04:58,277 server [INFO] Partial result 1242173.28754 with parameters: ('dia', 150, 5, 2, 91, 19) from localworker
    2014-05-03 15:04:58,566 server [INFO] Partial result 1203266.33502 with parameters: ('dia', 150, 5, 2, 81, 19) from localworker
    2014-05-03 15:05:50,965 server [INFO] Partial result 1220763.1579 with parameters: ('dia', 150, 5, 3, 83, 24) from localworker
    2014-05-03 15:05:51,325 server [INFO] Partial result 1221627.50793 with parameters: ('dia', 150, 5, 3, 80, 24) from localworker
    .
    .
    

    在“工作者”的控制台看到如下的输出:

    2014-05-03 15:02:25,360 localworker [INFO] Running strategy with parameters ('dia', 150, 5, 2, 84, 15)
    2014-05-03 15:02:25,377 localworker [INFO] Running strategy with parameters ('dia', 150, 5, 2, 94, 5)
    2014-05-03 15:02:25,661 localworker [INFO] Result 1090481.06342
    2014-05-03 15:02:25,661 localworker [INFO] Result 1031470.23717
    2014-05-03 15:02:25,662 localworker [INFO] Running strategy with parameters ('dia', 150, 5, 2, 93, 25)
    2014-05-03 15:02:25,665 localworker [INFO] Running strategy with parameters ('dia', 150, 5, 2, 84, 14)
    2014-05-03 15:02:25,995 localworker [INFO] Result 1135558.55667
    2014-05-03 15:02:25,996 localworker [INFO] Running strategy with parameters ('dia', 150, 5, 2, 93, 24)
    2014-05-03 15:02:26,006 localworker [INFO] Result 1083987.18174
    2014-05-03 15:02:26,007 localworker [INFO] Running strategy with parameters ('dia', 150, 5, 2, 84, 13)
    2014-05-03 15:02:26,256 localworker [INFO] Result 1093736.17175
    2014-05-03 15:02:26,257 localworker [INFO] Running strategy with parameters ('dia', 150, 5, 2, 84, 12)
    2014-05-03 15:02:26,280 localworker [INFO] Result 1135558.55667
    .
    .
    

    注意,你应当 仅运行一个服务器和一或多个“工作者”

    如果你只是在你的个人电脑上尝试一下并行计算,你可以从 pyalgotrade.optimizer.local 模块中获得好处,就像这样:

    import itertools
    from pyalgotrade.optimizer import local
    from pyalgotrade.barfeed import yahoofeed
    import rsi2
    
    def parameters_generator():
        instrument = ["dia"]
        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 feed from the CSV files.
        feed = yahoofeed.Feed()
        feed.addBarsFromCSV("dia", "dia-2009.csv")
        feed.addBarsFromCSV("dia", "dia-2010.csv")
        feed.addBarsFromCSV("dia", "dia-2011.csv")
    
        local.run(rsi2.RSI2, feed, parameters_generator())
    

    以上代码作了3件事情:

    1. 生命一个生成器函数,由它产生不同的参数组合。
    2. 加载我们下载的 CSV 数据。
    3. 使用 pyalgotrade.optimizer.local 模块,并行的运行策略找到最佳的收益。

    当你运行这段代码,你将会看到如下的输出:

    2014-05-03 15:08:06,587 server [INFO] Loading bars
    2014-05-03 15:08:06,910 server [INFO] Waiting for workers
    2014-05-03 15:08:58,347 server [INFO] Partial result 1242173.28754 with parameters: ('dia', 150, 5, 2, 91, 19) from worker-95583
    2014-05-03 15:08:58,967 server [INFO] Partial result 1203266.33502 with parameters: ('dia', 150, 5, 2, 81, 19) from worker-95584
    2014-05-03 15:09:52,097 server [INFO] Partial result 1220763.1579 with parameters: ('dia', 150, 5, 3, 83, 24) from worker-95584
    2014-05-03 15:09:52,921 server [INFO] Partial result 1221627.50793 with parameters: ('dia', 150, 5, 3, 80, 24) from worker-95583
    2014-05-03 15:10:40,826 server [INFO] Partial result 1142162.23912 with parameters: ('dia', 150, 5, 4, 76, 17) from worker-95584
    2014-05-03 15:10:41,318 server [INFO] Partial result 1107487.03214 with parameters: ('dia', 150, 5, 4, 83, 17) from worker-95583
    .
    .
    

    对于所提供的历史数据,最优的收益是 $2314.40,产生这个收益的对应参数如下:

    1. entrySMA: 154
    2. exitSMA: 5
    3. rsiPeriod: 2
    4. overBoughtThreshold: 91
    5. overSoldThreshold: 18

    图表

    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()
    

    再将以下代码放入另一个文件:

    from pyalgotrade import plotter
    from pyalgotrade.barfeed import yahoofeed
    from pyalgotrade.stratanalyzer import returns
    import sma_crossover
    
    # Load the yahoo feed from the CSV file
    feed = yahoofeed.Feed()
    feed.addBarsFromCSV("orcl", "orcl-2000.csv")
    
    # Evaluate the strategy with the feed's bars.
    myStrategy = sma_crossover.SMACrossOver(feed, "orcl", 20)
    
    # Attach a returns analyzers to the strategy.
    returnsAnalyzer = returns.Returns()
    myStrategy.attachAnalyzer(returnsAnalyzer)
    
    # Attach the plotter to the strategy.
    plt = plotter.StrategyPlotter(myStrategy)
    # Include the SMA in the instrument's subplot to get it displayed along with the closing prices.
    plt.getInstrumentSubplot("orcl").addDataSeries("SMA", myStrategy.getSMA())
    # Plot the simple returns on each bar.
    plt.getOrCreateSubplot("returns").addDataSeries("Simple returns", returnsAnalyzer.getReturns())
    
    # Run the strategy.
    myStrategy.run()
    myStrategy.info("Final portfolio value: $%.2f" % myStrategy.getResult())
    
    # Plot the strategy.
    plt.plot()
    

    这些代码作了3件事情:

    1. 从 CSV 文件加载数据源。
    2. 在所提供的数据上运行策略,并将结果附加到 StrategyPlotter 。
    3. 绘制策略。

    生成的图表如下:

    tutorial-5.png
    我希望你喜欢这个入门教程。我建议你下载 PyAlgoTrade 从: http://gbeced.github.io/pyalgotrade/downloads/index.html 然后开始写你自己的策略。

    你可以找到跟多的示例,在 简单策略 一节。

    相关文章

      网友评论

          本文标题:股票量化分析入门——PyAlgoTrade 中文文档

          本文链接:https://www.haomeiwen.com/subject/gcigsftx.html