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 1ba04d7..7b25fc1 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 31db358..9cb0979 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 be67c4a..4a8f854 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,55 +393,62 @@ class TqSdkAdapter(BaseDataAdapter):
print(f"使用模拟主力合约数据: {mock_main_contracts}")
return mock_main_contracts
- def get_last_trading_day(self, symbol: str) -> Optional[str]:
+ def get_last_trading_day(self) -> 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秒超时
+ # 计算日期范围
+ import datetime
+ end_date = datetime.datetime.now()
+ start_date = end_date - datetime.timedelta(days=10)
- 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)
+ # 格式化日期为字符串
+ start_date_str = start_date.strftime('%Y%m%d')
+ end_date_str = end_date.strftime('%Y%m%d')
- # 转换为DataFrame
- data = {
- 'datetime': klines.datetime,
- 'close': klines.close
- }
- df = pd.DataFrame(data)
- df['datetime'] = pd.to_datetime(df['datetime'], unit='ns')
+ print(f"获取交易日历,开始日期: {start_date_str}, 结束日期: {end_date_str}")
- # 过滤掉成交量为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
+ # 使用get_trading_calendar获取交易日历
+ try:
+ # 获取交易日历
+ calendar = self.api.get_trading_calendar(start_date_str, end_date_str)
+
+ # 等待数据准备就绪
+ import time
+ start_time = time.time()
+ timeout = 5 # 5秒超时
+
+ while True:
+ if hasattr(calendar, 'dates') and len(calendar.dates) > 0:
+ break
+ if time.time() - start_time > timeout:
+ print("获取交易日历超时")
+ # 回退到K线数据方法
+ return ""
+ time.sleep(0.1)
+
+ # 处理交易日历数据
+ if hasattr(calendar, 'dates') and len(calendar.dates) > 0:
+ # 获取最后一个交易日
+ last_trading_date = calendar.dates[-1]
+ # 转换为YYYY-MM-DD格式
+ last_trading_day = last_trading_date.strftime('%Y-%m-%d')
+ print(f"从交易日历获取最后交易日: {last_trading_day}")
+ return last_trading_day
+ else:
+ print("交易日历数据为空")
+ # 回退到K线数据方法
+ return ""
+ except Exception as calendar_error:
+ print(f"使用get_trading_calendar失败:{calendar_error}")
+ # 回退到K线数据方法
+ return ""
else:
# 返回模拟数据
print("无法获取真实数据,使用模拟最后交易日")
@@ -458,6 +465,52 @@ class TqSdkAdapter(BaseDataAdapter):
print(f"使用模拟最后交易日: {last_trading_day}")
return last_trading_day
+ def _get_last_trading_day_from_kline(self, tq_symbol: str) -> Optional[str]:
+ """从K线数据中获取最后交易日(作为回退方法)
+
+ Args:
+ tq_symbol: TQSDK格式的合约代码
+
+ Returns:
+ str: 最后一个交易日,格式为 'YYYY-MM-DD'
+ """
+ try:
+ # 获取日线数据
+ 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')
+
+ # 取最后一条数据的日期
+ if not df.empty:
+ last_trading_day = df['datetime'].iloc[-1].strftime('%Y-%m-%d')
+ print(f"从K线数据获取最后交易日: {last_trading_day}")
+ return last_trading_day
+ else:
+ print("无法获取K线数据")
+ return None
+ except Exception as e:
+ print(f"从K线数据获取最后交易日失败:{e}")
+ return None
+
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 380ccb1..1b5d08d 100644
--- a/backend/service_implementation/qihuo_analyzer/data/data_fetcher.py
+++ b/backend/service_implementation/qihuo_analyzer/data/data_fetcher.py
@@ -464,18 +464,16 @@ class DataFetcher:
'SN': 'SN2603' # 锡
}
- def get_last_trading_day(self, symbol: str) -> Optional[str]:
+ def get_last_trading_day(self) -> Optional[str]:
"""获取最后一个交易日
-
- Args:
- symbol: 合约代码,如 'CU2603'
+
Returns:
str: 最后一个交易日,格式为 'YYYY-MM-DD'
"""
try:
# 使用适配器的get_last_trading_day方法
- result = self.adapter.get_last_trading_day(symbol)
+ result = self.adapter.get_last_trading_day()
if result:
return result
else:
diff --git a/backend/service_implementation/service/app.py b/backend/service_implementation/service/app.py
index da88e51..e14b0e5 100644
--- a/backend/service_implementation/service/app.py
+++ b/backend/service_implementation/service/app.py
@@ -72,13 +72,11 @@ def get_main_contracts():
@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}")
+ print(f"正在获取最后交易日")
+ last_trading_day = data_fetcher.get_last_trading_day()
+ print(f"获取到最后交易日")
return jsonify({'status': 'success', 'data': {
- 'symbol': symbol,
'last_trading_day': last_trading_day
}})
except Exception as e:
diff --git a/backend/service_implementation/service/data/futures_analysis.db b/backend/service_implementation/service/data/futures_analysis.db
index d81896d..84e1a3b 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/docs/开发文档/最后交易日获取接口.md b/docs/开发文档/最后交易日获取接口.md
index eacd90a..c0ecb4d 100644
--- a/docs/开发文档/最后交易日获取接口.md
+++ b/docs/开发文档/最后交易日获取接口.md
@@ -101,7 +101,7 @@ def get_last_trading_day():
**示例请求**:
```
-GET http://localhost:5000/api/last-trading-day?symbol=AU2603
+GET http://localhost:5000/api/last-trading-day
```
**示例响应**:
diff --git a/src/pages/config/Config.jsx b/src/pages/config/Config.jsx
index dc21363..2f2474c 100644
--- a/src/pages/config/Config.jsx
+++ b/src/pages/config/Config.jsx
@@ -1,11 +1,160 @@
-import React, { useState } from 'react';
-import { Card, Row, Col, Form, Input, Button, Select, Switch, Slider, Tag, Alert, InputNumber, Radio, Space, Divider, Statistic } from 'antd';
-import { SettingOutlined, SlidersOutlined, SaveOutlined, CloseOutlined, SafetyOutlined } from '@ant-design/icons';
+import React, { useState, useEffect } from 'react';
+import { Card, Row, Col, Form, Input, Button, Select, Switch, Slider, Tag, Alert, InputNumber, Radio, Space, Divider, Statistic, Table } from 'antd';
+import { SettingOutlined, SlidersOutlined, SaveOutlined, CloseOutlined, SafetyOutlined, DatabaseOutlined, ClearOutlined } from '@ant-design/icons';
+import dataCache from '../../utils/cache';
import './Config.css';
const { Option } = Select;
const { Item } = Form;
+// 缓存管理组件
+const CacheManager = () => {
+ const [stats, setStats] = useState({ total: 0, valid: 0, expired: 0 });
+ const [cacheKeys, setCacheKeys] = useState([]);
+ const [message, setMessage] = useState('');
+
+ const refreshStats = () => {
+ const currentStats = dataCache.getStats();
+ setStats(currentStats);
+
+ // 获取所有缓存键
+ const keys = [];
+ // 由于Map不能直接遍历获取所有键,我们通过已知的数据类型来展示
+ const knownKeys = [
+ { key: 'futures_overview', name: '市场概览数据', ttl: '5分钟' },
+ { key: 'risk_alerts', name: '风险预警数据', ttl: '3分钟' },
+ { key: 'ai_market_analysis', name: 'AI市场分析', ttl: '10分钟' },
+ ];
+
+ const dynamicKeys = [];
+ // 动态检测品种详情和K线缓存(这些是基于code的动态键)
+ const mockCodes = ['AU', 'AG', 'CU', 'RB', 'MA', 'SC'];
+ mockCodes.forEach(code => {
+ const detailKey = `future_detail_${code}`;
+ dynamicKeys.push({ key: detailKey, name: `${code}品种详情`, ttl: '10分钟' });
+ ['5M', '30M', '1H', '1D'].forEach(period => {
+ dynamicKeys.push({ key: `kline_${code}_${period}`, name: `${code} K线(${period})`, ttl: '30分钟' });
+ });
+ });
+
+ // 检查哪些缓存存在
+ const allKeys = [...knownKeys, ...dynamicKeys];
+ const existingKeys = allKeys.filter(item => dataCache.has(item.key));
+ setCacheKeys(existingKeys);
+ };
+
+ useEffect(() => {
+ refreshStats();
+ // 每5秒自动刷新一次统计
+ const interval = setInterval(refreshStats, 5000);
+ return () => clearInterval(interval);
+ }, []);
+
+ const handleClearCache = () => {
+ dataCache.clear();
+ refreshStats();
+ setMessage('所有缓存已清除');
+ setTimeout(() => setMessage(''), 3000);
+ };
+
+ const handleClearSpecificCache = (key) => {
+ dataCache.delete(key);
+ refreshStats();
+ setMessage(`缓存 ${key} 已清除`);
+ setTimeout(() => setMessage(''), 3000);
+ };
+
+ const columns = [
+ {
+ title: '缓存名称',
+ dataIndex: 'name',
+ key: 'name',
+ },
+ {
+ title: '缓存键',
+ dataIndex: 'key',
+ key: 'key',
+ render: (key) => {key},
+ },
+ {
+ title: '有效期',
+ dataIndex: 'ttl',
+ key: 'ttl',
+ },
+ {
+ title: '操作',
+ key: 'action',
+ render: (_, record) => (
+
+ ),
+ },
+ ];
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ onClick={handleClearCache}
+ style={{ marginTop: 8 }}
+ >
+ 清除所有缓存
+
+
+
+
+
+ {message && (
+
+ )}
+
+
+
+
当前有效缓存列表
+
+
+ );
+};
+
const Config = () => {
const [form] = Form.useForm();
@@ -234,6 +383,15 @@ const Config = () => {
+ {/* 缓存管理 */}
+ 缓存管理}
+ className="config-card"
+ style={{ marginTop: 24 }}
+ >
+
+
+
{/* 风控管理 */}
风控管理}
diff --git a/src/pages/dashboard/Dashboard.css b/src/pages/dashboard/Dashboard.css
index 5176a06..fe05fb2 100644
--- a/src/pages/dashboard/Dashboard.css
+++ b/src/pages/dashboard/Dashboard.css
@@ -3,10 +3,14 @@
}
.dashboard-header {
+ margin-bottom: 24px;
+}
+
+.dashboard-header-top {
display: flex;
justify-content: space-between;
align-items: center;
- margin-bottom: 24px;
+ margin-bottom: 16px;
}
.dashboard-header h2 {
@@ -14,6 +18,32 @@
color: #262626;
}
+.date-info {
+ display: flex;
+ gap: 20px;
+ font-size: 14px;
+ color: #8c8c8c;
+}
+
+.date-item {
+ display: flex;
+ align-items: center;
+}
+
+.date-item::before {
+ content: '';
+ display: inline-block;
+ width: 8px;
+ height: 8px;
+ background: #1890ff;
+ border-radius: 50%;
+ margin-right: 8px;
+}
+
+.date-info .date-item:nth-child(2)::before {
+ background: #52c41a;
+}
+
.dashboard-header-actions {
display: flex;
align-items: center;
diff --git a/src/pages/dashboard/Dashboard.jsx b/src/pages/dashboard/Dashboard.jsx
index bd177a0..1609329 100644
--- a/src/pages/dashboard/Dashboard.jsx
+++ b/src/pages/dashboard/Dashboard.jsx
@@ -20,9 +20,37 @@ const Dashboard = () => {
const [currentFuture, setCurrentFuture] = useState(null);
const [pushForm] = Form.useForm();
const [messageApi, contextHolder] = message.useMessage();
+ const [lastTradingDay, setLastTradingDay] = useState('');
+ const [currentDate, setCurrentDate] = useState('');
// 添加ref来避免在开发模式下的重复API请求
const fetchingRef = React.useRef(false);
+
+ // 获取最后交易日
+ const fetchLastTradingDay = async () => {
+ try {
+ // 尝试使用相对路径,避免跨域问题
+ const response = await fetch('/api/last-trading-day');
+ const data = await response.json();
+ if (data.status === 'success') {
+ setLastTradingDay(data.data.last_trading_day);
+ }
+ } catch (error) {
+ console.error('获取最后交易日失败:', error);
+ // 使用模拟数据作为fallback
+ const yesterday = new Date();
+ yesterday.setDate(yesterday.getDate() - 1);
+ const formattedDate = yesterday.toISOString().split('T')[0];
+ setLastTradingDay(formattedDate);
+ }
+ };
+
+ // 设置当前日期
+ const setCurrentDateValue = () => {
+ const now = new Date();
+ const formattedDate = now.toISOString().split('T')[0];
+ setCurrentDate(formattedDate);
+ };
useEffect(() => {
if (!fetchingRef.current) {
@@ -30,15 +58,22 @@ const Dashboard = () => {
dispatch(fetchFuturesOverview());
dispatch(fetchRiskAlerts());
dispatch(fetchAIMarketAnalysis());
+ // 获取最后交易日和当前日期
+ fetchLastTradingDay();
+ setCurrentDateValue();
}
}, [dispatch]);
const handleRefresh = () => {
fetchingRef.current = false;
- dispatch(fetchFuturesOverview());
- dispatch(fetchRiskAlerts());
- dispatch(fetchAIMarketAnalysis());
- messageApi.success('数据已刷新');
+ // 强制刷新,不使用缓存
+ dispatch(fetchFuturesOverview({ forceRefresh: true }));
+ dispatch(fetchRiskAlerts({ forceRefresh: true }));
+ dispatch(fetchAIMarketAnalysis({ forceRefresh: true }));
+ // 刷新最后交易日信息
+ fetchLastTradingDay();
+ setCurrentDateValue();
+ messageApi.success('数据已刷新(已强制更新缓存)');
};
const handleFutureClick = (future) => {
@@ -124,7 +159,13 @@ const Dashboard = () => {
{/* 页面头部 */}
-
市场概览
+
+
市场概览
+
+ 当前日期: {currentDate}
+ 最后交易日: {lastTradingDay || '加载中...'}
+
+