初始化项目,提交基础代码(首页、分析详情)已支持

master
Lxy 4 months ago
commit b167d953db

@ -0,0 +1,89 @@
# AI 期货分析系统运行指南
## 1. 环境准备
### 1.1 系统要求
- 操作系统 : Windows 10/11 或 Linux/macOS
- Python 版本 : Python 3.8 及以上
- 内存 : 至少 4GB
- 存储空间 : 至少 500MB
### 1.2 检查 Python 环境
```
# 检查 Python 版本
python --version
# 检查 pip 版本
pip --version
```
## 2. 安装依赖
### 2.1 安装核心依赖
在项目根目录执行:
```
pip install -r requirements.txt
```
### 2.2 解决可能的依赖问题
- tqsdk 导入失败 : 系统会自动使用模拟数据,不影响基本功能
- TA-Lib 安装失败 : 可以注释掉该依赖,系统会使用内置的技术分析函数
## 3. 配置设置
### 3.1 创建环境配置文件
复制 .env.example 文件并重命名为 .env
```
# Windows
copy .env.example .env
# Linux/macOS
cp .env.example .env
```
### 3.2 配置关键参数
编辑 .env 文件,设置以下参数:
- DEEPSEEK_API_KEY : DeepSeek API 密钥(可选,不设置会使用模拟响应)
- DB_PATH : 数据库存储路径(默认即可)
- REVIEW_TIMES : 自动复盘时间默认09:00,12:30,15:30
## 4. 运行系统
### 4.1 运行系统测试
验证所有组件是否正常工作:
```
python test_system.py
```
### 4.2 运行功能演示
体验完整的系统功能:
```
python demo.py
```
### 4.3 预期输出
运行 demo.py 后,您将看到:
- 系统初始化信息
- 数据获取和分析过程
- 多维度分析结果
- AI 智能研判
- 交易建议生成
- 综合分析报告
## 5. 系统使用说明
### 5.1 核心功能
- 数据获取 : 支持实时数据和模拟数据
- 趋势分析 : 基于 ADX 和多周期分析
- 风险控制 : ATR 动态止损和仓位管理
- 资金监控 : 持仓量和资金流向分析
- AI 研判 : DeepSeek 大模型智能分析
- 换月预警 : 交割日风险提示
### 5.2 扩展功能
- 定时任务 : 可配置的自动复盘
- 数据存储 : 历史分析结果持久化
- API 接口 : 可扩展为 Web 服务
## 6. 故障排除
### 6.1 常见问题
- 模块导入失败 : 检查依赖安装是否完整
- API 调用失败 : 检查网络连接和 API 密钥
- 数据为空 : 系统会自动使用模拟数据,确保数据获取模块正常
### 6.2 日志查看
系统运行过程中会输出详细日志,可根据日志信息定位问题。
## 7. 后续开发建议
- Web 前端 : 开发可视化界面
- 实时行情 : 接入更多数据源
- 策略回测 : 增加历史数据回测功能
- 多模型融合 : 集成多个 AI 模型

25
.env

@ -0,0 +1,25 @@
# API配置
OPENAI_API_KEY=
DEEPSEEK_API_KEY=
DEEPSEEK_API_URL=https://api.deepseek.com/v1/chat/completions
# 天勤TQSDK配置
# 填写你的TQSDK账号密码以使用真实数据
TQSDK_USERNAME=windsdreamer
TQSDK_PASSWORD=1qazse42W3
TQSERVER_HOST=api.shinnytech.com
TQSERVER_PORT=7777
# 数据库配置
DB_PATH=./data/futures_analysis.db
# 风险配置
MAX_RISK_PERCENT=0.02
MIN_PROFIT_LOSS_RATIO=1.5
# 策略配置
DEFAULT_ATR_MULTIPLIER=2.0
DEFAULT_ADX_THRESHOLD=20
# 定时任务配置
REVIEW_TIMES=09:00,12:30,15:30

@ -0,0 +1,25 @@
# API配置
OPENAI_API_KEY=
DEEPSEEK_API_KEY=
DEEPSEEK_API_URL=https://api.deepseek.com/v1/chat/completions
# 天勤TQSDK配置
# 填写你的TQSDK账号密码以使用真实数据
TQSDK_USERNAME=
TQSDK_PASSWORD=
TQSERVER_HOST=api.shinnytech.com
TQSERVER_PORT=7777
# 数据库配置
DB_PATH=./data/futures_analysis.db
# 风险配置
MAX_RISK_PERCENT=0.02
MIN_PROFIT_LOSS_RATIO=1.5
# 策略配置
DEFAULT_ATR_MULTIPLIER=2.0
DEFAULT_ADX_THRESHOLD=20
# 定时任务配置
REVIEW_TIMES=09:00,12:30,15:30

Binary file not shown.

@ -0,0 +1,265 @@
#!/usr/bin/env python3
# 期货分析系统演示脚本
import sys
import json
from datetime import datetime
from qihuo_analyzer.data.data_fetcher import DataFetcher
from qihuo_analyzer.data.data_storage import DataStorage
from qihuo_analyzer.modules.trend_filter import TrendFilter
from qihuo_analyzer.modules.risk_manager import RiskManager
from qihuo_analyzer.modules.fund_flow_monitor import FundFlowMonitor
from qihuo_analyzer.modules.support_resistance import SupportResistance
from qihuo_analyzer.modules.rollover_detector import RolloverDetector
from qihuo_analyzer.modules.deepseek_agent import DeepseekAgent
from qihuo_analyzer.core.models import AnalysisResult
def print_header(title):
"""打印标题"""
print(f"\n{'='*60}")
print(f"{title:^60}")
print(f"{'='*60}")
def print_section(title):
"""打印章节标题"""
print(f"\n{'-'*40}")
print(f"{title:^40}")
print(f"{'-'*40}")
def print_json(data, indent=2):
"""打印JSON格式数据"""
print(json.dumps(data, ensure_ascii=False, indent=indent))
def main():
"""主函数"""
print_header("AI 期货分析系统演示")
print(f"当前时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
# 初始化组件
print_section("初始化系统组件")
data_fetcher = DataFetcher()
data_storage = DataStorage()
trend_filter = TrendFilter()
risk_manager = RiskManager()
fund_flow_monitor = FundFlowMonitor()
support_resistance = SupportResistance()
rollover_detector = RolloverDetector()
deepseek_agent = DeepseekAgent()
print("✅ 系统组件初始化完成")
# 选择合约
symbol = "CU2309" # 铜期货合约
print_section(f"分析合约: {symbol}")
# 获取K线数据
print("📊 获取K线数据...")
kline_data = data_fetcher.get_kline_data(symbol, "1d", 200)
if kline_data.empty:
print("❌ 获取K线数据失败")
return
print(f"✅ 获取K线数据成功{len(kline_data)} 条记录")
print(f"最新价格: {kline_data['close'].iloc[-1]:.2f}")
# 保存K线数据到数据库
data_storage.save_kline_data(symbol, "1d", kline_data)
print("✅ K线数据已保存到数据库")
# 1. 趋势分析
print_section("1. 趋势分析")
trend_analysis = trend_filter.analyze_trend(kline_data)
win_rate = trend_filter.calculate_win_rate(kline_data)
cycle = trend_filter.judge_cycle(kline_data)
print("📈 趋势分析结果:")
print(f"ADX: {trend_analysis['adx']:.2f}")
print(f"趋势强度: {trend_analysis['trend_strength']}")
print(f"趋势方向: {trend_analysis['trend_direction']}")
print(f"综合趋势: {trend_analysis['overall_trend']}")
print(f"胜率: {win_rate:.2f}%")
print(f"推荐周期: {cycle}")
# 2. 资金流向分析
print_section("2. 资金流向分析")
fund_flow_analysis = fund_flow_monitor.analyze_fund_flow(kline_data)
print("💹 资金流向分析结果:")
print(f"资金流向强度: {fund_flow_analysis['fund_flow_strength']:.2f}")
print(f"持仓量趋势: {fund_flow_analysis['oi_trend']}")
print(f"量价配合度: {fund_flow_analysis['volume_price_fit']:.2f}%")
print(f"量价背离: {fund_flow_analysis['divergence']}")
print(f"资金面信号: {fund_flow_analysis['fund_signal']}")
# 3. 压力支撑分析
print_section("3. 压力支撑分析")
sr_analysis = support_resistance.analyze_support_resistance(kline_data)
print("🛡️ 压力支撑分析结果:")
support_levels = sr_analysis['support_resistance_levels']['support_levels']
resistance_levels = sr_analysis['support_resistance_levels']['resistance_levels']
print(f"支撑位: {[f'{level:.2f}' for level in support_levels[:3]]}")
print(f"阻力位: {[f'{level:.2f}' for level in resistance_levels[:3]]}")
# 4. 风险分析
print_section("4. 风险分析")
current_price = kline_data['close'].iloc[-1]
atr = trend_analysis.get('atr', 20)
# 计算止损位
stop_loss_long = risk_manager.calculate_stop_loss(kline_data, current_price, "long")
stop_loss_short = risk_manager.calculate_stop_loss(kline_data, current_price, "short")
# 计算仓位大小
account_balance = 1000000 # 100万账户
position_info = risk_manager.calculate_position_size(account_balance, kline_data, "long", current_price)
print("⚡ 风险分析结果:")
print(f"ATR: {atr:.2f}")
print(f"做多止损: {stop_loss_long:.2f}")
print(f"做空止损: {stop_loss_short:.2f}")
print(f"建议仓位: {position_info['suggested_units']}")
print(f"风险比例: {position_info['actual_risk_percent']*100:.2f}%")
print(f"杠杆比例: {position_info['leverage']:.2f}")
# 5. 换月分析
print_section("5. 换月分析")
rollover_analysis = rollover_detector.analyze_rollover(symbol, kline_data)
print("📅 换月分析结果:")
print(f"交割日期: {rollover_analysis['expire_date']}")
print(f"距离交割日: {rollover_analysis['days_to_delivery']}")
print(f"预警级别: {rollover_analysis['warning_level']}")
print(f"流动性风险: {rollover_analysis['liquidity_risk']}")
print(f"换月建议: {rollover_analysis['rollover_warning']['warning_message']}")
# 6. AI 智能研判
print_section("6. AI 智能研判")
# 准备数据
market_data = {
'symbol': symbol,
'latest_price': current_price,
'volume': kline_data['volume'].iloc[-1],
'open_interest': kline_data['open_interest'].iloc[-1],
'timeframe': '1d'
}
technical_indicators = {
'macd': {'signal': '金叉'},
'rsi': 55,
'bollinger': {'position': '中轨附近'},
'kdj': {'signal': '金叉'},
'atr': atr
}
trend_data = {
'adx': trend_analysis['adx'],
'trend_strength': trend_analysis['trend_strength'],
'trend_direction': trend_analysis['trend_direction'],
'ma_relationship': trend_analysis['ma_relationship'],
'multi_period_analysis': trend_analysis['multi_period_analysis'],
'overall_trend': trend_analysis['overall_trend'],
'win_rate': win_rate
}
risk_metrics = {
'stop_loss': stop_loss_long,
'target_price': resistance_levels[0] if resistance_levels else current_price * 1.05,
'profit_loss_ratio': 1.8,
'position_size': position_info['suggested_units'],
'risk_ratio': position_info['actual_risk_percent'] * 100
}
# AI 分析
ai_analysis = deepseek_agent.analyze_market(market_data, technical_indicators, trend_data, risk_metrics)
print("🤖 AI 分析结果:")
print(f"趋势判断: {ai_analysis.get('trend_judgment', '未知')}")
print(f"胜率评估: {ai_analysis.get('win_rate_assessment', '未知')}")
print(f"风险预警: {ai_analysis.get('risk_warning', '未知')}")
print(f"交易建议: {ai_analysis.get('trade_recommendation', '未知')}")
print(f"分析逻辑: {ai_analysis.get('analysis_logic', '未知')}")
# 7. 生成交易建议
print_section("7. 交易建议")
recommendation = deepseek_agent.generate_trade_recommendation(ai_analysis)
print("📋 交易建议详情:")
print(f"交易方向: {recommendation.get('direction', '未知')}")
print(f"入场价格: {recommendation.get('entry_price', '未知')}")
print(f"止损价格: {recommendation.get('stop_loss', '未知')}")
print(f"目标价格: {recommendation.get('target_price', '未知')}")
print(f"建议仓位: {recommendation.get('position_size', '未知')}")
print(f"执行计划: {recommendation.get('execution_plan', '未知')}")
print(f"风险提示: {recommendation.get('risk_tips', '未知')}")
# 8. 保存分析结果
print_section("8. 保存分析结果")
# 创建分析结果对象
analysis_result = AnalysisResult(symbol)
analysis_result.trend = trend_analysis['overall_trend']
analysis_result.probability = win_rate
analysis_result.direction = recommendation.get('direction', 'wait')
analysis_result.cycle = cycle
analysis_result.atr = atr
analysis_result.adx = trend_analysis['adx']
analysis_result.support = support_levels[0] if support_levels else None
analysis_result.resistance = resistance_levels[0] if resistance_levels else None
analysis_result.stop_loss = recommendation.get('stop_loss')
analysis_result.target_price = recommendation.get('target_price')
analysis_result.position_size = recommendation.get('position_size')
analysis_result.risk_ratio = position_info['actual_risk_percent'] * 100
analysis_result.fund_flow = fund_flow_analysis
analysis_result.signals = {
'trend': trend_analysis['overall_trend'],
'fund': fund_flow_analysis['fund_signal'],
'risk': rollover_analysis['liquidity_risk'],
'ai': recommendation.get('direction', 'wait')
}
# 保存到数据库
result_dict = analysis_result.to_dict()
success = data_storage.save_analysis_result(result_dict)
if success:
print("✅ 分析结果已保存到数据库")
else:
print("❌ 保存分析结果失败")
# 9. 生成综合报告
print_section("9. 综合分析报告")
print_header("AI 期货分析系统 - 综合报告")
print(f"合约: {symbol}")
print(f"分析时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"最新价格: {current_price:.2f}")
print(f"趋势判断: {trend_analysis['overall_trend']}")
print(f"胜率评估: {win_rate:.2f}%")
print(f"资金信号: {fund_flow_analysis['fund_signal']}")
print(f"交易方向: {recommendation.get('direction', 'wait')}")
print(f"建议仓位: {recommendation.get('position_size', '未知')}")
print(f"止损价格: {recommendation.get('stop_loss', '未知')}")
print(f"目标价格: {recommendation.get('target_price', '未知')}")
print(f"风险等级: {rollover_analysis['liquidity_risk']}")
print(f"换月预警: {rollover_analysis['rollover_warning']['overall_warning']}")
print_header("演示完成")
print("感谢使用 AI 期货分析系统!")
print("系统已完成以下功能:")
print("✅ 数据获取与存储")
print("✅ 趋势分析与胜率计算")
print("✅ 资金流向监控")
print("✅ 压力支撑分析")
print("✅ 风险控制与仓位管理")
print("✅ 换月预警")
print("✅ AI 智能研判")
print("✅ 交易建议生成")
if __name__ == "__main__":
main()

@ -0,0 +1,3 @@
# 期货分析系统版本信息
__version__ = "1.0.0"
__author__ = "AI Futures Analyzer"

@ -0,0 +1,104 @@
# 核心数据模型
import datetime
from typing import Dict, List, Optional, Tuple
import pandas as pd
class MarketData:
"""市场数据模型"""
def __init__(self, symbol: str, kline_data: pd.DataFrame):
self.symbol = symbol
self.kline_data = kline_data
self.timestamp = datetime.datetime.now()
def get_latest_price(self) -> float:
"""获取最新价格"""
return float(self.kline_data['close'].iloc[-1])
def get_price_range(self, period: int = 20) -> Tuple[float, float]:
"""获取价格范围"""
prices = self.kline_data['close'].tail(period)
return float(prices.min()), float(prices.max())
class AnalysisResult:
"""分析结果模型"""
def __init__(self, symbol: str):
self.symbol = symbol
self.timestamp = datetime.datetime.now()
self.trend: Optional[str] = None # bullish, bearish, neutral
self.probability: Optional[float] = None # 胜率
self.direction: Optional[str] = None # long, short, wait
self.cycle: Optional[str] = None # short, medium, long
self.atr: Optional[float] = None # 真实波动幅度
self.adx: Optional[float] = None # 平均趋向指标
self.support: Optional[float] = None # 支撑位
self.resistance: Optional[float] = None # 阻力位
self.stop_loss: Optional[float] = None # 止损位
self.target_price: Optional[float] = None # 目标价
self.position_size: Optional[float] = None # 建议仓位
self.risk_ratio: Optional[float] = None # 风险比率
self.fund_flow: Optional[Dict[str, float]] = None # 资金流向
self.signals: Dict[str, str] = {} # 各维度信号
def to_dict(self) -> Dict:
"""转换为字典"""
return {
'symbol': self.symbol,
'timestamp': self.timestamp.isoformat(),
'trend': self.trend,
'probability': self.probability,
'direction': self.direction,
'cycle': self.cycle,
'atr': self.atr,
'adx': self.adx,
'support': self.support,
'resistance': self.resistance,
'stop_loss': self.stop_loss,
'target_price': self.target_price,
'position_size': self.position_size,
'risk_ratio': self.risk_ratio,
'fund_flow': self.fund_flow,
'signals': self.signals
}
class StrategyConfig:
"""策略配置模型"""
def __init__(self):
# 技术指标参数
self.macd_fast = 12
self.macd_slow = 26
self.macd_signal = 9
self.rsi_period = 14
self.bollinger_period = 20
self.bollinger_std = 2
self.kdj_period = 9
self.kdj_signal = 3
self.adx_period = 14
# 趋势过滤参数
self.short_ma = 20
self.long_ma = 60
# 风险控制参数
self.atr_multiplier = 2.0
self.max_risk_percent = 0.02
self.min_profit_loss_ratio = 1.5
# 资金监控参数
self.volume_change_threshold = 0.05
self.open_interest_change_threshold = 0.05
class RiskParams:
"""风险参数模型"""
def __init__(self, account_balance: float):
self.account_balance = account_balance
self.max_risk_amount = account_balance * 0.02
self.max_position_percent = 0.3
self.max_leverage = 5

@ -0,0 +1,461 @@
# 数据获取模块
import os
import time
import pandas as pd
from typing import Dict, Optional, List
from qihuo_analyzer.utils.config_manager import config_manager
# 尝试导入tqsdk如果失败则使用模拟数据
try:
from tqsdk import TqApi, TqAuth
TQSDK_AVAILABLE = True
except Exception as e:
print(f"tqsdk导入失败{e},将使用模拟数据")
TQSDK_AVAILABLE = False
class DataFetcher:
"""数据获取器"""
def __init__(self):
self.api = None
def connect(self) -> bool:
"""连接API"""
try:
if TQSDK_AVAILABLE:
# 使用天勤TQSDK连接
from qihuo_analyzer.utils.config_manager import config_manager
username = os.getenv('TQSDK_USERNAME', '')
password = os.getenv('TQSDK_PASSWORD', '')
if username and password:
self.api = TqApi(auth=TqAuth(username, password))
print("API连接成功")
return True
else:
print("TQSDK账号密码未配置将使用模拟数据")
self.api = None
return False
else:
# 模拟API用于测试
print("使用模拟API")
self.api = None
return False
except Exception as e:
print(f"API连接失败{e}")
# 模拟API用于测试
self.api = None
return False
def disconnect(self):
"""断开连接"""
if self.api:
try:
self.api.close()
print("API连接已断开")
except:
pass
def _convert_duration(self, duration: str) -> int:
"""将时间周期字符串转换为分钟数
Args:
duration: 时间周期 '1m', '5m', '15m', '1h', '1d'
Returns:
分钟数
"""
duration_map = {
'1m': 1,
'5m': 5,
'15m': 15,
'30m': 30,
'1h': 60,
'2h': 120,
'4h': 240,
'6h': 360,
'12h': 720,
'1d': 1440,
'1w': 10080
}
return duration_map.get(duration, 60) # 默认60分钟
def _convert_symbol(self, symbol: str) -> str:
"""将合约代码转换为TQSDK格式
Args:
symbol: 合约代码 'CU2603'
Returns:
TQSDK格式的合约代码 'SHFE.cu2603'
"""
# 交易所映射
exchange_map = {
'CU': 'SHFE', # 铜 - 上海期货交易所
'AL': 'SHFE', # 铝 - 上海期货交易所
'ZN': 'SHFE', # 锌 - 上海期货交易所
'PB': 'SHFE', # 铅 - 上海期货交易所
'NI': 'SHFE', # 镍 - 上海期货交易所
'SN': 'SHFE', # 锡 - 上海期货交易所
'AU': 'SHFE', # 黄金 - 上海期货交易所
'AG': 'SHFE', # 白银 - 上海期货交易所
'RB': 'SHFE', # 螺纹钢 - 上海期货交易所
'HC': 'SHFE', # 热轧卷板 - 上海期货交易所
'BU': 'SHFE', # 沥青 - 上海期货交易所
'RU': 'SHFE', # 橡胶 - 上海期货交易所
'FU': 'SHFE', # 燃油 - 上海期货交易所
'SC': 'INE', # 原油 - 上海国际能源交易中心
'I': 'DCE', # 铁矿石 - 大连商品交易所
'J': 'DCE', # 焦炭 - 大连商品交易所
'JM': 'DCE', # 焦煤 - 大连商品交易所
'A': 'DCE', # 大豆 - 大连商品交易所
'B': 'DCE', # 豆粕 - 大连商品交易所
'M': 'DCE', # 豆粕 - 大连商品交易所
'Y': 'DCE', # 豆油 - 大连商品交易所
'P': 'DCE', # 棕榈油 - 大连商品交易所
'C': 'DCE', # 玉米 - 大连商品交易所
'CS': 'DCE', # 玉米淀粉 - 大连商品交易所
'L': 'DCE', # 聚乙烯 - 大连商品交易所
'V': 'DCE', # 聚氯乙烯 - 大连商品交易所
'PP': 'DCE', # 聚丙烯 - 大连商品交易所
'TA': 'CZCE', # PTA - 郑州商品交易所
'CF': 'CZCE', # 棉花 - 郑州商品交易所
'SR': 'CZCE', # 白糖 - 郑州商品交易所
'MA': 'CZCE', # 甲醇 - 郑州商品交易所
'ZC': 'CZCE', # 动力煤 - 郑州商品交易所
'FG': 'CZCE', # 玻璃 - 郑州商品交易所
'RM': 'CZCE', # 菜籽粕 - 郑州商品交易所
'OI': 'CZCE', # 菜籽油 - 郑州商品交易所
'RS': 'CZCE', # 菜籽 - 郑州商品交易所
'WH': 'CZCE', # 强麦 - 郑州商品交易所
'JR': 'CZCE', # 粳稻 - 郑州商品交易所
'LR': 'CZCE', # 晚籼稻 - 郑州商品交易所
}
# 提取品种代码和合约月份
if len(symbol) >= 4:
product_code = symbol[:2].upper()
contract_month = symbol[2:].lower()
# 获取交易所代码
exchange = exchange_map.get(product_code, 'SHFE')
# 构建TQSDK格式的合约代码
tq_symbol = f"{exchange}.{product_code.lower()}{contract_month}"
return tq_symbol
else:
return symbol
def get_product_name_cn(self, symbol: str) -> str:
"""获取合约的中文名称
Args:
symbol: 合约代码 'CU2603'
Returns:
合约的中文名称 ''
"""
# 品种中文名称映射
product_name_map = {
'CU': '',
'AL': '',
'ZN': '',
'PB': '',
'NI': '',
'SN': '',
'AU': '黄金',
'AG': '白银',
'RB': '螺纹钢',
'HC': '热轧卷板',
'BU': '沥青',
'RU': '橡胶',
'FU': '燃油',
'SC': '原油',
'I': '铁矿石',
'J': '焦炭',
'JM': '焦煤',
'A': '大豆',
'B': '豆粕',
'M': '豆粕',
'Y': '豆油',
'P': '棕榈油',
'C': '玉米',
'CS': '玉米淀粉',
'L': '聚乙烯',
'V': '聚氯乙烯',
'PP': '聚丙烯',
'TA': 'PTA',
'CF': '棉花',
'SR': '白糖',
'MA': '甲醇',
'ZC': '动力煤',
'FG': '玻璃',
'RM': '菜籽粕',
'OI': '菜籽油',
'RS': '菜籽',
'WH': '强麦',
'JR': '粳稻',
'LR': '晚籼稻',
}
if len(symbol) >= 2:
product_code = symbol[:2].upper()
return product_name_map.get(product_code, product_code)
else:
return symbol
def get_kline_data(self, symbol: str, duration: str, count: int = 200) -> Optional[pd.DataFrame]:
"""获取K线数据
Args:
symbol: 合约代码
duration: 时间周期 '1m', '5m', '15m', '1h', '1d'
count: 数据数量
Returns:
K线数据DataFrame如果无法获取真实数据则返回None
"""
try:
if TQSDK_AVAILABLE and self.api:
# 转换合约代码为TQSDK格式
tq_symbol = self._convert_symbol(symbol)
print(f"使用TQSDK格式合约代码: {tq_symbol}")
# 转换时间周期为分钟数
duration_minutes = self._convert_duration(duration)
# 使用真实API获取数据
klines = self.api.get_kline_serial(tq_symbol, duration_minutes, data_length=count)
# 等待数据准备就绪
import time
start_time = time.time()
timeout = 5 # 5秒超时
while True:
if hasattr(klines, 'datetime') and len(klines.datetime) > 0:
break
if time.time() - start_time > timeout:
print("获取K线数据超时")
return None
time.sleep(0.1)
# 转换为DataFrame
data = {
'datetime': klines.datetime,
'open': klines.open,
'high': klines.high,
'low': klines.low,
'close': klines.close,
'volume': klines.volume,
'open_interest': klines.open_oi
}
df = pd.DataFrame(data)
df['datetime'] = pd.to_datetime(df['datetime'], unit='ns')
df.set_index('datetime', inplace=True)
print(f"成功获取K线数据数据长度: {len(df)}")
return df
else:
# 不再自动返回模拟数据返回None
print(f"无法获取真实数据:{'API未连接' if not self.api else 'TQSDK不可用'}")
return None
except Exception as e:
print(f"获取K线数据失败{e}")
# 不再自动返回模拟数据返回None
return None
def get_tick_data(self, symbol: str, count: int = 1000) -> Optional[pd.DataFrame]:
"""获取Tick数据"""
try:
if TQSDK_AVAILABLE and self.api:
# 使用真实API获取数据
ticks = self.api.get_tick_serial(symbol, data_length=count)
self.api.wait_update()
# 转换为DataFrame
data = {
'datetime': ticks.datetime,
'last_price': ticks.last_price,
'volume': ticks.volume,
'open_interest': ticks.open_interest,
'bid_price1': ticks.bid_price1,
'bid_volume1': ticks.bid_volume1,
'ask_price1': ticks.ask_price1,
'ask_volume1': ticks.ask_volume1
}
df = pd.DataFrame(data)
df['datetime'] = pd.to_datetime(df['datetime'], unit='ns')
df.set_index('datetime', inplace=True)
return df
else:
# 返回模拟数据
return self._get_mock_tick_data(symbol, count)
except Exception as e:
print(f"获取Tick数据失败{e}")
return self._get_mock_tick_data(symbol, count)
def get_contract_info(self, symbol: str) -> Optional[Dict]:
"""获取合约信息"""
try:
if TQSDK_AVAILABLE and self.api:
# 使用真实API获取数据
quote = self.api.get_quote(symbol)
self.api.wait_update()
return {
'symbol': symbol,
'name': quote.instrument_name,
'exchange': quote.exchange_id,
'product': quote.product_id,
'price_tick': quote.price_tick,
'volume_multiple': quote.volume_multiple,
'margin_rate': quote.margin_rate,
'expire_datetime': quote.expire_datetime,
'create_datetime': quote.create_datetime
}
else:
# 返回模拟数据
return self._get_mock_contract_info(symbol)
except Exception as e:
print(f"获取合约信息失败:{e}")
return self._get_mock_contract_info(symbol)
def get_market_data(self, symbols: List[str]) -> Dict[str, Dict]:
"""批量获取市场数据"""
market_data = {}
for symbol in symbols:
try:
if TQSDK_AVAILABLE and self.api:
quote = self.api.get_quote(symbol)
self.api.wait_update()
market_data[symbol] = {
'latest_price': quote.last_price,
'open': quote.open,
'high': quote.high,
'low': quote.low,
'pre_close': quote.pre_close,
'volume': quote.volume,
'open_interest': quote.open_interest,
'bid_price1': quote.bid_price1,
'ask_price1': quote.ask_price1
}
else:
# 模拟数据
market_data[symbol] = self._get_mock_market_data(symbol)
except Exception as e:
print(f"获取{symbol}市场数据失败:{e}")
market_data[symbol] = self._get_mock_market_data(symbol)
return market_data
def _get_mock_kline_data(self, symbol: str, duration: str, count: int) -> pd.DataFrame:
"""获取模拟K线数据"""
# 生成时间序列
end_time = pd.Timestamp.now()
if duration == '1m':
freq = '1T'
elif duration == '5m':
freq = '5T'
elif duration == '15m':
freq = '15T'
elif duration == '1h':
freq = '1H'
elif duration == '1d':
freq = '1D'
else:
freq = '1H'
datetime_index = pd.date_range(end=end_time, periods=count, freq=freq)
# 生成随机价格数据
base_price = 3500
price_changes = np.random.normal(0, 5, count)
prices = base_price + np.cumsum(price_changes)
# 生成其他数据
opens = prices * (1 + np.random.normal(0, 0.001, count))
highs = np.maximum(prices, opens) * (1 + np.random.normal(0, 0.002, count))
lows = np.minimum(prices, opens) * (1 - np.random.normal(0, 0.002, count))
volumes = np.random.randint(1000, 10000, count)
open_interests = np.random.randint(10000, 100000, count)
# 创建DataFrame
df = pd.DataFrame({
'open': opens,
'high': highs,
'low': lows,
'close': prices,
'volume': volumes,
'open_interest': open_interests
}, index=datetime_index)
return df
def _get_mock_tick_data(self, symbol: str, count: int) -> pd.DataFrame:
"""获取模拟Tick数据"""
# 生成时间序列
end_time = pd.Timestamp.now()
datetime_index = pd.date_range(end=end_time, periods=count, freq='1S')
# 生成随机价格数据
base_price = 3500
price_changes = np.random.normal(0, 0.5, count)
last_prices = base_price + np.cumsum(price_changes)
# 生成其他数据
volumes = np.random.randint(10, 100, count)
open_interests = np.random.randint(10000, 100000, count)
bid_prices = last_prices * (1 - np.random.normal(0, 0.0005, count))
ask_prices = last_prices * (1 + np.random.normal(0, 0.0005, count))
bid_volumes = np.random.randint(10, 50, count)
ask_volumes = np.random.randint(10, 50, count)
# 创建DataFrame
df = pd.DataFrame({
'last_price': last_prices,
'volume': volumes,
'open_interest': open_interests,
'bid_price1': bid_prices,
'bid_volume1': bid_volumes,
'ask_price1': ask_prices,
'ask_volume1': ask_volumes
}, index=datetime_index)
return df
def _get_mock_contract_info(self, symbol: str) -> Dict:
"""获取模拟合约信息"""
return {
'symbol': symbol,
'name': symbol,
'exchange': 'SHFE',
'product': symbol[:2],
'price_tick': 1,
'volume_multiple': 10,
'margin_rate': 0.1,
'expire_datetime': int(time.time() * 1e9) + 90 * 24 * 3600 * 1e9,
'create_datetime': int(time.time() * 1e9) - 180 * 24 * 3600 * 1e9
}
def _get_mock_market_data(self, symbol: str) -> Dict:
"""获取模拟市场数据"""
base_price = 3500
return {
'latest_price': base_price + np.random.normal(0, 10),
'open': base_price,
'high': base_price + 20,
'low': base_price - 20,
'pre_close': base_price,
'volume': np.random.randint(10000, 100000),
'open_interest': np.random.randint(100000, 1000000),
'bid_price1': base_price - 1,
'ask_price1': base_price + 1
}
# 导入numpy
import numpy as np

@ -0,0 +1,378 @@
# 数据存储模块
import sqlite3
import json
import os
from datetime import datetime
from typing import Dict, Optional, List
import pandas as pd
from qihuo_analyzer.utils.config_manager import config_manager
class DataStorage:
"""数据存储管理器"""
def __init__(self):
self.db_path = config_manager.db_path
self._init_database()
def _init_database(self):
"""初始化数据库"""
# 确保数据库目录存在
db_dir = os.path.dirname(self.db_path)
if db_dir and not os.path.exists(db_dir):
os.makedirs(db_dir)
# 连接数据库
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 创建表
# 分析结果表
cursor.execute('''
CREATE TABLE IF NOT EXISTS analysis_results (
id INTEGER PRIMARY KEY AUTOINCREMENT,
symbol TEXT NOT NULL,
timestamp TEXT NOT NULL,
trend TEXT,
probability REAL,
direction TEXT,
cycle TEXT,
atr REAL,
adx REAL,
support REAL,
resistance REAL,
stop_loss REAL,
target_price REAL,
position_size REAL,
risk_ratio REAL,
fund_flow TEXT,
signals TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
''')
# 历史K线数据表
cursor.execute('''
CREATE TABLE IF NOT EXISTS kline_data (
id INTEGER PRIMARY KEY AUTOINCREMENT,
symbol TEXT NOT NULL,
duration TEXT NOT NULL,
datetime TEXT NOT NULL,
open REAL,
high REAL,
low REAL,
close REAL,
volume INTEGER,
open_interest INTEGER,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
UNIQUE(symbol, duration, datetime)
)
''')
# 交易建议表
cursor.execute('''
CREATE TABLE IF NOT EXISTS trade_recommendations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
symbol TEXT NOT NULL,
timestamp TEXT NOT NULL,
direction TEXT,
entry_price REAL,
stop_loss REAL,
target_price REAL,
position_size REAL,
execution_plan TEXT,
risk_tips TEXT,
status TEXT DEFAULT 'pending',
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
''')
# 风险监控表
cursor.execute('''
CREATE TABLE IF NOT EXISTS risk_monitoring (
id INTEGER PRIMARY KEY AUTOINCREMENT,
symbol TEXT NOT NULL,
timestamp TEXT NOT NULL,
current_price REAL,
entry_price REAL,
stop_loss REAL,
target_price REAL,
current_profit REAL,
risk_status TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
''')
conn.commit()
conn.close()
def save_analysis_result(self, result: Dict) -> bool:
"""保存分析结果"""
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 准备数据
data = {
'symbol': result.get('symbol', ''),
'timestamp': result.get('timestamp', datetime.now().isoformat()),
'trend': result.get('trend'),
'probability': result.get('probability'),
'direction': result.get('direction'),
'cycle': result.get('cycle'),
'atr': result.get('atr'),
'adx': result.get('adx'),
'support': result.get('support'),
'resistance': result.get('resistance'),
'stop_loss': result.get('stop_loss'),
'target_price': result.get('target_price'),
'position_size': result.get('position_size'),
'risk_ratio': result.get('risk_ratio'),
'fund_flow': json.dumps(result.get('fund_flow', {})) if result.get('fund_flow') else None,
'signals': json.dumps(result.get('signals', {})) if result.get('signals') else None
}
# 插入数据
cursor.execute('''
INSERT INTO analysis_results (
symbol, timestamp, trend, probability, direction, cycle,
atr, adx, support, resistance, stop_loss, target_price,
position_size, risk_ratio, fund_flow, signals
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (
data['symbol'], data['timestamp'], data['trend'], data['probability'],
data['direction'], data['cycle'], data['atr'], data['adx'],
data['support'], data['resistance'], data['stop_loss'], data['target_price'],
data['position_size'], data['risk_ratio'], data['fund_flow'], data['signals']
))
conn.commit()
conn.close()
return True
except Exception as e:
print(f"保存分析结果失败:{e}")
return False
def save_kline_data(self, symbol: str, duration: str, df: pd.DataFrame) -> bool:
"""保存K线数据"""
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 批量插入数据
data_to_insert = []
for idx, row in df.iterrows():
data_to_insert.append((
symbol, duration, idx.isoformat(),
row['open'], row['high'], row['low'], row['close'],
row['volume'], row['open_interest']
))
# 使用事务批量插入
if data_to_insert:
cursor.executemany('''
INSERT OR IGNORE INTO kline_data (
symbol, duration, datetime, open, high, low, close, volume, open_interest
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
''', data_to_insert)
conn.commit()
conn.close()
return True
except Exception as e:
print(f"保存K线数据失败{e}")
return False
def save_trade_recommendation(self, recommendation: Dict) -> bool:
"""保存交易建议"""
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 准备数据
data = {
'symbol': recommendation.get('symbol', ''),
'timestamp': recommendation.get('timestamp', datetime.now().isoformat()),
'direction': recommendation.get('direction'),
'entry_price': recommendation.get('entry_price'),
'stop_loss': recommendation.get('stop_loss'),
'target_price': recommendation.get('target_price'),
'position_size': recommendation.get('position_size'),
'execution_plan': recommendation.get('execution_plan'),
'risk_tips': recommendation.get('risk_tips')
}
# 插入数据
cursor.execute('''
INSERT INTO trade_recommendations (
symbol, timestamp, direction, entry_price, stop_loss,
target_price, position_size, execution_plan, risk_tips
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (
data['symbol'], data['timestamp'], data['direction'], data['entry_price'],
data['stop_loss'], data['target_price'], data['position_size'],
data['execution_plan'], data['risk_tips']
))
conn.commit()
conn.close()
return True
except Exception as e:
print(f"保存交易建议失败:{e}")
return False
def save_risk_monitoring(self, monitoring_data: Dict) -> bool:
"""保存风险监控数据"""
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 准备数据
data = {
'symbol': monitoring_data.get('symbol', ''),
'timestamp': monitoring_data.get('timestamp', datetime.now().isoformat()),
'current_price': monitoring_data.get('current_price'),
'entry_price': monitoring_data.get('entry_price'),
'stop_loss': monitoring_data.get('stop_loss'),
'target_price': monitoring_data.get('target_price'),
'current_profit': monitoring_data.get('current_profit'),
'risk_status': monitoring_data.get('risk_status')
}
# 插入数据
cursor.execute('''
INSERT INTO risk_monitoring (
symbol, timestamp, current_price, entry_price, stop_loss,
target_price, current_profit, risk_status
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''', (
data['symbol'], data['timestamp'], data['current_price'], data['entry_price'],
data['stop_loss'], data['target_price'], data['current_profit'], data['risk_status']
))
conn.commit()
conn.close()
return True
except Exception as e:
print(f"保存风险监控数据失败:{e}")
return False
def get_analysis_results(self, symbol: str, limit: int = 100) -> pd.DataFrame:
"""获取分析结果"""
try:
conn = sqlite3.connect(self.db_path)
query = f"""
SELECT * FROM analysis_results
WHERE symbol = ?
ORDER BY timestamp DESC
LIMIT ?
"""
df = pd.read_sql_query(query, conn, params=(symbol, limit))
conn.close()
# 解析JSON字段
if not df.empty:
df['fund_flow'] = df['fund_flow'].apply(lambda x: json.loads(x) if x else {})
df['signals'] = df['signals'].apply(lambda x: json.loads(x) if x else {})
return df
except Exception as e:
print(f"获取分析结果失败:{e}")
return pd.DataFrame()
def get_kline_data(self, symbol: str, duration: str, limit: int = 200) -> pd.DataFrame:
"""获取K线数据"""
try:
conn = sqlite3.connect(self.db_path)
query = f"""
SELECT * FROM kline_data
WHERE symbol = ? AND duration = ?
ORDER BY datetime DESC
LIMIT ?
"""
df = pd.read_sql_query(query, conn, params=(symbol, duration, limit))
conn.close()
if not df.empty:
# 转换时间格式并设置索引
df['datetime'] = pd.to_datetime(df['datetime'])
df = df.sort_values('datetime')
df.set_index('datetime', inplace=True)
# 选择需要的列
df = df[['open', 'high', 'low', 'close', 'volume', 'open_interest']]
return df
except Exception as e:
print(f"获取K线数据失败{e}")
return pd.DataFrame()
def get_trade_recommendations(self, symbol: str, status: Optional[str] = None) -> pd.DataFrame:
"""获取交易建议"""
try:
conn = sqlite3.connect(self.db_path)
if status:
query = f"""
SELECT * FROM trade_recommendations
WHERE symbol = ? AND status = ?
ORDER BY timestamp DESC
"""
df = pd.read_sql_query(query, conn, params=(symbol, status))
else:
query = f"""
SELECT * FROM trade_recommendations
WHERE symbol = ?
ORDER BY timestamp DESC
"""
df = pd.read_sql_query(query, conn, params=(symbol,))
conn.close()
return df
except Exception as e:
print(f"获取交易建议失败:{e}")
return pd.DataFrame()
def update_recommendation_status(self, recommendation_id: int, status: str) -> bool:
"""更新交易建议状态"""
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('''
UPDATE trade_recommendations
SET status = ?
WHERE id = ?
''', (status, recommendation_id))
conn.commit()
conn.close()
return True
except Exception as e:
print(f"更新交易建议状态失败:{e}")
return False
def delete_old_data(self, days: int = 30) -> bool:
"""删除旧数据"""
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 计算删除时间点
delete_time = (datetime.now() - pd.Timedelta(days=days)).isoformat()
# 删除旧的分析结果
cursor.execute('DELETE FROM analysis_results WHERE created_at < ?', (delete_time,))
# 删除旧的K线数据
cursor.execute('DELETE FROM kline_data WHERE created_at < ?', (delete_time,))
# 删除旧的交易建议
cursor.execute('DELETE FROM trade_recommendations WHERE created_at < ?', (delete_time,))
# 删除旧的风险监控数据
cursor.execute('DELETE FROM risk_monitoring WHERE created_at < ?', (delete_time,))
conn.commit()
conn.close()
return True
except Exception as e:
print(f"删除旧数据失败:{e}")
return False

@ -0,0 +1,295 @@
# AI 研判模块
import json
import requests
from typing import Dict, Optional, List
from qihuo_analyzer.utils.config_manager import config_manager
from qihuo_analyzer.core.models import AnalysisResult
class DeepseekAgent:
"""DeepSeek AI 研判代理"""
def __init__(self):
self.api_key = config_manager.deepseek_api_key
self.api_url = config_manager.deepseek_api_url
self.headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {self.api_key}'
}
def analyze_market(self, market_data: Dict, technical_indicators: Dict,
trend_analysis: Dict, risk_metrics: Dict) -> Dict:
"""分析市场"""
# 构建提示词
prompt = self._build_analysis_prompt(market_data, technical_indicators, trend_analysis, risk_metrics)
# 调用API
response = self._call_deepseek_api(prompt)
# 解析结果
analysis_result = self._parse_analysis_result(response)
return analysis_result
def generate_trade_recommendation(self, analysis_result: Dict) -> Dict:
"""生成交易建议"""
# 构建提示词
prompt = self._build_recommendation_prompt(analysis_result)
# 调用API
response = self._call_deepseek_api(prompt)
# 解析结果
recommendation = self._parse_recommendation_result(response)
return recommendation
def _build_analysis_prompt(self, market_data: Dict, technical_indicators: Dict,
trend_analysis: Dict, risk_metrics: Dict) -> str:
"""构建分析提示词"""
prompt = f"""# 期货市场分析任务
你是一位专业的期货市场分析师需要基于以下多维度数据对市场进行综合研判
## 1. 市场基本数据
- 品种{market_data.get('symbol', '未知')}
- 最新价格{market_data.get('latest_price', '未知')}
- 成交量{market_data.get('volume', '未知')}
- 持仓量{market_data.get('open_interest', '未知')}
- 时间周期{market_data.get('timeframe', '未知')}
## 2. 技术指标数据
- MACD{json.dumps(technical_indicators.get('macd', {}), ensure_ascii=False)}
- RSI{technical_indicators.get('rsi', '未知')}
- 布林带{json.dumps(technical_indicators.get('bollinger', {}), ensure_ascii=False)}
- KDJ{json.dumps(technical_indicators.get('kdj', {}), ensure_ascii=False)}
- ATR{technical_indicators.get('atr', '未知')}
## 3. 趋势分析数据
- ADX{trend_analysis.get('adx', '未知')}
- 趋势强度{trend_analysis.get('trend_strength', '未知')}
- 趋势方向{trend_analysis.get('trend_direction', '未知')}
- 双均线关系{trend_analysis.get('ma_relationship', '未知')}
- 多周期共振{json.dumps(trend_analysis.get('multi_period_analysis', {}), ensure_ascii=False)}
- 综合趋势{trend_analysis.get('overall_trend', '未知')}
- 胜率{trend_analysis.get('win_rate', '未知')}%
## 4. 风险指标数据
- 止损位{risk_metrics.get('stop_loss', '未知')}
- 目标价{risk_metrics.get('target_price', '未知')}
- 盈亏比{risk_metrics.get('profit_loss_ratio', '未知')}
- 建议仓位{risk_metrics.get('position_size', '未知')}
- 风险比例{risk_metrics.get('risk_ratio', '未知')}%
## 分析要求
1. **趋势判断**基于多维度数据判断当前市场的主要趋势
2. **胜率评估**评估当前交易机会的胜率
3. **风险预警**识别潜在的风险因素
4. **交易建议**给出具体的交易方向仓位止损止盈建议
5. **逻辑解释**详细说明分析逻辑和依据
请以JSON格式输出分析结果包含以下字段
- trend_judgment趋势判断
- win_rate_assessment胜率评估
- risk_warning风险预警
- trade_recommendation交易建议
- analysis_logic分析逻辑
"""
return prompt
def _build_recommendation_prompt(self, analysis_result: Dict) -> str:
"""构建建议提示词"""
prompt = f"""# 期货交易建议生成任务
基于以下市场分析结果生成详细的交易建议
## 分析结果
{json.dumps(analysis_result, ensure_ascii=False, indent=2)}
## 建议要求
1. **明确的交易方向**做多/做空/观望
2. **具体的入场点位**基于技术分析的合理入场点
3. **严格的止损设置**基于ATR的动态止损
4. **合理的止盈目标**基于压力支撑位的目标价
5. **科学的仓位管理**基于账户资金的风险控制
6. **详细的执行计划**包括入场时机加仓策略出场条件
7. **风险提示**潜在的风险因素和应对措施
请以JSON格式输出交易建议包含以下字段
- direction交易方向
- entry_price入场价格
- stop_loss止损价格
- target_price目标价格
- position_size仓位大小
- execution_plan执行计划
- risk_tips风险提示
"""
return prompt
def _call_deepseek_api(self, prompt: str) -> str:
"""调用DeepSeek API"""
# 如果没有API密钥返回模拟结果
if not self.api_key:
return self._get_mock_response(prompt)
payload = {
'model': 'deepseek-chat',
'messages': [
{
'role': 'system',
'content': '你是一位专业的期货市场分析师,精通技术分析和基本面分析,能够基于多维度数据提供准确的市场研判和交易建议。'
},
{
'role': 'user',
'content': prompt
}
],
'temperature': 0.3,
'max_tokens': 2000,
'top_p': 0.9
}
try:
response = requests.post(
self.api_url,
headers=self.headers,
json=payload,
timeout=30
)
response.raise_for_status()
result = response.json()
return result['choices'][0]['message']['content']
except Exception as e:
print(f"API调用失败{e}")
return self._get_mock_response(prompt)
def _get_mock_response(self, prompt: str) -> str:
"""获取模拟响应"""
# 模拟分析结果
if '市场分析任务' in prompt:
return json.dumps({
'trend_judgment': '震荡偏多',
'win_rate_assessment': '65%',
'risk_warning': '短期波动较大,注意止损',
'trade_recommendation': '轻仓做多',
'analysis_logic': '基于MACD金叉、RSI中性偏多、双均线金叉等信号综合判断震荡偏多趋势'
}, ensure_ascii=False)
# 模拟建议结果
elif '交易建议生成任务' in prompt:
return json.dumps({
'direction': 'long',
'entry_price': 3500,
'stop_loss': 3450,
'target_price': 3600,
'position_size': 2,
'execution_plan': '回调至3480附近入场止损设置在3450目标位3600突破3600后可加仓',
'risk_tips': '若跌破3450立即止损若成交量萎缩考虑提前出场'
}, ensure_ascii=False)
else:
return json.dumps({
'error': '未知任务类型'
}, ensure_ascii=False)
def _parse_analysis_result(self, response: str) -> Dict:
"""解析分析结果"""
try:
# 尝试直接解析JSON
return json.loads(response)
except json.JSONDecodeError:
# 如果不是纯JSON提取JSON部分
import re
json_match = re.search(r'\{[\s\S]*\}', response)
if json_match:
try:
return json.loads(json_match.group())
except json.JSONDecodeError:
return {'error': '解析失败'}
else:
return {'error': '无有效JSON'}
def _parse_recommendation_result(self, response: str) -> Dict:
"""解析建议结果"""
try:
# 尝试直接解析JSON
return json.loads(response)
except json.JSONDecodeError:
# 如果不是纯JSON提取JSON部分
import re
json_match = re.search(r'\{[\s\S]*\}', response)
if json_match:
try:
return json.loads(json_match.group())
except json.JSONDecodeError:
return {'error': '解析失败'}
else:
return {'error': '无有效JSON'}
def fuse_multidimensional_data(self, data_sources: List[Dict]) -> Dict:
"""融合多维度数据"""
# 构建融合提示词
prompt = f"""# 多维度数据融合任务
请将以下多个数据源的数据进行融合分析提取关键信息形成综合的市场判断
## 数据源
{json.dumps(data_sources, ensure_ascii=False, indent=2)}
## 融合要求
1. **数据一致性检查**检查各数据源之间的一致性
2. **关键信息提取**提取各数据源的关键信息
3. **综合判断形成**基于融合数据形成综合市场判断
4. **不确定性评估**评估数据的不确定性和风险
请以JSON格式输出融合结果包含以下字段
- fused_data融合后的数据
- key_insights关键洞察
- comprehensive_judgment综合判断
- uncertainty_assessment不确定性评估
"""
# 调用API
response = self._call_deepseek_api(prompt)
# 解析结果
fused_result = self._parse_analysis_result(response)
return fused_result
def generate_market_insights(self, historical_data: List[Dict], current_data: Dict) -> Dict:
"""生成市场洞察"""
# 构建提示词
prompt = f"""# 市场洞察生成任务
基于以下历史数据和当前数据生成深度的市场洞察
## 历史数据
{json.dumps(historical_data, ensure_ascii=False, indent=2)}
## 当前数据
{json.dumps(current_data, ensure_ascii=False, indent=2)}
## 洞察要求
1. **趋势变化分析**分析市场趋势的变化
2. **关键转折点识别**识别重要的市场转折点
3. **异常情况检测**检测异常的市场行为
4. **未来走势预测**基于历史和当前数据预测未来走势
5. **投资机会挖掘**挖掘潜在的投资机会
请以JSON格式输出市场洞察包含以下字段
- trend_analysis趋势分析
- turning_points转折点分析
- anomalies异常检测
- future_prediction未来预测
- investment_opportunities投资机会
"""
# 调用API
response = self._call_deepseek_api(prompt)
# 解析结果
insights = self._parse_analysis_result(response)
return insights

@ -0,0 +1,284 @@
# 资金监控模块
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
}

@ -0,0 +1,274 @@
# 风控管理模块
import pandas as pd
from typing import Dict, Optional, Tuple
from qihuo_analyzer.utils.technical_analysis import calculate_atr
from qihuo_analyzer.core.models import StrategyConfig, RiskParams
class RiskManager:
"""风险管理器"""
def __init__(self, config: Optional[StrategyConfig] = None):
self.config = config or StrategyConfig()
def calculate_stop_loss(self, data: pd.DataFrame, entry_price: float, direction: str, atr_multiplier: Optional[float] = None) -> float:
"""计算止损位"""
atr_multiplier = atr_multiplier or self.config.atr_multiplier
# 计算ATR
atr = calculate_atr(data).iloc[-1]
# 根据方向计算止损位
if direction == 'long':
stop_loss = entry_price - (atr * atr_multiplier)
elif direction == 'short':
stop_loss = entry_price + (atr * atr_multiplier)
else:
raise ValueError("Direction must be 'long' or 'short'")
return stop_loss
def calculate_position_size(self, account_balance: float, data: pd.DataFrame, direction: str, entry_price: float,
contract_multiplier: float = 10, margin_rate: float = 0.1) -> Dict:
"""计算仓位大小"""
# 计算ATR
atr = calculate_atr(data).iloc[-1]
# 计算每手风险
if direction == 'long':
risk_per_unit = atr * self.config.atr_multiplier * contract_multiplier
elif direction == 'short':
risk_per_unit = atr * self.config.atr_multiplier * contract_multiplier
else:
raise ValueError("Direction must be 'long' or 'short'")
# 计算最大风险金额
max_risk_amount = account_balance * self.config.max_risk_percent
# 计算建议手数
suggested_units = max_risk_amount / risk_per_unit
suggested_units = max(1, int(suggested_units)) # 至少1手
# 计算保证金需求
margin_per_unit = entry_price * contract_multiplier * margin_rate
total_margin = suggested_units * margin_per_unit
# 计算实际风险比例
actual_risk_percent = (risk_per_unit * suggested_units) / account_balance
# 计算杠杆比例
leverage = (suggested_units * entry_price * contract_multiplier) / account_balance
return {
'suggested_units': suggested_units,
'risk_per_unit': risk_per_unit,
'max_risk_amount': max_risk_amount,
'margin_per_unit': margin_per_unit,
'total_margin': total_margin,
'actual_risk_percent': actual_risk_percent,
'leverage': leverage,
'atr': atr
}
def calculate_profit_loss_ratio(self, entry_price: float, stop_loss: float, target_price: float, direction: str) -> float:
"""计算盈亏比"""
if direction == 'long':
profit = target_price - entry_price
loss = entry_price - stop_loss
elif direction == 'short':
profit = entry_price - target_price
loss = stop_loss - entry_price
else:
raise ValueError("Direction must be 'long' or 'short'")
if loss == 0:
return float('inf')
return profit / loss
def validate_trade(self, account_balance: float, data: pd.DataFrame, direction: str,
entry_price: float, target_price: float, contract_multiplier: float = 10,
margin_rate: float = 0.1) -> Dict:
"""验证交易是否符合风控要求"""
# 计算止损位
stop_loss = self.calculate_stop_loss(data, entry_price, direction)
# 计算盈亏比
pl_ratio = self.calculate_profit_loss_ratio(entry_price, stop_loss, target_price, direction)
# 计算仓位大小
position_info = self.calculate_position_size(account_balance, data, direction, entry_price,
contract_multiplier, margin_rate)
# 检查各项风控指标
checks = {
'profit_loss_ratio': {
'value': pl_ratio,
'required': self.config.min_profit_loss_ratio,
'pass': pl_ratio >= self.config.min_profit_loss_ratio
},
'risk_percent': {
'value': position_info['actual_risk_percent'] * 100,
'required': self.config.max_risk_percent * 100,
'pass': position_info['actual_risk_percent'] <= self.config.max_risk_percent
},
'leverage': {
'value': position_info['leverage'],
'required': 5, # 最大杠杆
'pass': position_info['leverage'] <= 5
},
'margin_utilization': {
'value': (position_info['total_margin'] / account_balance) * 100,
'required': 30, # 最大保证金使用率
'pass': (position_info['total_margin'] / account_balance) <= 0.3
}
}
# 综合判断
all_passed = all(check['pass'] for check in checks.values())
return {
'valid': all_passed,
'checks': checks,
'position_info': position_info,
'stop_loss': stop_loss,
'profit_loss_ratio': pl_ratio
}
def generate_risk_report(self, account_balance: float, data: pd.DataFrame, direction: str,
entry_price: float, target_price: float, contract_multiplier: float = 10,
margin_rate: float = 0.1) -> Dict:
"""生成风险报告"""
# 验证交易
validation_result = self.validate_trade(account_balance, data, direction, entry_price,
target_price, contract_multiplier, margin_rate)
# 生成风险建议
suggestions = []
if not validation_result['checks']['profit_loss_ratio']['pass']:
suggestions.append(f"盈亏比不足,建议调整目标价至{self._calculate_adjusted_target(entry_price, validation_result['stop_loss'], direction):.2f}")
if not validation_result['checks']['risk_percent']['pass']:
suggestions.append(f"风险比例过高,建议减少仓位至{int(validation_result['position_info']['suggested_units'] * 0.8)}")
if not validation_result['checks']['leverage']['pass']:
suggestions.append("杠杆比例过高,建议降低仓位")
if not validation_result['checks']['margin_utilization']['pass']:
suggestions.append("保证金使用率过高,建议减少仓位")
# 计算风险回报比
risk_return_ratio = self._calculate_risk_return_ratio(validation_result['profit_loss_ratio'],
validation_result['position_info']['actual_risk_percent'])
report = {
'account_balance': account_balance,
'direction': direction,
'entry_price': entry_price,
'stop_loss': validation_result['stop_loss'],
'target_price': target_price,
'profit_loss_ratio': validation_result['profit_loss_ratio'],
'position_info': validation_result['position_info'],
'risk_metrics': {
'risk_return_ratio': risk_return_ratio,
'max_drawdown_estimate': self._estimate_max_drawdown(account_balance, validation_result['position_info']),
'recovery_factor': self._calculate_recovery_factor(risk_return_ratio)
},
'suggestions': suggestions,
'validation_result': validation_result
}
return report
def _calculate_adjusted_target(self, entry_price: float, stop_loss: float, direction: str) -> float:
"""计算调整后的目标价"""
if direction == 'long':
loss = entry_price - stop_loss
required_profit = loss * self.config.min_profit_loss_ratio
return entry_price + required_profit
elif direction == 'short':
loss = stop_loss - entry_price
required_profit = loss * self.config.min_profit_loss_ratio
return entry_price - required_profit
else:
raise ValueError("Direction must be 'long' or 'short'")
def _calculate_risk_return_ratio(self, pl_ratio: float, risk_percent: float) -> float:
"""计算风险回报比"""
return pl_ratio * (1 - risk_percent)
def _estimate_max_drawdown(self, account_balance: float, position_info: Dict) -> float:
"""估算最大回撤"""
max_loss = position_info['risk_per_unit'] * position_info['suggested_units']
return (max_loss / account_balance) * 100
def _calculate_recovery_factor(self, risk_return_ratio: float) -> float:
"""计算恢复因子"""
if risk_return_ratio <= 0:
return 0
return risk_return_ratio * 0.8
def monitor_position_risk(self, current_price: float, entry_price: float, stop_loss: float,
target_price: float, direction: str, units: int, contract_multiplier: float = 10) -> Dict:
"""监控持仓风险"""
# 计算当前盈亏
if direction == 'long':
current_profit = (current_price - entry_price) * units * contract_multiplier
distance_to_stop = entry_price - current_price
distance_to_target = target_price - current_price
elif direction == 'short':
current_profit = (entry_price - current_price) * units * contract_multiplier
distance_to_stop = current_price - entry_price
distance_to_target = entry_price - current_price
else:
raise ValueError("Direction must be 'long' or 'short'")
# 计算浮盈比例
unrealized_pnl_percent = (current_profit / (entry_price * units * contract_multiplier)) * 100
# 计算止损触发距离
stop_percent = (distance_to_stop / entry_price) * 100
# 计算目标达成距离
target_percent = (distance_to_target / entry_price) * 100
# 风险状态评估
risk_status = self._assess_risk_status(current_price, stop_loss, target_price, direction)
return {
'current_price': current_price,
'entry_price': entry_price,
'stop_loss': stop_loss,
'target_price': target_price,
'current_profit': current_profit,
'unrealized_pnl_percent': unrealized_pnl_percent,
'distance_to_stop': distance_to_stop,
'distance_to_target': distance_to_target,
'stop_percent': stop_percent,
'target_percent': target_percent,
'risk_status': risk_status
}
def _assess_risk_status(self, current_price: float, stop_loss: float, target_price: float, direction: str) -> str:
"""评估风险状态"""
if direction == 'long':
if current_price <= stop_loss:
return 'stop_loss_triggered'
elif current_price >= target_price:
return 'target_reached'
elif current_price > stop_loss * 1.05:
return 'low_risk'
else:
return 'medium_risk'
elif direction == 'short':
if current_price >= stop_loss:
return 'stop_loss_triggered'
elif current_price <= target_price:
return 'target_reached'
elif current_price < stop_loss * 0.95:
return 'low_risk'
else:
return 'medium_risk'
else:
raise ValueError("Direction must be 'long' or 'short'")

@ -0,0 +1,483 @@
# 换月预警模块
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

@ -0,0 +1,389 @@
# 压力支撑模块
import pandas as pd
import numpy as np
from typing import Dict, List, Tuple, Optional
from qihuo_analyzer.utils.technical_analysis import calculate_bollinger_bands
class SupportResistance:
"""压力支撑分析器"""
def __init__(self):
pass
def analyze_support_resistance(self, data: pd.DataFrame) -> Dict:
"""分析压力支撑位"""
result = {}
# 识别关键价位
key_levels = self._identify_key_levels(data)
result.update(key_levels)
# 计算枢轴点
pivot_points = self._calculate_pivot_points(data)
result.update(pivot_points)
# 基于布林带的支撑阻力
bollinger_levels = self._calculate_bollinger_levels(data)
result.update(bollinger_levels)
# 最近高低点分析
recent_high_low = self._analyze_recent_high_low(data)
result.update(recent_high_low)
# 斐波那契回调线
fibonacci_levels = self._calculate_fibonacci_levels(data)
result['fibonacci_levels'] = fibonacci_levels
# 综合支撑阻力位
support_resistance_levels = self._generate_support_resistance_levels(result)
result['support_resistance_levels'] = support_resistance_levels
return result
def _identify_key_levels(self, data: pd.DataFrame) -> Dict:
"""识别关键价位"""
# 计算最近N天的高低点
recent_high = data['high'].tail(50).max()
recent_low = data['low'].tail(50).min()
# 计算最近N天的平均波幅
avg_range = (data['high'] - data['low']).tail(50).mean()
# 识别成交量密集区
volume_profile = self._calculate_volume_profile(data)
# 识别价格密集区
price_density = self._calculate_price_density(data)
return {
'recent_high': recent_high,
'recent_low': recent_low,
'avg_range': avg_range,
'volume_profile': volume_profile,
'price_density': price_density
}
def _calculate_pivot_points(self, data: pd.DataFrame) -> Dict:
"""计算枢轴点"""
# 使用最近的高点、低点和收盘价计算枢轴点
if len(data) < 2:
return {
'pivot_point': None,
'resistance_1': None,
'resistance_2': None,
'support_1': None,
'support_2': None
}
high = data['high'].iloc[-1]
low = data['low'].iloc[-1]
close = data['close'].iloc[-1]
# 计算枢轴点
pivot_point = (high + low + close) / 3
# 计算阻力位和支撑位
resistance_1 = 2 * pivot_point - low
resistance_2 = pivot_point + (high - low)
support_1 = 2 * pivot_point - high
support_2 = pivot_point - (high - low)
return {
'pivot_point': pivot_point,
'resistance_1': resistance_1,
'resistance_2': resistance_2,
'support_1': support_1,
'support_2': support_2
}
def _calculate_bollinger_levels(self, data: pd.DataFrame) -> Dict:
"""基于布林带的支撑阻力"""
# 计算布林带
bollinger_data = calculate_bollinger_bands(data)
# 获取最新的布林带值
upper_band = bollinger_data['upper_band'].iloc[-1]
middle_band = bollinger_data['sma'].iloc[-1]
lower_band = bollinger_data['lower_band'].iloc[-1]
return {
'bollinger_upper': upper_band,
'bollinger_middle': middle_band,
'bollinger_lower': lower_band
}
def _analyze_recent_high_low(self, data: pd.DataFrame) -> Dict:
"""最近高低点分析"""
# 计算不同周期的高低点
periods = [10, 20, 50]
high_low_levels = {}
for period in periods:
if len(data) >= period:
high_low_levels[f'{period}d_high'] = data['high'].tail(period).max()
high_low_levels[f'{period}d_low'] = data['low'].tail(period).min()
else:
high_low_levels[f'{period}d_high'] = None
high_low_levels[f'{period}d_low'] = None
return high_low_levels
def _calculate_volume_profile(self, data: pd.DataFrame) -> Dict:
"""计算成交量分布"""
# 简化的成交量分布分析
# 将价格区间分成10个区间计算每个区间的成交量
if len(data) < 10:
return {}
price_min = data['low'].tail(50).min()
price_max = data['high'].tail(50).max()
price_range = price_max - price_min
bin_size = price_range / 10
volume_profile = {}
for i in range(10):
bin_low = price_min + i * bin_size
bin_high = price_min + (i + 1) * bin_size
# 计算该价格区间的成交量
bin_volume = data[
(data['low'] <= bin_high) &
(data['high'] >= bin_low)
]['volume'].sum()
volume_profile[f'bin_{i+1}'] = {
'price_range': (bin_low, bin_high),
'volume': bin_volume
}
# 找出成交量最大的区间
max_volume_bin = max(volume_profile.items(), key=lambda x: x[1]['volume'])
return {
'volume_profile': volume_profile,
'max_volume_bin': max_volume_bin
}
def _calculate_price_density(self, data: pd.DataFrame) -> Dict:
"""计算价格密度"""
# 简化的价格密度分析
if len(data) < 10:
return {}
# 计算收盘价的分布
prices = data['close'].tail(100)
price_std = prices.std()
price_mean = prices.mean()
# 计算价格分位数
price_percentiles = {
'p10': np.percentile(prices, 10),
'p25': np.percentile(prices, 25),
'p50': np.percentile(prices, 50),
'p75': np.percentile(prices, 75),
'p90': np.percentile(prices, 90)
}
return {
'price_mean': price_mean,
'price_std': price_std,
'price_percentiles': price_percentiles
}
def _calculate_fibonacci_levels(self, data: pd.DataFrame) -> Dict:
"""计算斐波那契回调线"""
if len(data) < 20:
return {}
# 找出最近的显著高低点
swing_high = data['high'].tail(50).max()
swing_low = data['low'].tail(50).min()
# 计算斐波那契回调位
range_high_low = swing_high - swing_low
fib_levels = {
'0': swing_low,
'0.236': swing_low + range_high_low * 0.236,
'0.382': swing_low + range_high_low * 0.382,
'0.5': swing_low + range_high_low * 0.5,
'0.618': swing_low + range_high_low * 0.618,
'0.786': swing_low + range_high_low * 0.786,
'1': swing_high
}
return fib_levels
def _generate_support_resistance_levels(self, analysis: Dict) -> Dict:
"""生成综合支撑阻力位"""
# 收集所有可能的支撑阻力位
all_levels = []
# 添加最近高低点
all_levels.append(analysis.get('recent_high', 0))
all_levels.append(analysis.get('recent_low', 0))
# 添加枢轴点相关价位
all_levels.extend([
analysis.get('pivot_point', 0),
analysis.get('resistance_1', 0),
analysis.get('resistance_2', 0),
analysis.get('support_1', 0),
analysis.get('support_2', 0)
])
# 添加布林带相关价位
all_levels.extend([
analysis.get('bollinger_upper', 0),
analysis.get('bollinger_middle', 0),
analysis.get('bollinger_lower', 0)
])
# 添加不同周期的高低点
periods = [10, 20, 50]
for period in periods:
all_levels.append(analysis.get(f'{period}d_high', 0))
all_levels.append(analysis.get(f'{period}d_low', 0))
# 添加斐波那契回调位
fib_levels = analysis.get('fibonacci_levels', {})
all_levels.extend(fib_levels.values())
# 过滤无效值并排序
all_levels = [level for level in all_levels if level and level > 0]
all_levels.sort()
# 去重(相近的价位视为同一价位)
if not all_levels:
return {'support_levels': [], 'resistance_levels': []}
unique_levels = []
threshold = analysis.get('avg_range', 10) * 0.3 # 阈值为平均波幅的30%
for level in all_levels:
if not unique_levels or abs(level - unique_levels[-1]) > threshold:
unique_levels.append(level)
# 确定当前价格
current_price = analysis.get('recent_high', 3500) * 0.95 # 使用最近高点的95%作为当前价格
# 分离支撑位和阻力位
support_levels = [level for level in unique_levels if level < current_price]
resistance_levels = [level for level in unique_levels if level > current_price]
# 按距离当前价格排序
support_levels.sort(reverse=True) # 最近的支撑位在前
resistance_levels.sort() # 最近的阻力位在前
# 取最近的几个支撑阻力位
support_levels = support_levels[:3] # 最近的3个支撑位
resistance_levels = resistance_levels[:3] # 最近的3个阻力位
return {
'support_levels': support_levels,
'resistance_levels': resistance_levels,
'current_price': current_price
}
def calculate_stop_loss_level(self, data: pd.DataFrame, direction: str, atr: float) -> float:
"""计算智能止损位"""
# 分析支撑阻力位
sr_analysis = self.analyze_support_resistance(data)
support_levels = sr_analysis.get('support_resistance_levels', {}).get('support_levels', [])
resistance_levels = sr_analysis.get('support_resistance_levels', {}).get('resistance_levels', [])
current_price = data['close'].iloc[-1]
if direction == 'long':
# 做多时,止损位应在最近的支撑位下方
if support_levels:
# 最近的支撑位下方ATR的0.5倍
stop_loss = support_levels[0] - atr * 0.5
else:
# 没有支撑位时使用ATR的2倍
stop_loss = current_price - atr * 2
elif direction == 'short':
# 做空时,止损位应在最近的阻力位上方
if resistance_levels:
# 最近的阻力位上方ATR的0.5倍
stop_loss = resistance_levels[0] + atr * 0.5
else:
# 没有阻力位时使用ATR的2倍
stop_loss = current_price + atr * 2
else:
raise ValueError("Direction must be 'long' or 'short'")
return stop_loss
def calculate_target_price(self, data: pd.DataFrame, direction: str, entry_price: float) -> float:
"""计算目标价"""
# 分析支撑阻力位
sr_analysis = self.analyze_support_resistance(data)
support_levels = sr_analysis.get('support_resistance_levels', {}).get('support_levels', [])
resistance_levels = sr_analysis.get('support_resistance_levels', {}).get('resistance_levels', [])
if direction == 'long':
# 做多时,目标价应在最近的阻力位
if resistance_levels:
target_price = resistance_levels[0]
else:
# 没有阻力位时,使用近期高点
target_price = sr_analysis.get('recent_high', entry_price * 1.05)
elif direction == 'short':
# 做空时,目标价应在最近的支撑位
if support_levels:
target_price = support_levels[0]
else:
# 没有支撑位时,使用近期低点
target_price = sr_analysis.get('recent_low', entry_price * 0.95)
else:
raise ValueError("Direction must be 'long' or 'short'")
return target_price
def analyze_price_position(self, data: pd.DataFrame) -> Dict:
"""分析价格位置"""
current_price = data['close'].iloc[-1]
# 分析支撑阻力位
sr_analysis = self.analyze_support_resistance(data)
support_levels = sr_analysis.get('support_resistance_levels', {}).get('support_levels', [])
resistance_levels = sr_analysis.get('support_resistance_levels', {}).get('resistance_levels', [])
# 计算价格与支撑阻力位的距离
distance_to_support = float('inf')
distance_to_resistance = float('inf')
if support_levels:
distance_to_support = current_price - support_levels[0]
if resistance_levels:
distance_to_resistance = resistance_levels[0] - current_price
# 分析价格位置
position = 'neutral'
if distance_to_resistance < sr_analysis.get('avg_range', 10) * 0.2:
position = 'near_resistance'
elif distance_to_support < sr_analysis.get('avg_range', 10) * 0.2:
position = 'near_support'
# 分析价格在布林带中的位置
bollinger_upper = sr_analysis.get('bollinger_upper', 0)
bollinger_middle = sr_analysis.get('bollinger_middle', 0)
bollinger_lower = sr_analysis.get('bollinger_lower', 0)
bollinger_position = 'middle'
if current_price > bollinger_upper:
bollinger_position = 'upper'
elif current_price < bollinger_lower:
bollinger_position = 'lower'
return {
'current_price': current_price,
'distance_to_support': distance_to_support,
'distance_to_resistance': distance_to_resistance,
'position': position,
'bollinger_position': bollinger_position,
'support_levels': support_levels,
'resistance_levels': resistance_levels
}

@ -0,0 +1,226 @@
# 趋势分析模块
import pandas as pd
from typing import Dict, Tuple, Optional
from qihuo_analyzer.utils.technical_analysis import (
calculate_adx,
calculate_moving_average,
calculate_price_quantile,
calculate_volume_price_strength
)
from qihuo_analyzer.core.models import StrategyConfig
class TrendFilter:
"""趋势分析过滤器"""
def __init__(self, config: Optional[StrategyConfig] = None):
self.config = config or StrategyConfig()
def analyze_trend(self, data: pd.DataFrame) -> Dict:
"""分析趋势"""
result = {}
# 计算ADX指标
adx_data = calculate_adx(data, self.config.adx_period)
adx = adx_data['adx'].iloc[-1]
plus_di = adx_data['plus_di'].iloc[-1]
minus_di = adx_data['minus_di'].iloc[-1]
# 趋势强度判断
trend_strength = self._judge_trend_strength(adx)
trend_direction = self._judge_trend_direction(plus_di, minus_di)
# 计算移动平均线
ma_data = calculate_moving_average(data, [self.config.short_ma, self.config.long_ma])
short_ma = ma_data[f'ma{self.config.short_ma}'].iloc[-1]
long_ma = ma_data[f'ma{self.config.long_ma}'].iloc[-1]
# 双均线排列判断
ma_relationship = self._judge_ma_relationship(short_ma, long_ma)
# 多周期共振分析
multi_period_analysis = self._analyze_multi_period(data)
# 综合趋势判断
overall_trend = self._judge_overall_trend(trend_strength, trend_direction, ma_relationship)
result.update({
'adx': adx,
'plus_di': plus_di,
'minus_di': minus_di,
'trend_strength': trend_strength,
'trend_direction': trend_direction,
'short_ma': short_ma,
'long_ma': long_ma,
'ma_relationship': ma_relationship,
'multi_period_analysis': multi_period_analysis,
'overall_trend': overall_trend
})
return result
def _judge_trend_strength(self, adx: float) -> str:
"""判断趋势强度"""
if adx > 40:
return 'strong'
elif adx >= 25:
return 'medium'
elif adx >= 20:
return 'weak'
else:
return 'none'
def _judge_trend_direction(self, plus_di: float, minus_di: float) -> str:
"""判断趋势方向"""
if plus_di > minus_di:
return 'up'
elif plus_di < minus_di:
return 'down'
else:
return 'neutral'
def _judge_ma_relationship(self, short_ma: float, long_ma: float) -> str:
"""判断均线关系"""
if short_ma > long_ma:
return 'bullish'
elif short_ma < long_ma:
return 'bearish'
else:
return 'neutral'
def _analyze_multi_period(self, data: pd.DataFrame) -> Dict:
"""多周期共振分析"""
periods = [15, 60, 240] # 15分钟、1小时、4小时
analysis = {}
for period in periods:
# 简化处理,使用不同周期的收盘价
if len(data) >= period:
period_data = data.tail(period)
ma_short = period_data['close'].rolling(window=5).mean().iloc[-1]
ma_long = period_data['close'].rolling(window=20).mean().iloc[-1]
if ma_short > ma_long:
analysis[f'{period}min'] = 'bullish'
elif ma_short < ma_long:
analysis[f'{period}min'] = 'bearish'
else:
analysis[f'{period}min'] = 'neutral'
else:
analysis[f'{period}min'] = 'insufficient_data'
# 计算共振程度
bullish_count = sum(1 for v in analysis.values() if v == 'bullish')
bearish_count = sum(1 for v in analysis.values() if v == 'bearish')
resonance = 'none'
if bullish_count >= 2:
resonance = 'bullish_resonance'
elif bearish_count >= 2:
resonance = 'bearish_resonance'
analysis['resonance'] = resonance
return analysis
def _judge_overall_trend(self, trend_strength: str, trend_direction: str, ma_relationship: str) -> str:
"""综合判断趋势"""
if trend_strength == 'none':
return 'neutral'
if trend_direction == 'up' and ma_relationship == 'bullish':
return 'strong_bullish'
elif trend_direction == 'down' and ma_relationship == 'bearish':
return 'strong_bearish'
elif trend_direction == 'up' and ma_relationship == 'bearish':
return 'weak_bullish'
elif trend_direction == 'down' and ma_relationship == 'bullish':
return 'weak_bearish'
else:
return 'neutral'
def calculate_win_rate(self, data: pd.DataFrame) -> float:
"""计算胜率"""
# 获取ADX值
adx_data = calculate_adx(data, self.config.adx_period)
adx = adx_data['adx'].iloc[-1]
# 计算价格分位
price_quantile = calculate_price_quantile(data)
price_score = self._calculate_price_score(price_quantile)
# 计算量价强度
volume_price_strength = calculate_volume_price_strength(data)
# 计算趋势强度评分
trend_strength_score = self._calculate_trend_strength_score(adx)
# 根据市场状态计算加权胜率
if adx < 20: # 震荡市
win_rate = (
price_score * 0.25 +
volume_price_strength * 0.6 +
trend_strength_score * 0.15
)
else: # 趋势市
# 价格分位权重随ADX递减
price_weight = max(0.3, 0.6 - (adx - 20) * 0.0075)
trend_adjustment = ((adx - 20) * 0.5) / 100 if adx_data['plus_di'].iloc[-1] > adx_data['minus_di'].iloc[-1] else -((adx - 20) * 0.5) / 100
win_rate = (
price_score * price_weight +
volume_price_strength * 0.4 +
trend_strength_score * (0.6 - price_weight)
) + trend_adjustment
# 确保胜率在合理范围内
win_rate = max(0, min(100, win_rate))
return win_rate
def _calculate_price_score(self, quantile: float) -> float:
"""计算价格分位评分"""
if quantile < 0.2:
return 90
elif quantile < 0.4:
return 75
elif quantile < 0.6:
return 55
elif quantile < 0.8:
return 40
else:
return 25
def _calculate_trend_strength_score(self, adx: float) -> float:
"""计算趋势强度评分"""
if adx > 40:
return 85
elif adx >= 25:
return 70
elif adx >= 20:
return 50
else:
return 30
def judge_cycle(self, data: pd.DataFrame) -> str:
"""判断周期"""
multi_period_analysis = self._analyze_multi_period(data)
adx_data = calculate_adx(data, self.config.adx_period)
adx = adx_data['adx'].iloc[-1]
# 检查各周期方向一致性
directions = [v for k, v in multi_period_analysis.items() if k.endswith('min')]
valid_directions = [d for d in directions if d != 'insufficient_data']
if not valid_directions:
return 'medium'
# 检查是否所有周期方向一致且不为中性
if len(set(valid_directions)) == 1 and valid_directions[0] != 'neutral':
# 检查是否为极强趋势
if adx > 40:
return 'long'
else:
return 'short'
else:
return 'medium'

@ -0,0 +1,70 @@
# 配置管理工具
import os
from dotenv import load_dotenv
from typing import Dict, Optional
class ConfigManager:
"""配置管理类"""
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(ConfigManager, cls).__new__(cls)
cls._instance._load_config()
return cls._instance
def _load_config(self):
"""加载配置"""
# 加载.env文件
load_dotenv()
# API配置
self.openai_api_key = os.getenv('OPENAI_API_KEY', '')
self.deepseek_api_key = os.getenv('DEEPSEEK_API_KEY', '')
self.deepseek_api_url = os.getenv('DEEPSEEK_API_URL', 'https://api.deepseek.com/v1/chat/completions')
# 数据库配置
self.db_path = os.getenv('DB_PATH', './data/futures_analysis.db')
# 天勤TQSDK配置
self.tqserver_host = os.getenv('TQSERVER_HOST', 'api.shinnytech.com')
self.tqserver_port = int(os.getenv('TQSERVER_PORT', '7777'))
# 风险配置
self.max_risk_percent = float(os.getenv('MAX_RISK_PERCENT', '0.02'))
self.min_profit_loss_ratio = float(os.getenv('MIN_PROFIT_LOSS_RATIO', '1.5'))
# 策略配置
self.default_atr_multiplier = float(os.getenv('DEFAULT_ATR_MULTIPLIER', '2.0'))
self.default_adx_threshold = float(os.getenv('DEFAULT_ADX_THRESHOLD', '20'))
# 定时任务配置
self.review_times = os.getenv('REVIEW_TIMES', '09:00,12:30,15:30').split(',')
def get_config(self) -> Dict:
"""获取所有配置"""
return {
'openai_api_key': self.openai_api_key,
'deepseek_api_key': self.deepseek_api_key,
'deepseek_api_url': self.deepseek_api_url,
'db_path': self.db_path,
'tqserver_host': self.tqserver_host,
'tqserver_port': self.tqserver_port,
'max_risk_percent': self.max_risk_percent,
'min_profit_loss_ratio': self.min_profit_loss_ratio,
'default_atr_multiplier': self.default_atr_multiplier,
'default_adx_threshold': self.default_adx_threshold,
'review_times': self.review_times
}
def update_config(self, config: Dict):
"""更新配置"""
for key, value in config.items():
if hasattr(self, key):
setattr(self, key, value)
# 全局配置实例
config_manager = ConfigManager()

@ -0,0 +1,153 @@
# 技术分析工具
import numpy as np
import pandas as pd
from typing import Dict, List, Tuple
def calculate_macd(data: pd.DataFrame, fast_period: int = 12, slow_period: int = 26, signal_period: int = 9) -> Dict[str, pd.Series]:
"""计算MACD指标"""
exp1 = data['close'].ewm(span=fast_period, adjust=False).mean()
exp2 = data['close'].ewm(span=slow_period, adjust=False).mean()
macd = exp1 - exp2
signal = macd.ewm(span=signal_period, adjust=False).mean()
histogram = macd - signal
return {
'macd': macd,
'signal': signal,
'histogram': histogram
}
def calculate_rsi(data: pd.DataFrame, period: int = 14) -> pd.Series:
"""计算RSI指标"""
delta = data['close'].diff()
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
rs = gain / loss
rsi = 100 - (100 / (1 + rs))
return rsi
def calculate_bollinger_bands(data: pd.DataFrame, period: int = 20, std_dev: float = 2.0) -> Dict[str, pd.Series]:
"""计算布林带"""
sma = data['close'].rolling(window=period).mean()
std = data['close'].rolling(window=period).std()
upper_band = sma + (std * std_dev)
lower_band = sma - (std * std_dev)
return {
'sma': sma,
'upper_band': upper_band,
'lower_band': lower_band
}
def calculate_kdj(data: pd.DataFrame, period: int = 9, signal_period: int = 3) -> Dict[str, pd.Series]:
"""计算KDJ指标"""
low_min = data['low'].rolling(window=period).min()
high_max = data['high'].rolling(window=period).max()
rsv = (data['close'] - low_min) / (high_max - low_min) * 100
k = rsv.ewm(alpha=1/signal_period, adjust=False).mean()
d = k.ewm(alpha=1/signal_period, adjust=False).mean()
j = 3 * k - 2 * d
return {
'k': k,
'd': d,
'j': j
}
def calculate_adx(data: pd.DataFrame, period: int = 14) -> Dict[str, pd.Series]:
"""计算ADX指标"""
high = data['high']
low = data['low']
close = data['close']
tr1 = high - low
tr2 = abs(high - close.shift())
tr3 = abs(low - close.shift())
tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
plus_dm = high.diff()
minus_dm = low.diff()
plus_dm[plus_dm < 0] = 0
minus_dm[minus_dm > 0] = 0
minus_dm = abs(minus_dm)
atr = tr.rolling(window=period).mean()
plus_di = (plus_dm.rolling(window=period).mean() / atr) * 100
minus_di = (minus_dm.rolling(window=period).mean() / atr) * 100
dx = (abs(plus_di - minus_di) / (plus_di + minus_di)) * 100
adx = dx.rolling(window=period).mean()
return {
'adx': adx,
'plus_di': plus_di,
'minus_di': minus_di
}
def calculate_atr(data: pd.DataFrame, period: int = 14) -> pd.Series:
"""计算ATR指标"""
high = data['high']
low = data['low']
close = data['close']
tr1 = high - low
tr2 = abs(high - close.shift())
tr3 = abs(low - close.shift())
tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
atr = tr.rolling(window=period).mean()
return atr
def calculate_moving_average(data: pd.DataFrame, periods: List[int]) -> Dict[str, pd.Series]:
"""计算移动平均线"""
mas = {}
for period in periods:
mas[f'ma{period}'] = data['close'].rolling(window=period).mean()
return mas
def calculate_price_quantile(data: pd.DataFrame, period: int = 100) -> float:
"""计算价格分位"""
prices = data['close'].tail(period)
current_price = prices.iloc[-1]
quantile = (prices <= current_price).sum() / len(prices)
return quantile
def calculate_volume_price_strength(data: pd.DataFrame, period: int = 20) -> float:
"""计算量价强度"""
df = data.tail(period).copy()
df['price_change'] = df['close'].pct_change()
df['volume_change'] = df['volume'].pct_change()
# 量价配合度
strength = 0
for i in range(1, len(df)):
if (df['price_change'].iloc[i] > 0 and df['volume_change'].iloc[i] > 0) or \
(df['price_change'].iloc[i] < 0 and df['volume_change'].iloc[i] < 0):
strength += abs(df['price_change'].iloc[i]) * (1 + abs(df['volume_change'].iloc[i]))
else:
strength -= abs(df['price_change'].iloc[i]) * (1 + abs(df['volume_change'].iloc[i]))
# 归一化到0-100
max_strength = abs(strength)
if max_strength == 0:
return 50
normalized_strength = (strength / max_strength + 1) / 2 * 100
return normalized_strength

@ -0,0 +1,9 @@
numpy==1.26.4
pandas==2.2.1
matplotlib==3.8.3
scikit-learn==1.4.0
tqsdk==1.6.3
requests==2.31.0
python-dotenv==1.0.0
APScheduler==3.10.4
pytest==7.4.4

@ -0,0 +1,192 @@
#!/usr/bin/env python3
# 系统测试脚本
import unittest
import pandas as pd
from qihuo_analyzer.data.data_fetcher import DataFetcher
from qihuo_analyzer.data.data_storage import DataStorage
from qihuo_analyzer.modules.trend_filter import TrendFilter
from qihuo_analyzer.modules.risk_manager import RiskManager
from qihuo_analyzer.modules.fund_flow_monitor import FundFlowMonitor
from qihuo_analyzer.modules.support_resistance import SupportResistance
from qihuo_analyzer.modules.rollover_detector import RolloverDetector
from qihuo_analyzer.modules.deepseek_agent import DeepseekAgent
class TestSystemComponents(unittest.TestCase):
"""测试系统组件"""
def setUp(self):
"""设置测试环境"""
self.symbol = "CU2309"
self.data_fetcher = DataFetcher()
self.data_storage = DataStorage()
def test_data_fetcher(self):
"""测试数据获取器"""
print("测试数据获取器...")
kline_data = self.data_fetcher.get_kline_data(self.symbol, "1d", 100)
self.assertFalse(kline_data.empty)
self.assertIn('close', kline_data.columns)
self.assertIn('volume', kline_data.columns)
print("✅ 数据获取器测试通过")
def test_data_storage(self):
"""测试数据存储"""
print("测试数据存储...")
kline_data = self.data_fetcher.get_kline_data(self.symbol, "1d", 20)
success = self.data_storage.save_kline_data(self.symbol, "1d", kline_data)
self.assertTrue(success)
# 测试读取数据
stored_data = self.data_storage.get_kline_data(self.symbol, "1d", 10)
self.assertFalse(stored_data.empty)
print("✅ 数据存储测试通过")
def test_trend_filter(self):
"""测试趋势过滤器"""
print("测试趋势过滤器...")
kline_data = self.data_fetcher.get_kline_data(self.symbol, "1d", 100)
trend_filter = TrendFilter()
# 测试趋势分析
trend_analysis = trend_filter.analyze_trend(kline_data)
self.assertIn('adx', trend_analysis)
self.assertIn('trend_strength', trend_analysis)
# 测试胜率计算
win_rate = trend_filter.calculate_win_rate(kline_data)
self.assertGreaterEqual(win_rate, 0)
self.assertLessEqual(win_rate, 100)
# 测试周期判断
cycle = trend_filter.judge_cycle(kline_data)
self.assertIn(cycle, ['short', 'medium', 'long'])
print("✅ 趋势过滤器测试通过")
def test_risk_manager(self):
"""测试风险管理器"""
print("测试风险管理器...")
kline_data = self.data_fetcher.get_kline_data(self.symbol, "1d", 100)
risk_manager = RiskManager()
# 测试止损计算
entry_price = kline_data['close'].iloc[-1]
stop_loss = risk_manager.calculate_stop_loss(kline_data, entry_price, "long")
self.assertLess(stop_loss, entry_price)
# 测试仓位计算
account_balance = 1000000
position_info = risk_manager.calculate_position_size(account_balance, kline_data, "long", entry_price)
self.assertIn('suggested_units', position_info)
self.assertGreater(position_info['suggested_units'], 0)
print("✅ 风险管理器测试通过")
def test_fund_flow_monitor(self):
"""测试资金流向监控器"""
print("测试资金流向监控器...")
kline_data = self.data_fetcher.get_kline_data(self.symbol, "1d", 100)
fund_flow_monitor = FundFlowMonitor()
# 测试资金流向分析
fund_flow_analysis = fund_flow_monitor.analyze_fund_flow(kline_data)
self.assertIn('fund_flow_strength', fund_flow_analysis)
self.assertIn('fund_signal', fund_flow_analysis)
print("✅ 资金流向监控器测试通过")
def test_support_resistance(self):
"""测试压力支撑分析器"""
print("测试压力支撑分析器...")
kline_data = self.data_fetcher.get_kline_data(self.symbol, "1d", 100)
support_resistance = SupportResistance()
# 测试压力支撑分析
sr_analysis = support_resistance.analyze_support_resistance(kline_data)
self.assertIn('support_resistance_levels', sr_analysis)
support_levels = sr_analysis['support_resistance_levels']['support_levels']
resistance_levels = sr_analysis['support_resistance_levels']['resistance_levels']
self.assertIsInstance(support_levels, list)
self.assertIsInstance(resistance_levels, list)
print("✅ 压力支撑分析器测试通过")
def test_rollover_detector(self):
"""测试换月检测器"""
print("测试换月检测器...")
kline_data = self.data_fetcher.get_kline_data(self.symbol, "1d", 100)
rollover_detector = RolloverDetector()
# 测试换月分析
rollover_analysis = rollover_detector.analyze_rollover(self.symbol, kline_data)
self.assertIn('expire_date', rollover_analysis)
self.assertIn('days_to_delivery', rollover_analysis)
self.assertIn('warning_level', rollover_analysis)
print("✅ 换月检测器测试通过")
def test_deepseek_agent(self):
"""测试DeepSeek代理"""
print("测试DeepSeek代理...")
deepseek_agent = DeepseekAgent()
# 测试市场分析
market_data = {
'symbol': self.symbol,
'latest_price': 35000,
'volume': 10000,
'open_interest': 50000,
'timeframe': '1d'
}
technical_indicators = {
'macd': {'signal': '金叉'},
'rsi': 55,
'bollinger': {'position': '中轨附近'},
'kdj': {'signal': '金叉'},
'atr': 200
}
trend_data = {
'adx': 25,
'trend_strength': 'medium',
'trend_direction': 'up',
'ma_relationship': 'bullish',
'overall_trend': 'strong_bullish',
'win_rate': 65
}
risk_metrics = {
'stop_loss': 34500,
'target_price': 36000,
'profit_loss_ratio': 1.8,
'position_size': 2,
'risk_ratio': 2.5
}
analysis_result = deepseek_agent.analyze_market(market_data, technical_indicators, trend_data, risk_metrics)
self.assertIn('trend_judgment', analysis_result)
self.assertIn('win_rate_assessment', analysis_result)
print("✅ DeepSeek代理测试通过")
def run_tests():
"""运行测试"""
print("="*60)
print("AI 期货分析系统 - 组件测试")
print("="*60)
# 创建测试套件
suite = unittest.TestLoader().loadTestsFromTestCase(TestSystemComponents)
# 运行测试
runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(suite)
print("\n" + "="*60)
if result.wasSuccessful():
print("✅ 所有测试通过!系统组件运行正常")
else:
print("❌ 测试失败,请检查系统组件")
print("="*60)
if __name__ == "__main__":
run_tests()

@ -0,0 +1,645 @@
#!/usr/bin/env python3
# Flask web 应用
from flask import Flask, render_template, jsonify, request
import sys
import os
# 添加项目根目录到 Python 路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from qihuo_analyzer.data.data_fetcher import DataFetcher
from qihuo_analyzer.data.data_storage import DataStorage
from qihuo_analyzer.modules.trend_filter import TrendFilter
from qihuo_analyzer.modules.risk_manager import RiskManager
from qihuo_analyzer.modules.fund_flow_monitor import FundFlowMonitor
from qihuo_analyzer.modules.support_resistance import SupportResistance
from qihuo_analyzer.modules.rollover_detector import RolloverDetector
from qihuo_analyzer.modules.deepseek_agent import DeepseekAgent
from qihuo_analyzer.core.models import AnalysisResult
app = Flask(__name__)
# 模板上下文处理器
@app.context_processor
def inject_functions():
import datetime
def now():
return datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
return {'now': now}
# 初始化组件
data_fetcher = DataFetcher()
data_storage = DataStorage()
trend_filter = TrendFilter()
risk_manager = RiskManager()
fund_flow_monitor = FundFlowMonitor()
support_resistance = SupportResistance()
rollover_detector = RolloverDetector()
deepseek_agent = DeepseekAgent()
# 连接API
data_fetcher.connect()
# 测试品种列表 - 使用当前有效的合约代码
test_symbols = ["CU2603", "AL2603", "ZN2603", "PB2603", "NI2603", "SN2603"]
@app.route('/')
def index():
"""首页 - 多品种分析面板"""
# 获取所有品种的分析数据
symbols_data = []
data_available = False
for symbol in test_symbols:
try:
# 获取K线数据
kline_data = data_fetcher.get_kline_data(symbol, "1d", 200)
if kline_data is None or kline_data.empty:
continue
data_available = True
# 趋势分析
trend_analysis = trend_filter.analyze_trend(kline_data)
win_rate = trend_filter.calculate_win_rate(kline_data)
cycle = trend_filter.judge_cycle(kline_data)
# 资金流向分析
fund_flow_analysis = fund_flow_monitor.analyze_fund_flow(kline_data)
# 换月分析
rollover_analysis = rollover_detector.analyze_rollover(symbol, kline_data)
# 价格数据
current_price = kline_data['close'].iloc[-1]
# 获取中文名称
product_name_cn = data_fetcher.get_product_name_cn(symbol)
# 转换周期为中文
def cycle_to_cn(cycle):
cycle_map = {
'short': '短期',
'medium': '中期',
'long': '长期',
'bullish': '多头',
'bearish': '空头',
'sideways': '震荡'
}
return cycle_map.get(cycle, cycle)
# 转换资金流向为中文
def fund_flow_to_cn(fund_flow):
fund_flow_map = {
'bullish': '多头',
'bearish': '空头',
'neutral': '中性',
'Strong_bullish': '强多头',
'Strong_bearish': '强空头'
}
return fund_flow_map.get(fund_flow, fund_flow)
# 构建数据
# 安全处理可能的NaN值
safe_current_price = 0
try:
if current_price is not None and (not isinstance(current_price, float) or current_price == current_price): # 不是NaN
safe_current_price = round(current_price, 2)
except (ValueError, TypeError):
safe_current_price = 0
safe_win_rate = 0
try:
if win_rate is not None and (not isinstance(win_rate, float) or win_rate == win_rate): # 不是NaN
safe_win_rate = round(win_rate, 1)
except (ValueError, TypeError):
safe_win_rate = 0
# 安全获取ADX值
safe_adx = 0
try:
adx = trend_analysis.get('adx', 0)
if adx is not None and (not isinstance(adx, float) or adx == adx): # 不是NaN
safe_adx = adx
except (ValueError, TypeError):
safe_adx = 0
symbol_data = {
'symbol': symbol,
'name': f"{product_name_cn}({symbol})".upper(),
'current_price': safe_current_price,
'direction': trend_analysis.get('overall_trend', 'sideways'),
'win_rate': safe_win_rate,
'trend_strength': _get_trend_strength_display(safe_adx),
'cycle': cycle_to_cn(cycle),
'rollover_warning': _get_rollover_warning(rollover_analysis),
'fund_flow': fund_flow_to_cn(fund_flow_analysis.get('fund_signal', 'neutral'))
}
symbols_data.append(symbol_data)
except Exception as e:
print(f"分析 {symbol} 失败: {e}")
continue
# 如果没有任何数据可用,显示友好提示
if not data_available:
return render_template('index.html', symbols_data=[], data_unavailable=True, message="无法获取真实市场数据请检查网络连接和TQSDK账号状态")
return render_template('index.html', symbols_data=symbols_data, data_unavailable=False)
@app.route('/symbol/<symbol>')
def symbol_detail(symbol):
"""品种详情页"""
try:
# 辅助函数:安全地四舍五入数字
def safe_round(value, decimals=2, context=''):
try:
if context:
print(f"[DEBUG] safe_round called with value: {value}, type: {type(value)}, context: {context}")
if value is None:
print(f"[DEBUG] safe_round returning 0 for None value, context: {context}")
return 0
# 检查是否为NaN
if isinstance(value, float) and value != value: # NaN检查
print(f"[DEBUG] safe_round returning 0 for NaN value, context: {context}")
return 0
# 检查是否为undefined
if value == 'undefined' or value == 'nan' or value == 'None':
print(f"[DEBUG] safe_round returning 0 for undefined-like value, context: {context}")
return 0
result = round(float(value), decimals)
if context:
print(f"[DEBUG] safe_round returning {result} for value: {value}, context: {context}")
return result
except (ValueError, TypeError) as e:
print(f"[DEBUG] safe_round exception: {e}, value: {value}, type: {type(value)}, context: {context}")
return 0
# 获取K线数据
kline_data = data_fetcher.get_kline_data(symbol, "1d", 200)
if kline_data is None or kline_data.empty:
return render_template('error.html', message="获取数据失败")
# 基础数据
try:
current_price = kline_data['close'].iloc[-1]
# 检查current_price是否为有效数字
if current_price is None or (isinstance(current_price, float) and current_price != current_price): # NaN检查
return render_template('error.html', message="获取数据失败:价格数据无效")
except (ValueError, TypeError, IndexError):
return render_template('error.html', message="获取数据失败:价格数据无效")
try:
price_change = kline_data['close'].iloc[-1] - kline_data['close'].iloc[-2]
price_change_pct = (price_change / kline_data['close'].iloc[-2]) * 100
except (ValueError, TypeError, IndexError):
price_change = 0
price_change_pct = 0
# 趋势分析
trend_analysis = trend_filter.analyze_trend(kline_data)
win_rate = trend_filter.calculate_win_rate(kline_data)
cycle = trend_filter.judge_cycle(kline_data)
# 资金流向分析
fund_flow_analysis = fund_flow_monitor.analyze_fund_flow(kline_data)
# 压力支撑分析
sr_analysis = support_resistance.analyze_support_resistance(kline_data)
support_levels = sr_analysis['support_resistance_levels']['support_levels']
resistance_levels = sr_analysis['support_resistance_levels']['resistance_levels']
# 风险分析
atr = trend_analysis.get('atr', 20)
stop_loss_long = risk_manager.calculate_stop_loss(kline_data, current_price, "long")
stop_loss_short = risk_manager.calculate_stop_loss(kline_data, current_price, "short")
# 仓位计算
account_balance = 1000000
position_info = risk_manager.calculate_position_size(account_balance, kline_data, "long", current_price)
# 换月分析
rollover_analysis = rollover_detector.analyze_rollover(symbol, kline_data)
# AI 分析
market_data = {
'symbol': symbol,
'latest_price': current_price,
'volume': kline_data['volume'].iloc[-1],
'open_interest': kline_data['open_interest'].iloc[-1],
'timeframe': '1d'
}
technical_indicators = {
'macd': {'signal': '金叉'},
'rsi': 55,
'bollinger': {'position': '中轨附近'},
'kdj': {'signal': '金叉'},
'atr': atr
}
# 安全获取趋势分析数据处理可能的NaN值
def safe_get_trend_value(key, default=0):
value = trend_analysis.get(key, default)
try:
if value is None:
return default
if isinstance(value, float) and value != value: # NaN检查
return default
return value
except (ValueError, TypeError):
return default
# 打印趋势分析数据
print(f"[DEBUG] trend_analysis: {trend_analysis}")
print(f"[DEBUG] win_rate: {win_rate}, type: {type(win_rate)}")
print(f"[DEBUG] atr: {atr}, type: {type(atr)}")
print(f"[DEBUG] stop_loss_long: {stop_loss_long}, type: {type(stop_loss_long)}")
print(f"[DEBUG] stop_loss_short: {stop_loss_short}, type: {type(stop_loss_short)}")
print(f"[DEBUG] position_info: {position_info}")
trend_data = {
'adx': safe_get_trend_value('adx'),
'trend_strength': safe_get_trend_value('trend_strength', 'none'),
'trend_direction': safe_get_trend_value('trend_direction', 'neutral'),
'ma_relationship': safe_get_trend_value('ma_relationship', 'neutral'),
'overall_trend': safe_get_trend_value('overall_trend', 'neutral'),
'win_rate': safe_round(win_rate, 1, context='trend_data_win_rate') if win_rate is not None else 0
}
# 安全获取止损和目标价格
safe_stop_loss = safe_round(stop_loss_long, 2, context='safe_stop_loss') if stop_loss_long is not None else 0
safe_target_price = 0
if resistance_levels:
try:
target = resistance_levels[0]
print(f"[DEBUG] resistance_levels[0]: {target}, type: {type(target)}")
if target is not None and (not isinstance(target, float) or target == target): # 不是NaN
safe_target_price = safe_round(target, 2, context='safe_target_price_from_resistance')
else:
safe_target_price = safe_round(current_price * 1.05, 2, context='safe_target_price_from_current')
except (ValueError, TypeError, IndexError) as e:
print(f"[DEBUG] Error getting target price: {e}")
safe_target_price = safe_round(current_price * 1.05, 2, context='safe_target_price_default')
else:
safe_target_price = safe_round(current_price * 1.05, 2, context='safe_target_price_no_resistance')
risk_metrics = {
'stop_loss': safe_stop_loss,
'target_price': safe_target_price,
'profit_loss_ratio': 1.8,
'position_size': position_info.get('suggested_units', 0),
'risk_ratio': safe_round(position_info.get('actual_risk_percent', 0) * 100, 2, context='risk_metrics_risk_ratio')
}
# AI 分析
ai_analysis = deepseek_agent.analyze_market(market_data, technical_indicators, trend_data, risk_metrics)
recommendation = deepseek_agent.generate_trade_recommendation(ai_analysis)
# 构建模板数据
# 获取中文名称
product_name_cn = data_fetcher.get_product_name_cn(symbol)
print(f"[DEBUG] product_name_cn: {product_name_cn}")
# 转换周期为中文
def cycle_to_cn(cycle):
cycle_map = {
'short': '短期',
'medium': '中期',
'long': '长期',
'bullish': '多头',
'bearish': '空头',
'sideways': '震荡'
}
return cycle_map.get(cycle, cycle)
# 转换趋势方向为中文
def direction_to_cn(direction):
direction_map = {
'bullish': '多头',
'bearish': '空头',
'sideways': '震荡'
}
return direction_map.get(direction, direction)
# 转换资金流向为中文
def fund_flow_to_cn(fund_flow):
fund_flow_map = {
'bullish': '多头',
'bearish': '空头',
'neutral': '中性',
'Strong_bullish': '强多头',
'Strong_bearish': '强空头',
'strong_increasing': '强劲增加',
'increasing': '增加',
'strong_decreasing': '强劲减少',
'decreasing': '减少',
'stable': '稳定',
'price_up_oi_up': '价涨量增',
'price_up_oi_down': '价涨量减',
'price_down_oi_up': '价跌量增',
'price_down_oi_down': '价跌量减',
'bullish_divergence': '看涨背离',
'bearish_divergence': '看跌背离',
'no_divergence': '无背离'
}
return fund_flow_map.get(fund_flow, fund_flow)
print(f"[DEBUG] current_price: {current_price}, type: {type(current_price)}")
print(f"[DEBUG] price_change: {price_change}, type: {type(price_change)}")
print(f"[DEBUG] price_change_pct: {price_change_pct}, type: {type(price_change_pct)}")
print(f"[DEBUG] cycle: {cycle}, type: {type(cycle)}")
context = {
'symbol': symbol,
'name': f"{product_name_cn}({symbol})".upper(),
'current_price': safe_round(current_price, 2, context='context_current_price'),
'price_change': safe_round(price_change, 2, context='context_price_change'),
'price_change_pct': safe_round(price_change_pct, 2, context='context_price_change_pct'),
'trend_analysis': trend_analysis,
'win_rate': safe_round(win_rate, 1, context='context_win_rate'),
'cycle': cycle_to_cn(cycle),
'fund_flow_analysis': fund_flow_analysis,
'sr_analysis': sr_analysis,
'support_levels': support_levels,
'resistance_levels': resistance_levels,
'risk_analysis': {
'atr': safe_round(atr, 2, context='risk_analysis_atr'),
'stop_loss_long': safe_round(stop_loss_long, 2, context='risk_analysis_stop_loss_long'),
'stop_loss_short': safe_round(stop_loss_short, 2, context='risk_analysis_stop_loss_short'),
'position_size': position_info.get('suggested_units', 0),
'risk_ratio': safe_round(position_info.get('actual_risk_percent', 0) * 100, 2, context='risk_analysis_risk_ratio'),
'leverage': safe_round(position_info.get('leverage', 0), 2, context='risk_analysis_leverage')
},
'rollover_analysis': rollover_analysis,
'ai_analysis': ai_analysis,
'recommendation': recommendation,
'kline_data': _get_kline_data_for_chart(kline_data)
}
print("[DEBUG] context built successfully")
return render_template('symbol_detail.html', **context)
except Exception as e:
print(f"分析 {symbol} 详情失败: {e}")
return render_template('error.html', message=f"分析失败: {str(e)}")
@app.route('/api/analysis/<symbol>')
def api_analysis(symbol):
"""API: 获取品种分析数据"""
try:
# 辅助函数:安全地四舍五入数字
def safe_round(value, decimals=2):
try:
if value is None:
return 0
# 检查是否为NaN
if isinstance(value, float) and value != value: # NaN检查
return 0
return round(float(value), decimals)
except (ValueError, TypeError):
return 0
# 获取K线数据
kline_data = data_fetcher.get_kline_data(symbol, "1d", 200)
if kline_data.empty:
return jsonify({"error": "获取数据失败"}), 400
# 趋势分析
trend_analysis = trend_filter.analyze_trend(kline_data)
win_rate = trend_filter.calculate_win_rate(kline_data)
cycle = trend_filter.judge_cycle(kline_data)
# 资金流向分析
fund_flow_analysis = fund_flow_monitor.analyze_fund_flow(kline_data)
# 压力支撑分析
sr_analysis = support_resistance.analyze_support_resistance(kline_data)
# 换月分析
rollover_analysis = rollover_detector.analyze_rollover(symbol, kline_data)
# 价格数据
current_price = kline_data['close'].iloc[-1]
# 构建响应
response = {
'symbol': symbol,
'current_price': safe_round(current_price, 2),
'trend_analysis': trend_analysis,
'win_rate': safe_round(win_rate, 1),
'cycle': cycle,
'fund_flow_analysis': fund_flow_analysis,
'sr_analysis': sr_analysis,
'rollover_analysis': rollover_analysis
}
return jsonify(response)
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/api/card/<symbol>')
def api_card(symbol):
"""API: 获取分析卡片数据"""
try:
# 辅助函数:安全地四舍五入数字
def safe_round(value, decimals=2):
try:
if value is None:
return 0
# 检查是否为NaN
if isinstance(value, float) and value != value: # NaN检查
return 0
return round(float(value), decimals)
except (ValueError, TypeError):
return 0
# 获取K线数据
kline_data = data_fetcher.get_kline_data(symbol, "1d", 200)
if kline_data.empty:
return jsonify({"error": "获取数据失败"}), 400
# 分析数据
trend_analysis = trend_filter.analyze_trend(kline_data)
win_rate = trend_filter.calculate_win_rate(kline_data)
fund_flow_analysis = fund_flow_monitor.analyze_fund_flow(kline_data)
sr_analysis = support_resistance.analyze_support_resistance(kline_data)
rollover_analysis = rollover_detector.analyze_rollover(symbol, kline_data)
# AI 分析
current_price = kline_data['close'].iloc[-1]
market_data = {
'symbol': symbol,
'latest_price': current_price,
'volume': kline_data['volume'].iloc[-1],
'open_interest': kline_data['open_interest'].iloc[-1],
'timeframe': '1d'
}
technical_indicators = {
'macd': {'signal': '金叉'},
'rsi': 55,
'bollinger': {'position': '中轨附近'},
'kdj': {'signal': '金叉'},
'atr': trend_analysis.get('atr', 20)
}
# 安全获取趋势分析数据处理可能的NaN值
def safe_get_trend_value(key, default=0):
value = trend_analysis.get(key, default)
try:
if value is None:
return default
if isinstance(value, float) and value != value: # NaN检查
return default
return value
except (ValueError, TypeError):
return default
trend_data = {
'adx': safe_get_trend_value('adx'),
'trend_strength': safe_get_trend_value('trend_strength', 'none'),
'trend_direction': safe_get_trend_value('trend_direction', 'neutral'),
'ma_relationship': safe_get_trend_value('ma_relationship', 'neutral'),
'overall_trend': safe_get_trend_value('overall_trend', 'neutral'),
'win_rate': safe_round(win_rate, 1) if win_rate is not None else 0
}
# 安全获取止损和目标价格
safe_stop_loss = 0
try:
stop_loss = risk_manager.calculate_stop_loss(kline_data, current_price, "long")
safe_stop_loss = safe_round(stop_loss, 2) if stop_loss is not None else 0
except Exception:
safe_stop_loss = 0
safe_target_price = 0
try:
resistance_levels = sr_analysis.get('support_resistance_levels', {}).get('resistance_levels', [])
if resistance_levels:
target = resistance_levels[0]
if target is not None and (not isinstance(target, float) or target == target): # 不是NaN
safe_target_price = safe_round(target, 2)
else:
safe_target_price = safe_round(current_price * 1.05, 2)
else:
safe_target_price = safe_round(current_price * 1.05, 2)
except Exception:
safe_target_price = safe_round(current_price * 1.05, 2)
risk_metrics = {
'stop_loss': safe_stop_loss,
'target_price': safe_target_price,
'profit_loss_ratio': 1.8,
'position_size': 2,
'risk_ratio': 2.5
}
ai_analysis = deepseek_agent.analyze_market(market_data, technical_indicators, trend_data, risk_metrics)
recommendation = deepseek_agent.generate_trade_recommendation(ai_analysis)
# 构建卡片数据
# 获取中文名称
product_name_cn = data_fetcher.get_product_name_cn(symbol)
# 转换趋势方向为中文
def direction_to_cn(direction):
direction_map = {
'bullish': '多头',
'bearish': '空头',
'sideways': '震荡'
}
return direction_map.get(direction, direction)
card_data = {
'symbol': symbol,
'name': f"{product_name_cn}({symbol})".upper(),
'current_price': safe_round(current_price, 2),
'direction': direction_to_cn(trend_analysis.get('overall_trend', 'sideways')),
'win_rate': safe_round(win_rate, 1),
'recommendation': recommendation,
'technical_indicators': technical_indicators,
'fund_flow': fund_flow_analysis,
'rollover_warning': rollover_analysis
}
return jsonify(card_data)
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/card/<symbol>')
def card(symbol):
"""分析卡片页面"""
try:
# 获取卡片数据
import requests
response = requests.get(f"http://localhost:5000/api/card/{symbol}")
card_data = response.json()
if 'error' in card_data:
return render_template('error.html', message=card_data['error'])
return render_template('card.html', **card_data)
except Exception as e:
print(f"获取卡片数据失败: {e}")
return render_template('error.html', message=f"获取卡片数据失败: {str(e)}")
def _get_trend_strength_display(adx):
"""获取趋势强度显示文本"""
try:
adx_value = float(adx) if adx is not None else 0
if adx_value > 40:
return f"强势多头({round(adx_value, 1)})"
elif adx_value > 25:
return f"多头({round(adx_value, 1)})"
elif adx_value > 20:
return f"弱势多头({round(adx_value, 1)})"
else:
return f"震荡({round(adx_value, 1)})"
except (ValueError, TypeError):
return "震荡(0.0)"
def _get_rollover_warning(rollover_analysis):
"""获取换月预警信息"""
try:
days = rollover_analysis.get('days_to_delivery', 0)
level = rollover_analysis.get('warning_level', 'low')
try:
days_value = int(days) if days is not None else 0
except (ValueError, TypeError):
days_value = 0
if level == 'critical':
return f"⚠️ {abs(days_value)}"
elif level == 'high':
return f"⚠️ {days_value}"
elif level == 'medium':
return f"⚠️ {days_value}"
else:
return f"{days_value}"
except (ValueError, TypeError):
return "✅ 0 天"
def _get_kline_data_for_chart(kline_data):
"""获取图表用的K线数据"""
try:
chart_data = []
if kline_data is not None and not kline_data.empty:
for idx, row in kline_data.tail(30).iterrows():
try:
chart_data.append({
'date': idx.strftime('%Y-%m-%d') if hasattr(idx, 'strftime') else '2023-01-01',
'open': float(row.get('open', 0)),
'high': float(row.get('high', 0)),
'low': float(row.get('low', 0)),
'close': float(row.get('close', 0)),
'volume': float(row.get('volume', 0))
})
except (ValueError, TypeError):
continue
return chart_data
except (ValueError, TypeError):
return []
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)

Binary file not shown.

@ -0,0 +1,405 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ name }} - 分析卡片</title>
<link href="https://cdn.jsdelivr.net/npm/antd@5.12.8/dist/reset.css" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background-color: #f0f2f5;
color: #333;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 24px;
border-radius: 12px;
margin-bottom: 24px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
font-size: 20px;
font-weight: 600;
}
.back-link {
color: white;
text-decoration: none;
font-size: 14px;
padding: 6px 12px;
background-color: rgba(255, 255, 255, 0.2);
border-radius: 6px;
transition: background-color 0.3s ease;
}
.back-link:hover {
background-color: rgba(255, 255, 255, 0.3);
}
.card {
background: white;
border-radius: 12px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
margin-bottom: 24px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
}
.symbol-info {
flex: 1;
}
.symbol-name {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 4px;
}
.symbol-code {
font-size: 14px;
color: #999;
}
.price-info {
text-align: right;
}
.current-price {
font-size: 32px;
font-weight: 700;
color: #333;
margin-bottom: 8px;
}
.direction-indicator {
display: inline-block;
padding: 6px 16px;
border-radius: 16px;
font-size: 14px;
font-weight: 500;
}
.direction-up {
background-color: #f6ffed;
color: #52c41a;
border: 1px solid #b7eb8f;
}
.direction-down {
background-color: #fff2f0;
color: #ff4d4f;
border: 1px solid #ffccc7;
}
.direction-sideways {
background-color: #f0f5ff;
color: #1890ff;
border: 1px solid #adc6ff;
}
.card-body {
margin-bottom: 20px;
}
.section {
margin-bottom: 20px;
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
}
.section-title::before {
content: '';
width: 4px;
height: 16px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 2px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 16px;
}
.stat-item {
background-color: #f9fafb;
padding: 16px;
border-radius: 8px;
text-align: center;
}
.stat-label {
font-size: 12px;
color: #666;
margin-bottom: 8px;
}
.stat-value {
font-size: 18px;
font-weight: 600;
color: #333;
}
.win-rate {
color: #1890ff;
}
.fund-flow {
color: #52c41a;
}
.rollover-warning {
color: #fa8c16;
}
.indicator-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px;
}
.indicator-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 12px;
background-color: #f9fafb;
border-radius: 6px;
}
.indicator-name {
font-size: 14px;
color: #666;
}
.indicator-value {
font-size: 14px;
font-weight: 500;
color: #333;
}
.recommendation {
background-color: #f0f5ff;
border-radius: 8px;
padding: 16px;
border-left: 4px solid #1890ff;
margin-top: 16px;
}
.recommendation-title {
font-size: 14px;
font-weight: 600;
color: #1890ff;
margin-bottom: 8px;
}
.recommendation-content {
font-size: 14px;
color: #333;
line-height: 1.5;
}
.card-footer {
text-align: center;
padding-top: 16px;
border-top: 1px solid #f0f0f0;
}
.detail-link {
color: #1890ff;
text-decoration: none;
font-size: 14px;
font-weight: 500;
display: inline-flex;
align-items: center;
gap: 4px;
}
.detail-link:hover {
text-decoration: underline;
}
.footer {
text-align: center;
padding: 20px;
color: #999;
font-size: 14px;
}
@media (max-width: 768px) {
.header-content {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.card-header {
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
.price-info {
text-align: left;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
.indicator-list {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<!-- 头部 -->
<div class="header">
<div class="header-content">
<h1>分析卡片</h1>
<a href="/" class="back-link">返回多品种面板</a>
</div>
</div>
<!-- 分析卡片 -->
<div class="card">
<div class="card-header">
<div class="symbol-info">
<div class="symbol-name">{{ name }}</div>
<div class="symbol-code">{{ symbol }}</div>
</div>
<div class="price-info">
<div class="current-price">{{ current_price }}</div>
<div class="direction-indicator
{% if direction == 'bullish' or direction == 'strong_bullish' or direction == 'weak_bullish' %}direction-up{% elif direction == 'bearish' or direction == 'strong_bearish' or direction == 'weak_bearish' %}direction-down{% else %}direction-sideways{% endif %}">
{% if direction == 'strong_bullish' %}强多头
{% elif direction == 'strong_bearish' %}强空头
{% elif direction == 'weak_bullish' %}弱多头
{% elif direction == 'weak_bearish' %}弱空头
{% elif direction == 'bullish' %}多头
{% elif direction == 'bearish' %}空头
{% elif direction == 'neutral' %}中性
{% else %}{{ direction | capitalize }}
{% endif %}
</div>
</div>
</div>
<div class="card-body">
<!-- 核心指标 -->
<div class="section">
<div class="section-title">核心指标</div>
<div class="stats-grid">
<div class="stat-item">
<div class="stat-label">胜率</div>
<div class="stat-value win-rate">{{ win_rate }}%</div>
</div>
<div class="stat-item">
<div class="stat-label">资金流向</div>
<div class="stat-value fund-flow">{{ fund_flow.fund_signal | capitalize }}</div>
</div>
<div class="stat-item">
<div class="stat-label">换月预警</div>
<div class="stat-value rollover-warning">{{ rollover_warning.warning_level | capitalize }}</div>
</div>
</div>
</div>
<!-- 技术指标 -->
<div class="section">
<div class="section-title">技术指标</div>
<div class="indicator-list">
<div class="indicator-item">
<div class="indicator-name">MACD</div>
<div class="indicator-value">{{ technical_indicators.macd.signal }}</div>
</div>
<div class="indicator-item">
<div class="indicator-name">RSI</div>
<div class="indicator-value">{{ technical_indicators.rsi }}</div>
</div>
<div class="indicator-item">
<div class="indicator-name">布林带</div>
<div class="indicator-value">{{ technical_indicators.bollinger.position }}</div>
</div>
<div class="indicator-item">
<div class="indicator-name">KDJ</div>
<div class="indicator-value">{{ technical_indicators.kdj.signal }}</div>
</div>
<div class="indicator-item">
<div class="indicator-name">ATR</div>
<div class="indicator-value">{{ technical_indicators.atr | round(2) }}</div>
</div>
</div>
</div>
<!-- AI 建议 -->
<div class="section">
<div class="section-title">AI交易建议</div>
<div class="recommendation">
<div class="recommendation-title">建议</div>
<div class="recommendation-content">{{ recommendation }}</div>
</div>
</div>
</div>
<div class="card-footer">
<a href="/symbol/{{ symbol }}" class="detail-link">查看详细分析 →</a>
</div>
</div>
<!-- 底部 -->
<div class="footer">
<p>© 2024 AI期货分析系统 | 数据更新时间: {{ now() }}</p>
</div>
</div>
<script>
// 获取当前时间
function now() {
const date = new Date();
return date.toLocaleString('zh-CN');
}
</script>
</body>
</html>

@ -0,0 +1,117 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>错误 - AI期货分析系统</title>
<link href="https://cdn.jsdelivr.net/npm/antd@5.12.8/dist/reset.css" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background-color: #f0f2f5;
color: #333;
}
.container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
}
.error-card {
background: white;
border-radius: 12px;
padding: 40px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
text-align: center;
}
.error-icon {
width: 80px;
height: 80px;
background-color: #fff2f0;
color: #ff4d4f;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 40px;
margin: 0 auto 24px;
}
.error-title {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 16px;
}
.error-message {
font-size: 16px;
color: #666;
margin-bottom: 32px;
line-height: 1.5;
}
.back-button {
display: inline-block;
padding: 10px 24px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
text-decoration: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
transition: opacity 0.3s ease;
}
.back-button:hover {
opacity: 0.9;
}
@media (max-width: 768px) {
.container {
padding: 16px;
}
.error-card {
padding: 32px 24px;
}
.error-icon {
width: 64px;
height: 64px;
font-size: 32px;
}
.error-title {
font-size: 20px;
}
.error-message {
font-size: 14px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="error-card">
<div class="error-icon">⚠️</div>
<h1 class="error-title">发生错误</h1>
<p class="error-message">{{ message }}</p>
<a href="/" class="back-button">返回首页</a>
</div>
</div>
</body>
</html>

@ -0,0 +1,272 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI期货分析系统 - 多品种分析面板</title>
<link href="https://cdn.jsdelivr.net/npm/antd@5.12.8/dist/reset.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background-color: #f0f2f5;
color: #333;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
border-radius: 12px;
margin-bottom: 30px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.header h1 {
font-size: 28px;
font-weight: 600;
margin-bottom: 10px;
}
.header p {
font-size: 16px;
opacity: 0.9;
}
.panel-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.symbol-card {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: transform 0.3s ease, box-shadow 0.3s ease;
cursor: pointer;
}
.symbol-card:hover {
transform: translateY(-5px);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
}
.symbol-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.symbol-name {
font-size: 18px;
font-weight: 600;
color: #333;
}
.symbol-code {
font-size: 14px;
color: #999;
margin-top: 4px;
}
.price-info {
text-align: right;
}
.current-price {
font-size: 24px;
font-weight: 700;
color: #333;
}
.direction-indicator {
display: inline-block;
padding: 4px 12px;
border-radius: 16px;
font-size: 12px;
font-weight: 500;
margin-top: 8px;
}
.direction-up {
background-color: #f6ffed;
color: #52c41a;
border: 1px solid #b7eb8f;
}
.direction-down {
background-color: #fff2f0;
color: #ff4d4f;
border: 1px solid #ffccc7;
}
.direction-sideways {
background-color: #f0f5ff;
color: #1890ff;
border: 1px solid #adc6ff;
}
.analysis-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-top: 20px;
}
.analysis-item {
background-color: #f9fafb;
padding: 12px;
border-radius: 8px;
}
.analysis-label {
font-size: 12px;
color: #666;
margin-bottom: 4px;
}
.analysis-value {
font-size: 14px;
font-weight: 500;
color: #333;
}
.win-rate {
color: #1890ff;
}
.rollover-warning {
color: #fa8c16;
}
.fund-flow {
color: #52c41a;
}
.footer {
text-align: center;
padding: 20px;
color: #999;
font-size: 14px;
margin-top: 40px;
}
@media (max-width: 768px) {
.panel-grid {
grid-template-columns: 1fr;
}
.analysis-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<!-- 头部 -->
<div class="header">
<h1>AI期货分析系统</h1>
<p>基于DeepSeek大模型和量化分析算法的智能决策平台</p>
</div>
<!-- 多品种分析面板 -->
{% if data_unavailable %}
<div style="text-align: center; padding: 60px; background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);">
<div style="font-size: 48px; margin-bottom: 20px;">⚠️</div>
<h2 style="font-size: 24px; font-weight: 600; color: #333; margin-bottom: 16px;">数据不可用</h2>
<p style="font-size: 16px; color: #666; margin-bottom: 32px;">{{ message }}</p>
<div style="display: flex; justify-content: center; gap: 16px;">
<a href="/" style="padding: 10px 24px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; text-decoration: none; border-radius: 6px; font-size: 14px; font-weight: 500;">重试</a>
<a href="javascript:location.reload()" style="padding: 10px 24px; background: #f0f0f0; color: #333; text-decoration: none; border-radius: 6px; font-size: 14px; font-weight: 500;">刷新</a>
</div>
</div>
{% else %}
<div class="panel-grid">
{% for symbol_data in symbols_data %}
<div class="symbol-card" onclick="window.location.href='/symbol/{{ symbol_data.symbol }}'">
<div class="symbol-header">
<div>
<div class="symbol-name">{{ symbol_data.name }}</div>
<div class="symbol-code">{{ symbol_data.symbol }}</div>
</div>
<div class="price-info">
<div class="current-price">{{ symbol_data.current_price }}</div>
<div class="direction-indicator
{% if symbol_data.direction == 'bullish' or symbol_data.direction == 'strong_bullish' or symbol_data.direction == 'weak_bullish' %}direction-up{% elif symbol_data.direction == 'bearish' or symbol_data.direction == 'strong_bearish' or symbol_data.direction == 'weak_bearish' %}direction-down{% else %}direction-sideways{% endif %}">
{% if symbol_data.direction == 'strong_bullish' %}强多头
{% elif symbol_data.direction == 'strong_bearish' %}强空头
{% elif symbol_data.direction == 'weak_bullish' %}弱多头
{% elif symbol_data.direction == 'weak_bearish' %}弱空头
{% elif symbol_data.direction == 'bullish' %}多头
{% elif symbol_data.direction == 'bearish' %}空头
{% elif symbol_data.direction == 'neutral' %}中性
{% else %}{{ symbol_data.direction | capitalize }}
{% endif %}
</div>
</div>
</div>
<div class="analysis-grid">
<div class="analysis-item">
<div class="analysis-label">胜率</div>
<div class="analysis-value win-rate">{{ symbol_data.win_rate }}%</div>
</div>
<div class="analysis-item">
<div class="analysis-label">趋势强度</div>
<div class="analysis-value">{{ symbol_data.trend_strength }}</div>
</div>
<div class="analysis-item">
<div class="analysis-label">周期</div>
<div class="analysis-value">{{ symbol_data.cycle }}</div>
</div>
<div class="analysis-item">
<div class="analysis-label">换月预警</div>
<div class="analysis-value rollover-warning">{{ symbol_data.rollover_warning }}</div>
</div>
<div class="analysis-item">
<div class="analysis-label">资金流向</div>
<div class="analysis-value fund-flow">{{ symbol_data.fund_flow }}</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% endif %}
<!-- 底部 -->
<div class="footer">
<p>© 2024 AI期货分析系统 | 数据更新时间: {{ now() }}</p>
</div>
</div>
<script>
// 获取当前时间
function now() {
const date = new Date();
return date.toLocaleString('zh-CN');
}
// 定时刷新数据
setInterval(() => {
window.location.reload();
}, 60000); // 每分钟刷新一次
</script>
</body>
</html>

@ -0,0 +1,630 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ name }} - AI期货分析系统</title>
<link href="https://cdn.jsdelivr.net/npm/antd@5.12.8/dist/reset.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background-color: #f0f2f5;
color: #333;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
border-radius: 12px;
margin-bottom: 30px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
font-size: 24px;
font-weight: 600;
margin-bottom: 8px;
}
.header p {
font-size: 14px;
opacity: 0.9;
}
.back-link {
color: white;
text-decoration: none;
font-size: 14px;
padding: 8px 16px;
background-color: rgba(255, 255, 255, 0.2);
border-radius: 6px;
transition: background-color 0.3s ease;
}
.back-link:hover {
background-color: rgba(255, 255, 255, 0.3);
}
.price-section {
background: white;
border-radius: 12px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
margin-bottom: 30px;
}
.price-info {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 20px;
}
.price-main {
flex: 1;
}
.price-value {
font-size: 48px;
font-weight: 700;
color: #333;
margin-bottom: 8px;
}
.price-change {
font-size: 16px;
font-weight: 500;
}
.price-change.up {
color: #52c41a;
}
.price-change.down {
color: #ff4d4f;
}
.price-stats {
display: flex;
gap: 30px;
margin-top: 20px;
}
.stat-item {
text-align: center;
}
.stat-label {
font-size: 14px;
color: #666;
margin-bottom: 4px;
}
.stat-value {
font-size: 18px;
font-weight: 600;
color: #333;
}
.chart-section {
background: white;
border-radius: 12px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
margin-bottom: 30px;
}
.chart-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
}
.chart-container {
width: 100%;
height: 400px;
}
.analysis-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.analysis-card {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.card-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid #f0f0f0;
}
.analysis-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #f9f9f9;
}
.analysis-label {
font-size: 14px;
color: #666;
}
.analysis-value {
font-size: 14px;
font-weight: 500;
color: #333;
}
.direction-up {
color: #52c41a;
}
.direction-down {
color: #ff4d4f;
}
.warning {
color: #fa8c16;
}
.ai-analysis {
background: white;
border-radius: 12px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
margin-bottom: 30px;
}
.ai-title {
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 8px;
}
.ai-icon {
width: 24px;
height: 24px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 14px;
font-weight: 700;
}
.ai-content {
background-color: #f9fafb;
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
}
.recommendation {
background-color: #f0f5ff;
border-radius: 8px;
padding: 16px;
border-left: 4px solid #1890ff;
}
.recommendation-title {
font-size: 14px;
font-weight: 600;
color: #1890ff;
margin-bottom: 8px;
}
.recommendation-content {
font-size: 14px;
color: #333;
}
.footer {
text-align: center;
padding: 20px;
color: #999;
font-size: 14px;
margin-top: 40px;
}
@media (max-width: 768px) {
.header-content {
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
.price-info {
flex-direction: column;
gap: 20px;
}
.price-stats {
flex-wrap: wrap;
gap: 16px;
}
.analysis-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<!-- 头部 -->
<div class="header">
<div class="header-content">
<div>
<h1>{{ name }}</h1>
<p>品种详细分析</p>
</div>
<a href="/" class="back-link">返回多品种面板</a>
</div>
</div>
<!-- 价格信息 -->
<div class="price-section">
<div class="price-info">
<div class="price-main">
<div class="price-value">{{ current_price }}</div>
<div class="price-change
{% if price_change > 0 %}up{% else %}down{% endif %}">
{{ price_change }} ({{ price_change_pct }}%)
</div>
</div>
<div class="price-stats">
<div class="stat-item">
<div class="stat-label">胜率</div>
<div class="stat-value">{{ win_rate }}%</div>
</div>
<div class="stat-item">
<div class="stat-label">趋势</div>
<div class="stat-value
{% if trend_analysis.overall_trend == 'bullish' or trend_analysis.overall_trend == 'strong_bullish' or trend_analysis.overall_trend == 'weak_bullish' %}direction-up{% elif trend_analysis.overall_trend == 'bearish' or trend_analysis.overall_trend == 'strong_bearish' or trend_analysis.overall_trend == 'weak_bearish' %}direction-down{% endif %}">
{% if trend_analysis.overall_trend == 'strong_bullish' %}强多头
{% elif trend_analysis.overall_trend == 'strong_bearish' %}强空头
{% elif trend_analysis.overall_trend == 'weak_bullish' %}弱多头
{% elif trend_analysis.overall_trend == 'weak_bearish' %}弱空头
{% elif trend_analysis.overall_trend == 'bullish' %}多头
{% elif trend_analysis.overall_trend == 'bearish' %}空头
{% elif trend_analysis.overall_trend == 'neutral' %}中性
{% else %}{{ trend_analysis.overall_trend | capitalize }}
{% endif %}
</div>
</div>
<div class="stat-item">
<div class="stat-label">周期</div>
<div class="stat-value">{{ cycle }}</div>
</div>
</div>
</div>
</div>
<!-- K线图表 -->
<div class="chart-section">
<div class="chart-title">K线走势图</div>
<div id="kline-chart" class="chart-container"></div>
</div>
<!-- 分析网格 -->
<div class="analysis-grid">
<!-- 趋势分析 -->
<div class="analysis-card">
<div class="card-title">趋势分析</div>
<div class="analysis-item">
<div class="analysis-label">趋势方向</div>
<div class="analysis-value
{% if trend_analysis.overall_trend == 'bullish' or trend_analysis.overall_trend == 'strong_bullish' or trend_analysis.overall_trend == 'weak_bullish' %}direction-up{% elif trend_analysis.overall_trend == 'bearish' or trend_analysis.overall_trend == 'strong_bearish' or trend_analysis.overall_trend == 'weak_bearish' %}direction-down{% endif %}">
{% if trend_analysis.overall_trend == 'strong_bullish' %}强多头
{% elif trend_analysis.overall_trend == 'strong_bearish' %}强空头
{% elif trend_analysis.overall_trend == 'weak_bullish' %}弱多头
{% elif trend_analysis.overall_trend == 'weak_bearish' %}弱空头
{% elif trend_analysis.overall_trend == 'bullish' %}多头
{% elif trend_analysis.overall_trend == 'bearish' %}空头
{% elif trend_analysis.overall_trend == 'neutral' %}中性
{% else %}{{ trend_analysis.overall_trend | capitalize }}
{% endif %}
</div>
</div>
<div class="analysis-item">
<div class="analysis-label">趋势强度</div>
<div class="analysis-value">{{ trend_analysis.trend_strength }}</div>
</div>
<div class="analysis-item">
<div class="analysis-label">ADX指标</div>
<div class="analysis-value">
{% if trend_analysis.adx is defined and trend_analysis.adx is not none %}
{{ trend_analysis.adx | round(2) }}
{% else %}
0
{% endif %}
</div>
</div>
<div class="analysis-item">
<div class="analysis-label">MA关系</div>
<div class="analysis-value">{{ trend_analysis.ma_relationship }}</div>
</div>
</div>
<!-- 风险分析 -->
<div class="analysis-card">
<div class="card-title">风险分析</div>
<div class="analysis-item">
<div class="analysis-label">ATR</div>
<div class="analysis-value">{{ risk_analysis.atr }}</div>
</div>
<div class="analysis-item">
<div class="analysis-label">多头止损</div>
<div class="analysis-value">{{ risk_analysis.stop_loss_long }}</div>
</div>
<div class="analysis-item">
<div class="analysis-label">空头止损</div>
<div class="analysis-value">{{ risk_analysis.stop_loss_short }}</div>
</div>
<div class="analysis-item">
<div class="analysis-label">仓位大小</div>
<div class="analysis-value">{{ risk_analysis.position_size }}</div>
</div>
<div class="analysis-item">
<div class="analysis-label">风险比例</div>
<div class="analysis-value">{{ risk_analysis.risk_ratio }}%</div>
</div>
</div>
<!-- 资金流向 -->
<div class="analysis-card">
<div class="card-title">资金流向</div>
<div class="analysis-item">
<div class="analysis-label">资金信号</div>
<div class="analysis-value
{% if fund_flow_analysis.fund_signal == 'positive' %}direction-up{% elif fund_flow_analysis.fund_signal == 'negative' %}direction-down{% endif %}">
{{ fund_flow_analysis.fund_signal | capitalize }}
</div>
</div>
<div class="analysis-item">
<div class="analysis-label">持仓变化</div>
<div class="analysis-value">
{% if fund_flow_analysis.open_interest_change is defined and fund_flow_analysis.open_interest_change is not none %}
{{ fund_flow_analysis.open_interest_change | round(2) }}%
{% else %}
0%
{% endif %}
</div>
</div>
<div class="analysis-item">
<div class="analysis-label">量价关系</div>
<div class="analysis-value">{{ fund_flow_analysis.volume_price_relationship }}</div>
</div>
</div>
<!-- 换月分析 -->
<div class="analysis-card">
<div class="card-title">换月分析</div>
<div class="analysis-item">
<div class="analysis-label">距离交割</div>
<div class="analysis-value warning">{{ rollover_analysis.days_to_delivery }} 天</div>
</div>
<div class="analysis-item">
<div class="analysis-label">预警级别</div>
<div class="analysis-value warning">{{ rollover_analysis.warning_level | capitalize }}</div>
</div>
<div class="analysis-item">
<div class="analysis-label">流动性风险</div>
<div class="analysis-value warning">{{ rollover_analysis.liquidity_risk }}</div>
</div>
</div>
</div>
<!-- AI分析 -->
<div class="ai-analysis">
<div class="ai-title">
<div class="ai-icon">AI</div>
AI智能分析
</div>
<div class="ai-content">
<div class="analysis-grid">
<!-- 趋势分析 -->
<div class="analysis-card">
<div class="card-title">趋势判断</div>
<div class="analysis-item">
<div class="analysis-label">趋势方向</div>
<div class="analysis-value
{% if ai_analysis.trend_judgment and ('多' in ai_analysis.trend_judgment) %}direction-up{% elif ai_analysis.trend_judgment and ('空' in ai_analysis.trend_judgment) %}direction-down{% endif %}">
{{ ai_analysis.trend_judgment | default('未知') }}
</div>
</div>
<div class="analysis-item">
<div class="analysis-label">胜率评估</div>
<div class="analysis-value">{{ ai_analysis.win_rate_assessment | default('未知') }}</div>
</div>
<div class="analysis-item">
<div class="analysis-label">风险预警</div>
<div class="analysis-value warning">{{ ai_analysis.risk_warning | default('无') }}</div>
</div>
<div class="analysis-item">
<div class="analysis-label">交易建议</div>
<div class="analysis-value
{% if ai_analysis.trade_recommendation and ('多' in ai_analysis.trade_recommendation) %}direction-up{% elif ai_analysis.trade_recommendation and ('空' in ai_analysis.trade_recommendation) %}direction-down{% endif %}">
{{ ai_analysis.trade_recommendation | default('未知') }}
</div>
</div>
</div>
<!-- 分析逻辑 -->
<div class="analysis-card">
<div class="card-title">分析逻辑</div>
<div style="padding: 8px 0;">
{{ ai_analysis.analysis_logic | default('无详细分析') }}
</div>
</div>
</div>
</div>
<!-- 交易建议 -->
<div class="recommendation" style="margin-top: 20px;">
<div class="recommendation-title">详细交易建议</div>
<div class="analysis-grid" style="margin-top: 16px;">
<!-- 交易参数 -->
<div class="analysis-card">
<div class="card-title">交易参数</div>
<div class="analysis-item">
<div class="analysis-label">交易方向</div>
<div class="analysis-value
{% if recommendation.direction == 'long' %}direction-up{% elif recommendation.direction == 'short' %}direction-down{% endif %}">
{% if recommendation.direction == 'long' %}多头
{% elif recommendation.direction == 'short' %}空头
{% else %}{{ recommendation.direction | default('未知') }}
{% endif %}
</div>
</div>
<div class="analysis-item">
<div class="analysis-label">入场价格</div>
<div class="analysis-value">{{ recommendation.entry_price | default('未知') }}</div>
</div>
<div class="analysis-item">
<div class="analysis-label">止损价格</div>
<div class="analysis-value warning">{{ recommendation.stop_loss | default('未知') }}</div>
</div>
<div class="analysis-item">
<div class="analysis-label">目标价格</div>
<div class="analysis-value direction-up">{{ recommendation.target_price | default('未知') }}</div>
</div>
<div class="analysis-item">
<div class="analysis-label">仓位大小</div>
<div class="analysis-value">{{ recommendation.position_size | default('未知') }}手</div>
</div>
</div>
<!-- 执行计划 -->
<div class="analysis-card">
<div class="card-title">执行计划</div>
<div style="padding: 8px 0;">
{{ recommendation.execution_plan | default('无详细计划') }}
</div>
</div>
<!-- 风险提示 -->
<div class="analysis-card">
<div class="card-title">风险提示</div>
<div style="padding: 8px 0; color: #ff4d4f;">
{{ recommendation.risk_tips | default('无风险提示') }}
</div>
</div>
</div>
</div>
</div>
<!-- 底部 -->
<div class="footer">
<p>© 2024 AI期货分析系统 | 数据更新时间: {{ now() }}</p>
</div>
</div>
<script>
// 获取当前时间
function now() {
const date = new Date();
return date.toLocaleString('zh-CN');
}
// 初始化K线图表
window.onload = function() {
const chart = echarts.init(document.getElementById('kline-chart'));
// 准备K线数据
const klineData = [
{% for item in kline_data %}
["{{ item.date }}", {{ item.open }}, {{ item.close }}, {{ item.low }}, {{ item.high }}],
{% endfor %}
];
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
}
},
legend: {
data: ['K线']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: klineData.map(item => item[0]),
boundaryGap: false
},
yAxis: {
type: 'value',
scale: true
},
series: [
{
name: 'K线',
type: 'candlestick',
data: klineData.map(item => [item[1], item[4], item[3], item[2]]),
itemStyle: {
color: '#52c41a',
color0: '#ff4d4f',
borderColor: '#52c41a',
borderColor0: '#ff4d4f'
}
}
]
};
chart.setOption(option);
// 响应式调整
window.addEventListener('resize', function() {
chart.resize();
});
};
</script>
</body>
</html>

@ -0,0 +1,372 @@
AI 期货分析系统是基于**DeepSeek 大模型**和**量化分析算法**的智能期货决策辅助系统,为期货投资者提供:
- 📊 **多维度市场分析**(技术面、资金面、政策面)
- 🤖 **AI 智能研判**(趋势判断、胜率评估、风险预警)
- ⚡ **全自动数据更新**(每日 3 次自动复盘)
- 🛡️ **专业风控管理**(止损建议、仓位管理、换月预警)
### 1.2 核心能力
| 能力维度 | 具体功能 | 技术实现 |
|---------|---------|---------|
| **趋势分析** | ADX 趋势强度、双均线过滤、多周期共振 | TrendFilter 类 |
| **风控管理** | ATR 动态仓位、技术位止损、盈亏比控制 | RiskManager 类 |
| **资金监控** | 持仓量分析、量价背离、资金流向 | FundFlowMonitor 类 |
| **压力支撑** | 关键价位识别、智能止损位、目标位测算 | SupportResistance 类 |
| **换月预警** | 交割日检测、流动性保护、自动减仓 | RolloverDetector 类 |
| **AI 研判** | DeepSeek 大模型、多维度融合、决策透明 | DeepseekAgent 类 |
---
## 2. 功能架构
### 2.1 系统架构图
```
┌─────────────────────────────────────────────────────────────┐
│ 用户界面层 (UI) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 多品种面板 │ │ 品种详情页 │ │ 分析卡片 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 业务逻辑层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ generate_card│ │ deepseek_agent│ │ risk_manager│ │
│ │ (卡片生成) │ │ (AI 分析) │ │ (风控计算) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 数据计算层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ fetch_market │ │ fund_flow │ │ trend_filter│ │
│ │ (市场数据) │ │ (资金分析) │ │ (趋势计算) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 数据源层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 天勤 TQSDK │ │ 新闻 API │ │ 数据库 │ │
│ │ (行情数据) │ │ (政策新闻) │ │ (SQLite) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 2.2 数据流向
```
API 获取 (天勤 TQSDK)
├─→ K 线数据 (open/high/low/close/volume/close_oi)
数据清洗与计算
├─→ 技术指标 (MACD/RSI/布林带/KDJ)
├─→ 趋势分析 (ADX/双均线/多周期)
├─→ 资金分析 (持仓量/量价关系)
└─→ 压力支撑 (枢轴点/近期高低点)
AI 综合研判 (DeepSeek)
├─→ 多维度数据融合
├─→ 趋势过滤应用
└─→ 交易建议生成
数据库存储 (SQLite)
前端展示 (Web 界面)
```
---
## 3. 业务逻辑
### 3.1 胜率计算逻辑
胜率是**综合评分**,由三个维度加权计算:
```
胜率 = 价格分位评分 × 权重 + 量价强度评分 × 权重 + 趋势强度评分 × 权重
```
**震荡市ADX<20权重分配**
- 价格分位25%(降低,避免逆势抄底)
- 量价分析60%(提高,震荡市更重要)
- 趋势强度15%
**趋势市ADX≥20权重分配**
- 价格分位30-60%(随 ADX 递减)
- 量价分析40%
- 趋势调整:根据方向±(ADX-20)×0.5%
**价格分位评分标准**
- <20%极低90 分
- <40%相对低75 分
- 40-60%中性55 分
- >60%相对高40 分
- >80%极高25 分
### 3.2 方向判断逻辑
方向(做多/做空/观望)由**多因素投票**决定:
1. **资金面信号**1 票)
- 胜率>60% → 做多
- 胜率<40% → 做空
- 40-60% → 观望
2. **技术面信号**1 票)
- MACD 金叉/多头排列 → 做多
- MACD 死叉/空头排列 → 做空
- RSI 超买且做多 → 修正为观望
- RSI 超卖且做空 → 修正为观望
3. **价格位置信号**1 票)
- 价格分位<30% → 做多
- 价格分位>70% → 做空
- 30-70% → 观望
4. **政策面信号**1 票)
- 利好 → 做多
- 利空 → 做空
- 中性 → 观望
**最终方向**:得票最多的方向
**趋势过滤**ADX<20无趋势强制改为「观望」
### 3.3 周期判断逻辑
周期(短线/中线/长线)由**多周期一致性**决定:
```Python
if 所有周期方向一致且不为观望:
cycle = 「短线」 # 各周期共振,适合短线操作
else:
cycle = 「中线」 # 周期分歧,中线布局
if 极强趋势ADX>40 且所有周期同向):
cycle = 「长线」 # 强势趋势,长线持有
```
## 4. 数据指标说明
### 4.1 核心指标
#### 资金胜率 (fund_probability)
| 范围 | 含义 | 建议 |
|------|------|------|
| 80-100% | 极高胜率 | 高置信度交易机会 |
| 60-80% | 中高胜率 | 较好的交易机会 |
| 40-60% | 中性 | 观望或轻仓试探 |
| 20-40% | 较低胜率 | 谨慎或回避 |
| 0-20% | 低胜率 | 不建议交易 |
**计算公式**
```
震荡市: 价格分位×25% + 量价强度×60% + 趋势×15%
趋势市: 价格分位×(0.3-0.6) + 量价强度×40% + 趋势调整
```
#### ADX 趋势强度
| ADX 值 | 趋势状态 | 交易策略 |
|-------|---------|---------|
| >40 | 强趋势 | 顺势交易,禁止逆势 |
| 25-40 | 中等趋势 | 顺势为主 |
| 20-25 | 弱趋势 | 谨慎参与 |
| <20 | 无趋势/震荡 | 高抛低吸或观望 |
#### 持仓量信号
| 信号类型 | 持仓变化 | 价格变化 | 含义 |
|---------|---------|---------|------|
| 增仓上涨 | ↑ >5% | ↑ | 多头主力积极建仓 |
| 减仓上涨 | ↓ >5% | ↑ | 资金离场,上涨动能减弱 |
| 增仓下跌 | ↑ >5% | ↓ | 空头主力积极建仓 |
| 减仓下跌 | ↓ >5% | ↓ | 资金离场,下跌动能减弱 |
| 持仓平稳 | -5%~+5% | - | 多空双方观望 |
### 4.2 技术指标详解
#### MACD (指数平滑异同平均线)
**金叉/死叉判断**
- 金叉DIF 上穿 DEA → 买入信号
- 死叉DIF 下穿 DEA → 卖出信号
**多头排列**
- MACD 柱状图>0 且持续扩大
- DIF>DEA>0
#### RSI (相对强弱指标)
| RSI 值 | 状态 | 操作建议 |
|-------|------|---------|
| >70 | 超买 | 警惕回调,考虑减仓 |
| 50-70 | 偏强 | 多头占优 |
| 30-50 | 偏弱 | 空头占优 |
| <30 | 超卖 | 警惕反弹,考虑建仓 |
#### 布林带 (Bollinger Bands)
| 位置 | 含义 | 策略 |
|------|------|------|
| 触及上轨 | 相对高位 | 警惕回落 |
| 中轨附近 | 均衡位置 | 观望或轻仓 |
| 触及下轨 | 相对低位 | 警惕反弹 |
#### KDJ (随机指标)
| 信号 | 条件 | 操作 |
|------|------|------|
| 金叉 | K 上穿 D | 买入信号 |
| 死叉 | K 下穿 D | 卖出信号 |
| 超买 | K,D,J>80 | 警惕回调 |
| 超卖 | K,D,J<20 | 警惕反弹 |
### 4.3 风控指标
#### ATR (真实波动幅度)
**止损位计算**
```
做多止损 = 入场价 - ATR × 倍数(通常 2 倍)
做空止损 = 入场价 + ATR × 倍数(通常 2 倍)
```
**仓位计算**
```
建议手数 = 账户资金 × 风险比例 / (ATR × 合约乘数)
```
#### 盈亏比
```
盈亏比 = (目标价 - 入场价) / (入场价 - 止损价)
要求: 盈亏比 >= 1.5
```
**示例**
- 入场价3500
- 止损价345050 点)
- 目标价3600100 点)
- 盈亏比100/50 = 2.0 ✅
---
## 5. 界面展示说明
### 5.1 多品种分析面板
**展示字段**
| 字段 | 说明 | 示例 |
|------|------|------|
| **品种名称** | 期货品种 | 沥青(BU) |
| **当前价格** | 最新价格 | 3500 |
| **趋势方向** | AI 建议方向 | 观望/做多/做空 |
| **胜率** | 综合胜率 | 75% |
| **趋势强度** | ADX 评分 | 强势多头(85) |
| **周期** | 建议周期 | 短线/中线 |
| **换月预警** | 距离交割天数 | ⚠️ 12 天 |
| **主力资金** | 资金流向 | 增仓上涨 |
**颜色标识**
- 🟢 看多/强烈看多
- 🔴 看空/强烈看空
- ⚪ 观望/中性
### 5.2 品种详情页
**模块 1: 市场概况**
- 当前价格、涨跌幅
- 价格分位(近一年)
- 波动率ATR 比率)
**模块 2: 技术指标**
- MACD 信号(金叉/死叉/多头排列)
- RSI 值(超买/超卖/中性)
- 布林带位置(上轨/中轨/下轨)
- KDJ 信号(金叉/死叉)
**模块 3: 趋势分析**
- ADX 趋势强度
- 多周期方向5min/30min/1hour/1day
- 多周期一致性说明
**模块 4: 压力支撑**
- 最近支撑位(距离)
- 最近阻力位(距离)
- 智能止损位建议
- 目标位测算
**模块 5: 主力资金**
- 持仓量变化(百分比)
- 价格变化5 日)
- 资金信号解读
- 综合评分
**模块 6: AI 交易建议**
- 方向:做多/做空/观望
- 周期:短线/中线/长线
- 入场价、止损价、目标价
- 信心等级:高/中/低
- 详细理由说明
**模块 7: 风控建议**
- 建议手数
- 每手保证金
- 最大仓位限制
- 单日最大亏损限制
- 风险回报比
**模块 8: 相关新闻**
- 最新政策新闻列表
- 新闻情绪(积极/中性/消极)
- 新闻摘要
### 5.3 分析卡片文本
**格式示例**
```
┌─────────────────────────────────────┐
│ 📊 沥青(BU) 分析卡片 │
├─────────────────────────────────────┤
│ 当前价格: 3500 │
│ 趋势方向: 观望 | 周期: 短线 │
│ 资金胜率: 75% │
├─────────────────────────────────────┤
│ 🎯 交易建议 │
│ • 方向: 观望 │
│ • 入场: 3450 │
│ • 止损: 3400 (2 倍 ATR) │
│ • 目标: 3600 (盈亏比 2.0) │
│ • 信心: 中 │
├─────────────────────────────────────┤
│ 📈 技术指标 │
│ • MACD: 多头排列 │
│ • RSI: 60(中性) │
│ • 布林带: 中轨区域 │
├─────────────────────────────────────┤
│ 💰 主力资金 │
│ • 持仓大增 10% │
│ • 价格上升 5% │
│ • 多头主力积极建仓 │
├─────────────────────────────────────┤
│ ⚠️ 风险提示 │
│ • 换月期预警: 距离交割 12 天 │
│ • 多周期分歧: 仅日线看多 │
└─────────────────────────────────────┘
```
Loading…
Cancel
Save