114 lines
4.2 KiB
Python
114 lines
4.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""情绪温度计——涨停池三件套四指标(消费 provider.get_limit_pool 出口)。
|
|
|
|
原料:每日 19:30 ak-events 落盘的 zt/zbgc/dtgc 三池(get_limit_pool kind
|
|
三合一,英文标准列 code/consecutive_boards/industry…,真空日=标准列空表)。
|
|
|
|
四指标口径(与数据 session 契约对齐 2026-09-03):
|
|
- 涨停/跌停数: 行数计;**dtgc 空=当日 0 跌停,是合法值非缺数**
|
|
- 连板高度: zt.consecutive_boards 取 max(字符串值 to_numeric 吸收)
|
|
- 炸板率: zbgc行数/(zt行数+zbgc行数)——涨停池含「炸过又回封」的票
|
|
(break_count>0 仍在 zt),两池并集=当日触板全集,勿用 zt 内
|
|
break_count>0 把回封票从分母里扣掉
|
|
- 涨停行业集中度: zt.industry top1 占比+行业名(空串/NaN 剔除)
|
|
|
|
定位:巡检/守卫层(veto/overlay)原料,不进信号层;阈值由 history_thermometer
|
|
对已积累历史校准后另行确定,本模块只算不判。
|
|
"""
|
|
from typing import Any, Dict, Optional
|
|
|
|
import pandas as pd
|
|
|
|
|
|
def compute_thermometer(
|
|
zt: pd.DataFrame,
|
|
zbgc: pd.DataFrame,
|
|
dtgc: pd.DataFrame,
|
|
) -> Dict[str, Any]:
|
|
"""单日四指标(纯函数,输入 get_limit_pool 三 kind 的当日切版)。"""
|
|
zt_n, zbgc_n, dt_n = len(zt), len(zbgc), len(dtgc)
|
|
touch_total = zt_n + zbgc_n
|
|
|
|
max_boards = 0
|
|
if zt_n and "consecutive_boards" in zt.columns:
|
|
boards = pd.to_numeric(zt["consecutive_boards"], errors="coerce")
|
|
if boards.notna().any():
|
|
max_boards = int(boards.max())
|
|
|
|
if zt_n and "industry" in zt.columns:
|
|
ind = zt["industry"].dropna().astype(str).str.strip()
|
|
ind = ind[ind != ""]
|
|
else:
|
|
ind = pd.Series(dtype=object)
|
|
if len(ind):
|
|
counts = ind.value_counts()
|
|
top_industry: Optional[str] = str(counts.index[0])
|
|
top1_ratio: float = float(counts.iloc[0]) / float(len(ind))
|
|
else:
|
|
top_industry, top1_ratio = None, 0.0
|
|
|
|
return {
|
|
"limit_up_count": zt_n,
|
|
"limit_down_count": dt_n,
|
|
"net_limit": zt_n - dt_n,
|
|
"limit_up_down_ratio": (float(zt_n) / float(dt_n)) if dt_n else None,
|
|
"max_consecutive_boards": max_boards,
|
|
"broken_board_rate": (float(zbgc_n) / float(touch_total)) if touch_total else 0.0,
|
|
"top_industry": top_industry,
|
|
"industry_top1_ratio": round(top1_ratio, 4),
|
|
}
|
|
|
|
|
|
def daily_thermometer(
|
|
provider: Any,
|
|
date: Optional[str] = None,
|
|
) -> Dict[str, Any]:
|
|
"""当日快照:三 kind 单日拉取(单日即 raw 语义,节假日副本不入场)。
|
|
|
|
date=None → provider 的最新落盘日语义,trade_date 从返回尾行解析。
|
|
"""
|
|
zt = provider.get_limit_pool("zt", date=date)
|
|
zbgc = provider.get_limit_pool("zbgc", date=date)
|
|
dtgc = provider.get_limit_pool("dtgc", date=date)
|
|
result = compute_thermometer(zt, zbgc, dtgc)
|
|
if date is not None:
|
|
result["trade_date"] = str(date)
|
|
elif "trade_date" in zt.columns and len(zt):
|
|
result["trade_date"] = str(zt["trade_date"].iloc[-1])
|
|
else:
|
|
result["trade_date"] = None
|
|
return result
|
|
|
|
|
|
def history_thermometer(
|
|
provider: Any,
|
|
start: str,
|
|
end: Optional[str] = None,
|
|
) -> pd.DataFrame:
|
|
"""区间逐日指标(阈值校准用):三 kind 各一次区间拉取,按 trade_date 对齐。
|
|
|
|
日期并集驱动——某池某日无行(如跌停 0)按 0 计,不丢该日;
|
|
trading_days_only 滤非交易日由 provider 承担,本层不再判历。
|
|
"""
|
|
frames: Dict[str, pd.DataFrame] = {}
|
|
for kind in ("zt", "zbgc", "dtgc"):
|
|
df = provider.get_limit_pool(kind, start=start, end=end)
|
|
frames[kind] = df if isinstance(df, pd.DataFrame) else pd.DataFrame()
|
|
|
|
dates: set = set()
|
|
for df in frames.values():
|
|
if not df.empty and "trade_date" in df.columns:
|
|
dates.update(str(d) for d in df["trade_date"])
|
|
|
|
rows = []
|
|
for d in sorted(dates):
|
|
slices = {
|
|
kind: (df[df["trade_date"] == d] if not df.empty else df)
|
|
for kind, df in frames.items()
|
|
}
|
|
row = compute_thermometer(
|
|
slices["zt"], slices["zbgc"], slices["dtgc"])
|
|
row["trade_date"] = d
|
|
rows.append(row)
|
|
return pd.DataFrame(rows)
|