backtrader策略库:基于bt外调仓表的多因子选股策略

扫地僧教程中的多股操作定期再平衡案例,是一种多因子选股策略,它是在backtrader内部,定期选股,确定各股权重(采用的是等权法)。

另外一个思路是,将选股和确定权重的逻辑在backtrader外部做,形成类似如下的调仓表,该表按月调仓(再平衡),并给出了各选定股票的权重:

然后再将调仓表传给 Backtrader ,让 Backtrader 读取调仓表上的信息,进行策略回测。调仓表上存的选股结果,其实就是每个调仓日应该持有哪些股票以及对应的持仓权重。

以下给出策略源码,核心思想是:

  • 在 __init__() 中一次性读入调仓表,从调仓表中提取出调仓日期;
  • 在 next() 中判断当前回测时间点是否为调仓日:如果是调仓日,对被剔除的标的进行平仓,买入新增的标的;如果是非调仓日,不触发下单操作。

 

import backtrader as bt
import pandas as pd
import datetime

# 回测策略
class StockSelectStrategy(bt.Strategy):
    '''多因子选股 - 基于调仓表'''
    def __init__(self):
        # 读取调仓表,表结构如下所示:
        #       trade_date  sec_code    weight
        # 0     2019-01-31  000006.SZ   0.007282
        # 1     2019-01-31  000008.SZ   0.009783
        # ...   ...         ...         ...
        # 2494  2021-01-28  688088.SH   0.007600
        self.buy_stock = pd.read_csv("./data/trade_info.csv", parse_dates=['trade_date']) 
        # 读取调仓日期,即每月的最后一个交易日,回测时,会在这一天下单,然后在下一个交易日,以开盘价买入
        self.trade_dates = pd.to_datetime(self.buy_stock['trade_date'].unique()).tolist()
        self.order_list = []  # 记录以往订单,方便调仓日对未完成订单做处理
        self.buy_stocks_pre = [] # 记录上一期持仓
    

    def log(self, txt, dt=None):
        ''' 策略日志打印函数'''
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))

    def next(self):
        dt = self.datas[0].datetime.date(0) # 获取当前的回测时间点
        # 如果是调仓日,则进行调仓操作
        if dt in self.trade_dates:
            print("--------------{} 为调仓日----------".format(dt))
            # 在调仓之前,取消之前所下的没成交也未到期的订单
            if len(self.order_list) > 0:
                for od in self.order_list:
                    self.cancel(od) # 如果订单未完成,则撤销订单
                self.order_list = []  #重置订单列表
            # 提取当前调仓日的持仓列表
            buy_stocks_data = self.buy_stock.query(f"trade_date=='{dt}'")
            long_list = buy_stocks_data['sec_code'].tolist()
            print('long_list', long_list)  # 打印持仓列表
            # 对现有持仓中,调仓后不再继续持有的股票进行卖出平仓
            sell_stock = [i for i in self.buy_stocks_pre if i not in long_list]
            print('sell_stock', sell_stock) # 打印平仓列表
            if len(sell_stock) > 0:
                print("-----------对不再持有的股票进行平仓--------------")
                for stock in sell_stock:
                    data = self.getdatabyname(stock)
                    if self.getposition(data).size > 0 :
                        od = self.close(data=data)  
                        self.order_list.append(od) # 记录卖出订单
            # 买入此次调仓的股票:多退少补原则
            print("-----------买入此次调仓期的股票--------------")
            for stock in long_list:
                w = buy_stocks_data.query(f"sec_code=='{stock}'")['weight'].iloc[0] # 提取持仓权重
                data = self.getdatabyname(stock)
                order = self.order_target_percent(data=data, target=w*0.95) # 为减少可用资金不足的情况,留 5% 的现金做备用
                self.order_list.append(order)
       
            self.buy_stocks_pre = long_list  # 保存此次调仓的股票列表
        
    def notify_order(self, order):
        # 未被处理的订单
        if order.status in [order.Submitted, order.Accepted]:
            return
        # 已经处理的订单
        if order.status in [order.Completed, order.Canceled, order.Margin]:
            if order.isbuy():
                self.log(
                        'BUY EXECUTED, ref:%.0f, Price: %.2f, Cost: %.2f, Comm %.2f, Size: %.2f, Stock: %s' %
                        (order.ref, # 订单编号
                         order.executed.price, # 成交价
                         order.executed.value, # 成交额
                         order.executed.comm, # 佣金
                         order.executed.size, # 成交量
                         order.data._name))  # 股票名称
            else:  # Sell
                self.log('SELL EXECUTED, ref:%.0f, Price: %.2f, Cost: %.2f, Comm %.2f, Size: %.2f, Stock: %s' %
                            (order.ref,
                             order.executed.price,
                             order.executed.value,
                             order.executed.comm,
                             order.executed.size,
                             order.data._name))
        

# 实例化 cerebro
cerebro = bt.Cerebro()
# 读取行情数据 
daily_price = pd.read_csv("./data/daily_price.csv", parse_dates=['datetime'])
daily_price = daily_price.set_index(['datetime'])  # 将datetime设置成index
# 按股票代码,依次循环传入数据
for stock in daily_price['sec_code'].unique():
    # 日期对齐
    data = pd.DataFrame(index=daily_price.index.unique()) # 获取回测区间内所有交易日
    df = daily_price.query(f"sec_code=='{stock}'")[['open','high','low','close','volume','openinterest']]
    data_ = pd.merge(data, df, left_index=True, right_index=True, how='left')
    # 缺失值处理:日期对齐时会使得有些交易日的数据为空,所以需要对缺失数据进行填充
    data_.loc[:,['volume','openinterest']] = data_.loc[:,['volume','openinterest']].fillna(0)
    data_.loc[:,['open','high','low','close']] = data_.loc[:,['open','high','low','close']].fillna(method='pad')
    data_.loc[:,['open','high','low','close']] = data_.loc[:,['open','high','low','close']].fillna(0)
    # 导入数据
    datafeed = bt.feeds.PandasData(dataname=data_, 
                                   fromdate=datetime.datetime(2019,1,2), 
                                   todate=datetime.datetime(2021,1,28))
    cerebro.adddata(datafeed, name=stock) # 通过 name 实现数据集与股票的一一对应
    print(f"{stock} Done !") 
# 初始资金 100,000,000    
cerebro.broker.setcash(100000000.0) 
# 佣金,双边各 0.0003
cerebro.broker.setcommission(commission=0.0003) 
# 滑点:双边各 0.0001
cerebro.broker.set_slippage_perc(perc=0.0001) 
# 将编写的策略添加给大脑,别忘了 !
cerebro.addstrategy(StockSelectStrategy)
# 回测时需要添加 PyFolio 分析器
cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio')
result = cerebro.run()
# 借助 pyfolio 进一步做回测结果分析

pyfolio = result[0].analyzers.pyfolio  # 注意:后面不要调用 .get_analysis() 方法
# 或者是 result[0].analyzers.getbyname('pyfolio')
returns, positions, transactions, gross_lev = pyfolio.get_pf_items()

import pyfolio as pf
pf.create_full_tear_sheet(returns)

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Unity是一款非常流行的跨平台游戏引擎,可以用于开发各种类型的游戏应用。在Unity中,我们可以通过外调txt(文本)文件来读取和写入游戏内容。 要实现Unity外调txt,首先需要创建一个txt文件,并将其放置在Unity项目的特定位置,例如Assets文件夹中。然后,可以使用C#脚本来读取和写入该txt文件。 对于读取txt文件,可以使用StreamReader来打开文件,并使用ReadLine函数逐行读取文本内容。以下是一个简单的示例代码: ``` using System.IO; using UnityEngine; public class TxtFileReader : MonoBehaviour { void Start() { string filePath = Application.dataPath + "/file.txt"; // txt文件路径 StreamReader reader = new StreamReader(filePath); string line = reader.ReadLine(); // 读取一行文本内容 while (line != null) { Debug.Log(line); // 输出文本内容 line = reader.ReadLine(); // 继续读取下一行 } reader.Close(); // 关闭StreamReader } } ``` 这样,我们就可以在Unity中读取txt文件的内容,并在控制台中输出。需要注意的是,txt文件的路径应使用Application.dataPath来获取,以确保路径的正确性。 如果要写入txt文件,可以使用StreamWriter来打开文件,并使用WriteLine函数逐行写入文本内容。以下是一个简单的示例代码: ``` using System.IO; using UnityEngine; public class TxtFileWriter : MonoBehaviour { void Start() { string filePath = Application.dataPath + "/file.txt"; // txt文件路径 StreamWriter writer = new StreamWriter(filePath); writer.WriteLine("Hello, Unity!"); // 写入一行文本内容 writer.WriteLine("This is a txt file."); writer.Close(); // 关闭StreamWriter } } ``` 这样,我们就可以在Unity中写入txt文件的内容。运行该代码后,会在指定路径下生成txt文件,并写入指定的文本内容。 通过这种方式,我们可以方便地进行Unity外调txt文件的读取和写入操作,以实现游戏的文本数据管理和交互等功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

扫地僧量化

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值