Skip to content

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

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

首先,让我们用两行文字总结一下反向交易者的工作原理:

  • 它就像一个带有基本构造块(Cerebro的构造套件,可以插入许多不同的部件

  • 基本分布包含许多块,如指标分析工具观察者尺寸仪过滤器数据源经纪人佣金/资产信息方案、。。。

  • 新的构建块可以很容易地从头开始构建,或者基于现有的构建块

  • 基本构建块(Cerebro)已经进行了一些自动【插入】的操作,以便更轻松地使用框架,而不必担心所有细节。

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

  • 使用单个/主数据源
  • 1-day时间段/压缩组合
  • 10000 货币单位
  • 股票交易

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

交易股票:整数

如上所述,默认配置为股票交易,当交易股票时,买入/卖出完整的股票,(即: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),它有以下 docstring

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单位的短线交易已经开始。由于明显的原因,没有显示整个日志,其中包含许多其他操作,所有操作都具有完整大小的交易。

!No Fractions

分批运行

在对分数进行了艰难的子类化和一次衬砌工作之后。。。

$ ./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

!Fractions

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

结论

是的,反向交易者可以。使用可插拔/可扩展的构造工具包方法,可以很容易地根据交易者程序员的特定需求定制行为。

剧本

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

我们一直在努力

apachecn/AiLearning

【布客】中文翻译组