在 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.5
或1001.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)
。除了添加一个适用于所有资产的方案(当name
为None
时),还可以设置仅适用于具有特定名称资产的方案。
实施分数方案
这可以通过扩展现有的基础方案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上成功复制已发布的算法交易策略的帖子。首先
- 我已经重现了 130 多篇关于“预测股市”的研究论文,从头开始编码,并记录了结果。这是我学到的东西因为这里已被删除,快速摘要如下:
- 这些策略不起作用
- 如果作者声称某种策略停止工作是由于阿尔法衰减,那么测试将针对过去的数据运行,而且仍然无法工作
- 底线是:这都是过度拟合,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)
一些实现细节
抛硬币被建模为一个指标,以便于可视化翻转的位置(例如,如果几个条目朝着相同的方向,这在随机情况下是可以预料的)
为了也能很好地可视化止损及其移动方式,止损价格计算和逻辑也嵌入到了指标中。注意,止损计算逻辑有两个不同的阶段
- 当交易开始时,止损价格必须与前一个止损价格无关地设置在给定距离之内
- 当交易进行时,如果可能,止损价格将根据趋势进行调整。
绘图
代码产生两种类型的图表
- 包含单次测试运行详细信息的图表(
--plot
选项)。在运行单次迭代(--iterations 1
)时使用它最有意义 - 显示运行的利润和损失的散点图。
#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