首先,让我们用两行总结反向交易方法的工作原理:
它就像一个带有基本构建块 (
Cerebro
) 的建筑套件,可以插入许多不同的部件基本分布包含许多部分,如指针、分析器、观察器、 Sizer 、过滤器、数据馈送、经纪人、佣金/资产信息方案,...
可以从头开始或基于现有的构建块轻松构建新的构建块
基本构建块 (
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
系列可插拔部件实现的。
一些文档: Docs - Commission Schemes - 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,可以将方案添加到系统中。
代理文档位于: Docs - Broker - 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
请注意,图表中的最终投资组合价值不同,这是因为实际交易规模不同。
是的,反向交易者可以。使用可插入/可扩展的构建工具包方法,可以很容易地根据交易者进程员的特定需求定制行为。
#!/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()