mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 17:54:15 +08:00
- 全部 K 线取数点改走 CompactKlineData 列式直转 polars (无 pandas 中转, 全市场单轮本地转换 ~1.1s → ~0.1s), 时区口径统一 _timestamp_to_beijing_datetime - 除权因子 trade_date 原取 UTC 日期, 北京零点事件整体早一天 → 转北京墙钟后取日期 - 盘中分钟增量间隔上限 300s → 120s (universe 仅回最新 3 根, 超限必留缺口)
122 lines
4.5 KiB
Python
122 lines
4.5 KiB
Python
"""TickFlow provider implementation."""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from collections.abc import Callable
|
|
from datetime import datetime
|
|
|
|
import polars as pl
|
|
|
|
from app.data_providers.base import AssetType, ProviderCapabilities
|
|
from app.data_providers.normalizer import normalize_adj_factors, normalize_daily, normalize_instruments
|
|
from app.tickflow.client import get_client
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_EXCHANGES = ["SH", "SZ", "BJ"]
|
|
|
|
|
|
class TickFlowProvider:
|
|
name = "tickflow"
|
|
capabilities = ProviderCapabilities(
|
|
instruments=True,
|
|
daily=True,
|
|
adj_factor=True,
|
|
minute=True,
|
|
realtime=True,
|
|
financial=True,
|
|
)
|
|
|
|
def get_instruments(self, asset_type: AssetType) -> pl.DataFrame:
|
|
tf = get_client()
|
|
instrument_type = "stock" if asset_type == "stock" else asset_type
|
|
rows: list[dict] = []
|
|
for ex in _EXCHANGES:
|
|
try:
|
|
items = tf.exchanges.get_instruments(ex, instrument_type=instrument_type)
|
|
rows.extend([it for it in (items or []) if isinstance(it, dict)])
|
|
except Exception as e: # noqa: BLE001
|
|
logger.warning("TickFlow instruments %s/%s failed: %s", ex, instrument_type, e)
|
|
return normalize_instruments(rows, asset_type=asset_type, source=self.name)
|
|
|
|
def get_daily(
|
|
self,
|
|
symbols: list[str],
|
|
start_time: datetime | None,
|
|
end_time: datetime | None,
|
|
asset_type: AssetType, # noqa: ARG002
|
|
) -> pl.DataFrame:
|
|
if not symbols:
|
|
return pl.DataFrame()
|
|
tf = get_client()
|
|
kwargs = {
|
|
"period": "1d",
|
|
"adjust": "none",
|
|
"count": 10000 if start_time and end_time else 250,
|
|
"as_dataframe": False,
|
|
"show_progress": False,
|
|
}
|
|
if start_time and end_time:
|
|
from app.services.kline_sync import _compact_klines_to_df, _datetime_to_ms, _timestamp_to_beijing_datetime
|
|
kwargs["start_time"] = _datetime_to_ms(start_time)
|
|
kwargs["end_time"] = _datetime_to_ms(end_time)
|
|
else:
|
|
from app.services.kline_sync import _compact_klines_to_df, _timestamp_to_beijing_datetime
|
|
raw = tf.klines.batch(symbols, **kwargs)
|
|
# False 直转: 列数组→polars (无 pandas 中转), 加北京墙钟 datetime 列
|
|
# (normalize_daily 映射为 date); 保留 timestamp 原列 — normalize_daily
|
|
# 会把它改名为 quote_ts (盘后校验/量比折算用), 不能像 kline_sync 路径那样丢弃。
|
|
seg = _compact_klines_to_df(raw)
|
|
if seg.is_empty():
|
|
return pl.DataFrame()
|
|
seg = seg.with_columns(_timestamp_to_beijing_datetime(pl.col("timestamp")).alias("datetime"))
|
|
return normalize_daily(seg, source=self.name)
|
|
|
|
def get_adj_factors(
|
|
self,
|
|
symbols: list[str],
|
|
start_time: datetime | None,
|
|
end_time: datetime | None,
|
|
asset_type: AssetType, # noqa: ARG002
|
|
) -> pl.DataFrame:
|
|
if not symbols:
|
|
return pl.DataFrame()
|
|
tf = get_client()
|
|
kwargs = {"as_dataframe": False}
|
|
if start_time or end_time:
|
|
from app.services.kline_sync import _datetime_to_ms
|
|
if start_time:
|
|
kwargs["start_time"] = _datetime_to_ms(start_time)
|
|
if end_time:
|
|
kwargs["end_time"] = _datetime_to_ms(end_time)
|
|
raw = tf.klines.ex_factors(symbols, **kwargs)
|
|
return normalize_adj_factors(raw, source=self.name)
|
|
|
|
def get_minute(
|
|
self,
|
|
symbols: list[str],
|
|
start_time: datetime | None,
|
|
end_time: datetime | None,
|
|
asset_type: AssetType = "stock", # noqa: ARG002
|
|
freq: str = "1m", # noqa: ARG002
|
|
on_chunk_done: Callable[[int, int], None] | None = None, # noqa: ARG002
|
|
) -> pl.DataFrame:
|
|
# Existing minute sync remains in app.services.kline_sync for now.
|
|
return pl.DataFrame()
|
|
|
|
def get_realtime(
|
|
self,
|
|
universes: list[str] | None = None,
|
|
symbols: list[str] | None = None,
|
|
) -> pl.DataFrame:
|
|
tf = get_client()
|
|
if universes and symbols:
|
|
raise ValueError("TickFlow realtime accepts either universes or symbols, not both")
|
|
if universes:
|
|
resp = tf.quotes.get_by_universes(universes=universes)
|
|
elif symbols:
|
|
resp = tf.quotes.get(symbols=symbols)
|
|
else:
|
|
return pl.DataFrame()
|
|
return pl.DataFrame(resp or [])
|