Skip to content

Commit 116fb80

Browse files
author
AdvancingTitans
committed
fix: require two-sided ths fund flow
1 parent dd2fc7c commit 116fb80

6 files changed

Lines changed: 97 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.1.11] - 2026-06-02
9+
10+
### Fixed
11+
- Tonghuashun concept fund-flow is used only when both net-inflow and net-outflow rankings are available; otherwise `young flow` falls back to the existing Eastmoney/Sina/Tencent/local-cache chain.
12+
- Fund-flow headings now match the actual source scope, including concept boards, sector boards, market-activity references, and Eastmoney main-capital flow.
13+
14+
### Changed
15+
- Normal command output no longer prints engineering-style data-quality/completeness/source-diagnostic sections; detailed diagnostics remain available with `YOUNG_STOCK_DEBUG=1`.
16+
817
## [0.1.10] - 2026-06-02
918

1019
### Added

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ It is also a foundation for analysis pipelines: every subcommand maps to a Pytho
7777
- **Smart caching**`~/.young_stock/cache/`, 7-day TTL, auto-pruned. Pass `--refresh` to skip.
7878
- **Trade-day awareness** — nearest-trade-day resolution including weekends and (best-effort) holidays.
7979
- **Rich terminal tables** — readable on dark and light terminals.
80-
- **Verified A-share fund flow**`young flow` first tries Tonghuashun concept-board fund flow for fresh board-direction money signals, then Eastmoney main-capital flow/page indicators, Sina Finance sector fund-flow, Sina/Tencent market-activity references, and finally the most recent locally cached good record. Non-equivalent fallbacks are clearly labeled instead of being presented as whole-market main-capital net inflow.
80+
- **Verified A-share fund flow**`young flow` uses Tonghuashun concept-board fund flow only when both net-inflow and net-outflow rankings are available, then falls back to Eastmoney main-capital flow/page indicators, Sina Finance sector fund-flow, Sina/Tencent market-activity references, and finally the most recent locally cached good record. Non-equivalent fallbacks are clearly labeled instead of being presented as whole-market main-capital net inflow.
8181
- **News heat ranking** — HK/US focus stocks can be ranked by filtered news heat from multiple no-login sources: Futu, Sina Finance, and Eastmoney fast news. Xueqiu/THS are intentionally not hardwired unless a stable no-login interface is available.
8282
- **Same-day news discipline** — news sections only show items published on the requested trading date, up to five items, with a clear empty-state message when nothing valid is available.
8383
- **Readable news links** — linked news items are checked for obvious empty/404/no-content pages and replaced by other same-day news when possible.
@@ -139,6 +139,6 @@ young a -d 20260530 --refresh # 指定日期 + 强制刷新
139139
young update # 在当前 Python 环境中更新 CLI
140140
```
141141

142-
数据来源:同花顺概念资金流页面、腾讯财经、新浪财经、东方财富公开行情与快讯接口(`data.10jqka.com.cn` / `qt.gtimg.cn` / `hq.sinajs.cn` / `push2.eastmoney.com` / `push2ex.eastmoney.com` / `np-listapi.eastmoney.com`),多源自动切换。本地缓存 7 天,目录 `~/.young_stock/cache/`,可用 `young cache-clear` 清理。命令会标注当前阶段:上午盘、午间、下午盘或盘后;若展示的是非请求日的最新可用数据,会标注为该交易日盘后数据。
142+
数据来源:同花顺概念资金流页面(仅在同时拿到净流入/净流出榜时采用)、腾讯财经、新浪财经、东方财富公开行情与快讯接口(`data.10jqka.com.cn` / `qt.gtimg.cn` / `hq.sinajs.cn` / `push2.eastmoney.com` / `push2ex.eastmoney.com` / `np-listapi.eastmoney.com`),多源自动切换。本地缓存 7 天,目录 `~/.young_stock/cache/`,可用 `young cache-clear` 清理。命令会标注当前阶段:上午盘、午间、下午盘或盘后;若展示的是非请求日的最新可用数据,会标注为该交易日盘后数据。普通输出不展示完整度和数据源诊断,排查问题时可设置 `YOUNG_STOCK_DEBUG=1` 查看详细切换记录
143143

144144
适用人群:每天盘后想用一条命令看完五张图的开发者 / 量化研究者 / 自动化爱好者。欢迎 issue / PR。

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "young-stock-cli"
7-
version = "0.1.10"
7+
version = "0.1.11"
88
description = "A-share (China stock market) after-hours CLI — no login, no scraping tricks, just data."
99
readme = "README.md"
1010
requires-python = ">=3.10"

src/young_stock/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""young-stock-cli: A-share after-hours CLI."""
22

3-
__version__ = "0.1.10"
3+
__version__ = "0.1.11"

src/young_stock/_core.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,14 @@ def fetch_ths_concept_money_flow_snapshot(date_str: str) -> dict[str, str]:
608608
"""同花顺概念资金流。页面可直接返回表格,作为 young flow 的优先在线源。"""
609609
cached = cache_load("fund_flow", date_str, "ths", ttl=CACHE_TTL_SECONDS)
610610
if cached:
611-
return cached
611+
try:
612+
cached_in = json.loads(cached.get("_concept_in", "[]"))
613+
cached_out = json.loads(cached.get("_concept_out", "[]"))
614+
except json.JSONDecodeError:
615+
cached_in, cached_out = [], []
616+
if cached_in and cached_out:
617+
return cached
618+
diag("Ignored THS concept flow cache without two-sided net flow")
612619
headers = {
613620
"User-Agent": (
614621
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
@@ -635,6 +642,9 @@ def fetch_ths_concept_money_flow_snapshot(date_str: str) -> dict[str, str]:
635642
return {}
636643
top_in_rows = sorted([row for row in rows if float(row.get("net") or 0) > 0], key=lambda item: float(item.get("net") or 0), reverse=True)[:5]
637644
top_out_rows = sorted([row for row in rows if float(row.get("net") or 0) < 0], key=lambda item: float(item.get("net") or 0))[:5]
645+
if not top_in_rows or not top_out_rows:
646+
diag("THS concept money-flow snapshot: missing two-sided net flow")
647+
return {}
638648
trade_date = _display_date(nearest_trade_date())
639649
result = {
640650
"date": trade_date,
@@ -2370,6 +2380,8 @@ def print_fund_flow(flow_data: dict[str, str]) -> None:
23702380
scope_title = "A股资金流向(概念板块口径)"
23712381
elif flow_data.get("_fallback_indicator") == "sector_money_flow":
23722382
scope_title = "A股资金流向(行业板块口径)"
2383+
elif flow_data.get("_fallback_indicator") == "market_activity":
2384+
scope_title = "A股资金流向(指数活跃度参考)"
23732385
print(f"## {scope_title}\n")
23742386
if not flow_data or "_error" in flow_data:
23752387
print(" 暂不展示:当前没有拿到可核验交易日的免登录资金流数据。")
@@ -2501,8 +2513,8 @@ def print_sentiment_summary(zt_data: dict, dt_data: dict, zb_data: dict, flow_da
25012513
def print_global_indices(indices: list[QuoteData], market_name: str) -> None:
25022514
print(f"## {market_name} 大盘指数\n")
25032515
activity_label = "成交额" if any(q.turnover is not None for q in indices) else "成交量"
2504-
print(f"{'指数':<15} {'点位':>12} {'涨跌幅':>10} {activity_label:>14} {'来源':>8} {'完整度':>8}")
2505-
print("-" * 82)
2516+
print(f"{'指数':<15} {'点位':>12} {'涨跌幅':>10} {activity_label:>14} {'来源':>8}")
2517+
print("-" * 72)
25062518
for qd in indices:
25072519
name = qd.name or qd.symbol
25082520
price_str = fmt_price(qd.price)
@@ -2516,8 +2528,7 @@ def print_global_indices(indices: list[QuoteData], market_name: str) -> None:
25162528
"eastmoney_stock_get": "东财",
25172529
"eastmoney_clist": "东财",
25182530
}.get(qd.source, qd.source or "-")
2519-
quality_str = f"{qd.completeness:.0f}%"
2520-
print(f"{name:<15} {price_str:>12} {pct_str:>10} {vol_str:>14} {source_str:>8} {quality_str:>8}")
2531+
print(f"{name:<15} {price_str:>12} {pct_str:>10} {vol_str:>14} {source_str:>8}")
25212532
print()
25222533

25232534

@@ -2742,7 +2753,6 @@ def print_single_stock_report(qd: QuoteData, requested_date: str) -> None:
27422753
if qd.turnover is not None:
27432754
print(f" 成交额: {fmt_amount(qd.turnover)}")
27442755
print(f" 成交量: {fmt_volume(qd.volume)}")
2745-
print(f" 完整度: {qd.completeness:.0f}%")
27462756
if qd.notes:
27472757
print(f" 提醒: {';'.join(qd.notes)}")
27482758
print()
@@ -3001,6 +3011,8 @@ def print_global_sentiment(indices: list[QuoteData]) -> None:
30013011

30023012

30033013
def print_data_quality_report(results: list[QuoteData]) -> None:
3014+
if os.environ.get("YOUNG_STOCK_DEBUG") != "1":
3015+
return
30043016
warnings = []
30053017
source_notes = []
30063018
recommendations = []

tests/test_core.py

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -334,8 +334,8 @@ def test_fund_flow_prefers_ths_concept_flow(monkeypatch):
334334
"date": "2026-06-01",
335335
"_source": "同花顺概念资金流",
336336
"_fallback_indicator": "concept_money_flow",
337-
"_concept_in": "[]",
338-
"_concept_out": "[]",
337+
"_concept_in": '[{"name":"ERP概念","net":14.28}]',
338+
"_concept_out": '[{"name":"有色金属","net":-8.0}]',
339339
},
340340
)
341341
monkeypatch.setattr(_core, "fetch_fund_flow_json", lambda url: (_ for _ in ()).throw(AssertionError("should not call Eastmoney first")))
@@ -566,10 +566,74 @@ def test_print_fund_flow_market_activity_indicator(capsys):
566566

567567
output = capsys.readouterr().out
568568
assert "不等同于主力资金净流入" in output
569+
assert "指数活跃度参考" in output
569570
assert "合计成交额" in output
570571
assert "主力净流入:" not in output
571572

572573

574+
def test_print_global_indices_hides_completeness(capsys):
575+
_core.print_global_indices(
576+
[
577+
_core.QuoteData(
578+
symbol="^HSI",
579+
name="恒生指数",
580+
market="hk_market",
581+
price=25182.39,
582+
change_pct=0.70,
583+
turnover=462070141280.0,
584+
source="tencent",
585+
completeness=100,
586+
)
587+
],
588+
"港股",
589+
)
590+
591+
output = capsys.readouterr().out
592+
assert "完整度" not in output
593+
assert "100%" not in output
594+
595+
596+
def test_print_data_quality_report_is_quiet_by_default(capsys):
597+
_core.DIAGNOSTICS[:] = ["Tencent HK index口径提示"]
598+
599+
_core.print_data_quality_report(
600+
[
601+
_core.QuoteData(
602+
symbol="^HSI",
603+
name="恒生指数",
604+
completeness=100,
605+
notes=["港股指数采用腾讯收盘口径"],
606+
)
607+
]
608+
)
609+
610+
assert capsys.readouterr().out == ""
611+
_core.DIAGNOSTICS[:] = []
612+
613+
614+
def test_print_single_stock_report_hides_completeness(capsys):
615+
_core.print_single_stock_report(
616+
_core.QuoteData(
617+
symbol="600519",
618+
name="贵州茅台",
619+
market="cn_market",
620+
date="2026-06-01",
621+
price=1500.0,
622+
prev_close=1490.0,
623+
change=10.0,
624+
change_pct=0.67,
625+
volume=123456,
626+
source="sina",
627+
completeness=100,
628+
),
629+
requested_date="20260601",
630+
)
631+
632+
output = capsys.readouterr().out
633+
assert "完整度" not in output
634+
assert "100%" not in output
635+
636+
573637
def test_diagnostic_summary_is_quiet_by_default(capsys):
574638
_core.DIAGNOSTICS[:] = ["Sina missing AAPL"]
575639

0 commit comments

Comments
 (0)