fix: 港股/美股代码不再被当成 A 股静默查询 (#43 前置)

_normalize_ticker 文档写着"返回纯 6 位代码"却从不校验位数:港股 00700 /
0700.HK、美股 AAPL 全部原样放行,拿去问 mootdx/腾讯/东财。这些源对不存在的
代码往往不报错,只返回空值或僵尸报价(北交所 920 号段同类问题),模型会拿一份
看起来正常、实际属于别的市场的数据写完整篇报告。

- 一个卡点覆盖 15 个数据接口(所有 vendor 方法都过 _normalize_ticker)
- 港股报错指明去处(global-stock-data)与 roadmap(#43),不只说"不支持"
- A 股各写法不受影响:SH600519 / 600519.SH / sz000001 / 北交所 920002

港股多 Agent 分析仍在 roadmap。数据层可行性已实测:腾讯/新浪/东财 push2/
Yahoo K线/东财港股三表 五个端点全部可用;但政策/游资/解禁三个 A 股特化角色
对港股不适用,需单独设计角色集。

测试:新增 24 例(两侧都锁),全量回归 301 passed
This commit is contained in:
Simon Lin
2026-08-06 20:53:04 +12:00
parent cdca5e1f8f
commit b0c39ea915
4 changed files with 141 additions and 2 deletions
+22
View File
@@ -6,6 +6,28 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
Breaking changes within the 0.x line are called out explicitly.
## [0.5.3] — 2026-08-06
港股/美股代码不再被当成 A 股静默查询([#43](https://github.com/simonlin1212/TradingAgents-astock/issues/43) 前置修复)。
`_normalize_ticker()` 的文档写着"返回纯 6 位代码",但从不校验位数:港股 `00700`
/ `0700.HK`、美股 `AAPL` 都被**原样放行**,然后拿去问 mootdx / 腾讯 / 东财。这些
源对不存在的代码往往不报错,只返回空值或僵尸报价(北交所 920 号段踩过同类问题),
于是模型会拿着一份看起来正常、实际属于别的市场的数据写完整篇报告——报告里完全
看不出来。
- **一个卡点覆盖 15 个数据接口**:`_normalize_ticker` 是所有 vendor 方法的必经之路。
- 港股代码报错时**指明去处**(姊妹项目 global-stock-data)和 roadmap#43),
而不是只说"不支持"。
- A 股各种写法(`SH600519` / `600519.SH` / `sz000001` / 北交所 `920002`)一个都
不受影响,24 例测试两侧都锁。
港股多 Agent 分析本身仍在 roadmap:数据层可行性已验证(腾讯 / 新浪 / 东财 push2 /
Yahoo K线 / 东财港股三表 五个端点实测均可用),但 A 股特化的政策 / 游资 / 解禁三个
角色对港股不适用,需要单独设计角色集。
---
## [0.5.2] — 2026-08-06
新增决策绩效统计,并修掉一个会污染它的评级解析漏洞。
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "tradingagents-astock"
version = "0.5.2"
version = "0.5.3"
description = "A股多Agent投研框架 — 基于 TradingAgents 深度特化"
readme = "README.md"
requires-python = ">=3.10"
+83
View File
@@ -0,0 +1,83 @@
"""非 A 股代码必须当场报错,不能拿去查 A 股数据源(#43)。
港股代码是 4~5 位数字或带 `.HK`此前会被 `_normalize_ticker` **原样放行**
然后拿去问 mootdx / 腾讯 / 东财这些源对不存在的代码往往不报错只返回空值或
僵尸报价北交所 920 号段踩过同类问题于是模型会拿着一份看起来正常实际
属于别的市场的数据写完整篇报告报告里完全看不出来
这里锁的是两侧 A 股必须被拦下A 股的各种写法一个都不能被误伤
"""
import pytest
from tradingagents.dataflows.a_stock import _normalize_ticker
# ---------------------------------------------------------------------------
# A 股照常工作(防止防护误伤)
# ---------------------------------------------------------------------------
@pytest.mark.parametrize(
"raw,expected",
[
("600519", "600519"), # 沪市主板
("000001", "000001"), # 深市主板
("300750", "300750"), # 创业板
("688017", "688017"), # 科创板
("920002", "920002"), # 北交所新号段
("SH600519", "600519"),
("600519.SH", "600519"),
("sz000001", "000001"),
("BJ920002", "920002"),
(" 600519 ", "600519"),
],
)
def test_a_share_forms_still_pass(raw, expected):
assert _normalize_ticker(raw) == expected
# ---------------------------------------------------------------------------
# 港股被拦下,并且说清楚去哪
# ---------------------------------------------------------------------------
@pytest.mark.parametrize("raw", ["00700", "0700.HK", "00700.HK", "9988", "09988", "0700.hk"])
def test_hk_codes_are_rejected(raw):
with pytest.raises(ValueError) as exc:
_normalize_ticker(raw)
assert "港股" in str(exc.value)
def test_hk_error_points_to_the_alternative():
"""光说"不支持"不够,要告诉用户现在能用什么。"""
with pytest.raises(ValueError) as exc:
_normalize_ticker("00700")
message = str(exc.value)
assert "global-stock-data" in message
assert "#43" in message
# ---------------------------------------------------------------------------
# 美股与畸形输入
# ---------------------------------------------------------------------------
@pytest.mark.parametrize("raw", ["AAPL", "TSLA", "BABA"])
def test_us_tickers_are_rejected(raw):
with pytest.raises(ValueError, match="不是 A 股代码"):
_normalize_ticker(raw)
@pytest.mark.parametrize("raw", ["700", "12", "1234567"])
def test_wrong_length_numeric_is_rejected(raw):
"""位数不对的纯数字既不是 A 股也不该被猜成别的市场,直接报错。"""
with pytest.raises(ValueError):
_normalize_ticker(raw)
def test_error_names_the_original_input():
"""报错要带上用户原本传进来的东西,否则不知道是哪一步出的问题。"""
with pytest.raises(ValueError) as exc:
_normalize_ticker("0700.HK")
assert "0700.HK" in str(exc.value)
+35 -1
View File
@@ -58,10 +58,42 @@ def _get_prefix(code: str) -> str:
return "sz"
def _reject_non_a_share(original: str, code: str) -> None:
"""港股/美股代码走到 A 股数据层时当场报错,而不是拿去查 A 股(#43)。
A 股代码恒为 6 位数字港股是 4~5 `00700`或带 `.HK` 后缀美股是字母
这些代码此前会被**原样放行**然后拿去问 mootdx / 腾讯 / 东财而这些源对
不存在的代码往往不报错只返回空值或僵尸报价北交所 920 号段就踩过
`_normalize_ticker` 上游的 `_get_prefix`于是模型会拿着一份看起来正常
实际属于别的市场或根本不存在的数据写完整篇报告报告里完全看不出来
"""
if code.isdigit() and len(code) == 6:
return
upper = original.strip().upper()
if upper.endswith(".HK") or (code.isdigit() and len(code) in (4, 5)):
raise ValueError(
f"'{original}' 是港股代码。本数据层只支持 A 股(6 位数字代码,"
f"如 600519 / 000001)。港股数据请用姊妹项目 global-stock-data"
f"多 Agent 港股分析仍在 roadmapissue #43)。"
)
if code and not code.isdigit():
raise ValueError(
f"'{original}' 不是 A 股代码。本数据层只支持 A 股 6 位数字代码"
f"(如 600519);美股/港股请用姊妹项目 global-stock-data。"
)
raise ValueError(
f"'{original}' 不是有效的 A 股代码:A 股代码恒为 6 位数字(如 600519),"
f"这里解析出的是 '{code}'"
)
def _normalize_ticker(symbol: str) -> str:
"""Strip exchange prefix/suffix, return pure 6-digit code.
Handles: '688017', 'SH688017', '688017.SH', 'sh688017'
A 股代码港股 `00700` / `0700.HK`美股 `AAPL`会直接报错不再原样
放行去查 A 股数据源#43)。
"""
s = symbol.strip().upper()
# Remove .SH / .SZ / .BJ suffix
@@ -74,7 +106,9 @@ def _normalize_ticker(symbol: str) -> str:
if s.startswith(prefix):
s = s[len(prefix) :]
break
return safe_ticker_component(s)
code = safe_ticker_component(s)
_reject_non_a_share(symbol, code)
return code
# ---------------------------------------------------------------------------