This is a collection of some of the concepts of the platform. It tries to gather information bits which can be useful in using the platform.
這里將介紹平台的一些概念的集合。它試圖收集在使用該平台時有用的信息位。
Before Starting
在開始之前
All mini-code examples assume the following imports are available:
所有微小代碼示例都假設以下導入可用:
import backtrader as bt import backtrader.indicators as btind import backtrader.feeds as btfeeds
注意
An alternative syntax for accessing sub-modules like indicators and feeds:
訪問子模塊的另外方法比如indicators 和 feeds
import backtrader as bt
And then:
然后
thefeed = bt.feeds.OneOfTheFeeds(...) theind = bt.indicators.SimpleMovingAverage(...)
Data Feeds - Passing them around
數據源---傳遞數據
The basis of the work with the platform will be done with Strategies. And these will get passed Data Feeds. The platform end user does not need to care about receiving them:
平台工作的基礎的基礎將以策略來完成,而這些都將通過數據源,平台終端用戶不需要在意接收它們。
Data Feeds are automagically provided member variables to the strategy in the form of an array and shortcuts to the array positions
數據源以數組的形式自動向策略提供成員變量,並提供指向數組位置的快捷方式
Quick preview of a Strategy derived class declaration and running the platform:
快速預覽一個策略派生類聲明和運行平台:
class MyStrategy(bt.Strategy): params = dict(period=20) def __init__(self): sma = btind.SimpleMovingAverage(self.datas[0], period=self.params.period) ... cerebro = bt.Cerebro() ... data = btfeeds.MyFeed(...) cerebro.adddata(data) ... cerebro.addstrategy(MyStrategy, period=30) ...
Notice the following:
請注意以下幾點
No *args
or **kwargs
are being received by the strategy’s __init__
method (they may still be used)
策略的__init__方法沒有使用*args與**kwargs接收參數(它們任然是可以用的)
A member variable self.datas
exists which is array/list/iterable holding at least one item (hopefully or else an exception will be raised)
self.datas變量對象存在,它至少是數組/列表/可迭代數據的一種(希望如此,否則將引發異常)
So it is. Data Feeds get added to the platform and they will show up inside the strategy in the sequential order in which they were added to the system.
因此,數據源被添加到平台中,它們將按照它們被添加到系統的順序顯示在策略中。
Note
注意
This also applies to Indicators
, should the end user develop his own custom Indicator or when having a look at the source code for some of the existing Indicator Reference
這也適用於指標,如果最終用戶開發自己的自定義指標,或者在查看一些現有指標引用的源代碼時
Shortcuts for Data Feeds
快捷的數據提供
The self.datas array items can be directly accessed with additional automatic member variables:
self.datas數組項可直接訪問附加的自動成員變量
-
self.data
targetsself.datas[0]
-
self.data
對應self.datas[0]
self.dataX
targetsself.datas[X]
-
self.dataX
對應self.datas[X]
The example then:
示例如下
class MyStrategy(bt.Strategy): params = dict(period=20) def __init__(self): sma = btind.SimpleMovingAverage(self.data, period=self.params.period) ...
Omitting the Data Feeds
忽略數據傳遞
The example above can be further simplified to:
上面的示例可以簡化如下:
class MyStrategy(bt.Strategy): params = dict(period=20) def __init__(self): sma = btind.SimpleMovingAverage(period=self.params.period) ...
self.data
has been completely removed from the invocation of SimpleMovingAverage
. If this is done, the indicator (in this case the SimpleMovingAverage
) receives the first data of the object in which is being created (the Strategy), which is self.data
(aka self.data0
or self.datas[0]
)
self.data已經完全從SimpleMovingAverage調用中刪除.如果這么做,指標線(在本例中是
SimpleMovingAverage
)將接收正在創建策略對象的第一個數據,就是self.data(self.data0或self.datas[0])
Almost everything is a Data Feed
幾乎所有的東西都可以是數據源
Not only Data Feeds are data and can be passed around. Indicators
and results of Operations
are also data.
不僅數據源可以稱為數據,可以四處傳遞。指標和操作結果也可以是數據
In the previous example the SimpleMovingAverage
was receiving self.datas[0]
as input to operate on. An example with operations and extra indicators:
在前面的例子中,最簡單的平均數是接收self.datas[0
]作為操作的輸入。下面是一個帶有操作和額外指標線的例子:
class MyStrategy(bt.Strategy): params = dict(period1=20, period2=25, period3=10, period4) def __init__(self): sma1 = btind.SimpleMovingAverage(self.datas[0], period=self.p.period1) # This 2nd Moving Average operates using sma1 as "data"
# 操作指標線1 sma2 = btind.SimpleMovingAverage(sma1, period=self.p.period2) # New data created via arithmetic operation
# 對指標線進行算術操作 something = sma2 - sma1 + self.data.close # This 3rd Moving Average operates using something as "data" sma3 = btind.SimpleMovingAverage(something, period=self.p.period3) # Comparison operators work too ...
進行比較操作 greater = sma3 > sma1 # Pointless Moving Average of True/False values but valid # This 4th Moving Average operates using greater as "data" sma3 = btind.SimpleMovingAverage(greater, period=self.p.period4) ...
Basically everything gets transformed into an object which can be used as a data feed once it has been operated upon.
基本上,所有的東西都被轉換成一個對象,一旦對它進行操作,它就可以用作數據源。
Parameters
參數
Mostly every other class
in the platform supports the notion of parameters.
平台中的大多數其他類都支持參數的概念。
Parameters along with default values are declared as a class attribute (tuple of tuples or dict-like object)
參數和默認值被聲明為類屬性(元組套元組或類似於dict的對象)
Keywords args (**kwargs
) are scanned for matching parameters, removing them from **kwargs
if found and assigning the value to the corresponding parameter
self.params
(shorthand: self.p
)
The previous quick Strategy preview already contains a parameters example, but for the sake of redundancy, again, focusing only on the parameters. Using tuples:
前面的快速策略已經包含了一個參數示例,但為了保險起見,再來一次只關注參數。使用元祖
class MyStrategy(bt.Strategy): params = (('period', 20),) def __init__(self): sma = btind.SimpleMovingAverage(self.data, period=self.p.period)
And using a dict
:
使用字典
class MyStrategy(bt.Strategy): params = dict(period=20) def __init__(self): sma = btind.SimpleMovingAverage(self.data, period=self.p.period)
Lines
Again mostly every other object in the platform is a Lines
enabled object. From a end user point of view this means:
同樣,平台中的所有其他對象都是啟用了lines的對象(屬性)。從最終用戶的角度來看,這意味着:
It can hold one of more line series, being a line series an array of values were the values put together in a chart they would form a line.
它可以容納多個線系列,作為一個線系列,就是數組的各個值聯系在一起,在圖標中就會形成一條線
A good example of a line (or lineseries) is the line formed by the closing prices of a stock. This is actually a well-known chart representation of the evolution of prices (known as Line on Close)
行(或行序列)的一個很好的例子就是由股票收盤價構成的線。這實際上是一個眾所周知的價格演變圖表(稱為線上收盤價)
Regular use of the platform is only concerned with accessing lines
. The previous mini-strategy example, lightly extended, comes in handy again:
該平台的常規使用只關心lines對象。前面的迷你策略示例稍加擴展,就又派上用場了:
class MyStrategy(bt.Strategy): params = dict(period=20) def __init__(self): self.movav = btind.SimpleMovingAverage(self.data, period=self.p.period) def next(self):
# self.movav調用出線對象,通過sma取出具體的線,通過[0]取值 if self.movav.lines.sma[0] > self.data.lines.close[0]: print('Simple Moving Average is greater than the closing price')
Two objects with lines
have been exposed:
暴露了兩個lines對象
self.data
It has a lines
attribute which contains a close
attribute in turn
self.data它有一個lines屬性,該屬性依次包含一個close屬性
self.movav
which is a SimpleMovingAverage
indicator It has a lines
attribute which contains a sma
attribute in turn
self.movav是一個簡單的平均指標,它有一個lines屬性,該屬性依次包含一個sma屬性
Note
注意
It should be obvious from this, that lines
are named. They can also be accessed sequentially following the declaration order, but this should only be used in Indicator
development
很明顯,行被命名了。也可以按照聲明的順序訪問它們,但這只能在指標開發中使用
And both lines, namely close
and sma
can be queried for a point (index 0) to compare the values.
同時可以查詢close和sma這兩條線,取一個點(index 0)進行數值比較。
Shorthand access to lines do exist:
對線的快速訪問也存在:
-
xxx.lines
can be shortened toxxx.l
- xxx.lines短寫成xxx.l
-
xxx.lines.name
can be shortened toxxx.lines_name
- xxx.lines.name短些成xxx.lines_name
Complex objects like Strategies and Indicators offer quick access to data’s lines
像策略和指標這樣的復雜對象提供了對數據線的快速訪問
-
self.data_name
offers a direct access toself.data.lines.name
- self.data_name提供了對self.data.lines.name的直接訪問
- Which also applies to the numbered data variables:
self.data1_name
->self.data1.lines.name
- 這也適用於編號數據變量:self.data1_name - > self.data1.lines.name
Additionally the line names are directly accessible with:
另外行名也可以直接在對象上使用:
self.data.close
and self.movav.sma
But the notation doesn’t make as clear as the previous one if lines are actually being accessed.
但是如果行被直接訪問,這個符號就沒有像前一個這么清楚了。
直接數據源訪問lines對象內的具體線.
注意
Setting/Assigning the lines with these two later notations is not supported
不支持在lines屬性后面進行設置與分配
Lines declaration
Lines 聲明
If an Indicator is being developed, the lines which the indicator has must be declared.
如果正在開發指標,則必須聲明指標所包含的行。
Just as with params this takes place as a class attribute this time ONLY as a tuple. Dictionaries are not supported because they do not store things following insertion order.
與params一樣,這一次它作為類屬性只作為元組發生。字典不受支持,因為它們不按照插入順序存儲內容。
For the Simple Moving Average it would be done like this:
對於簡單的移動平均,可以這樣做:
class SimpleMovingAverage(Indicator):
# lines屬性可以調用的名字 lines = ('sma',) ...
注意
The comma following the declaration is needed in tuples if you pass a single string to the tuple or else each letter in the string would be interpreted as an item to be added to the tuple. Possibly one of the few spots where Python’s syntax got it wrong.
對於簡單的移動平均值,可以這樣做:如果向元組傳遞單個字符串,則在元組中需要聲明后的逗號,否則字符串中的每個字母將被解釋為要添加到元組中的項。這可能是Python語法出錯的少數幾個地方之一。(元祖不要少了逗號)
As seen in the previous example this declaration creates a sma
line in the Indicator that can be later accessed in the Strategy’s logic (and possibly by other indicators to create more complex indicators)
如上例所示,此聲明在指標中創建了一個sma線,該線稍后可以在策略邏輯中訪問(也可能被其他指標用於創建更復雜的指標)。
For development is sometimes useful to access the lines in a generic non-named manner and this is where numbered access comes in handy:
對於開發來說,用通用的非命名方式訪問行有時很有用,這就是編號訪問的用處所在
self.lines[0]
points toself.lines.sma
self.lines[0]
指向self.lines.sma
Had more lines been defined they would be accessed with index 1, 2, and higher.
Had more lines been defined they would be accessed with index 1, 2, and higher.
如果定義了更多的行,就可以通過索引1、2或更高的方式訪問它們。
And of course, extra shorthand versions do exist:
當然也有簡化訪問的方式存在
-
self.line
points toself.lines[0]
-
self.lineX
point toself.lines[X]
-
self.line_X
point toself.lines[X]
Inside objects which are receiving datas feeds the lines below these data feeds can also be quickly accessed by number:
在接收數據的對象內部,這些數據的下面的行也可以通過數字快速訪問:
-
self.data_Y
points toself.data.lines[Y]
-
self.dataX_Y
points toself.dataX.lines[X]
which is a full shorthard version ofself.datas[X].lines[Y]
課程里面這個地方有個小錯誤
Accessing lines
in Data Feeds
在數據源中訪問lines
Inside data feeds the lines
can also be accessed omitting the lines
. This makes it more natural to work with thinks like close
prices.
在數據源中訪問lines可以忽略lines,這使的它更加自然的工作,像收盤價
比如:
data = btfeeds.BacktraderCSVData(dataname='mydata.csv') ... class MyStrategy(bt.Strategy): ... def next(self): if self.data.close[0] > 30.0: ...
Which seems more natural than the also valid: if self.data.lines.close[0] > 30.0:
.
這樣看起來更加自然,你也可以通過這樣訪問:if self.data.lines.close[0] > 30.0:
The same doesn’t apply to Indicators
with the reasoning being:
這個推斷不適用於指標,其理由是:
An Indicator
could have an attribute close
which holds an intermediate calculation, which is later delivered to the actual lines
also named close
指示符可以有一個close屬性,該屬性保存一個中間計算,然后傳遞到名為close的實際行
In the case of Data Feeds, no calculation takes place, because it is only a data source.
對於數據源,不需要進行計算,因為他原本一個數據源。
Lines len
Lines have a set of points and grow dynamically during execution, therefore the length can be measured at any time by invoking the standard Python len
function.
Lines有一組點,在執行期間動態增長,因此可以通過調用標准Python len函數隨時測量長度。
This applies to for example:
這適用於例如:
-
Data Feeds
-
Strategies
-
Indicator
An additional property applies to Data Feeds when the data is preloaded:
一個附加的屬性適用於數據源時,數據是預先加載:
Method buflen
方法 buflen
The method returns the actual number of bars the Data Feed has available.
該方法返回數據饋送可用的實際條數。
The difference between len
and buflen
len和buflen的區別
-
len
reports how many bars have been processed - len報告有多少bars已經被處理
-
buflen
reports the total number of bars which have been loaded for the Data Feed - buflen報告了為數據飼料加載的條的總數
self.data的數據源,默認是預加載的,所以就它有buflen的方法,可以看出已經預加載的數據。便於后期進行resimple或者filter使用
If both return the same value, either no data has been preloaded or the processing of bars has consumed all preloaded bars (and unless the system is connected to a live feed, this will mean the end of processing)
如果兩者返回相同的值,要么沒有預加載數據,要么對條的處理消耗了所有預加載條(除非系統連接到實時提要,否則這將意味着處理結束)
Inheritance of Lines and Params
Lines與Params的繼承
A kind of metalanguage is in place to support declaration of Params and Lines. Every effort has been made to make it compatible with standard Python inheritance rules.
有一種元語言支持參數和行的聲明。為了使它與標准的Python繼承規則兼容,我們已經盡了一切努力。
Params inheritance
Inheritance should work as expected:
繼承應該像預期的那樣工作:
-
Multiple inheritance is supported
- 支持多重繼承
-
Params from base classes are inherited
- 參數來至基類或者繼承
-
If multiple base classes define the same param the default value of the last class in the inheritance list is used
- 如果多個基類定義相同的參數,則使用繼承列表中最后一個類的默認值
-
If the same param is redefined in a child class, the new default value takes over that of the base class
- 如果在子類中重新定義相同的參數,則新的默認值將接管基類的默認值
這個跟Python中類的繼承一樣,使用了Python的特性
Lines Inheritance
-
Multiple inheritance is supported
- 支持多重繼承
-
Lines from all base classes are inherited. Being named lines there will only be one version of a line if the same name has been used more than once in base classes
- 所有的lines對象都使從基類中繼承的。作為命名行,如果在基類中多次使用同一個名稱,則只有一個版本的行
這上面寫的這些概念,讓我一下感覺很唐突
Indexing: 0 and -1
索引:0和-1
Lines as seen before are line series and have a set of points that conform a line when drawn together (like when joining all closing prices together along a time axis)
如前所述,Lines就是一系列的line,line有一組點順着一條線畫在一起(比如在沿着時間軸將所有收盤價合並在一起時)
To access those points in regular code, the choice has been to use a 0 based approach for the current get/set instant.
要通過常規代碼訪問這些點,可以適用0為基礎的方法,對瞬間的狀態進行讀取或者設置
Strategies do only get values. Indicators do also set values.
策略只能讀取值,指標還能進行設置值
From the previous quick strategy example where the next
method was briefly seen:
從以前的快速策略例子中,我們可以簡要的看到next方法
def next(self): if self.movav.lines.sma[0] > self.data.lines.close[0]: print('Simple Moving Average is greater than the closing price')
The logic is getting the current value of the moving average and the current closing price by applying index 0
.
該邏輯通過索引0將得到當前移動平均線的值與收盤價
注意
Actually for index 0
and when applying logic/arithmetic operators the comparison can be made directly as in:
實際上,對於索引0在應用邏輯/算術運算符時,可以直接進行比較:
if self.movav.lines.sma > self.data.lines.close: ...
See later in the document the explanation for operators.
請參閱文檔后面對操作符的解釋。
Setting is meant to be used when developing, for example, an Indicator, because the current output value has to be set by the indicator.
設置是在開發的時候使用的。比如,一個指標,因為指標的輸出用來設置另一個指標
A SimpleMovingAverage can be calculated for the current get/set point as follows:
可按如下方式計算當前獲取/設置SimpleMovingAverage的點:
def next(self): self.line[0] = math.fsum(self.data.get(0, size=self.p.period)) / self.p.period
通過.get的方式取出一段bar周期內的數據
Accessing previous set points has been modeled following the definition Python makes for -1
when accessing an array/iterable
訪問以前的設置點是按照Python在訪問array/iterable時,使用-1定義的
It points to the last item of the array
這個將指向數組的最后一項
The platform consider the last set item (before the current live get/set point) to be -1
.
該平台認為最后一個設置項(當前狀態get/set點之前)為-1
As such comparing the current close
to the previous close
is a 0
vs -1
thing. In a strategy, for example:
因此對比當前close與前一個close就是0與-1的事情。在策略中,可以這樣
def next(self): if self.data.close[0] > self.data.close[-1]: print('Closing price is higher today')
Of course and logically, prices set before -1
will be accessed with -2, -3, ...
.
按照這個邏輯,-2,-3能夠訪問-1之前的價格
Slicing(切片)
backtrader doesn’t support slicing for lines objects and this is a design decision following the [0]
and [-1]
indexing scheme. With regular indexable Python objects you would do things like:
backtrader不支持lines對象的切片,這是遵循[0]和[-1]索引方案的設計決策。使用常規的可索引Python對象,你會做如下事情:
myslice = self.my_sma[0:] # slice from the beginning til the end
But remember that with the choice for 0
… it is actually the currently delivered value, there is nothing after it. Also:
但是請記住,在lines對於0的選擇…它實際上是當前交付的價值,在它之后沒有任何東西。另外:
myslice = self.my_sma[0:-1] # slice from the beginning til the end
Again … 0
is the current value and -1
is the latest (previous) delivered value. That’s why a slice from 0
-> -1
makes no sense in the backtrader ecosystem.
再次…0是當前值,-1是最近(先前)交付的值。這就是為什么從0 -> -1在backtrader的生態系統中毫無意義。
If slicing were ever to be supported, it would look like:
如果切片曾經被支持,它將看起來像:
myslice = self.my_sma[:0] # slice from current point backwards to the beginning
或者
myslice = self.my_sma[-1:0] # last value and current value
或者
myslice = self.my_sma[-3:-1] # from last value backwards to the 3rd last value
前面講的一堆沒用的,下面才是講到如何切片
Getting a slice
(獲得切片)
An array with the latest values can still be gotten. The syntax:
仍然可以獲得具有最新值的數組。語法:
myslice = self.my_sma.get(ago=0, size=1) # default values shown
That would have returned an arry with 1
value (size=1
) with the current moment 0
as the staring point to look backwards.
這將返回一個帶有1個值(size=1)的array(數組),並將當前時刻0作為向后查看的起始點
To get 10 values from the current point in time (i.e.: the last 10 values):
從當前時間獲取最近的10個值
myslice = self.my_sma.get(size=10) # ago defaults to 0
Of course the array has the ordering you would expect. The leftmost value is the oldest one and the rightmost value is the most current (it is a regular python array and not a lines object)
當然,數組具有您所期望的順序。最左邊的值是最老的,最右邊的值是最新的(它是一個常規的python數組,而不是lines對象)
To get the last 10 values skipping only the current point:
要獲得最后10個值,只跳過當前點:
myslice = self.my_sma.get(ago=-1, size=10)
在取數組對象的時候,要注意,如果價格你需要取前面20個數據,現在還沒有這么多數據,你是取不到數據的。
這個跟指標線很像,你定義的指標應該也在取前面的數值,如果取不到數值,你的指標也不會出來。
LInes:DELAYED indexing
The []
operator syntax is there to extract individual values during the next
logic phase. Lines objects support an additional notation to address values through a delayed lines object during the __init__
phase.
[]操作語法用於在nex方法邏輯中提取單個值。Lines對象支持一種附加的表示法,用於在__init__階段通過delayed Lines對象尋址值。
Let’s say that the interest in the logic is to compare the previous close value to the actual value of a simple moving average. Rather than doing it manually in each next
iteration a pre-canned lines object can be generated:
我們假設,邏輯的興趣在於比較簡單移動平均的實際值和之前的接近值。與其next迭代中手動操作,還不如生成一個預封閉的lines對象:
class MyStrategy(bt.Strategy): params = dict(period=20) def __init__(self): self.movav = btind.SimpleMovingAverage(self.data, period=self.p.period)
# 前一天的收盤價大於均線 self.data.close(-1)昨天的收盤價 self.cmpval = self.data.close(-1) > self.sma def next(self):
# 取值每個點應該是0或1值,成立輸出 if self.cmpval[0]: print('Previous close is higher than the moving average')
Here the (delay)
notation is being used:
這里使用了(延遲)表示法
This delivers a replica of the close
prices but delayed by -1
.
這提供了一個副本,是延遲-1的
And the comparison self.data.close(-1) > self.sma
generates another lines object which returns either 1
if the condition is True
or 0
if False
然后,生成一個對照self.data.close(-1) > self.sma的lines對象,如果條件為真返回1,條件為假返回0
延遲用在stratrgy的__init__階段,通過操作符,創建一個新的indicator來使用。
Lines Coupling(耦合)
The operator ()
can be used as shown above with delay
value to provide a delayed version of a lines object.
如上所示,適用帶有延遲值的操作符()來生成lines對象的延遲版本.
If the syntax is used WITHOUT providing a delay
value, then a LinesCoupler
lines object is returned. This is meant to establish a coupling between indicators that operate on datas with different timeframes.
如果適用的語法沒有提供延遲值,則返回一個LinesCoupler lines對象。這意味着在不同的時間框架內對數據指標的操作進行耦合
Data Feeds with different timeframes have different lengths, and the indicators operating on them replicate the length of the data. Example:
不同時間框架的數據提要有不同的長度,對它們進行操作的指標會復制數據的長度。例子:
-
A daily data feed has around 250 bars per year
- 每天的數據傳輸每年大約有250個bars
-
A weekly data feed has 52 bars per year
- 每周的數據feed每年有52bars
Trying to create an operation (for example) which compares 2 simple moving averages, each operating on the datas quoted above would break. It would be unclear how to match the 250 bars from the daily timeframe to the 52 bars of the weekly timeframe.
嘗試創建一個操作(比如)來比較兩個簡單的移動平均線。在數據上引用,每次操作都會中斷。我們不清楚如何將日線框架的250bars與周線的52bars進行匹配。
The reader could imagine a date
comparison taking place in the background to find out a day - week correspondence, but:
讀者可以想象在在日期比較后台找打了天-周的對應。
Indicators
are just mathematical formulas and have no datetime information
指標只是數學公式,沒有日期時間信息
They know nothing about the environment, just that if the data provides enough values, a calculation can take place.
他們對環境一無所知,只知道如果數據提供了足夠的值,就可以進行計算。
這個()空調用 來拯救我們
class MyStrategy(bt.Strategy): params = dict(period=20) def __init__(self): # data0 is a daily data # 來至文件1 sma0 = btind.SMA(self.data0, period=15) # 15 days sma # data1 is a weekly data # 來至文件2 sma1 = btind.SMA(self.data1, period=5) # 5 weeks sma self.buysig = sma0 > sma1() def next(self): if self.buysig[0]: print('daily sma is greater than weekly sma1')
Here the larger timeframe indicator, sma1
is coupled to the daily timeframe with sma1()
. This returns an object which is compatible with the larger numbers of bars of sma0
and copies the values produced by sma1
, effectively spreading the 52 weekly bars in 250 daily bars
這是一個較大 時間框架指標,sma1通過sma1()耦合到日框架。這將返回一個對象,它兼容了sma0中大量的bars,並復制了sma1產生的值,有效的將52的周bars擴展到250個日bars
這個需要兩個不同的時間框架表導入,指標只有數據,沒有時間軸
Operators, using natural constructs
操作符,適用自然構造
In order to achieve the “ease of use” goal the platform allows (within the constraints of Python) the use of operators. And to further enhance this goal , the use of operators has been broken in two stages.
為了實現“易於使用”的目標,該平台允許(在Python的約束下)使用操作符。為了進一步提高這一目標,對操作符的使用已分兩個階段打破
Stage 1 - Operators Create Objects
階段1- 操作符創建對象
An example has already been seen even if not explicitly meant for this. During the initialization phase (__init__
method) of objects like Indicators and Strategies, operators create objects that can be operated upon, assigned or kept as reference for later using during the evaluation phase of the Strategy’s logic.
我們已經看到一個這樣的示例,即使它沒有表明用於此。在對象的初始化階段(__init__方法)比如Indicators(指標)、Strategies(策略),操作符可以創建可以操作、分配或保留的對象,以供以后在策略邏輯評估階段適用。
Once again a potential implementation of a SimpleMovingAverage, further broken down into steps.
再一次實現了SimpleMovingAverage,進一步細分為步驟。
The code inside the SimpleMovingAverage indicator __init__
could look like:
代碼內部SimpleMovingAverage的指標__init__是應該像這樣:
def __init__(self): # Sum N period values - datasum is now a *Lines* object # that when queried with the operator [] and index 0 # returns the current sum datasum = btind.SumN(self.data, period=self.params.period) # datasum (being *Lines* object although single line) can be # naturally divided by an int/float as in this case. It could # actually be divided by anothr *Lines* object. # The operation returns an object assigned to "av" which again # returns the current average at the current instant in time # when queried with [0] av = datasum / self.params.period # The av *Lines* object can be naturally assigned to the named # line this indicator delivers. Other objects using this # indicator will have direct access to the calculation self.line.sma = av
A more complete use case is shown during the initialization of a Strategy:
一個更完整的用例顯示在策略的初始化過程中:
class MyStrategy(bt.Strategy): def __init__(self): sma = btind.SimpleMovinAverage(self.data, period=20) # 收盤大於均線 close_over_sma = self.data.close > sma sma_dist_to_high = self.data.high - sma # 最高價低於3.5 sma_dist_small = sma_dist_to_high < 3.5 # Unfortunately "and" cannot be overridden in Python being # a language construct and not an operator and thus a # function has to be provided by the platform to emulate it
# 通過And要求兩個邏輯都成立 sell_sig = bt.And(close_over_sma, sma_dist_small)
After the above operations have taken place, sell_sig is a Lines object which can be later used in the logic of the Strategy, indicating if the conditions are met or not.
在執行了上述操作之后,sell_sig是一個Lines對象,可以稍后在策略的邏輯中使用,指示條件是否滿足。
Stage 2 - Operators true to nature
階段2 操作符真假對象
Let’s first remember that a strategy has a next
method which is called for every bar the system processes. This is where operators are actually in the stage 2 mode. Building on the previous example:
首先讓我們記住,一個策略有一個next方法,該方法針對系統處理的每個bar調用。這就是操作員實際上處於階段2模式的地方。在上一個示例的基礎上:
class MyStrategy(bt.Strategy): def __init__(self): self.sma = sma = btind.SimpleMovinAverage(self.data, period=20) close_over_sma = self.data.close > sma self.sma_dist_to_high = self.data.high - sma sma_dist_small = sma_dist_to_high < 3.5 # Unfortunately "and" cannot be overridden in Python being # a language construct and not an operator and thus a # function has to be provided by the platform to emulate it self.sell_sig = bt.And(close_over_sma, sma_dist_small) def next(self): # Although this does not seem like an "operator" it actually is # in the sense that the object is being tested for a True/False # response if self.sma > 30.0: print('sma is greater than 30.0') if self.sma > self.data.close: print('sma is above the close price') if self.sell_sig: # if sell_sig == True: would also be valid print('sell sig is True') else: print('sell sig is False') if self.sma_dist_to_high > 5.0: print('distance from sma to hig is greater than 5.0')
Not a very useful strategy, just an example. During Stage 2 operators return the expected values (boolean if testing for truth and floats if comparing them to floats) and also arithmetic operations do.
這不是一個很有用的策略,只是一個例子。在第2階段,操作符返回期望值(如果測試是否為真,則返回布爾值;如果與浮點數比較,則返回浮點數),算術操作也返回期望值。
注意
Notice that comparisons are actually not using the [] operator. This is meant to further simplify things.
注意,比較實際上沒有使用[]操作符。這意味着進一步簡化事情。
if self.sma > 30.0: … compares self.sma[0] to 30.0 (1st line and current value) if self.sma > self.data.close: … compares self.sma[0] to self.data.close[0]
Some non-overriden operators/functions
一些沒有被重寫的操作符方法
Python will not allow overriding everything and thus some functions are provided to cope with the cases.
Python不允許覆蓋所有內容,因此提供了一些函數來處理這種情況。
注意
Only meant to be used during Stage 1, to create objects which later provide values.
僅在階段1中使用,用於創建以后提供值的對象。
Operators:
-
and
->And
-
or
->Or
Logic Control:
if
->If
Functions:
-
any
->Any
-
all
->All
-
cmp
->Cmp
-
max
->Max
-
min
->Min
-
sum
->Sum
Sum實際使用math.fsum
作為底層操作,因為平台使用浮點數,並且應用常規和可能會影響精度。
-
reduce
->Reduce
These utility operators/functions operate on iterables. The elements in the iterables can be regular Python numeric types (ints, floats, …) and also objects with Lines.
這些實用程序操作符/函數對迭代進行操作。迭代器中的元素可以是常規的Python數值類型(int、float、…),也可以是帶行的對象。
An example generating a very dumb buy signal:
一個產生笨方法購買信號的例子:
class MyStrategy(bt.Strategy): def __init__(self): sma1 = btind.SMA(self.data.close, period=15) self.buysig = bt.And(sma1 > self.data.close, sma1 > self.data.high) def next(self): if self.buysig[0]: pass # do something here
It is obvious that if the sma1
is higher than the high, it must be higher than the close. But the point is illustrating the use of bt.And
.
很明顯,如果sma1高於高點,那么它一定高於收盤價。但重點是要說明bt.And的用法。
Using bt.If
:
class MyStrategy(bt.Strategy): def __init__(self): sma1 = btind.SMA(self.data.close, period=15)
# 需要三個參數,第一個為條件參數,如果為真返回第一個參數,否則返回第二個參數 high_or_low = bt.If(sma1 > self.data.close, self.data.low, self.data.high) sma2 = btind.SMA(high_or_low, period=15)
Breakdown:
分解:
Generate a SMA
on data.close
of period=15
基於收盤價生成15日均線SMA
And then
然后
bt.If
the value of the sma is larger thanclose
, returnlow
, else returnhigh
- bt.If如果收盤價大於15日均線價返回最低價,否則返回最高價。
Remember that no actual value is being returned when bt.If
is being invoked. It returns a Lines object which is just like a SimpleMovingAverage.
請記住正在調用bt.if返回的沒有實際價格。它返回了一個LINES對象,就像SimpleMovingAverage
The values will be calculated later when the system runs
這些值將在系統運行時計算
The generated bt.If Lines object is then fed to a 2nd SMA which will sometimes use the low prices and sometimes the high prices for the calculation
生成的bt.If Lines對象然后被送入第二SMA,該SMA有時使用低價格,有時使用高價格進行計算
Those functions take also numeric values. The same example with a modification:
這些函數也接受數值。同樣的例子有一個修改:
class MyStrategy(bt.Strategy): def __init__(self): sma1 = btind.SMA(self.data.close, period=15) high_or_30 = bt.If(sma1 > self.data.close, 30.0, self.data.high) sma2 = btind.SMA(high_or_30, period=15)
Now the 2nd moving average uses either 30.0
or the high
prices to perform the calculation, depending on the logic status of sma
vs close
現在第二移動平均線使用30.0或高價格執行計算,取決於sma1 vs close的邏輯狀態
注意
The value 30
is transformed internally into a pseudo-iterable which always returns 30
值30在內部被轉換為一個總是返回30的偽迭代