diff --git a/app/api/__pycache__/data.cpython-311.pyc b/app/api/__pycache__/data.cpython-311.pyc index a1badf7..5d3f8c9 100644 Binary files a/app/api/__pycache__/data.cpython-311.pyc and b/app/api/__pycache__/data.cpython-311.pyc differ diff --git a/app/api/data.py b/app/api/data.py index e02a212..a4a6ea8 100644 --- a/app/api/data.py +++ b/app/api/data.py @@ -135,40 +135,65 @@ def get_latest( symbol: str, data_type: str = "futures", period: Optional[str] = None, + end_time: Optional[str] = None, db: Session = Depends(get_db), ): """ 从缓存获取最新数据。 - 可指定单个 period,不指定则返回所有已缓存周期。 + 可指定单个 period,不指定则返回所有已缓存周期。 + 可选指定 end_time 过滤K线数据(ISO格式),默认为当前时间。 + end_time 可以是日期(YYYY-MM-DD)或日期时间(YYYY-MM-DDTHH:MM:SS)。 """ - cached = get_cached_data(db, symbol, data_type, [period] if period else None) - if not cached: - raise HTTPException(status_code=404, detail=f"未找到 {symbol} 的缓存数据") - - timeframes = [] - for p, candles in cached["timeframes"].items(): - # 转换数据格式: time -> datetime - normalized_candles = [] - for c in candles: - candle_dict = dict(c) - if 'time' in candle_dict and 'datetime' not in candle_dict: - candle_dict['datetime'] = candle_dict.pop('time') - normalized_candles.append(candle_dict) - - timeframes.append(TimeframeData( - period=p, - candles=[CandleItem(**c) for c in normalized_candles], - candle_count=len(normalized_candles), - fetched_at=cached.get("timestamp", ""), - )) - - return SymbolDataResponse( - symbol=symbol, - data_type=data_type, - current_price=cached.get("current_price"), - timeframes=timeframes, - source="cache" if cached.get("is_fresh", False) else "cache_stale", - ) + import traceback + + # 解析时间参数,默认为当前时间 + end_dt = None + if end_time: + try: + # 尝试解析ISO格式时间 + end_dt = datetime.fromisoformat(end_time) + logger.info(f"成功解析 end_time: {end_time} -> {end_dt}") + except Exception as e: + logger.error(f"end_time 解析失败: {end_time}, 错误: {e}") + logger.error(f"错误堆栈: {traceback.format_exc()}") + raise HTTPException(status_code=400, detail=f"end_time 格式错误: {str(e)}") + + try: + cached = get_cached_data(db, symbol, data_type, [period] if period else None, end_time=end_dt) + if not cached: + raise HTTPException(status_code=404, detail=f"未找到 {symbol} 的缓存数据") + + timeframes = [] + for p, candles in cached["timeframes"].items(): + # 转换数据格式: time -> datetime + normalized_candles = [] + for c in candles: + candle_dict = dict(c) + if 'time' in candle_dict and 'datetime' not in candle_dict: + candle_dict['datetime'] = candle_dict.pop('time') + normalized_candles.append(candle_dict) + + timeframes.append(TimeframeData( + period=p, + candles=[CandleItem(**c) for c in normalized_candles], + candle_count=len(normalized_candles), + fetched_at=cached.get("timestamp", ""), + )) + + return SymbolDataResponse( + symbol=symbol, + data_type=data_type, + current_price=cached.get("current_price"), + timeframes=timeframes, + source="cache" if cached.get("is_fresh", False) else "cache_stale", + ) + except HTTPException: + raise + except Exception as e: + logger.error(f"获取数据失败: symbol={symbol}, period={period}, end_time={end_time}") + logger.error(f"错误: {e}") + logger.error(f"错误堆栈: {traceback.format_exc()}") + raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}") @router.get("/latest/{symbol}/{period}") diff --git a/app/services/__pycache__/cache.cpython-311.pyc b/app/services/__pycache__/cache.cpython-311.pyc index 5333b96..aca66dc 100644 Binary files a/app/services/__pycache__/cache.cpython-311.pyc and b/app/services/__pycache__/cache.cpython-311.pyc differ diff --git a/app/services/cache.py b/app/services/cache.py index f6c8533..2f62d20 100644 --- a/app/services/cache.py +++ b/app/services/cache.py @@ -188,10 +188,20 @@ def get_cached_data( symbol: str, data_type: str = "futures", periods: Optional[List[str]] = None, + end_time: Optional[datetime] = None, + max_candles: int = 100, ) -> Optional[Dict]: """ 从缓存中获取完整的多周期数据。 + Args: + db: 数据库会话 + symbol: 品种代码 + data_type: 数据类型 + periods: 周期列表 + end_time: 结束时间(可选),默认为当前时间 + max_candles: 每个周期最大K线数量,默认100 + Returns: 与采集脚本相同格式的数据,或 None """ @@ -208,10 +218,51 @@ def get_cached_data( newest = max(r.fetched_at for r in records) is_fresh = (now - newest).total_seconds() < CACHE_TTL_SECONDS + # 如果未指定结束时间,默认为当前时间 + filter_end_time = end_time if end_time else now + + # 确保filter_end_time是naive datetime(无时区) + if filter_end_time.tzinfo is not None: + filter_end_time = filter_end_time.replace(tzinfo=None) + timeframes = {} current_price = None for r in records: - timeframes[r.period] = json.loads(r.candles_json) + candles = json.loads(r.candles_json) + + # 过滤结束时间之前的K线数据 + filtered_candles = [] + for candle in candles: + candle_time = candle.get('datetime') or candle.get('time') + if candle_time: + # 解析K线时间 + if isinstance(candle_time, str): + try: + candle_dt = datetime.fromisoformat(candle_time.replace('Z', '+00:00')) + # 转换为naive datetime进行比较 + if candle_dt.tzinfo is not None: + candle_dt = candle_dt.replace(tzinfo=None) + except: + filtered_candles.append(candle) + continue + else: + candle_dt = candle_time + # 如果是aware datetime,转换为naive + if candle_dt.tzinfo is not None: + candle_dt = candle_dt.replace(tzinfo=None) + + # 只保留结束时间之前的数据 + if candle_dt <= filter_end_time: + filtered_candles.append(candle) + else: + filtered_candles.append(candle) + + # 限制K线数量,超过max_candles则取最新的max_candles条 + if len(filtered_candles) > max_candles: + filtered_candles = filtered_candles[-max_candles:] + + timeframes[r.period] = filtered_candles + if current_price is None: current_price = r.current_price diff --git a/app/static/index.html b/app/static/index.html index 6c155fb..d386778 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -917,6 +917,10 @@ +
+ + +