qlib智能量化里的“因子分析“,“多空分析”

今天是第18篇,关于“AI智能量化,财富与成长感悟”。

 

01 关于财富自由

最近把市面上几乎能找到的财富管理相关的,成长类的书籍,都读了一遍。

古云:“书读百遍,其义自现”,这里可以改在“书读百本,其义自现”。

个中的底层逻辑大多是相通的。

这类财富自由书,一般都不讲两个东西:

一是怎么创业和经营公司,这看似最与财富相关的方向;

二是不讲职场成长,人际社交。

我把这类财富自由称之为“财富小自由”。

像马云这种大企业家,成功集天时、地利、人和,领袖气质,冒险精神,人脉资源,家族传承,客观讲不是什么书本可以教授的东西。

这种成功“一将功成万骨枯”,普通人看看就好。

另外“小自由”,这里的“自由”,一般就是指“脱离”职场,即不必为生活去工作,即“工作自由”,所以自然不会描述工作中的成长相关。

她是选择中间的一条路,储蓄与投资(理财),被动收入

从开源节流的角度,越靠节流这一端,就偏向过极简生活(FIRE运动);越是靠近开源这一端,就是价值创造,进而实现被动收入(注意不是工资收入)

当下的环境,对于普通人这条路是有可能的。

02  关于读书

海量阅读是非常有价值的。

在你迷茫没有方向,内心焦虑,苦闷的时候,大量地去读书。

书中自有答案,你经历的,很多人都经历过。

低成本地经历一下别人的人生,“太阳底下并无新鲜事”。

听书只是为了判断这本书值不值得读,如果是好书,还是建议自己读原书,至少翻翻电子书。

每个人的阅历不同,解读的角度也不一样。

03 国运与大盘

时代趋势决定了我们的基本盘。

苏东坡壮年,王安石变法,举国上下鸡飞狗跳,这就注定了他起伏的一生。

国运昌盛,则资本市场兴。

04 因子分析

qlib模型训练及回测阶段代码使用了mlfow,很多人不是那么熟悉,我把它的代码最简化拿出来,去掉mlflow框架。

with R.start(experiment_name="workflow"):
    # train
    #R.log_params(**flatten_dict(task))
    model.fit(dataset)

    # prediction
    recorder = R.get_recorder()
    sr = SignalRecord(model, dataset, recorder)
    sr.generate()

    # Signal Analysis
    sar = SigAnaRecord(recorder)
    sar.generate()

它有各种recoreder,然后统一都调用generate,里面发生了什么呢?

其实很简单。

SiganlRecord只做一件事,就是——调用训练好的模型,预测数据集,并保存到pred.pkl里,同时把测试数据集的label,保存到label.pkl里。

自己的代码如下:(这样看起来是不是就很直观了)

model.fit(dataset)
pred = model.predict(dataset)
if isinstance(pred, pd.Series):
    print('is serice...')
    pred = pred.to_frame('score')
pprint(pred)
pred.to_pickle('output/pred.pkl')

# 取label并保存
label = dataset.prepare(segments="test", col_set="label", data_key=DataHandlerLP.DK_R)
pprint(label)
label.to_pickle('output/label.pkl')

SigAnaRecord也一样,它读出pred.pkl和label.pkl后,计算IC值,就是预测值与label之间的“相关性”,从简化的角度,这里我们可以不save和load直接计算也行。

def calc_ic(pred: pd.Series, label: pd.Series, date_col="datetime", dropna=False) -> (pd.Series, pd.Series):
    df = pd.DataFrame({"pred": pred, "label": label})
    ic = df.groupby(date_col).apply(lambda df: df["pred"].corr(df["label"]))
    ric = df.groupby(date_col).apply(lambda df: df["pred"].corr(df["label"], method="spearman"))
    if dropna:
        return ic.dropna(), ric.dropna()
    else:
        return ic, ric

calc_ic这个函数,按日期groupby后计算 pred 和 label 这两个序列的 pearson相关性和spearman相关性。

二者其实是统计学的基本概念,pearson就是传统的相关系数,而spearman是“秩相关系数”,金融上叫IC与RIC。

我们经常听金融工程的同学说“因子的IC值”,其实就是这个因子的预测结果与实际结果的相关性

RIC大家可能陌生一点,叫“斯皮尔曼等级(秩)相关性”。

看下面这个例子,“考试成绩与产量”的相关系数=0.6+, 但若先转换为等级后,再算相关系数=1。

了解了原理,计算就比较简单了:

all = pd.concat([pred, label], axis=1)
pprint(all)

ic = all.groupby('datetime').apply(lambda df: df["score"].corr(df["LABEL0"]))
ric = all.groupby('datetime').apply(lambda df: df["score"].corr(df["LABEL0"], method="spearman"))

05 多空分析

def calc_long_short_return(
    pred: pd.Series,
    label: pd.Series,
    date_col: str = "datetime",
    quantile: float = 0.2,
    dropna: bool = False,
) -> Tuple[pd.Series, pd.Series]:
    
    df = pd.DataFrame({"pred": pred, "label": label})
    if dropna:
        df.dropna(inplace=True)
    group = df.groupby(level=date_col)

    def N(x):
        return int(len(x) * quantile)

    r_long = group.apply(lambda x: x.nlargest(N(x), columns="pred").label.mean())
    r_short = group.apply(lambda x: x.nsmallest(N(x), columns="pred").label.mean())
    r_avg = group.label.mean()
    return (r_long - r_short) / 2, r_avg

用自己的代码实现类似:

group = all.groupby(level='datetime')

def N(x):
    return int(len(x) * 0.2)

# 打分的前20%, 与后20% 对应的 真实收益率
r_long = group.apply(lambda x: x.nlargest(N(x), columns="score").LABEL0.mean())
r_short = group.apply(lambda x: x.nsmallest(N(x), columns="score").LABEL0.mean())
r_avg = group.LABEL0.mean()

print((r_long-r_short)/2, r_avg)

这里r_avg就是当天所有股票的收益率的平均;

r_long是买入前20%高分的股票的平均收益;

r_short是买入后20%(低分)的股票的收益;

(r_long-r_short)/2即 最好组与最差组的 收益之差/2。

最后对根据序列计算年化的 收益率与夏普比(注意这个不是回测结果,而是模型的因子分析):

print("多空年化收益", r_long_short.mean() * 252)
print("多空夏普比", r_long_short.mean() / r_long_short.std() * 252 ** 0.5)
print("平均年化收益", r_avg.mean() * 252)
print("平均年化夏普比", r_avg.mean() / r_avg.std() * 252 ** 0.5)

上面这些如下:

sar = SigAnaRecord(recorder)

sar.generate()

两行代码做的事情。

06 回测

基于实验的流程里的代码:

par = PortAnaRecord(recorder, port_analysis_config, "day")
par.generate()

调用的是backtest_loop

单独初始化:

def get_strategy(pred_score, topK=50, n_drop=5, ):
    STRATEGY_CONFIG = {
        "topk": 50,
        "n_drop": 5,
        "signal": pred_score  # pred_score,
    }
    strategy_obj = TopkDropoutStrategy(**STRATEGY_CONFIG)
    return strategy_obj


def get_executor():
    EXECUTOR_CONFIG = {
        "time_per_step": "day",
        "generate_portfolio_metrics": True,
    }
    executor_obj = executor.SimulatorExecutor(**EXECUTOR_CONFIG)
    return executor_obj

调用回测函数得到结果:

def do_backtest(executor, strategy, benchmark='SH000300', start_time="2017-01-01", end_time="2022-08-01"):
    backtest_config = {
        "start_time": start_time,
        "end_time": end_time,
        "account": 100000000,
        "benchmark": benchmark,
        "exchange_kwargs": {
            "freq": 'day',
            "limit_threshold": 0.095,
            "deal_price": "close",
            "open_cost": 0.0005,
            "close_cost": 0.0015,
            "min_cost": 5,
        },
    }
    portfolio_metric_dict, indicator_dict = backtest(executor=executor, strategy=strategy, **backtest_config)
    analysis_freq = "{0}{1}".format(*Freq.parse('day'))
    pprint(portfolio_metric_dict, indicator_dict)
    # backtest info
    report_normal_df, positions_normal = portfolio_metric_dict.get(analysis_freq)

明天可以分析回测结果,以及对结果进行可视化。

小结:

把qlib原先的yaml驱动的全流程,基本上拆开了,由于它本身就是松散结构,这个工作不算复杂。

核心是理清数据结构(多为pandas的dataframe)。

与传统策略相比,多了一步因子的 IC分析与多空分析。

就是统计学上基本概念,不复杂。

  • 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、付费专栏及课程。

余额充值