refactor: establish standalone application boundary
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
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}
|
||||
Reference in New Issue
Block a user