You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

285 lines
11 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 资金监控模块
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
}