期货和现货补偿#

发布版本``1.9.32.116``为 社区 <https://community.backtrader.com/> _ 提出的一个有趣的用例添加了支持

  • 以**实物交割**方式进行期货交易

  • 使用指标进行交易决策

  • 如果需要,在现货价格上进行操作来平仓,有效地取消实物交割,无论是收货还是交付(并希望获得利润)

    期货合约在进行现货价格操作的同一天到期

这意味着:

  • 平台要接收来自两个不同资产的数据点

  • 平台必须以某种方式理解这两个资产的关联性,并且操作现货价格将关闭在期货上打开的仓位

    实际上,期货合约没有关闭,只是实物交割被*补偿*了

利用这种*补偿*的概念,``backtrader``增加了一种让用户向平台传达在一个数据源上的操作将对另一个数据源产生补偿效果的方式。 使用模式如下:

import backtrader as bt

cerebro = bt.Cerebro()   data0 = bt.feeds.MyFavouriteDataFeed(dataname='futurename')
cerebro.adddata(data0)

data1 = bt.feeds.MyFavouriteDataFeed(dataname='spotname')
data1.compensate(data0)  # 让系统知道对 data1 的操作会影响到 data0
cerebro.adddata(data1)

...

cerebro.run()

将它们整合在一起#

一个示例总是胜过千言万语,让我们把所有的部分都放在一起。

  • 使用 backtrader 源代码中的标准示例数据源之一。这是期货数据源。

  • 通过重用相同的数据源并添加一个过滤器,在价格上随机移动几个点,形成价差,以模拟类似但不同的价格。就像这样简单:

    # The filter which changes the close price
    def close_changer(data, *args, **kwargs):
        data.close[0] += 50.0 * random.randint(-1, 1)
        return False  # length of stream is unchanged
    
  • 在同一个坐标轴上绘图将混合默认的包含的 BuyObserver 标记,因此需要禁用标准观察者,并手动重新添加以使用不同的数据标记进行绘图。

  • 仓位将在随机时间进入并在 10 天后退出。这个不匹配未来到期期间,但这只是为了将功能放在那里,而不是检查交易日历。

注意

一个包括在未来到期当天执行现货价格的模拟会需要激活``cheat-on-close``以确保订单在未来到期时执行。在这个样本中不需要这样做,因为到期时间是随机选择的。

  • 注意这个策略

    • “买入”操作在“data0”上执行

    • “卖出”操作在“data1”上执行

    class St(bt.Strategy):
        def __init__(self):
            bt.obs.BuySell(self.data0, barplot=True)  # done here for
            BuySellArrows(self.data1, barplot=True)  # different markers per data
    
        def next(self):
            if not self.position:
                if random.randint(0, 1):
                    self.buy(data=self.data0)
                    self.entered = len(self)
    
            else:  # in the market
                if (len(self) - self.entered) >= 10:
                    self.sell(data=self.data1)
    

执行结果:

$ ./future-spot.py –no-comp

这是图形输出。

而且它可以工作:- buy 操作用一个指向上方的绿色三角形来标识,图例告诉我们它们属于 data0,正如预期的那样。

  • sell 操作用一个指向下方的箭头来标识,图例告诉我们它们属于 data1,正如预期的那样。

  • 即使使用 data0 开仓并使用 data1 平仓,也能够实现交易的关闭效果(在现实中是为了避免通过*期货*物理交割所购买的商品)。

如果没有*补偿*措施,谁知道会发生什么呢?让我们试一下:

$ ./future-spot.py –no-comp

输出结果如下:

很明显这样做是彻底失败的:

  • 逻辑期望 data0 的持仓操作由 data1 进行平仓,只有在市场上不存在持仓时才在 data0 开仓

  • 但是 补偿 已经被禁用了,所以初始的 data0 操作(绿色三角形)从未被平仓,因此无法进行其他操作,而 data1 的空头持仓开始累积。使用示例


$ ./future-spot.py --help
用法:future-spot.py [-h] [--no-comp]

补偿示例

可选参数:
  -h, --help  显示帮助信息并退出
  --no-comp

示例代码#

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import argparse
import random
import backtrader as bt


# The filter which changes the close price
def close_changer(data, *args, **kwargs):
    data.close[0] += 50.0 * random.randint(-1, 1)
    return False  # length of stream is unchanged


# override the standard markers
class BuySellArrows(bt.observers.BuySell):
    plotlines = dict(buy=dict(marker='$\u21E7$', markersize=12.0),
                     sell=dict(marker='$\u21E9$', markersize=12.0))


class St(bt.Strategy):
    def __init__(self):
        bt.obs.BuySell(self.data0, barplot=True)  # done here for
        BuySellArrows(self.data1, barplot=True)  # different markers per data

    def next(self):
        if not self.position:
            if random.randint(0, 1):
                self.buy(data=self.data0)
                self.entered = len(self)

        else:  # in the market
            if (len(self) - self.entered) >= 10:
                self.sell(data=self.data1)


def runstrat(args=None):
    args = parse_args(args)
    cerebro = bt.Cerebro()

    dataname = '../../datas/2006-day-001.txt'  # data feed

    data0 = bt.feeds.BacktraderCSVData(dataname=dataname, name='data0')
    cerebro.adddata(data0)

    data1 = bt.feeds.BacktraderCSVData(dataname=dataname, name='data1')
    data1.addfilter(close_changer)
    if not args.no_comp:
        data1.compensate(data0)
    data1.plotinfo.plotmaster = data0
    cerebro.adddata(data1)

    cerebro.addstrategy(St)  # sample strategy

    cerebro.addobserver(bt.obs.Broker)  # removed below with stdstats=False
    cerebro.addobserver(bt.obs.Trades)  # removed below with stdstats=False

    cerebro.broker.set_coc(True)
    cerebro.run(stdstats=False)  # execute
    cerebro.plot(volume=False)  # and plot


def parse_args(pargs=None):
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description=('Compensation example'))

    parser.add_argument('--no-comp', required=False, action='store_true')
    return parser.parse_args(pargs)


if __name__ == '__main__':
    runstrat()