386 lines
14 KiB
Python
386 lines
14 KiB
Python
"""
|
||
资金流向监控系统
|
||
功能:
|
||
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.2,1 → 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}")
|