commit b167d953db9c54f48f620bd020bd7eb337539256 Author: Lxy Date: Sat Feb 14 12:22:09 2026 +0800 初始化项目,提交基础代码(首页、分析详情)已支持 diff --git a/# AI 期货分析系统运行指南.ini b/# AI 期货分析系统运行指南.ini new file mode 100644 index 0000000..178012a --- /dev/null +++ b/# AI 期货分析系统运行指南.ini @@ -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 模型 \ No newline at end of file diff --git a/.env b/.env new file mode 100644 index 0000000..0af9986 --- /dev/null +++ b/.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 \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..c2c4900 --- /dev/null +++ b/.env.example @@ -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 \ No newline at end of file diff --git a/data/futures_analysis.db b/data/futures_analysis.db new file mode 100644 index 0000000..608c458 Binary files /dev/null and b/data/futures_analysis.db differ diff --git a/demo.py b/demo.py new file mode 100644 index 0000000..f1eee4c --- /dev/null +++ b/demo.py @@ -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() diff --git a/qihuo_analyzer/__init__.py b/qihuo_analyzer/__init__.py new file mode 100644 index 0000000..72e6721 --- /dev/null +++ b/qihuo_analyzer/__init__.py @@ -0,0 +1,3 @@ +# 期货分析系统版本信息 +__version__ = "1.0.0" +__author__ = "AI Futures Analyzer" diff --git a/qihuo_analyzer/__pycache__/__init__.cpython-311.pyc b/qihuo_analyzer/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..0106008 Binary files /dev/null and b/qihuo_analyzer/__pycache__/__init__.cpython-311.pyc differ diff --git a/qihuo_analyzer/core/__pycache__/models.cpython-311.pyc b/qihuo_analyzer/core/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000..2c42fba Binary files /dev/null and b/qihuo_analyzer/core/__pycache__/models.cpython-311.pyc differ diff --git a/qihuo_analyzer/core/models.py b/qihuo_analyzer/core/models.py new file mode 100644 index 0000000..eb0e2a3 --- /dev/null +++ b/qihuo_analyzer/core/models.py @@ -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 diff --git a/qihuo_analyzer/data/__pycache__/data_fetcher.cpython-311.pyc b/qihuo_analyzer/data/__pycache__/data_fetcher.cpython-311.pyc new file mode 100644 index 0000000..494f83f Binary files /dev/null and b/qihuo_analyzer/data/__pycache__/data_fetcher.cpython-311.pyc differ diff --git a/qihuo_analyzer/data/__pycache__/data_storage.cpython-311.pyc b/qihuo_analyzer/data/__pycache__/data_storage.cpython-311.pyc new file mode 100644 index 0000000..f41af78 Binary files /dev/null and b/qihuo_analyzer/data/__pycache__/data_storage.cpython-311.pyc differ diff --git a/qihuo_analyzer/data/data_fetcher.py b/qihuo_analyzer/data/data_fetcher.py new file mode 100644 index 0000000..1b62b6f --- /dev/null +++ b/qihuo_analyzer/data/data_fetcher.py @@ -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 diff --git a/qihuo_analyzer/data/data_storage.py b/qihuo_analyzer/data/data_storage.py new file mode 100644 index 0000000..0655878 --- /dev/null +++ b/qihuo_analyzer/data/data_storage.py @@ -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 diff --git a/qihuo_analyzer/modules/__pycache__/deepseek_agent.cpython-311.pyc b/qihuo_analyzer/modules/__pycache__/deepseek_agent.cpython-311.pyc new file mode 100644 index 0000000..b5d63b4 Binary files /dev/null and b/qihuo_analyzer/modules/__pycache__/deepseek_agent.cpython-311.pyc differ diff --git a/qihuo_analyzer/modules/__pycache__/fund_flow_monitor.cpython-311.pyc b/qihuo_analyzer/modules/__pycache__/fund_flow_monitor.cpython-311.pyc new file mode 100644 index 0000000..61b1107 Binary files /dev/null and b/qihuo_analyzer/modules/__pycache__/fund_flow_monitor.cpython-311.pyc differ diff --git a/qihuo_analyzer/modules/__pycache__/risk_manager.cpython-311.pyc b/qihuo_analyzer/modules/__pycache__/risk_manager.cpython-311.pyc new file mode 100644 index 0000000..9803e56 Binary files /dev/null and b/qihuo_analyzer/modules/__pycache__/risk_manager.cpython-311.pyc differ diff --git a/qihuo_analyzer/modules/__pycache__/rollover_detector.cpython-311.pyc b/qihuo_analyzer/modules/__pycache__/rollover_detector.cpython-311.pyc new file mode 100644 index 0000000..ef7fb85 Binary files /dev/null and b/qihuo_analyzer/modules/__pycache__/rollover_detector.cpython-311.pyc differ diff --git a/qihuo_analyzer/modules/__pycache__/support_resistance.cpython-311.pyc b/qihuo_analyzer/modules/__pycache__/support_resistance.cpython-311.pyc new file mode 100644 index 0000000..a6fe6de Binary files /dev/null and b/qihuo_analyzer/modules/__pycache__/support_resistance.cpython-311.pyc differ diff --git a/qihuo_analyzer/modules/__pycache__/trend_filter.cpython-311.pyc b/qihuo_analyzer/modules/__pycache__/trend_filter.cpython-311.pyc new file mode 100644 index 0000000..8de17cf Binary files /dev/null and b/qihuo_analyzer/modules/__pycache__/trend_filter.cpython-311.pyc differ diff --git a/qihuo_analyzer/modules/deepseek_agent.py b/qihuo_analyzer/modules/deepseek_agent.py new file mode 100644 index 0000000..45edc52 --- /dev/null +++ b/qihuo_analyzer/modules/deepseek_agent.py @@ -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 diff --git a/qihuo_analyzer/modules/fund_flow_monitor.py b/qihuo_analyzer/modules/fund_flow_monitor.py new file mode 100644 index 0000000..17acd6e --- /dev/null +++ b/qihuo_analyzer/modules/fund_flow_monitor.py @@ -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 + } diff --git a/qihuo_analyzer/modules/risk_manager.py b/qihuo_analyzer/modules/risk_manager.py new file mode 100644 index 0000000..2a8bf0a --- /dev/null +++ b/qihuo_analyzer/modules/risk_manager.py @@ -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'") diff --git a/qihuo_analyzer/modules/rollover_detector.py b/qihuo_analyzer/modules/rollover_detector.py new file mode 100644 index 0000000..288aba5 --- /dev/null +++ b/qihuo_analyzer/modules/rollover_detector.py @@ -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 diff --git a/qihuo_analyzer/modules/support_resistance.py b/qihuo_analyzer/modules/support_resistance.py new file mode 100644 index 0000000..e685732 --- /dev/null +++ b/qihuo_analyzer/modules/support_resistance.py @@ -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 + } diff --git a/qihuo_analyzer/modules/trend_filter.py b/qihuo_analyzer/modules/trend_filter.py new file mode 100644 index 0000000..e3a47fc --- /dev/null +++ b/qihuo_analyzer/modules/trend_filter.py @@ -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' diff --git a/qihuo_analyzer/utils/__pycache__/config_manager.cpython-311.pyc b/qihuo_analyzer/utils/__pycache__/config_manager.cpython-311.pyc new file mode 100644 index 0000000..e1342aa Binary files /dev/null and b/qihuo_analyzer/utils/__pycache__/config_manager.cpython-311.pyc differ diff --git a/qihuo_analyzer/utils/__pycache__/technical_analysis.cpython-311.pyc b/qihuo_analyzer/utils/__pycache__/technical_analysis.cpython-311.pyc new file mode 100644 index 0000000..2771879 Binary files /dev/null and b/qihuo_analyzer/utils/__pycache__/technical_analysis.cpython-311.pyc differ diff --git a/qihuo_analyzer/utils/config_manager.py b/qihuo_analyzer/utils/config_manager.py new file mode 100644 index 0000000..ef816ff --- /dev/null +++ b/qihuo_analyzer/utils/config_manager.py @@ -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() diff --git a/qihuo_analyzer/utils/technical_analysis.py b/qihuo_analyzer/utils/technical_analysis.py new file mode 100644 index 0000000..9a294e1 --- /dev/null +++ b/qihuo_analyzer/utils/technical_analysis.py @@ -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 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3edd9bd --- /dev/null +++ b/requirements.txt @@ -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 diff --git a/test_system.py b/test_system.py new file mode 100644 index 0000000..bf46f0f --- /dev/null +++ b/test_system.py @@ -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() diff --git a/web/app.py b/web/app.py new file mode 100644 index 0000000..87c92e5 --- /dev/null +++ b/web/app.py @@ -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/') +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/') +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/') +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/') +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) diff --git a/web/data/futures_analysis.db b/web/data/futures_analysis.db new file mode 100644 index 0000000..46d935b Binary files /dev/null and b/web/data/futures_analysis.db differ diff --git a/web/templates/card.html b/web/templates/card.html new file mode 100644 index 0000000..e213a4b --- /dev/null +++ b/web/templates/card.html @@ -0,0 +1,405 @@ + + + + + + {{ name }} - 分析卡片 + + + + +
+ +
+
+

分析卡片

+ 返回多品种面板 +
+
+ + +
+
+
+
{{ name }}
+
{{ symbol }}
+
+
+
{{ current_price }}
+
+ {% 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 %} +
+
+
+ +
+ +
+
核心指标
+
+
+
胜率
+
{{ win_rate }}%
+
+
+
资金流向
+
{{ fund_flow.fund_signal | capitalize }}
+
+
+
换月预警
+
{{ rollover_warning.warning_level | capitalize }}
+
+
+
+ + +
+
技术指标
+
+
+
MACD
+
{{ technical_indicators.macd.signal }}
+
+
+
RSI
+
{{ technical_indicators.rsi }}
+
+
+
布林带
+
{{ technical_indicators.bollinger.position }}
+
+
+
KDJ
+
{{ technical_indicators.kdj.signal }}
+
+
+
ATR
+
{{ technical_indicators.atr | round(2) }}
+
+
+
+ + +
+
AI交易建议
+
+
建议
+
{{ recommendation }}
+
+
+
+ + +
+ + + +
+ + + + \ No newline at end of file diff --git a/web/templates/error.html b/web/templates/error.html new file mode 100644 index 0000000..7906c94 --- /dev/null +++ b/web/templates/error.html @@ -0,0 +1,117 @@ + + + + + + 错误 - AI期货分析系统 + + + + +
+
+
⚠️
+

发生错误

+

{{ message }}

+ 返回首页 +
+
+ + \ No newline at end of file diff --git a/web/templates/index.html b/web/templates/index.html new file mode 100644 index 0000000..e53be2a --- /dev/null +++ b/web/templates/index.html @@ -0,0 +1,272 @@ + + + + + + AI期货分析系统 - 多品种分析面板 + + + + + +
+ +
+

AI期货分析系统

+

基于DeepSeek大模型和量化分析算法的智能决策平台

+
+ + + {% if data_unavailable %} +
+
⚠️
+

数据不可用

+

{{ message }}

+
+ 重试 + 刷新 +
+
+ {% else %} +
+ {% for symbol_data in symbols_data %} +
+
+
+
{{ symbol_data.name }}
+
{{ symbol_data.symbol }}
+
+
+
{{ symbol_data.current_price }}
+
+ {% 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 %} +
+
+
+ +
+
+
胜率
+
{{ symbol_data.win_rate }}%
+
+
+
趋势强度
+
{{ symbol_data.trend_strength }}
+
+
+
周期
+
{{ symbol_data.cycle }}
+
+
+
换月预警
+
{{ symbol_data.rollover_warning }}
+
+
+
资金流向
+
{{ symbol_data.fund_flow }}
+
+
+
+ {% endfor %} +
+ {% endif %} + + + +
+ + + + \ No newline at end of file diff --git a/web/templates/symbol_detail.html b/web/templates/symbol_detail.html new file mode 100644 index 0000000..1d1bd1e --- /dev/null +++ b/web/templates/symbol_detail.html @@ -0,0 +1,630 @@ + + + + + + {{ name }} - AI期货分析系统 + + + + + +
+ +
+
+
+

{{ name }}

+

品种详细分析

+
+ 返回多品种面板 +
+
+ + +
+
+
+
{{ current_price }}
+
+ {{ price_change }} ({{ price_change_pct }}%) +
+
+
+
+
胜率
+
{{ win_rate }}%
+
+
+
趋势
+
+ {% 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 %} +
+
+
+
周期
+
{{ cycle }}
+
+
+
+
+ + +
+
K线走势图
+
+
+ + +
+ +
+
趋势分析
+
+
趋势方向
+
+ {% 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 %} +
+
+
+
趋势强度
+
{{ trend_analysis.trend_strength }}
+
+
+
ADX指标
+
+ {% if trend_analysis.adx is defined and trend_analysis.adx is not none %} + {{ trend_analysis.adx | round(2) }} + {% else %} + 0 + {% endif %} +
+
+
+
MA关系
+
{{ trend_analysis.ma_relationship }}
+
+
+ + +
+
风险分析
+
+
ATR
+
{{ risk_analysis.atr }}
+
+
+
多头止损
+
{{ risk_analysis.stop_loss_long }}
+
+
+
空头止损
+
{{ risk_analysis.stop_loss_short }}
+
+
+
仓位大小
+
{{ risk_analysis.position_size }}
+
+
+
风险比例
+
{{ risk_analysis.risk_ratio }}%
+
+
+ + +
+
资金流向
+
+
资金信号
+
+ {{ fund_flow_analysis.fund_signal | capitalize }} +
+
+
+
持仓变化
+
+ {% 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 %} +
+
+
+
量价关系
+
{{ fund_flow_analysis.volume_price_relationship }}
+
+
+ + +
+
换月分析
+
+
距离交割
+
{{ rollover_analysis.days_to_delivery }} 天
+
+
+
预警级别
+
{{ rollover_analysis.warning_level | capitalize }}
+
+
+
流动性风险
+
{{ rollover_analysis.liquidity_risk }}
+
+
+
+ + +
+
+
AI
+ AI智能分析 +
+
+
+ +
+
趋势判断
+
+
趋势方向
+
+ {{ ai_analysis.trend_judgment | default('未知') }} +
+
+
+
胜率评估
+
{{ ai_analysis.win_rate_assessment | default('未知') }}
+
+
+
风险预警
+
{{ ai_analysis.risk_warning | default('无') }}
+
+
+
交易建议
+
+ {{ ai_analysis.trade_recommendation | default('未知') }} +
+
+
+ + +
+
分析逻辑
+
+ {{ ai_analysis.analysis_logic | default('无详细分析') }} +
+
+
+
+ + +
+
详细交易建议
+
+ +
+
交易参数
+
+
交易方向
+
+ {% if recommendation.direction == 'long' %}多头 + {% elif recommendation.direction == 'short' %}空头 + {% else %}{{ recommendation.direction | default('未知') }} + {% endif %} +
+
+
+
入场价格
+
{{ recommendation.entry_price | default('未知') }}
+
+
+
止损价格
+
{{ recommendation.stop_loss | default('未知') }}
+
+
+
目标价格
+
{{ recommendation.target_price | default('未知') }}
+
+
+
仓位大小
+
{{ recommendation.position_size | default('未知') }}手
+
+
+ + +
+
执行计划
+
+ {{ recommendation.execution_plan | default('无详细计划') }} +
+
+ + +
+
风险提示
+
+ {{ recommendation.risk_tips | default('无风险提示') }} +
+
+
+
+
+ + + +
+ + + + \ No newline at end of file diff --git a/期货分析系统架构提示.ini b/期货分析系统架构提示.ini new file mode 100644 index 0000000..a6dc3f4 --- /dev/null +++ b/期货分析系统架构提示.ini @@ -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 +- 止损价:3450(50 点) +- 目标价:3600(100 点) +- 盈亏比: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 天 │ +│ • 多周期分歧: 仅日线看多 │ +└─────────────────────────────────────┘ +``` \ No newline at end of file