refactor: establish standalone application boundary

This commit is contained in:
leefer
2026-08-03 21:42:25 +08:00
parent cc5fb8d73e
commit e1e76cd51e
324 changed files with 63090 additions and 44743 deletions
@@ -0,0 +1,214 @@
from __future__ import annotations
import json
import re
from datetime import datetime
from typing import Any
from backend.bootstrap.config import display_compact_date as _display_date
from backend.data.numbers import finite_number as _number
from backend.data.providers.tushare_helpers import _text
from backend.data.providers.tushare_transport import TushareError
class DragonTigerMixin:
def hot_money_profiles(self) -> dict[str, Any]:
rows = self.query("hm_list", {}, "name,desc,orgs")
profiles: list[dict[str, Any]] = []
seen_names: set[str] = set()
for row in rows:
name = str(row.get("name") or "").strip()
if not name or name in seen_names:
continue
seen_names.add(name)
description = _text(row.get("desc"))
organization_text = _text(row.get("orgs"))
parsed_organizations: Any = None
if organization_text.startswith("["):
try:
parsed_organizations = json.loads(organization_text)
except json.JSONDecodeError:
parsed_organizations = None
organization_parts = (
parsed_organizations
if isinstance(parsed_organizations, list)
else re.split(r"[,;\n]+", organization_text)
)
organizations = list(dict.fromkeys(
_text(part)
for part in organization_parts
if _text(part)
))
profiles.append(
{
"id": f"hot-money-profile-{len(profiles) + 1}",
"name": name,
"description": description,
"organizations": organizations,
"organization_count": len(organizations),
}
)
return {
"meta": {
"source": "tushare",
"status": "success" if profiles else "empty",
"schema_version": 1,
"updated_at": datetime.now().astimezone().isoformat(timespec="seconds"),
"notice": "",
},
"summary": {
"profile_count": len(profiles),
"described_count": sum(bool(item["description"]) for item in profiles),
"organization_count": sum(item["organization_count"] for item in profiles),
},
"profiles": profiles,
}
def dragon_tiger(self, requested_date: str) -> dict[str, Any]:
trade_date, _ = self.resolve_trade_context(requested_date)
detail_rows = self.query(
"hm_detail",
{"trade_date": trade_date},
"trade_date,ts_code,ts_name,buy_amount,sell_amount,net_amount,"
"hm_name,hm_orgs,tag",
)
notices: list[str] = []
try:
directory_rows = self.query("hm_list", {}, "name,desc,orgs")
except TushareError as exc:
directory_rows = []
notices.append(f"游资名录暂不可用:{exc}")
directory = {
str(row.get("name") or "").strip(): {
"description": _text(row.get("desc")),
"orgs": _text(row.get("orgs")),
}
for row in directory_rows
if str(row.get("name") or "").strip()
}
# 个股龙虎榜仅用于补充涨幅和上榜原因,不参与游资身份识别。
try:
top_rows = self.query(
"top_list",
{"trade_date": trade_date},
"trade_date,ts_code,name,pct_change,reason",
)
except TushareError as exc:
top_rows = []
notices.append(f"个股龙虎榜辅助信息暂不可用:{exc}")
stock_context: dict[str, dict[str, Any]] = {}
for row in top_rows:
ts_code = str(row.get("ts_code") or "")
if ts_code and ts_code not in stock_context:
stock_context[ts_code] = row
groups: dict[str, dict[str, Any]] = {}
for row in detail_rows:
trader_name = str(row.get("hm_name") or "未命名游资").strip()
ts_code = str(row.get("ts_code") or "").strip()
stock = stock_context.get(ts_code, {})
directory_item = directory.get(trader_name, {})
seat_name = _text(row.get("hm_orgs")) or directory_item.get("orgs") or "--"
buy = round(_number(row.get("buy_amount")) / 1000000, 2)
sell = round(_number(row.get("sell_amount")) / 1000000, 2)
net_buy = round(_number(row.get("net_amount")) / 1000000, 2)
group = groups.setdefault(
trader_name,
{
"name": trader_name,
"description": directory_item.get("description") or "",
"directory_orgs": directory_item.get("orgs") or "",
"identity_type": "trader",
"identity_source": "tushare_hm",
"recognized": True,
"buy_million": 0.0,
"sell_million": 0.0,
"net_buy_million": 0.0,
"seat_names": set(),
"stock_codes": set(),
"operations": [],
},
)
group["buy_million"] += buy
group["sell_million"] += sell
group["net_buy_million"] += net_buy
if seat_name != "--":
group["seat_names"].add(seat_name)
code = ts_code.split(".")[0]
if code:
group["stock_codes"].add(code)
group["operations"].append(
{
"code": code,
"ts_code": ts_code,
"name": row.get("ts_name") or stock.get("name") or "--",
"change": (
_number(stock.get("pct_change"))
if stock.get("pct_change") is not None
else None
),
"direction": "买入" if net_buy > 0 else "卖出" if net_buy < 0 else "持平",
"buy_million": buy,
"sell_million": sell,
"net_buy_million": net_buy,
"seat_name": seat_name,
"seat_alias": trader_name,
"tag": _text(row.get("tag")) or "--",
"reason": _text(stock.get("reason")) or "--",
}
)
traders = list(groups.values())
traders.sort(key=lambda item: abs(item["net_buy_million"]), reverse=True)
for index, group in enumerate(traders, start=1):
group["id"] = f"hot-money-{index}"
group["buy_million"] = round(group["buy_million"], 2)
group["sell_million"] = round(group["sell_million"], 2)
group["net_buy_million"] = round(group["net_buy_million"], 2)
group["seat_count"] = len(group.pop("seat_names"))
group["stock_count"] = len(group.pop("stock_codes"))
group["operation_count"] = len(group["operations"])
group["operations"].sort(
key=lambda item: abs(float(item.get("net_buy_million") or 0)), reverse=True
)
operation_count = sum(item["operation_count"] for item in traders)
active_stocks = {
operation["code"] for item in traders for operation in item["operations"]
if operation["code"]
}
net_buy_total = round(sum(item["net_buy_million"] for item in traders), 2)
status = "success" if detail_rows else "partial" if top_rows else "empty"
if not detail_rows:
notices.insert(
0,
f"当日有 {len(stock_context)} 只股票上榜,但未返回可识别的游资每日明细。"
if top_rows
else "该交易日未返回龙虎榜或游资每日明细。",
)
return {
"meta": {
"requested_date": _display_date(requested_date),
"trade_date": _display_date(trade_date),
"source": "tushare",
"status": status,
"schema_version": 3,
"updated_at": datetime.now().astimezone().isoformat(timespec="seconds"),
"notice": "".join(notices),
},
"summary": {
"trader_count": len(traders),
"identity_count": len(traders),
"operation_count": operation_count,
"active_stock_count": len(active_stocks),
"seat_net_buy_million": net_buy_total,
"unclassified_count": 0,
"directory_count": len(directory),
"official_stock_count": len(stock_context),
},
"traders": traders,
"unclassified_seats": [],
"rows": [],
}