注意
對以下指令的支援從提交開始
發佈1.9.30.x
將是包含它的第1個版本 。
backtrader的原始目標之一是成為純python,即:僅使用標準發行版中可用的軟體包。只有一個例外是matplotlib
在沒有重新發明輪子的情況下進行繪圖。雖然是在最晚的可能時刻導入的,以避免中斷可能根本不需要繪圖的標準操作(如果未安裝且不希望,則避免錯誤)
第二 個例外是 pytz
,隨著即時 data feeds 的出現而增加對時區的支持,這些可能位於本地時區之外。同樣,導入操作在後台發生,並且僅在可用時才 pytz
發生(使用者可以選擇傳遞 pytz
實例)
但是現在完全破例了,因為反向交易者正在使用眾所周知的軟體包,如numpy
,pandas
statsmodel
和一些更適度的軟體包,如pykalman
.或者包含在使用這些軟體包的事物的平臺中。
來自社區的一些例子:
這個願望被添加到這裡草擬的快速路線圖中:
聲明性方法
保持 backtrader 的原始精神,同時允許使用這些軟體包的關鍵是不要強迫 純python用戶必須安裝這些軟體包。
儘管這似乎具有挑戰性,並且容易出現多個條件語句,再加上異常處理,但平台內部和外部的使用者方法都是依賴於已經用於開發其他概念的相同原則,例如大多數對象的 參數 (命名參數)。
讓我們回想一下,如何定義一個Indicator
接受參數並定義 lines:
class MyIndicator(bt.Indicator): lines = ('myline',) params = ( ('period', 50), )
period
參數稍後可尋址為self.params.period
或self.p.period
如:
def __init__(self): print('my period is:', self.p.period)
和 line 中的當前值,如self.lines.myline
或 self.l.myline
如:
def next(self): print('mylines[0]:', self.lines.myline[0])
這並不是特別有用,只是展示了params後台機制的聲明性方法,該方法還具有對繼承(包括多重繼承)的適當支援。
正在推出packages
使用相同的聲明性技術(有些人稱之為元程式設計),對外來包的支援可以通過以下方式獲得:
class MyIndicator(bt.Indicator): packages = ('pandas',) lines = ('myline',) params = ( ('period', 50), )
起泡的藤壺!!!這似乎只是另一個宣言。指標實施者面臨的第一個問題是:
- 我必須手動導入「熊貓」嗎?
答案很簡單:不。後台機制將導入 pandas
並使其在定義的模組中 MyIndicator
可用。現在可以在 next
中執行以下操作:
def next(self): print('mylines[0]:', pandas.SomeFunction(self.lines.myline[0]))
該packages
指令還可用於:
-
在一個聲明中導入多個包
-
將導入分配給別名 ala
import pandas as pd
假設statsmodel也希望sm
完成 pandas.SomeFunction
:
class MyIndicator(bt.Indicator): packages = ('pandas', ('statsmodel', 'sm'),) lines = ('myline',) params = ( ('period', 50), ) def next(self): print('mylines[0]:', sm.XX(pandas.SomeFunction(self.lines.myline[0])))
statsmodel
已導入為 sm
且可用。它只需要傳遞一個可反覆運算的(a tuple
是 backtrader 約定),其中包含包的名稱和所需的別名。
添加frompackages
Python以不斷查找事物而聞名,這是該語言在動態性,內省設施和元程式設計方面表現出色的原因之一。同時也是無法提供相同性能的原因之一。
通常的加速之一是通過直接從模組中導入符號來刪除對模組的查找,以進行本地查找。從這個SomeFunction
pandas
看起來像:
from pandas import SomeFunction
或使用別名:
from pandas import SomeFunction as SomeFunc
backtrader 通過指令frompackages
為兩者都提供支援。讓我們返工 MyIndicator
:
class MyIndicator(bt.Indicator): frompackages = (('pandas', 'SomeFunction'),) lines = ('myline',) params = ( ('period', 50), ) def next(self): print('mylines[0]:', SomeFunction(self.lines.myline[0]))
當然,這開始添加更多的括弧。例如,如果要從中pandas
導入兩(2)個東西,則如下所示:
class MyIndicator(bt.Indicator): frompackages = (('pandas', ['SomeFunction', 'SomeFunction2']),) lines = ('myline',) params = ( ('period', 50), ) def next(self): print('mylines[0]:', SomeFunction2(SomeFunction(self.lines.myline[0])))
為了清楚起見SomeFunction
,已經SomeFunction2
放在a而不是atuple
list
中,以方括弧,[]
並且能夠更好地閱讀它。
也可以別名SomeFunction
例如 SFunc
.完整範例:
class MyIndicator(bt.Indicator): frompackages = (('pandas', [('SomeFunction', 'SFunc'), 'SomeFunction2']),) lines = ('myline',) params = ( ('period', 50), ) def next(self): print('mylines[0]:', SomeFunction2(SFunc(self.lines.myline[0])))
從不同的包導入是可能的,但代價是更多的括弧。當然, line 斷裂和縮進確實有説明:
class MyIndicator(bt.Indicator): frompackages = ( ('pandas', [('SomeFunction', 'SFunc'), 'SomeFunction2']), ('statsmodel', 'XX'), ) lines = ('myline',) params = ( ('period', 50), ) def next(self): print('mylines[0]:', XX(SomeFunction2(SFunc(self.lines.myline[0]))))
使用繼承
frompackages
兩者都packages
支援(多重)繼承。例如,可能有一個基類,它為所有子類添加numpy
支援:
class NumPySupport(object): packages = ('numpy',) class MyIndicator(bt.Indicator, NumPySupport): packages = ('pandas',)
MyIndicator
將需要從後台機械導入兩者 numpy
,並且 pandas
將能夠使用它們。
介紹卡爾曼和朋友們
注意
以下兩個指標都需要同行評審以確認實施情況。請小心使用。
可以在下面找到實現 的範例KalmanMovingAverage
。這是根據這裡的一篇文章建模的: Quantopian講座系列:卡爾曼濾波器
實現:
class KalmanMovingAverage(bt.indicators.MovingAverageBase): packages = ('pykalman',) frompackages = (('pykalman', [('KalmanFilter', 'KF')]),) lines = ('kma',) alias = ('KMA',) params = ( ('initial_state_covariance', 1.0), ('observation_covariance', 1.0), ('transition_covariance', 0.05), ) plotlines = dict(cov=dict(_plotskip=True)) def __init__(self): self.addminperiod(self.p.period) # when to deliver values self._dlast = self.data(-1) # get previous day value def nextstart(self): self._k1 = self._dlast[0] self._c1 = self.p.initial_state_covariance self._kf = pykalman.KalmanFilter( transition_matrices=[1], observation_matrices=[1], observation_covariance=self.p.observation_covariance, transition_covariance=self.p.transition_covariance, initial_state_mean=self._k1, initial_state_covariance=self._c1, ) self.next() def next(self): k1, self._c1 = self._kf.filter_update(self._k1, self._c1, self.data[0]) self.lines.kma[0] = self._k1 = k1
KalmanFilter
並在這裡發佈以下帖子: QSTrader中基於卡爾曼篩檢程式的對交易策略
class NumPy(object): packages = (('numpy', 'np'),) class KalmanFilterInd(bt.Indicator, NumPy): _mindatas = 2 # needs at least 2 data feeds packages = ('pandas',) lines = ('et', 'sqrt_qt') params = dict( delta=1e-4, vt=1e-3, ) def __init__(self): self.wt = self.p.delta / (1 - self.p.delta) * np.eye(2) self.theta = np.zeros(2) self.P = np.zeros((2, 2)) self.R = None self.d1_prev = self.data1(-1) # data1 yesterday's price def next(self): F = np.asarray([self.data0[0], 1.0]).reshape((1, 2)) y = self.d1_prev[0] if self.R is not None: # self.R starts as None, self.C set below self.R = self.C + self.wt else: self.R = np.zeros((2, 2)) yhat = F.dot(self.theta) et = y - yhat # Q_t is the variance of the prediction of observations and hence # \sqrt{Q_t} is the standard deviation of the predictions Qt = F.dot(self.R).dot(F.T) + self.p.vt sqrt_Qt = np.sqrt(Qt) # The posterior value of the states \theta_t is distributed as a # multivariate Gaussian with mean m_t and variance-covariance C_t At = self.R.dot(F.T) / Qt self.theta = self.theta + At.flatten() * et self.C = self.R - At * F.dot(self.R) # Fill the lines self.lines.et[0] = et self.lines.sqrt_qt[0] = sqrt_Qt
為了它,它顯示了如何packages
與繼承一起工作(pandas
不是真的需要)
範例的執行:
$ ./kalman-things.py --plot
生成此圖表
示例用法
$ ./kalman-things.py --help usage: kalman-things.py [-h] [--data0 DATA0] [--data1 DATA1] [--fromdate FROMDATE] [--todate TODATE] [--cerebro kwargs] [--broker kwargs] [--sizer kwargs] [--strat kwargs] [--plot [kwargs]] Packages and Kalman optional arguments: -h, --help show this help message and exit --data0 DATA0 Data to read in (default: ../../datas/nvda-1999-2014.txt) --data1 DATA1 Data to read in (default: ../../datas/orcl-1995-2014.txt) --fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: 2006-01-01) --todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: 2007-01-01) --cerebro kwargs kwargs in key=value format (default: runonce=False) --broker kwargs kwargs in key=value format (default: ) --sizer kwargs kwargs in key=value format (default: ) --strat kwargs kwargs in key=value format (default: ) --plot [kwargs] kwargs in key=value format (default: )
示例代碼
from __future__ import (absolute_import, division, print_function, unicode_literals) import argparse import datetime import backtrader as bt class KalmanMovingAverage(bt.indicators.MovingAverageBase): packages = ('pykalman',) frompackages = (('pykalman', [('KalmanFilter', 'KF')]),) lines = ('kma',) alias = ('KMA',) params = ( ('initial_state_covariance', 1.0), ('observation_covariance', 1.0), ('transition_covariance', 0.05), ) def __init__(self): self.addminperiod(self.p.period) # when to deliver values self._dlast = self.data(-1) # get previous day value def nextstart(self): self._k1 = self._dlast[0] self._c1 = self.p.initial_state_covariance self._kf = pykalman.KalmanFilter( transition_matrices=[1], observation_matrices=[1], observation_covariance=self.p.observation_covariance, transition_covariance=self.p.transition_covariance, initial_state_mean=self._k1, initial_state_covariance=self._c1, ) self.next() def next(self): k1, self._c1 = self._kf.filter_update(self._k1, self._c1, self.data[0]) self.lines.kma[0] = self._k1 = k1 class NumPy(object): packages = (('numpy', 'np'),) class KalmanFilterInd(bt.Indicator, NumPy): _mindatas = 2 # needs at least 2 data feeds packages = ('pandas',) lines = ('et', 'sqrt_qt') params = dict( delta=1e-4, vt=1e-3, ) def __init__(self): self.wt = self.p.delta / (1 - self.p.delta) * np.eye(2) self.theta = np.zeros(2) self.R = None self.d1_prev = self.data1(-1) # data1 yesterday's price def next(self): F = np.asarray([self.data0[0], 1.0]).reshape((1, 2)) y = self.d1_prev[0] if self.R is not None: # self.R starts as None, self.C set below self.R = self.C + self.wt else: self.R = np.zeros((2, 2)) yhat = F.dot(self.theta) et = y - yhat # Q_t is the variance of the prediction of observations and hence # \sqrt{Q_t} is the standard deviation of the predictions Qt = F.dot(self.R).dot(F.T) + self.p.vt sqrt_Qt = np.sqrt(Qt) # The posterior value of the states \theta_t is distributed as a # multivariate Gaussian with mean m_t and variance-covariance C_t At = self.R.dot(F.T) / Qt self.theta = self.theta + At.flatten() * et self.C = self.R - At * F.dot(self.R) # Fill the lines self.lines.et[0] = et self.lines.sqrt_qt[0] = sqrt_Qt class KalmanSignals(bt.Indicator): _mindatas = 2 # needs at least 2 data feeds lines = ('long', 'short',) def __init__(self): kf = KalmanFilterInd() et, sqrt_qt = kf.lines.et, kf.lines.sqrt_qt self.lines.long = et < -1.0 * sqrt_qt # longexit is et > -1.0 * sqrt_qt ... the opposite of long self.lines.short = et > sqrt_qt # shortexit is et < sqrt_qt ... the opposite of short class St(bt.Strategy): params = dict( ksigs=False, # attempt trading period=30, ) def __init__(self): if self.p.ksigs: self.ksig = KalmanSignals() KalmanFilter() KalmanMovingAverage(period=self.p.period) bt.ind.SMA(period=self.p.period) if True: kf = KalmanFilterInd() kf.plotlines.sqrt_qt._plotskip = True def next(self): if not self.p.ksigs: return size = self.position.size if not size: if self.ksig.long: self.buy() elif self.ksig.short: self.sell() elif size > 0: if not self.ksig.long: self.close() elif not self.ksig.short: # implicit size < 0 self.close() def runstrat(args=None): args = parse_args(args) cerebro = bt.Cerebro() # Data feed kwargs kwargs = dict() # Parse from/to-date dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S' for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']): if a: strpfmt = dtfmt + tmfmt * ('T' in a) kwargs[d] = datetime.datetime.strptime(a, strpfmt) # Data feed data0 = bt.feeds.YahooFinanceCSVData(dataname=args.data0, **kwargs) cerebro.adddata(data0) data1 = bt.feeds.YahooFinanceCSVData(dataname=args.data1, **kwargs) data1.plotmaster = data0 cerebro.adddata(data1) # Broker cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')')) # Sizer cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')')) # Strategy cerebro.addstrategy(St, **eval('dict(' + args.strat + ')')) # Execute cerebro.run(**eval('dict(' + args.cerebro + ')')) if args.plot: # Plot if requested to cerebro.plot(**eval('dict(' + args.plot + ')')) def parse_args(pargs=None): parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, description=( 'Packages and Kalman' ) ) parser.add_argument('--data0', default='../../datas/nvda-1999-2014.txt', required=False, help='Data to read in') parser.add_argument('--data1', default='../../datas/orcl-1995-2014.txt', required=False, help='Data to read in') # Defaults for dates parser.add_argument('--fromdate', required=False, default='2006-01-01', help='Date[time] in YYYY-MM-DD[THH:MM:SS] format') parser.add_argument('--todate', required=False, default='2007-01-01', help='Date[time] in YYYY-MM-DD[THH:MM:SS] format') parser.add_argument('--cerebro', required=False, default='runonce=False', metavar='kwargs', help='kwargs in key=value format') parser.add_argument('--broker', required=False, default='', metavar='kwargs', help='kwargs in key=value format') parser.add_argument('--sizer', required=False, default='', metavar='kwargs', help='kwargs in key=value format') parser.add_argument('--strat', required=False, default='', metavar='kwargs', help='kwargs in key=value format') parser.add_argument('--plot', required=False, default='', nargs='?', const='{}', metavar='kwargs', help='kwargs in key=value format') return parser.parse_args(pargs) if __name__ == '__main__': runstrat()