|
|
|
|
|
# 资金监控模块
|
|
|
|
|
|
import pandas as pd
|
|
|
|
|
|
import numpy as np
|
|
|
|
|
|
from typing import Dict, Optional, List
|
|
|
|
|
|
from qihuo_analyzer.core.models import StrategyConfig
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FundFlowMonitor:
|
|
|
|
|
|
"""资金流向监控器"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, config: Optional[StrategyConfig] = None):
|
|
|
|
|
|
self.config = config or StrategyConfig()
|
|
|
|
|
|
|
|
|
|
|
|
def analyze_fund_flow(self, data: pd.DataFrame) -> Dict:
|
|
|
|
|
|
"""分析资金流向"""
|
|
|
|
|
|
result = {}
|
|
|
|
|
|
|
|
|
|
|
|
# 分析持仓量变化
|
|
|
|
|
|
oi_analysis = self._analyze_open_interest(data)
|
|
|
|
|
|
result.update(oi_analysis)
|
|
|
|
|
|
|
|
|
|
|
|
# 分析量价关系
|
|
|
|
|
|
volume_price_analysis = self._analyze_volume_price_relationship(data)
|
|
|
|
|
|
result.update(volume_price_analysis)
|
|
|
|
|
|
|
|
|
|
|
|
# 分析资金流向强度
|
|
|
|
|
|
fund_flow_strength = self._calculate_fund_flow_strength(data)
|
|
|
|
|
|
result['fund_flow_strength'] = fund_flow_strength
|
|
|
|
|
|
|
|
|
|
|
|
# 分析资金集中度
|
|
|
|
|
|
fund_concentration = self._analyze_fund_concentration(data)
|
|
|
|
|
|
result.update(fund_concentration)
|
|
|
|
|
|
|
|
|
|
|
|
# 综合资金面信号
|
|
|
|
|
|
fund_signal = self._generate_fund_signal(result)
|
|
|
|
|
|
result['fund_signal'] = fund_signal
|
|
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
def _analyze_open_interest(self, data: pd.DataFrame) -> Dict:
|
|
|
|
|
|
"""分析持仓量变化"""
|
|
|
|
|
|
# 计算持仓量变化
|
|
|
|
|
|
data['oi_change'] = data['open_interest'].diff()
|
|
|
|
|
|
data['oi_change_pct'] = data['oi_change'] / data['open_interest'].shift(1) * 100
|
|
|
|
|
|
|
|
|
|
|
|
# 最近N天持仓量变化
|
|
|
|
|
|
recent_oi_change = data['oi_change'].tail(5).sum()
|
|
|
|
|
|
recent_oi_change_pct = data['oi_change_pct'].tail(5).mean()
|
|
|
|
|
|
|
|
|
|
|
|
# 持仓量趋势
|
|
|
|
|
|
oi_trend = self._judge_oi_trend(data['open_interest'])
|
|
|
|
|
|
|
|
|
|
|
|
# 持仓量与价格关系
|
|
|
|
|
|
oi_price_relationship = self._judge_oi_price_relationship(data)
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
'recent_oi_change': recent_oi_change,
|
|
|
|
|
|
'recent_oi_change_pct': recent_oi_change_pct,
|
|
|
|
|
|
'oi_trend': oi_trend,
|
|
|
|
|
|
'oi_price_relationship': oi_price_relationship
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def _analyze_volume_price_relationship(self, data: pd.DataFrame) -> Dict:
|
|
|
|
|
|
"""分析量价关系"""
|
|
|
|
|
|
# 计算价格变化
|
|
|
|
|
|
data['price_change'] = data['close'].diff()
|
|
|
|
|
|
data['price_change_pct'] = data['price_change'] / data['close'].shift(1) * 100
|
|
|
|
|
|
|
|
|
|
|
|
# 计算成交量变化
|
|
|
|
|
|
data['volume_change'] = data['volume'].diff()
|
|
|
|
|
|
data['volume_change_pct'] = data['volume_change'] / data['volume'].shift(1) * 100
|
|
|
|
|
|
|
|
|
|
|
|
# 量价配合度
|
|
|
|
|
|
volume_price_fit = self._calculate_volume_price_fit(data)
|
|
|
|
|
|
|
|
|
|
|
|
# 量价背离检测
|
|
|
|
|
|
divergence = self._detect_volume_price_divergence(data)
|
|
|
|
|
|
|
|
|
|
|
|
# 成交量趋势
|
|
|
|
|
|
volume_trend = self._judge_volume_trend(data['volume'])
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
'volume_price_fit': volume_price_fit,
|
|
|
|
|
|
'divergence': divergence,
|
|
|
|
|
|
'volume_trend': volume_trend
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def _calculate_fund_flow_strength(self, data: pd.DataFrame) -> float:
|
|
|
|
|
|
"""计算资金流向强度"""
|
|
|
|
|
|
# 计算资金流向
|
|
|
|
|
|
# 简化计算:(收盘价 - 开盘价) * 成交量
|
|
|
|
|
|
fund_flow = ((data['close'] - data['open']) * data['volume']).tail(20).sum()
|
|
|
|
|
|
|
|
|
|
|
|
# 归一化到-100到100
|
|
|
|
|
|
if fund_flow > 0:
|
|
|
|
|
|
strength = min(100, (fund_flow / data['volume'].tail(20).sum()) * 1000)
|
|
|
|
|
|
else:
|
|
|
|
|
|
strength = max(-100, (fund_flow / data['volume'].tail(20).sum()) * 1000)
|
|
|
|
|
|
|
|
|
|
|
|
return strength
|
|
|
|
|
|
|
|
|
|
|
|
def _analyze_fund_concentration(self, data: pd.DataFrame) -> Dict:
|
|
|
|
|
|
"""分析资金集中度"""
|
|
|
|
|
|
# 计算成交量集中度(前5天成交量占比)
|
|
|
|
|
|
recent_volume = data['volume'].tail(5).sum()
|
|
|
|
|
|
total_volume = data['volume'].tail(30).sum()
|
|
|
|
|
|
volume_concentration = recent_volume / total_volume if total_volume > 0 else 0
|
|
|
|
|
|
|
|
|
|
|
|
# 计算持仓量集中度(最近持仓量变化占比)
|
|
|
|
|
|
recent_oi_change = abs(data['oi_change'].tail(5).sum())
|
|
|
|
|
|
total_oi = data['open_interest'].iloc[-1]
|
|
|
|
|
|
oi_concentration = recent_oi_change / total_oi if total_oi > 0 else 0
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
'volume_concentration': volume_concentration,
|
|
|
|
|
|
'oi_concentration': oi_concentration
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def _judge_oi_trend(self, oi_series: pd.Series) -> str:
|
|
|
|
|
|
"""判断持仓量趋势"""
|
|
|
|
|
|
# 使用简单移动平均线判断趋势
|
|
|
|
|
|
ma5 = oi_series.rolling(window=5).mean().iloc[-1]
|
|
|
|
|
|
ma20 = oi_series.rolling(window=20).mean().iloc[-1]
|
|
|
|
|
|
|
|
|
|
|
|
if ma5 > ma20 * 1.02:
|
|
|
|
|
|
return 'strong_increasing'
|
|
|
|
|
|
elif ma5 > ma20:
|
|
|
|
|
|
return 'increasing'
|
|
|
|
|
|
elif ma5 < ma20 * 0.98:
|
|
|
|
|
|
return 'strong_decreasing'
|
|
|
|
|
|
elif ma5 < ma20:
|
|
|
|
|
|
return 'decreasing'
|
|
|
|
|
|
else:
|
|
|
|
|
|
return 'stable'
|
|
|
|
|
|
|
|
|
|
|
|
def _judge_oi_price_relationship(self, data: pd.DataFrame) -> Dict:
|
|
|
|
|
|
"""判断持仓量与价格关系"""
|
|
|
|
|
|
recent_data = data.tail(10)
|
|
|
|
|
|
|
|
|
|
|
|
# 计算价格变化
|
|
|
|
|
|
if 'price_change' not in recent_data.columns:
|
|
|
|
|
|
recent_data['price_change'] = recent_data['close'].diff()
|
|
|
|
|
|
|
|
|
|
|
|
# 计算持仓量变化
|
|
|
|
|
|
if 'oi_change' not in recent_data.columns:
|
|
|
|
|
|
recent_data['oi_change'] = recent_data['open_interest'].diff()
|
|
|
|
|
|
|
|
|
|
|
|
# 计算平均价格变化和平均持仓量变化
|
|
|
|
|
|
avg_price_change = recent_data['price_change'].mean()
|
|
|
|
|
|
avg_oi_change = recent_data['oi_change'].mean()
|
|
|
|
|
|
|
|
|
|
|
|
if avg_price_change > 0 and avg_oi_change > 0:
|
|
|
|
|
|
return 'price_up_oi_up' # 价涨量增
|
|
|
|
|
|
elif avg_price_change > 0 and avg_oi_change < 0:
|
|
|
|
|
|
return 'price_up_oi_down' # 价涨量减
|
|
|
|
|
|
elif avg_price_change < 0 and avg_oi_change > 0:
|
|
|
|
|
|
return 'price_down_oi_up' # 价跌量增
|
|
|
|
|
|
elif avg_price_change < 0 and avg_oi_change < 0:
|
|
|
|
|
|
return 'price_down_oi_down' # 价跌量减
|
|
|
|
|
|
else:
|
|
|
|
|
|
return 'stable'
|
|
|
|
|
|
|
|
|
|
|
|
def _calculate_volume_price_fit(self, data: pd.DataFrame) -> float:
|
|
|
|
|
|
"""计算量价配合度"""
|
|
|
|
|
|
recent_data = data.tail(20)
|
|
|
|
|
|
|
|
|
|
|
|
# 计算量价配合的次数
|
|
|
|
|
|
fit_count = 0
|
|
|
|
|
|
total_count = len(recent_data) - 1
|
|
|
|
|
|
|
|
|
|
|
|
for i in range(1, len(recent_data)):
|
|
|
|
|
|
price_change = recent_data['price_change'].iloc[i]
|
|
|
|
|
|
volume_change = recent_data['volume_change'].iloc[i]
|
|
|
|
|
|
|
|
|
|
|
|
# 量价配合:价格上涨成交量增加,价格下跌成交量减少
|
|
|
|
|
|
if (price_change > 0 and volume_change > 0) or (price_change < 0 and volume_change < 0):
|
|
|
|
|
|
fit_count += 1
|
|
|
|
|
|
|
|
|
|
|
|
fit_ratio = fit_count / total_count if total_count > 0 else 0
|
|
|
|
|
|
return fit_ratio * 100
|
|
|
|
|
|
|
|
|
|
|
|
def _detect_volume_price_divergence(self, data: pd.DataFrame) -> str:
|
|
|
|
|
|
"""检测量价背离"""
|
|
|
|
|
|
recent_data = data.tail(10)
|
|
|
|
|
|
|
|
|
|
|
|
# 计算价格趋势(斜率)
|
|
|
|
|
|
price_slope = np.polyfit(range(len(recent_data)), recent_data['close'], 1)[0]
|
|
|
|
|
|
|
|
|
|
|
|
# 计算成交量趋势(斜率)
|
|
|
|
|
|
volume_slope = np.polyfit(range(len(recent_data)), recent_data['volume'], 1)[0]
|
|
|
|
|
|
|
|
|
|
|
|
# 判断背离
|
|
|
|
|
|
if price_slope > 0 and volume_slope < 0:
|
|
|
|
|
|
return 'bearish_divergence' # 价格上涨,成交量下降,看跌背离
|
|
|
|
|
|
elif price_slope < 0 and volume_slope > 0:
|
|
|
|
|
|
return 'bullish_divergence' # 价格下降,成交量上升,看涨背离
|
|
|
|
|
|
else:
|
|
|
|
|
|
return 'no_divergence'
|
|
|
|
|
|
|
|
|
|
|
|
def _judge_volume_trend(self, volume_series: pd.Series) -> str:
|
|
|
|
|
|
"""判断成交量趋势"""
|
|
|
|
|
|
ma5 = volume_series.rolling(window=5).mean().iloc[-1]
|
|
|
|
|
|
ma20 = volume_series.rolling(window=20).mean().iloc[-1]
|
|
|
|
|
|
|
|
|
|
|
|
if ma5 > ma20 * 1.1:
|
|
|
|
|
|
return 'strong_increasing'
|
|
|
|
|
|
elif ma5 > ma20:
|
|
|
|
|
|
return 'increasing'
|
|
|
|
|
|
elif ma5 < ma20 * 0.9:
|
|
|
|
|
|
return 'strong_decreasing'
|
|
|
|
|
|
elif ma5 < ma20:
|
|
|
|
|
|
return 'decreasing'
|
|
|
|
|
|
else:
|
|
|
|
|
|
return 'stable'
|
|
|
|
|
|
|
|
|
|
|
|
def _generate_fund_signal(self, fund_analysis: Dict) -> str:
|
|
|
|
|
|
"""生成资金面信号"""
|
|
|
|
|
|
signals = []
|
|
|
|
|
|
|
|
|
|
|
|
# 持仓量信号
|
|
|
|
|
|
if fund_analysis.get('oi_trend') in ['strong_increasing', 'increasing']:
|
|
|
|
|
|
if fund_analysis.get('oi_price_relationship') == 'price_up_oi_up':
|
|
|
|
|
|
signals.append('bullish')
|
|
|
|
|
|
elif fund_analysis.get('oi_price_relationship') == 'price_down_oi_up':
|
|
|
|
|
|
signals.append('bearish')
|
|
|
|
|
|
|
|
|
|
|
|
# 量价关系信号
|
|
|
|
|
|
if fund_analysis.get('volume_price_fit') > 60:
|
|
|
|
|
|
if fund_analysis.get('volume_trend') in ['strong_increasing', 'increasing']:
|
|
|
|
|
|
signals.append('bullish')
|
|
|
|
|
|
|
|
|
|
|
|
# 量价背离信号
|
|
|
|
|
|
if fund_analysis.get('divergence') == 'bullish_divergence':
|
|
|
|
|
|
signals.append('bullish')
|
|
|
|
|
|
elif fund_analysis.get('divergence') == 'bearish_divergence':
|
|
|
|
|
|
signals.append('bearish')
|
|
|
|
|
|
|
|
|
|
|
|
# 资金流向强度信号
|
|
|
|
|
|
fund_flow_strength = fund_analysis.get('fund_flow_strength', 0)
|
|
|
|
|
|
if fund_flow_strength > 30:
|
|
|
|
|
|
signals.append('bullish')
|
|
|
|
|
|
elif fund_flow_strength < -30:
|
|
|
|
|
|
signals.append('bearish')
|
|
|
|
|
|
|
|
|
|
|
|
# 综合信号
|
|
|
|
|
|
if signals.count('bullish') > signals.count('bearish'):
|
|
|
|
|
|
return 'bullish'
|
|
|
|
|
|
elif signals.count('bearish') > signals.count('bullish'):
|
|
|
|
|
|
return 'bearish'
|
|
|
|
|
|
else:
|
|
|
|
|
|
return 'neutral'
|
|
|
|
|
|
|
|
|
|
|
|
def detect_volume_spikes(self, data: pd.DataFrame, threshold: float = 2.0) -> List[int]:
|
|
|
|
|
|
"""检测成交量异动"""
|
|
|
|
|
|
# 计算成交量移动平均线和标准差
|
|
|
|
|
|
data['volume_ma'] = data['volume'].rolling(window=20).mean()
|
|
|
|
|
|
data['volume_std'] = data['volume'].rolling(window=20).std()
|
|
|
|
|
|
|
|
|
|
|
|
# 计算成交量偏离度
|
|
|
|
|
|
data['volume_zscore'] = (data['volume'] - data['volume_ma']) / data['volume_std']
|
|
|
|
|
|
|
|
|
|
|
|
# 找出成交量异动的位置
|
|
|
|
|
|
spikes = data[data['volume_zscore'] > threshold].index
|
|
|
|
|
|
|
|
|
|
|
|
return list(spikes)
|
|
|
|
|
|
|
|
|
|
|
|
def analyze_institutional_activity(self, data: pd.DataFrame) -> Dict:
|
|
|
|
|
|
"""分析机构活动"""
|
|
|
|
|
|
# 基于持仓量和成交量的变化分析机构活动
|
|
|
|
|
|
# 机构通常会引起较大的持仓量变化
|
|
|
|
|
|
|
|
|
|
|
|
# 计算大资金活动指标
|
|
|
|
|
|
data['institutional_activity'] = data['oi_change'] * abs(data['price_change'])
|
|
|
|
|
|
|
|
|
|
|
|
# 最近机构活动强度
|
|
|
|
|
|
recent_institutional_activity = data['institutional_activity'].tail(5).sum()
|
|
|
|
|
|
|
|
|
|
|
|
# 机构活动趋势
|
|
|
|
|
|
institutional_trend = 'increasing' if recent_institutional_activity > 0 else 'decreasing'
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
'recent_institutional_activity': recent_institutional_activity,
|
|
|
|
|
|
'institutional_trend': institutional_trend
|
|
|
|
|
|
}
|