注意
對以下指令的支援從提交開始
發佈1.9.30.x 將是包含它的第1個版本 。
backtrader的原始目標之一是成為純python,即:僅使用標準發行版中可用的軟體包。只有一個例外是matplotlib在沒有重新發明輪子的情況下進行繪圖。雖然是在最晚的可能時刻導入的,以避免中斷可能根本不需要繪圖的標準操作(如果未安裝且不希望,則避免錯誤)
第二 個例外是 pytz ,隨著即時 data feeds 的出現而增加對時區的支持,這些可能位於本地時區之外。同樣,導入操作在後台發生,並且僅在可用時才 pytz 發生(使用者可以選擇傳遞 pytz 實例)
但是現在完全破例了,因為反向交易者正在使用眾所周知的軟體包,如numpy,pandasstatsmodel和一些更適度的軟體包,如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以不斷查找事物而聞名,這是該語言在動態性,內省設施和元程式設計方面表現出色的原因之一。同時也是無法提供相同性能的原因之一。
通常的加速之一是通過直接從模組中導入符號來刪除對模組的查找,以進行本地查找。從這個SomeFunctionpandas看起來像:
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而不是atuplelist中,以方括弧,[]並且能夠更好地閱讀它。
也可以別名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()