fix: 增加获取最后一个交易日接口

master
Lxy 3 months ago
parent 0196d531ed
commit 07debec4ce

@ -393,6 +393,71 @@ class TqSdkAdapter(BaseDataAdapter):
print(f"使用模拟主力合约数据: {mock_main_contracts}") print(f"使用模拟主力合约数据: {mock_main_contracts}")
return 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]: def _get_mock_all_symbols(self) -> List[str]:
"""获取模拟品种列表""" """获取模拟品种列表"""
# 返回exchange_map中映射的所有品种 # 返回exchange_map中映射的所有品种

@ -464,6 +464,36 @@ class DataFetcher:
'SN': 'SN2603' # 锡 '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 # 导入numpy
import numpy as np import numpy as np

@ -68,6 +68,22 @@ def get_main_contracts():
except Exception as e: except Exception as e:
return jsonify({'status': 'error', 'message': str(e)}), 500 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线数据获取接口 # K线数据获取接口
@app.route('/api/kline', methods=['GET']) @app.route('/api/kline', methods=['GET'])
def get_kline(): def get_kline():

@ -316,7 +316,7 @@ export const fetchKlineData = async (symbol: string, period: string) => {
// 获取K线数据 // 获取K线数据
logger.log(`获取合约${contractSymbol}K线数据周期: ${duration}...`); 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; const klineData = klineResponse.data;
if (klineData.length > 0) { if (klineData.length > 0) {

@ -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线数据、交易建议、风险监控数据
这种多层缓存策略确保了数据的快速访问和持久化存储,同时减少了对外部数据源的请求频率。

@ -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调用或代码调用用户可以轻松获取所需的最后交易日信息为交易策略和风险管理提供支持。
Loading…
Cancel
Save