Files
NewStock/main/factor/money_flow_factors.py
2025-11-29 00:23:12 +08:00

348 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
资金流因子模块
包含基于股票截面的资金流因子实现
"""
import numpy as np
import polars as pl
from main.factor.operator_framework import StockWiseFactor
class LGFlowFactor(StockWiseFactor):
"""大单净买量因子"""
def __init__(self):
super().__init__(
name="lg_flow",
parameters={},
required_factor_ids=["buy_lg_vol", "buy_elg_vol", "sell_lg_vol", "sell_elg_vol"]
)
def calc_factor(self, group_df: pl.DataFrame) -> pl.Series:
# 计算大单净买量
buy_lg = group_df["buy_lg_vol"]
buy_elg = group_df["buy_elg_vol"]
sell_lg = group_df["sell_lg_vol"]
sell_elg = group_df["sell_elg_vol"]
lg_net_flow = (buy_lg + buy_elg) - (sell_lg + sell_elg)
return lg_net_flow.alias(self.factor_id)
class FlowIntensityFactor(StockWiseFactor):
"""资金流强度因子"""
def __init__(self):
super().__init__(
name="flow_intensity",
parameters={},
required_factor_ids=["buy_lg_vol", "buy_elg_vol", "sell_lg_vol", "sell_elg_vol", "vol"]
)
def calc_factor(self, group_df: pl.DataFrame) -> pl.Series:
# 计算资金流强度
buy_lg = group_df["buy_lg_vol"]
buy_elg = group_df["buy_elg_vol"]
sell_lg = group_df["sell_lg_vol"]
sell_elg = group_df["sell_elg_vol"]
vol = group_df["vol"]
lg_net_flow = (buy_lg + buy_elg) - (sell_lg + sell_elg)
flow_intensity = lg_net_flow / (vol + 1e-8) # 避免除零
return flow_intensity.alias(self.factor_id)
class FlowDivergenceFactor(StockWiseFactor):
"""资金流背离因子"""
def __init__(self):
super().__init__(
name="flow_divergence",
parameters={},
required_factor_ids=["buy_sm_vol", "sell_sm_vol", "buy_lg_vol", "buy_elg_vol", "sell_lg_vol", "sell_elg_vol"]
)
def calc_factor(self, group_df: pl.DataFrame) -> pl.Series:
# 计算资金流背离度
buy_sm = group_df["buy_sm_vol"]
sell_sm = group_df["sell_sm_vol"]
buy_lg = group_df["buy_lg_vol"]
buy_elg = group_df["buy_elg_vol"]
sell_lg = group_df["sell_lg_vol"]
sell_elg = group_df["sell_elg_vol"]
sm_net_flow = buy_sm - sell_sm
lg_net_flow = (buy_lg + buy_elg) - (sell_lg + sell_elg)
flow_divergence = sm_net_flow - lg_net_flow
return flow_divergence.alias(self.factor_id)
class FlowStructureFactor(StockWiseFactor):
"""资金流结构因子"""
def __init__(self):
super().__init__(
name="flow_structure",
parameters={},
required_factor_ids=["buy_sm_vol", "buy_lg_vol", "buy_elg_vol", "vol"]
)
def calc_factor(self, group_df: pl.DataFrame) -> pl.Series:
# 计算资金流结构
buy_sm = group_df["buy_sm_vol"]
buy_lg = group_df["buy_lg_vol"]
buy_elg = group_df["buy_elg_vol"]
vol = group_df["vol"]
total_buy = buy_sm + buy_lg + buy_elg
lg_elg_buy_prop = (buy_lg + buy_elg) / (total_buy + 1e-8) # 避免除零
flow_structure = lg_elg_buy_prop.diff().alias(self.factor_id)
return flow_structure
class FlowAccelerationFactor(StockWiseFactor):
"""资金流加速度因子"""
def __init__(self):
super().__init__(
name="flow_acceleration",
parameters={},
required_factor_ids=["buy_lg_vol", "buy_elg_vol", "sell_lg_vol", "sell_elg_vol"]
)
def calc_factor(self, group_df: pl.DataFrame) -> pl.Series:
# 计算资金流加速度
buy_lg = group_df["buy_lg_vol"]
buy_elg = group_df["buy_elg_vol"]
sell_lg = group_df["sell_lg_vol"]
sell_elg = group_df["sell_elg_vol"]
lg_net_flow = (buy_lg + buy_elg) - (sell_lg + sell_elg)
lg_net_flow_change = lg_net_flow.diff()
flow_acceleration = lg_net_flow_change.diff()
return flow_acceleration.alias(self.factor_id)
class CostSqueeze(StockWiseFactor):
factor_id = "factor_cost_squeeze"
required_factor_ids = ["close", "cost_15pct", "cost_50pct", "cost_85pct", "vol"]
def __init__(self):
super().__init__(
name=self.factor_id,
parameters={},
required_factor_ids=self.required_factor_ids
)
def calc_factor(self, g: pl.DataFrame) -> pl.Series:
close = g["close"]
cost15 = g["cost_15pct"]
cost50 = g["cost_50pct"]
cost85 = g["cost_85pct"]
vol = g["vol"]
cost_range = cost85 - cost15
median_cost = cost50
price_pos = (close - median_cost) / (cost_range + 1e-6)
vol_5d = vol.rolling_mean(window_size=5, min_periods=1)
vol_ratio = vol / (vol_5d + 1e-6)
# 核心逻辑:成本区间窄 + 价格居中 + 量能萎缩 → 高挤压度
squeeze_score = (
(1.0 / (cost_range / (close + 1e-6) + 1e-6))
* (1.0 - price_pos.abs())
* (1.0 / (vol_ratio + 1e-6))
)
# 稳态化:对数变换
factor = (squeeze_score + 1.0).log()
return factor.alias(self.factor_id)
class HighCostSelling(StockWiseFactor):
factor_id = "factor_high_cost_selling"
required_factor_ids = ["close", "cost_85pct", "buy_sm_vol", "sell_lg_vol", "vol"]
def __init__(self):
super().__init__(
name=self.factor_id,
parameters={},
required_factor_ids=self.required_factor_ids
)
def calc_factor(self, g: pl.DataFrame) -> pl.Series:
close = g["close"]
cost85 = g["cost_85pct"]
buy_sm_vol = g["buy_sm_vol"]
sell_lg_vol = g["sell_lg_vol"]
vol = g["vol"]
is_above_85 = (close > cost85).cast(pl.Float64)
small_buy_ratio = buy_sm_vol / (vol + 1e-6)
large_sell_ratio = sell_lg_vol / (vol + 1e-6)
hcsp = is_above_85 * small_buy_ratio * large_sell_ratio
# 稳态化:取对数(避免极端值)
factor = (hcsp + 1e-6).log()
return factor.alias(self.factor_id)
class LowCostAccumulation(StockWiseFactor):
factor_id = "factor_low_cost_accumulation"
required_factor_ids = ["close", "his_low", "cost_15pct", "buy_lg_vol", "buy_elg_vol", "vol"]
def __init__(self):
super().__init__(
name=self.factor_id,
parameters={}, # 无参数,可扩展
required_factor_ids=self.required_factor_ids
)
def calc_factor(self, g: pl.DataFrame) -> pl.Series:
close = g["close"]
his_low = g["his_low"]
cost15 = g["cost_15pct"]
buy_lg_vol = g["buy_lg_vol"]
buy_elg_vol = g["buy_elg_vol"]
vol = g["vol"]
is_below_15 = (close < cost15).cast(pl.Float64)
# 近5日最低价含当日
rolling_min_5 = his_low.rolling_min(window_size=5, min_periods=1)
# 注意his_low 通常是历史最低,但这里我们用 close 的滚动最小更合理
# 修正:应使用 close 的滚动最小判断是否新低
close_rolling_min_5 = close.rolling_min(window_size=5, min_periods=1)
not_new_low = (close >= close_rolling_min_5).cast(pl.Float64)
big_buy_vol = buy_lg_vol + buy_elg_vol
big_buy_ratio = big_buy_vol / (vol + 1e-6)
lc_am = is_below_15 * not_new_low * big_buy_ratio
# 稳态化
factor = (lc_am + 1e-6).log()
return factor.alias(self.factor_id)
class InstNetAccum(StockWiseFactor):
factor_id = "inst_net_accum"
required_factor_ids = ["close", "buy_lg_vol", "buy_elg_vol", "sell_lg_vol", "sell_elg_vol", "circ_mv"]
def __init__(self):
super(InstNetAccum, self).__init__(
name=self.factor_id,
parameters={},
required_factor_ids=self.required_factor_ids
)
def calc_factor(self, g: pl.DataFrame) -> pl.Series:
close = g["close"]
buy_lg = g["buy_lg_vol"]
buy_elg = g["buy_elg_vol"]
sell_lg = g["sell_lg_vol"]
sell_elg = g["sell_elg_vol"]
circ_mv = g["circ_mv"]
big_net_vol = (buy_lg + buy_elg) - (sell_lg + sell_elg)
circ_shares = circ_mv / (close + 1e-6)
ina = big_net_vol / (circ_shares + 1e-6)
# 3日收益率抑制大涨
ret3 = close / close.shift(3) - 1
ret3 = ret3.fill_null(strategy="forward").fill_null(0.0)
ina = pl.when(ret3.abs() < 0.05).then(ina).otherwise(0.0)
return ina.log1p().alias(self.factor_id)
class ChipLockin(StockWiseFactor):
factor_id = "chip_lockin"
required_factor_ids = ["cost_5pct", "cost_95pct", "winner_rate"]
def __init__(self):
super(ChipLockin, self).__init__(
name=self.factor_id,
parameters={},
required_factor_ids=self.required_factor_ids
)
def calc_factor(self, g: pl.DataFrame) -> pl.Series:
cost5 = g["cost_5pct"]
cost95 = g["cost_95pct"]
winner_rate = g["winner_rate"]
cost_width = cost95 - cost5
width_5d = cost_width.rolling_mean(window_size=5, min_periods=1)
width_10d = cost_width.rolling_mean(window_size=10, min_periods=1)
# 避免除零
width_contraction = (width_10d - width_5d) / (width_10d + 1e-6)
winner_std_5 = winner_rate.rolling_std(window_size=5, min_periods=1).fill_null(1e-6)
clm = width_contraction * (1.0 / (winner_std_5 + 1e-6))
return clm.log1p().alias(self.factor_id)
class RetailOutInstIn(StockWiseFactor):
factor_id = "retail_out_inst_in"
required_factor_ids = ["close", "buy_sm_vol", "sell_sm_vol", "buy_lg_vol", "buy_elg_vol", "sell_lg_vol", "sell_elg_vol"]
def __init__(self):
super(RetailOutInstIn, self).__init__(
name=self.factor_id,
parameters={},
required_factor_ids=self.required_factor_ids
)
def calc_factor(self, g: pl.DataFrame) -> pl.Series:
close = g["close"]
buy_sm = g["buy_sm_vol"]
sell_sm = g["sell_sm_vol"]
buy_lg = g["buy_lg_vol"]
buy_elg = g["buy_elg_vol"]
sell_lg = g["sell_lg_vol"]
sell_elg = g["sell_elg_vol"]
small_net_out = sell_sm - buy_sm
big_net_in = (buy_lg + buy_elg) - (sell_lg + sell_elg)
# 价格抗跌近5日未破位用 close 自身滚动最小)
close_min_5 = close.rolling_min(window_size=5, min_periods=1)
roii = small_net_out * big_net_in
return roii.log1p().alias(self.factor_id)
class AccumAccel(StockWiseFactor):
factor_id = "accum_accel"
required_factor_ids = ["buy_lg_vol", "buy_elg_vol", "sell_lg_vol", "sell_elg_vol", "vol"]
def __init__(self):
super(AccumAccel, self).__init__(
name=self.factor_id,
parameters={},
required_factor_ids=self.required_factor_ids
)
def calc_factor(self, g: pl.DataFrame) -> pl.Series:
buy_lg = g["buy_lg_vol"]
buy_elg = g["buy_elg_vol"]
sell_lg = g["sell_lg_vol"]
sell_elg = g["sell_elg_vol"]
vol = g["vol"]
big_net_vol = (buy_lg + buy_elg) - (sell_lg + sell_elg)
big_net_ratio = big_net_vol / (vol + 1e-6)
net_5d = big_net_ratio.rolling_mean(window_size=5, min_periods=1)
net_5d_lag = net_5d.shift(5).fill_null(strategy="forward").fill_null(0.0)
acceleration = net_5d - net_5d_lag
return acceleration.log1p().alias(self.factor_id)