如何使用backtrader进行资金费率策略回测
作者:yunjinqi   类别:    日期:2024-12-24 10:34:41    阅读:22 次   消耗积分:0 分    

如何使用backtrader进行资金费率策略回测

1. 准备工作

需要获取交易所的1小时价格数据,标记价格数据,以及资金费率数据,并按照币对进行合成,每个币对合成一个csv文件。

参考格式如下:

datetimeopenhighlowclosevolumequote_volumecounttaker_buy_volumetaker_buy_quote_volumemark_price_openmark_price_closecurrent_funding_rate
1/1/2020 0:007189.437190.527170.157171.552449.04917576424.443688996.1987149370.7647195.3658197176.600761-0.00012359
1/1/2020 1:007171.4372257171.17210.243865.03827838046.0166352340.87816860294.057176.6007927213.4597240
1/1/2020 2:007210.387239.37206.467237.993228.36523324810.4151201774.14512818470.647213.4597557240.398890
1/1/2020 3:007237.417239.7472157221.652513.30718161821.8641431245.0658996218.6887240.5216397224.7520270



2. 导入backtrader

需要使用我维护的backtrader版本,增加了资金费率的支持。如果用官方版本,需要增加一个计算资金费率的类。

github: 按照这个方法安装backtrader:

gitee: 按照这个方法安装backtrader

或者自行添加下面的资金费率类到comminfo.py中:

class ComminfoFundingRate(CommInfoBase):
    # 实现一个数字货币的资金费率类
    params = (
        ('commission', 0.0), ('mult', 1.0), ('margin', None),
        ('stocklike', False),
        ('commtype', CommInfoBase.COMM_PERC),
        ('percabs', True)
    )

    def __init__(self):
        super(ComminfoFundingRate, self).__init__()

    def _getcommission(self, size, price, pseudoexec):
        total_commission = abs(size) * price * self.p.mult * self.p.commission
        # print("total_commission", total_commission)
        return total_commission

    def get_margin(self, price):
        return price * self.p.mult * self.p.margin

    # 计算利息费用,这里面涉及到一些简化
    def get_credit_interest(self, data, pos, dt):
        """计算币安合约的资金费率,先暂时使用价格代替标记价格,后续再优化"""
        # 仓位及价格
        size, price = pos.size, pos.price
        # 计算资金费率的时候,使用下个bar的开盘价会更精准一些,实际上两者差距应该不大。
        try:
            current_price = data.mark_price_open[1]
        except IndexError:
            current_price = data.mark_price_close[0]
        position_value = size * current_price * self.p.mult
        # 得到当前的资金费率
        try:
            funding_rate = data.current_funding_rate[1]
        except IndexError:
            funding_rate = 0.0
        # 如果资金费率为正,则做空的时候会得到资金费率,如果资金费率为负,则做多的时候会得到资金费率
        # total_funding_rate = -1 * funding_rate * position_value
        # 但是broker里面计算的时候是减去这个值,所以需要取相反数
        total_funding_rate = funding_rate * position_value
        # if total_funding_rate != 0:
        #     print(bt.num2date(data.datetime[0]), data._name, "get funding ", total_funding_rate)
        return total_funding_rate

3. 编写策略

策略主要代码如下:

import backtrader as bt
class FundingRateStrategy(bt.Strategy):
    params = (('period', 30),
              ('hold_percent', 0.2)
              )

    def log(self, txt, dt=None):
        """Logging function fot this strategy"""
        dt = dt or bt.num2date(self.datas[0].datetime[0])
        print('{}, {}'.format(dt.isoformat(), txt))

    def __init__(self):
        # Keep a reference to the "close" line in the data[0] dataseries
        self.bar_num = 0
        # 保存现有持仓的币对
        self.position_dict = {}
        # 当前有交易的币对
        self.crypto_dict = {}
        # 计算平均的资金费率
        self.funding_rate_dict = {i._name: bt.indicators.SMA(i.current_funding_rate, period=self.p.period)
                                  for i in self.datas}

    def prenext(self):

        self.next()

    def next(self):
        # 记录策略经历了多少个bar
        self.bar_num += 1
        # 总的账户价值
        total_value = self.broker.get_value()
        # 当前时间, 把比特币数据放第一个,读取比特币的时间,币对等数据最好用指数
        current_datetime = bt.num2date(self.datas[0].datetime[0])
        # 第一个数据是指数,校正时间使用,不能用于交易
        # 循环所有的币对,计算币对的数目
        for data in self.datas[1:]:
            # 上市不满一年的币对,忽略不计
            if len(data) >= 252:
                data_datetime = bt.num2date(data.datetime[0])
                # 如果两个日期相等,说明当前币对在交易
                if current_datetime == data_datetime:
                    crypto_name = data._name
                    if crypto_name not in self.crypto_dict:
                        self.crypto_dict[crypto_name] = 1
        # 如果入选的币对小于20支,不使用策略
        if len(self.crypto_dict) < 20:
            return
        total_target_crypto_num = len(self.crypto_dict)
        # 现在持仓的币对数目
        total_holding_crypto_num = len(self.position_dict)
        # 计算理论上的手数
        now_value = total_value / int(total_target_crypto_num * self.p.hold_percent * 2)
        # 如果今天是调仓日
        if self.bar_num % self.p.period == 0:

            # 循环币对,平掉所有的币对,计算现在可以交易的币对的累计收益率
            result = []
            for crypto_name in self.crypto_dict:
                data = self.getdatabyname(crypto_name)
                data_datetime = bt.num2date(data.datetime[0])
                size = self.getposition(data).size
                # 如果有仓位
                if size != 0:
                    self.close(data)
                    if data._name in self.position_dict:
                        self.position_dict.pop(data._name)

                # 已经下单,但是订单没有成交
                if data._name in self.position_dict and size == 0:
                    order = self.position_dict[data._name]
                    self.cancel(order)
                    self.position_dict.pop(data._name)
                    # 如果两个日期相等,说明币对在交易,就计算收益率,进行排序
                if current_datetime == data_datetime:
                    # 获取币对的资金费率
                    funding_rate = self.funding_rate_dict[data._name][0]
                    result.append([data, funding_rate])
            # 根据计算出来的累计收益率进行排序,选出资金费率靠前的币对做空,靠后的币对做多
            new_result = sorted(result, key=lambda x: x[1])
            num = int(self.p.hold_percent * total_target_crypto_num)
            buy_list = new_result[:num]
            sell_list = new_result[-num:]
            # 根据计算出来的信号,买卖相应的币对
            for data, _ in buy_list:
                lots = now_value / data.close[0]
                order = self.buy(data, size=lots)
                self.position_dict[data._name] = order
            for data, _ in sell_list:
                lots = now_value / data.close[0]
                order = self.sell(data, size=lots)
                self.position_dict[data._name] = order

    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            # order被提交和接受
            return
        if order.status == order.Rejected:
            self.log(f"order is rejected : order_ref:{order.ref}  order_info:{order.info}")
        if order.status == order.Margin:
            self.log(f"order need more margin : order_ref:{order.ref}  order_info:{order.info}")
        if order.status == order.Cancelled:
            self.log(f"order is cancelled : order_ref:{order.ref}  order_info:{order.info}")
        if order.status == order.Partial:
            self.log(f"order is partial : order_ref:{order.ref}  order_info:{order.info}")
        # Check if an order has been completed
        # Attention: broker could reject order if not enougth cash
        if order.status == order.Completed:
            if order.isbuy():
                self.log(f"{order.data._name} buy order : "
                         f"price : {round(order.executed.price, 6)} , "
                         f"size : {round(order.executed.size, 6)} , "
                         f"margin : {round(order.executed.value, 6)} , "
                         f"cost : {round(order.executed.comm, 6)}")

            else:  # Sell
                self.log(f"{order.data._name} sell order : "
                         f"price : {round(order.executed.price, 6)} , "
                         f"size : {round(order.executed.size, 6)} , "
                         f"margin : {round(order.executed.value, 6)} , "
                         f"cost : {round(order.executed.comm, 6)}")

    def notify_trade(self, trade):
        # 一个trade结束的时候输出信息
        if trade.isclosed:
            self.log(f'closed symbol is : {trade.getdataname()} , '
                     f'total_profit : {round(trade.pnl, 6)} , '
                     f'net_profit : {round(trade.pnlcomm, 6)}')
        if trade.isopen:
            self.log(f'open symbol is : {trade.getdataname()} , price : {trade.price} ')

    def stop(self):

        pass

4. 运行策略

策略运行之后,结果如下:


Screenshot 2024-12-24 at 9.55.42 AM.png


5. 总结

本文介绍了如何使用backtrader进行资金费率策略回测, 主要是使用了资金费率的计算方法, 以及如何使用自定义的资金费率类。

从回测结果来看,2023年之后资金费率策略表现并没有前几年那么好, 可能是因为使用资金费率套利策略的人太多了,导致资金费率策略逐渐失效。


数据和全部代码,详见付费文章:https://yunjinqi.blog.csdn.net/article/details/144687400


版权所有,转载本站文章请注明出处:云子量化, http://www.woniunote.com/article/407
上一篇:alphalens因子分析包更新啦,python3.11适用
下一篇:如何使用backtrader进行资金费率策略回测