<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Pandas on 远思码技术实验室</title><link>https://yuansima.com/tags/pandas/</link><description>Recent content in Pandas on 远思码技术实验室</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Sat, 17 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://yuansima.com/tags/pandas/index.xml" rel="self" type="application/rss+xml"/><item><title>向量化回测引擎：从零实现一个可信的日频回测框架</title><link>https://yuansima.com/p/vectorized-backtest-engine/</link><pubDate>Sat, 17 Oct 2026 20:40:00 +0800</pubDate><guid>https://yuansima.com/p/vectorized-backtest-engine/</guid><description>&lt;p&gt;&lt;em&gt;远思为径，码动价值。欢迎来到远思码技术实验室的最新技术实践记录。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;回测是量化研究里&lt;strong&gt;最容易产生幻觉的一步&lt;/strong&gt;。同样一段策略逻辑，用不同框架、不同参数假设跑出来的年化收益率可以差出十几个点，而绝大多数差异都藏在三件小事里：成交价、交易限制、成本。&lt;/p&gt;
&lt;p&gt;实验室的结论是：&lt;strong&gt;先自己写一个能看懂每一行的引擎&lt;/strong&gt;，再去用现成框架。这篇就是那个引擎，全部代码不到 200 行。&lt;/p&gt;
&lt;h2 id="一引擎的契约"&gt;一、引擎的契约
&lt;/h2&gt;&lt;p&gt;在写代码之前，先把输入输出定死，这是防止自己作弊的第一步：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;输入&lt;/th&gt;
 &lt;th&gt;形状&lt;/th&gt;
 &lt;th&gt;含义&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;close&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;T×N&lt;/td&gt;
 &lt;td&gt;复权收盘价矩阵&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;volume&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;T×N&lt;/td&gt;
 &lt;td&gt;成交量矩阵（用于判断可交易性）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;pct_chg&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;T×N&lt;/td&gt;
 &lt;td&gt;当日涨跌幅，用于判断涨跌停&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;target_w&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;T×N&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;T 日收盘后&lt;/strong&gt;决定的下一期目标权重，行和 ≤ 1，余下为现金&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;输出&lt;/th&gt;
 &lt;th&gt;含义&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;nav&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;净值曲线（起点为 1）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;holdings&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;每日实际持仓权重、换手、成本明细&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;关键约定只有一条：&lt;strong&gt;&lt;code&gt;target_w&lt;/code&gt; 的第 t 行，是从第 t 日收盘之后开始生效的权重&lt;/strong&gt;。引擎内部自己做 &lt;code&gt;shift(1)&lt;/code&gt; 来实现&amp;quot;信号滞后一期&amp;quot;，策略代码里严禁再手动 shift，否则会重复滞后。&lt;/p&gt;
&lt;h2 id="二骨架收益漂移成本"&gt;二、骨架：收益、漂移、成本
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# backtest/engine.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; __future__ &lt;span style="color:#f92672"&gt;import&lt;/span&gt; annotations
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; itertools
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; dataclasses &lt;span style="color:#f92672"&gt;import&lt;/span&gt; dataclass, field
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; numpy &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; np
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; pandas &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; pd
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@dataclass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;BacktestConfig&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; fee_rate: float &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0.0003&lt;/span&gt; &lt;span style="color:#75715e"&gt;# 佣金，双边&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stamp_duty: float &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0.0005&lt;/span&gt; &lt;span style="color:#75715e"&gt;# 印花税，仅卖出&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; slippage: float &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0.0010&lt;/span&gt; &lt;span style="color:#75715e"&gt;# 单边滑点&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; impact_coef: float &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0.0015&lt;/span&gt; &lt;span style="color:#75715e"&gt;# 冲击成本系数（相对换手）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; rf: float &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0.02&lt;/span&gt; &lt;span style="color:#75715e"&gt;# 无风险利率（年化）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; periods: int &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;244&lt;/span&gt; &lt;span style="color:#75715e"&gt;# 年化交易日&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@dataclass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;BacktestResult&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; nav: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Series
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; daily_return: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Series
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; turnover: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Series
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cost: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Series
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; holdings: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; metrics: dict &lt;span style="color:#f92672"&gt;=&lt;/span&gt; field(default_factory&lt;span style="color:#f92672"&gt;=&lt;/span&gt;dict)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;核心循环全部用矩阵运算完成，一行 &lt;code&gt;for&lt;/code&gt; 都不需要：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;run_backtest&lt;/span&gt;(close: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; volume: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; target_w: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; pct_chg: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cfg: BacktestConfig &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; BacktestResult:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cfg &lt;span style="color:#f92672"&gt;=&lt;/span&gt; cfg &lt;span style="color:#f92672"&gt;or&lt;/span&gt; BacktestConfig()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; close, volume &lt;span style="color:#f92672"&gt;=&lt;/span&gt; close&lt;span style="color:#f92672"&gt;.&lt;/span&gt;align(volume, join&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;inner&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; target_w &lt;span style="color:#f92672"&gt;=&lt;/span&gt; target_w&lt;span style="color:#f92672"&gt;.&lt;/span&gt;reindex(index&lt;span style="color:#f92672"&gt;=&lt;/span&gt;close&lt;span style="color:#f92672"&gt;.&lt;/span&gt;index, columns&lt;span style="color:#f92672"&gt;=&lt;/span&gt;close&lt;span style="color:#f92672"&gt;.&lt;/span&gt;columns)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;fillna(&lt;span style="color:#ae81ff"&gt;0.0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ret &lt;span style="color:#f92672"&gt;=&lt;/span&gt; close&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pct_change()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;fillna(&lt;span style="color:#ae81ff"&gt;0.0&lt;/span&gt;) &lt;span style="color:#75715e"&gt;# 第 t 日收益，close_{t-1} -&amp;gt; close_t&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# ---- 1. 可交易性：停牌(volume=0) / 涨跌停 / 缺失价 ----&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tradable &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (volume &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; close&lt;span style="color:#f92672"&gt;.&lt;/span&gt;notna() &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; (target_w&lt;span style="color:#f92672"&gt;.&lt;/span&gt;notna())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; pct_chg &lt;span style="color:#f92672"&gt;is&lt;/span&gt; &lt;span style="color:#f92672"&gt;not&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; pct &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pct_chg&lt;span style="color:#f92672"&gt;.&lt;/span&gt;reindex_like(tradable)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 主板 ±10%、创业板/科创板 ±20%，用绝对阈值统一近似：≥9.8% 视为涨停不可买&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; limit_up &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pct &lt;span style="color:#f92672"&gt;&amp;gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;9.8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; limit_down &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pct &lt;span style="color:#f92672"&gt;&amp;lt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;9.8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 目标权重 &amp;gt; 0 且涨停 -&amp;gt; 买不进；目标权重 &amp;lt; 0 且跌停 -&amp;gt; 卖不出&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; can_buy &lt;span style="color:#f92672"&gt;=&lt;/span&gt; tradable &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color:#f92672"&gt;~&lt;/span&gt;limit_up
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; can_sell &lt;span style="color:#f92672"&gt;=&lt;/span&gt; tradable &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color:#f92672"&gt;~&lt;/span&gt;limit_down
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; w_prev &lt;span style="color:#f92672"&gt;=&lt;/span&gt; target_w&lt;span style="color:#f92672"&gt;.&lt;/span&gt;shift(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; blocked_buy &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (w_prev&lt;span style="color:#f92672"&gt;.&lt;/span&gt;isna() &lt;span style="color:#f92672"&gt;|&lt;/span&gt; (w_prev &lt;span style="color:#f92672"&gt;&amp;lt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)) &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; (target_w &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color:#f92672"&gt;~&lt;/span&gt;can_buy
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; blocked_sell &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (w_prev &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; (target_w &lt;span style="color:#f92672"&gt;&amp;lt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; &lt;span style="color:#f92672"&gt;~&lt;/span&gt;can_sell
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; target_w &lt;span style="color:#f92672"&gt;=&lt;/span&gt; target_w&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mask(blocked_buy, &lt;span style="color:#ae81ff"&gt;0.0&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mask(blocked_sell, w_prev)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# ---- 2. 归一化：不足 1 的部分视为现金 ----&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 允许做多为主；若行和 &amp;gt; 1 则等比压缩，避免隐式杠杆&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; gross &lt;span style="color:#f92672"&gt;=&lt;/span&gt; target_w&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sum(axis&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;replace(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, np&lt;span style="color:#f92672"&gt;.&lt;/span&gt;nan)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; scale &lt;span style="color:#f92672"&gt;=&lt;/span&gt; np&lt;span style="color:#f92672"&gt;.&lt;/span&gt;where(gross &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&lt;/span&gt; gross, &lt;span style="color:#ae81ff"&gt;1.0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; target_w &lt;span style="color:#f92672"&gt;=&lt;/span&gt; target_w&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mul(pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Series(scale, index&lt;span style="color:#f92672"&gt;=&lt;/span&gt;target_w&lt;span style="color:#f92672"&gt;.&lt;/span&gt;index), axis&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;fillna(&lt;span style="color:#ae81ff"&gt;0.0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# ---- 3. 实际持仓 = 上一期目标权重（信号滞后一期） ----&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; w_hold &lt;span style="color:#f92672"&gt;=&lt;/span&gt; target_w&lt;span style="color:#f92672"&gt;.&lt;/span&gt;shift(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;fillna(&lt;span style="color:#ae81ff"&gt;0.0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# ---- 4. 毛收益：今日持仓 × 今日收益 ----&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; gross_ret &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (w_hold &lt;span style="color:#f92672"&gt;*&lt;/span&gt; ret)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sum(axis&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# ---- 5. 权重漂移：今日收盘后各资产权重自然变化 ----&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; denom &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; gross_ret)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;replace(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, np&lt;span style="color:#f92672"&gt;.&lt;/span&gt;nan)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; w_drift &lt;span style="color:#f92672"&gt;=&lt;/span&gt; w_hold&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mul(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; ret, axis&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;div(denom, axis&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;fillna(&lt;span style="color:#ae81ff"&gt;0.0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# ---- 6. 换手与成本：今日收盘调仓，相对漂移权重 ----&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; delta &lt;span style="color:#f92672"&gt;=&lt;/span&gt; target_w &lt;span style="color:#f92672"&gt;-&lt;/span&gt; w_drift
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; turnover_buy &lt;span style="color:#f92672"&gt;=&lt;/span&gt; delta&lt;span style="color:#f92672"&gt;.&lt;/span&gt;clip(lower&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sum(axis&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;) &lt;span style="color:#75715e"&gt;# 买入额占比&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; turnover_sell &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (&lt;span style="color:#f92672"&gt;-&lt;/span&gt;delta)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;clip(lower&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sum(axis&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;) &lt;span style="color:#75715e"&gt;# 卖出额占比&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; turnover &lt;span style="color:#f92672"&gt;=&lt;/span&gt; turnover_buy &lt;span style="color:#f92672"&gt;+&lt;/span&gt; turnover_sell
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cost &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (turnover_buy &lt;span style="color:#f92672"&gt;*&lt;/span&gt; (cfg&lt;span style="color:#f92672"&gt;.&lt;/span&gt;fee_rate &lt;span style="color:#f92672"&gt;+&lt;/span&gt; cfg&lt;span style="color:#f92672"&gt;.&lt;/span&gt;slippage)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; turnover_sell &lt;span style="color:#f92672"&gt;*&lt;/span&gt; (cfg&lt;span style="color:#f92672"&gt;.&lt;/span&gt;fee_rate &lt;span style="color:#f92672"&gt;+&lt;/span&gt; cfg&lt;span style="color:#f92672"&gt;.&lt;/span&gt;slippage &lt;span style="color:#f92672"&gt;+&lt;/span&gt; cfg&lt;span style="color:#f92672"&gt;.&lt;/span&gt;stamp_duty)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; turnover &lt;span style="color:#f92672"&gt;*&lt;/span&gt; cfg&lt;span style="color:#f92672"&gt;.&lt;/span&gt;impact_coef &lt;span style="color:#f92672"&gt;*&lt;/span&gt; turnover) &lt;span style="color:#75715e"&gt;# 冲击成本近似平方项&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; port_ret &lt;span style="color:#f92672"&gt;=&lt;/span&gt; gross_ret &lt;span style="color:#f92672"&gt;-&lt;/span&gt; cost
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; nav &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; port_ret)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;cumprod()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; res &lt;span style="color:#f92672"&gt;=&lt;/span&gt; BacktestResult(nav&lt;span style="color:#f92672"&gt;=&lt;/span&gt;nav, daily_return&lt;span style="color:#f92672"&gt;=&lt;/span&gt;port_ret, turnover&lt;span style="color:#f92672"&gt;=&lt;/span&gt;turnover,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cost&lt;span style="color:#f92672"&gt;=&lt;/span&gt;cost, holdings&lt;span style="color:#f92672"&gt;=&lt;/span&gt;w_hold)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; res&lt;span style="color:#f92672"&gt;.&lt;/span&gt;metrics &lt;span style="color:#f92672"&gt;=&lt;/span&gt; performance(nav, port_ret, turnover, cfg)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; res
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;有几处设计值得展开说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;w_hold = target_w.shift(1)&lt;/code&gt;&lt;/strong&gt; 就是&amp;quot;信号滞后一期&amp;quot;的全部实现。策略在 T 日收盘后生成信号，T+1 日才能享受（或承受）收益。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;漂移权重 &lt;code&gt;w_drift&lt;/code&gt;&lt;/strong&gt; 常被忽略。如果你 T 日满仓买入，T+1 日股票涨了 5%，那么 T+1 收盘时它在组合里的权重是 5% 之后的自然权重，不是当初的目标权重。调仓换手必须相对它算，否则每次调仓的换手都会被高估。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;冲击成本写成 &lt;code&gt;turnover * coef * turnover&lt;/code&gt;&lt;/strong&gt;，即随换手平方增长。这比线性模型更贴近现实：大额调仓的边际成本上升。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="三绩效指标"&gt;三、绩效指标
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;performance&lt;/span&gt;(nav: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Series, ret: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Series, turnover: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Series,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cfg: BacktestConfig) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; dict:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; n &lt;span style="color:#f92672"&gt;=&lt;/span&gt; len(nav)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; n &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#f92672"&gt;or&lt;/span&gt; nav&lt;span style="color:#f92672"&gt;.&lt;/span&gt;iloc[&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;] &lt;span style="color:#f92672"&gt;&amp;lt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; {&lt;span style="color:#e6db74"&gt;&amp;#34;error&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;样本不足或净值归零&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ann &lt;span style="color:#f92672"&gt;=&lt;/span&gt; nav&lt;span style="color:#f92672"&gt;.&lt;/span&gt;iloc[&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;] &lt;span style="color:#f92672"&gt;**&lt;/span&gt; (cfg&lt;span style="color:#f92672"&gt;.&lt;/span&gt;periods &lt;span style="color:#f92672"&gt;/&lt;/span&gt; n) &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vol &lt;span style="color:#f92672"&gt;=&lt;/span&gt; ret&lt;span style="color:#f92672"&gt;.&lt;/span&gt;std(ddof&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;) &lt;span style="color:#f92672"&gt;*&lt;/span&gt; np&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sqrt(cfg&lt;span style="color:#f92672"&gt;.&lt;/span&gt;periods)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sharpe &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (ann &lt;span style="color:#f92672"&gt;-&lt;/span&gt; cfg&lt;span style="color:#f92672"&gt;.&lt;/span&gt;rf) &lt;span style="color:#f92672"&gt;/&lt;/span&gt; vol &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; vol &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; np&lt;span style="color:#f92672"&gt;.&lt;/span&gt;nan
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dd &lt;span style="color:#f92672"&gt;=&lt;/span&gt; nav &lt;span style="color:#f92672"&gt;/&lt;/span&gt; nav&lt;span style="color:#f92672"&gt;.&lt;/span&gt;cummax() &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mdd &lt;span style="color:#f92672"&gt;=&lt;/span&gt; dd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;min()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; calmar &lt;span style="color:#f92672"&gt;=&lt;/span&gt; ann &lt;span style="color:#f92672"&gt;/&lt;/span&gt; abs(mdd) &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; mdd &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; np&lt;span style="color:#f92672"&gt;.&lt;/span&gt;nan
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 最大回撤持续天数（净值在水面之下的最长连续区间）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; underwater &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (dd &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;astype(int)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; max_flat &lt;span style="color:#f92672"&gt;=&lt;/span&gt; max((len(list(g)) &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; k, g &lt;span style="color:#f92672"&gt;in&lt;/span&gt; itertools&lt;span style="color:#f92672"&gt;.&lt;/span&gt;groupby(underwater) &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; k &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; default&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; win &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (ret &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mean()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; avg_turnover &lt;span style="color:#f92672"&gt;=&lt;/span&gt; turnover&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mean()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;累计收益&amp;#34;&lt;/span&gt;: round(float(nav&lt;span style="color:#f92672"&gt;.&lt;/span&gt;iloc[&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;] &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;), &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;年化收益&amp;#34;&lt;/span&gt;: round(float(ann), &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;年化波动&amp;#34;&lt;/span&gt;: round(float(vol), &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;夏普&amp;#34;&lt;/span&gt;: round(float(sharpe), &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;最大回撤&amp;#34;&lt;/span&gt;: round(float(mdd), &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;最大回撤天数&amp;#34;&lt;/span&gt;: int(max_flat),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Calmar&amp;#34;&lt;/span&gt;: round(float(calmar), &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;日胜率&amp;#34;&lt;/span&gt;: round(float(win), &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;日均换手&amp;#34;&lt;/span&gt;: round(float(avg_turnover), &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;年化换手&amp;#34;&lt;/span&gt;: round(float(avg_turnover &lt;span style="color:#f92672"&gt;*&lt;/span&gt; cfg&lt;span style="color:#f92672"&gt;.&lt;/span&gt;periods), &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;交易日数&amp;#34;&lt;/span&gt;: int(n),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里 &lt;code&gt;日均换手 × 244 = 年化换手&lt;/code&gt; 是个粗略但足够用的口径。&lt;strong&gt;日频策略年化换手超过 30 倍，成本假设就必须认真对待&lt;/strong&gt;，不能再用&amp;quot;万一三&amp;quot;敷衍过去。&lt;/p&gt;
&lt;h2 id="四把正确性钉死三个单元测试"&gt;四、把正确性钉死：三个单元测试
&lt;/h2&gt;&lt;p&gt;引擎写完不测，等于没写。实验室用三个手算案例做回归测试，任何改动都必须过：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# tests/test_engine.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; numpy &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; np
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; pandas &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; pd
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; backtest.engine &lt;span style="color:#f92672"&gt;import&lt;/span&gt; BacktestConfig, run_backtest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_frames&lt;/span&gt;(ret_rows):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;由收益矩阵构造价格矩阵（起点 10 元）。&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; close &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame(ret_rows))&lt;span style="color:#f92672"&gt;.&lt;/span&gt;cumprod() &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; volume &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame(&lt;span style="color:#ae81ff"&gt;1e6&lt;/span&gt;, index&lt;span style="color:#f92672"&gt;=&lt;/span&gt;close&lt;span style="color:#f92672"&gt;.&lt;/span&gt;index, columns&lt;span style="color:#f92672"&gt;=&lt;/span&gt;close&lt;span style="color:#f92672"&gt;.&lt;/span&gt;columns)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; close, volume
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;test_single_stock_buy_and_hold&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 两天，每天涨 10%。T日收盘建仓 -&amp;gt; T+1 才吃到收益&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; close, volume &lt;span style="color:#f92672"&gt;=&lt;/span&gt; _frames([[&lt;span style="color:#ae81ff"&gt;0.10&lt;/span&gt;], [&lt;span style="color:#ae81ff"&gt;0.10&lt;/span&gt;]])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; w &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame([[&lt;span style="color:#ae81ff"&gt;1.0&lt;/span&gt;], [&lt;span style="color:#ae81ff"&gt;1.0&lt;/span&gt;]], index&lt;span style="color:#f92672"&gt;=&lt;/span&gt;close&lt;span style="color:#f92672"&gt;.&lt;/span&gt;index, columns&lt;span style="color:#f92672"&gt;=&lt;/span&gt;close&lt;span style="color:#f92672"&gt;.&lt;/span&gt;columns)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; res &lt;span style="color:#f92672"&gt;=&lt;/span&gt; run_backtest(close, volume, w, cfg&lt;span style="color:#f92672"&gt;=&lt;/span&gt;BacktestConfig(fee_rate&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, stamp_duty&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; slippage&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, impact_coef&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 第一天无持仓（w 被 shift），第二天满仓吃 10%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;assert&lt;/span&gt; abs(res&lt;span style="color:#f92672"&gt;.&lt;/span&gt;nav&lt;span style="color:#f92672"&gt;.&lt;/span&gt;iloc[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;] &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1.0&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1e-12&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;assert&lt;/span&gt; abs(res&lt;span style="color:#f92672"&gt;.&lt;/span&gt;nav&lt;span style="color:#f92672"&gt;.&lt;/span&gt;iloc[&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;] &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1.10&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1e-12&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;test_cost_is_charged_on_turnover&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; close, volume &lt;span style="color:#f92672"&gt;=&lt;/span&gt; _frames([[&lt;span style="color:#ae81ff"&gt;0.0&lt;/span&gt;], [&lt;span style="color:#ae81ff"&gt;0.0&lt;/span&gt;]])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; w &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame([[&lt;span style="color:#ae81ff"&gt;1.0&lt;/span&gt;], [&lt;span style="color:#ae81ff"&gt;0.0&lt;/span&gt;]], index&lt;span style="color:#f92672"&gt;=&lt;/span&gt;close&lt;span style="color:#f92672"&gt;.&lt;/span&gt;index, columns&lt;span style="color:#f92672"&gt;=&lt;/span&gt;close&lt;span style="color:#f92672"&gt;.&lt;/span&gt;columns)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cfg &lt;span style="color:#f92672"&gt;=&lt;/span&gt; BacktestConfig(fee_rate&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0.001&lt;/span&gt;, stamp_duty&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0.001&lt;/span&gt;, slippage&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, impact_coef&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; res &lt;span style="color:#f92672"&gt;=&lt;/span&gt; run_backtest(close, volume, w, cfg&lt;span style="color:#f92672"&gt;=&lt;/span&gt;cfg)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 第一天：w.shift 后仍为 0 -&amp;gt; 无持仓；第二天持仓 1 后立刻清仓？&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 实际：hold 恒为 [0, 1]，故第二天收盘换手 = 1，卖出成本 = 1*(佣金0.001+印花税0.001)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;assert&lt;/span&gt; res&lt;span style="color:#f92672"&gt;.&lt;/span&gt;turnover&lt;span style="color:#f92672"&gt;.&lt;/span&gt;iloc[&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;] &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;assert&lt;/span&gt; abs(res&lt;span style="color:#f92672"&gt;.&lt;/span&gt;cost&lt;span style="color:#f92672"&gt;.&lt;/span&gt;iloc[&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;] &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0.002&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1e-12&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;test_weights_never_imply_leverage&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; close, volume &lt;span style="color:#f92672"&gt;=&lt;/span&gt; _frames([[&lt;span style="color:#ae81ff"&gt;0.01&lt;/span&gt;], [&lt;span style="color:#ae81ff"&gt;0.01&lt;/span&gt;], [&lt;span style="color:#ae81ff"&gt;0.01&lt;/span&gt;]], )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; w &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame([[&lt;span style="color:#ae81ff"&gt;0.8&lt;/span&gt;], [&lt;span style="color:#ae81ff"&gt;0.8&lt;/span&gt;], [&lt;span style="color:#ae81ff"&gt;0.8&lt;/span&gt;]], index&lt;span style="color:#f92672"&gt;=&lt;/span&gt;close&lt;span style="color:#f92672"&gt;.&lt;/span&gt;index, columns&lt;span style="color:#f92672"&gt;=&lt;/span&gt;close&lt;span style="color:#f92672"&gt;.&lt;/span&gt;columns)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; res &lt;span style="color:#f92672"&gt;=&lt;/span&gt; run_backtest(close, volume, w, cfg&lt;span style="color:#f92672"&gt;=&lt;/span&gt;BacktestConfig(fee_rate&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, stamp_duty&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; slippage&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, impact_coef&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;assert&lt;/span&gt; (res&lt;span style="color:#f92672"&gt;.&lt;/span&gt;holdings&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sum(axis&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;lt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1e-9&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;all()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;第二个测试看起来有点绕，但它锁住了一个非常具体的语义：&lt;strong&gt;换手发生在&amp;quot;目标权重与漂移权重不一致&amp;quot;的那一天，成本当天计提&lt;/strong&gt;。这个语义一旦写错，所有策略的成本都会被系统性低估。&lt;/p&gt;
&lt;h2 id="五什么时候该换成事件驱动框架"&gt;五、什么时候该换成事件驱动框架
&lt;/h2&gt;&lt;p&gt;向量化引擎的假设是：&lt;strong&gt;同一时点所有股票都能按收盘价成交，且份额可以无限细分&lt;/strong&gt;。它在下列场景会失真：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;场景&lt;/th&gt;
 &lt;th&gt;向量化引擎的问题&lt;/th&gt;
 &lt;th&gt;建议&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;分钟级 / 盘口撮合&lt;/td&gt;
 &lt;td&gt;无法表达排队、部分成交&lt;/td&gt;
 &lt;td&gt;事件驱动（vn.py / backtrader / 自研撮合）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;资金量大于标的日成交额的 1%&lt;/td&gt;
 &lt;td&gt;冲击成本模型过于乐观&lt;/td&gt;
 &lt;td&gt;引入成交量参与率约束&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;需要盘中撤单、止损单&lt;/td&gt;
 &lt;td&gt;无订单生命周期&lt;/td&gt;
 &lt;td&gt;事件驱动&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;组合再平衡涉及资金约束（现金不足）&lt;/td&gt;
 &lt;td&gt;权重假设掩盖了资金不足&lt;/td&gt;
 &lt;td&gt;加现金约束层&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;实验室的日常研究 90% 用向量化，因为要跑几千组参数；只有在策略进入仿真阶段，才用事件驱动复核一遍。&lt;strong&gt;先用快而粗糙的尺子找方向，再用慢而精确的尺子确认&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="六一个常见误用"&gt;六、一个常见误用
&lt;/h2&gt;&lt;p&gt;有人会把 &lt;code&gt;target_w&lt;/code&gt; 直接设成&amp;quot;今天涨得最好的股票&amp;quot;，然后跑出 500% 的年化——因为他在 T 日收盘时用了 T 日的数据去选股，而引擎的 &lt;code&gt;shift(1)&lt;/code&gt; 让这份&amp;quot;未来信息&amp;quot;合法化了。&lt;/p&gt;
&lt;p&gt;判断方法很简单：&lt;strong&gt;把 &lt;code&gt;shift(1)&lt;/code&gt; 改成 &lt;code&gt;shift(2)&lt;/code&gt;，如果年化收益暴跌，说明策略严重依赖即时信息，值得警惕&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="结语"&gt;结语
&lt;/h2&gt;&lt;p&gt;到这里我们有了两样东西：可检验的&lt;a class="link" href="https://yuansima.com/p/quant-factor-engineering/" &gt;因子&lt;/a&gt;，和可信的&lt;a class="link" href="https://yuansima.com/p/vectorized-backtest-engine/" &gt;尺子&lt;/a&gt;。下一篇用它们跑第一个真实策略——双均线与动量轮动的组合，顺带把参数稳健性检验做完。&lt;/p&gt;
&lt;p&gt;远思为径，码动价值。回测是用来证伪的，不是用来给自己发奖金的。&lt;/p&gt;</description></item><item><title>因子工程实战：从行情数据到可检验的 Alpha 因子</title><link>https://yuansima.com/p/quant-factor-engineering/</link><pubDate>Mon, 12 Oct 2026 20:10:00 +0800</pubDate><guid>https://yuansima.com/p/quant-factor-engineering/</guid><description>&lt;p&gt;&lt;em&gt;远思为径，码动价值。欢迎来到远思码技术实验室的最新技术实践记录。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;在&lt;a class="link" href="https://yuansima.com/p/akshare-tutorial/" &gt;上一篇数据抓取&lt;/a&gt;里，我们已经把 A 股日线行情写进了 TimescaleDB 的超表。但&lt;strong&gt;干净的数据不等于可用的信号&lt;/strong&gt;——从&amp;quot;能取到数据&amp;quot;到&amp;quot;能下单&amp;quot;，中间隔着整个因子工程。&lt;/p&gt;
&lt;p&gt;这篇文章把我们实验室的因子流水线拆开讲：怎么写、怎么校验、怎么避免自己骗自己。&lt;/p&gt;
&lt;h2 id="一先立四条纪律"&gt;一、先立四条纪律
&lt;/h2&gt;&lt;p&gt;因子工程里绝大多数翻车，不是因为数学不够花哨，而是因为违反了下面前四条之一：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;不使用未来信息&lt;/strong&gt;。T 日收盘后才能拿到的数据，只能在 T 日收盘后生成信号，T+1 开盘执行。任何 &lt;code&gt;center=True&lt;/code&gt; 的滚动窗口、任何全样本拟合的标准化，都是未来函数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;截面可比&lt;/strong&gt;。因子值必须在&lt;strong&gt;同一个交易日的横截面上&lt;/strong&gt;做标准化，而不是用时间序列均值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可复现&lt;/strong&gt;。因子定义、参数、数据版本都写进代码与因子表，重跑必须得到同样的数字。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可检验&lt;/strong&gt;。一个因子在入库前必须回答两个问题：它对未来收益有没有单调信息（IC / 分层），扣掉成本后还剩多少（换手）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;第 4 条最容易被跳过，而它恰好是唯一真正重要的一条。&lt;/p&gt;
&lt;h2 id="二取数把宽表拉成矩阵"&gt;二、取数：把宽表拉成矩阵
&lt;/h2&gt;&lt;p&gt;实验室的行情表结构在上一篇文章里已经建好，核心字段是 &lt;code&gt;symbol / trade_date / close / volume / amount / turnover / pct_chg&lt;/code&gt;。因子计算更适合&amp;quot;矩阵&amp;quot;而不是&amp;quot;长表&amp;quot;，所以第一步先 pivot：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# src/data/market.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; __future__ &lt;span style="color:#f92672"&gt;import&lt;/span&gt; annotations
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; pandas &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; pd
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; sqlalchemy &lt;span style="color:#f92672"&gt;import&lt;/span&gt; create_engine
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;DSN &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;postgresql+psycopg2://quant:quant@127.0.0.1:5432/quant&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;load_price_matrix&lt;/span&gt;(start: str, end: str, engine&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; dict[str, pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame]:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;返回 {&amp;#39;close&amp;#39;,&amp;#39;volume&amp;#39;,&amp;#39;amount&amp;#39;,&amp;#39;turnover&amp;#39;,&amp;#39;pct_chg&amp;#39;} 五张 T×N 矩阵。&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; engine &lt;span style="color:#f92672"&gt;=&lt;/span&gt; engine &lt;span style="color:#f92672"&gt;or&lt;/span&gt; create_engine(DSN, pool_pre_ping&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;, pool_size&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sql &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; SELECT symbol, trade_date, close, volume, amount, turnover, pct_chg
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; FROM daily_bar
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; WHERE trade_date BETWEEN &lt;/span&gt;&lt;span style="color:#e6db74"&gt;%(start)s&lt;/span&gt;&lt;span style="color:#e6db74"&gt; AND &lt;/span&gt;&lt;span style="color:#e6db74"&gt;%(end)s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; ORDER BY trade_date, symbol
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; df &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;read_sql(sql, engine, params&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;start&amp;#34;&lt;/span&gt;: start, &lt;span style="color:#e6db74"&gt;&amp;#34;end&amp;#34;&lt;/span&gt;: end})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; out: dict[str, pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; col &lt;span style="color:#f92672"&gt;in&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;close&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;volume&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;amount&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;turnover&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;pct_chg&amp;#34;&lt;/span&gt;]:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; out[col] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; df&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pivot(index&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;trade_date&amp;#34;&lt;/span&gt;, columns&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;symbol&amp;#34;&lt;/span&gt;, values&lt;span style="color:#f92672"&gt;=&lt;/span&gt;col)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sort_index()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# 停牌日通常缺失，这里保留 NaN：后续因子计算里 NaN 会自然传播成&amp;#34;不可交易&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; out
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;两个细节值得强调：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;不要 &lt;code&gt;fillna(method=&amp;quot;ffill&amp;quot;)&lt;/code&gt; 无脑填充&lt;/strong&gt;。停牌日的价格是&amp;quot;没有成交&amp;quot;，强行前向填充会让动量因子把停牌期算成 0 收益，回测里会凭空产生收益。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;矩阵的列（股票）会随时间变化&lt;/strong&gt;。新股上市、退市都会造成列不对齐，后续用 &lt;code&gt;DataFrame.align&lt;/code&gt; 或直接按日截面处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="三五个基础因子先够用再说"&gt;三、五个基础因子，先够用再说
&lt;/h2&gt;&lt;p&gt;不用一上来就上百个因子。实验室的第一版只用了五类，覆盖量价四个维度：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;因子&lt;/th&gt;
 &lt;th&gt;定义&lt;/th&gt;
 &lt;th&gt;逻辑&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;mom_20&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;过去 20 日收益&lt;/td&gt;
 &lt;td&gt;动量效应&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;rev_5&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;过去 5 日收益取负&lt;/td&gt;
 &lt;td&gt;短期反转&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;vol_20&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;20 日收益率标准差取负&lt;/td&gt;
 &lt;td&gt;低波动异象&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;turn_20&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;20 日平均换手取负&lt;/td&gt;
 &lt;td&gt;高换手 = 拥挤&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;bias_20&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;close / MA20 - 1&lt;/code&gt; 取负&lt;/td&gt;
 &lt;td&gt;均值回复&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# src/factor/basic.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; numpy &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; np
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; pandas &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; pd
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;momentum&lt;/span&gt;(close: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame, n: int &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;n 日动量：T 日因子 = T 日收盘 / T-n 日收盘 - 1（截止当日，不含未来信息）。&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; close &lt;span style="color:#f92672"&gt;/&lt;/span&gt; close&lt;span style="color:#f92672"&gt;.&lt;/span&gt;shift(n) &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;reversal&lt;/span&gt;(close: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame, n: int &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt;momentum(close, n)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;volatility&lt;/span&gt;(pct: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame, n: int &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# pct_chg 已是百分数，先转成小数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt;(pct &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;rolling(n)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;std()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;turnover_avg&lt;/span&gt;(turnover: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame, n: int &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt;turnover&lt;span style="color:#f92672"&gt;.&lt;/span&gt;rolling(n)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mean()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;bias&lt;/span&gt;(close: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame, n: int &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ma &lt;span style="color:#f92672"&gt;=&lt;/span&gt; close&lt;span style="color:#f92672"&gt;.&lt;/span&gt;rolling(n)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mean()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt;(close &lt;span style="color:#f92672"&gt;/&lt;/span&gt; ma &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;上面每个因子都只用「截止当日」的数据，这是故意保持的语义：&lt;strong&gt;因子定义里不包含任何执行滞后，滞后只在回测引擎里统一做一次&lt;/strong&gt;。原因很实际——如果因子内部先 &lt;code&gt;shift(1)&lt;/code&gt;，回测引擎再叠一层 &lt;code&gt;shift(1)&lt;/code&gt;，策略就凭空慢了半拍，收益会被系统性低估；反过来如果某处漏了 shift，又会偷到未来收益。&lt;strong&gt;滞后只在一处发生，才方便审计&lt;/strong&gt;。下一篇的引擎会严格实现这条约定。&lt;/p&gt;
&lt;h2 id="四去极值与标准化截面处理三步"&gt;四、去极值与标准化：截面处理三步
&lt;/h2&gt;&lt;p&gt;原始因子值直接进模型会被几只极端值股票带偏。截面处理固定三步：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# src/factor/process.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; numpy &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; np
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; pandas &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; pd
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;mad_winsorize&lt;/span&gt;(df: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame, n: float &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3.0&lt;/span&gt;) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;按日截面做 MAD 去极值（中位数 ± n * 1.4826 * MAD）。&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; med &lt;span style="color:#f92672"&gt;=&lt;/span&gt; df&lt;span style="color:#f92672"&gt;.&lt;/span&gt;median(axis&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mad &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (df&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sub(med, axis&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;))&lt;span style="color:#f92672"&gt;.&lt;/span&gt;abs()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;median(axis&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;) &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1.4826&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; upper &lt;span style="color:#f92672"&gt;=&lt;/span&gt; med &lt;span style="color:#f92672"&gt;+&lt;/span&gt; n &lt;span style="color:#f92672"&gt;*&lt;/span&gt; mad
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lower &lt;span style="color:#f92672"&gt;=&lt;/span&gt; med &lt;span style="color:#f92672"&gt;-&lt;/span&gt; n &lt;span style="color:#f92672"&gt;*&lt;/span&gt; mad
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; df&lt;span style="color:#f92672"&gt;.&lt;/span&gt;clip(lower&lt;span style="color:#f92672"&gt;=&lt;/span&gt;lower, upper&lt;span style="color:#f92672"&gt;=&lt;/span&gt;upper, axis&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;zscore&lt;/span&gt;(df: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;按日截面做 z-score。std=0（全市场同一个值）时返回 0 而不是 inf。&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mu &lt;span style="color:#f92672"&gt;=&lt;/span&gt; df&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mean(axis&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sd &lt;span style="color:#f92672"&gt;=&lt;/span&gt; df&lt;span style="color:#f92672"&gt;.&lt;/span&gt;std(axis&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;replace(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, np&lt;span style="color:#f92672"&gt;.&lt;/span&gt;nan)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; df&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sub(mu, axis&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;div(sd, axis&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;industry_neutralize&lt;/span&gt;(factor: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame, industry: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;行业中性化：对每个交易日，把因子对行业哑变量做 OLS，取残差。
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; industry: 与 factor 同形状的行业标签矩阵（如 &amp;#34;银行&amp;#34;、&amp;#34;电子&amp;#34;），缺失记为 NaN。
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; residual &lt;span style="color:#f92672"&gt;=&lt;/span&gt; factor&lt;span style="color:#f92672"&gt;.&lt;/span&gt;copy() &lt;span style="color:#f92672"&gt;*&lt;/span&gt; np&lt;span style="color:#f92672"&gt;.&lt;/span&gt;nan
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; date &lt;span style="color:#f92672"&gt;in&lt;/span&gt; factor&lt;span style="color:#f92672"&gt;.&lt;/span&gt;index:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; y &lt;span style="color:#f92672"&gt;=&lt;/span&gt; factor&lt;span style="color:#f92672"&gt;.&lt;/span&gt;loc[date]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; g &lt;span style="color:#f92672"&gt;=&lt;/span&gt; industry&lt;span style="color:#f92672"&gt;.&lt;/span&gt;loc[date]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mask &lt;span style="color:#f92672"&gt;=&lt;/span&gt; y&lt;span style="color:#f92672"&gt;.&lt;/span&gt;notna() &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; g&lt;span style="color:#f92672"&gt;.&lt;/span&gt;notna()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; mask&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sum() &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dummies &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get_dummies(g[mask], drop_first&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;, dtype&lt;span style="color:#f92672"&gt;=&lt;/span&gt;float)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; dummies&lt;span style="color:#f92672"&gt;.&lt;/span&gt;empty:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; residual&lt;span style="color:#f92672"&gt;.&lt;/span&gt;loc[date] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; y[mask]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; X &lt;span style="color:#f92672"&gt;=&lt;/span&gt; np&lt;span style="color:#f92672"&gt;.&lt;/span&gt;column_stack([np&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ones(mask&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sum()), dummies&lt;span style="color:#f92672"&gt;.&lt;/span&gt;to_numpy()])
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; yv &lt;span style="color:#f92672"&gt;=&lt;/span&gt; y[mask]&lt;span style="color:#f92672"&gt;.&lt;/span&gt;to_numpy()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; beta, &lt;span style="color:#f92672"&gt;*&lt;/span&gt;_ &lt;span style="color:#f92672"&gt;=&lt;/span&gt; np&lt;span style="color:#f92672"&gt;.&lt;/span&gt;linalg&lt;span style="color:#f92672"&gt;.&lt;/span&gt;lstsq(X, yv, rcond&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; residual&lt;span style="color:#f92672"&gt;.&lt;/span&gt;loc[date, mask] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; yv &lt;span style="color:#f92672"&gt;-&lt;/span&gt; X &lt;span style="color:#f92672"&gt;@&lt;/span&gt; beta
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; residual
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;顺序很重要：&lt;strong&gt;先 MAD 去极值 → 再行业中性化 → 最后 z-score&lt;/strong&gt;。如果先标准化再中性化，残差的量纲会乱，后面做因子加权时权重就失去意义了。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;提醒：行业中性化一定会&amp;quot;吃掉&amp;quot;一部分收益。如果你的因子本来就是行业轮动逻辑（比如&amp;quot;超配银行&amp;quot;），中性化之后就只剩行业内选股能力了——这不是坏事，只是要清楚自己在测什么。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="五检验ic分层换手一个都不能少"&gt;五、检验：IC、分层、换手，一个都不能少
&lt;/h2&gt;&lt;p&gt;因子是否有效，实验室只用三个指标下结论。&lt;/p&gt;
&lt;h3 id="51-ic-与-icir"&gt;5.1 IC 与 ICIR
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# src/factor/evaluate.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; numpy &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; np
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; pandas &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; pd
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; scipy.stats &lt;span style="color:#f92672"&gt;import&lt;/span&gt; spearmanr
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;forward_return&lt;/span&gt;(close: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame, hold: int &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;未来 hold 日收益（用于评价，绝不进入因子计算）。&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; close&lt;span style="color:#f92672"&gt;.&lt;/span&gt;shift(&lt;span style="color:#f92672"&gt;-&lt;/span&gt;hold) &lt;span style="color:#f92672"&gt;/&lt;/span&gt; close &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;daily_ic&lt;/span&gt;(factor: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame, fwd_ret: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame, method: str &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;spearman&amp;#34;&lt;/span&gt;) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Series:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;每个交易日横截面上的因子与未来收益的相关性。&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ics &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; date &lt;span style="color:#f92672"&gt;in&lt;/span&gt; factor&lt;span style="color:#f92672"&gt;.&lt;/span&gt;index:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; x, y &lt;span style="color:#f92672"&gt;=&lt;/span&gt; factor&lt;span style="color:#f92672"&gt;.&lt;/span&gt;loc[date], fwd_ret&lt;span style="color:#f92672"&gt;.&lt;/span&gt;loc[date]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mask &lt;span style="color:#f92672"&gt;=&lt;/span&gt; x&lt;span style="color:#f92672"&gt;.&lt;/span&gt;notna() &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; y&lt;span style="color:#f92672"&gt;.&lt;/span&gt;notna()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; mask&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sum() &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; method &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;spearman&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ics[date] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; spearmanr(x[mask], y[mask])&lt;span style="color:#f92672"&gt;.&lt;/span&gt;statistic
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ics[date] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; np&lt;span style="color:#f92672"&gt;.&lt;/span&gt;corrcoef(x[mask], y[mask])[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Series(ics)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sort_index()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ic_summary&lt;/span&gt;(ic: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Series, periods_per_year: int &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;244&lt;/span&gt;) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; dict:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ir &lt;span style="color:#f92672"&gt;=&lt;/span&gt; ic&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mean() &lt;span style="color:#f92672"&gt;/&lt;/span&gt; ic&lt;span style="color:#f92672"&gt;.&lt;/span&gt;std(ddof&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; ic&lt;span style="color:#f92672"&gt;.&lt;/span&gt;std(ddof&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; np&lt;span style="color:#f92672"&gt;.&lt;/span&gt;nan
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;IC均值&amp;#34;&lt;/span&gt;: round(float(ic&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mean()), &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;IC标准差&amp;#34;&lt;/span&gt;: round(float(ic&lt;span style="color:#f92672"&gt;.&lt;/span&gt;std(ddof&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)), &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;ICIR&amp;#34;&lt;/span&gt;: round(float(ir), &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;IC&amp;gt;0占比&amp;#34;&lt;/span&gt;: round(float((ic &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mean()), &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;年化ICIR&amp;#34;&lt;/span&gt;: round(float(ir &lt;span style="color:#f92672"&gt;*&lt;/span&gt; np&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sqrt(periods_per_year &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;)), &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; ir &lt;span style="color:#f92672"&gt;==&lt;/span&gt; ir &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;样本天数&amp;#34;&lt;/span&gt;: int(len(ic)),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;经验阈值（日频、5 日持有）：&lt;code&gt;|IC均值| &amp;gt; 0.02&lt;/code&gt;、&lt;code&gt;ICIR &amp;gt; 0.3&lt;/code&gt;、&lt;code&gt;IC &amp;gt; 0 占比 &amp;gt; 0.55&lt;/code&gt; 才值得往下做分层测试。低于这个水平，后面所有回测都只是在拟合噪声。&lt;/p&gt;
&lt;h3 id="52-分层收益"&gt;5.2 分层收益
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;layered_return&lt;/span&gt;(factor: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame, fwd_ret: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame, q: int &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;按日截面分 q 组，等权持有 forward 收益，返回每组的年化收益。&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; groups: dict[int, list[float]] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {i: [] &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; i &lt;span style="color:#f92672"&gt;in&lt;/span&gt; range(q)}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; date &lt;span style="color:#f92672"&gt;in&lt;/span&gt; factor&lt;span style="color:#f92672"&gt;.&lt;/span&gt;index:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; x, y &lt;span style="color:#f92672"&gt;=&lt;/span&gt; factor&lt;span style="color:#f92672"&gt;.&lt;/span&gt;loc[date], fwd_ret&lt;span style="color:#f92672"&gt;.&lt;/span&gt;loc[date]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mask &lt;span style="color:#f92672"&gt;=&lt;/span&gt; x&lt;span style="color:#f92672"&gt;.&lt;/span&gt;notna() &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; y&lt;span style="color:#f92672"&gt;.&lt;/span&gt;notna()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; mask&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sum() &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; q &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; labels &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;qcut(x[mask]&lt;span style="color:#f92672"&gt;.&lt;/span&gt;rank(method&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;first&amp;#34;&lt;/span&gt;), q, labels&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; g, idx &lt;span style="color:#f92672"&gt;in&lt;/span&gt; labels&lt;span style="color:#f92672"&gt;.&lt;/span&gt;groupby(labels)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;groups&lt;span style="color:#f92672"&gt;.&lt;/span&gt;items():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; groups[int(g)]&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append(float(y[mask]&lt;span style="color:#f92672"&gt;.&lt;/span&gt;loc[idx]&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mean()))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; annual &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; g, rets &lt;span style="color:#f92672"&gt;in&lt;/span&gt; groups&lt;span style="color:#f92672"&gt;.&lt;/span&gt;items():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; s &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Series(rets)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; annual[&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Q&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;g &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; round(float(s&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mean() &lt;span style="color:#f92672"&gt;*&lt;/span&gt; (&lt;span style="color:#ae81ff"&gt;244&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;)), &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; len(s) &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame([annual], index&lt;span style="color:#f92672"&gt;=&lt;/span&gt;[&lt;span style="color:#e6db74"&gt;&amp;#34;年化收益&amp;#34;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;一个真正有效的因子，&lt;code&gt;Q1 → Q5&lt;/code&gt; 应该单调，并且首尾组年化差异是稳定的，而不是靠某一年撑起来的。&lt;/p&gt;
&lt;h3 id="53-换手率"&gt;5.3 换手率
&lt;/h3&gt;&lt;p&gt;IC 再漂亮，如果因子每天把整个组合换个遍，扣完成本就归零。实验室的标准做法是算&lt;strong&gt;因子秩的日间自相关&lt;/strong&gt;来近似换手：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rank_autocorr&lt;/span&gt;(factor: pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DataFrame, lag: int &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; float:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;因子截面秩的自相关，越高越稳定（0.9 以上说明换手很低）。&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ranks &lt;span style="color:#f92672"&gt;=&lt;/span&gt; factor&lt;span style="color:#f92672"&gt;.&lt;/span&gt;rank(axis&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, pct&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vals &lt;span style="color:#f92672"&gt;=&lt;/span&gt; []
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; date &lt;span style="color:#f92672"&gt;in&lt;/span&gt; ranks&lt;span style="color:#f92672"&gt;.&lt;/span&gt;index[lag:]:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; a &lt;span style="color:#f92672"&gt;=&lt;/span&gt; ranks&lt;span style="color:#f92672"&gt;.&lt;/span&gt;loc[date]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; b &lt;span style="color:#f92672"&gt;=&lt;/span&gt; ranks&lt;span style="color:#f92672"&gt;.&lt;/span&gt;shift(lag)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;loc[date]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mask &lt;span style="color:#f92672"&gt;=&lt;/span&gt; a&lt;span style="color:#f92672"&gt;.&lt;/span&gt;notna() &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt; b&lt;span style="color:#f92672"&gt;.&lt;/span&gt;notna()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; mask&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sum() &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vals&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append(float(np&lt;span style="color:#f92672"&gt;.&lt;/span&gt;corrcoef(a[mask], b[mask])[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;]))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; float(np&lt;span style="color:#f92672"&gt;.&lt;/span&gt;nanmean(vals))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="六落库因子表长什么样"&gt;六、落库：因子表长什么样
&lt;/h2&gt;&lt;p&gt;因子算完不要停在 notebook 里。实验室统一写入 &lt;code&gt;factor_value&lt;/code&gt; 表，主键 &lt;code&gt;(factor_name, trade_date, symbol)&lt;/code&gt;，字段 &lt;code&gt;raw / z&lt;/code&gt;，外加版本号：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TABLE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;EXISTS&lt;/span&gt; factor_value (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; factor_name text &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; trade_date date &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; symbol text &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; raw double &lt;span style="color:#66d9ef"&gt;precision&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; z double &lt;span style="color:#66d9ef"&gt;precision&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;version&lt;/span&gt; text &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;DEFAULT&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;v1&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;PRIMARY&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;KEY&lt;/span&gt; (factor_name, trade_date, symbol)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;SELECT&lt;/span&gt; create_hypertable(&lt;span style="color:#e6db74"&gt;&amp;#39;factor_value&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;trade_date&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; chunk_time_interval &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; INTERVAL &lt;span style="color:#e6db74"&gt;&amp;#39;90 days&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; if_not_exists &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TRUE&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;写入时用 &lt;code&gt;ON CONFLICT DO UPDATE&lt;/code&gt; 做幂等 upsert，这样重跑某一天的因子不会产生重复行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; sqlalchemy &lt;span style="color:#f92672"&gt;import&lt;/span&gt; text
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;UPSERT &lt;span style="color:#f92672"&gt;=&lt;/span&gt; text(&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; INSERT INTO factor_value (factor_name, trade_date, symbol, raw, z, version)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; VALUES (:factor_name, :trade_date, :symbol, :raw, :z, :version)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; ON CONFLICT (factor_name, trade_date, symbol)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; DO UPDATE SET raw = EXCLUDED.raw, z = EXCLUDED.z, version = EXCLUDED.version
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="七几个一定会踩的坑"&gt;七、几个一定会踩的坑
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;复权基准会变&lt;/strong&gt;。前复权价格在每次除权后整体重算，历史因子值会&amp;quot;漂移&amp;quot;。实验室的做法是因子表存&lt;strong&gt;未复权价 + 复权因子&lt;/strong&gt;，复权只在计算时做，并且记录 &lt;code&gt;version&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ST / 退市股没剔除&lt;/strong&gt;。&lt;code&gt;z&lt;/code&gt; 值算对了，但买不进去。因子入库前就该把&amp;quot;不可交易&amp;quot;标出来，别等到回测才过滤。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;成分股用的是今天的名单&lt;/strong&gt;。这是幸存者偏差的经典来源，做指数增强类策略时必须用当期成分股快照。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IC 算的是样本内&lt;/strong&gt;。因子是全样本算出来的，IC 也是全样本。真正的检验要在滚动窗口里做，下一篇会展开。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="结语"&gt;结语
&lt;/h2&gt;&lt;p&gt;到这里，我们有了一个可复现、可检验的因子表。接下来两件事必须按顺序做：先把&lt;a class="link" href="https://yuansima.com/p/vectorized-backtest-engine/" &gt;回测引擎&lt;/a&gt;写对，再用它去证伪这些因子——&lt;strong&gt;先有可信的尺子，再去量东西&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;远思为径，码动价值。数据给了原料，因子给了假设，剩下的交给回测去审判。&lt;/p&gt;</description></item></channel></rss>