Files
sanguo_quant_live/strategies/structured-dynamic-factors-20260327/capital_flow_monitor.py
T

386 lines
14 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.
"""
资金流向监控系统
功能:
1. 北向资金每日流向监控
2. 北向资金板块流向统计
3. 主力资金流向监控
4. 资金流量化因子构建
5. 资金背离信号预警(指数涨资金跌/指数跌资金涨)
Author: 关羽(云长)
Date: 2026-03-27
"""
from dataclasses import dataclass
from typing import List, Dict, Optional, Tuple
import pandas as pd
import numpy as np
@dataclass
class CapitalFlowData:
"""单日资金流向数据"""
date: str
# 北向资金
northbound_total: float # 当日北向净流入(亿)
northbound_5d_avg: float # 5日平均净流入
northbound_20d_avg: float # 20日平均净流入
# 主力资金
main_force_net: float # 当日主力净流入(亿)
main_force_5d_avg: float
main_force_20d_avg: float
# 板块数据
sector_northbound_flow: Dict[str, float] # 板块北向净流入
sector_main_flow: Dict[str, float] # 板块主力净流入
@dataclass
class CapitalFlowStock:
"""个股资金流向"""
code: str
name: str
sector: str
# 资金数据
northbound_hold_change: float # 北向持仓变化(周%
main_force_flow_5d: float # 5日主力净流入(亿)
main_force_flow_20d: float # 20日主力净流入
turnover_rate: float # 换手率
# 价格数据
pct_change_5d: float # 5日涨跌幅%
class CapitalFlowConfig:
"""资金监控参数配置"""
def __init__(self,
northbound_bull_threshold: float = 20.0, # 单日北向净流入超20亿算强势
northbound_bear_threshold: float = -20.0, # 单日净流出超20亿算弱势
consecutive_bull_days: int = 3, # 连续3天净流入算趋势转强
consecutive_bear_days: int = 3, # 连续3天净流出算趋势转弱
main_flow_deviation_threshold: float = -0.5, # 资金背离阈值:价涨资金跌
hot_sector_flow_pct_threshold: float = 0.3): # 板块资金占比超30%算热点
self.northbound_bull_threshold = northbound_bull_threshold
self.northbound_bear_threshold = northbound_bear_threshold
self.consecutive_bull_days = consecutive_bull_days
self.consecutive_bear_days = consecutive_bear_days
self.main_flow_deviation_threshold = main_flow_deviation_threshold
self.hot_sector_flow_pct_threshold = hot_sector_flow_pct_threshold
default_config = CapitalFlowConfig()
class NorthboundMonitor:
"""北向资金监控"""
def __init__(self, config: CapitalFlowConfig = None):
self.config = config or default_config
self.history: List[CapitalFlowData] = []
def add_daily_data(self, data: CapitalFlowData):
"""添加每日数据"""
self.history.append(data)
# 按日期排序
self.history.sort(key=lambda x: x.date)
def get_trend(self) -> str:
"""判断北向资金整体趋势"""
if len(self.history) < self.config.consecutive_bull_days:
return "neutral"
recent = self.history[-self.config.consecutive_bull_days:]
bull_days = sum(1 for d in recent if d.northbound_total > self.config.northbound_bull_threshold)
bear_days = sum(1 for d in recent if d.northbound_total < self.config.northbound_bear_threshold)
if bull_days >= self.config.consecutive_bull_days:
return "strong_bull"
elif bear_days >= self.config.consecutive_bear_days:
return "strong_bear"
elif np.mean([d.northbound_total for d in recent]) > 0:
return "weak_bull"
else:
return "weak_bear"
def get_sector_rank(self) -> List[Tuple[str, float]]:
"""获取板块北向资金流入排名"""
if not self.history:
return []
latest = self.history[-1]
sectors = latest.sector_northbound_flow
sorted_sectors = sorted(sectors.items(), key=lambda x: x[1], reverse=True)
return sorted_sectors
def get_hot_sectors(self) -> List[str]:
"""获取当前热点板块(北向资金集中流入)"""
ranked = self.get_sector_rank()
if not ranked:
return []
total_inflow = sum(s[1] for s in ranked if s[1] > 0)
if total_inflow <= 0:
return []
hot = []
cumulative = 0
for name, flow in ranked:
if flow <= 0:
break
pct = flow / total_inflow
cumulative += pct
hot.append(name)
if cumulative >= self.config.hot_sector_flow_pct_threshold:
break
return hot
def get_trend_signal(self) -> Tuple[str, str]:
"""获取趋势信号和说明"""
trend = self.get_trend()
signal_map = {
"strong_bull": ("🔼 强烈看多", "北向连续三日净流入超20亿,趋势转强"),
"weak_bull": ("▶️ 偏多", "北向整体净流入,趋势偏多"),
"neutral": (" 中性", "北向资金没有明确趋势"),
"weak_bear": ("◀️ 偏空", "北向整体净流出,趋势偏空"),
"strong_bear": ("🔽 强烈看空", "北向连续三日净流出超20亿,趋势转弱")
}
return signal_map[trend]
class MainForceMonitor:
"""主力资金监控"""
def __init__(self, config: CapitalFlowConfig = None):
self.config = config or default_config
def check_deviation(self, index_pct_5d: float, main_flow_5d: float) -> bool:
"""
检查资金背离
指数涨但主力资金净流出 → 顶背离,风险预警
"""
if index_pct_5d > 5 and main_flow_5d < self.config.main_flow_deviation_threshold * abs(index_pct_5d):
return True # 背离,预警
return False
def rank_stocks_by_main_flow(self, stocks: List[CapitalFlowStock]) -> Tuple[List[str], List[str]]:
"""对个股按主力资金流排名,返回强流入和强流出列表"""
sorted_stocks = sorted(stocks, key=lambda x: x.main_force_flow_5d, reverse=True)
# 前20%强流入
top_count = max(len(sorted_stocks) // 5, 1)
top_codes = [s.code for s in sorted_stocks[:top_count]]
# 后20%强流出
bottom_codes = [s.code for s in sorted_stocks[-top_count:]]
return top_codes, bottom_codes
def check_sector_main_trend(self, flow_data: Dict[str, float]) -> List[Tuple[str, str]]:
"""检查板块主力资金趋势"""
result = []
for sector, flow in flow_data.items():
if flow > 10:
result.append((sector, "bull"))
elif flow < -10:
result.append((sector, "bear"))
return result
class CapitalFlowFactorBuilder:
"""资金流量化因子构建"""
@staticmethod
def build_northbound_factor(stock: CapitalFlowStock) -> float:
"""
北向资金因子
周度北向持仓变化,归一化到 0~1,越高越好
"""
change = stock.northbound_hold_change
# 归一化:-5% → 0+5% → 1
factor = (change + 5.0) / 10.0
return max(0.0, min(1.0, factor))
@staticmethod
def build_main_force_factor(stock: CapitalFlowStock) -> float:
"""
主力资金因子
5日主力净流入/流通市值,归一化到 0~1
"""
# 假设流通市值大概100亿,5日净流入超5亿算满分
flow = stock.main_force_flow_5d
factor = (flow + 5.0) / 10.0
return max(0.0, min(1.0, factor))
@staticmethod
def build_turnover_factor(stock: CapitalFlowStock) -> float:
"""
换手率因子
换手率适中最好,过低没流动性,过高太疯狂
最优区间:2%~8%
"""
tr = stock.turnover_rate
if tr < 1:
return 0.2 + tr * 0.3 # 0 → 0.21 → 0.5
elif tr <= 8:
return 0.8 - abs(tr - 5) * 0.05 # 5% → 0.8
else:
return max(0.2, 1.0 - (tr - 8) * 0.05)
@staticmethod
def build_combined_factor(stock: CapitalFlowStock) -> float:
"""综合资金因子"""
nf = CapitalFlowFactorBuilder.build_northbound_factor(stock)
mf = CapitalFlowFactorBuilder.build_main_force_factor(stock)
tf = CapitalFlowFactorBuilder.build_turnover_factor(stock)
# 加权:北向40%,主力40%,换手率20%
combined = nf * 0.4 + mf * 0.4 + tf * 0.2
return combined
class CapitalFlowRiskMonitor:
"""资金流向风险预警"""
def __init__(self, config: CapitalFlowConfig = None):
self.config = config or default_config
self.northbound_monitor = NorthboundMonitor(config)
def check_systemic_risk(self, index_pct_5d: float, total_main_flow_5d: float) -> Tuple[bool, str]:
"""检查系统性风险"""
# 指数涨但主力资金大规模流出 → 风险
if self.northbound_monitor.get_trend() == "strong_bear":
return True, "北向资金连续大幅流出,系统性风险上升"
# 指数涨主力跌 → 背离
if index_pct_5d > 5 and total_main_flow_5d < 0:
return True, f"指数5日涨{index_pct_5d:.1f}%但主力资金净流出,顶背离风险"
return False, ""
class CapitalFlowMonitor:
"""总资金流向监控器"""
def __init__(self, config: CapitalFlowConfig = None):
self.config = config or default_config
self.northbound = NorthboundMonitor(config)
self.main_force = MainForceMonitor(config)
self.risk = CapitalFlowRiskMonitor(config)
self.factor_builder = CapitalFlowFactorBuilder()
def get_daily_report(self,
data: CapitalFlowData,
stocks: List[CapitalFlowStock],
index_pct_5d: float) -> str:
"""生成每日资金流向报告"""
self.northbound.add_daily_data(data)
lines = []
lines.append("=" * 60)
lines.append("资金流向监控日报")
lines.append("=" * 60)
# 北向趋势
trend, desc = self.northbound.get_trend_signal()
lines.append(f"北向资金趋势: {trend}{desc}")
lines.append(f"最近一日净流入: {data.northbound_total:.1f}亿")
lines.append(f"5日均: {data.northbound_5d_avg:.1f}亿 20日均: {data.northbound_20d_avg:.1f}亿")
lines.append("")
# 北向板块排名
hot_sectors = self.northbound.get_hot_sectors()
if hot_sectors:
lines.append(f"🔼 北向资金集中流入板块: {', '.join(hot_sectors)}")
lines.append("")
# 主力资金板块
sector_trend = self.main_force.check_sector_main_trend(data.sector_main_flow)
bull_sectors = [s[0] for s in sector_trend if s[1] == "bull"]
bear_sectors = [s[0] for s in sector_trend if s[1] == "bear"]
if bull_sectors:
lines.append(f"✅ 主力资金净流入板块: {', '.join(bull_sectors)}")
if bear_sectors:
lines.append(f"⚠️ 主力资金净流出板块: {', '.join(bear_sectors)}")
lines.append("")
# 风险检查
has_risk, reason = self.risk.check_systemic_risk(index_pct_5d, data.main_force_5d_avg)
if has_risk:
lines.append(f"⚠️ 风险预警: {reason}")
else:
lines.append("✅ 无明显系统性风险")
# 个股资金因子示例
if stocks:
top_stocks = sorted(stocks, key=lambda x: self.factor_builder.build_combined_factor(x), reverse=True)[:5]
lines.append("")
lines.append("💯 综合资金因子前五名:")
for s in top_stocks:
f = self.factor_builder.build_combined_factor(s)
lines.append(f" {s.code} {s.name} 因子得分: {f:.2f}")
lines.append("=" * 60)
return "\n".join(lines)
if __name__ == "__main__":
print("=== 测试资金流向监控模块 ===\n")
# 测试北向监控
from datetime import datetime, timedelta
monitor = CapitalFlowMonitor()
# 添加最近几天数据
dates = ["2026-03-23", "2026-03-24", "2026-03-25", "2026-03-26", "2026-03-27"]
flows = [25, 32, 18, -10, -28]
for date, flow in zip(dates, flows):
data = CapitalFlowData(
date=date,
northbound_total=flow,
northbound_5d_avg=np.mean(flows[-5:]),
northbound_20d_avg=np.mean(flows[-20:]) if len(flows)>=20 else np.mean(flows),
main_force_net=flow * 2,
main_force_5d_avg=np.mean([f*2 for f in flows[-5:]]),
main_force_20d_avg=np.mean([f*2 for f in flows]),
sector_northbound_flow={"AI": 45, "新能源": -15, "消费": 8, "金融": -5},
sector_main_flow={"AI": 60, "新能源": -20, "消费": 12}
)
monitor.northbound.add_daily_data(data)
# 测试个股
stocks = [
CapitalFlowStock(
code="600000", name="浦发银行", sector="银行",
northbound_hold_change=1.2,
main_force_flow_5d=2.5,
main_force_flow_20d=8.0,
turnover_rate=3.5,
pct_change_5d=2.1
),
CapitalFlowStock(
code="002XXX", name="AI龙头", sector="AI",
northbound_hold_change=3.5,
main_force_flow_5d=8.5,
main_force_flow_20d=25.0,
turnover_rate=5.2,
pct_change_5d=8.5
),
CapitalFlowStock(
code="601XXX", name="周期股", sector="周期",
northbound_hold_change=-2.1,
main_force_flow_5d=-5.2,
main_force_flow_20d=-15.0,
turnover_rate=12.5,
pct_change_5d=-6.2
)
]
# 生成报告
print(monitor.get_daily_report(data, stocks, 2.5))
# 测试因子构建
print("\n=== 测试资金因子构建 ===")
for s in stocks:
f = monitor.factor_builder.build_combined_factor(s)
print(f"{s.name}: 综合因子得分 {f:.2f}")