前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >BackTrader 中文文档(十六)

BackTrader 中文文档(十六)

作者头像
ApacheCN_飞龙
发布2024-05-24 16:52:19
730
发布2024-05-24 16:52:19
举报
文章被收录于专栏:信数据得永生信数据得永生

原文:www.backtrader.com/

砖形图

原文:www.backtrader.com/blog/posts/2017-06-26-renko-bricks/renko-bricks/

砖形图是一种以价格比时间更重要的方式展示价格演变的方法。这已经在backtrader1.9.54.122版本中作为过滤器引入了。

Stockcharts砖形图有一个很好的参考。请参阅StockCharts 上的砖形图

一些示例

注意

size=35align=10.0参数适用于backtrader存储库中的示例数据。这些值必须针对每个数据资产进行微调。

首先,让我们将砖形图单独放在一个图表上:

代码语言:javascript
复制
$ ./renko.py --renko size=35,align=10.0 --plot

输出

image
image

人们可以看到图表立即显示出一些支撑/阻力区域,这是砖形图的主要优势之一。显然,X 轴上的时间演变不再是恒定的,而是被拉伸或压缩,取决于价格行动在期间是更静态还是移动了几个砖块。

为了更好地看到和欣赏效果,让我们将普通的价格条和砖形图放在一起在一个图表上:

代码语言:javascript
复制
$ ./renko.py --renko size=35,align=10.0 --plot --dual

输出

image
image

时间周期的拉伸和压缩现在更加明显。另一个要考虑的因素是价格行动的关注如何影响应用的指标,正如图表上显示的两个RSI指标所示。

示例用法

代码语言:javascript
复制
$ ./renko.py --help
usage: renko.py [-h] [--data0 DATA0] [--fromdate FROMDATE] [--todate TODATE]
                [--cerebro kwargs] [--broker kwargs] [--sizer kwargs]
                [--strat kwargs] [--plot [kwargs]] [--renko kwargs] [--dual]

Renko bricks sample

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: )
  --renko kwargs       kwargs in key=value format (default: )
  --dual               put the filter on a second version of the data
                       (default: False)

示例代码

代码语言:javascript
复制
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import argparse
import datetime

import backtrader as bt

class St(bt.Strategy):
    params = dict(
    )

    def __init__(self):
        for d in self.datas:
            bt.ind.RSI(d)

    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)

    data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **kwargs)

    fkwargs = dict()
    fkwargs.update(**eval('dict(' + args.renko + ')'))

    if not args.dual:
        data0.addfilter(bt.filters.Renko, **fkwargs)
        cerebro.adddata(data0)
    else:
        cerebro.adddata(data0)
        data1 = data0.clone()
        data1.addfilter(bt.filters.Renko, **fkwargs)
        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
    kwargs = dict(stdstats=False)
    kwargs.update(**eval('dict(' + args.cerebro + ')'))
    cerebro.run(**kwargs)

    if args.plot:  # Plot if requested to
        kwargs = dict(style='candle')
        kwargs.update(**eval('dict(' + args.plot + ')'))
        cerebro.plot(**kwargs)

def parse_args(pargs=None):
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description=(
            'Renko bricks sample'
        )
    )

    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')

    parser.add_argument('--renko', required=False, default='',
                        metavar='kwargs', help='kwargs in key=value format')

    parser.add_argument('--dual', required=False, action='store_true',
                        help='put the filter on a second version of the data')

    return parser.parse_args(pargs)

if __name__ == '__main__':
    runstrat()

Backtrader:该基金

原文:www.backtrader.com/blog/posts/2017-06-19-fund-tracking/fund-tracking/

backtrader已经有一段时间被使用,可以说是专业使用,除了一些银行和交易公司之外,还有Backtrader 基金

历史

一群志同道合且相互认识已久的人决定走上开设(避险)基金的道路,并以backtrader作为交易理念的基石。有一件事情是不可或缺的:它必须是 100%受监管的(但不在开曼群岛或类似地方)

位置、传统和网络在欧盟首先,然后在西班牙放置重点,西班牙(以及其他一些地方)的立法允许伞形基金托管子基金,这使得可以用较少的资金和参与者数量创建一个完全受监管的基金。

还有……基金已被西班牙的监管机构CNMV(西班牙国家证券市场委员会)批准,ISINES0131444038。链接:

对于能阅读西班牙语的人,backtrader的使用已在官方基金说明书中记录。

对于那些可能在某个时候决定走这条路的人,最重要的事情是:

  • 官僚主义很慢,沿途会有很多问题
  • 保持一切的记录(执行的操作、现金/净资产价值水平、头寸、杠杆)
  • 向监管机构报告是必须的(因此需要收集和保持前述信息的良好组织)
  • 遵循定义的风险/波动性水平不仅仅是一个指导方针
  • 管理其他人的资金(OPM)是一种真正的心理负担。会有损失,会有问题。无论问题有多么善意和天真:它们都会产生影响。

backtrader是交易理念的基础,并且它已经找到了一个新的应用领域:报告。自定义的分析器指标可以帮助控制风险/波动性,从而减轻了行政负担。

可能是因为我们年纪大了(而且是老派的),我们仍然更喜欢手动执行(自动执行将在未来某个时候接管)

下面描述的功能是为了辅助管理基金和回测资金进出以及绩效不再是追踪净资产价值的问题。

基金跟踪

在版本1.9.52.121中,backtrader中的经纪人不仅在现金/价值方面跟踪会计,而且像在基金中一样进行,即:

  • 基金价值(实际基金份额的价值)
  • 股份数量

通过这样做,我们可以模拟现金存款和提款,同时仍然跟踪实际表现,而常规会计会被现金流入/流出所扭曲。

除了经纪人的变化外,分析器观察者也已经适应(那些与净资产价值有关的)以支持fund参数,以决定实际应该追踪什么。例如TimeReturn

代码语言:javascript
复制
...
cerebro.addanalyzer(bt.analyzers.TimeReturn)  # auto-detect broker mode
cerebro.addanalyzer(bt.analyzers.TimeReturn, fund=True)  # track fund value
cerebro.addanalyzer(bt.analyzers.TimeReturn, fund=False)  # track net asset value
...

这是什么基金追踪

想象一下使用案例(稍后在示例中):某人从 1000 货币单位开始,在每个月的 15 日添加 100 货币单位。12 个月后,账户中的总额为 2200。根据最初的持仓计算的回报率为

以通常的方式计算回报,这意味着在没有执行任何操作的情况下,年度回报率为120%。当然,这是不正确的。

为了缓解这个问题,不管账户的初始值如何,基金份额(fundvalue)的价值被设定为100.0。并且根据此值和起始净资产价值(1000货币单位),计算基金份额数量如下:

  • fundshares = 净资产价值 / 基金价值

在这种情况下,实际上是1000 / 100.0 = 10 份股份

每次加入现金后,我们都会增加股份数量:

  • new_fund_shares = 加入现金 / 基金价值

因为我们每次添加100.0货币单位且没有执行任何操作:

代码语言:javascript
复制
- ``100.0 / 100.0 = 1 share```

```py

Notice that the *fundvalue* remains unchanged. Quickly forwarding to the end of the year we have the following:

*   Starting net-asset-value: `1000`

*   Final net-asset-value: `2200`

*   Starting fundvalue = `100`

*   Final fundvalue = `100`

*   Starting number of shares: `10`

*   Final number of shares: `22`

Now if we calculate the returns using the starting and ending *fundvalues* and because they are the same we have a: `0%` which matches the reality. because cash additions have not changed

## Using *fund tracking* in backtrader

### Adding cash

First, the broker has gained a method to canonically add cash to the system:

add_cash(cash)

代码语言:javascript
复制
Using it inside the strategy for example:

`def next(self):

代码语言:javascript
复制
如果条件成立:

    self.broker.add_cash(1000.0)`
代码语言:javascript
复制
This method **MUST** be used to track the entry and exit of cash into the system and properly track the fund value.

### Automatic

Activate it in the *broker*:

`…

cerebro.broker.set_fundmode(True)

…`

代码语言:javascript
复制
changing at the same time the default fund start value:

`…

cerebro.broker.set_fundmode(True, 10.0) # 默认为 100

…`

代码语言:javascript
复制
or in independent calls:

`…

cerebro.broker.set_fundmode(True)

cerebro.broker.set_fundstartval(10.0) # 默认为 100

…`

代码语言:javascript
复制
After activation of the default mode and coming back to the `TimeReturn` analyzer examples from above:

`…

1

cerebro.addanalyzer(bt.analyzers.TimeReturn) # 自动检测经纪人模式

2

cerebro.addanalyzer(bt.analyzers.TimeReturn, fund=True) # 追踪基金价值

3

cerebro.addanalyzer(bt.analyzers.TimeReturn, fund=False) # 追踪净资产价值

…`

代码语言:javascript
复制
`1` and `2` are equivalent. But one should go for `1`. If one wishes to compare, one can always force the `TimeReturn` analyzer to not use the *fundvalue* and rather track the *net-asset-value*

An example is worth a thousand words. In the sample, we’ll be using doing as described above but with some extra cash (the asset has a per-share value over `3000`). The initial cash level will be `10000`, the default in *backtrader* and on the *15^(th)* of each month, `1000` extra monetary units will be added (using a recurring `Timer`). It will be 24 months (which is the size of the standard data sample used in *backtrader*)

#### Without any operations

$ ./fund-tracker.py --broker fundmode=True --strat cash2add=1000 --cerebro writer=True --plot

代码语言:javascript
复制
The graphical view

![image](https://gitcode.net/OpenDocCN/flygon-quant-docs-zh/-/raw/master/docs/backtrader/img/0dc175c0aaa817528eafd4a73718a16d.png)

And the text output (capped for readability):

`- timereturn:

代码语言:javascript
复制
    ...

    - 基金:无
  • 分析:
    • 2005-12-31: 0.0
    • 2006-12-31: 0.0

  • timereturn1:
代码语言:javascript
复制
- 参数:

    ...

    - 基金:真
  • 分析:
    • 2005-12-31: 0.0
    • 2006-12-31: 0.0

  • timereturn2:
代码语言:javascript
复制
    ...

    - 基金:假
  • 分析:
    • 2005-12-31: 1.2
    • 2006-12-31: 0.545454545455`
代码语言:javascript
复制
There are 3 `TimeReturn` analyzers getting added.

*   The 1^(st) has `fund=None` (default) which means to track the actual `fundmode` set in the broker (in this case `True`)

    It says that the yearly returns were `0.0` and `0.0`. Since we made no operations: **Ok**

*   The 2^(nd) has `fund=True` which means to use the *fundvalue* always

    It says that the yearly returns were `0.0` and `0.0`. Since we made no operations: **Ok**

*   The 3^(rd) has `fund=False` which means to use the *net-asset-value* always

    It says that the yearly returns were `1.2` (*120%*) and `0.54` (*54%*). Since we made no operations: **This is clearly wrong**

The plot contains also the 2 new *Observers* (`FundValue` and `FundShares`) which allow to see how even if the *net-asset-value* grows with the addition of cash every month, the *fundvalue* remains constant as `100.0`. At the same times the shares grow with each cash addition.

#### Let’s trade

The same as above but with some trading using a standard moving average crossover

$ ./fund-tracker.py --broker fundmode=True --strat cash2add=1000,trade=True --cerebro writer=True --plot

代码语言:javascript
复制
The graphical view

![image](https://gitcode.net/OpenDocCN/flygon-quant-docs-zh/-/raw/master/docs/backtrader/img/03a9b4204180b3201aa5cfece1a37a3d.png)

And the text output (capped for readability):

`- timereturn:

代码语言:javascript
复制
...

- 基金:无
代码语言:javascript
复制
- 分析:

    - 2005-12-31:-0.00642229824537

    - 2006-12-31:7.78998679263e-05

.......................................................................

- timereturn1:

    ...

    - 基金:真
  • 分析:
    • 2005-12-31:-0.00642229824537
    • 2006-12-31:7.78998679263e-05

  • timereturn2:
代码语言:javascript
复制
    ...

    - 基金:假
  • 分析:
    • 2005-12-31:1.19378185337
    • 2006-12-31:0.546479045423`
代码语言:javascript
复制
The same three `TimeReturn` analyzers from before. The ones with `fund=None` and `fund=True` give reasonable results whereas the one using `fund=False` is clearly off the chart again with `119%` and `54%`, which is clearly not the return offered by the moving average crossover.

### Manual

In this case (which is the default in the broker and even if the broker is tracking the value of the fund, only those analyzers with `fund=True` will use the value.

A quick run with only the textual ouput:

$ ./fund-tracker.py --strat cash2add=1000,trade=True --cerebro writer=True

代码语言:javascript
复制
Output:

`- timereturn:

代码语言:javascript
复制
    ...

    - 基金:无
  • 分析:
    • 2005-12-31:1.19378185337
    • 2006-12-31:0.546479045423

  • timereturn1:
代码语言:javascript
复制
    ...

    - 基金:真
  • 分析:
    • 2005-12-31:-0.00642229824537
    • 2006-12-31:7.78998679263e-05

  • timereturn2:
代码语言:javascript
复制
    ...

    - 基金:假
  • 分析:
    • 2005-12-31:1.19378185337
    • 2006-12-31:0.546479045423`
代码语言:javascript
复制
Now only the `TimeReturn` with `fund=True` delivers sensible results.

## Conclusion

The new `fundmode` implemented in the broker and which can be used (automatically/manually) in the analyzers, allows to use *backtrader* to model the inner workings of a real fund or use cases like constant investment of money at given intervals.

## Sample Usage

`$ ./fund-tracker.py --help

用法:fund-tracker.py [-h] [–data0 DATA0] [–fromdate FROMDATE]

代码语言:javascript
复制
                [--todate TODATE] [--cerebro kwargs] [--broker kwargs]

                [--sizer kwargs] [--strat kwargs] [--plot [kwargs]]

基金跟踪示例

可选参数:

-h,–help 显示此帮助消息并退出

–data0 DATA0 要读取的数据(默认值:

代码语言:javascript
复制
                ../../datas/2005-2006-day-001.txt)

–fromdate FROMDATE 日期[时间]的格式为 YYYY-MM-DD[THH:MM:SS](默认值:

–todate TODATE 日期[时间]的格式为 YYYY-MM-DD[THH:MM:SS](默认值:

–cerebro kwargs 以键=值格式的 kwargs(默认值:)

–broker kwargs 以键=值格式的 kwargs(默认值:)

–sizer kwargs 以键=值格式的 kwargs(默认值:)

–strat kwargs 以键=值格式的 kwargs(默认值:)

–plot [kwargs] 以键=值格式的 kwargs(默认值:)`

代码语言:javascript
复制
## Sample Code

`from future import (absolute_import, division, print_function,

代码语言:javascript
复制
                    unicode_literals)

导入 argparse

导入 datetime

导入 backtrader as bt

类 St(bt.SignalStrategy):

代码语言:javascript
复制
参数 = 字典(

    cash2add=None,

    cashonday=15,

    pfast=10,

    pslow=30,

    trade=False,

)

def __init__(self):

    self.add_timer(when=bt.Timer.SESSION_END, monthdays=[self.p.cashonday])

    sma1 = bt.ind.SMA(period=self.p.pfast)

    sma2 = bt.ind.SMA(period=self.p.pslow)

    signal = bt.ind.CrossOver(sma1, sma2)

    if self.p.trade:

        self.signal_add(bt.SIGNAL_LONGSHORT, signal)

def notify_timer(self, timer, when, *args, **kwargs):

    # 无需检查计时器,只有一个

    if self.p.cash2add is not None:

        self.broker.add_cash(self.p.cash2add)

def next(self):

    pass

def runstrat(args=None):

代码语言:javascript
复制
args = parse_args(args)

cerebro = bt.Cerebro()

# 数据源关键字参数

kwargs = dict()

# 从/到日期解析

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)

data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **kwargs)

cerebro.adddata(data0)

# 经纪人

cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')'))

# 大小规模

cerebro.addsizer(bt.sizers.PercentSizer,

                **eval('dict(' + args.sizer + ')'))

# 策略

cerebro.addstrategy(St, **eval('dict(' + args.strat + ')'))

cerebro.addobserver(bt.observers.FundValue)

cerebro.addobserver(bt.observers.FundShares)

ankwargs = dict(timeframe=bt.TimeFrame.Years)

cerebro.addanalyzer(bt.analyzers.TimeReturn, **ankwargs)

cerebro.addanalyzer(bt.analyzers.TimeReturn, fund=True, **ankwargs)

cerebro.addanalyzer(bt.analyzers.TimeReturn, fund=False, **ankwargs)

# 执行

cerebro.run(**eval('dict(' + args.cerebro + ')'))

if args.plot:  # 如果要求绘图,则绘制

    cerebro.plot(**eval('dict(' + args.plot + ')'))

def parse_args(pargs=None):

代码语言:javascript
复制
parser = argparse.ArgumentParser(

    formatter_class=argparse.ArgumentDefaultsHelpFormatter,

    描述=(

        '基金跟踪示例'

    )

)

parser.add_argument('--data0', default='../../datas/2005-2006-day-001.txt',

                    required=False, help='要读取的数据')

# 日期的默认值

parser.add_argument('--fromdate', required=False, default='',

                    help='以 YYYY-MM-DD[THH:MM:SS] 格式的日期[时间]')

parser.add_argument('--todate', required=False, default='',

                    help='以 YYYY-MM-DD[THH:MM:SS] 格式的日期[时间]')

parser.add_argument('--cerebro', required=False, default='',

                    metavar='kwargs', help='以键=值格式的关键字参数')

parser.add_argument('--broker', required=False, default='',

                    metavar='kwargs', help='以键=值格式的关键字参数')

parser.add_argument('--sizer', required=False, default='',

                    metavar='kwargs', help='以键=值格式的关键字参数')

parser.add_argument('--strat', required=False, default='',

                    metavar='kwargs', help='以键=值格式的关键字参数')

parser.add_argument('--plot', required=False, default='',

                    nargs='?', const='{}',

                    metavar='kwargs', help='以键=值格式的关键字参数')

return parser.parse_args(pargs)

if name == ‘main’:

代码语言:javascript
复制
runstrat()`
代码语言:javascript
复制
# 发布版本 1.9.51.121

> 原文:[`www.backtrader.com/blog/posts/2017-06-12-release-1.9.51.121/release-1.9.51.121/`](https://www.backtrader.com/blog/posts/2017-06-12-release-1.9.51.121/release-1.9.51.121/)

即使是一个次要版本,也有一些有趣的东西,可能值得为它们撰写专门的博客文章。

## `linealias`

[Pull-Request #320](https://github.com/mementum/backtrader/pull/320) 包括指标`相对动量指数`(或`RMI`),根据文献,这是`RSI`的演变,它:

+   考虑*up*和*down*周期,回溯期大于`1`

因此,与其让一个指标重复`RSI`的大部分内容,做两件事似乎更有用:

1.  扩展`RSI`(以及子指标如`UpDay`和`DownDay`)以支持大于*1*的回溯期。`RMI`可以作为一个简单具有一些不同默认值的子类来实现。

1.  `RMI`指标的逻辑名称是`rmi`,但`RSI`已经决定了名称为`rsi`。通过添加一个名为`linealias`的新功能来解决这个问题

`RMI`的实现看起来是这样的:

```py
class RelativeMomentumIndex(RSI):
    alias = ('RMI', )

    linealias = (('rsi', 'rmi',),)  # add an alias for this class rmi -> rsi
    plotlines = dict(rsi=dict(_name='rmi'))  # change line plotting name

为基类添加了rsi线的别名,名称为rmi。如果有人想要创建一个子类并使用名称rmi,现在是可能的。

此外,rsi线的绘图名称也更改为rmi。还有一种替代实现方式:

代码语言:javascript
复制
class RelativeMomentumIndex(RSI):
    alias = ('RMI', )

    linesoverrride = True  # allow redefinition of the lines hierarcy
    lines = ('rmi',)  # define the line
    linealias = (('rmi', 'rsi',),)  # add an alias for base class rsi -> rmi

在这里,不再考虑RSI的现有层次结构,而是使用lines来定义唯一的名为rmi的线。不需要定义绘图名称,因为唯一的线现在具有预期的名称。

但基类将无法填充值,因为它期望有一个名为rsi的线。因此添加了一个反向别名,让它找到该线。

交互式经纪人优化

使用交互式经纪人作为优化数据源的实时连接并未被预见。然而,有用户尝试了这种方法,导致了出现了速度违规。原因在于交互式经纪人数据源标记为live数据源,允许系统绕过某些事情,比如数据预加载。

没有预加载,每次优化实例都会尝试重新从交互式经纪人下载相同的历史数据。考虑到这一点,很明显数据源可以查看用户是否只请求历史下载,在这种情况下不报告自己为live,允许平台预加载数据并在优化实例之间共享。

查看社区帖子。使用 IBStore 进行优化会导致冗余连接/下载

平均趋势蜡烛图

这个其他社区帖子试图开发平滑蜡烛图作为指标:Develop Heikinashi Indicators,面临着一些问题,因为需要一个种子值,这可以在指标的prenext阶段完成。

作为传统蜡烛图的一个有趣的显示替代方案,这已经被实现为一个过滤器,允许修改数据源以真正提供平滑蜡烛图。就像这样:

代码语言:javascript
复制
data0 = MyDataFeed(dataname='xxx', timeframe=bt.TimeFrame.Days, compression=1)
data0.addfilter(bt.filters.HeikinAshi)
cerebro.adddata(data0)

任何人都可以通过这段代码快速比较蜡烛图:

代码语言:javascript
复制
data0 = MyDataFeed(dataname='xxx', timeframe=bt.TimeFrame.Days, compression=1)
cerebro.adddata(data0)

data1 = data0.clone()
data1.addfilter(bt.filters.HeikinAshi)
cerebro.adddata(data1)

要绘制蜡烛图,请记住执行:

代码语言:javascript
复制
cerebro.plot(style='candle')

使用样本每日数据来自 2005 年和 2006 年的来源。

image
image

并稍微放大以更好地欣赏差异

image
image

允许辅助演员重新调整 y 轴的比例

数据源的轴始终将主数据源用作比例所有者,因为数据始终是视图中最重要的部分。例如,考虑一下布林带,可能会导致顶部带远离数据的最大值,并且允许此带重新调整图表,将减少数据在图表中占用的空间,这是不希望的。

现在可以使用plotylimited来控制行为,例如:

代码语言:javascript
复制
...
data0 = MyDataFeed(dataname='xxx', timeframe=bt.TimeFrame.Days, compression=1)
data0.plotinfo.plotlog = False  # allow other actors to resize the axis
...

在下图中,底部的数据源使用了plotylimited=False进行绘制。布林带不会超出图表,因为它们会影响缩放,并且一切都适合图表中。

image
image

这在社区中也有所评论。How max - min plot boundaries are set?

半对数图(也称为对数图)

现在可以使用半对数刻度(y 轴刻度)绘制单个轴。例如:

代码语言:javascript
复制
...
data0 = MyDataFeed(dataname='xxx', timeframe=bt.TimeFrame.Days, compression=1)
data0.plotinfo.plotlog = True
data0.plotinfo.plotylimited = True
cerebro.adddata(data0)
...

这意味着由此数据源控制的轴将使用对数刻度,但其他轴不会,因此

  • 在数据上绘制移动平均线也将使用该刻度进行绘制
  • 随机指标(位于不同轴上且具有不同刻度)仍将以线性方式绘制

注意

请注意,使用了plotylimited=True。这是为了让matplotlib正确地计算对数图表的限制(因为刻度是 10 的幂)以适应图表中的内容。

简单地比较Yahoo数据的长期期间的示例。

image
image

允许plotmaster指向自身

在同一轴上绘制多个数据源已经是可能的,但是一个小麻烦不允许设置plotinfo.plotmaster值的干净循环。之前必须执行以下操作:

代码语言:javascript
复制
mydatas = []
data = MyDataFeed(dataname=mytickers[0], timeframe=..., compression=...)
mydatafeeds.append(data)
for ticker in mytickers[1:]
    data = MyDataFeed(dataname=ticker, timeframe=..., compression=...)
    mydatafeeds.append(data)
    data.plotinfo.plotmaster = mydatas[0]

现在可以使用更清晰的循环:

代码语言:javascript
复制
mydatas = []
for ticker in mytickers:
    data = MyDataFeed(dataname=ticker, timeframe=..., compression=...)
    mydatafeeds.append(data)
    data.plotinfo.plotmaster = mydatas[0]

并且dnames已经被记录了

引用数据源的名称已经可用,但它被忽略了,没有出现在文档中,因此它是一个隐藏的宝藏。策略中的 dnames 属性支持点表示法和*[]*表示法(实际上是一个 dict 子类)。如果我们首先添加一些数据源:

代码语言:javascript
复制
mytickers = ['YHOO', 'IBM', 'AAPL']
for t in mytickers:
  d = bt.feeds.YahooFinanceData(dataname=t, fromdate=..., name=t.lower())

在策略中稍后可以执行以下操作:

代码语言:javascript
复制
def __init__(self):
  yhoosma = bt.ind.SMA(self.dnames.yhoo, period=20)
  aaplsma = bt.ind.SMA(self.dnames['aapl'], period=30)

  # or even go over the keys/items/values like in a regular dict
  # for example with a dictionary comprehension
  stocs = {name: bt.ind.Stochastic(data) for name, data in self.dnames.items()}

结论

一个包含小改动的小版本发布,增加了一些巧妙的功能。

策略选择再访

原文:www.backtrader.com/blog/posts/2017-05-16-stsel-revisited/stsel-revisited/

最初的策略选择方法使用了两种策略,它们是手动注册的,以及一个简单的 [0, 1] 列表来决定哪个是策略的目标。

由于 Python 提供了许多元类的反射可能性,可以实际上自动化这种方法。让我们使用 装饰器 方法来做这件事,在这种情况下可能是最不侵入性的方法(不需要为策略定义 元类

重新设计工厂

工厂现在:

  • 在策略之前声明
  • 具有一个空的 _STRATS 类属性(之前它包含要返回的策略)
  • 具有一个 register 类方法,将用作装饰器,并接受一个参数,该参数将添加到 _STRATS
  • 具有一个 COUNT 类方法,将返回一个迭代器(实际上是一个 range),其中包含要优化的可用策略的计数
  • 对实际工厂方法没有任何更改:__new__,它仍然使用 idx 参数来返回 _STRATS 类属性中给定索引处的任何内容
代码语言:javascript
复制
class StFetcher(object):
    _STRATS = []

    @classmethod
    def register(cls, target):
        cls._STRATS.append(target)

    @classmethod
    def COUNT(cls):
        return range(len(cls._STRATS))

    def __new__(cls, *args, **kwargs):
        idx = kwargs.pop('idx')

        obj = cls._STRATSidx
        return obj

如下:

  • StFetcher 策略工厂不再包含任何硬编码的策略

装饰待优化的策略

示例中的策略不需要重新设计。使用 StFetcherregister 方法进行装饰就足以将它们添加到选择池中。

代码语言:javascript
复制
@StFetcher.register
class St0(bt.SignalStrategy):

代码语言:javascript
复制
@StFetcher.register
class St1(bt.SignalStrategy):

利用 COUNT

在将策略工厂添加到系统中并使用 optstrategy 时,过去的手动 [0, 1] 列表可以完全替换为对 StFetcher.COUNT() 的透明调用。硬编码已经结束。

代码语言:javascript
复制
 cerebro.optstrategy(StFetcher, idx=StFetcher.COUNT())

一个样本运行

代码语言:javascript
复制
$ ./stselection-revisited.py --optreturn
Strat 0 Name OptReturn:
  - analyzer: OrderedDict([(u'rtot', 0.04847392369449283), (u'ravg', 9.467563221580632e-05), (u'rnorm', 0.02414514457151587), (u'rnorm100', 2.414514457151587)])

Strat 1 Name OptReturn:
  - analyzer: OrderedDict([(u'rtot', 0.05124714332260593), (u'ravg', 0.00010009207680196471), (u'rnorm', 0.025543999840699633), (u'rnorm100', 2.5543999840699634)])

我们的两种策略已经运行并且(如预期)产生了不同的结果。

注意

该示例很简单,但已经在所有可用的 CPU 上运行。使用 --maxpcpus=1 执行将更快。对于更复杂的情况,使用所有 CPU 将很有用。

结论

选择已经完全自动化。与之前一样,可以想象类似于查询数据库以获取可用策略数量,然后逐个获取策略的方法。

样本用法
代码语言:javascript
复制
$ ./stselection-revisited.py --help
usage: strategy-selection.py [-h] [--data DATA] [--maxcpus MAXCPUS]
                             [--optreturn]

Sample for strategy selection

optional arguments:
  -h, --help         show this help message and exit
  --data DATA        Data to be read in (default:
                     ../../datas/2005-2006-day-001.txt)
  --maxcpus MAXCPUS  Limit the numer of CPUs to use (default: None)
  --optreturn        Return reduced/mocked strategy object (default: False)
代码

已经包含在 backtrader 的源代码中

代码语言:javascript
复制
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import argparse

import backtrader as bt
from backtrader.utils.py3 import range

class StFetcher(object):
    _STRATS = []

    @classmethod
    def register(cls, target):
        cls._STRATS.append(target)

    @classmethod
    def COUNT(cls):
        return range(len(cls._STRATS))

    def __new__(cls, *args, **kwargs):
        idx = kwargs.pop('idx')

        obj = cls._STRATSidx
        return obj

@StFetcher.register
class St0(bt.SignalStrategy):
    def __init__(self):
        sma1, sma2 = bt.ind.SMA(period=10), bt.ind.SMA(period=30)
        crossover = bt.ind.CrossOver(sma1, sma2)
        self.signal_add(bt.SIGNAL_LONG, crossover)

@StFetcher.register
class St1(bt.SignalStrategy):
    def __init__(self):
        sma1 = bt.ind.SMA(period=10)
        crossover = bt.ind.CrossOver(self.data.close, sma1)
        self.signal_add(bt.SIGNAL_LONG, crossover)

def runstrat(pargs=None):
    args = parse_args(pargs)

    cerebro = bt.Cerebro()
    data = bt.feeds.BacktraderCSVData(dataname=args.data)
    cerebro.adddata(data)

    cerebro.addanalyzer(bt.analyzers.Returns)
    cerebro.optstrategy(StFetcher, idx=StFetcher.COUNT())
    results = cerebro.run(maxcpus=args.maxcpus, optreturn=args.optreturn)

    strats = [x[0] for x in results]  # flatten the result
    for i, strat in enumerate(strats):
        rets = strat.analyzers.returns.get_analysis()
        print('Strat {} Name {}:\n  - analyzer: {}\n'.format(
            i, strat.__class__.__name__, rets))

def parse_args(pargs=None):

    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='Sample for strategy selection')

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

    parser.add_argument('--maxcpus', required=False, action='store',
                        default=None, type=int,
                        help='Limit the numer of CPUs to use')

    parser.add_argument('--optreturn', required=False, action='store_true',
                        help='Return reduced/mocked strategy object')

    return parser.parse_args(pargs)

if __name__ == '__main__':
    runstrat()

定时器

原文:www.backtrader.com/blog/posts/2017-05-03-timers/timers/

发布 1.9.44.116backtrader 可用工具中添加了 timers 功能。此功能允许在指定时间点收到对 notify_timer 的回调(在 CerebroStrategy 中可用),并且有细粒度的最终用户控制。

注意

1.9.46.116 中进行了一些更正

选项

  • 基于绝对时间输入或与会话开始/结束时间相关的定时器
  • 时区规范为时间规范,无论是直接还是通过 pytz 兼容对象,还是通过数据源会话结束时间
  • 与指定时间相关的起始偏移量
  • 重复间隔
  • 工作日筛选器(带有延续选项)
  • 月日筛选器(带有延续选项)
  • 自定义回调筛选器

使用模式

CerebroStrategy 子类中,定时器回调将在以下方法中收到。

代码语言:javascript
复制
def notify_timer(self, timer, when, *args, **kwargs):
    '''Receives a timer notification where ``timer`` is the timer which was
    returned by ``add_timer``, and ``when`` is the calling time. ``args``
    and ``kwargs`` are any additional arguments passed to ``add_timer``

    The actual ``when`` time can be later, but the system may have not be
    able to call the timer before. This value is the timer value and not the
    system time.
    '''
添加定时器 - 通过 Strategy

使用该方法完成

代码语言:javascript
复制
def add_timer(self, when,
              offset=datetime.timedelta(), repeat=datetime.timedelta(),
              weekdays=[], weekcarry=False,
              monthdays=[], monthcarry=True,
              allow=None,
              tzdata=None, cheat=False,
              *args, **kwargs):
    '''

返回创建的 Timer 实例。

有关参数解释,请参见下文。

添加定时器 - 通过 Cerebro

使用相同的方法完成,只需添加参数 strats。如果设置为 True,则定时器不仅将通知给 cerebro,还将通知给系统中运行的所有策略。

代码语言:javascript
复制
def add_timer(self, when,
              offset=datetime.timedelta(), repeat=datetime.timedelta(),
              weekdays=[], weekcarry=False,
              monthdays=[], monthcarry=True,
              allow=None,
              tzdata=None, cheat=False, strats=False,
              *args, **kwargs):
    '''

返回创建的 Timer 实例。

定时器何时被调用

如果 cheat=False

这是默认设置。在这种情况下,定时器将被调用:

  • 在数据源加载了当前柱形图的新值之后
  • 在经纪人评估订单并重新计算组合价值之后
  • 在指标重新计算之前(因为这是由策略触发的)
  • 在任何策略的 next 方法被调用之前
如果 cheat=True

在这种情况下,将调用定时器:

  • 在数据源加载了当前柱形图的新值之后
  • 经纪人评估订单并重新计算组合价值之前
  • 因此在指标被重新计算之前和任何策略的 next 方法被调用之前

这允许例如以下场景与每日柱形图:

  • 在经纪人评估新柱形图之前,定时器被调用
  • 指标具有前一天收盘价的值,可以用于生成入场/出场信号(或在上次评估 next 时可能已设置了标志)
  • 因为新价格可用,所以可以使用开盘价计算赌注。这假设例如通过观察开盘竞价可以得到关于 open 的良好指示。

使用每日柱形图运行

示例 scheduled.py 默认使用 backtrader 发行版中提供的标准每日柱形图运行。策略的参数

代码语言:javascript
复制
class St(bt.Strategy):
    params = dict(
        when=bt.timer.SESSION_START,
        timer=True,
        cheat=False,
        offset=datetime.timedelta(),
        repeat=datetime.timedelta(),
        weekdays=[],
    )

数据具有以下会话时间:

  • 开始:09:00
  • 结束:17:30

仅使用时间运行

代码语言:javascript
复制
$ ./scheduled.py --strat when='datetime.time(15,30)'

strategy notify_timer with tid 0, when 2005-01-03 15:30:00 cheat False
1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
strategy notify_timer with tid 0, when 2005-01-04 15:30:00 cheat False
2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
strategy notify_timer with tid 0, when 2005-01-05 15:30:00 cheat False
3, 2005-01-05 17:30:00, Week 1, Day 3, O 2969.0, H 2969.0, L 2942.69, C 2947.19
strategy notify_timer with tid 0, when 2005-01-06 15:30:00 cheat False
...

如指定的,定时器在 15:30 滴答。没有意外。让我们添加一个偏移量为 30 分钟。

代码语言:javascript
复制
$ ./scheduled.py --strat when='datetime.time(15,30)',offset='datetime.timedelta(minutes=30)'

strategy notify_timer with tid 0, when 2005-01-03 16:00:00 cheat False
1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
strategy notify_timer with tid 0, when 2005-01-04 16:00:00 cheat False
2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
strategy notify_timer with tid 0, when 2005-01-05 16:00:00 cheat False
...

时间从15:30更改为16:00。没有什么惊喜。让我们做同样的事情,但是引用会话开始。

代码语言:javascript
复制
$ ./scheduled.py --strat when='bt.timer.SESSION_START',offset='datetime.timedelta(minutes=30)'

strategy notify_timer with tid 0, when 2005-01-03 09:30:00 cheat False
1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
strategy notify_timer with tid 0, when 2005-01-04 09:30:00 cheat False
2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
...

Et voilá!回调调用的时间是09:30。并且会话开始,见上文,是09:00。这使得可以简单地说希望在会话开始后30 分钟执行某个动作。

让我们添加一个重复:

代码语言:javascript
复制
$ ./scheduled.py --strat when='bt.timer.SESSION_START',offset='datetime.timedelta(minutes=30)',repeat='datetime.timedelta(minutes=30)'

strategy notify_timer with tid 0, when 2005-01-03 09:30:00 cheat False
1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
strategy notify_timer with tid 0, when 2005-01-04 09:30:00 cheat False
2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
strategy notify_timer with tid 0, when 2005-01-05 09:30:00 cheat False
...

没有重复。原因是价格的分辨率是每日的。计时器像前一个示例一样在09:30首次调用。但是当系统获取下一批价格时,它们发生在下一天。显然,计时器只能被调用一次。需要更低的分辨率。

但在转向更低的分辨率之前,让我们通过在会话结束之前调用计时器来作弊。

代码语言:javascript
复制
$ ./scheduled.py --strat when='bt.timer.SESSION_START',cheat=True

strategy notify_timer with tid 1, when 2005-01-03 09:00:00 cheat True
-- 2005-01-03 Create buy order
strategy notify_timer with tid 0, when 2005-01-03 09:00:00 cheat False
1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
strategy notify_timer with tid 1, when 2005-01-04 09:00:00 cheat True
strategy notify_timer with tid 0, when 2005-01-04 09:00:00 cheat False
-- 2005-01-04 Buy Exec @ 2969.78
2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
strategy notify_timer with tid 1, when 2005-01-05 09:00:00 cheat True
strategy notify_timer with tid 0, when 2005-01-05 09:00:00 cheat False
...

策略添加了一个带有cheat=True的第 2 个计时器。这是添加的第 2 个,因此将接收到第 2 个tid计时器 id),即1(请参见上述示例,分配的tid0

并且10之前被调用,因为该计时器正在作弊,并且在系统中的许多事件发生之前被调用(请参见上文的解释)

由于价格的每日分辨率,这并没有太大的区别,除了:

  • 该策略还在开盘前发出订单……并且在第二天与开盘价匹配。 这,即使在开盘前作弊,仍然是正常的行为,因为经纪人也没有激活cheating-on-open

经纪人的coo=True相同。

代码语言:javascript
复制
$ ./scheduled.py --strat when='bt.timer.SESSION_START',cheat=True --broker coo=True

strategy notify_timer with tid 1, when 2005-01-03 09:00:00 cheat True
-- 2005-01-03 Create buy order
strategy notify_timer with tid 0, when 2005-01-03 09:00:00 cheat False
-- 2005-01-03 Buy Exec @ 2952.29
1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
strategy notify_timer with tid 1, when 2005-01-04 09:00:00 cheat True
strategy notify_timer with tid 0, when 2005-01-04 09:00:00 cheat False
2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
strategy notify_timer with tid 1, when 2005-01-05 09:00:00 cheat True
strategy notify_timer with tid 0, when 2005-01-05 09:00:00 cheat False
...

有些事情发生了变化。

  • 订单在作弊计时器中在2005-01-03发出
  • 订单在2005-01-03以开盘价执行 实际上就像在市场真正开盘前几秒钟就已经根据开盘竞价行动了一样。

使用 5 分钟的条形图运行

示例scheduled-min.py默认使用backtrader发行的标准 5 分钟条形图运行。策略的参数被扩展以包括monthdayscarry选项

代码语言:javascript
复制
class St(bt.Strategy):
    params = dict(
        when=bt.timer.SESSION_START,
        timer=True,
        cheat=False,
        offset=datetime.timedelta(),
        repeat=datetime.timedelta(),
        weekdays=[],
        weekcarry=False,
        monthdays=[],
        monthcarry=True,
    )

数据具有相同的交易时间:

  • 开始时间:09:00
  • 结束时间:17:30

让我们进行一些实验。首先是一个单一的计时器。

代码语言:javascript
复制
$ ./scheduled-min.py --strat when='datetime.time(15, 30)'

1, 2006-01-02 09:05:00, Week 1, Day 1, O 3578.73, H 3587.88, L 3578.73, C 3582.99
2, 2006-01-02 09:10:00, Week 1, Day 1, O 3583.01, H 3588.4, L 3583.01, C 3588.03
...
77, 2006-01-02 15:25:00, Week 1, Day 1, O 3599.07, H 3599.68, L 3598.47, C 3599.68
strategy notify_timer with tid 0, when 2006-01-02 15:30:00 cheat False
78, 2006-01-02 15:30:00, Week 1, Day 1, O 3599.64, H 3599.73, L 3599.0, C 3599.67
...
179, 2006-01-03 15:25:00, Week 1, Day 2, O 3634.72, H 3635.0, L 3634.06, C 3634.87
strategy notify_timer with tid 0, when 2006-01-03 15:30:00 cheat False
180, 2006-01-03 15:30:00, Week 1, Day 2, O 3634.81, H 3634.89, L 3634.04, C 3634.23
...

计时器按要求在15:30开始工作。日志显示了它在前两天如何工作。

15 分钟repeat添加到混合中

代码语言:javascript
复制
$ ./scheduled-min.py --strat when='datetime.time(15, 30)',repeat='datetime.timedelta(minutes=15)'

...
74, 2006-01-02 15:10:00, Week 1, Day 1, O 3596.12, H 3596.63, L 3595.92, C 3596.63
75, 2006-01-02 15:15:00, Week 1, Day 1, O 3596.36, H 3596.65, L 3596.19, C 3596.65
76, 2006-01-02 15:20:00, Week 1, Day 1, O 3596.53, H 3599.13, L 3596.12, C 3598.9
77, 2006-01-02 15:25:00, Week 1, Day 1, O 3599.07, H 3599.68, L 3598.47, C 3599.68
strategy notify_timer with tid 0, when 2006-01-02 15:30:00 cheat False
78, 2006-01-02 15:30:00, Week 1, Day 1, O 3599.64, H 3599.73, L 3599.0, C 3599.67
79, 2006-01-02 15:35:00, Week 1, Day 1, O 3599.61, H 3600.29, L 3599.52, C 3599.92
80, 2006-01-02 15:40:00, Week 1, Day 1, O 3599.96, H 3602.06, L 3599.76, C 3602.05
strategy notify_timer with tid 0, when 2006-01-02 15:45:00 cheat False
81, 2006-01-02 15:45:00, Week 1, Day 1, O 3601.97, H 3602.07, L 3601.45, C 3601.83
82, 2006-01-02 15:50:00, Week 1, Day 1, O 3601.74, H 3602.8, L 3601.63, C 3602.8
83, 2006-01-02 15:55:00, Week 1, Day 1, O 3602.53, H 3602.74, L 3602.33, C 3602.61
strategy notify_timer with tid 0, when 2006-01-02 16:00:00 cheat False
84, 2006-01-02 16:00:00, Week 1, Day 1, O 3602.58, H 3602.75, L 3601.81, C 3602.14
85, 2006-01-02 16:05:00, Week 1, Day 1, O 3602.16, H 3602.16, L 3600.86, C 3600.96
86, 2006-01-02 16:10:00, Week 1, Day 1, O 3601.2, H 3601.49, L 3600.94, C 3601.27
...
strategy notify_timer with tid 0, when 2006-01-02 17:15:00 cheat False
99, 2006-01-02 17:15:00, Week 1, Day 1, O 3603.96, H 3603.96, L 3602.89, C 3603.79
100, 2006-01-02 17:20:00, Week 1, Day 1, O 3603.94, H 3605.95, L 3603.87, C 3603.91
101, 2006-01-02 17:25:00, Week 1, Day 1, O 3604.0, H 3604.76, L 3603.85, C 3604.64
strategy notify_timer with tid 0, when 2006-01-02 17:30:00 cheat False
102, 2006-01-02 17:30:00, Week 1, Day 1, O 3604.06, H 3604.41, L 3603.95, C 3604.33
103, 2006-01-03 09:05:00, Week 1, Day 2, O 3604.08, H 3609.6, L 3604.08, C 3609.6
104, 2006-01-03 09:10:00, Week 1, Day 2, O 3610.34, H 3617.31, L 3610.34, C 3617.31
105, 2006-01-03 09:15:00, Week 1, Day 2, O 3617.61, H 3617.87, L 3616.03, C 3617.51
106, 2006-01-03 09:20:00, Week 1, Day 2, O 3617.24, H 3618.86, L 3616.09, C 3618.42
...
179, 2006-01-03 15:25:00, Week 1, Day 2, O 3634.72, H 3635.0, L 3634.06, C 3634.87
strategy notify_timer with tid 0, when 2006-01-03 15:30:00 cheat False
180, 2006-01-03 15:30:00, Week 1, Day 2, O 3634.81, H 3634.89, L 3634.04, C 3634.23
...

预期的是第 1 次调用在15:30触发,然后每隔 15 分钟重复一次,直到17:30会话结束。新会话开始时,计时器再次重置为15:30

现在在会话开始之前作弊

代码语言:javascript
复制
$ ./scheduled-min.py --strat when='bt.timer.SESSION_START',cheat=True

strategy notify_timer with tid 1, when 2006-01-02 09:00:00 cheat True
-- 2006-01-02 09:05:00 Create buy order
strategy notify_timer with tid 0, when 2006-01-02 09:00:00 cheat False
1, 2006-01-02 09:05:00, Week 1, Day 1, O 3578.73, H 3587.88, L 3578.73, C 3582.99
-- 2006-01-02 09:10:00 Buy Exec @ 3583.01
2, 2006-01-02 09:10:00, Week 1, Day 1, O 3583.01, H 3588.4, L 3583.01, C 3588.03
...

订单创建时间为09:05:00,执行时间为09:10:00,因为经纪人不处于欺骗开盘模式。让我们设置它……

代码语言:javascript
复制
$ ./scheduled-min.py --strat when='bt.timer.SESSION_START',cheat=True --broker coo=True

strategy notify_timer with tid 1, when 2006-01-02 09:00:00 cheat True
-- 2006-01-02 09:05:00 Create buy order
strategy notify_timer with tid 0, when 2006-01-02 09:00:00 cheat False
-- 2006-01-02 09:05:00 Buy Exec @ 3578.73
1, 2006-01-02 09:05:00, Week 1, Day 1, O 3578.73, H 3587.88, L 3578.73, C 3582.99
2, 2006-01-02 09:10:00, Week 1, Day 1, O 3583.01, H 3588.4, L 3583.01, C 3588.03
...

发行时间和执行时间为09:05:00,执行价格为09:05:00的开盘价。

其他情景

计时器允许通过传递一个日期列表(按照 iso 规范,其中 Mon=1 且 Sun=7 的整数)来指定它们必须执行的日期,如

  • weekdays=[5],这将要求计时器仅在星期五有效 如果星期五是非交易日,而计时器应在下一个交易日启动,则可以添加 weekcarry=True

类似于此,可以决定在每个月的第 15 天采取行动:

  • monthdays=[15] 如果第 15 天恰好是非交易日,而计时器应在下一个交易日启动,则可以添加 monthcarry=True

对于诸如:三月、六月、九月和十二月的第 3 个星期五(期货/期权到期日)之类的事情并没有实现,但是可以通过传递来实现规则:

allow=callable,其中可调用接受 datetime.date 实例。请注意,这不是 datetime.datetime 实例,因为 allow 可调用仅用于确定给定日期是否适合用于计时器。

要实现类似上面规则的内容:

代码语言:javascript
复制
class FutOpExp(object):
    def __init__(self):
        self.fridays = 0
        self.curmonth = -1

    def __call__(self, d):
        _, _, isowkday = d.isocalendar()

        if d.month != self.curmonth:
            self.curmonth = d.month
            self.fridays = 0

        # Mon=1 ... Sun=7
        if isowkday == 5 and self.curmonth in [3, 6, 9, 12]:
            self.fridays += 1

            if self.friday == 3:  # 3rd Friday
                return True  # timer allowed

        return False  # timer disallowed` 

并且一个将 allow=FutOpeExp() 传递给计时器的创建

这将允许计时器在这些月份的第 3 个星期五启动,并且可能在期货到期前平仓。

add_timer 的参数

代码语言:javascript
复制
- `when`: can be

  - `datetime.time` instance (see below `tzdata`)

  - `bt.timer.SESSION_START` to reference a session start

  - `bt.timer.SESSION_END` to reference a session end

offset 必须是一个 datetime.timedelta 实例

用于偏移值 when 的值。它与 SESSION_STARTSESSION_END 结合使用时具有有意义的用途,例如指示计时器在会话开始后15 分钟被调用。

  • repeat 必须是一个 datetime.timedelta 实例

表示在第 1 次调用后,进一步的调用是否将在同一个会话中按计划的repeat间隔内安排

一旦计时器超过会话结束,它将被重置为 when 的原始值

  • weekdays:一个排序的可迭代对象,其中包含指示实际可以调用计时器的日期(ISO 代码,星期一为 1,星期日为 7)的整数

如果未指定,计时器将在所有日期上活动

  • weekcarry(默认:False)。如果为 True 并且没有看到工作日(例如:交易假日),则计时器将在下一天执行(即使是在新的一周)
  • monthdays:一个排序的可迭代对象,其中包含指示月份中哪些日期要执行计时器的整数。例如,总是在每个月的第 15

如果未指定,计时器将在所有日期上活动

  • monthcarry(默认:True)。如果没有看到该天(周末,交易假日),则计时器将在下一个可用日期执行。
  • allow(默认:None)。一个回调函数,接收一个 datetime.date 实例,并返回 True(如果日期允许计时器)或返回 False
  • tzdata 可以是 None(默认值),一个 pytz 实例或一个 数据源 实例。

Nonewhen 被按字面值解释(即使它不是,也要处理它为 UTC)

pytz 实例:when 将被解释为由时区实例指定的本地时间。

data feed实例:when将被解释为在数据源实例的tz参数指定的本地时间中指定。

注意:如果whenSESSION_START或者

代码语言:javascript
复制
 `SESSION_END` and `tzdata` is `None`, the 1st *data feed*
  in the system (aka `self.data0`) will be used as the reference
  to find out the session times.` 
  • strats(默认为False)还会调用策略的notify_timer
  • cheat(默认为False)如果为True,则计时器将在经纪人有机会评估订单之前被调用。这就打开了在会话开始之前例如根据开盘价发布订单的机会
  • \*args:任何额外的参数将传递给notify_timer
  • \*\*kwargs:任何额外的关键字参数将传递给notify_timer

示例用法scheduled.py

代码语言:javascript
复制
$ ./scheduled.py --help
usage: scheduled.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: )

示例用法scheduled-min.py

代码语言:javascript
复制
$ ./scheduled-min.py --help
usage: scheduled-min.py [-h] [--data0 DATA0] [--fromdate FROMDATE]
                        [--todate TODATE] [--cerebro kwargs] [--broker kwargs]
                        [--sizer kwargs] [--strat kwargs] [--plot [kwargs]]

Timer Test Intraday

optional arguments:
  -h, --help           show this help message and exit
  --data0 DATA0        Data to read in (default: ../../datas/2006-min-005.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: )

示例源代码scheduled.py

代码语言:javascript
复制
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import argparse
import datetime

import backtrader as bt

class St(bt.Strategy):
    params = dict(
        when=bt.timer.SESSION_START,
        timer=True,
        cheat=False,
        offset=datetime.timedelta(),
        repeat=datetime.timedelta(),
        weekdays=[],
    )

    def __init__(self):
        bt.ind.SMA()
        if self.p.timer:
            self.add_timer(
                when=self.p.when,
                offset=self.p.offset,
                repeat=self.p.repeat,
                weekdays=self.p.weekdays,
            )
        if self.p.cheat:
            self.add_timer(
                when=self.p.when,
                offset=self.p.offset,
                repeat=self.p.repeat,
                cheat=True,
            )

        self.order = None

    def prenext(self):
        self.next()

    def next(self):
        _, isowk, isowkday = self.datetime.date().isocalendar()
        txt = '{}, {}, Week {}, Day {}, O {}, H {}, L {}, C {}'.format(
            len(self), self.datetime.datetime(),
            isowk, isowkday,
            self.data.open[0], self.data.high[0],
            self.data.low[0], self.data.close[0])

        print(txt)

    def notify_timer(self, timer, when, *args, **kwargs):
        print('strategy notify_timer with tid {}, when {} cheat {}'.
              format(timer.p.tid, when, timer.p.cheat))

        if self.order is None and timer.p.cheat:
            print('-- {} Create buy order'.format(self.data.datetime.date()))
            self.order = self.buy()

    def notify_order(self, order):
        if order.status == order.Completed:
            print('-- {} Buy Exec @ {}'.format(
                self.data.datetime.date(), order.executed.price))

def runstrat(args=None):
    args = parse_args(args)

    cerebro = bt.Cerebro()

    # Data feed kwargs
    kwargs = dict(
        timeframe=bt.TimeFrame.Days,
        compression=1,
        sessionstart=datetime.time(9, 0),
        sessionend=datetime.time(17, 30),
    )

    # 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()

示例源代码scheduled-min.py

代码语言:javascript
复制
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import argparse
import datetime

import backtrader as bt

class St(bt.Strategy):
    params = dict(
        when=bt.timer.SESSION_START,
        timer=True,
        cheat=False,
        offset=datetime.timedelta(),
        repeat=datetime.timedelta(),
        weekdays=[],
        weekcarry=False,
        monthdays=[],
        monthcarry=True,
    )

    def __init__(self):
        bt.ind.SMA()
        if self.p.timer:
            self.add_timer(
                when=self.p.when,
                offset=self.p.offset,
                repeat=self.p.repeat,
                weekdays=self.p.weekdays,
                weekcarry=self.p.weekcarry,
                monthdays=self.p.monthdays,
                monthcarry=self.p.monthcarry,
                # tzdata=self.data0,
            )
        if self.p.cheat:
            self.add_timer(
                when=self.p.when,
                offset=self.p.offset,
                repeat=self.p.repeat,
                weekdays=self.p.weekdays,
                weekcarry=self.p.weekcarry,
                monthdays=self.p.monthdays,
                monthcarry=self.p.monthcarry,
                tzdata=self.data0,
                cheat=True,
            )

        self.order = None

    def prenext(self):
        self.next()

    def next(self):
        _, isowk, isowkday = self.datetime.date().isocalendar()
        txt = '{}, {}, Week {}, Day {}, O {}, H {}, L {}, C {}'.format(
            len(self), self.datetime.datetime(),
            isowk, isowkday,
            self.data.open[0], self.data.high[0],
            self.data.low[0], self.data.close[0])

        print(txt)

    def notify_timer(self, timer, when, *args, **kwargs):
        print('strategy notify_timer with tid {}, when {} cheat {}'.
              format(timer.p.tid, when, timer.p.cheat))

        if self.order is None and timer.params.cheat:
            print('-- {} Create buy order'.format(
                self.data.datetime.datetime()))
            self.order = self.buy()

    def notify_order(self, order):
        if order.status == order.Completed:
            print('-- {} Buy Exec @ {}'.format(
                self.data.datetime.datetime(), order.executed.price))

def runstrat(args=None):
    args = parse_args(args)
    cerebro = bt.Cerebro()

    # Data feed kwargs
    kwargs = dict(
        timeframe=bt.TimeFrame.Minutes,
        compression=5,
        sessionstart=datetime.time(9, 0),
        sessionend=datetime.time(17, 30),
    )

    # 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=(
            'Timer Test Intraday'
        )
    )

    parser.add_argument('--data0', default='../../datas/2006-min-005.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()
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-05-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 砖形图
    • 一些示例
      • 示例用法
        • 示例代码
        • Backtrader:该基金
          • 历史
          • 基金跟踪
            • 这是什么基金追踪?
            • 1
            • 2
            • 3
              • 交互式经纪人优化
                • 平均趋势蜡烛图
                  • 允许辅助演员重新调整 y 轴的比例
                    • 半对数图(也称为对数图)
                      • 允许plotmaster指向自身
                        • 并且dnames已经被记录了
                          • 结论
                          • 策略选择再访
                          • 重新设计工厂
                          • 装饰待优化的策略
                          • 利用 COUNT
                            • 一个样本运行
                              • 结论
                                • 样本用法
                                • 代码
                            • 定时器
                              • 选项
                                • 使用模式
                                  • 添加定时器 - 通过 Strategy
                                  • 添加定时器 - 通过 Cerebro
                                • 定时器何时被调用
                                  • 如果 cheat=False
                                  • 如果 cheat=True
                                • 使用每日柱形图运行
                                  • 使用 5 分钟的条形图运行
                                    • 其他情景
                                      • add_timer 的参数
                                        • 示例用法scheduled.py
                                          • 示例用法scheduled-min.py
                                            • 示例源代码scheduled.py
                                              • 示例源代码scheduled-min.py
                                              领券
                                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档