from __future__ import annotations from typing import Any from backend.data.numbers import finite_number as _number from backend.data.providers.tushare_helpers import _display_time, _prices_equal class DailyMarketMixin: def resolve_trade_context(self, requested: str) -> tuple[str, str]: requested_rows = self.query( "trade_cal", {"exchange": "SSE", "start_date": requested, "end_date": requested}, "cal_date,is_open,pretrade_date", ) if not requested_rows: trade_date = requested else: row = requested_rows[0] trade_date = row["cal_date"] if row.get("is_open") == 1 else row.get("pretrade_date", requested) resolved_rows = self.query( "trade_cal", {"exchange": "SSE", "start_date": trade_date, "end_date": trade_date}, "cal_date,is_open,pretrade_date", ) previous = resolved_rows[0].get("pretrade_date") if resolved_rows else "" return trade_date, previous or trade_date def _load_daily(self, trade_date: str) -> list[dict[str, Any]]: return self.query( "daily", {"trade_date": trade_date}, "ts_code,trade_date,open,high,low,close,pct_chg,amount", ) def _load_limit_type(self, trade_date: str, limit_type: str) -> list[dict[str, Any]]: fields = ( "trade_date,ts_code,industry,name,close,pct_chg,amount,limit_amount," "float_mv,total_mv,turnover_ratio,fd_amount,first_time,last_time," "open_times,up_stat,limit_times" ) rows = self.query( "limit_list_d", {"trade_date": trade_date, "limit_type": limit_type}, fields, ) for row in rows: row["limit_type"] = limit_type row["amount_unit"] = "yuan" return rows def _load_limit_lists(self, trade_date: str) -> list[dict[str, Any]]: rows: list[dict[str, Any]] = [] for limit_type in ("U", "D", "Z"): rows.extend(self._load_limit_type(trade_date, limit_type)) return rows def _derive_limits( self, trade_date: str, daily: list[dict[str, Any]], price_limits: list[dict[str, Any]] | None = None, basic_rows: list[dict[str, Any]] | None = None, previous_limit_rows: list[dict[str, Any]] | None = None, capital_rows: list[dict[str, Any]] | None = None, ) -> list[dict[str, Any]]: if price_limits is None: price_limits = self.query( "stk_limit", {"trade_date": trade_date}, "ts_code,trade_date,up_limit,down_limit", ) limit_map = {row["ts_code"]: row for row in price_limits} if basic_rows is None: basic_rows = self.query( "stock_basic", {"list_status": "L"}, "ts_code,name,industry", ) basic_map = {row["ts_code"]: row for row in basic_rows} previous_limit_map = { str(row.get("ts_code") or ""): row for row in (previous_limit_rows or []) } capital_map = { str(row.get("ts_code") or ""): row for row in (capital_rows or []) } result: list[dict[str, Any]] = [] for row in daily: bounds = limit_map.get(row.get("ts_code")) if not bounds or row.get("close") is None: continue limit_type = "" if _prices_equal(row["close"], bounds.get("up_limit")): limit_type = "U" elif _prices_equal(row["close"], bounds.get("down_limit")): limit_type = "D" elif _prices_equal(row.get("high"), bounds.get("up_limit")): limit_type = "Z" if not limit_type: continue basic = basic_map.get(row["ts_code"], {}) previous_limit = previous_limit_map.get(str(row.get("ts_code") or ""), {}) streak = ( max(1, int(_number(previous_limit.get("limit_times"), 1)) + 1) if limit_type == "U" and previous_limit else 1 ) item = { **row, "name": basic.get("name", "--"), "industry": basic.get("industry") or "其他", "limit_type": limit_type, "limit_times": streak, "open_times": 1 if limit_type == "Z" else 0, "amount_unit": row.get("amount_unit") or "thousand_yuan", } if row.get("amount_unit") == "yuan": capital = capital_map.get(str(row.get("ts_code") or ""), {}) if not capital and capital_rows is None: capital = self._latest_capital(str(row.get("ts_code") or ""), trade_date) float_share = _number(capital.get("float_share")) item["turnover_ratio"] = ( _number(row.get("vol")) / float_share / 100 if float_share else 0 ) item["turnover_source"] = ( "rt_volume/latest_float_share" if float_share else "unavailable" ) item["capital_trade_date"] = str(capital.get("trade_date") or "") result.append(item) return result @staticmethod def _normalize_limit(row: dict[str, Any], status: str) -> dict[str, Any]: amount = _number(row.get("amount")) if row.get("amount_unit") == "thousand_yuan": amount_billion = amount / 100000 else: amount_billion = amount / 100000000 return { "code": str(row.get("ts_code", "")).split(".")[0], "ts_code": row.get("ts_code", ""), "name": row.get("name") or "--", "price": _number(row.get("close")), "change": _number(row.get("pct_chg")), "sector": row.get("industry") or "其他", "reason": row.get("industry") or "待补充", "first_time": _display_time(row.get("first_time")), "last_time": _display_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")), "turnover_source": row.get("turnover_source") or "provider", "capital_trade_date": row.get("capital_trade_date") or "", "amount_billion": round(amount_billion, 2), "seal_amount_million": round(_number(row.get("fd_amount")) / 10000, 0), "float_mv_billion": round(_number(row.get("float_mv")) / 100000000, 1), "status": status, }