138 lines
6.1 KiB
Python
138 lines
6.1 KiB
Python
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}
|