BackTrader 中文文档(十四)(1)

简介: BackTrader 中文文档(十四)


原文:www.backtrader.com/

backtrader 中交易加密货币的分数大小

原文:www.backtrader.com/blog/posts/2019-08-29-fractional-sizes/fractional-sizes/

首先,让我们用两行总结一下backtrader的工作方式:

  • 就像一个基本构建块(Cerebro)的构建套件,可以将许多不同的部件插入其中
  • 基本分发包含许多部件,如指标分析器观察器调整器过滤器数据源经纪人佣金/资产信息方案,…
  • 新的构建模块可以很容易地从头开始构建,或者基于现有的构建模块
  • 基本构建块(Cerebro)已经自动进行了一些*“插入”*,使得在不必担心所有细节的情况下更容易使用该框架。

因此,该框架预先配置为提供具有默认行为的行为,例如:

  • 使用单个/主数据源
  • 1-day 时间框架/压缩组合
  • 10,000 单位的货币
  • 股票交易

这可能适合或不适合每个人,但重要的是:它可以根据每个交易者/程序员的个人需求进行定制

交易股票:整数

如上所述,默认配置是用于股票交易,当交易股票时,购买/出售完整股票(即:1、2…50…1000,而不是像1.51001.7589股票那样的金额。

这意味着当用户在默认配置中执行以下操作时:

def next(self):
        # Apply 50% of the portfolio to buy the main asset
        self.order_target_percent(target=0.5)

以下发生了:

  • 系统计算需要多少份资产股票,以便给定资产组合中的价值尽可能接近50%
  • 但由于默认配置是使用股票进行操作,因此得到的股票数量将是一个整数,即:一个整数

注意

请注意,默认配置是使用单个/主数据源进行操作,这就是为什么在调用order_percent_target时没有指定实际数据。当操作多个数据源时,必须指定要获取/出售的数据(除非是主要数据)

交易加密货币:分数

很明显,在交易加密货币时,即使有 20 位小数,也可以购买*“半个比特币”*。

好处在于,实际上可以更改有关资产的信息。这是通过可插入的CommissionInfo系列实现的。

一些文档:文档 - 佣金方案 - https://www.backtrader.com/docu/commission-schemes/commission-schemes/

注意

必须承认这个名字不太幸运,因为这些方案不仅包含有关佣金的信息,还包含其他信息。

在分数场景中,该方案的方法是:getsize(price, cash),其具有以下文档字符串

Returns the needed size to meet a cash operation at a given price

方案与经纪人密切相关,通过经纪人 API,可以在系统中添加方案。

经纪人文档位于:文档 - 经纪人 - https://www.backtrader.com/docu/broker/

相关方法为:addcommissioninfo(comminfo, name=None)。除了添加一个适用于所有资产的方案(当nameNone时),还可以设置仅适用于具有特定名称资产的方案。

实施分数方案

这可以通过扩展现有的基础方案CommissionInfo来轻松实现。

class CommInfoFractional(bt.CommissionInfo):
    def getsize(self, price, cash):
  '''Returns fractional size for cash operation @price'''
        return self.p.leverage * (cash / price)

同上并完成。通过子类化CommissionInfo并编写一行方法,实现了目标。因为原始方案定义支持leverage,这一点已经考虑在内,以防加密货币可以使用杠杆购买(其中默认值为1.0,即:无杠杆)

代码后面,该方案将被添加(通过命令行参数控制),如下所示

if args.fractional:  # use the fractional scheme if requested
        cerebro.broker.addcommissioninfo(CommInfoFractional())

也就是说:子类方案的一个实例(注意用()进行实例化)被添加了。如上所述,未设置name参数,这意味着它将应用于系统中的所有资产。

测试野兽

下面提供了一个完整的脚本,实现了一个简单的移动平均线交叉策略,用于长/短仓位,可以直接在 shell 中使用。测试的默认数据源来自backtrader仓库中的一个数据源。

整数运行:无分数 - 无趣

$ ./fractional-sizes.py --plot
2005-02-14,3079.93,3083.38,3065.27,3075.76,0.00
2005-02-15,3075.20,3091.64,3071.08,3086.95,0.00
...
2005-03-21,3052.39,3059.18,3037.80,3038.14,0.00
2005-03-21,Enter Short
2005-03-22,Sell Order Completed - Size: -16 @Price: 3040.55 Value: -48648.80 Comm: 0.00
2005-03-22,Trade Opened  - Size -16 @Price 3040.55
2005-03-22,3040.55,3053.18,3021.66,3050.44,0.00
...

一个大小为16单位的短期交易已经开启。由于显而易见的原因,整个日志未显示,其中包含许多其他操作,都是以整数大小进行交易。

分数运行

经过分数的艰苦子类化和一行代码的工作后…

$ ./fractional-sizes.py --fractional --plot
2005-02-14,3079.93,3083.38,3065.27,3075.76,0.00
2005-02-15,3075.20,3091.64,3071.08,3086.95,0.00
...
2005-03-21,3052.39,3059.18,3037.80,3038.14,0.00
2005-03-21,Enter Short
2005-03-22,Sell Order Completed - Size: -16.457437774427774 @Price: 3040.55 Value: -50039.66 Comm: 0.00
2005-03-22,Trade Opened  - Size -16.457437774427774 @Price 3040.55
2005-03-22,3040.55,3053.18,3021.66,3050.44,0.00
...

V 为胜利。短期交易已经通过相同的交叉方式开启,但这次是以-16.457437774427774的分数大小。

请注意,图表中的最终投资组合价值不同,这是因为实际交易大小不同。

结论

是的,backtrader 可以。采用可插拔/可扩展的构建工具方法,很容易将行为定制为交易程序员的特定需求。

该脚本

#!/usr/bin/env python
# -*- coding: utf-8; py-indent-offset:4 -*-
###############################################################################
# Copyright (C) 2019 Daniel Rodriguez - MIT License
#  - https://opensource.org/licenses/MIT
#  - https://en.wikipedia.org/wiki/MIT_License
###############################################################################
import argparse
import logging
import sys
import backtrader as bt
# This defines not only the commission info, but some other aspects
# of a given data asset like the "getsize" information from below
# params = dict(stocklike=True)  # No margin, no multiplier
class CommInfoFractional(bt.CommissionInfo):
    def getsize(self, price, cash):
  '''Returns fractional size for cash operation @price'''
        return self.p.leverage * (cash / price)
class St(bt.Strategy):
    params = dict(
        p1=10, p2=30,  # periods for crossover
        ma=bt.ind.SMA,  # moving average to use
        target=0.5,  # percentage of value to use
    )
    def __init__(self):
        ma1, ma2 = [self.p.ma(period=p) for p in (self.p.p1, self.p.p2)]
        self.cross = bt.ind.CrossOver(ma1, ma2)
    def next(self):
        self.logdata()
        if self.cross > 0:
            self.loginfo('Enter Long')
            self.order_target_percent(target=self.p.target)
        elif self.cross < 0:
            self.loginfo('Enter Short')
            self.order_target_percent(target=-self.p.target)
    def notify_trade(self, trade):
        if trade.justopened:
            self.loginfo('Trade Opened  - Size {} @Price {}',
                         trade.size, trade.price)
        elif trade.isclosed:
            self.loginfo('Trade Closed  - Profit {}', trade.pnlcomm)
        else:  # trade updated
            self.loginfo('Trade Updated - Size {} @Price {}',
                         trade.size, trade.price)
    def notify_order(self, order):
        if order.alive():
            return
        otypetxt = 'Buy ' if order.isbuy() else 'Sell'
        if order.status == order.Completed:
            self.loginfo(
                ('{} Order Completed - '
                 'Size: {} @Price: {} '
                 'Value: {:.2f} Comm: {:.2f}'),
                otypetxt, order.executed.size, order.executed.price,
                order.executed.value, order.executed.comm
            )
        else:
            self.loginfo('{} Order rejected', otypetxt)
    def loginfo(self, txt, *args):
        out = [self.datetime.date().isoformat(), txt.format(*args)]
        logging.info(','.join(out))
    def logerror(self, txt, *args):
        out = [self.datetime.date().isoformat(), txt.format(*args)]
        logging.error(','.join(out))
    def logdebug(self, txt, *args):
        out = [self.datetime.date().isoformat(), txt.format(*args)]
        logging.debug(','.join(out))
    def logdata(self):
        txt = []
        txt += ['{:.2f}'.format(self.data.open[0])]
        txt += ['{:.2f}'.format(self.data.high[0])]
        txt += ['{:.2f}'.format(self.data.low[0])]
        txt += ['{:.2f}'.format(self.data.close[0])]
        txt += ['{:.2f}'.format(self.data.volume[0])]
        self.loginfo(','.join(txt))
def run(args=None):
    args = parse_args(args)
    cerebro = bt.Cerebro()
    data = bt.feeds.BacktraderCSVData(dataname=args.data)
    cerebro.adddata(data)  # create and add data feed
    cerebro.addstrategy(St)  # add the strategy
    cerebro.broker.set_cash(args.cash)  # set broker cash
    if args.fractional:  # use the fractional scheme if requested
        cerebro.broker.addcommissioninfo(CommInfoFractional())
    cerebro.run()  # execute
    if args.plot:  # Plot if requested to
        cerebro.plot(**eval('dict(' + args.plot + ')'))
def logconfig(pargs):
    if pargs.quiet:
        verbose_level = logging.ERROR
    else:
        verbose_level = logging.INFO - pargs.verbose * 10  # -> DEBUG
    logger = logging.getLogger()
    for h in logger.handlers:  # Remove all loggers from root
        logger.removeHandler(h)
    stream = sys.stdout if not pargs.stderr else sys.stderr  # choose stream
    logging.basicConfig(
        stream=stream,
        format="%(message)s",  # format="%(levelname)s: %(message)s",
        level=verbose_level,
    )
def parse_args(pargs=None):
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='Fractional Sizes with CommInfo',
    )
    pgroup = parser.add_argument_group('Data Options')
    parser.add_argument('--data', default='../../datas/2005-2006-day-001.txt',
                        help='Data to read in')
    pgroup = parser.add_argument_group(title='Broker Arguments')
    pgroup.add_argument('--cash', default=100000.0, type=float,
                        help='Starting cash to use')
    pgroup.add_argument('--fractional', action='store_true',
                        help='Use fractional commission info')
    pgroup = parser.add_argument_group(title='Plotting Arguments')
    pgroup.add_argument('--plot', default='', nargs='?', const='{}',
                        metavar='kwargs', help='kwargs: "k1=v1,k2=v2,..."')
    pgroup = parser.add_argument_group('Verbosity Options')
    pgroup.add_argument('--stderr', action='store_true',
                        help='Log to stderr, else to stdout')
    pgroup = pgroup.add_mutually_exclusive_group()
    pgroup.add_argument('--quiet', '-q', action='store_true',
                        help='Silent (errors will be reported)')
    pgroup.add_argument('--verbose', '-v', action='store_true',
                        help='Increase verbosity level')
    # Parse and process some args
    pargs = parser.parse_args(pargs)
    logconfig(pargs)  # config logging
    return pargs
if __name__ == '__main__':
    run()

击败随机进入

原文:www.backtrader.com/blog/2019-08-22-practical-backtesting-replication/practical-replication/

最近有一些关于在reddit/r/algotrading上成功复制已发布的算法交易策略的帖子。首先

  • 这些策略不起作用
  • 如果作者声称某种策略停止工作是由于阿尔法衰减,那么测试将针对过去的数据运行,而且仍然无法工作
  • 底线是:这都是过度拟合,p-值调整或微小的阿尔法,不需要衰减,因为佣金已经破坏了阿尔法。

Artem Kaznatcheev撰写的复制问题的复制品在以下位置:

接下来是:

前两者是理论的(即使第一个提到已经实施了 130 种策略),而“过度拟合”提供了实际代码。

在这么多事情发生的情况下,那么试图复制一些已经发布但不是作为论文的东西呢,就像在“过度拟合”案例中一样,采取实践方法。某些发表在著名书籍中的东西。

目标:“尝试击败随机进入”。这是本书的第 3 部分第八章中的一节:

书中提出了一种有结构的方法参与算法交易,特别强调:仓位大小和仓位管理(即:何时实际退出交易)。这比例如入场设置更重要,后者显然大多数人认为是主要驱动因素。

在第八章中,范·K·塞尔普与汤姆·巴索交谈并说:“从你的讲话听起来,你似乎可以通过随机进入并智能地确定仓位来稳定赚钱。” 对此的回答是他可能可以

规则

  • 基于抛硬币的进入
  • 始终处于市场中 - 多头或空头
  • 一旦给出退出信号,立即重新进入
  • 市场的波动性由 10 天的“平均真实范围”的“指数移动平均”确定
  • 从收盘价的距离追踪止损是波动性的 3 倍
  • 止损只能朝着交易的方向移动
  • 固定仓位(1 份合约)或 1%风险模型(书中第十二章)

结果

  • 测试对 10 个市场
  • 固定投注:80%的时间赚钱
  • 1%风险模型:100%的时间赚钱
  • 可靠性水平:38%(获胜交易的百分比)

缺失的部分

  • 测试市场
  • 测试期间
  • 如果始终处于市场中意味着“今天”关闭交易并在“明天”重新进入市场,或者意味着同时发出关闭/重新开放订单。
    这实际上是最容易克服的。

对于最后两个项目,书中说 1991 年进行了谈话并使用了期货。为了对书本公平,将使用 1991 年之前的期货数据。鉴于提到了10 日指数移动平均线,还假设了 1 天的价格条。

最明显的问题似乎是正确地获得算法,但在这种情况下,书中对简单算法和结果的描述做得很好。为了完成它,让我们总结一下 “百分比风险模型”(书中称为“模型 3”的)第十二章

  • 最大损失:限制在账户价值的x%(即:百分比风险)
  • 合约风险:根据给定的算法,它将是初始止损距离(3 倍波动性)乘以未来的倍数
  • 合同金额:最大损失 / 合约风险

复制细节

数据

将使用 1985 年至 1990 年(即 6 年)的 CL(原油)期货数据。合同规格为:

  • 跳变大小:0.01(即:每个点 100 跳变)
  • 每个跳变成本:$10

有了这个想法,我们将使用1000乘以每个1 点的乘数(100 个跳点/点 x 10 美元/跳 = 1000 美元)

佣金

每次交易的合同将使用2.00货币单位(类似 IB)

一些实现细节

抛硬币被建模为一个指标,以便于可视化翻转的位置(例如,如果几个条目朝着相同的方向,这在随机情况下是可以预料的)

为了也能很好地可视化止损及其移动方式,止损价格计算和逻辑也嵌入到了指标中。注意,止损计算逻辑有两个不同的阶段

  • 当交易开始时,止损价格必须与前一个止损价格无关地设置在给定距离之内
  • 当交易进行时,如果可能,止损价格将根据趋势进行调整。

绘图

代码产生两种类型的图表

  1. 包含单次测试运行详细信息的图表(--plot选项)。在运行单次迭代(--iterations 1)时使用它最有意义
  2. 显示运行的利润和损失的散点图。

#1 样本

10 次运行的#2 样本

脚本的样本调用

固定大小投注和绘图的单次运行

./vanktharp-coinflip.py --years 1985-1990 --fixedsize --sizer stake=1 --iterations 1 --plot
**** Iteration:    1
-- PNL: 10482.00
--   Trades 49 - Won 22 - %_Won: 0.45
**** Summary of Runs
-- Total       :        1
-- Won         :        1
-- % Won       : 1.00
**** Summary of Trades
-- Total       :       49
-- Total Won   :       22
-- % Total Won : 0.45

使用 1%风险模型、10 次迭代和散点图的 100 次运行

(为了实际目的,输出已缩短)

$ ./vanktharp-coinflip.py --years 1985-1990 --percrisk --sizer percrisk=0.01 --iterations 100 --scatter
**** Iteration:    1
-- PNL: -18218.00
--   Trades 60 - Won 24 - %_Won: 0.40
**** Iteration:    2
...
...
**** Iteration:  100
-- PNL: 111366.00
--   Trades 50 - Won 26 - %_Won: 0.52
**** Summary of Runs
-- Total       :      100
-- Won         :       50
-- % Won       : 0.50
**** Summary of Trades
-- Total       :     5504
-- Total Won   :     2284
-- % Total Won : 0.41

测试运行混合

进行了 100 次迭代的 10 次测试运行,混合了以下变量:

  • 固定大小的投注额为 1,或者使用 1%的百分比风险模型。
  • 在同一根或连续的几根柱子上执行入场/出场操作

结果摘要

  • 平均而言,有 49%的交易是盈利的。固定大小的投注在测试中保持在 50%左右,而百分比风险模型的变化较大,一次测试的盈利交易率最低为 39%,另一次测试的盈利交易率最高为 65%(共进行了 10 次测试)。
  • 平均而言,有 39%的交易是盈利的(小偏差)

回想书中所说的:

  • 当使用固定大小的投注额为 1 时,有 80%的交易是盈利的。
  • 使用 1%百分比风险模型,有 100%的交易是盈利的。
  • 有 38%的盈利交易

因此似乎:

  • 只有最后一项被复制了。

结论

正如阿尔捷姆·卡兹纳切夫所指出的,复制危机可能是由于:

  • 使用错误的数据集
  • 未能正确实施算法

或者原始实施可能并没有遵循自己的规则,或者并没有发布所有细节。

注意

无论如何,我个人仍然建议阅读这本书。未能复制特定情况并不意味着这本书不值得一读,它展示了一种实用的算法交易方法。


BackTrader 中文文档(十四)(2)https://developer.aliyun.com/article/1505354

相关文章
|
1月前
|
存储
BackTrader 中文文档(十四)(4)
BackTrader 中文文档(十四)
21 0
BackTrader 中文文档(十四)(4)
|
1月前
|
机器学习/深度学习 人工智能 测试技术
BackTrader 中文文档(十四)(3)
BackTrader 中文文档(十四)
22 0
BackTrader 中文文档(十四)(3)
|
1月前
|
Python
BackTrader 中文文档(十四)(2)
BackTrader 中文文档(十四)
20 0
|
1月前
BackTrader 中文文档(十五)(3)
BackTrader 中文文档(十五)
20 0
|
1月前
|
算法 索引 Python
BackTrader 中文文档(十五)(4)
BackTrader 中文文档(十五)
18 0
|
1月前
|
编解码 算法 开发者
BackTrader 中文文档(十五)(1)
BackTrader 中文文档(十五)
20 0
|
1月前
|
调度
BackTrader 中文文档(十五)(2)
BackTrader 中文文档(十五)
15 0
|
1月前
|
存储 测试技术 API
BackTrader 中文文档(十二)(1)
BackTrader 中文文档(十二)
36 0
|
1月前
BackTrader 中文文档(十二)(4)
BackTrader 中文文档(十二)
16 0
|
1月前
|
存储 编解码
BackTrader 中文文档(十二)(3)
BackTrader 中文文档(十二)
30 0