5328673ce8
Co-authored-by: Cursor <cursoragent@cursor.com>
138 lines
4.5 KiB
Python
138 lines
4.5 KiB
Python
# 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()
|