#!/usr/bin/env python3 """整点后5秒抓取 BTC 1h K线(至少512根,且包含最新时间)""" import os import sys import time from datetime import datetime, timedelta import pandas as pd import pytz # 直接运行脚本时,确保可以从项目根目录导入 data 包 sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from data.fetcher import BinanceDataFetcher def get_next_run_time(delay_seconds: int = 5) -> datetime: """计算下一次执行时间:下一个整点 + delay_seconds。""" now_utc = datetime.now(pytz.UTC) next_hour = (now_utc + timedelta(hours=1)).replace(minute=0, second=0, microsecond=0) return next_hour + timedelta(seconds=delay_seconds) def fetch_min_klines_with_latest( fetcher: BinanceDataFetcher, min_klines: int = 512, latest_check_retries: int = 8, latest_check_interval: int = 2, ) -> pd.DataFrame: """ 拉取 BTC 1h K线,确保: 1) 数量不少于 min_klines 2) 包含当前小时的最新时间(UTC 整点) """ now_utc = datetime.now(pytz.UTC) expected_latest_ts = now_utc.replace(minute=0, second=0, microsecond=0).replace(tzinfo=None) lookback_days = 35 for attempt in range(latest_check_retries): start_date = (datetime.now(pytz.UTC) - timedelta(days=lookback_days)).strftime("%Y-%m-%d") # 使用 WebSocket 拉取,避免仅命中缓存导致最新K线不更新。 df = fetcher.fetch_ohlcv( symbol="BTC", timeframe="1h", start_date=start_date, end_date=None, use_cache=False, max_retries=3, ) if len(df) < min_klines: lookback_days += 7 print( f"[WARN] 当前仅获取 {len(df)} 根K线,小于 {min_klines},扩大回看窗口到 {lookback_days} 天后重试。" ) continue latest_ts = df["timestamp"].max().to_pydatetime().replace(tzinfo=None) if latest_ts >= expected_latest_ts: result = df.tail(min_klines).copy().reset_index(drop=True) print( f"[OK] 获取成功: {len(result)} 根 | 最新时间: {latest_ts} UTC | 期望最新时间: {expected_latest_ts} UTC" ) return result if attempt < latest_check_retries - 1: print( f"[WAIT] 最新K线未到位,当前最新: {latest_ts} UTC,期望: {expected_latest_ts} UTC," f"{latest_check_interval}秒后重试({attempt + 1}/{latest_check_retries})。" ) time.sleep(latest_check_interval) raise RuntimeError("多次重试后仍未拿到包含最新时间的BTC 1h K线数据") def run_once(min_klines: int = 512) -> None: """执行一次抓取任务。""" fetcher = BinanceDataFetcher() data = fetch_min_klines_with_latest(fetcher=fetcher, min_klines=min_klines) print("-" * 80) print(f"抓取完成,返回K线数量: {len(data)}") print(f"时间范围: {data['timestamp'].min()} -> {data['timestamp'].max()} (UTC)") print(data.tail(3)[["timestamp", "open", "high", "low", "close", "volume"]]) print("-" * 80) def run_forever(min_klines: int = 512, delay_seconds: int = 5) -> None: """常驻运行:每个整点延迟 delay_seconds 后执行抓取。""" while True: next_run = get_next_run_time(delay_seconds=delay_seconds) sleep_seconds = max(0.0, (next_run - datetime.now(pytz.UTC)).total_seconds()) print(f"[SCHEDULE] 当前UTC: {datetime.now(pytz.UTC)},下次执行: {next_run},等待 {sleep_seconds:.1f} 秒") time.sleep(sleep_seconds) try: run_once(min_klines=min_klines) except Exception as exc: print(f"[ERROR] 本轮抓取失败: {exc}") def main() -> None: """脚本入口。""" # 默认常驻运行,如需只执行一次,可把这里改成 run_once(min_klines=512) run_forever(min_klines=512, delay_seconds=5) if __name__ == "__main__": main()