Backtrader交易基础

68 篇文章 14 订阅
13 篇文章 22 订阅

查看账户情况:

class TestStrategy(bt.Strategy):
    def next(self):
        print('当前可用资金', self.broker.getcash())
        print('当前总资产', self.broker.getvalue())
        print('当前持仓量', self.broker.getposition(self.data).size)
        print('当前持仓成本', self.broker.getposition(self.data).price)
        # 也可以直接获取持仓
        print('当前持仓量', self.getposition(self.data).size)
        print('当前持仓成本', self.getposition(self.data).price)
        # 注:getposition() 需要指定具体的标的数据集

滑点设置:

# 方式1:通过 BackBroker 类中的 slip_perc 参数设置百分比滑点
cerebro.broker = bt.brokers.BackBroker(slip_perc=0.0001)
# 方式2:通过调用 brokers 的 set_slippage_perc 方法设置百分比滑点
cerebro.broker.set_slippage_perc(perc=0.0001)

# 方式1:通过 BackBroker 类中的 slip_fixed 参数设置固定滑点
cerebro.broker = bt.brokers.BackBroker(slip_fixed=0.001)
# 方式2:通过调用 brokers 的 set_slippage_fixed 方法设置固定滑点
cerebro.broker = cerebro.broker.set_slippage_fixed(fixed=0.001)

参数说明:

有关滑点的其他设置
slip_open:是否对开盘价做滑点处理,该参数在 BackBroker() 类中默认为 False,在 set_slippage_perc 和set_slippage_fixed 方法中默认为 True;
slip_match:是否将滑点处理后的新成交价与成交当天的价格区间 low ~ high 做匹配,如果为 True,则根据新成交价重新匹配调整价格区间,确保订单能被执行;如果为 False,则不会与价格区间做匹配,订单不会执行,但会在下一日执行一个空订单;默认取值为 True;
slip_out:如果新成交价高于最高价或低于最高价,是否以超出的价格成交,如果为 True,则允许以超出的价格成交;如果为 False,实际成交价将被限制在价格区间内  low ~ high;默认取值为 False;
slip_limit:是否对限价单执行滑点,如果为 True,即使 slip_match 为Fasle,也会对价格做匹配,确保订单被执行;如果为 False,则不做价格匹配;默认取值为 True。

# 情况1:
set_slippage_fixed(fixed=0.35,
                   slip_open=False,
                   slip_match=True,
                   slip_out=False)
# 由于 slip_open=False ,不会对开盘价做滑点处理,所以仍然以原始开盘价 32.63307367 成交

# 情况2:
set_slippage_fixed(fixed=0.35,
                   slip_open=True,
                   slip_match=True,
                   slip_out=False)

# 情况3:
set_slippage_fixed(fixed=0.35,
                   slip_open=True,
                   slip_match=True,
                   slip_out=True)
# 滑点调整的新成交价为 32.63307367+0.35 = 32.98307367,超出了当天最高价 32.94151482
# 允许做价格匹配 slip_match=True, 而且运行以超出价格区间的新成交价执行 slip_out=True
# 最终以新成交价 32.98307367 成交

# 情况4:
set_slippage_fixed(fixed=0.35,
                   slip_open=True,
                   slip_match=False,
                   slip_out=True)
# 滑点调整的新成交价为 32.63307367+0.35 = 32.98307367,超出了当天最高价 32.94151482
# 由于不进行价格匹配 slip_match=False,新成交价超出价格区间无法成交
# 2019-01-17 这一天订单不会执行,但会在下一日 2019-01-18 执行一个空订单
# 再往后的 2019-07-02,也未执行订单,下一日 2019-07-03 执行空订单
# 即使 2019-07-03的 open 39.96627412+0.35 < high 42.0866713 满足成交条件,也不会补充成交

交易税费管理

股票:目前 A 股的交易费用分为 2 部分:佣金和印花税,
其中佣金双边征收,不同证券公司收取的佣金各不相同,一般在 0.02%-0.03% 左右,单笔佣金不少于 5 元;
印花税只在卖出时收取,税率为 0.1%。


期货:期货交易费用包括交易所收取手续费和期货公司收取佣金 2 部分,交易所手续费较为固定,
不同期货公司佣金不一致,而且不同期货品种的收取方式不相同,有的按照固定费用收取,有的按成交金额的固定百分比收取:
合约现价*合约乘数*手续费费率。除了交易费用外,期货交易时还需上交一定比例的保证金 。

Backtrader 也提供了多种交易费设置方式,既可以简单的通过参数进行设置,也可以结合交易条件自定义费用函数:

根据交易品种的不同,Backtrader 将交易费用分为 股票 Stock-like 模式和期货 Futures-like 种模式;
根据计算方式的不同,Backtrader 将交易费用分为 PERC 百分比费用模式 和 FIXED 固定费用模式 ;

Stock-like 模式与 PERC 百分比费用模式对应,期货 Futures-like 与 FIXED 固定费用模式对应;

在设置交易费用时,最常涉及如下 3 个参数:

commission:手续费 / 佣金;

mult:乘数;

margin:保证金 / 保证金比率 。

双边征收:买入和卖出操作都要收取相同的交易费用 。

cerebro.broker.setcommission(
    # 交易手续费,根据margin取值情况区分是百分比手续费还是固定手续费
    commission=0.0,
    # 期货保证金,决定着交易费用的类型,只有在stocklike=False时起作用
    margin=None,
    # 乘数,盈亏会按该乘数进行放大
    mult=1.0,
    # 交易费用计算方式,取值有:
    # 1.CommInfoBase.COMM_PERC 百分比费用
    # 2.CommInfoBase.COMM_FIXED 固定费用
    # 3.None 根据 margin 取值来确定类型
    commtype=None,
    # 当交易费用处于百分比模式下时,commission 是否为 % 形式
    # True,表示不以 % 为单位,0.XX 形式;False,表示以 % 为单位,XX% 形式
    percabs=True,
    # 是否为股票模式,该模式通常由margin和commtype参数决定
    # margin=None或COMM_PERC模式时,就会stocklike=True,对应股票手续费;
    # margin设置了取值或COMM_FIXED模式时,就会stocklike=False,对应期货手续费
    stocklike=False,
    # 计算持有的空头头寸的年化利息
    # days * price * abs(size) * (interest / 365)
    interest=0.0,
    # 计算持有的多头头寸的年化利息
    interest_long=False,
    # 杠杆比率,交易时按该杠杆调整所需现金
    leverage=1.0,
    # 自动计算保证金
    # 如果False,则通过margin参数确定保证金
    # 如果automargin<0,通过mult*price确定保证金
    # 如果automargin>0,如果automargin*price确定保证金
    automargin=False,
    # 交易费用设置作用的数据集(也就是作用的标的)
    # 如果取值为None,则默认作用于所有数据集(也就是作用于所有assets)
    name=None)

从上述各参数的含义和作用可知,margin 、commtype、stocklike 存在 2 种默认的配置规则:股票百分比费用、期货固定费用,具体如下:
第 1 条规则:未设置 margin(即 margin 为 0 / None / False)→ commtype 会指向 COMM_PERC 百分比费用 → 底层的 _stocklike 属性会设置为 True → 对应的是“股票百分比费用”。
所以如果想为股票设置交易费用,就令 margin = 0 / None / False,或者令 stocklike=True;

第 2 条规则:为 margin 设置了取值 →   commtype 会指向 COMM_FIXED 固定费用 → 底层的 _stocklike 属性会设置为 False → 对应的是“期货固定费用”,因为只有期货才会涉及保证金。
所以如果想为期货设置交易费用,就需要设置 margin,此外还需令 stocklike=True,margin 参数才会起作用 。

自定义交易费用的例子

# 自定义期货百分比费用
class CommInfo_Fut_Perc_Mult(bt.CommInfoBase):
    params = (
      ('stocklike', False), # 指定为期货模式
      ('commtype', bt.CommInfoBase.COMM_PERC), # 使用百分比费用
      ('percabs', False), # commission 以 % 为单位
    )

    def _getcommission(self, size, price, pseudoexec):
        # 计算交易费用
        return (abs(size) * price) * (self.p.commission/100) * self.p.mult
    # pseudoexec 用于提示当前是否在真实统计交易费用
    # 如果只是试算费用,pseudoexec=False
    # 如果是真实的统计费用,pseudoexec=True

comminfo = CommInfo_Fut_Perc_Mult(
    commission=0.1, # 0.1%
    mult=10,
    margin=2000) # 实例化
cerebro.broker.addcommissioninfo(comminfo)

# 上述自定义函数,也可以通过 setcommission 来实现
cerebro.broker.setcommission(commission=0.1, #0.1%
                             mult=10,
                             margin=2000,
                             percabs=False,
                             commtype=bt.CommInfoBase.COMM_PERC,
                             stocklike=False)

下面是考虑佣金和印花税的股票百分比费用:

class StockCommission(bt.CommInfoBase):
    params = (
      ('stocklike', True), # 指定为期货模式
      ('commtype', bt.CommInfoBase.COMM_PERC), # 使用百分比费用模式
      ('percabs', True), # commission 不以 % 为单位
      ('stamp_duty', 0.001),) # 印花税默认为 0.1%
    
    # 自定义费用计算公式
      def _getcommission(self, size, price, pseudoexec):
            if size > 0: # 买入时,只考虑佣金
                return abs(size) * price * self.p.commission
            elif size < 0: # 卖出时,同时考虑佣金和印花税
        return abs(size) * price * (self.p.commission + self.p.stamp_duty)
            else:
                return 0

成交量限制管理

形式1:bt.broker.fillers.FixedSize(size) 

通过 FixedSize() 方法设置最大的固定成交量:size,该种模式下的成交量限制规则如下:

订单实际成交量的确定规则:取(size、订单执行那天的 volume 、订单中要求的成交数量)中的最小者;

订单执行那天,如果订单中要求的成交数量无法全部满足,则只成交部分数量。第二天不会补单。

# 通过 BackBroker() 类直接设置
cerebro = Cerebro()
filler = bt.broker.fillers.FixedSize(size=xxx)
newbroker = bt.broker.BrokerBack(filler=filler)
cerebro.broker = newbroker

# 通过 set_filler 方法设置
cerebro = Cerebro()
cerebro.broker.set_filler(bt.broker.fillers.FixedSize(size=xxx))

# self.order = self.buy(size=2000) # 每次买入 2000 股
# cerebro.broker.set_filler(bt.broker.fillers.FixedSize(size=3000)) # 固定最大成交量

形式2:bt.broker.fillers.FixedBarPerc(perc)

通过 FixedBarPerc(perc) 将 订单执行当天 bar 的总成交量 volume 的 perc % 设置为最大的固定成交量,该模式的成交量限制规则如下:

订单实际成交量的确定规则:取 (volume * perc /100、订单中要求的成交数量) 的 最小者;
订单执行那天,如果订单中要求的成交数量无法全部满足,则只成交部分数量。

# 通过 BackBroker() 类直接设置
cerebro = Cerebro()
filler = bt.broker.fillers.FixedBarPerc(perc=xxx)
newbroker = bt.broker.BrokerBack(filler=filler)
cerebro.broker = newbroker

# 通过 set_filler 方法设置
cerebro = Cerebro()
cerebro.broker.set_filler(bt.broker.fillers.FixedBarPerc(perc=xxx))
# perc 以 % 为单位,取值范围为[0.0,100.0]

# self.order = self.buy(size=2000) # 以下一日开盘价买入2000股
# cerebro.broker.set_filler(bt.broker.fillers.FixedBarPerc(perc=50))

形式3:bt.broker.fillers.BarPointPerc(minmov=0.01,perc=100.0)

BarPointPerc() 在考虑了价格区间的基础上确定成交量,在订单执行当天,成交量确定规则为:

通过 minmov 将 当天 bar 的价格区间 low ~ high 进行均匀划分,得到划分的份数:

part =  (high -low +minmov)  // minmov  (向下取整)

再对当天 bar 的总成交量 volume 也划分成相同的份数 part ,这样就能得到每份的平均成交量:

volume_per = volume // part 

最终,volume_per * (perc / 100)就是允许的最大成交量,实际成交时,对比订单中要求的成交量,就可以得到最终实际成交量

实际成交量 = min ( volume_per * (perc / 100), 订单中要求的成交数量 )

# 通过 BackBroker() 类直接设置
cerebro = Cerebro()
filler = bt.broker.fillers.BarPointPerc(minmov=0.01,perc=100.0)
newbroker = bt.broker.BrokerBack(filler=filler)
cerebro.broker = newbroker

# 通过 set_filler 方法设置
cerebro = Cerebro()
cerebro.broker.set_filler(bt.broker.fillers.BarPointPerc(minmov=0.01,perc=100.0))
# perc 以 % 为单位,取值范围为[0.0,100.0]


# self.order = self.buy(size=2000) # 以下一日开盘价买入2000股

# cerebro.broker.set_filler(bt.broker.fillers.BarPointPerc(minmov=0.1, perc=50)) # 表示 50%

交易时机管理
对于交易订单生成和执行时间,Backtrader 默认是 “当日收盘后下单,次日以开盘价成交”,这种模式在回测过程中能有效避免使用未来数据。
但对于一些特殊的交易场景,比如“all_in”情况下,当日所下订单中的数量是用当日收盘价计算的(总资金 / 当日收盘价),次日以开盘价执行订单时,
如果开盘价比昨天的收盘价提高了,就会出现可用资金不足的情况。
为了应对一些特殊交易场景,Backtrader 还提供了一些 cheating 式的交易时机模式:Cheat-On-Open 和 Cheat-On-Close。

Cheat-On-Open

Cheat-On-Open 是“当日下单,当日以开盘价成交”模式,在该模式下,Strategy 中的交易逻辑不再写在 next() 方法里,而是写在特定的 next_open()、nextstart_open() 、prenext_open() 函数中,具体设置可参考如下案例:

方式1:bt.Cerebro(cheat_on_open=True);
方式2:cerebro.broker.set_coo(True);
方式3:BackBroker(coo=True)。

Cheat-On-Close

Cheat-On-Close 是“当日下单,当日以收盘价成交”模式,在该模式下,Strategy 中的交易逻辑仍写在 next() 中,具体设置如下:

方式1:cerebro.broker.set_coc(True);
方式2:BackBroker(coc=True)

class TestStrategy(bt.Strategy):
    ......
    def next(self):
        # 取消之前未执行的订单
        if self.order:
            self.cancel(self.order)
        # 检查是否有持仓
        if not self.position:
            # 10日均线上穿5日均线,买入
            if self.crossover > 0:
                print('{} Send Buy, open {}'.format(self.data.datetime.date(),self.data.open[0]))
                self.order = self.buy(size=100) # 以下一日开盘价买入100股
        # # 10日均线下穿5日均线,卖出
        elif self.crossover < 0:
            self.order = self.close() # 平仓,以下一日开盘价卖出
    ......

# 实例化大脑
cerebro= bt.Cerebro()
.......
# 当日下单,当日收盘价成交
cerebro.broker.set_coc(True)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神出鬼没,指的就是我!

必须花钱,数据超好

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值