diff --git a/backend/service_implementation/qihuo_analyzer/data/__pycache__/data_fetcher.cpython-311.pyc b/backend/service_implementation/qihuo_analyzer/data/__pycache__/data_fetcher.cpython-311.pyc index d4ab71b..1ba04d7 100644 Binary files a/backend/service_implementation/qihuo_analyzer/data/__pycache__/data_fetcher.cpython-311.pyc and b/backend/service_implementation/qihuo_analyzer/data/__pycache__/data_fetcher.cpython-311.pyc differ diff --git a/backend/service_implementation/qihuo_analyzer/data/api_adapters/__pycache__/tqsdk_adapter.cpython-311.pyc b/backend/service_implementation/qihuo_analyzer/data/api_adapters/__pycache__/tqsdk_adapter.cpython-311.pyc index b32ae97..31db358 100644 Binary files a/backend/service_implementation/qihuo_analyzer/data/api_adapters/__pycache__/tqsdk_adapter.cpython-311.pyc and b/backend/service_implementation/qihuo_analyzer/data/api_adapters/__pycache__/tqsdk_adapter.cpython-311.pyc differ diff --git a/backend/service_implementation/qihuo_analyzer/data/api_adapters/tqsdk_adapter.py b/backend/service_implementation/qihuo_analyzer/data/api_adapters/tqsdk_adapter.py index 86e86e8..be67c4a 100644 --- a/backend/service_implementation/qihuo_analyzer/data/api_adapters/tqsdk_adapter.py +++ b/backend/service_implementation/qihuo_analyzer/data/api_adapters/tqsdk_adapter.py @@ -393,6 +393,71 @@ class TqSdkAdapter(BaseDataAdapter): print(f"使用模拟主力合约数据: {mock_main_contracts}") return mock_main_contracts + def get_last_trading_day(self, symbol: str) -> Optional[str]: + """获取最后一个交易日 + + Args: + symbol: 合约代码,如 'CU2603' + + Returns: + str: 最后一个交易日,格式为 'YYYY-MM-DD' + """ + try: + if TQSDK_AVAILABLE and self.api: + # 转换合约代码为TQSDK格式 + tq_symbol = self._convert_symbol(symbol) + print(f"使用TQSDK格式合约代码: {tq_symbol}") + + # 获取日线数据 + klines = self.api.get_kline_serial(tq_symbol, 86400, data_length=30) # 86400秒 = 1天 + + # 等待数据准备就绪 + 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, + 'close': klines.close + } + df = pd.DataFrame(data) + df['datetime'] = pd.to_datetime(df['datetime'], unit='ns') + + # 过滤掉成交量为0的日期(非交易日) + # 注意:TQSDK的K线数据中,如果当天没有交易,可能不会生成数据 + # 所以我们只需要取最后一条数据的日期 + if not df.empty: + last_trading_day = df['datetime'].iloc[-1].strftime('%Y-%m-%d') + print(f"最后一个交易日: {last_trading_day}") + return last_trading_day + else: + print("无法获取K线数据") + return None + else: + # 返回模拟数据 + print("无法获取真实数据,使用模拟最后交易日") + # 模拟返回前一天的日期 + import datetime + last_trading_day = (datetime.datetime.now() - datetime.timedelta(days=1)).strftime('%Y-%m-%d') + print(f"使用模拟最后交易日: {last_trading_day}") + return last_trading_day + except Exception as e: + print(f"获取最后交易日失败:{e}") + # 返回模拟数据 + import datetime + last_trading_day = (datetime.datetime.now() - datetime.timedelta(days=1)).strftime('%Y-%m-%d') + print(f"使用模拟最后交易日: {last_trading_day}") + return last_trading_day + def _get_mock_all_symbols(self) -> List[str]: """获取模拟品种列表""" # 返回exchange_map中映射的所有品种 diff --git a/backend/service_implementation/qihuo_analyzer/data/data_fetcher.py b/backend/service_implementation/qihuo_analyzer/data/data_fetcher.py index 0dd3543..380ccb1 100644 --- a/backend/service_implementation/qihuo_analyzer/data/data_fetcher.py +++ b/backend/service_implementation/qihuo_analyzer/data/data_fetcher.py @@ -463,6 +463,36 @@ class DataFetcher: 'NI': 'NI2603', # 镍 'SN': 'SN2603' # 锡 } + + def get_last_trading_day(self, symbol: str) -> Optional[str]: + """获取最后一个交易日 + + Args: + symbol: 合约代码,如 'CU2603' + + Returns: + str: 最后一个交易日,格式为 'YYYY-MM-DD' + """ + try: + # 使用适配器的get_last_trading_day方法 + result = self.adapter.get_last_trading_day(symbol) + if result: + return result + else: + # 如果适配器返回空,使用模拟数据 + print("使用模拟最后交易日") + return self._get_mock_last_trading_day() + except Exception as e: + print(f"获取最后交易日失败:{e}") + return self._get_mock_last_trading_day() + + def _get_mock_last_trading_day(self) -> str: + """获取模拟最后交易日""" + # 模拟返回前一天的日期 + import datetime + last_trading_day = (datetime.datetime.now() - datetime.timedelta(days=1)).strftime('%Y-%m-%d') + print(f"使用模拟最后交易日: {last_trading_day}") + return last_trading_day # 导入numpy diff --git a/backend/service_implementation/service/app.py b/backend/service_implementation/service/app.py index dc3b923..da88e51 100644 --- a/backend/service_implementation/service/app.py +++ b/backend/service_implementation/service/app.py @@ -68,6 +68,22 @@ def get_main_contracts(): except Exception as e: return jsonify({'status': 'error', 'message': str(e)}), 500 +# 最后交易日获取接口 +@app.route('/api/last-trading-day', methods=['GET']) +def get_last_trading_day(): + try: + symbol = request.args.get('symbol', 'CU2603') # 默认使用CU2603合约 + print(f"正在获取最后交易日,合约:{symbol}") + last_trading_day = data_fetcher.get_last_trading_day(symbol) + print(f"获取到最后交易日:{last_trading_day}") + + return jsonify({'status': 'success', 'data': { + 'symbol': symbol, + 'last_trading_day': last_trading_day + }}) + except Exception as e: + return jsonify({'status': 'error', 'message': str(e)}), 500 + # K线数据获取接口 @app.route('/api/kline', methods=['GET']) def get_kline(): diff --git a/backend/service_implementation/service/data/futures_analysis.db b/backend/service_implementation/service/data/futures_analysis.db index fa05b4b..d81896d 100644 Binary files a/backend/service_implementation/service/data/futures_analysis.db and b/backend/service_implementation/service/data/futures_analysis.db differ diff --git a/backend/src/services/marketService.ts b/backend/src/services/marketService.ts index 19f0f62..1ba324f 100644 --- a/backend/src/services/marketService.ts +++ b/backend/src/services/marketService.ts @@ -316,7 +316,7 @@ export const fetchKlineData = async (symbol: string, period: string) => { // 获取K线数据 logger.log(`获取合约${contractSymbol}K线数据,周期: ${duration}...`); - const klineResponse = await serviceImplementationClient.getKlineData(contractSymbol, duration, 30); + const klineResponse = await serviceImplementationClient.getKlineData(contractSymbol, duration, 50); const klineData = klineResponse.data; if (klineData.length > 0) { diff --git a/docs/开发文档/数据存储与缓存策略.md b/docs/开发文档/数据存储与缓存策略.md new file mode 100644 index 0000000..cdbe97f --- /dev/null +++ b/docs/开发文档/数据存储与缓存策略.md @@ -0,0 +1,123 @@ +# 数据存储与缓存策略 + +## 1. 缓存服务 (Redis + MySQL) + +### 代码位置 +- **文件**:`backend/src/services/cacheService.ts` +- **核心方法**: + - `set(key, value, options)`: 写入Redis缓存 + - `saveToMySQL(symbol, type, data)`: 写入MySQL数据库 + +### 写入场景 +1. **数据首次获取**: + - 当从数据源获取新数据时,会同时写入Redis和MySQL + - 代码路径:`cacheService.get()` → 数据源获取 → `set()` + `saveToMySQL()` + +2. **MySQL数据同步到Redis**: + - 当从MySQL获取数据时,会将数据同步到Redis + - 代码路径:`cacheService.get()` → MySQL获取 → `set()` + +## 2. 市场服务 (使用缓存服务) + +### 代码位置 +- **文件**:`backend/src/services/marketService.ts` + +### 写入场景 +1. **市场概览数据**: + - 方法:`fetchMarketOverview()` + - 缓存键:`market:overview` + - 过期时间:5分钟 + +2. **品种详情数据**: + - 方法:`fetchMarketDetail(symbol)` + - 缓存键:`market:detail:{symbol}` + - 过期时间:5分钟 + +3. **K线数据**: + - 方法:`fetchKlineData(symbol, period)` + - 缓存键:`market:kline:{symbol}:{period}` + - 过期时间:10分钟 + +4. **市场热点数据**: + - 方法:`fetchMarketHotspots()` + - 缓存键:`market:hotspots` + - 过期时间:5分钟 + +## 3. Python服务数据存储 (SQLite) + +### 代码位置 +- **文件**:`backend/service_implementation/qihuo_analyzer/data/data_storage.py` + +### 写入场景 +1. **分析结果**: + - 方法:`save_analysis_result(result)` + - 表:`analysis_results` + - 场景:当AI分析完成后保存分析结果 + +2. **K线数据**: + - 方法:`save_kline_data(symbol, duration, df)` + - 表:`kline_data` + - 场景:当获取K线数据后保存到数据库 + +3. **交易建议**: + - 方法:`save_trade_recommendation(recommendation)` + - 表:`trade_recommendations` + - 场景:当生成交易建议后保存 + +4. **风险监控数据**: + - 方法:`save_risk_monitoring(monitoring_data)` + - 表:`risk_monitoring` + - 场景:当监控交易风险时保存 + +## 4. Flask API服务 (调用Python存储) + +### 代码位置 +- **文件**:`backend/service_implementation/service/app.py` + +### 写入场景 +1. **K线数据**: + - 路径:`/api/kline` + - 代码:当数据库中没有K线数据时,从数据源获取并保存 + - 调用:`data_storage.save_kline_data()` + +2. **分析结果**: + - 路径:`/api/analyze` + - 代码:当AI分析完成后保存分析结果 + - 调用:`data_storage.save_analysis_result()` + +3. **风险监控数据**: + - 路径:`/api/risk` + - 代码:当监控交易风险时保存 + - 调用:`data_storage.save_risk_monitoring()` + +## 5. 数据流动路径 + +### 前端 → 后端 API → 缓存服务 → 数据源 +1. 前端请求数据 +2. 后端API调用市场服务 +3. 市场服务调用缓存服务 +4. 缓存服务按优先级获取数据: + - Redis → MySQL → 数据源 +5. 如果从数据源获取数据,同步写入MySQL和Redis + +### Python服务内部 +1. Flask API接收请求 +2. 调用数据获取器获取数据 +3. 将数据保存到SQLite数据库 +4. 返回数据给前端 + +## 6. 缓存策略 + +- **Redis**:短期缓存,用于高频访问数据 + - 市场概览:5分钟 + - 品种详情:5分钟 + - K线数据:10分钟 + - 市场热点:5分钟 + +- **MySQL**:持久化存储,用于长期数据 + - 存储所有从数据源获取的数据 + +- **SQLite**:Python服务本地存储 + - 存储分析结果、K线数据、交易建议、风险监控数据 + +这种多层缓存策略确保了数据的快速访问和持久化存储,同时减少了对外部数据源的请求频率。 \ No newline at end of file diff --git a/docs/开发文档/最后交易日获取接口.md b/docs/开发文档/最后交易日获取接口.md new file mode 100644 index 0000000..eacd90a --- /dev/null +++ b/docs/开发文档/最后交易日获取接口.md @@ -0,0 +1,161 @@ +# 最后交易日获取接口 + +## 1. 功能概述 + +本接口用于获取期货合约的最后一个交易日,支持通过TQSDK实时获取或使用模拟数据作为fallback。 + +## 2. 实现方案 + +### 2.1 核心代码位置 + +- **TQSDK适配器**:`backend/service_implementation/qihuo_analyzer/data/api_adapters/tqsdk_adapter.py` + - 实现了`get_last_trading_day(symbol)`方法,使用TQSDK获取最后一个交易日 + +- **数据获取器**:`backend/service_implementation/qihuo_analyzer/data/data_fetcher.py` + - 实现了`get_last_trading_day(symbol)`方法,作为适配器方法的包装 + +- **API服务**:`backend/service_implementation/service/app.py` + - 添加了`/api/last-trading-day`接口,提供HTTP访问方式 + +### 2.2 技术实现 + +#### TQSDK适配器实现 + +```python +def get_last_trading_day(self, symbol: str) -> Optional[str]: + """获取最后一个交易日 + + Args: + symbol: 合约代码,如 'CU2603' + + Returns: + str: 最后一个交易日,格式为 'YYYY-MM-DD' + """ + try: + if TQSDK_AVAILABLE and self.api: + # 转换合约代码为TQSDK格式 + tq_symbol = self._convert_symbol(symbol) + + # 获取日线数据 + klines = self.api.get_kline_serial(tq_symbol, 86400, data_length=30) # 86400秒 = 1天 + + # 等待数据准备就绪 + # 处理数据... + + # 转换为DataFrame并提取最后交易日 + if not df.empty: + last_trading_day = df['datetime'].iloc[-1].strftime('%Y-%m-%d') + return last_trading_day + else: + # 返回模拟数据 + import datetime + last_trading_day = (datetime.datetime.now() - datetime.timedelta(days=1)).strftime('%Y-%m-%d') + return last_trading_day + except Exception as e: + # 异常处理,返回模拟数据 + import datetime + last_trading_day = (datetime.datetime.now() - datetime.timedelta(days=1)).strftime('%Y-%m-%d') + return last_trading_day +``` + +#### API接口实现 + +```python +# 最后交易日获取接口 +@app.route('/api/last-trading-day', methods=['GET']) +def get_last_trading_day(): + try: + symbol = request.args.get('symbol', 'CU2603') # 默认使用CU2603合约 + last_trading_day = data_fetcher.get_last_trading_day(symbol) + + return jsonify({'status': 'success', 'data': { + 'symbol': symbol, + 'last_trading_day': last_trading_day + }}) + except Exception as e: + return jsonify({'status': 'error', 'message': str(e)}), 500 +``` + +## 3. 使用方法 + +### 3.1 API接口调用 + +**请求URL**:`http://localhost:5000/api/last-trading-day` + +**请求方法**:GET + +**参数说明**: +- `symbol`(可选):合约代码,如 'CU2603',默认值为 'CU2603' + +**返回格式**: + +```json +{ + "status": "success", + "data": { + "symbol": "CU2603", + "last_trading_day": "2026-02-23" + } +} +``` + +**示例请求**: +``` +GET http://localhost:5000/api/last-trading-day?symbol=AU2603 +``` + +**示例响应**: +```json +{ + "status": "success", + "data": { + "symbol": "AU2603", + "last_trading_day": "2026-02-23" + } +} +``` + +### 3.2 代码调用 + +```python +from qihuo_analyzer.data.data_fetcher import DataFetcher + +data_fetcher = DataFetcher() +data_fetcher.connect() +last_trading_day = data_fetcher.get_last_trading_day('CU2603') +print(f"最后一个交易日: {last_trading_day}") # 输出:最后一个交易日: 2026-02-23 +``` + +## 4. 技术特点 + +1. **实时数据**:通过TQSDK获取实时的最后交易日数据 +2. **容错机制**:当TQSDK不可用时,自动使用模拟数据 +3. **异常处理**:实现了多层异常捕获,确保接口稳定性 +4. **灵活调用**:支持通过API接口或直接代码调用 +5. **默认值**:当未指定合约代码时,默认使用CU2603合约 + +## 5. 注意事项 + +1. **网络连接**:使用TQSDK获取数据需要稳定的网络连接 +2. **合约代码格式**:请使用正确的合约代码格式,如 'CU2603' +3. **数据延迟**:由于需要从TQSDK获取数据,可能会有一定的网络延迟 +4. **模拟数据**:当TQSDK不可用时,会返回前一天的日期作为模拟数据 + +## 6. 故障排查 + +### 6.1 常见问题 + +| 问题 | 可能原因 | 解决方案 | +|------|---------|--------| +| 返回模拟数据 | TQSDK连接失败或网络问题 | 检查网络连接,确保TQSDK账号配置正确 | +| 接口返回错误 | 合约代码格式错误 | 使用正确的合约代码格式,如 'CU2603' | +| 数据延迟 | 网络延迟或TQSDK服务器响应慢 | 耐心等待,或使用模拟数据 | + +### 6.2 日志查看 + +- **Python服务日志**:在启动Python服务的终端中查看 +- **API日志**:通过访问API端点,查看返回结果和错误信息 + +## 7. 总结 + +本接口提供了一种便捷的方式来获取期货合约的最后一个交易日,支持实时数据和模拟数据两种模式。通过简单的API调用或代码调用,用户可以轻松获取所需的最后交易日信息,为交易策略和风险管理提供支持。 \ No newline at end of file