Files
xiaobaifupan/app/backend/data/providers/tushare_dragon_tiger.py
T

215 lines
8.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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": [],
}