mirror of
https://github.com/simonlin1212/TradingAgents-astock.git
synced 2026-08-31 01:23:38 +00:00
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:
@@ -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
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
@@ -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 港股分析仍在 roadmap(issue #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
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user