# Copyright (c) 2025-2026 马建军. All rights reserved. # 专有软件 — 未经授权禁止复制、传播、转售。 # 详见 LICENSE.zh-CN.txt """20 万以下四品种 K 线预下载(玉米、豆粕、甲醇、螺纹钢)。""" from __future__ import annotations import logging import threading import time from typing import Optional from modules.core.symbols import PRODUCTS, resolve_main_contract from modules.market.kline_chart import fetch_sina_klines, ths_to_sina_chart_symbol from modules.market.kline_store import ( connect_kline_db, ensure_kline_tables, load_meta, save_bars, ) from modules.trading.product_recommend import ( SMALL_ACCOUNT_PRODUCT_THS, normalize_product_ths, ) logger = logging.getLogger(__name__) # 小账户默认预下载周期(行情页 + 关键位常用) SMALL_ACCOUNT_KLINE_PERIODS = ("5m", "15m", "1h", "d") MIN_SEED_BARS = 30 _SEED_LOCK = threading.Lock() _SEED_STARTED = False def _small_account_products() -> list[dict]: allowed = {x.upper() for x in SMALL_ACCOUNT_PRODUCT_THS} out: list[dict] = [] for p in PRODUCTS: root = normalize_product_ths(p.get("ths") or "") if root.upper() in allowed: out.append(p) return out def _resolve_main_symbol(product: dict) -> Optional[str]: try: from modules.core.symbols import _main_index, _main_index_lock with _main_index_lock: cached = (_main_index or {}).get(product.get("sina") or "") if cached and cached.get("ths_code"): return str(cached["ths_code"]) except Exception: pass try: main = resolve_main_contract(product) if main and main.get("ths_code"): return str(main["ths_code"]) except Exception as exc: logger.debug("resolve main for seed %s: %s", product.get("ths"), exc) try: from modules.core.symbols import _stub_main_contract stub = _stub_main_contract(product) if stub and stub.get("ths_code"): return str(stub["ths_code"]) except Exception: pass return None def seed_small_account_klines( *, db_path: Optional[str] = None, force: bool = False, ) -> dict[str, int]: """下载四品种主力合约 K 线到独立库;已存在且充足时跳过。""" conn = connect_kline_db(db_path) try: ensure_kline_tables(conn) saved: dict[str, int] = {} for product in _small_account_products(): sym = _resolve_main_symbol(product) if not sym: continue chart_sym = ths_to_sina_chart_symbol(sym) if not chart_sym: continue for period in SMALL_ACCOUNT_KLINE_PERIODS: key = f"{sym}:{period}" meta = load_meta(conn, chart_sym, period) if ( not force and meta and int(meta.get("bar_count") or 0) >= MIN_SEED_BARS ): continue try: bars = fetch_sina_klines(sym, period) or [] except Exception as exc: logger.warning("seed kline fetch failed %s %s: %s", sym, period, exc) continue if len(bars) < MIN_SEED_BARS: logger.debug( "seed kline too few bars %s %s: %d", sym, period, len(bars), ) continue n = save_bars(conn, chart_sym, period, bars) saved[key] = n logger.info("seeded kline %s %s (%d bars)", sym, period, n) return saved finally: conn.close() def start_small_account_kline_seed(*, db_path: Optional[str] = None, delay_sec: float = 8.0) -> None: """后台预下载(仅执行一次)。""" global _SEED_STARTED with _SEED_LOCK: if _SEED_STARTED: return _SEED_STARTED = True def _run() -> None: if delay_sec > 0: time.sleep(delay_sec) try: saved = seed_small_account_klines(db_path=db_path) if saved: logger.info("small-account kline seed done: %s", ", ".join(saved.keys())) else: logger.debug("small-account kline seed: nothing new to download") except Exception as exc: logger.warning("small-account kline seed failed: %s", exc) threading.Thread(target=_run, daemon=True, name="kline-seed").start()