股票量化分析入門——PyAlgoTrade 中文文檔

因為要學習,所以沒找到官方手冊的中文版哮笆。所以翻譯一下来颤,翻譯的過程自己能夠仔細閱讀,翻譯的結(jié)果也能給大家做一下參考稠肘。翻譯的對應的版本是0.18福铅,對應的原文地址在這里

分隔線以下為原文的翻譯


教程

編寫本教程的目的是提供 PyAlgoTrade 的快速入門启具。就像介紹里所說的本讥, PyAlgoTrade 的目標是幫助你對股票交易的策略進行回測珊泳÷撤耄或許你自己有一個交易的策略,并且你希望能夠在歷史數(shù)據(jù)上使用這個策略做模擬交易色查,一遍查看這個策略的效果薯演。使用 PyAlgoTrade 只需要做很少的工作,就可以幫助你實現(xiàn)這個目標秧了。

在繼續(xù)之前我需要向 Pablo Jorge 表達我衷心的謝意跨扮,他幫助我校對了最初的設(shè)計和文檔。

這個教程是在 UNIX 環(huán)境之下制作的,但是其中的步驟要用于 Windows 環(huán)境也很簡單衡创。

PyAlgoTrade 有6個主要的概念:

  • 策略
    交易的邏輯就是頂級在策略類中帝嗡。包含何時買入、何時賣出等璃氢。

  • 數(shù)據(jù)源
    這個一個抽象的數(shù)據(jù)提供源哟玷。例如,你可以使用 CSV 數(shù)據(jù)源一也,它可以從 CSV(每一行代表一條數(shù)據(jù)巢寡,行內(nèi)使用逗號分隔的文件)文件將歷史數(shù)據(jù)提供給一個交易策略。數(shù)據(jù)源不限于歷史價格數(shù)據(jù)椰苟。例如抑月,Twitter的數(shù)據(jù)源可以將 Tiwtter 的事件整合進交易決策中。

  • 經(jīng)紀商
    經(jīng)紀商負責執(zhí)行交易指令舆蝴。

  • 數(shù)據(jù)序列(DataSeries)
    數(shù)據(jù)序列是一個用戶管理時間序列數(shù)據(jù)的抽象類谦絮。

  • 技術(shù)指標(Technicals)
    這里有一系列的過濾工具用戶在數(shù)據(jù)序列之上進行計算。例如 SMA(簡單移動平均)须误,RSI(相對強弱指數(shù))等挨稿。這些過濾工具都集成為數(shù)據(jù)序列的裝飾器。

  • 優(yōu)化器(Optimizer)
    這里有一系列的類京痢,可以幫助你將回測分發(fā)到不同的計算機上奶甘,或者交給同一臺計算機的多個線程,也可以分到多臺計算機的多個線程祭椰。他們讓水平分割計算量變得十分簡單臭家。

說了這么多,我們首先需要做的就是在一些數(shù)據(jù)上測試我們的交易策略方淤。讓我們使用2000年 Oracle 的股票價格數(shù)據(jù)钉赁,這些數(shù)據(jù)我們可以通過如下的命令下載到:

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

pyalgotrade.tools.yahoofinance 包從啞無財經(jīng)下載 CSV 格式的數(shù)據(jù)。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數(shù)據(jù)源
feed = yahoofeed.Feed()
feed.addBarsFromCSV("orcl", "orcl-2000.csv")

# 在數(shù)據(jù)源的記錄中逐條執(zhí)行策略。
myStrategy = MyStrategy(feed, "orcl")
myStrategy.run()

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

  1. 申明一個新的策略讳苦。其中之定義了一個方法 onBars带膜,這個方法將使用數(shù)據(jù)源的每一條數(shù)據(jù)作為參數(shù),逐條調(diào)用鸳谜。
  2. 從 CSV 文件加載數(shù)據(jù)源膝藕。
  3. 使用數(shù)據(jù)源提供給的每一條數(shù)據(jù)運行所定義的策略。

如果你運行這個腳本咐扭,你將會看到順序排列的收盤價:

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

讓我們前進一小步芭挽,將策略調(diào)整為打印收盤價的簡單移動平均值滑废,從而演示如何使用技術(shù)指標:

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 文件加載雅虎的數(shù)據(jù)源
feed = yahoofeed.Feed()
feed.addBarsFromCSV("orcl", "orcl-2000.csv")

# 使用數(shù)據(jù)源中的數(shù)據(jù)逐條運行策略袜爪。
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

當值在給定的時間區(qū)間上無法被計算出來的時候妻导,所有的技術(shù)指標都會返回 None。

關(guān)于技術(shù)指標一個很重要的點是他們可以組合使用怀各。這是因為它們都被設(shè)計為了數(shù)據(jù)序列倔韭。例如,基于收盤價計算 RSI瓢对,再基于RSI 計算 SMA寿酌,要實現(xiàn)上述操作十分簡單,代碼如下:

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 文件加載雅虎數(shù)據(jù)源
feed = yahoofeed.Feed()
feed.addBarsFromCSV("orcl", "orcl-2000.csv")

# 使用數(shù)據(jù)源中的數(shù)據(jù)逐條運行策略硕蛹。
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
        # 我們使用已調(diào)整收盤價代替常規(guī)的收盤價.
        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):
        # 等待足夠的數(shù)據(jù)條數(shù)埃仪,以便計算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 文件加載雅虎數(shù)據(jù)源
    feed = yahoofeed.Feed()
    feed.addBarsFromCSV("orcl", "orcl-2000.csv")

    # 使用數(shù)據(jù)源計算策略
    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

如果我們把計算移動平均的區(qū)間從15天調(diào)整為30天會怎樣?這會讓我們獲得跟好的收益么葡缰?我們可以立即驗證我們這個想法亏掀,如下:

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

然后我們會發(fā)現(xiàn)使用SMA(20)我們可以獲得最佳的收益:

Final portfolio value: $1075.38

如果我們只是嘗試有限的幾個參數(shù)調(diào)整,這樣是可以的泛释。但是如果我們要測試很多的參數(shù)滤愕,這樣串行執(zhí)行方法的規(guī)模會隨著策略的復雜而增長。

優(yōu)化

讓我們來看一下優(yōu)化器組件胁澳。它的實現(xiàn)思路也很簡單:

為了演示它该互,我們將使用 RSI2 策略 (http://stockcharts.com/school/doku.php?id=chart_school:trading_strategies:rsi2) 它需要如下的參數(shù):

  • 一個 SMA 指標作為趨勢的指標米者。我們稱這個為 entrySMA 并且他的范圍在 150至250之間韭畸。
  • 一個較小的 SMA 指標作為判斷是否退出的依據(jù)宇智。我們稱這個為 exitSMA 它的范圍在 5至15之間。
  • 一個 RSI 指標判斷是否進入長或短的 positions胰丁。我們稱之為 rsiPeriod 并且它的的范圍在 2至10之間随橘。
  • 一個 RSI 指標作為超賣入口的確定指標,判斷是否執(zhí)行 long position锦庸。我們稱之為 overSoldThreshold 并且它的范圍在 5至25之間机蔗。
  • 一個 RSI 指標作為判斷超買的依據(jù),確定是否執(zhí)行 short position 甘萧。我們稱之為 overBoughtThreshold 并且它的范圍在 75至95之間萝嘁。

如果我的數(shù)學知識還行,那么這里有 4409559 種不同的組合扬卷。

使用其中一種組合測試這個策略耗時0.16秒牙言。如果我順序測試所有的組合,將需要8.5天來找出里邊的最優(yōu)參數(shù)怪得。這是一個很長的時間咱枉,但是如果我使用10臺8核的計算機來做這個事情,那么總時間將下降為2.5小時徒恋。

縮短運行時間, 我們需要使用并行計算.

讓我們從下載3年的‘道瓊斯工業(yè)指數(shù)’的日數(shù)據(jù)開始:

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 模塊的工作腳本蚕断,用于在服務器提供的數(shù)據(jù)上并行的計算策略:

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. 生命一個生成器函數(shù)风皿,由它產(chǎn)生不同的參數(shù)組合。
  2. 加載我們下載的 CSV 數(shù)據(jù)匠璧。
  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
.
.

對于所提供的歷史數(shù)據(jù)魔眨,最優(yōu)的收益是 $2314.40,產(chǎn)生這個收益的對應參數(shù)如下:

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

圖表

PyAlgoTrade 繪制一個策略的執(zhí)行情況很容易酿雪。

把以下代碼放入 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 文件加載數(shù)據(jù)源遏暴。
  2. 在所提供的數(shù)據(jù)上運行策略,并將結(jié)果附加到 StrategyPlotter 指黎。
  3. 繪制策略朋凉。

生成的圖表如下:

tutorial-5.png

我希望你喜歡這個入門教程。我建議你下載 PyAlgoTrade 從: http://gbeced.github.io/pyalgotrade/downloads/index.html 然后開始寫你自己的策略醋安。

你可以找到跟多的示例杂彭,在 簡單策略 一節(jié)墓毒。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市亲怠,隨后出現(xiàn)的幾起案子所计,更是在濱河造成了極大的恐慌,老刑警劉巖团秽,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件主胧,死亡現(xiàn)場離奇詭異,居然都是意外死亡习勤,警方通過查閱死者的電腦和手機踪栋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來图毕,“玉大人己英,你說我怎么就攤上這事∥庑” “怎么了损肛?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長荣瑟。 經(jīng)常有香客問我治拿,道長,這世上最難降的妖魔是什么笆焰? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任劫谅,我火速辦了婚禮,結(jié)果婚禮上嚷掠,老公的妹妹穿的比我還像新娘捏检。我一直安慰自己,他們只是感情好不皆,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布贯城。 她就那樣靜靜地躺著,像睡著了一般霹娄。 火紅的嫁衣襯著肌膚如雪能犯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天犬耻,我揣著相機與錄音踩晶,去河邊找鬼。 笑死枕磁,一個胖子當著我的面吹牛渡蜻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼茸苇,長吁一口氣:“原來是場噩夢啊……” “哼排苍!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起税弃,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎凑队,沒想到半個月后则果,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡漩氨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年西壮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叫惊。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡款青,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出霍狰,到底是詐尸還是另有隱情抡草,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布蔗坯,位于F島的核電站康震,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏宾濒。R本人自食惡果不足惜腿短,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望绘梦。 院中可真熱鬧橘忱,春花似錦、人聲如沸卸奉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽榄棵。三九已至敲长,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間秉继,已是汗流浹背祈噪。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留尚辑,地道東北人辑鲤。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像杠茬,于是被迫代替她去往敵國和親月褥。 傳聞我的和親對象是個殘疾皇子弛随,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容