BackTrader 中文文档(二十五)(3)

简介: BackTrader 中文文档(二十五)

BackTrader 中文文档(二十五)(2)https://developer.aliyun.com/article/1505458

用户定义的佣金

原文:www.backtrader.com/blog/posts/2015-11-20-commission-schemes-subclassing/commission-schemes-subclassing/

佣金方案实现不久前进行了重新设计。最重要的部分是:

  • 保留原始的 CommissionInfo 类和行为
  • 为轻松创建用户定义的佣金打开大门
  • 将格式 xx%设置为新佣金方案的默认值,而不是 0.xx(只是一种口味),保持行为可配置

在扩展佣金中概述了基础知识。

注意

请参见下文CommInfoBase的文档字符串以获取参数参考

定义佣金方案

它涉及 1 或 2 个步骤

  1. 子类化CommInfoBase
    简单地更改默认参数可能就足够了。backtrader已经在模块backtrader.commissions中对一些定义进行了这样的更改。期货的常规行业标准是每合约和每轮固定金额。定义可以如下进行:
class CommInfo_Futures_Fixed(CommInfoBase):
    params = (
        ('stocklike', False),
        ('commtype', CommInfoBase.COMM_FIXED),
    )` 
  1. 对于股票和百分比佣金:
class CommInfo_Stocks_Perc(CommInfoBase):
    params = (
        ('stocklike', True),
        ('commtype', CommInfoBase.COMM_PERC),
    )` 
  1. 如上所述,百分比的默认解释(作为参数commission传递)为:xx%。如果希望使用旧的/其他行为 0.xx,可以轻松实现:
class CommInfo_Stocks_PercAbs(CommInfoBase):
    params = (
        ('stocklike', True),
        ('commtype', CommInfoBase.COMM_PERC),
        ('percabs', True),
    )` 
  1. 覆盖(如果需要)_getcommission方法
    定义如下:
def _getcommission(self, size, price, pseudoexec):
   '''Calculates the commission of an operation at a given price
   pseudoexec: if True the operation has not yet been executed
   '''` 
  1. 更多实际示例的细节见下文

如何将其应用于平台

一旦CommInfoBase子类就位,诀窍就是使用broker.addcommissioninfo而不是通常的broker.setcommission。后者将在内部使用旧有的CommissionInfoObject

说起来比做起来容易:

...
comminfo = CommInfo_Stocks_PercAbs(commission=0.005)  # 0.5%
cerebro.broker.addcommissioninfo(comminfo)

addcommissioninfo方法的定义如下:

def addcommissioninfo(self, comminfo, name=None):
    self.comminfo[name] = comminfo

设置name意味着comminfo对象只适用于具有该名称的资产。None的默认值意味着它适用于系统中的所有资产。

一个实际的示例

Ticket #45要求有一个适用于期货的佣金方案,按比例计算,并使用合同的整个“虚拟”价值的佣金百分比。即:在佣金计算中包括期货乘数。

这应该很容易:

import backtrader as bt
class CommInfo_Fut_Perc_Mult(bt.CommInfoBase):
    params = (
      ('stocklike', False),  # Futures
      ('commtype', bt.CommInfoBase.COMM_PERC),  # Apply % Commission
    # ('percabs', False),  # pass perc as xx% which is the default
    )
    def _getcommission(self, size, price, pseudoexec):
        return size * price * self.p.commission * self.p.mult

将其放入系统中:

comminfo = CommInfo_Fut_Perc_Mult(
    commission=0.1,  # 0.1%
    mult=10,
    margin=2000  # Margin is needed for futures-like instruments
)
cerebro.broker.addcommission(comminfo)

如果默认值偏好格式为 0.xx,只需将参数percabs设置为True

class CommInfo_Fut_Perc_Mult(bt.CommInfoBase):
    params = (
      ('stocklike', False),  # Futures
      ('commtype', bt.CommInfoBase.COMM_PERC),  # Apply % Commission
      ('percabs', True),  # pass perc as 0.xx
    )
comminfo = CommInfo_Fut_Perc_Mult(
    commission=0.001,  # 0.1%
    mult=10,
    margin=2000  # Margin is needed for futures-like instruments
)
cerebro.broker.addcommissioninfo(comminfo)

所有这些都应该奏效。

解释pseudoexec

让我们回顾一下_getcommission的定义:

def _getcommission(self, size, price, pseudoexec):
    '''Calculates the commission of an operation at a given price
    pseudoexec: if True the operation has not yet been executed
    '''

pseudoexec参数的目的可能看起来很模糊,但它确实有用。

  • 平台可能会调用此方法进行可用现金的预先计算和一些其他任务
  • 这意味着该方法可能会(而且实际上会)多次使用相同的参数进行调用。

pseudoexec 指示调用是否对应于实际执行订单。虽然乍一看这可能看起来“不相关”,但如果考虑到以下情景,它就是相关的:

  • 一家经纪人在协商合同数量超过 5000 单位后,将提供期货往返佣金 50%的折扣。
    在这种情况下,如果没有 pseudoexec,对该方法的多次非执行调用将很快触发折扣已经生效的假设。

将场景投入实际运作:

import backtrader as bt
class CommInfo_Fut_Discount(bt.CommInfoBase):
    params = (
      ('stocklike', False),  # Futures
      ('commtype', bt.CommInfoBase.COMM_FIXED),  # Apply Commission
      # Custom params for the discount
      ('discount_volume', 5000),  # minimum contracts to achieve discount
      ('discount_perc', 50.0),  # 50.0% discount
    )
    negotiated_volume = 0  # attribute to keep track of the actual volume
    def _getcommission(self, size, price, pseudoexec):
        if self.negotiated_volume > self.p.discount_volume:
           actual_discount = self.p.discount_perc / 100.0
        else:
           actual_discount = 0.0
        commission = self.p.commission * (1.0 - actual_discount)
        commvalue = size * price * commission
        if not pseudoexec:
           # keep track of actual real executed size for future discounts
           self.negotiated_volume += size
        return commvalue

pseudoexec 的目的和存在意义现在应该是清楚的了。

CommInfoBase 文档字符串和参数

这里是:

class CommInfoBase(with_metaclass(MetaParams)):
    '''Base Class for the Commission Schemes.
    Params:
      - commission (def: 0.0): base commission value in percentage or monetary
        units
      - mult (def 1.0): multiplier applied to the asset for value/profit
      - margin (def: None): amount of monetary units needed to open/hold an
        operation. It only applies if the final ``_stocklike`` attribute in the
        class is set to False
      - commtype (def: None): Supported values are CommInfoBase.COMM_PERC
        (commission to be understood as %) and CommInfoBase.COMM_FIXED
        (commission to be understood as monetary units)
        The default value of ``None`` is a supported value to retain
        compatibility with the legacy ``CommissionInfo`` object. If
        ``commtype`` is set to None, then the following applies:
          - margin is None: Internal _commtype is set to COMM_PERC and
            _stocklike is set to True (Operating %-wise with Stocks)
          - margin is not None: _commtype set to COMM_FIXED and _stocklike set
            to False (Operating with fixed rount-trip commission with Futures)
        If this param is set to something else than None, then it will be
        passed to the internal ``_commtype`` attribute and the same will be
        done with the param ``stocklike`` and the internal attribute
        ``_stocklike``
      - stocklike (def: False):  Indicates if the instrument is Stock-like or
        Futures-like (see the ``commtype`` discussion above)
      - percabs (def: False): when ``commtype`` is set to COMM_PERC, whether
        the parameter ``commission`` has to be understood as XX% or 0.XX
        If this param is True: 0.XX
        If this param is False: XX%
    Attributes:
      - _stocklike: Final value to use for Stock-like/Futures-like behavior
      - _commtype: Final value to use for PERC vs FIXED commissions
      This two are used internally instead of the declared params to enable the
      compatibility check described above for the legacy ``CommissionInfo``
      object
    '''
    COMM_PERC, COMM_FIXED = range(2)
    params = (
        ('commission', 0.0), ('mult', 1.0), ('margin', None),
        ('commtype', None),
        ('stocklike', False),
        ('percabs', False),
    )

扩展佣金

原文:www.backtrader.com/blog/posts/2015-11-05-commission-schemes-extended/commission-schemes-extended/

佣金和相关功能由一个单独的类CommissionInfo管理,该类主要通过调用broker.setcommission来实例化。

有一些帖子讨论了这种行为。

  • 佣金:股票 vs 期货
  • 改进佣金:股票 vs 期货

这个概念仅限于具有保证金的期货和具有基于价格/大小百分比的佣金的股票。 即使它已经达到了它的目的,它也不是最灵活的方案。

我自己的实现中只有一件事我不喜欢,那就是CommissionInfo将百分比值以绝对值(0.xx)而不是相对值(xx%)的方式传递

GitHub 的增强请求#29导致一些重新设计,以:

  • 保持CommissionInfobroker.setcommission与原始行为兼容
  • 对代码进行清理
  • 使佣金方案灵活以支持增强请求和进一步可能性

进入示例之前的实际工作:

class CommInfoBase(with_metaclass(MetaParams)):
    COMM_PERC, COMM_FIXED = range(2)
    params = (
        ('commission', 0.0), ('mult', 1.0), ('margin', None),
        ('commtype', None),
        ('stocklike', False),
        ('percabs', False),
    )

引入了一个CommissionInfo的基类,将新参数添加到混合中:

  • commtype(默认:None)这是兼容性的关键。 如果值为None,则CommissionInfo对象和broker.setcommission的行为将与以前相同。 即:
  • 如果设置了margin,则佣金方案适用于具有固定合约佣金的期货
  • 如果未设置margin,则佣金方案适用于具有基于百分比的股票方法
  • 如果值为COMM_PERCCOMM_FIXED(或派生类中的任何其他值),则这显然决定了佣金是固定的还是基于百分比的
  • stocklike(默认:False)
    如上所述,旧的CommissionInfo对象中的实际行为由参数margin决定
    如上所述,如果将commtype设置为除None之外的其他值,则此值指示资产是否为类似期货的资产(将使用保证金,并执行基于条的现金调整)或者这是类似股票的资产
  • percabs(默认:False)
    如果为False,则百分比必须以相对术语传递(xx%)
    如果为True,则百分比必须以绝对值(0.xx)传递
    CommissionInfo是从CommInfoBase派生的,将此参数的默认值更改为True以保持兼容的行为

所有这些参数也可以在broker.setcommission中使用,现在看起来像这样:

def setcommission(self,
                  commission=0.0, margin=None, mult=1.0,
                  commtype=None, percabs=True, stocklike=False,
                  name=None):

请注意以下内容:

  • percabs设置为True,以保持与旧调用的CommissionInfo对象上述行为的兼容

重新设计了用于测试commissions-schemes的旧样本,以支持命令行参数和新行为。 使用帮助:

$ ./commission-schemes.py --help
usage: commission-schemes.py [-h] [--data DATA] [--fromdate FROMDATE]
                             [--todate TODATE] [--stake STAKE]
                             [--period PERIOD] [--cash CASH] [--comm COMM]
                             [--mult MULT] [--margin MARGIN]
                             [--commtype {none,perc,fixed}] [--stocklike]
                             [--percrel] [--plot] [--numfigs NUMFIGS]
Commission schemes
optional arguments:
  -h, --help            show this help message and exit
  --data DATA, -d DATA  data to add to the system (default:
                        ../../datas/2006-day-001.txt)
  --fromdate FROMDATE, -f FROMDATE
                        Starting date in YYYY-MM-DD format (default:
                        2006-01-01)
  --todate TODATE, -t TODATE
                        Starting date in YYYY-MM-DD format (default:
                        2006-12-31)
  --stake STAKE         Stake to apply in each operation (default: 1)
  --period PERIOD       Period to apply to the Simple Moving Average (default:
                        30)
  --cash CASH           Starting Cash (default: 10000.0)
  --comm COMM           Commission factor for operation, either apercentage or
                        a per stake unit absolute value (default: 2.0)
  --mult MULT           Multiplier for operations calculation (default: 10)
  --margin MARGIN       Margin for futures-like operations (default: 2000.0)
  --commtype {none,perc,fixed}
                        Commission - choose none for the old CommissionInfo
                        behavior (default: none)
  --stocklike           If the operation is for stock-like assets orfuture-
                        like assets (default: False)
  --percrel             If perc is expressed in relative xx{'const': True,
                        'help': u'If perc is expressed in relative xx%
                        ratherthan absolute value 0.xx', 'option_strings': [u'
                        --percrel'], 'dest': u'percrel', 'required': False,
                        'nargs': 0, 'choices': None, 'default': False, 'prog':
                        'commission-schemes.py', 'container':
                        <argparse._ArgumentGroup object at
                        0x0000000007EC9828>, 'type': None, 'metavar':
                        None}atherthan absolute value 0.xx (default: False)
  --plot, -p            Plot the read data (default: False)
  --numfigs NUMFIGS, -n NUMFIGS
                        Plot using numfigs figures (default: 1)

让我们进行一些运行,以重新创建原始佣金方案帖子的原始行为。

期货的佣金(固定和带保证金)

执行和图表:

$ ./commission-schemes.py --comm 2.0 --margin 2000.0 --mult 10 --plot

并且输出显示固定佣金为 2.0 货币单位(默认押注为 1):

2006-03-09, BUY CREATE, 3757.59
2006-03-10, BUY EXECUTED, Price: 3754.13, Cost: 2000.00, Comm 2.00
2006-04-11, SELL CREATE, 3788.81
2006-04-12, SELL EXECUTED, Price: 3786.93, Cost: 2000.00, Comm 2.00
2006-04-12, TRADE PROFIT, GROSS 328.00, NET 324.00
...

股票的佣金(百分比和无保证金)

执行和图表:

$ ./commission-schemes.py --comm 0.005 --margin 0 --mult 1 --plot

为了提高可读性,可以使用相对%值:

$ ./commission-schemes.py --percrel --comm 0.5 --margin 0 --mult 1 --plot

现在0.5直接表示0.5%

输出在两种情况下都是:

2006-03-09, BUY CREATE, 3757.59
2006-03-10, BUY EXECUTED, Price: 3754.13, Cost: 3754.13, Comm 18.77
2006-04-11, SELL CREATE, 3788.81
2006-04-12, SELL EXECUTED, Price: 3786.93, Cost: 3754.13, Comm 18.93
2006-04-12, TRADE PROFIT, GROSS 32.80, NET -4.91
...

期货的佣金(百分比和带保证金)

使用新参数,基于百分比的期货:

$ ./commission-schemes.py --commtype perc --percrel --comm 0.5 --margin 2000 --mult 10 --plot

改变佣金…最终结果发生变化并不奇怪

输出显示佣金现在是可变的:

2006-03-09, BUY CREATE, 3757.59
2006-03-10, BUY EXECUTED, Price: 3754.13, Cost: 2000.00, Comm 18.77
2006-04-11, SELL CREATE, 3788.81
2006-04-12, SELL EXECUTED, Price: 3786.93, Cost: 2000.00, Comm 18.93
2006-04-12, TRADE PROFIT, GROSS 328.00, NET 290.29
...

在上一次运行中设置了 2.0 货币单位(默认押注为 1)

另一篇文章将详细介绍新的类别和自制佣金方案的实施。

示例代码

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
import argparse
import datetime
import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind
class SMACrossOver(bt.Strategy):
    params = (
        ('stake', 1),
        ('period', 30),
    )
    def log(self, txt, dt=None):
        ''' Logging function fot this strategy'''
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))
    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            # Buy/Sell order submitted/accepted to/by broker - Nothing to do
            return
        # Check if an order has been completed
        # Attention: broker could reject order if not enougth cash
        if order.status in [order.Completed, order.Canceled, order.Margin]:
            if order.isbuy():
                self.log(
                    'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                    (order.executed.price,
                     order.executed.value,
                     order.executed.comm))
            else:  # Sell
                self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                         (order.executed.price,
                          order.executed.value,
                          order.executed.comm))
    def notify_trade(self, trade):
        if trade.isclosed:
            self.log('TRADE PROFIT, GROSS %.2f, NET %.2f' %
                     (trade.pnl, trade.pnlcomm))
    def __init__(self):
        sma = btind.SMA(self.data, period=self.p.period)
        # > 0 crossing up / < 0 crossing down
        self.buysell_sig = btind.CrossOver(self.data, sma)
    def next(self):
        if self.buysell_sig > 0:
            self.log('BUY CREATE, %.2f' % self.data.close[0])
            self.buy(size=self.p.stake)  # keep order ref to avoid 2nd orders
        elif self.position and self.buysell_sig < 0:
            self.log('SELL CREATE, %.2f' % self.data.close[0])
            self.sell(size=self.p.stake)
def runstrategy():
    args = parse_args()
    # Create a cerebro
    cerebro = bt.Cerebro()
    # Get the dates from the args
    fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
    todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
    # Create the 1st data
    data = btfeeds.BacktraderCSVData(
        dataname=args.data,
        fromdate=fromdate,
        todate=todate)
    # Add the 1st data to cerebro
    cerebro.adddata(data)
    # Add a strategy
    cerebro.addstrategy(SMACrossOver, period=args.period, stake=args.stake)
    # Add the commission - only stocks like a for each operation
    cerebro.broker.setcash(args.cash)
    commtypes = dict(
        none=None,
        perc=bt.CommInfoBase.COMM_PERC,
        fixed=bt.CommInfoBase.COMM_FIXED)
    # Add the commission - only stocks like a for each operation
    cerebro.broker.setcommission(commission=args.comm,
                                 mult=args.mult,
                                 margin=args.margin,
                                 percabs=not args.percrel,
                                 commtype=commtypes[args.commtype],
                                 stocklike=args.stocklike)
    # And run it
    cerebro.run()
    # Plot if requested
    if args.plot:
        cerebro.plot(numfigs=args.numfigs, volume=False)
def parse_args():
    parser = argparse.ArgumentParser(
        description='Commission schemes',
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,)
    parser.add_argument('--data', '-d',
                        default='../../datas/2006-day-001.txt',
                        help='data to add to the system')
    parser.add_argument('--fromdate', '-f',
                        default='2006-01-01',
                        help='Starting date in YYYY-MM-DD format')
    parser.add_argument('--todate', '-t',
                        default='2006-12-31',
                        help='Starting date in YYYY-MM-DD format')
    parser.add_argument('--stake', default=1, type=int,
                        help='Stake to apply in each operation')
    parser.add_argument('--period', default=30, type=int,
                        help='Period to apply to the Simple Moving Average')
    parser.add_argument('--cash', default=10000.0, type=float,
                        help='Starting Cash')
    parser.add_argument('--comm', default=2.0, type=float,
                        help=('Commission factor for operation, either a'
                              'percentage or a per stake unit absolute value'))
    parser.add_argument('--mult', default=10, type=int,
                        help='Multiplier for operations calculation')
    parser.add_argument('--margin', default=2000.0, type=float,
                        help='Margin for futures-like operations')
    parser.add_argument('--commtype', required=False, default='none',
                        choices=['none', 'perc', 'fixed'],
                        help=('Commission - choose none for the old'
                              ' CommissionInfo behavior'))
    parser.add_argument('--stocklike', required=False, action='store_true',
                        help=('If the operation is for stock-like assets or'
                              'future-like assets'))
    parser.add_argument('--percrel', required=False, action='store_true',
                        help=('If perc is expressed in relative xx% rather'
                              'than absolute value 0.xx'))
    parser.add_argument('--plot', '-p', action='store_true',
                        help='Plot the read data')
    parser.add_argument('--numfigs', '-n', default=1,
                        help='Plot using numfigs figures')
    return parser.parse_args()
if __name__ == '__main__':
    runstrategy()


BackTrader 中文文档(二十五)(4)https://developer.aliyun.com/article/1505461

相关文章
|
8月前
BackTrader 中文文档(二十五)(2)
BackTrader 中文文档(二十五)
43 0
|
8月前
|
数据可视化
BackTrader 中文文档(二十五)(4)
BackTrader 中文文档(二十五)
65 0
|
8月前
|
数据可视化 API Python
BackTrader 中文文档(二十五)(1)
BackTrader 中文文档(二十五)
59 0
|
8月前
|
存储 Python
BackTrader 中文文档(二十二)(3)
BackTrader 中文文档(二十二)
57 0
|
8月前
|
存储 测试技术 Python
BackTrader 中文文档(二十二)(2)
BackTrader 中文文档(二十二)
65 0
|
8月前
|
测试技术
BackTrader 中文文档(二十二)(4)
BackTrader 中文文档(二十二)
51 0
|
8月前
BackTrader 中文文档(二十二)(1)
BackTrader 中文文档(二十二)
40 0
|
8月前
|
编解码 索引
BackTrader 中文文档(二十三)(2)
BackTrader 中文文档(二十三)
51 0
|
8月前
BackTrader 中文文档(二十三)(4)
BackTrader 中文文档(二十三)
46 0
|
8月前
|
编解码 API Windows
BackTrader 中文文档(二十三)(3)
BackTrader 中文文档(二十三)
52 0

热门文章

最新文章

下一篇
开通oss服务