Files
tick-stock-panel/backend/app/share_capital.py
T

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",
)