Per the maintainer review on #1772/#1773, scope this PR to market detection
and data routing only — no Taiwan stock-index / seed / pool-resolution, so a
bare code can never be silently rewritten to a .TW suffix (the index path is a
deferred follow-up with the jp/kr-style honest doc + entry-point tests).
- data_provider/base.py: _is_tw_market / _is_tw_suffix_stock, _market_tag,
normalize_stock_code, get_daily_data + get_realtime_quote routing,
_DAILY_MARKET_FETCHER_SUPPORT (YfinanceFetcher only), offshore fundamental path
- data_provider/yfinance_fetcher.py: _get_tw_main_indices (^TWII / ^TWOII) +
.TW/.TWO suffix passthrough
- src/core/trading_calendar.py: XTAI / Asia/Taipei + get_market_for_stock
- src/market_context.py: detect_market + Taiwan prompt guidelines
- src/{market_phase_summary, services/stock_code_utils, core/pipeline}: tw plumbing
(stock_code_utils only recognizes .TW/.TWO codes; no bare->suffix rewrite)
- src/services/decision_signal_extractor.py: gracefully skip tw (recognized by
the data layer but not yet supported by the signal service) — no swallowed
ValueError + traceback
- tests: test_tw_market_support.py + test_decision_signal_extractor.py
(test_build_payload_skips_tw_market_gracefully — real tw skip regression)
- docs/market-support.md + CHANGELOG
Deferred follow-ups: stock index/seed + autocomplete + frontend, API market
enums + api_spec, and the service-layer market enums.
Bare numeric codes keep A-share semantics; only an explicit .TW/.TWO suffix
opts into Taiwan.
Refs #1772
* feat: harden alphasift data source routing
* fix(review-feedback-1680): Keep the sample AlphaSift pin in sync and Avoid circuit-breaking
* fix(review-feedback-1680): Defer half-open health checks until a source is attempted and Treat
* fix(review-feedback-1680): fix only bypasses this branch for fetchers that opt into allow empty
* fix(review-feedback-1680): Avoid accepting truncated Tencent histories
* fix(review-feedback-1680): 修正 .env.example 的 AlphaSift 快照源默认说明,并避免在 docs/CHANGELOG.md
* fix(review-feedback-1680): src/config.py、src/services/alphasift service.py
* fix(review-feedback-1680): requirements.txt、src/config.py、.env.example、src/services/alphasift
* fix(review-feedback-1680): 修复 TencentFetcher 对显式历史窗口的处理问题
* fix(review-feedback-1680): Check the cap was hit before rejecting Tencent history
* fix(review-feedback-1680): Use Tencent's hyphenated date format and Convert Tencent daily volume
* fix(review-feedback-1680): Accept the executor timeout argument and preserve capped windows with
* fix: limit empty daily data opt-in behavior
* docs: restore AlphaSift 3.22 changelog entry
* fix(issue-1464): [bug]-scheduled-analysis-hangs-when-stoc
* fix(review-feedback-1465): Avoid leaking daemon threads on timed-out Akshare calls
* fix(review-feedback-1465): fix can reintroduce hangs/crashes in the exact scheduled pipeline it
* fix(review-feedback-1465): Use a safe start method for timeout subprocesses
* fix(review-feedback-1465): Call freeze support before spawning timeout workers
* feat: enrich email report with financial / dividend / related-boards blocks for CN/HK/US
Render three structured sections in single-stock and dashboard markdown reports
(used by email and other static channels) sourced from fundamental_context:
- 财务摘要: report date, revenue, parent net profit, operating cash flow, ROE,
YoY growth, gross margin
- 股东回报: TTM cash dividend per share (pre-tax), TTM event count, TTM yield,
latest ex-dividend date
- 关联板块: belong_boards with sector-leading/lagging signal & change_pct when
available; auto-collapses to a 2-col (name / type) layout when no sector
ranking data exists (typical for HK/US)
To unlock the offshore (HK/US) data path:
- Add YfinanceFundamentalAdapter producing the same bundle shape as the
AkShare adapter, with growth, financial_report (incl. currency),
TTM dividend events, and sector+industry as belong_boards
- Route get_fundamental_context for hk/us through a new
_build_offshore_fundamental_context helper instead of short-circuiting
to not_supported; keep capital_flow / dragon_tiger / boards as
not_supported (no equivalent feed)
- Stop overwriting belong_boards in the pipeline for non-CN markets so the
adapter-provided sector/industry data survives
Notification renderer:
- Attach fundamental_context onto AnalysisResult so the notification layer
can reach it without an extra DB round-trip
- Currency-aware formatting: USD -> 亿/万美元, HKD -> 亿/万港元,
CNY/other -> 亿/万元; per-share amounts get the same suffix
- Right-align numeric columns and center the report-date column in the
financial summary and shareholder return tables for cleaner rendering
Tests:
- Offline coverage for the new yfinance adapter (symbol conversion, bundle
shape, refusal to use QoQ as YoY, graceful degradation when yfinance is
unavailable)
- Offshore market path through DataFetcherManager.get_fundamental_context
- Adaptive related-boards rendering (2-col when no sector signal,
4-col when at least one row carries data)
- Currency-aware amount formatting for AAPL (USD) and HK09988 (CNY)
* fix: split financial vs dividend currency and recompute TTM yield for HK/US
HK ADRs typically report financialCurrency=CNY but pay dividends in HKD
(live yfinance confirms: 9988.HK / 0700.HK both have financialCurrency=CNY
and currency=HKD; BABA US ADR has financialCurrency=CNY and currency=USD).
Previously the adapter wrote a single currency field shared by both the
financial report and the dividend block, which rendered HK dividends as
yuan in the email report.
This change writes earnings.financial_report.currency from
info.financialCurrency and earnings.dividend.currency from info.currency
independently, and the notification renderer reads each block's own
currency (falling back to financial_report.currency when dividend.currency
is absent, which keeps AkShare A-share rendering unchanged).
TTM dividend yield was sourced from info.dividendYield, mixing the
numerator (TTM cash window) with a denominator-defined yield from a
different yfinance computation. The yield is now recomputed as
ttm_cash_dividend_per_share / latest_price * 100 with both sides in the
trading currency (latest_price falls through info.currentPrice ->
regularMarketPrice -> previousClose). info.dividendYield is now a
last-resort passthrough (no *100, since current yfinance already returns
percent), behind info.trailingAnnualDividendYield (decimal).
Docs (full-guide / full-guide_EN) updated: HK/US now returns
valuation/growth/earnings/belong_boards rather than not_supported, with
the dual-currency split and the TTM yield formula explicitly documented.
CHANGELOG entries rewritten to reflect the corrected behavior.
Verified live against 9988.HK / 0700.HK / AAPL / 600519: the rendered
email shows financial summary in 亿元 for HK ADRs (CNY statements) while
the shareholder return block shows 港元 (HKD dividends), and the
recomputed yields match ttm_cash / latest_price in the same currency.
---------
Co-authored-by: suan baby <suanai@tianle.me>
* feat: add Finnhub and AlphaVantage API key config fields
* feat: add FinnhubFetcher for US market OHLCV and realtime quotes
* feat: add AlphaVantageFetcher for US market OHLCV and realtime quotes
* feat: register FinnhubFetcher and AlphaVantageFetcher in DataFetcherManager
* fix: address PR review feedback on US fetcher routing and AlphaVantage pct_chg
- Fix AlphaVantage pct_chg calculation: sort by date ascending before
computing percentage change to handle newest-first API response
- Extend US daily routing source_order to include FinnhubFetcher and
AlphaVantageFetcher in the failover chain
- Add newest-first pct_chg regression test for AlphaVantage
- Add US routing fallback order test
- Update CHANGELOG.md with new feature and fix entries
- Add docs/specs/ to .gitignore to prevent accidental commits
Verified: 1956 tests passed, ci_gate.sh clean
* fix: address third round PR review - routing gaps, index isolation, CHANGELOG cleanup
- Isolate US index routing: YFinance-first, skip Finnhub/AlphaVantage for index codes
- Add Finnhub/AlphaVantage to US realtime_quote and get_stock_name routing
- Clean CHANGELOG.md: only our 3 Finnhub/AlphaVantage entries in [Unreleased]
* fix(data-provider): skip unavailable optional fetchers and back off longbridge reconnects
* fix(data-provider): back off unavailable longbridge routes
* fix(data-provider): back off noisy pytdx name lookups
* docs: align PR 1249 changelog and compatibility notes
* fix(config): preserve legacy schedule override precedence
* fix: update report title from 'A股智能分析报告' to '股票智能分析报告'
* feat: add support for Hong Kong market in market review and analysis features
* fix: update daily_analysis workflow name and adjust region check in market analyzer
* fix: enhance market region handling in run_market_review and compute_effective_region functions
* test: add Hong Kong market support in MarketReviewLocalizationTestCase
* fix: update market review region handling to include Hong Kong support in documentation and logic
* fix: update daily_analysis workflow name to Chinese and adjust Hong Kong index symbols in yfinance_fetcher
* fix: refactor market review execution to use run_market_review for improved handling
* fix: implement trading day filtering in MarketCommand to skip reviews on market holidays
* fix: update changelog and README to reflect market command enhancements and support for multiple regions
* fix: update YfinanceFetcher to include HK index symbol mappings for improved data retrieval
Co-authored-by: Jonty <jonty.wang@hk.com.sg>
Co-authored-by: mumu <42829555+ZhuLinsen@users.noreply.github.com>
Problem Statement:
On non-trade dats, get_trade_time() returned the second-to-last trade instead of the latest one.
Root Cause:
_pick_trade_date(trade_dates,use_today=False) skips trade_dates[0], but on non-trade days trade_dates[0] is already the most recent trade date
Solution:
for non-trade days, put use_today=Ture
* feat(autocomplete): rebuild web autocomplete mvp
* feat(autocomplete): add error boundary and runtime fallback
Add error boundary and runtime error handling to improve autocomplete stability:
- Add StockAutocompleteBoundary class component to catch render errors
- Add FallbackInput component for graceful degradation
- Add runtimeFallback state and error handling in useAutocomplete hook
- Wrap search logic with try-catch to prevent crashes
- Add comprehensive tests for error scenarios
This ensures the autocomplete degrades to a plain input when:
- Index loading fails (existing behavior)
- Runtime search throws (new)
- Component tree throws during render (new)
* feat(autocomplete): add input validation and optimize name resolution
- Add early rejection for obviously invalid mixed alphanumeric input
- Skip expensive AkShare/fuzzy fallback for non-CJK free text
- Enhance frontend validation with isObviouslyInvalidStockQuery
- Increase minimum query length from 1 to 2 characters
- Add comprehensive test coverage for validation logic
- Update E2E test placeholder text to reflect new capabilities
These changes address code review feedback about:
- Preventing abuse from invalid input patterns
- Improving performance by avoiding unnecessary network calls
- Ensuring consistent validation between frontend and backend
* fix(autocomplete): harden input validation and runtime guards
* update doc
* style(autocomplete): adjust suggestion item hover effect
- Reduce hover background opacity from 35% to 25% for lighter visual effect
- Brighten hover color in dark theme from #257280 to #2dcae6
* fix(autocomplete): do not auto-highlight first suggestion to preserve raw input on Enter
* test(home): align report fixture with report language metadata
* fix: unify task queue stock dedupe key
* fix: runtime robustness — efinance timeout, type-safe integrity, report save decoupling
- efinance_fetcher: wrap all efinance API calls with _ef_call_with_timeout()
to prevent indefinite hangs when eastmoney hosts are unreachable (was 81min+)
- analyzer: make check_content_integrity() type-safe when LLM returns dict
for operation_advice or string for core_conclusion/intelligence/battle_plan
- pipeline: decouple report saving from notification flag (--no-notify no longer
skips local report generation); normalize operation_advice dict to string
- backtest_engine: handle operation_advice as dict in _compute_advice_breakdown
- runner: guard response.usage against None to prevent AttributeError
- orchestrator: log non-critical stage failures (intel/risk) instead of silent skip
* fix: address PR #660 review — timeout shutdown, env guard, decision_type normalize, non-str advice
- efinance_fetcher: _ef_call_with_timeout() now uses explicit executor.shutdown(
wait=False) to avoid re-blocking on hung thread after FuturesTimeoutError
- efinance_fetcher: guard _EF_CALL_TIMEOUT parsing with try/except to prevent
module load failure on invalid EFINANCE_CALL_TIMEOUT env value
- pipeline: normalize decision_type with strip/lower before operation_advice
dict lookup so 'BUY'/'Buy' variants map correctly instead of falling to default
- analyzer: treat non-str operation_advice/analysis_summary as missing to
prevent downstream TypeError (e.g. get_emoji() with unhashable dict)
- .env.example: add EFINANCE_CALL_TIMEOUT config entry
* feat: enhance yfinance fetcher with Stooq fallback and stock mapping
- Implement _get_us_stock_quote_from_stooq for US stock data resilience
- Add stock_mapping.py for common stock code-to-name conversion
- Add unit test for Stooq fallback logic
- Part of #1
* feat: expand STOCK_NAME_MAP with major A-share stocks
* refactor: decouple stock_mapping from yfinance_fetcher (optional import)
* fix: correct Stooq fallback prev close and lint
* fix: data fetcher improvements and gitignore updates
- Prefer static stock name mapping for prefetch
- Fix gitignore to track src/data
- Document Stooq fallback prev close behavior in CHANGELOG
This combines the last three commits while excluding README.md changes.
- EfinanceFetcher: switch ETF from ef.fund.get_quote_history (NAV only)
to ef.stock.get_quote_history (full OHLCV) so ETFs get proper
open/high/low/close/volume/amount data instead of zeros
- Remove obsolete NAV column mappings from _normalize_data
- Pin tiktoken>=0.8.0,<0.12.0 to avoid 'Unknown encoding cl100k_base'
plugin registration issue in tiktoken 0.12.0
Fixes#541Fixes#527Fixes#537