qlib平台实现可转债“双低”策略

目录

“双低”策略

低价格

目录

“双低”策略

低价格

低溢价率

策略设计:

 回测

但我们偏偏要计算横截面的排名。


低溢价率

如果一个投资标的,可以估值,那太好了。

可转债下有底,上不封顶。

这个底就可以估。

可转债本身是债,但向上走,又是正股的看涨期权。

“双低”策略

这里的双低,是指两个指标,一是价格,二是溢价率。

低价格

可转债一般100块1张,就好比是一个100块的借条,到期往本付息可能是103块。

都着价格的波动,这个借条可能低于100(市场低迷的时候),也可能高于130,甚至更高。

都买入的角度 ,当然价格越便宜越安全。

低溢价率

每张转债里内含几份股票是规定好的,这个张数N = 100/X,X是转股价。比如转股价是20, 那这100块就可以转成5股,或者说“约定好,未来以20块的价格卖入股票”。

而正股的价格每天都在波动,可能高于20,也可能低于20,这个值=Y

转股价值 = N*Y = 100/X * Y,就是若转为股票,这个借条值多少线。

溢价率 = 转债价格/转股价值 -1 。

这么理解:转股价值,有点像这个借条内含的价值,就是我可以转成股票,若转了,值多少钱,比如是108块。但现在这个借条实际标价是110,就是有溢价的, 110/108 -1。你说会不会溢价为负,有可以的,石油价格都可能为负,金融市场一切皆有可能。

溢价率当然越小越好,负的更好。就是说,如果你买了,如果今天就转股,你都是赚的。

策略设计:

1、选择距离可转债到期日期大于100天。

2、计算溢价率并标准化

3、价格排名与溢价率排期按5:5权重,得到综合排名。

4、最后选择前10名构建持仓。

实际上,这是“轮动”策略,就是说并没有说出现在“卖出”的强逻辑,而是“只持有”最划算的,或者评份最高的。

“轮动”是一个很好的思想。我只持有当下“最好的N支”。Qlib里的回测模型默认就是topK。

无论你是什么因子集,最终反正算出一个score,选择score高的集合就好。

数据准备:

可转债的价格是现成的。

需要每天的正股价。

这里有几种实现,把正股价作为转债的一个序列,生成到一个数据库里。

一是看qlib是否支持“跨库计算”,第一种肯定可以,但比较麻烦。第二种是今天调研之重点。

跨库计算没有找到计算的方式。

所以,把正股的收盘价接过下,然后concat,两个dataframe的index都设置为日期,然后使用pandas的concat,axis=1,join='inner'这样模向连接就可以了。

 python qlib-main/scripts/dump_bin.py dump_all --csv_path  ./cb_quotes --qlib_dir ./data/cb_data --include_fields open,close,high,low,volume,factor,change_price,stock_close  --symbol_field_name ts_code  --date_field_name trade_date     

比传统的OHLCV,多了三例:

factor,change_price,stock_close, 复权因子,转股价,正股价。

 回测

qlib的回测与传统量化框架如pyalgotrade和backtrader这种事件驱动不同。

传统框架是事件驱动,每天根据信号写自己的买入或卖出逻辑。

而qlib是给列表打分。

每天每个instrment都有一个分,前K个最高份的持仓(这里有一个逻辑,就是排序,但有可能所有都不取,比如动量策略,要求动量最大的N个,但动量必须为正!)。可以考虑一个变通的方法,就是把动量为负的直接过滤掉,不出现在pre_score里。

这是典型的”轮动“逻辑。

这个框架不太适合: 单instrument的择时。

数据加载及因子计算:

时间序列很多计算都直接可以使用表达式,比如MACD这种。

 provider_uri = "./data/cb_data"  # "./data/cn_data"  # target_dir
    qlib.init(provider_uri=provider_uri, region=REG_CN)

    instruments = D.instruments(market='all')
    fields = ['$close', '$close/(100/$change_price*$stock_close) -1']

    #df = D.features(instruments, fields, start_time='2010-01-01', end_time='2018-12-31', freq='day')
    #print(df)

    fields = ["$close*0.5 + $close/(100/$change_price*$stock_close) -1", ]
    names = ['score']
    data_loader_config = {

        "feature": (fields, names),
        #"label": (labels, label_names)
    }
    data_loader = QlibDataLoader(config=data_loader_config)
    df = data_loader.load(instruments='all', start_time='2010-01-01', end_time='2017-12-31')
    print(df)

但我们偏偏要计算横截面的排名。

我们看一下当前支持的指标:

 ChangeInstrument,
    Rolling,
    Ref,
    Max,
    Min,
    Sum,
    Mean,
    Std,
    Var,
    Skew,
    Kurt,
    Med,
    Mad,
    Slope,
    Rsquare,
    Resi,
    Rank,
    Quantile,
    Count,
    EMA,
    WMA,
    Corr,
    Cov,
    Delta,
    Abs,
    Sign,
    Log,
    Power,
    Add,
    Sub,
    Mul,
    Div,
    Greater,
    Less,
    And,
    Or,
    Not,
    Gt,
    Ge,
    Lt,
    Le,
    Eq,
    Ne,
    Mask,
    IdxMax,
    IdxMin,
    If,
    Feature,
    PFeature,
] + [TResample]
class Rank(Rolling):
#这里的排名是针对时间序列,比如Rank($close,20),过去20天,当前收盘价处于过去20天的分位。
instruments = D.instruments(market='all')
    fields = ['$close', '$close/(100/$change_price*$stock_close) -1']

    #df = D.features(instruments, fields, start_time='2010-01-01', end_time='2018-12-31', freq='day')
    #print(df)

    fields = ["$close","$close/(100/$change_price*$stock_close) -1"]
    names = ['收盘价','溢价率']
    data_loader_config = {

        "feature": (fields, names),
        #"label": (labels, label_names)
    }
    data_loader = QlibDataLoader(config=data_loader_config)
    df = data_loader.load(instruments='all', start_time='2020-01-01', end_time='2020-01-10')
    df = df['feature']
    #print(df.columns)
    df['rank_close'] = df.groupby(['datetime'],as_index=False)['收盘价'].rank(ascending=False)
    df['rank_rate'] = df.groupby(['datetime'], as_index=False)['溢价率'].rank(ascending=False)
    df['score'] = df['rank_rate'] * 0.5 + df['rank_close']*0.5

    df = df[['score']]
    print(df.head(50))

 按天把每支转债的score计算出来的,就是价格排期,以及溢价率排名的综合排名。

这两年年化17%,还没有参数调优:

 

data_loader_config = {

        "feature": (fields, names),
        # "label": (labels, label_names)
    }

    start_date = '2021-01-01'
    end_date = '2022-08-01'
    data_loader = QlibDataLoader(config=data_loader_config)
    df = data_loader.load(instruments='all', start_time=start_date, end_time=end_date)
    df = df['feature']
    # print(df.columns)
    df['rank_close'] = df.groupby(['datetime'], as_index=False)['收盘价'].rank(ascending=False)
    df['rank_rate'] = df.groupby(['datetime'], as_index=False)['溢价率'].rank(ascending=False)
    df['score'] = df['rank_rate'] * 0.5 + df['rank_close'] * 0.5

    df = df[['score']]
    print(df.head(50))

    CSI300_BENCH = "113013.SH"
    STRATEGY_CONFIG = {
        "topk": 10,
        "n_drop": 1,
        # pred_score, pd.Series
        "signal": df,
    }

    strategy_obj = TopkDropoutStrategy(**STRATEGY_CONFIG)
    report_normal, positions_normal = backtest_daily(
        start_time=start_date, end_time=end_date, strategy=strategy_obj, benchmark=CSI300_BENCH
    )
    analysis = dict()
    # default frequency will be daily (i.e. "day")
    analysis["excess_return_without_cost"] = risk_analysis(report_normal["return"]) #- report_normal["bench"])
    analysis["excess_return_with_cost"] = risk_analysis(
        report_normal["return"] # - report_normal["bench"]
        - report_normal["cost"]
    )

    analysis_df = pd.concat(analysis)  # type: pd.DataFrame
    pprint(analysis_df)

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

AI量化投资实验室

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

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

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

打赏作者

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

抵扣说明:

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

余额充值