from __future__ import annotations from datetime import datetime, timedelta from typing import Any from backend.bootstrap.config import display_compact_date as _display_date from backend.data.numbers import finite_number as _number class StockMixin: def stock_detail(self, ts_code: str, requested_date: str) -> dict[str, Any]: trade_date, _ = self.resolve_trade_context(requested_date) end = datetime.strptime(trade_date, "%Y%m%d") start_date = (end - timedelta(days=190)).strftime("%Y%m%d") daily = self.query( "daily", {"ts_code": ts_code, "start_date": start_date, "end_date": trade_date}, "ts_code,trade_date,open,high,low,close,pct_chg,vol,amount", ) factors = self.query( "adj_factor", {"ts_code": ts_code, "start_date": start_date, "end_date": trade_date}, "ts_code,trade_date,adj_factor", ) basics = self.query( "stock_basic", {"ts_code": ts_code}, "ts_code,symbol,name,area,industry,market,list_date", ) daily_basics = self.query( "daily_basic", {"ts_code": ts_code, "trade_date": trade_date}, "ts_code,trade_date,turnover_rate,volume_ratio,total_mv,circ_mv", ) moneyflow = self.query( "moneyflow", {"ts_code": ts_code, "trade_date": trade_date}, "ts_code,trade_date,buy_sm_amount,sell_sm_amount,buy_md_amount,sell_md_amount," "buy_lg_amount,sell_lg_amount,buy_elg_amount,sell_elg_amount,net_mf_amount", ) factor_map = {row["trade_date"]: _number(row.get("adj_factor"), 1) for row in factors} latest_factor = max(factor_map.values(), default=1) or 1 prices = [] for row in sorted(daily, key=lambda item: item.get("trade_date", ""))[-90:]: factor = factor_map.get(row.get("trade_date"), latest_factor) ratio = factor / latest_factor prices.append( { "trade_date": _display_date(str(row.get("trade_date", ""))), "open": round(_number(row.get("open")) * ratio, 3), "high": round(_number(row.get("high")) * ratio, 3), "low": round(_number(row.get("low")) * ratio, 3), "close": round(_number(row.get("close")) * ratio, 3), "change": _number(row.get("pct_chg")), "volume": _number(row.get("vol")), "amount_billion": round(_number(row.get("amount")) / 100000, 2), } ) flow = moneyflow[0] if moneyflow else {} basic = basics[0] if basics else {} daily_basic = daily_basics[0] if daily_basics else {} latest = prices[-1] if prices else {} actual_trade_date = max( (str(row.get("trade_date") or "") for row in daily), default=trade_date, ) or trade_date return { "meta": { "requested_date": _display_date(requested_date), "trade_date": _display_date(actual_trade_date), "source": "tushare", "updated_at": datetime.now().astimezone().isoformat(timespec="seconds"), "notice": "", }, "stock": { "code": ts_code.split(".")[0], "ts_code": ts_code, "name": basic.get("name") or "--", "industry": basic.get("industry") or "其他", "area": basic.get("area") or "--", "market": basic.get("market") or "--", "list_date": _display_date(str(basic.get("list_date") or "")), "price": latest.get("close", 0), "change": latest.get("change", 0), "turnover_rate": _number(daily_basic.get("turnover_rate")), "volume_ratio": _number(daily_basic.get("volume_ratio")), "amount_billion": latest.get("amount_billion", 0), }, "prices": prices, "moneyflow": { "net_million": round(_number(flow.get("net_mf_amount")) / 100, 2), "large_million": round( (_number(flow.get("buy_lg_amount")) + _number(flow.get("buy_elg_amount")) - _number(flow.get("sell_lg_amount")) - _number(flow.get("sell_elg_amount"))) / 100, 2, ), "medium_million": round( (_number(flow.get("buy_md_amount")) - _number(flow.get("sell_md_amount"))) / 100, 2, ), "small_million": round( (_number(flow.get("buy_sm_amount")) - _number(flow.get("sell_sm_amount"))) / 100, 2, ), }, } def stock_intraday(self, ts_code: str, requested_date: str) -> dict[str, Any]: trade_date, _ = self.resolve_trade_context(requested_date) display_date = _display_date(trade_date) rows = self.query( "stk_mins", { "ts_code": ts_code, "freq": "1min", "start_date": f"{display_date} 09:00:00", "end_date": f"{display_date} 15:30:00", }, "ts_code,trade_time,open,close,high,low,vol,amount", ) points = [] for row in sorted(rows, key=lambda item: str(item.get("trade_time") or "")): trade_time = str(row.get("trade_time") or "") if not trade_time: continue points.append( { "time": trade_time[-8:-3] if len(trade_time) >= 8 else trade_time, "open": round(_number(row.get("open")), 3), "high": round(_number(row.get("high")), 3), "low": round(_number(row.get("low")), 3), "close": round(_number(row.get("close")), 3), "volume": _number(row.get("vol")), "amount": _number(row.get("amount")), } ) return {"trade_date": display_date, "points": points}