4月量化总结

挖掘时间

4.9-4.11 EUR_garp_mdl177_fnd17
4.11- IND_garp_mdl177_fnd

garp IND TOP500

fnd94_q_q_qxpspe + anl4_afv4_eps_mean
fnd94_q_q_qqec + mdl177_tw_ep
fnd94_q_q_qbi + anl4_afv4_eps_mean

fnd17_rhsfcmtt + forward_ebitda_to_enterprise_value_3

garp EUR

fnd17_ttmdivshr + anl4_sadaf_epsr_mean + close
fnd17_rhsfcf1a + ebitda_to_enterprise_value_ratio_2

尝试的模板

条件动量

4.1~4.3尝试了IND TOP500的sentiment,risk,option等数据集

1
2
3
4
5
6
clean_data_pos = ts_backfill(<data_pos/>,10);
clean_data_neg = ts_backfill(<data_neg/>,10);
condition = clean_data_pos - clean_data_neg;
target_flow = ts_backfill(imb5_score, 5) - 0.5;
raw_signal=<ts_op/>(if_else(condition > 0, target_flow, 0), <window/>);
group_rank(raw_signal, sector) - 0.5

在IND TOP500中,沿用了上次的条件动量模板,尝试了sent21和sent23。sent23没什么信号,sent21有信号但是pc巨高。

1
2
3
4
5
clean_sent_data = ts_backfill(<<sentiment_field/>, 10);
clean_vol_data = ts_backfill(<vec_op/>(<volatility_field/>), 10);
raw_signal = <combine_op/>( <ts_op/>( clean_sent_data, <d1/>), <ts_op/>(clean_vol_data, <d2/>));
signal = winsorize(raw_signal, std=3);
<group_op/>(signal, sector)

然后这个条件动量模板最理想的情况应该是vol_data中填option的数据,但是情况并不理想。没啥信号

改版条件动量

4.3~4.4在IND TOP500的analyst和institution

本来只是想将condition换成某个anl字段,然后因为returns用多了pv报警了,所以把returns换成institution的某个字段。Gemini推荐count_institutional_buyers_security与count_institutional_sellers_security相减,根据正负会产生一个做多做空的信号。然后前面就用分析师的预测,作为condition.但是最初版本是一个if_else,只会在condition>0的时候使用机构的信号,其实是需要两层if_else的嵌套,才能在做多和做空端“两条腿走路”。然后Gemini的建议是全部group_rank一下,分析师和机构就都被映射到了[-1,1],相加之后就是两个信号叠加的情况。

核心模板:

1
2
3
4
5
6
7
8
net_inst = <ts_op/>(buyers - sellers,120);
rank_inst = group_rank(net_inst, sector);

anl_change = ts_zscore(<anl_data/>, 60);
rank_anl = group_rank(anl_change, sector);
combo_signal = rank_inst + rank_anl;
combo_signal

然后一个实例

1
2
3
4
5
6
7
8
buyers = ts_backfill(count_institutional_buyers_security, 10);
sellers = ts_backfill(count_institutional_sellers_security, 10);
net_inst = ts_zscore(buyers - sellers,120);
rank_inst = group_rank(net_inst, sector);
anl_change = ts_zscore(ts_backfill(anl4_afv4_eps_high, 30), 60);
rank_anl = group_rank(anl_change, sector);
combo_signal = rank_inst + rank_anl;
combo_signal

4.4 提交了下面这个
因为我后来去看了group_rank的文档,他会重新归一化到[0,1]所以我后面有减去一个0.5

1
2
3
4
5
6
7
8
9
buyers = ts_backfill(count_institutional_buyers_security, 10);
sellers = ts_backfill(count_institutional_sellers_security, 10);
net_inst = ts_zscore(buyers - sellers,252);
rank_inst = group_rank(net_inst, sector) - 0.5;

anl_change = ts_rank(ts_backfill(anl4_ebit_low, 30), 60);
rank_anl = group_rank(anl_change, sector) - 0.5;
combo_signal = rank_inst + rank_anl;
combo_signal

anl4_afv4_median_eps
anl4_ebit_low

在GLB、EUR、USA等区域,每个区域尝试了大约500个,sharpe都在1.4左右。condition都选的是analyst,第二参数有insider也有institution,感觉效果并不好。

4.5
按照上面那份模板提交了一个。
根据Betting Against Beta (Frazzini & Pedersen, 2014, JFE)手搓了几个alpha,但是没啥信号:
这篇文章的核心思路是“做空高beta,做多低beta”
在USA和IND都尝试了一下,下面这个是在IND的一个实例sharpe在1.02,但是看了一下pc在0.8了,就没有继续挖掘。

1
2
3
4
beta = group_rank(ts_backfill(fnd17_beta, 10), sector);
beta_centered = beta - 0.6;
raw_bab = -1 * beta_centered / (abs(beta) + 0.1);
scale(raw_bab)

论坛有用户基于这个策略在USA做,打了很多补丁:
https://support.worldquantbrain.com/hc/zh-cn/community/posts/23964166630807--Alpha%E7%81%B5%E6%84%9F-Betting-Against-Beta-%E5%8F%8Dbeta%E6%8A%95%E6%B3%A8%E5%9B%A0%E5%AD%90

Gemini给我推荐了这篇文章:
The Profitability Premium: Macroeconomic Risks or Expectation Errors?

更具体的内容可以看这篇笔记:【Alpha Idea】市场犯错,不是风险补偿

4.6

交了一个由昨天的模板挖出来的因子:

1
2
3
4
5
6
7
8
raw_fund = ts_backfill(fnd94_q_q_qxpspe, 10);
raw_anl = ts_backfill(anl4_afv4_eps_mean, 10);
fund_momentum = ts_rank(raw_fund, 252);
profit_score = group_rank(fund_momentum, sector);
valuation_metric = ts_delta(close / raw_anl, 10);
value_score = -group_rank(valuation_metric, sector);
core_signal = profit_score + value_score;
group_neutralize(core_signal, sector)

Aggregate Data
Sharpe 3.24
Turnover 16.96%
Fitness 3.15
Returns 16.07%
Drawdown 4.26%
Margin 18.95‱

Risk neutralized Aggregate Data
Sharpe 2.79
Turnover 16.96%
Fitness 2.03
Returns 9.01%
Drawdown 2.92%
Margin 10.63‱

Investability constrained Aggregate Data
Sharpe 1.95
Turnover 10.33%
Fitness 1.70
Returns 9.45%
Drawdown 5.78%
Margin 18.29‱

pc在0.65,表现还是不错的。就是我尝试了一些方法,去掉close字段,但是会有稳健度错误。
然后尝试在EUR TOP2500使用这个模板,也跑出来了一些不错的因子。

4.7
今天主线是在探索EUR TOP2500的GRAP因子,但是总会有sub-universe的问题。
例如这个alpha:

1
2
3
4
5
6
7
8
9
gp = group_cartesian_product(country, subindustry);
raw_fund = ts_backfill(fnd17_adivshr, 20);
raw_anl = ts_backfill(anl4_sadaf_median_epsreported, 20);
fund_momentum = ts_rank(raw_fund, 120);
profit_score = group_rank(fund_momentum, gp);
valuation_metric = ts_zscore(close / raw_anl, 60);
value_score = -group_rank(valuation_metric, gp);
core_signal = profit_score + value_score;
group_neutralize(core_signal, gp)

Sharpe 3.23
Turnover 13.04%
Fitness 2.40
Returns 7.22%
Drawdown 1.93%
Margin 11.08‱
整体表现是不错的,但是总会有Sub-universe Sharpe of 1.41 is below cutoff of 1.68.
而且这一批alphaReturns都在7左右,基本没有突破10的。margin也不高。
我调试了一段时间,总是不能过关。

估值部分的核心如下

1
2
raw_anl = ts_backfill(anl4_sadaf_median_epsreported, 20);
valuation_metric = ts_zscore(close / raw_anl, 60);

然后我前几天一直在思考的事情是,如何将close字段去掉。就去问了Deepseek,其实在这里close/anl4_sadaf_median_epsreported相当于forward P/E (预期市盈率),那么我就去寻找了EUR TOP2500里的P/E字段,还真的给我找到了:

1
2
3
4
5
6
7
8
gp = group_cartesian_product(country, sector);
fund_momentum = ts_zscore(fnd17_ttmdivshr, 60);
profit_score = group_rank(fund_momentum, gp);
data = vec_avg(anl69_best_pe_ratio);
valuation_metric = ts_delta(data, 30);
value_score = -group_rank(valuation_metric, gp);
core_signal = profit_score + value_score;
group_neutralize(core_signal, gp)

Sharpe 1.96
Turnover 14.78%
Fitness 1.23
Returns 5.84%
Drawdown 3.15%
Margin 7.90‱
仍然是老问题,returns不高,margin也不高,pc在0.68,还是过关的。问了Gemini和Deepseek,对于margin都没有很好的优化方法。所以这个alpha先放置一下。

于是就照猫画虎,到IND TOP500试了一下这个操作,做出了不错的alpha:

1
2
3
4
5
6
fund_momentum = ts_rank(fnd94_q_q_qxpspe, 252);
profit_score = group_rank(fund_momentum, sector);
valuation_metric = ts_rank(mdl177_fc_dypeg, 10);
value_score = -group_rank(valuation_metric, sector);
core_signal = profit_score + value_score;
group_neutralize(core_signal, sector)

Sharpe 2.35
Turnover 14.64%
Fitness 2.24
Returns 13.31%
Drawdown 7.60%
Margin 18.18‱

因为数据字段没什么确实,甚至不需要ts_backfill.

4.8

有点累,摆了,把昨天那个alpha三字段版本交上去了。

4.9
明天组会,摆一下。不打算提交
因为前几天实验后发现确实可以将close/anl_data,优化成一个字段。那么我就去找了p/e,以及类似的字段。
根据Gemini的推荐,估值端可供选择的数据字段关键词:

  1. “倒数/收益率”类估值
    关键字:Yield、E/P、Earning Yield、FCF Yield

  2. “成长调整后”的估值
    PEG、Growth Adjusted

  3. 企业价值”类估值
    EV2EBITDA、EV_EBITDA、EBITDA2EV、EV/EBIT

  4. “前瞻性/预期”估值
    FC_PE、Forward PE、FY1_PE、NTM_PE

对于基本面端,Gemini的推荐如下:

  1. “资本回报率”
    Return on、ROE、ROA、ROIC

  2. “利润率/护城河”
    Margin、Operating Margin、Gross Margin

  3. “真实印钞能力”
    Cash Flow、Free Cash Flow、Operating Cash

  4. “核心主业利润”
    EBITDA、EBIT、Operating Income

  5. “每股净收益”
    EPS、Earnings per share、Normalized EPS

4.10

休息一天

4.11

vf更新,现在的统计窗口是12、1、2月,vf是0.18
EUR_garp_mdl177_fnd17这一批pc比较高,所以我打算停止挖掘
将mdl+fnd这一套组合移植到IND TOP500

交了两个,一个EUR一个IND

4.19
这周杂事比较多,中间断了几天。
在4.11~1.18期间找了一篇论文来复现:

“Crowded Trades and Tail Risk” (Lou & Yan, 2017, Review of Financial Studies)
市场上最大的风险不是基本面变差,而是“机构抱团”。当大量主动型基金(Mutual Funds)集中买入某些股票时(形成拥挤交易),这些股票在短期内会因为资金流入而上涨,但随后极易发生动量崩溃(Momentum Crash)和巨大的尾部风险。

在IND TOP500上跑,构造出来形如:

1
2
3
4
5
6
news_data = vec_sum(mws54_eventcallbasicinfo_cancelledflag);
clean_news_data = ts_backfill(news_data,5);
raw_inst_ratio = ts_backfill(aggregate_share_count_institutions / (aggregate_share_count_all_owners + 1), 10);
gunpowder = ts_rank(raw_inst_ratio, 5) - 0.5;
igniter = rank(ts_zscore(clean_news_data, 2)) - 0.5;
group_neutralize(-multiply(gunpowder,igniter), sector)

对于这个模板,改了很多版本,这是我认为逻辑上最合理的一版,但是很少有sharpe>1.5的。

四月下旬

中间科研和学校作业太多所以没怎么记录。这里直接汇总一个。
下旬的策略是In search of attention这篇文章。
摘要:当某只股票的Google搜索量(SVI)异常升高时,未来2周股价会上涨,但随后会在年内发生反转。

我在IND、EUR、USA都尝试了这个策略,只有USA比较明显。或许是因为USA对于search interest的数据字段比较齐全。所以信号都不错。价格这一部分,pv、insider、institution都有不错的数据字段能够表示。

例子:

1
2
3
4
5
6
views = vec_avg(relative_interest_score_5);
clean_money = pv104_hask_mean;
abnormal_attention = views / (ts_mean(views, 60) + 0.001);
smart_money_alarm = ts_zscore(clean_money, 10);
raw_alpha = -abnormal_attention * smart_money_alarm;
group_rank(raw_alpha, sector)

Sharpe 2.82
Turnover 12.01%
Fitness 2.58
Returns 10.43%
Drawdown 3.72%
Margin 17.37‱

表现非常好,但是pc很高,基本在0.75以上。所以我怀疑这个策略是不是已经变成了一种分析师策略或者一种model.于是我切换到ILLIQUID_MINVOL1M,并且使用风险中性化,是降低了一些pc.