diff --git a/app.py b/app.py index b37c2f35..828de135 100755 --- a/app.py +++ b/app.py @@ -11004,9 +11004,12 @@ def get_events_effectiveness_stats(): 按交易日统计事件数据,展示事件预测的有效性 + 交易日定义:上一交易日15:00到当前交易日15:00 + 例如:周一15:00到周二15:00为"周二交易日"的事件 + 参数: date: 指定日期(YYYY-MM-DD格式,可选,默认今天) - days: 统计天数(默认7天) + days: 统计天数(默认7天,days=1表示当前交易日) 返回: { @@ -11041,21 +11044,78 @@ def get_events_effectiveness_stats(): days = request.args.get('days', 7, type=int) days = min(max(days, 1), 30) # 限制1-30天 - # 确定查询日期范围 + # 确定基准时间 if date_str: try: - end_date = datetime.strptime(date_str, '%Y-%m-%d') + base_date = datetime.strptime(date_str, '%Y-%m-%d') except ValueError: - end_date = datetime.now() + base_date = datetime.now() else: - end_date = datetime.now() + base_date = datetime.now() - start_date = end_date - timedelta(days=days) + # 使用交易日15:00作为分界点计算时间范围 + # 当前交易日的结束时间:当天15:00(如果当前时间<15:00)或下一交易日15:00(如果当前时间>=15:00) + current_time = base_date.time() if isinstance(base_date, datetime) else dt_time(12, 0) + current_date = base_date.date() if isinstance(base_date, datetime) else base_date + + # 确保交易日数据已加载 + if not trading_days: + load_trading_days() + + # 判断当前是否是交易日以及是否收盘后 + is_trading = current_date in trading_days_set + is_after_close = current_time >= dt_time(15, 0) + + # 确定当前交易日 + if is_trading: + if is_after_close: + # 交易日收盘后,当前交易日就是今天 + current_trading_day = current_date + else: + # 交易日盘中,当前交易日是今天 + current_trading_day = current_date + else: + # 非交易日,找最近的上一个交易日 + current_trading_day = None + for td in reversed(trading_days): + if td <= current_date: + current_trading_day = td + break + + if not current_trading_day: + current_trading_day = current_date + + # 计算时间范围:每个交易日从上一交易日15:00到当天15:00 + # 对于 days=1(当前交易日),范围是:上一交易日15:00 到 当前交易日15:00 + + # 找到往前 days 个交易日 + try: + current_idx = trading_days.index(current_trading_day) + start_trading_day_idx = max(0, current_idx - days + 1) + start_trading_day = trading_days[start_trading_day_idx] + + # 找 start_trading_day 的前一个交易日 + if start_trading_day_idx > 0: + prev_start_day = trading_days[start_trading_day_idx - 1] + else: + prev_start_day = start_trading_day - timedelta(days=1) + except ValueError: + # current_trading_day 不在列表中,使用简单计算 + start_trading_day = current_trading_day - timedelta(days=days) + prev_start_day = start_trading_day - timedelta(days=1) + + # 查询时间范围:从 prev_start_day 15:00 到 current_trading_day 15:00 + start_datetime = datetime.combine(prev_start_day, dt_time(15, 0)) + end_datetime = datetime.combine(current_trading_day, dt_time(15, 0)) + + # 如果当前时间还没到15:00,结束时间用当前时间 + if is_trading and not is_after_close: + end_datetime = base_date if isinstance(base_date, datetime) else datetime.combine(current_date, dt_time(23, 59)) # 查询事件数据 events_query = db.session.query(Event).filter( - Event.created_at >= start_date, - Event.created_at <= end_date + timedelta(days=1), + Event.created_at >= start_datetime, + Event.created_at <= end_datetime, Event.status == 'active' ).order_by(Event.created_at.desc()).all() diff --git a/src/views/Community/components/EventDailyStats.js b/src/views/Community/components/EventDailyStats.js index 48137010..7624ed05 100644 --- a/src/views/Community/components/EventDailyStats.js +++ b/src/views/Community/components/EventDailyStats.js @@ -24,6 +24,17 @@ import { } from '@ant-design/icons'; import { getApiBase } from '@utils/apiConfig'; +/** + * 生成事件详情页 URL + * @param {number} eventId - 事件ID + * @returns {string} 事件详情页 URL + */ +const getEventDetailUrl = (eventId) => { + // 使用 base64 编码 ID,格式:ev-{id} -> base64 + const encodedId = btoa(`ev-${eventId}`); + return `/event-detail?id=${encodedId}`; +}; + /** * 格式化涨跌幅 */ @@ -99,48 +110,61 @@ const CompactStatCard = ({ label, value, icon, color = '#FFD700', subText, progr ); /** - * TOP事件列表项 + * TOP事件列表项 - 支持点击跳转 */ -const TopEventItem = ({ event, rank }) => ( - - { + const handleClick = () => { + if (event.id) { + // 在新标签页打开事件详情 + window.open(getEventDetailUrl(event.id), '_blank'); + } + }; + + return ( + e.key === 'Enter' && handleClick()} > - {rank} - - + + {rank} + + + + {event.title} + + - {event.title} + {formatChg(event.maxChg)} - - - {formatChg(event.maxChg)} - - -); + + ); +}; const EventDailyStats = () => { const [loading, setLoading] = useState(true); @@ -215,9 +239,7 @@ const EventDailyStats = () => { ); } - const { summary, topPerformers = [], dailyStats = [] } = stats; - // 获取当日TOP事件 - const todayTopEvents = dailyStats[0]?.topEvents || topPerformers.slice(0, 3); + const { summary, topPerformers = [] } = stats; return ( { {/* 分割线 */} - {/* TOP表现事件 */} - + {/* TOP表现事件 - 显示 TOP10 */} + 今日 TOP 表现 + + (点击查看详情) + - - {todayTopEvents.slice(0, 3).map((event, idx) => ( + + {topPerformers.slice(0, 10).map((event, idx) => ( ))} - {todayTopEvents.length === 0 && ( + {topPerformers.length === 0 && ( 暂无数据