mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 17:54:15 +08:00
111 lines
3.5 KiB
Python
111 lines
3.5 KiB
Python
"""历史股本解析。
|
|
|
|
财务股本按公告日可用,历史缺失时回退 instruments 最新流通股本。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import date
|
|
from pathlib import Path
|
|
|
|
import polars as pl
|
|
|
|
|
|
def load_share_history(data_dir: Path) -> pl.DataFrame:
|
|
"""读取本地财务股本表;未同步或损坏时返回空表。"""
|
|
path = data_dir / "financials" / "shares" / "part.parquet"
|
|
if not path.exists():
|
|
return pl.DataFrame()
|
|
try:
|
|
shares = pl.read_parquet(path)
|
|
if not {"symbol", "period_end", "float_shares"} <= set(shares.columns):
|
|
return pl.DataFrame()
|
|
return shares
|
|
except Exception:
|
|
return pl.DataFrame()
|
|
|
|
|
|
def apply_historical_float_shares(
|
|
rows: pl.DataFrame,
|
|
shares: pl.DataFrame | None,
|
|
*,
|
|
today: date,
|
|
) -> pl.DataFrame:
|
|
"""为行情行解析有效流通股本。
|
|
|
|
当日保留 rows.float_shares;历史日期使用公告日不晚于交易日的最新股本,
|
|
找不到历史记录时继续使用 rows.float_shares。
|
|
"""
|
|
required = {"symbol", "date", "float_shares"}
|
|
if (
|
|
rows.is_empty()
|
|
or not required <= set(rows.columns)
|
|
or shares is None
|
|
or shares.is_empty()
|
|
or not {"symbol", "period_end", "float_shares"} <= set(shares.columns)
|
|
):
|
|
return rows
|
|
|
|
def as_date_expr(column: str) -> pl.Expr:
|
|
dtype = shares.schema[column]
|
|
if dtype == pl.Utf8:
|
|
return pl.col(column).str.to_date(strict=False)
|
|
return pl.col(column).cast(pl.Date, strict=False)
|
|
|
|
available_date = as_date_expr("period_end")
|
|
if "announce_date" in shares.columns:
|
|
available_date = as_date_expr("announce_date").fill_null(available_date)
|
|
|
|
history = (
|
|
shares
|
|
.select(
|
|
pl.col("symbol").cast(pl.Utf8),
|
|
available_date.alias("_share_available_date"),
|
|
pl.col("period_end").cast(pl.Utf8).alias("_share_period_end"),
|
|
pl.col("float_shares").cast(pl.Float64, strict=False).alias("_historical_float_shares"),
|
|
)
|
|
.filter(
|
|
pl.col("symbol").is_not_null()
|
|
& pl.col("_share_available_date").is_not_null()
|
|
& (pl.col("_historical_float_shares") > 0)
|
|
)
|
|
.sort(["symbol", "_share_available_date", "_share_period_end"])
|
|
.unique(subset=["symbol", "_share_available_date"], keep="last")
|
|
.sort(["symbol", "_share_available_date"])
|
|
)
|
|
if history.is_empty():
|
|
return rows
|
|
|
|
resolved = (
|
|
rows
|
|
.with_row_index("_share_row_order")
|
|
.with_columns(
|
|
pl.col("symbol").cast(pl.Utf8),
|
|
pl.col("date").cast(pl.Date, strict=False).alias("_share_trade_date"),
|
|
)
|
|
.sort(["symbol", "_share_trade_date"])
|
|
.join_asof(
|
|
history,
|
|
left_on="_share_trade_date",
|
|
right_on="_share_available_date",
|
|
by="symbol",
|
|
strategy="backward",
|
|
check_sortedness=False,
|
|
)
|
|
.with_columns(
|
|
pl.when(pl.col("_share_trade_date") == pl.lit(today))
|
|
.then(pl.col("float_shares"))
|
|
.otherwise(
|
|
pl.coalesce("_historical_float_shares", "float_shares")
|
|
)
|
|
.alias("float_shares")
|
|
)
|
|
.sort("_share_row_order")
|
|
)
|
|
return resolved.drop(
|
|
"_share_row_order",
|
|
"_share_trade_date",
|
|
"_share_available_date",
|
|
"_share_period_end",
|
|
"_historical_float_shares",
|
|
)
|