|
|
# 换月预警模块
|
|
|
import pandas as pd
|
|
|
import numpy as np
|
|
|
from datetime import datetime, timedelta
|
|
|
from typing import Dict, List, Optional, Tuple
|
|
|
|
|
|
|
|
|
class RolloverDetector:
|
|
|
"""换月预警检测器"""
|
|
|
|
|
|
def __init__(self):
|
|
|
pass
|
|
|
|
|
|
def analyze_rollover(self, symbol: str, data: pd.DataFrame, contract_info: Optional[Dict] = None) -> Dict:
|
|
|
"""分析换月情况"""
|
|
|
result = {}
|
|
|
|
|
|
# 检测交割日
|
|
|
delivery_info = self._detect_delivery_date(symbol, contract_info)
|
|
|
result.update(delivery_info)
|
|
|
|
|
|
# 分析流动性
|
|
|
liquidity_analysis = self._analyze_liquidity(data)
|
|
|
result.update(liquidity_analysis)
|
|
|
|
|
|
# 分析价差
|
|
|
spread_analysis = self._analyze_spread(symbol)
|
|
|
result.update(spread_analysis)
|
|
|
|
|
|
# 生成换月预警
|
|
|
rollover_warning = self._generate_rollover_warning(delivery_info, liquidity_analysis)
|
|
|
result['rollover_warning'] = rollover_warning
|
|
|
|
|
|
# 生成减仓建议
|
|
|
position_adjustment = self._generate_position_adjustment(delivery_info, liquidity_analysis)
|
|
|
result['position_adjustment'] = position_adjustment
|
|
|
|
|
|
return result
|
|
|
|
|
|
def _detect_delivery_date(self, symbol: str, contract_info: Optional[Dict] = None) -> Dict:
|
|
|
"""检测交割日"""
|
|
|
if contract_info and 'expire_datetime' in contract_info:
|
|
|
# 使用合约信息中的交割日
|
|
|
expire_timestamp = contract_info['expire_datetime']
|
|
|
if isinstance(expire_timestamp, int):
|
|
|
# 处理纳秒时间戳
|
|
|
if expire_timestamp > 1e15:
|
|
|
expire_date = datetime.fromtimestamp(expire_timestamp / 1e9)
|
|
|
else:
|
|
|
expire_date = datetime.fromtimestamp(expire_timestamp)
|
|
|
else:
|
|
|
expire_date = pd.to_datetime(expire_timestamp)
|
|
|
else:
|
|
|
# 基于合约代码推断交割日
|
|
|
expire_date = self._infer_delivery_date(symbol)
|
|
|
|
|
|
# 计算距离交割日的天数
|
|
|
today = datetime.now()
|
|
|
days_to_delivery = (expire_date - today).days
|
|
|
|
|
|
# 确定换月预警级别
|
|
|
warning_level = self._calculate_warning_level(days_to_delivery)
|
|
|
|
|
|
return {
|
|
|
'expire_date': expire_date.strftime('%Y-%m-%d'),
|
|
|
'days_to_delivery': days_to_delivery,
|
|
|
'warning_level': warning_level
|
|
|
}
|
|
|
|
|
|
def _infer_delivery_date(self, symbol: str) -> datetime:
|
|
|
"""基于合约代码推断交割日"""
|
|
|
# 简化的合约代码解析
|
|
|
# 假设合约代码格式为:品种+年份+月份,如 'CU2309'
|
|
|
try:
|
|
|
# 提取年份和月份
|
|
|
year_str = symbol[-4:-2]
|
|
|
month_str = symbol[-2:]
|
|
|
|
|
|
# 构建年份(加上世纪)
|
|
|
year = 2000 + int(year_str)
|
|
|
month = int(month_str)
|
|
|
|
|
|
# 假设交割日为合约月份的15日
|
|
|
# 实际交割日可能因品种而异,这里使用简化处理
|
|
|
expire_date = datetime(year, month, 15)
|
|
|
|
|
|
return expire_date
|
|
|
except Exception:
|
|
|
# 如果解析失败,返回60天后的日期
|
|
|
return datetime.now() + timedelta(days=60)
|
|
|
|
|
|
def _calculate_warning_level(self, days_to_delivery: int) -> str:
|
|
|
"""计算换月预警级别"""
|
|
|
if days_to_delivery <= 3:
|
|
|
return 'critical'
|
|
|
elif days_to_delivery <= 7:
|
|
|
return 'high'
|
|
|
elif days_to_delivery <= 15:
|
|
|
return 'medium'
|
|
|
elif days_to_delivery <= 30:
|
|
|
return 'low'
|
|
|
else:
|
|
|
return 'none'
|
|
|
|
|
|
def _analyze_liquidity(self, data: pd.DataFrame) -> Dict:
|
|
|
"""分析流动性"""
|
|
|
# 计算成交量指标
|
|
|
avg_volume = data['volume'].tail(20).mean()
|
|
|
volume_trend = self._analyze_volume_trend(data['volume'])
|
|
|
|
|
|
# 计算持仓量指标
|
|
|
avg_open_interest = data['open_interest'].tail(20).mean()
|
|
|
oi_trend = self._analyze_oi_trend(data['open_interest'])
|
|
|
|
|
|
# 计算买卖价差(简化处理)
|
|
|
# 实际应该使用Tick数据计算
|
|
|
bid_ask_spread = self._estimate_bid_ask_spread(data)
|
|
|
|
|
|
# 计算流动性评分
|
|
|
liquidity_score = self._calculate_liquidity_score(avg_volume, volume_trend, avg_open_interest, oi_trend, bid_ask_spread)
|
|
|
|
|
|
# 确定流动性风险级别
|
|
|
liquidity_risk = self._calculate_liquidity_risk(liquidity_score)
|
|
|
|
|
|
return {
|
|
|
'avg_volume': avg_volume,
|
|
|
'volume_trend': volume_trend,
|
|
|
'avg_open_interest': avg_open_interest,
|
|
|
'oi_trend': oi_trend,
|
|
|
'bid_ask_spread': bid_ask_spread,
|
|
|
'liquidity_score': liquidity_score,
|
|
|
'liquidity_risk': liquidity_risk
|
|
|
}
|
|
|
|
|
|
def _analyze_volume_trend(self, volume_series: pd.Series) -> str:
|
|
|
"""分析成交量趋势"""
|
|
|
if len(volume_series) < 10:
|
|
|
return 'stable'
|
|
|
|
|
|
# 计算短期和长期移动平均线
|
|
|
short_ma = volume_series.tail(10).mean()
|
|
|
long_ma = volume_series.tail(30).mean()
|
|
|
|
|
|
if short_ma > long_ma * 1.1:
|
|
|
return 'increasing'
|
|
|
elif short_ma < long_ma * 0.9:
|
|
|
return 'decreasing'
|
|
|
else:
|
|
|
return 'stable'
|
|
|
|
|
|
def _analyze_oi_trend(self, oi_series: pd.Series) -> str:
|
|
|
"""分析持仓量趋势"""
|
|
|
if len(oi_series) < 10:
|
|
|
return 'stable'
|
|
|
|
|
|
# 计算短期和长期移动平均线
|
|
|
short_ma = oi_series.tail(10).mean()
|
|
|
long_ma = oi_series.tail(30).mean()
|
|
|
|
|
|
if short_ma > long_ma * 1.1:
|
|
|
return 'increasing'
|
|
|
elif short_ma < long_ma * 0.9:
|
|
|
return 'decreasing'
|
|
|
else:
|
|
|
return 'stable'
|
|
|
|
|
|
def _estimate_bid_ask_spread(self, data: pd.DataFrame) -> float:
|
|
|
"""估算买卖价差"""
|
|
|
# 简化处理,使用收盘价的波动来估算
|
|
|
price_volatility = data['close'].tail(20).std()
|
|
|
# 假设价差为波动率的10%
|
|
|
return price_volatility * 0.1
|
|
|
|
|
|
def _calculate_liquidity_score(self, avg_volume: float, volume_trend: str,
|
|
|
avg_open_interest: float, oi_trend: str,
|
|
|
bid_ask_spread: float) -> float:
|
|
|
"""计算流动性评分"""
|
|
|
# 基础分数
|
|
|
base_score = 100
|
|
|
|
|
|
# 成交量因素
|
|
|
if avg_volume < 1000:
|
|
|
base_score -= 30
|
|
|
elif avg_volume < 5000:
|
|
|
base_score -= 15
|
|
|
|
|
|
# 成交量趋势因素
|
|
|
if volume_trend == 'decreasing':
|
|
|
base_score -= 20
|
|
|
elif volume_trend == 'increasing':
|
|
|
base_score += 10
|
|
|
|
|
|
# 持仓量因素
|
|
|
if avg_open_interest < 5000:
|
|
|
base_score -= 20
|
|
|
elif avg_open_interest < 20000:
|
|
|
base_score -= 10
|
|
|
|
|
|
# 持仓量趋势因素
|
|
|
if oi_trend == 'decreasing':
|
|
|
base_score -= 15
|
|
|
elif oi_trend == 'increasing':
|
|
|
base_score += 5
|
|
|
|
|
|
# 买卖价差因素
|
|
|
if bid_ask_spread > 0.5:
|
|
|
base_score -= 25
|
|
|
elif bid_ask_spread > 0.2:
|
|
|
base_score -= 10
|
|
|
|
|
|
# 确保分数在0-100之间
|
|
|
return max(0, min(100, base_score))
|
|
|
|
|
|
def _calculate_liquidity_risk(self, liquidity_score: float) -> str:
|
|
|
"""计算流动性风险"""
|
|
|
if liquidity_score < 30:
|
|
|
return 'high'
|
|
|
elif liquidity_score < 60:
|
|
|
return 'medium'
|
|
|
else:
|
|
|
return 'low'
|
|
|
|
|
|
def _analyze_spread(self, symbol: str) -> Dict:
|
|
|
"""分析价差"""
|
|
|
# 简化处理,实际应该比较当前合约和下一个合约的价差
|
|
|
# 这里返回模拟数据
|
|
|
return {
|
|
|
'current_next_spread': 5.2,
|
|
|
'spread_trend': 'stable',
|
|
|
'spread_ratio': 0.0015
|
|
|
}
|
|
|
|
|
|
def _generate_rollover_warning(self, delivery_info: Dict, liquidity_info: Dict) -> Dict:
|
|
|
"""生成换月预警"""
|
|
|
warning_level = delivery_info['warning_level']
|
|
|
liquidity_risk = liquidity_info['liquidity_risk']
|
|
|
|
|
|
# 综合预警
|
|
|
overall_warning = 'none'
|
|
|
if warning_level in ['critical', 'high'] or liquidity_risk == 'high':
|
|
|
overall_warning = 'high'
|
|
|
elif warning_level == 'medium' or liquidity_risk == 'medium':
|
|
|
overall_warning = 'medium'
|
|
|
elif warning_level == 'low':
|
|
|
overall_warning = 'low'
|
|
|
|
|
|
# 预警信息
|
|
|
warning_message = self._generate_warning_message(warning_level, liquidity_risk)
|
|
|
|
|
|
# 建议操作
|
|
|
recommended_actions = self._generate_recommended_actions(warning_level, liquidity_risk)
|
|
|
|
|
|
return {
|
|
|
'overall_warning': overall_warning,
|
|
|
'warning_message': warning_message,
|
|
|
'recommended_actions': recommended_actions
|
|
|
}
|
|
|
|
|
|
def _generate_warning_message(self, warning_level: str, liquidity_risk: str) -> str:
|
|
|
"""生成预警信息"""
|
|
|
messages = []
|
|
|
|
|
|
if warning_level == 'critical':
|
|
|
messages.append('合约即将到期,距离交割日不足3天')
|
|
|
elif warning_level == 'high':
|
|
|
messages.append('合约接近到期,距离交割日不足7天')
|
|
|
elif warning_level == 'medium':
|
|
|
messages.append('合约距离交割日不足15天,建议开始关注换月')
|
|
|
|
|
|
if liquidity_risk == 'high':
|
|
|
messages.append('流动性风险较高,可能影响交易执行')
|
|
|
elif liquidity_risk == 'medium':
|
|
|
messages.append('流动性风险中等,建议谨慎交易')
|
|
|
|
|
|
if not messages:
|
|
|
return '合约状态正常,无需特殊关注'
|
|
|
|
|
|
return '; '.join(messages)
|
|
|
|
|
|
def _generate_recommended_actions(self, warning_level: str, liquidity_risk: str) -> List[str]:
|
|
|
"""生成建议操作"""
|
|
|
actions = []
|
|
|
|
|
|
if warning_level in ['critical', 'high']:
|
|
|
actions.append('立即开始换月操作')
|
|
|
actions.append('逐步减仓当前合约')
|
|
|
actions.append('在新合约建立相应仓位')
|
|
|
elif warning_level == 'medium':
|
|
|
actions.append('开始评估换月时机')
|
|
|
actions.append('关注新合约流动性')
|
|
|
|
|
|
if liquidity_risk == 'high':
|
|
|
actions.append('减小单笔交易规模')
|
|
|
actions.append('使用限价单而非市价单')
|
|
|
actions.append('考虑提前换月')
|
|
|
|
|
|
return actions
|
|
|
|
|
|
def _generate_position_adjustment(self, delivery_info: Dict, liquidity_info: Dict) -> Dict:
|
|
|
"""生成仓位调整建议"""
|
|
|
days_to_delivery = delivery_info['days_to_delivery']
|
|
|
warning_level = delivery_info['warning_level']
|
|
|
liquidity_risk = liquidity_info['liquidity_risk']
|
|
|
|
|
|
# 计算减仓比例
|
|
|
reduction_ratio = self._calculate_reduction_ratio(days_to_delivery, warning_level, liquidity_risk)
|
|
|
|
|
|
# 计算建议的减仓时间表
|
|
|
reduction_schedule = self._generate_reduction_schedule(days_to_delivery, reduction_ratio)
|
|
|
|
|
|
# 计算新合约建仓建议
|
|
|
new_contract_adjustment = self._generate_new_contract_adjustment(reduction_ratio)
|
|
|
|
|
|
return {
|
|
|
'reduction_ratio': reduction_ratio,
|
|
|
'reduction_schedule': reduction_schedule,
|
|
|
'new_contract_adjustment': new_contract_adjustment
|
|
|
}
|
|
|
|
|
|
def _calculate_reduction_ratio(self, days_to_delivery: int, warning_level: str, liquidity_risk: str) -> float:
|
|
|
"""计算减仓比例"""
|
|
|
# 基础减仓比例
|
|
|
base_ratio = 0.0
|
|
|
|
|
|
if warning_level == 'critical':
|
|
|
base_ratio = 0.9 # 减仓90%
|
|
|
elif warning_level == 'high':
|
|
|
base_ratio = 0.7 # 减仓70%
|
|
|
elif warning_level == 'medium':
|
|
|
base_ratio = 0.4 # 减仓40%
|
|
|
elif warning_level == 'low':
|
|
|
base_ratio = 0.2 # 减仓20%
|
|
|
|
|
|
# 流动性风险调整
|
|
|
if liquidity_risk == 'high':
|
|
|
base_ratio = min(1.0, base_ratio + 0.2)
|
|
|
elif liquidity_risk == 'medium':
|
|
|
base_ratio = min(1.0, base_ratio + 0.1)
|
|
|
|
|
|
return base_ratio
|
|
|
|
|
|
def _generate_reduction_schedule(self, days_to_delivery: int, reduction_ratio: float) -> List[Dict]:
|
|
|
"""生成减仓时间表"""
|
|
|
schedule = []
|
|
|
|
|
|
if days_to_delivery <= 3:
|
|
|
# 紧急减仓
|
|
|
schedule.append({
|
|
|
'timeframe': '今日',
|
|
|
'reduction_ratio': reduction_ratio
|
|
|
})
|
|
|
elif days_to_delivery <= 7:
|
|
|
# 快速减仓
|
|
|
daily_ratio = reduction_ratio / 3
|
|
|
for i in range(3):
|
|
|
schedule.append({
|
|
|
'timeframe': f'{i+1}天内',
|
|
|
'reduction_ratio': daily_ratio
|
|
|
})
|
|
|
elif days_to_delivery <= 15:
|
|
|
# 逐步减仓
|
|
|
daily_ratio = reduction_ratio / 5
|
|
|
for i in range(5):
|
|
|
schedule.append({
|
|
|
'timeframe': f'{i+1}天内',
|
|
|
'reduction_ratio': daily_ratio
|
|
|
})
|
|
|
elif days_to_delivery <= 30:
|
|
|
# 缓慢减仓
|
|
|
weekly_ratio = reduction_ratio / 2
|
|
|
schedule.append({
|
|
|
'timeframe': '第一周',
|
|
|
'reduction_ratio': weekly_ratio
|
|
|
})
|
|
|
schedule.append({
|
|
|
'timeframe': '第二周',
|
|
|
'reduction_ratio': weekly_ratio
|
|
|
})
|
|
|
|
|
|
return schedule
|
|
|
|
|
|
def _generate_new_contract_adjustment(self, reduction_ratio: float) -> Dict:
|
|
|
"""生成新合约建仓建议"""
|
|
|
# 建议在新合约建立与原合约相同方向的仓位
|
|
|
# 建仓比例应与减仓比例对应
|
|
|
return {
|
|
|
'direction': 'same_as_current',
|
|
|
'target_ratio': reduction_ratio,
|
|
|
'execution_strategy': 'gradual',
|
|
|
'considerations': [
|
|
|
'关注新合约流动性',
|
|
|
'注意合约间价差',
|
|
|
'避免在换月高峰期交易'
|
|
|
]
|
|
|
}
|
|
|
|
|
|
def monitor_rollover_risk(self, symbol: str, data: pd.DataFrame, position_size: float) -> Dict:
|
|
|
"""监控换月风险"""
|
|
|
# 分析换月情况
|
|
|
rollover_analysis = self.analyze_rollover(symbol, data)
|
|
|
|
|
|
# 计算风险暴露
|
|
|
risk_exposure = self._calculate_risk_exposure(position_size, rollover_analysis)
|
|
|
|
|
|
# 生成风险报告
|
|
|
risk_report = {
|
|
|
'symbol': symbol,
|
|
|
'position_size': position_size,
|
|
|
'rollover_analysis': rollover_analysis,
|
|
|
'risk_exposure': risk_exposure,
|
|
|
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
|
}
|
|
|
|
|
|
return risk_report
|
|
|
|
|
|
def _calculate_risk_exposure(self, position_size: float, rollover_analysis: Dict) -> Dict:
|
|
|
"""计算风险暴露"""
|
|
|
warning_level = rollover_analysis['warning_level']
|
|
|
liquidity_risk = rollover_analysis.get('liquidity_risk', 'low')
|
|
|
|
|
|
# 基础风险分数
|
|
|
base_risk = 0
|
|
|
|
|
|
if warning_level == 'critical':
|
|
|
base_risk = 90
|
|
|
elif warning_level == 'high':
|
|
|
base_risk = 70
|
|
|
elif warning_level == 'medium':
|
|
|
base_risk = 40
|
|
|
elif warning_level == 'low':
|
|
|
base_risk = 20
|
|
|
|
|
|
# 流动性风险调整
|
|
|
if liquidity_risk == 'high':
|
|
|
base_risk += 20
|
|
|
elif liquidity_risk == 'medium':
|
|
|
base_risk += 10
|
|
|
|
|
|
# 仓位大小调整
|
|
|
if position_size > 10:
|
|
|
base_risk += 15
|
|
|
elif position_size > 5:
|
|
|
base_risk += 5
|
|
|
|
|
|
# 确保风险分数在0-100之间
|
|
|
risk_score = max(0, min(100, base_risk))
|
|
|
|
|
|
# 风险等级
|
|
|
risk_level = 'low'
|
|
|
if risk_score >= 80:
|
|
|
risk_level = 'critical'
|
|
|
elif risk_score >= 60:
|
|
|
risk_level = 'high'
|
|
|
elif risk_score >= 30:
|
|
|
risk_level = 'medium'
|
|
|
|
|
|
return {
|
|
|
'risk_score': risk_score,
|
|
|
'risk_level': risk_level,
|
|
|
'recommendations': self._generate_risk_recommendations(risk_level)
|
|
|
}
|
|
|
|
|
|
def _generate_risk_recommendations(self, risk_level: str) -> List[str]:
|
|
|
"""生成风险建议"""
|
|
|
recommendations = []
|
|
|
|
|
|
if risk_level == 'critical':
|
|
|
recommendations.append('立即减仓至最小仓位')
|
|
|
recommendations.append('优先处理换月操作')
|
|
|
recommendations.append('密切监控市场流动性')
|
|
|
elif risk_level == 'high':
|
|
|
recommendations.append('大幅减仓当前合约')
|
|
|
recommendations.append('加速换月进程')
|
|
|
recommendations.append('使用限价单控制交易成本')
|
|
|
elif risk_level == 'medium':
|
|
|
recommendations.append('开始有序减仓')
|
|
|
recommendations.append('评估换月时机')
|
|
|
recommendations.append('关注新合约表现')
|
|
|
else:
|
|
|
recommendations.append('保持正常交易')
|
|
|
recommendations.append('定期监控合约到期情况')
|
|
|
|
|
|
return recommendations
|