BacktraderPercentRank 重新加載

  |  

社區用戶@randyt已經能夠將反向交易者擴展到其極限。找到一些晦澀難懂的角落,甚至在這里和那裡添加pdb語句,這一直是獲得更精細的重採樣流同步的驅動力。

最近, @randyt添加了一個拉取請求以集成一個名為PercentRank的新指標。這是原始代碼

class PercentRank(bt.Indicator):
    lines = ('pctrank',)
    params = (('period', 50),)

    def __init__(self):
        self.addminperiod(self.p.period)

    def next(self):
        self.lines.pctrank[0] = \
            (math.fsum([x < self.data[0]
                       for x in self.data.get(size=self.p.period)])
            / self.p.period)
        super(PercentRank, self).__init__()

它確實顯示了有人如何進入backtrader的源代碼,提出一些問題並掌握一些概念。這真的很好:

self.addminperiod(self.p.period)

出乎意料,因為最終用戶甚至不會知道有人可以在行對像中使用該 API 調用。此調用告訴機器確保指標至少有可用的數據饋送self.p.period樣本,因為它們是計算所必需的。

在原始代碼中可以看到self.data.get(size=self.p.period) ,只有在後台引擎確保在進行第一次計算之前有這麼多樣本可用時(並且如果exactbars用於減少內存使用,那些許多樣本總是在那裡)

初始重新加載

可以重新編寫代碼以利用旨在緩解開發的預先存在的實用程序。最終用戶無需注意任何事情,但很高興知道一個人是否在不斷地開發或原型設計指標。

class PercentRank_PeriodN1(bt.ind.PeriodN):
    lines = ('pctrank',)
    params = (('period', 50),)

    def next(self):
        d0 = self.data[0]  # avoid dict/array lookups each time
        dx = self.data.get(size=self.p.period)
        self.l.pctrank[0] = math.fsum((x < d0 for x in dx)) / self.p.period

重用PeriodN是消除self.addminperiod魔法並使指標在某種程度上更適合的關鍵。 PeriodN已經具有一個period參數,並將為用戶進行調用(如果__init__被覆蓋,請記住調用super(cls, self).__init__()

計算分為 3,首先緩存字典和數組查找並使其更具可讀性(儘管後者只是口味問題)

代碼也從 13 行減少到 8。這通常在閱讀時有所幫助。

通過 OperationN 重新加載

現有的指標,如SumN ,它對一段時間內數據源的值求和,不像上面那樣直接建立在PeriodN上,而是建立在它的一個名為OperationN的子類上。像它的父類一樣,它仍然沒有定義任何,並且有一個名為func的類屬性。

func將使用一個數組調用,該數組包含主機函數必須操作的時間段的數據。簽名基本上是: func(data[0:period])並返回適合存儲在line中的內容,即:浮點值。

知道了,我們可以試試顯而易見的

class PercentRank_OperationN1(bt.ind.OperationN):
    lines = ('pctrank',)
    params = (('period', 50),)
    func = (lambda d: math.fsum((x < d[-1] for x in d)) / self.p.period)

降到 4。但這將失敗(只需要最後一行):

TypeError: <lambda>() takes 1 positional argument but 2 were given

(使用--strat n1= True使樣本失敗)

通過將我們未命名的函數放在func中,它似乎已經變成了一個方法,因為它需要兩個參數。這可以很快治愈。

class PercentRank_OperationN2(bt.ind.OperationN):
    lines = ('pctrank',)
    params = (('period', 50),)
    func = (lambda self, d: math.fsum((x < d[-1] for x in d)) / self.p.period)

它有效。但是有一些醜陋的東西:這不是人們大多數時候期望的傳遞函數的方式,即:將self作為參數。在這種情況下,我們可以控制函數,但情況可能並非總是如此(需要一個包裝器來解決它)

Python 中的語法糖用staticmethod來拯救,但在我們這樣做之前,我們知道在staticmethod中不再可能引用self.p.period ,失去像以前一樣進行平均計算的能力。

但是由於func接收一個固定長度的可迭代對象,所以可以使用len

現在是新代碼。

class PercentRank_OperationN3(bt.ind.OperationN):
    lines = ('pctrank',)
    params = (('period', 50),)
    func = staticmethod(lambda d: math.fsum((x < d[-1] for x in d)) / len(d))

一切都很好,但這讓人們思考了為什麼以前沒有考慮過讓用戶有機會傳遞他們自己的功能。子類化OperationN是一個不錯的選擇,但可以避免使用staticmethod或將self作為參數並在backtrader的機制上構建更好的方法。

讓我們定義一個方便的OperationN子類。

class ApplyN(bt.ind.OperationN):
    lines = ('apply',)
    params = (('func', None),)

    def __init__(self):
        self.func = self.p.func
        super(ApplyN, self).__init__()

這可能早就應該在平台上。這裡唯一真正的辨別是是否必須有lines = ('apply',)或者用戶應該可以自由定義該和其他一些行。在集成之前需要考慮的事情。

有了ApplyNPercentRank的最終版本完全符合我們的預期。一是手動平均計算的版本。

class PercentRank_ApplyN(ApplyN):
    params = (
        ('period', 50),
        ('func', lambda d: math.fsum((x < d[-1] for x in d)) / len(d)),
    )

在不破壞PEP-8情況下,我們仍然可以重新格式化兩者以適應 3……好!

讓我們運行示例

下面可以看到的示例具有通常的骨架樣板,但旨在顯示不同PercentRank實現的視覺比較。

筆記

使用--strat n1= True執行它以嘗試不起作用的PercentRank_OperationN1版本

圖形輸出。

示例使用

$ ./percentrank.py --help
usage: percentrank.py [-h] [--data0 DATA0] [--fromdate FROMDATE]
                      [--todate TODATE] [--cerebro kwargs] [--broker kwargs]
                      [--sizer kwargs] [--strat kwargs] [--plot [kwargs]]

Sample Skeleton

optional arguments:
  -h, --help           show this help message and exit
  --data0 DATA0        Data to read in (default:
                       ../../datas/2005-2006-day-001.txt)
  --fromdate FROMDATE  Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
  --todate TODATE      Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
  --cerebro kwargs     kwargs in key=value format (default: )
  --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 math

import backtrader as bt



class PercentRank(bt.Indicator):
    lines = ('pctrank',)
    params = (('period', 50),)

    def __init__(self):
        self.addminperiod(self.p.period)

    def next(self):
        self.lines.pctrank[0] = \
            (math.fsum([x < self.data[0]
                       for x in self.data.get(size=self.p.period)])
            / self.p.period)
        super(PercentRank, self).__init__()


class PercentRank_PeriodN1(bt.ind.PeriodN):
    lines = ('pctrank',)
    params = (('period', 50),)

    def next(self):
        d0 = self.data[0]  # avoid dict/array lookups each time
        dx = self.data.get(size=self.p.period)
        self.l.pctrank[0] = math.fsum((x < d0 for x in dx)) / self.p.period


class PercentRank_OperationN1(bt.ind.OperationN):
    lines = ('pctrank',)
    params = (('period', 50),)
    func = (lambda d: math.fsum((x < d[-1] for x in d)) / self.p.period)


class PercentRank_OperationN2(bt.ind.OperationN):
    lines = ('pctrank',)
    params = (('period', 50),)
    func = (lambda self, d: math.fsum((x < d[-1] for x in d)) / self.p.period)


class PercentRank_OperationN3(bt.ind.OperationN):
    lines = ('pctrank',)
    params = (('period', 50),)
    func = staticmethod(lambda d: math.fsum((x < d[-1] for x in d)) / len(d))


class ApplyN(bt.ind.OperationN):
    lines = ('apply',)
    params = (('func', None),)

    def __init__(self):
        self.func = self.p.func
        super(ApplyN, self).__init__()


class PercentRank_ApplyN(ApplyN):
    params = (
        ('period', 50),
        ('func', lambda d: math.fsum((x < d[-1] for x in d)) / len(d)),
    )


class St(bt.Strategy):
    params = (
        ('n1', False),
    )

    def __init__(self):
        PercentRank()
        PercentRank_PeriodN1()
        if self.p.n1:
            PercentRank_OperationN1()
        PercentRank_OperationN2()
        PercentRank_OperationN3()
        PercentRank_ApplyN()

    def next(self):
        pass


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.BacktraderCSVData(dataname=args.data0, **kwargs)
    cerebro.adddata(data0)

    # 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=(
            'Sample Skeleton'
        )
    )

    parser.add_argument('--data0', default='../../datas/2005-2006-day-001.txt',
                        required=False, help='Data to read in')

    # Defaults for dates
    parser.add_argument('--fromdate', required=False, default='',
                        help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')

    parser.add_argument('--todate', required=False, default='',
                        help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')

    parser.add_argument('--cerebro', required=False, default='',
                        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()

推薦閱讀

相關文章

Backtrader按日線交易

似乎在世界某個地方有一種权益(Interest)可以總結如下: 使用每日柱線引入訂單,但使用開盤價 這來自工單#105订单执行逻辑与当前数据和#101动态投注计算中的對話 backtrader 嘗試盡可能保持現實,並且在處理每日柱線時適用以下前提: 當每日柱被評估時,柱線已經結束 這是有道理的,

Backtrader 教程:佣金計劃

不可知論在繼續之前,讓我們記住, backtrader者試圖對數據代表什麼保持不可知論。不同的佣金方案可以應用於相同的數據集。讓我們看看它是如何做到的。使用代理快捷方式這使最終用戶遠離CommissionInfo對象,因為可以通過單個函數調用創建/設置佣金方案。

Backtrader對逐筆報價數據重新取樣

backtrader 已經可以從分鐘數據中重新採樣。接受價格變動數據不是問題,只需將 4 個常用欄位(open、 high、 low、 close)設置為價格變動值。 但是傳遞要重新採樣的逐筆報價數據再次生成相同的數據。作為或版本 1.1.11.88,情況已不再如此。

Backtrader在同一軸上列印

上一篇文章期貨和現貨補償,在同一空間上繪製原始數據和略微(隨機)修改的數據,但不是在同一軸上。 從該帖子中恢復第 1張圖片。 人們可以看到: 圖表的左側和右側有不同的刻度 當查看在原始數據周圍振蕩+- 50點的旋轉紅line(隨機數據)時,這一點最為明顯。

Backtrader 教程:繪圖 - 同一軸

上一篇future-spot 將原始數據和稍微(隨機)修改的數據繪製在同一空間上,但不在同一軸上。從該帖子中恢復第一張圖片。有人能看見:圖表左右兩側有不同的刻度當查看在原始數據周圍振盪+- 50點的擺動紅線(隨機數據)時,這一點最為明顯。在圖表上,視覺印像是這些隨機數據大多總是高於原始數據。

Backtrader教程:數據饋送

backtrader 附帶一組 Data Feed 解析器(在編寫所有基於CSV時),可讓您從不同的來源載入數據。

Backtrader教程:篩檢程式

此功能是 backtrader 的相對較新的補充,必須安裝到已經存在的內部結構中。這使得它不像希望的那樣靈活且100%功能齊全,但在許多情況下它仍然可以達到目的。 儘管該實現試圖允許隨插即用的篩檢程式連結,但預先存在的內部結構使得很難確保始終可以實現。因此,某些篩選器可能是連結的,而其他一些篩選器可能不是。

Backtrader數據同步

在最新版本中,次要編號已從 8 移至 9,以指示即使已考慮相容性,也可能會對行為產生一些影響。 在 1.9.0.99 版中,使用 datetime 同步多個數據的整個機制已經重新設計(適用於下一個和一次模式)。

Backtrader教程:傭金計劃 - 信貸利息

在某些情況下,真實經紀人的現金金額可能會減少,因為資產操作包括利率。例子: 賣空股票 交易所買賣基金包括多頭和空頭 該費用直接與經紀人帳戶中的現金餘額挂鉤。但它仍然可以被視為傭金計劃的一部分。因此,它已被建模為 backtrader。

Backtrader蟒蛇隱藏的力量3

Last,但並非最不重要的一點是,在這個系列中,關於如何在 backtrader 中使用Python的隱藏功能是一些神奇變數是如何出現的。