From d661b801df4c14c9103273a5a18fb565a828583f Mon Sep 17 00:00:00 2001 From: wanghep Date: Sat, 18 Apr 2026 13:06:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=B8=B8=E8=B5=84?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E8=82=A1=E7=A5=A8=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/lhbfx/queries.py | 109 +++- docs/技术文档.md | 2 +- docs/需求文档.md | 6 +- docs/需求梳理-今日游资操作优先.md | 5 +- .../src/components/TraderDetailScreen.vue | 471 ++++++++++++------ frontend/src/types.ts | 6 + 6 files changed, 440 insertions(+), 159 deletions(-) diff --git a/backend/src/lhbfx/queries.py b/backend/src/lhbfx/queries.py index a793fe2..d52efb6 100644 --- a/backend/src/lhbfx/queries.py +++ b/backend/src/lhbfx/queries.py @@ -354,8 +354,7 @@ def fetch_trader_detail(trader_id: int) -> dict[str, Any]: ) seats = [_normalize_row(row) for row in cursor.fetchall()] - cursor.execute( - """ + stock_query = """ SELECT d.stock_code, MAX(COALESCE(o.stock_name, d.stock_name)) AS stock_name, @@ -365,22 +364,120 @@ def fetch_trader_detail(trader_id: int) -> dict[str, Any]: MAX(d.trade_date) AS last_trade_date, SUM(CASE WHEN CAST(COALESCE(NULLIF(d.buy_amount_wan, ''), '0') AS DECIMAL(18,2)) > 0 THEN 1 ELSE 0 END) AS buy_action_count, SUM(CASE WHEN CAST(COALESCE(NULLIF(d.sell_amount_wan, ''), '0') AS DECIMAL(18,2)) > 0 THEN 1 ELSE 0 END) AS sell_action_count, + SUM(CAST(COALESCE(NULLIF(d.net_amount_wan, ''), '0') AS DECIMAL(18,2))) AS total_net_amount_wan, MAX(CASE WHEN w.warning_type = 'sell_alert' THEN 1 ELSE 0 END) AS has_sell_alert, - MAX(CASE WHEN w.warning_type = 'slow_exit_watch' THEN 1 ELSE 0 END) AS has_slow_exit + MAX(CASE WHEN w.warning_type = 'slow_exit_watch' THEN 1 ELSE 0 END) AS has_slow_exit, + MAX(s.industry) AS industry, + MAX(s.market) AS market, + MAX(s.total_market_value) AS total_market_value, + MAX(s.circulating_market_value) AS circulating_market_value FROM lhb_detail_seats d LEFT JOIN lhb_overview o ON o.stock_code = d.stock_code AND o.trade_date = d.trade_date LEFT JOIN warning_events w ON w.stock_code = d.stock_code AND w.trader_name = d.matched_trader_name + LEFT JOIN stocks s + ON s.stock_code = d.stock_code WHERE d.matched_trader_name = %s GROUP BY d.stock_code ORDER BY last_trade_date DESC, action_count DESC LIMIT 100 - """, - (trader_name,), - ) + """ + + cursor.execute(stock_query, (trader_name,)) stocks = [_normalize_row(row) for row in cursor.fetchall()] + stock_codes = [row["stock_code"] for row in stocks if row.get("stock_code")] + increasing_by_stock: dict[str, bool] = {} + if stock_codes: + placeholders = ", ".join(["%s"] * len(stock_codes)) + cursor.execute( + f""" + SELECT + stock_code, + trade_date, + SUM(CAST(COALESCE(NULLIF(net_amount_wan, ''), '0') AS DECIMAL(18,2))) AS daily_net_amount_wan + FROM lhb_detail_seats + WHERE matched_trader_name = %s + AND stock_code IN ({placeholders}) + AND trade_date IS NOT NULL + GROUP BY stock_code, trade_date + ORDER BY stock_code, trade_date + """, + (trader_name, *stock_codes), + ) + net_history_by_stock: dict[str, list[float]] = {} + for row in cursor.fetchall(): + stock_code = row["stock_code"] + net_history_by_stock.setdefault(stock_code, []).append(float(row["daily_net_amount_wan"] or 0)) + + for stock_code, net_history in net_history_by_stock.items(): + increasing_by_stock[stock_code] = len(net_history) >= 2 and all( + current > previous for previous, current in zip(net_history, net_history[1:]) + ) + + for stock in stocks: + stock["is_net_amount_increasing"] = increasing_by_stock.get(stock["stock_code"], False) + + missing_codes = [ + row["stock_code"] + for row in stocks[:30] + if not row.get("industry") or not row.get("market") or row.get("total_market_value") is None + ] + if missing_codes: + eastmoney = EastMoneyClient() + seen_codes: set[str] = set() + for stock_code in missing_codes: + if stock_code in seen_codes: + continue + seen_codes.add(stock_code) + profile: dict[str, Any] = {} + snapshot: dict[str, Any] = {} + try: + profile = eastmoney.fetch_company_profile(stock_code) + except Exception: + profile = {} + try: + snapshot = eastmoney.fetch_quote_snapshot(stock_code) + except Exception: + snapshot = {} + + cursor.execute( + """ + INSERT INTO stocks ( + stock_code, + stock_name, + market, + industry, + concept_tags, + total_market_value, + circulating_market_value + ) + VALUES (%s, %s, %s, %s, %s, %s, %s) + ON DUPLICATE KEY UPDATE + stock_name = COALESCE(NULLIF(VALUES(stock_name), ''), stock_name), + market = COALESCE(NULLIF(VALUES(market), ''), market), + industry = COALESCE(NULLIF(VALUES(industry), ''), industry), + concept_tags = COALESCE(VALUES(concept_tags), concept_tags), + total_market_value = COALESCE(VALUES(total_market_value), total_market_value), + circulating_market_value = COALESCE(VALUES(circulating_market_value), circulating_market_value) + """, + ( + stock_code, + profile.get("stock_name") or (snapshot.get("stock_name") if snapshot else None) or stock_code, + profile.get("market"), + profile.get("industry") or snapshot.get("industry"), + json.dumps(profile.get("concept_tags") or [], ensure_ascii=False) if profile.get("concept_tags") else None, + snapshot.get("total_market_value"), + snapshot.get("circulating_market_value"), + ), + ) + + cursor.execute(stock_query, (trader_name,)) + stocks = [_normalize_row(row) for row in cursor.fetchall()] + for stock in stocks: + stock["is_net_amount_increasing"] = increasing_by_stock.get(stock["stock_code"], False) + cursor.execute( """ SELECT trade_date, stock_code, stock_name, warning_type, warning_level, trigger_reason diff --git a/docs/技术文档.md b/docs/技术文档.md index c8ce7b7..b82e34b 100644 --- a/docs/技术文档.md +++ b/docs/技术文档.md @@ -140,7 +140,7 @@ longhubang/ ### 4.3 页面组件 - `HomeControlScreen.vue`:首页总控台 -- `TraderDetailScreen.vue`:游资详情 +- `TraderDetailScreen.vue`:游资详情,采用单列全宽股票列表,支持时间、名称、净额连续增大筛选,并展示行业、板块、总市值、净额和预警标签 - `StockDetailScreen.vue`:个股详情 - `WarningCenterScreen.vue`:预警中心 diff --git a/docs/需求文档.md b/docs/需求文档.md index bf3111a..5b67870 100644 --- a/docs/需求文档.md +++ b/docs/需求文档.md @@ -64,9 +64,11 @@ 游资详情页支持: - 游资档案 -- 核心席位 -- 近期参与股票 - 风格标签 +- 股票列表全宽展示 +- 时间、名称、净额连续增大筛选 +- 按时间、净额、动作数排序 +- 行业、上市板块、总市值、净额和预警标签展示 ### 2.6 预警中心 diff --git a/docs/需求梳理-今日游资操作优先.md b/docs/需求梳理-今日游资操作优先.md index 593232f..f203425 100644 --- a/docs/需求梳理-今日游资操作优先.md +++ b/docs/需求梳理-今日游资操作优先.md @@ -144,9 +144,11 @@ 游资详情页保留,但不再作为首页首要信息。 -游资详情页用于查看某个游资的长期参与情况、席位信息、风格标签和近期参与股票。 +游资详情页用于查看某个游资的长期参与情况、风格标签和近期参与股票。 已移除“动作时间线”模块。 +已移除“净额重点”和“席位概览”侧栏,股票列表改为单列全宽展示。 +股票列表支持按时间、名称、净额连续增大筛选,并支持按时间、净额、动作数排序。 原因: @@ -206,3 +208,4 @@ 6. 首页顶部指标与关注列表联动,取消关注后自动剔除统计。 7. 所有股票入口都能跳转到股票详情。 8. 游资详情页去掉动作时间线。 +9. 游资详情页去掉净额重点和席位概览侧栏,保留单列股票列表和净额趋势筛选。 diff --git a/frontend/src/components/TraderDetailScreen.vue b/frontend/src/components/TraderDetailScreen.vue index 842036d..e494702 100644 --- a/frontend/src/components/TraderDetailScreen.vue +++ b/frontend/src/components/TraderDetailScreen.vue @@ -1,8 +1,8 @@ diff --git a/frontend/src/types.ts b/frontend/src/types.ts index d5e09fa..8d7d35d 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -72,8 +72,14 @@ export interface TraderStock { last_trade_date: string | null buy_action_count: number sell_action_count: number + total_net_amount_wan?: number | null has_sell_alert: number has_slow_exit: number + is_net_amount_increasing?: boolean + industry?: string | null + market?: string | null + total_market_value?: number | null + circulating_market_value?: number | null } export interface TraderDetail {