4. 资金迁移方向的可视化与阶段划分
资金迁移方向图的数据组织
要把“资金从哪个板块流向哪个板块”画清楚,先要确定迁移矩阵的构造口径。本节采用相邻交易日净流入变化量作为迁移强度的代理指标:对板块 i 在交易日 t,记其日度净流入为 F(i,t)(单位:亿元),则迁移强度定义为
ΔF(i,t) = F(i,t) − F(i,t−1)
当 ΔF(i,t) > 0 时,说明该板块当日相对前一日获得增量资金;当 ΔF(i,t) < 0 时,说明资金相对流出。由于全市场资金近似守恒,正增量板块与负增量板块之间存在此消彼长的对应关系,这正是“迁移方向”的可视化基础。
为了让方向图可读,需要把原始增量压缩成有向边。常用做法是:取当日增量最大的前 k 个板块作为“资金接收方”,增量最小的前 k 个板块作为“资金供给方”,按增量绝对值比例分配边权,形成供给方 → 接收方的有向加权图。示例数据如下(教学示例,非实测):
| 板块 | F(t−1) | F(t) | ΔF | 角色 |
|---|---|---|---|---|
| 半导体 | 12.4 | 28.9 | +16.5 | 接收方 |
| 军工 | 5.1 | 14.7 | +9.6 | 接收方 |
| 银行 | 31.2 | 18.5 | −12.7 | 供给方 |
| 地产 | 9.8 | 2.3 | −7.5 | 供给方 |
| 煤炭 | 6.6 | 2.9 | −3.7 | 供给方 |
按边权比例分配后,可得到“银行→半导体 8.9、银行→军工 3.8、地产→半导体 5.2、地产→军工 2.3、煤炭→半导体 2.4、煤炭→军工 1.3”等有向边。方向图的价值在于:它把分散在各板块的净流入数字,转成一眼可见的“谁在供血、谁在吸血”的拓扑结构。
迁移强度与阶段划分规则
仅有方向还不够,还需要判断当前处于迁移的哪个阶段。本节用迁移强度指数 M(t) 刻画当日资金迁移的剧烈程度:
M(t) = Σ|ΔF(i,t)| / Σ|F(i,t−1)|
该比值越大,说明资金在板块间的重新配置越剧烈。为抑制单日噪声,再对 M(t) 做 5 日移动平均得到 MA5(M),并据此划分四个阶段:
- 静默期:
MA5(M) < 0.15,资金迁移微弱,方向图边稀疏、边权小。 - 启动期:
0.15 ≤ MA5(M) < 0.30且较前 5 日抬升,方向图开始出现稳定的主边。 - 加速期:
0.30 ≤ MA5(M) < 0.55,方向图主边变粗、供给方与接收方分化明显。 - 过热期:
MA5(M) ≥ 0.55,迁移强度极高,方向图可能出现多边并发、角色频繁互换。
阶段标签不是孤立结论,而是后续错位判断的输入:静默期与启动期适合观察方向图的“主边是否稳定”,加速期与过热期则要警惕主边反转带来的错位信号。
阶段标签的落地计算
下面给出可直接运行的 Python 片段,输入为已清洗的板块日度净流入序列(宽表,索引为交易日,列为板块),输出每日迁移强度、阶段标签以及当日方向图的边列表。代码不依赖未展示的前序实现,可独立运行。
import numpy as np
import pandas as pd
def build_migration(df_flow: pd.DataFrame, top_k: int = 3) -> pd.DataFrame:
"""df_flow: index=交易日, columns=板块, values=日度净流入(亿元)"""
delta = df_flow.diff() # ΔF(i,t)
total_prev = df_flow.shift(1).abs().sum(axis=1)
m = delta.abs().sum(axis=1) / total_prev.replace(0, np.nan)
ma5 = m.rolling(5, min_periods=3).mean()
def stage(x):
if pd.isna(x):
return "未定"
if x < 0.15:
return "静默期"
if x < 0.30:
return "启动期"
if x < 0.55:
return "加速期"
return "过热期"
out = pd.DataFrame({"M": m, "MA5_M": ma5, "阶段": ma5.map(stage)})
return out
def daily_edges(delta_row: pd.Series, top_k: int = 3):
"""按增量排序,构造供给方→接收方的有向边"""
s = delta_row.dropna().sort_values()
suppliers = s.head(top_k) # 最负
receivers = s.tail(top_k) # 最正
if suppliers.sum() == 0 or receivers.sum() == 0:
return []
edges = []
for sup, sv in suppliers.items():
for rec, rv in receivers.items():
w = abs(sv) * (rv / receivers.sum())
edges.append((sup, rec, round(w, 2)))
return edges
# 教学示例数据(非实测)
idx = pd.date_range("2024-01-02", periods=12, freq="B")
demo = pd.DataFrame({
"半导体": [10, 11, 12.4, 28.9, 30.1, 31.0, 29.5, 33.2, 35.0, 34.1, 36.2, 37.0],
"军工": [4.8, 5.0, 5.1, 14.7, 15.2, 16.0, 15.1, 17.3, 18.0, 17.6, 18.4, 19.0],
"银行": [30.0, 30.8, 31.2, 18.5, 17.9, 17.2, 18.0, 15.4, 14.8, 15.1, 14.2, 13.9],
"地产": [9.2, 9.5, 9.8, 2.3, 2.1, 2.0, 2.4, 1.8, 1.6, 1.7, 1.5, 1.4],
"煤炭": [6.2, 6.4, 6.6, 2.9, 2.7, 2.6, 2.8, 2.3, 2.1, 2.2, 2.0, 1.9],
}, index=idx)
res = build_migration(demo)
print(res.tail(6))
print(daily_edges(demo.diff().iloc[3], top_k=3))
运行后 res 会给出每日的 M、MA5_M 与阶段标签;daily_edges 返回当日方向图的边列表,可直接喂给绘图函数。示例数据中第 4 个交易日(2024-01-05)的迁移强度明显抬升,阶段标签会从静默期切换到启动期,方向图主边集中在“银行/地产 → 半导体/军工”。
M(t) 会被放大甚至失真,应在计算前剔除分母低于阈值的交易日,或改用滚动中位数做稳健化处理。结果判读与阶段标签校验
方向图与阶段标签生成后,需要做两项基本校验,避免把噪声当成迁移信号:
- 主边稳定性:连续 3 个交易日的主边(边权最大的那条)是否指向同一接收方。若主边频繁反转,说明当前阶段标签可能被单日异常值推高,应回看原始
ΔF是否存在停牌复牌或涨跌停导致的跳变。 - 阶段与边数一致性:静默期方向图边数应偏少且边权小;若静默期出现多条粗边,说明
top_k取值过大或分母口径不一致,需要重新核对M(t)的计算。
通过校验后,每个交易日都会得到“方向图 + 阶段标签”的组合输出,这正是后续轮动错位判断所需的阶段化输入。