rebuild(stage-6): deliver emotion and market pools

This commit is contained in:
leefer
2026-07-30 03:03:53 +08:00
parent 889963862a
commit 59f6011ae8
30 changed files with 1751 additions and 9 deletions
+176
View File
@@ -0,0 +1,176 @@
from __future__ import annotations
from statistics import mean
from typing import Any
from backend.data.contracts import ProviderResult
def build_snapshot(
trade_date: str,
previous_trade_date: str,
inputs: dict[str, ProviderResult | dict[str, Any]],
) -> dict[str, Any]:
daily_rows = _rows(inputs, "daily")
daily = {str(row.get("ts_code") or ""): row for row in daily_rows}
limits = _pool(_rows(inputs, "limit_up"), "涨停")
broken = _pool(_rows(inputs, "broken"), "炸板")
down_limits = _pool(_rows(inputs, "limit_down"), "跌停")
price_limits = {str(row.get("ts_code") or ""): row for row in _rows(inputs, "price_limits")}
for row in broken:
up_limit = _number(price_limits.get(row["identifier"], {}).get("up_limit"))
row["distance_to_limit"] = (
round((up_limit - row["price"]) / up_limit * 100, 2) if up_limit else None
)
yesterday = _yesterday(
_pool(_rows(inputs, "previous_limit_up"), "涨停"),
daily,
limits,
broken,
down_limits,
)
up_count = sum(_number(row.get("pct_chg")) > 0 for row in daily_rows)
down_count = sum(_number(row.get("pct_chg")) < 0 for row in daily_rows)
flat_count = len(daily_rows) - up_count - down_count
amount = sum(_number(row.get("amount")) * 1000 for row in daily_rows)
seal_rate = len(limits) / max(len(limits) + len(broken), 1) * 100
overview = {
"up_count": up_count,
"down_count": down_count,
"flat_count": flat_count,
"limit_up": len(limits),
"limit_down": len(down_limits),
"broken": len(broken),
"seal_rate": round(seal_rate, 1),
"amount": round(amount, 2),
}
return {
"trade_date": trade_date,
"previous_trade_date": previous_trade_date,
"overview": overview,
"limits": limits,
"broken": broken,
"down_limits": down_limits,
"yesterday_limits": yesterday,
"limit_performance": _performance(yesterday),
}
def _rows(
inputs: dict[str, ProviderResult | dict[str, Any]], key: str
) -> tuple[dict[str, Any], ...]:
value = inputs.get(key)
return value.rows if isinstance(value, ProviderResult) else ()
def _pool(rows: tuple[dict[str, Any], ...], status: str) -> list[dict[str, Any]]:
result = []
for row in rows:
identifier = str(row.get("ts_code") or "")
amount = _number(row.get("amount")) * 1000
result.append(
{
"identifier": identifier,
"code": identifier.split(".")[0],
"name": str(row.get("name") or "").strip(),
"price": _number(row.get("close")),
"change": _number(row.get("pct_chg")),
"sector": str(row.get("industry") or "").strip(),
"reason": "",
"first_time": _time(row.get("first_time")),
"last_time": _time(row.get("last_time")),
"open_times": int(_number(row.get("open_times"))),
"streak": max(1, int(_number(row.get("limit_times"), 1))),
"turnover_rate": _number(row.get("turnover_ratio")),
"amount": amount,
"seal_amount": _number(row.get("fd_amount")),
"float_market_value": _number(row.get("float_mv")),
"status": status,
}
)
return result
def _yesterday(
previous: list[dict[str, Any]],
daily: dict[str, dict[str, Any]],
current: list[dict[str, Any]],
broken: list[dict[str, Any]],
down: list[dict[str, Any]],
) -> list[dict[str, Any]]:
current_map = {row["identifier"]: row for row in current}
broken_codes = {row["identifier"] for row in broken}
down_codes = {row["identifier"] for row in down}
result = []
for prior in previous:
identifier = prior["identifier"]
quote = daily.get(identifier, {})
change = _number(quote.get("pct_chg"))
if identifier in current_map:
outcome = "晋级"
elif identifier in broken_codes:
outcome = "炸板"
elif identifier in down_codes:
outcome = "跌停"
elif change > 0:
outcome = "红盘"
else:
outcome = "断板"
result.append(
{
"identifier": identifier,
"code": prior["code"],
"name": prior["name"],
"prior_streak": prior["streak"],
"current_streak": current_map.get(identifier, {}).get("streak", 0),
"current_change": change,
"current_price": _number(quote.get("close")),
"sector": prior["sector"],
"reason": prior["reason"],
"outcome": outcome,
}
)
return result
def _performance(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
result = []
for level in sorted({int(row["prior_streak"]) for row in rows}, reverse=True):
group = [row for row in rows if int(row["prior_streak"]) == level]
advanced = sum(row["outcome"] == "晋级" for row in group)
positive = sum(_number(row["current_change"]) > 0 for row in group)
outcomes = {
outcome: sum(row["outcome"] == outcome for row in group)
for outcome in ("晋级", "红盘", "断板", "炸板", "跌停")
}
result.append(
{
"level": level,
"count": len(group),
"advanced": advanced,
"red": outcomes["红盘"],
"broken": outcomes["断板"],
"opened": outcomes["炸板"],
"limit_down": outcomes["跌停"],
"advance_rate": round(advanced / len(group) * 100, 1),
"positive_rate": round(positive / len(group) * 100, 1),
"average_change": round(mean(_number(row["current_change"]) for row in group), 2),
}
)
return result
def _number(value: Any, default: float = 0.0) -> float:
try:
number = float(value)
return number if number == number else default
except (TypeError, ValueError):
return default
def _time(value: Any) -> str:
text = str(value or "").strip().replace(":", "")
if len(text) < 4 or not text[:4].isdigit():
return ""
return f"{text[:2]}:{text[2:4]}"