669 Commits

Author SHA1 Message Date
SynapShift
f9e7b778c4 fix(market): bound AkShare market stats calls (#2345) 2026-09-19 23:09:08 +08:00
summer-meng
1168e31626 feat(runtime): partial notify and diagnostics after scheduler timeout (#2338)
* feat(runtime): partial notify and diagnostics after scheduler timeout

After a hard timeout, scan already-saved analyses and enrich last_error
with completed/pending counts; optional push via DSA_TIMEOUT_PARTIAL_NOTIFY.

Refs #2328

* test(runtime): cover timeout partial delivery helpers

Refs #2328

* docs: document DSA_TIMEOUT_PARTIAL_NOTIFY

Refs #2328

* fix(config): use switch ui_control for timeout partial notify

DSA_TIMEOUT_PARTIAL_NOTIFY used ui_control=toggle, which SystemConfigResponse rejects and broke GET /config in backend-tests 1/3.

* docs(runtime): document timeout partial fail-open for operators

Channel exceptions are swallowed after the analysis lock is released, so they cannot keep status.running true. Collect/import failures stay in warning logs because last_error cannot distinguish them from zero completions.
2026-09-13 17:18:52 +08:00
zhulinsen
afca4fb732 docs: update SerpApi sponsorship links (#2339) 2026-09-13 17:17:08 +08:00
s4kura
2ffe4e729e fix: handle UTF-8 BOM in stock imports (#2333) 2026-09-13 16:48:59 +08:00
zhulinsen
089d9d26d6 test: synchronize cold-start timeout waiters before releasing fetch 2026-09-06 20:34:15 +08:00
zhulinsen
09fa491ae9 docs: prepare v3.32.0 release 2026-09-06 18:59:43 +08:00
Elvis Wang
303f4e1c18 feat: GitHub Actions 指数入口 + 指数注册表补全与自选股配置文档 (Refs #2303) (#2332)
* feat: GitHub Actions STOCK_LIST 指数入口与 CLI unsupported 明确拒绝

* feat: 指数注册表新增国证粮食与中证钢铁,补充指数自选股配置文档

- seed 与 bundled 指数清单 31 项扩展至 33 项(sz399365、csi930606)
- 中英 full-guide 新增指数自选股配置小节,README/DEPLOY 同步提示
- 相关确定性测试断言 31→33 并锁定新 canonical

* fix: market-only 分类跳过测试补齐交易日历 mock,消除周末失败

* fix: 测试类级默认关闭 GITHUB_ACTIONS,修复 CI runner 环境注入导致的分类分支误入

* fix: 入口分类按模式边界跳过不消费个股列表的模式,文档收窄指数入口适用范围

- 新增 _skips_stock_entry guard:--backtest/--market-review/--serve-only/--webui-only/--portfolio/--schedule/config.schedule_enabled 整体跳过 --stocks 与 Actions STOCK_LIST 的分类与索引刷新
- --schedule --stocks 的忽略快照警告与分类解耦(传 args.stocks or None)
- 恢复 --stocks + --portfolio 同框提示日志
- 测试:review 反例四格 + Actions portfolio 格 + portfolio 覆盖到达 pipeline.run 入参断言
- 文档:指数分类与整批拒绝收窄为 --stocks 与 GitHub Actions 两入口
2026-09-05 22:03:49 +08:00
Elvis Wang
e866cfa48d feat: 支持 Bot 指数分析入口与在线验收 (#2327)
* feat: add bot index analysis entry

* test: add Bot index online smoke

* fix: guard Windows-only spawn flag in smoke tests for Linux CI

* fix: clean up worker tree on parent interrupt in smoke runner

* fix: surface interrupt cleanup failures in smoke runner

---------

Co-authored-by: zhulinsen <42829555+ZhuLinsen@users.noreply.github.com>
2026-09-04 18:04:14 +08:00
subaoyan16
b6f9a61671 feat: 新增最小 Agent 轨迹评估入口(Refs #1956) (#2325)
* feat: add minimal agent trajectory metrics with golden samples

* feat: add runnable agent trajectory eval entry with JSON and text reports

* fix: treat unsuccessful AgentResult as a run failure in trajectory eval

* fix: reject multi-arch executor and validate golden tools against the real registry
2026-09-04 18:02:50 +08:00
Elvis Wang
972c314656 feat: Web/API 指数入口与共享 canonical 去重基础 (#2312)
* feat: Web/API 指数入口与共享 canonical 去重基础

为 Issue #2303 Phase 2 PR1 落地 Web/API 指数入口适配:

- API 使用 parse_analysis_target 构造结构化 AnalysisTarget 并贯通到 pipeline
- API 与 TaskQueue 去重按 asset_type 分支(指数用 canonical_id,个股用 legacy code)
- BatchTaskAcceptedResponse 追加可选 rejected 字段,未登记 CSI 单股 400、批量仅该目标失败
- TaskInfo 固化 dedupe_key,避免指数与同码个股折叠及 _analyzing_stocks 残留
- Web 移除 assetType=index 全局过滤,Chat 名称识别保护指数 canonical
- 补齐 Pipeline 指数 DecisionSignal market_override=cn 真实分支测试

* fix: 收敛指数 canonical 身份与批量响应契约

PR #2312 review 修复:报告 meta 补充 asset_type 隐藏指数自选;/analyze 在解析前限制非空原始 token;is_single 统一驱动 metadata/409/单任务 202;HomePage 三元计数继续后续 chunk。

* fix: avoid double space in index news search query

* fix: preserve canonical index identity

* fix: validate legacy task asset type

* fix: preserve canonical index identity in chat

* fix: preserve index identity across chat backends
2026-09-01 19:25:15 +08:00
subaoyan16
3d890a0731 fix: 将 litellm 依赖窗口收敛到已验证上界(排除 1.99.0 回归) (#2320)
* Exclude litellm 1.99.0 from allowed versions

litellm 1.99.0 (released 2026-09-01T00:42:41Z) now forwards the
prompt_cache_key argument to the OpenAI provider. The provider cache
contract test asserts the key is not passed through without verified
capture, so CI backend-tests (3/3) and the downstream backend-gate
went red on every branch right after the release, unrelated to any
code change.

Local capture server reproduction: with 1.98.0 the captured request
body carries no prompt_cache_key; with 1.99.0 it carries
prompt_cache_key=cache-key. This one-line exclusion follows the
existing quarantine pattern for 1.82.7 / 1.82.8 and keeps the tested
behaviour until the contract is adapted deliberately.

* Bound litellm below the 1.99.0 behavioral change

Pin the dependency window to a verified upper bound (latest 1.98.x)
instead of quarantining only 1.99.0, and sync every doc that hardcodes
the litellm constraint string so docs cannot drift from requirements.txt
again.

* Sync .env.example runtime constraint comment to the litellm upper bound
2026-09-01 19:22:45 +08:00
TimingAlmostMet
ac8bd29b0f feat: 自建 SearXNG 搜索超时可配置(SEARXNG_TIMEOUT_SECONDS) (#2249)
* feat: make self-hosted SearXNG search timeout configurable

SEARXNG_TIMEOUT_SECONDS (default 10s, min 1) now controls the per-search
timeout for self-hosted SearXNG instances, threaded through all three
SearchService construction sites. Public-instance timeout stays fixed.

* fix: thread SEARXNG_TIMEOUT_SECONDS through subprocess rebuild and Actions

- persist searxng_timeout_seconds in _constructor_kwargs so the bounded
  topic-news subprocess rebuild no longer silently falls back to 10s
- map SEARXNG_TIMEOUT_SECONDS (vars||secrets) in 00-daily-analysis.yml
- document the variable in full-guide/full-guide_EN data-source tables,
  add CHANGELOG entry, and add regression tests for both paths

* fix: document SearXNG timeout environment contract

* fix: register SearXNG timeout setting

* fix: keep SearXNG timeout env-only

---------

Co-authored-by: zhulinsen <42829555+ZhuLinsen@users.noreply.github.com>
Co-authored-by: zhulinsen <zhuls97@163.com>
2026-08-30 21:31:26 +08:00
Gach-Coder
2ab6f147f3 fix:修复Akshare拉取股票存在空格和字母全角的不一致问题 (#2271)
Co-authored-by: zhulinsen <42829555+ZhuLinsen@users.noreply.github.com>
2026-08-30 20:46:21 +08:00
zhulinsen
ee2c113a1b feat: rebuild partial-data stock profile contract (#2308)
* feat: add partial-data stock profile contract

* fix: query stock profile aliases

* fix: converge US stock profile aliases

* fix: preserve stock profile intelligence coverage

* fix: preserve JP and KR profile identities

* fix: disambiguate profile persisted identities

* fix: complete offshore profile identities

* fix: isolate offshore profile identities

* fix: preserve Taiwan profile aliases

* fix: preserve explicit profile market identity

* fix: preserve market in history candidates

* fix: preserve stock profile research evidence

* fix: complete stock profile history evidence

* fix: reject conflicting stock profile identities

* fix: normalize persisted profile symbol filters

* fix: exclude ambiguous global stock aliases

* fix: deduplicate profile alias queries

* fix: exclude ambiguous profile history aliases

* fix: break stock profile import cycle

* fix: preserve market-unique legacy aliases
2026-08-30 20:38:33 +08:00
Kun Zhu
77cc9ec30f fix(web): 选股结果持久化恢复 - 新增历史记录入口并支持刷新恢复 (#2154)
* fix(web): 选股历史结果持久化恢复(方案A)

- 选股页新增历史记录区块:加载时展示最近 10 次选股运行,点击条目
  通过 history API 加载并展示该次完整结果
- 任务完成后将 runId 持久化到 sessionStorage(不再清空),刷新后
  优先从 history API 按 run_id 恢复结果,避免依赖内存态 task 队列
- task 恢复失败(服务重启/任务过期)时回退到原有 task 轮询逻辑
- 失败/未知状态任务仍清理持久化记录

* docs: 记录选股结果持久化恢复修复到 CHANGELOG

* fix(web): 闭合选股恢复状态清理的两个正确性 blocker

- 不可恢复任务分支补 clearPersistedScreenTask(),避免刷新后反复恢复同一失效任务
- clearScreeningResults() 同步清理持久化的 runId/taskId,切换筛选条件后刷新不再恢复旧结果
- 新增两条回归测试覆盖上述场景

* fix(web): 选股历史条目展示筛选条件,切换筛选条件不清除恢复状态

- 撤销场景2错误修复:clearScreeningResults() 不再清除持久化 runId/taskId,
  切换筛选条件后历史与刷新恢复能力完整保留
- 历史条目展示筛选条件:策略中文名(formatHistoryStrategyName)、
  市场标签(formatHistoryMarketLabel)、返回数量(该次 run 实际候选数 candidateCount)
- 重写对应回归测试 + 新增历史条件展示测试

* fix(web): 历史记录区块移到选股结果下方,符合先看结果再看历史的浏览顺序

* fix(web): 历史 run 恢复时同步策略与市场上下文;changelog 条目移入 Unreleased

- handleHistoryRunSelect 与挂载时 runId 恢复分支现在会从 history detail
  同步 strategy/market 到页面 state,结果区文案和后续深度分析的
  analysisSkills 使用该历史 run 对应的策略,而非当前表单选择
  (关闭 Codex P2 + OR-COR-6c1e5d27)
- CHANGELOG 选股持久化恢复条目从 [3.30.0] 文档小节移至 [Unreleased] 段
- 新增回归测试:点击历史条目后结果区展示同步后的策略/市场上下文

* fix(web): 忽略过期历史详情响应,修复快速切换历史条目的竞态

- handleHistoryRunSelect 用 historyRunRequestIdRef 追踪最新请求,
  响应返回时校验仍是最新请求才应用结果/错误/loading 状态,
  避免 run A 晚到覆盖 run B 的结果(关闭 Codex P2)
- 新增回归测试:先点 run-a 再点 run-b,run-a 迟到返回不覆盖
  run-b 的结果与策略上下文

* fix(web): 历史恢复与手动选择互斥,自动恢复接入竞态保护

- handleHistoryRunSelect 选择历史记录时取消运行中的选股任务轮询(setActiveTaskId(null)),避免任务完成后把当前任务候选回写到历史上下文
- 页面初始自动恢复 effect 接入 historyRunRequestIdRef 竞态保护:若自动恢复返回前用户已手动选择历史记录,则放弃自动恢复响应,避免较晚返回的旧 run 覆盖用户最新选择
- 自动恢复失败回退 task 轮询时也校验用户未手动选择历史记录
- 补充两条回归测试:晚到自动恢复不覆盖手动选择、选择历史时取消进行中的任务

* fix(web): 修复历史恢复链路三处竞态(OpenReview 2026-08-24 复审反馈)

- OR-COR-9b1f8c4e: 过期自动恢复请求的 finally 不再无条件清共享
  loading;setLoading(false) 复用 request-id 守卫,setRestoreResolved(true)
  保持无条件执行,避免提前放开"运行选股"入口
- OR-COR-2c71d8af: handleSubmit 递增 historyRunRequestIdRef 作废飞行中
  的历史/恢复响应并解除 restoreResolved 门闩,新任务轮询立即启动,
  迟到响应不再覆盖新任务状态
- 策略列表竞态: 新增 historyContextStrategyRef,历史选择/自动恢复写入
  的策略跳过 loadStrategies 的归一化回退,迟到的 /strategies 响应不再把
  历史自定义策略改写回默认策略

验证: StockScreeningPage 测试 35 passed(新增 3 条回归)、screening API
测试 13 passed、tsc -b && vite build 通过

* fix(web): 手动选中历史记录后同步持久化恢复指针(OR-COR-4d1a7e90)

handleHistoryRunSelect 成功加载历史详情时调用 persistScreenTask 记录当前
选中的 runId/strategy/market,刷新后自动恢复用户刚查看的历史 run,
而不是停留在更早的 task/run。历史详情不携带 taskId,以 runId 作为占位:
正常路径刷新走 getRun(runId) 恢复、不会触发轮询;若该 run 恢复失败,
占位轮询会命中不可恢复错误并清理过期指针。useCallback 依赖同步补全,
确保持久化使用当前表单值。

验证: StockScreeningPage 测试 36 passed(新增 1 条回归:选中历史后刷新
仍恢复该 run)、screening API 测试 13 passed、tsc -b && vite build 通过

---------

Co-authored-by: ariesy <ariesy.bleiben@gmail.com>
2026-08-30 17:42:38 +08:00
TimingAlmostMet
03e11fe35c fix: US daily routing consumes per-source fetcher priorities (#2250)
The US daily-kline branch ignored fetcher priorities and used a fixed
Finnhub/AlphaVantage/Yfinance/Longbridge order, so per-source knobs
like YFINANCE_PRIORITY=0 never affected US routing. The chain is now
ordered by each fetcher's current priority (stable): defaults match
the builtin chain exactly, single-source *_PRIORITY tweaks take
effect immediately, and the deliberate first-position semantics
(index -> Yfinance, Longbridge-preferred) stay pinned.
2026-08-29 20:48:40 +08:00
zhulinsen
0cf61c637f fix: sync research artifact OpenAPI contract (#2311)
* fix: sync research artifact OpenAPI contract

* chore: reduce follow-up merge conflicts
2026-08-29 18:35:58 +08:00
zhulinsen
86bbf01535 fix: align US index realtime capability (#2310)
* fix: align US index realtime capability

* fix: enforce YFinance-only US index quotes

* chore: reduce follow-up merge conflicts
2026-08-29 18:29:48 +08:00
zhulinsen
0b5f9a6e31 feat: add research artifact contract (#2291)
* feat: add research artifact contract

* fix: preserve research artifact identity and falsey values

* fix: correct research artifact evidence quality
2026-08-29 17:20:25 +08:00
zhulinsen
0709b17ed1 feat: add data capability contract (#2289)
* feat: add data capability contract

* fix(review-feedback-2289): preserve unknown status until availability is checked and Make

* fix(review-feedback-2289): Aggregate daily quality across supported markets and add kline

* fix(review-feedback-2289): Honor daily-source circuit breakers in quality selection and Make

* fix(review-feedback-2289): Scope market-overview quality by market and Do not select a news

* fix: align data capability with runtime routes

* fix: align data capability runtime coverage

* fix: align source and index capability routes

* fix: preserve runtime capability uncertainty

* fix: include US index capability routes

* fix: align realtime and monitor capabilities

* fix: remove unsupported Tushare index capability

* fix: align capabilities with runtime routes

* fix: model realtime and breaker routes

* fix: align US realtime request priority

* fix: align realtime circuit coverage

* fix: filter unavailable daily priorities

* fix: align US realtime capability claims

* fix: align executable US data routes
2026-08-29 17:19:18 +08:00
Elvis Wang
eb1333a678 feat: 支持通过 --stocks 一次性分析板块指数 (#2300)
* feat: add one-shot index analysis via --stocks with structured AnalysisTarget pipeline

* fix: render index data sources in aggregate reports

* fix: refresh index registry before --stocks parsing and filter indices on CN holidays

---------

Co-authored-by: zhulinsen <42829555+ZhuLinsen@users.noreply.github.com>
2026-08-28 21:56:48 +08:00
SPEC
ee635a35bf feat: 桌面端右上角增加更新入口 (#2274)
* feat: add a global desktop update entry in the shell

Expose a top-right DesktopUpdateIndicator in Electron runtime that
shares update state with Settings, without triggering extra background
checks or showing the control in ordinary browser WebUI.

Co-authored-by: SPEC <zt1y17@soton.ac.uk>

* fix: restore changelog header and honor desktop update deep links

Keep the Unreleased section intact, show download progress in the
shell tooltip, and open Settings version info from the search/hash.

Co-authored-by: SPEC <zt1y17@soton.ac.uk>

* fix: share desktop update checking state across header and settings

Keep a module-level update store so the shell indicator and settings
page disable together, and reject overlapping manual checks in the
Electron main process.

Co-authored-by: SPEC <zt1y17@soton.ac.uk>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-08-28 21:10:48 +08:00
zhulinsen
e75c4bb657 docs: link arXiv paper and add citation metadata (#2301) 2026-08-28 21:08:20 +08:00
sunkai174634
fb4735a105 feat: add Futu OpenD as optional HK realtime and fundamental data source (#2269)
* feat: add Futu OpenD as an optional HK realtime and fundamental data source

Add FutuFetcher and FutuFundamentalAdapter behind FUTU_OPEND_HOST/PORT,
register the settings in Config and config_registry so the Web settings
page can expose host, port and HK realtime priority, and route HK
realtime quotes through a configurable futu/longbridge/akshare/yfinance
order while keeping A-share priority untouched. Include offline tests
for the adapter, config schema and HK routing/fallback, plus docs and
CHANGELOG entries.

* fix: wire Futu fundamentals into HK pipeline and restore quote supplementation

- _fetch_offshore_fundamental_bundle() prefers the Futu fundamental
  adapter for HK when FUTU_OPEND_HOST is configured, and falls back to
  yfinance when Futu is absent or returns no usable content.
- HK realtime priority loop now supplements missing quote fields
  (volume_ratio / turnover_rate / pe/pb / market cap) from later
  configured sources instead of returning after the first non-empty
  quote, matching the US path's _supplement_quote behavior.
- capital_flow / boards blocks are filled from the Futu bundle for HK
  instead of being hard-coded not_supported; status and missing_fields
  aggregation updated accordingly.
- Add regression tests for partial-quote supplementation and Futu
  fundamental bundle routing/fallback.

* test: expect boards block ok when bundle provides belong_boards

The Futu integration made the offshore boards block data-driven instead
of hard-coded not_supported; update the existing US/HK fundamental
context test to match (belong_boards from the bundle now surface as an
ok boards block).

* fix: preserve HK fallback_from metadata and normalize Futu quote timestamps

- HK realtime priority loop now records the failed preferred source token
  and passes it as fallback_from when a later source takes over, so the
  pipeline and analysis context can mark the quote as degraded.
- Futu snapshot update_time is a naive Beijing-time (UTC+8) string; attach
  the +08:00 offset before storing provider_timestamp so stale_seconds /
  is_stale / provider_timestamp freshness semantics are correct instead of
  being parsed as UTC.
- Add regression tests for fallback_from propagation and timestamp
  normalization.

* fix: normalize Futu belong_boards to name/type/code contract

OpenD owner_plate returns plate_code / plate_name / plate_type, but DSA
downstream consumers (notification, extract_board_detail_fields, market
structure) only read name/type/code. Map the fields in
FutuFundamentalAdapter._boards so HK Futu boards are actually consumed
instead of silently dropped, and add regression tests including an
end-to-end check through extract_board_detail_fields.

* fix: merge yfinance bundle when Futu fundamental returns partial blocks

Futu partial success (e.g. statements failed but static info worked) used
to short-circuit the whole bundle, silently dropping the growth/earnings
that the existing yfinance path could still provide. Now, when Futu
returns content but is missing growth or earnings, fetch the yfinance
bundle within the remaining budget and merge the missing blocks
(growth/earnings/institution/capital_flow/belong_boards), keeping
Futu-preferred values where both exist. Add regression test for the
partial-success merge path.

* fix: use field-level checks when deciding Futu-vs-yfinance growth/earnings

The previous merge condition only checked dict truthiness, so a truthy
growth/earnings shell (all-None core values or metadata-only keys such
as report_date/period/currency) would skip the yfinance supplement and
silently downgrade existing HK fundamentals. Add _earnings_block_has_values
(a core numeric field or a populated dividend is required) and reuse the
existing _has_meaningful_payload for growth; both the missing_core check
and the merge loop now use these. Add regression test for the
all-None-shell scenario.

* fix: fill HK fundamental field gaps from yfinance instead of block-level checks

Block-level meaningful checks still skipped the yfinance supplement when
Futu hit only part of the growth/earnings fields (e.g. revenue_yoy but
None net_profit_yoy, or earnings with only basic_eps), silently dropping
fields the main branch used to provide. Replace the missing_core decision
with a per-field gap list (growth: revenue_yoy/net_profit_yoy/gross_margin;
earnings.financial_report: revenue/net_profit_parent/basic_eps/gross_profit)
and make the merge field-level: keep Futu values, fill each missing field
from yfinance. Add regression tests for partial-hit and all-None shells.

* fix: normalize Futu dividends to the repo contract and treat dividend gaps as supplement triggers

Futu OpenD dividend_list carries raw fields (statement/ex_date/record_date)
which the notification/data_processing market-structure consumers do not
read; the repo contract is ttm_cash_dividend_per_share,
ttm_dividend_yield_pct and events[].cash_dividend_per_share /
ex_dividend_date / event_date. Normalize events in
FutuFundamentalAdapter._dividends_and_splits, compute TTM count/cash and
yield from the latest quote, and teach _field_gaps/_merge_bundles to treat
a dividend block that does not satisfy the contract as a gap so yfinance
supplements it. Also dedupe FUTU_OPEND_HOST/PORT in full-guide_EN.

* fix: read dividend yield price from UnifiedRealtimeQuote objects

FutuFetcher.get_realtime_quote returns a UnifiedRealtimeQuote dataclass,
not a dict, so the yield branch in _dividends_and_splits that guarded on
isinstance(quote, dict) never ran on the live Futu path, silently dropping
ttm_dividend_yield_pct while the contract check considered the dividend
block complete. Read price via getattr(quote, 'price', None) and keep the
dict fallback for other fetchers; add a regression test driving the real
UnifiedRealtimeQuote shape.

* fix: treat dividend blocks with TTM cash but no yield as supplement gaps

The repo contract consumes ttm_cash_dividend_per_share and
ttm_dividend_yield_pct together. When the Futu dividend path has events
and TTM cash but the extra realtime price snapshot failed (quote None /
no price), ttm_dividend_yield_pct cannot be computed and the block was
previously treated as complete, so yfinance was never consulted and the
notification rendered the yield as N/A.

_dividend_contract_has_values() now requires the paired yield whenever
TTM cash is present, so _field_gaps() triggers the yfinance supplement
and _merge_bundles() replaces the incomplete dividend block.

Add regression tests for the adapter-level gap shape (quote unavailable
leaves no yield) and the manager-level supplement path (Futu cash
without yield pulls yfinance and fills the yield).

* fix: skip unconfigured Futu in HK realtime routing

When FUTU_OPEND_HOST is not configured, the HK realtime priority loop
used to still attempt the futu source, record it as the failed primary,
and attach fallback_from='futu' to a successful quote from the next
enabled source (longbridge/akshare/yfinance). Consumers then wrongly
treated an enabled source's first success as degraded fallback data,
contradicting the documented contract that Futu only participates when
OpenD is configured.

The HK loop now checks FutuFetcher.has_configured_endpoint() once and
skips the futu token entirely when it is disabled, so no fallback_from
is written. Existing configured-Futu routing tests explicitly patch the
endpoint check; a new regression test asserts an unconfigured Futu is
never called and the enriched quote carries fallback_from=None.

* fix: release cached HK Futu fundamental fetcher in DataFetcherManager.close()

The HK Futu fundamental path lazily creates and caches its own
FutuFetcher (an OpenQuoteContext-backed OpenD connection) on
_futu_fundamental_fetcher, but close() only released the TickFlow
fetcher and the default fetchers snapshot. Explicit close / reload
paths therefore left the OpenD connection hanging.

close() now takes the cached _futu_fundamental_fetcher, clears the
reference and calls its close() best-effort. A regression test injects
an observable fetcher into _futu_fundamental_fetcher and asserts
close() invokes it and clears the attribute.

---------

Co-authored-by: BayMax local review <baymax-local@invalid>
2026-08-25 22:54:01 +08:00
Gach-Coder
f92ad2e7d4 feat:分词模块 (#2272) 2026-08-25 22:39:11 +08:00
Elvis Wang
1b42907605 feat: 扩展 A 股指数注册表与 CSI provider 路由 (#2267)
* feat: add JSON-backed index registry

* fix: address index registry review feedback

* fix: validate raw duplicate index aliases

* fix: converge CSI task identities and registry fallback

* docs: align index identity terminology
2026-08-25 22:32:04 +08:00
zhulinsen
262cf5d5de ci: skip heavy checks for docs-only changes (#2255)
* ci: skip heavy checks for docs-only changes

* test: cover docs-only CI path filters

* fix: preserve CI checks for contract assets

* fix: preserve CI checks for license notices
2026-08-25 22:03:03 +08:00
zhulinsen
e45d5ce202 fix: 修复 Linux 分享图中文缺失 (#2270)
* fix: render CJK text in Linux share images

* fix(review-feedback-2270): 补到 PR 附件或评论里,和本次渲染修复说明保持一致

* fix: preserve report language contract in share images
2026-08-24 23:20:56 +08:00
wvyan
5bb3139209 fix: isolate runtime scheduler with portable watchdog (#2219)
* fix: isolate runtime scheduler with portable watchdog

* fix: harden runtime scheduler watchdog shutdown

* fix: prevent scheduled analysis after shutdown

* fix: fence scheduler shutdown and harden watchdog

---------

Co-authored-by: wvyan <13724623+wvyan@users.noreply.github.com>
2026-08-24 22:43:46 +08:00
zhulinsen
9ab79b8299 docs: prepare v3.31.0 release (#2260)
* docs: prepare v3.31.0 release

* fix(review-feedback-2260): add the v3
2026-08-23 22:32:59 +08:00
Elvis Wang
55ad446855 feat: 增加 A 股指数多数据源 fallback 路由 (#2258)
* feat: add A-share index fallback routing

* docs: clarify index priority configuration scope
2026-08-23 21:42:07 +08:00
青玉案
d0e66a1dc3 feat: 新闻检索为空时在报告中如实标注 (#2229)
* feat: 新闻检索为空时在报告中如实标注

消息面章节此前是「有内容才渲染」,检索一条没拿到时整段直接消失,
读报告的人无从判断是确实没新闻,还是检索静默失败了(搜索源限流、
未配置可用渠道等)。这把「抓取失败」呈现成了「确实没有新闻」。

- src/analyzer.py: AnalysisResult 新增 news_result_count,默认 None
- src/core/pipeline.py: 把 Step 4 已算好的计数交给结果对象
  (此前只进了 diagnostic context snapshot,报告层拿不到)
- src/notification.py: news_lines 为空且计数为 0 时,渲染明确提示,
  并说明结论未纳入新闻维度证据
- tests: 新增 5 条用例,含两条负例——计数为 None 时不得报警
  (那是未配置搜索渠道,不是失败)、拿到新闻时行为与改动前一致

不触碰任何检索路径,纯展示层增量。

* fix: 把新闻缺失提示放进真实渲染路径,并独立于模型输出判定

按 review 三条意见修正:

P1-1 提示只存在于 generate_daily_report,而正常流程从不调用它——
_send_single_stock_notification 与聚合报告走的是 dashboard / brief /
single_stock。原实现对所有标准 REPORT_TYPE 都不生效。
改为抽出共享判定 _empty_news_disclosure,四个渲染器统一接入。

P2 检索零命中但模型按 schema 写出了 market_sentiment / hot_topics 时,
原 elif 分支被跳过,报告会展示模型生成的情绪判断却隐瞒无新闻证据。
改为独立判定 news_result_count == 0,与模型是否产出文字无关。

P1-2 补 docs/CHANGELOG.md [Unreleased] 条目,并在
docs/data-source-stability.md 的「用户可见提示建议」一节记录该行为,
含 None / 0 / >0 三态语义表。

测试从 5 条增至 10 条,新增覆盖 dashboard、brief、single_stock 三个真实
渲染器,以及「模型有输出但检索为空」这一最糟组合。39 passed

* fix: 把新闻零命中披露覆盖到模板链路与企业微信入口

按 review 指出的 blocker 修正。此前只接了字符串拼接分支,遗漏两类活路径:

1. REPORT_RENDERER_ENABLED=true 时,generate_dashboard_report /
   generate_brief_report / generate_wechat_dashboard 会先 return render(...),
   模板链路一路不渲染披露;
2. generate_wechat_dashboard 的非模板 fallback 从未接入,而 pipeline 在
   企业微信非 brief 场景会直接调用它。

后果是同一份分析结果在部分渠道披露、在另一些渠道沉默。

改法不再逐点打补丁,而是抽出单一事实来源:

- 新增 src/services/empty_news.py 持有判定与中英文案
- src/notification.py 的 _empty_news_disclosure 改为委托该模块
- src/services/report_renderer.py 为每条结果预计算 empty_news_disclosure,
  三个平台模板共用
- templates/report_markdown.j2 / report_brief.j2 / report_wechat.j2 各加渲染分支
- generate_wechat_dashboard 的 fallback 正文接入披露

新增 6 条回归测试:模板链路三个平台各一条、企业微信入口一条,
外加两条负例(未执行检索时模板与企业微信均不得提示)。

本文件测试 10 → 16 全过;全量 5824 passed,9 个既有失败与本 PR 无关
(干净 main 上同样失败,属测试顺序依赖)。

* fix: 修正计数源头的两处缺口(自查发现)

按 review 的 merge-base..HEAD 方法自查全链路,发现此前几轮都只盯着渲染出口,
从未核对计数源头,而源头本身在两条路径上是错的:

1. src/core/pipeline.py: news_result_count 只在 intel_results 非空时赋值,
   搜索服务整体失败(正是所有搜索源限流全挂的场景)时停留在 None,
   语义为「未执行检索」,于是本 PR 想解决的头号场景反而不提示。
   改为检索一发起即置 0。

2. _analyze_with_agent: Agent 模式自行调用 search_stock_news 完成检索,
   却从不回写计数,该路径下零命中永远静默。改为按检索结果回写 0 或实际条数。

渲染层再周全,源头数据不对则全部落空。

新增 2 条测试锁住这两处语义(18 passed,此前 16)。
全量 5826 passed,9 个既有失败与本 PR 无关。

* fix: disclose missing news search configuration

* chore: remove unrelated agent guidance

* test: run all empty news tests directly

* fix: preserve empty news disclosure across reports

* fix: 让 Agent 模式的新闻披露跟随实际消费的证据

原问题:agent_arch=multi 等受支持的 Agent 配置下,报告可能声称「未纳入新闻
面证据」而分析其实用了新闻,或反过来该提示而不提示。

根因:news_result_count 取自 executor.run() 结束后为持久化情报补打的一次
search_stock_news()。真实情报由 IntelAgent 通过 search_comprehensive_intel
取得,两者不等价,因此披露与真实证据链可能相反。

修复点:新增 src/agent/news_evidence.py,以运行期证据作用域收集 Agent 搜索
工具的真实返回条数;搜索渠道不可用为 None(未执行检索),可用则从 0 起步、
拿到多少算多少。pipeline 在 executor.run() 前后开启并读取该作用域,事后的
持久化补查不再回写计数。

回归风险:工具在 ThreadPoolExecutor 中执行,runner.py 以
contextvars.copy_context() 提交,故作用域中必须是可变累加器对象,换成不可变
值会让父线程读不到;已加回归测试锁住该机制。原 test_agent_path_records_count
断言的正是被修复的错误行为,已替换为反向断言。

Refs #2225

* fix: 让新闻披露以实际证据为准而非搜索命中数

原问题:本地已落库的资讯池或社交情绪进入 news_context 参与分析后,报告仍可能
声称「未配置搜索渠道,本次分析未纳入新闻面证据」或「零命中」。

根因:news_context 由三路来源拼成——实时检索、社交情绪(美股)、本地资讯池,
但只有实时检索会更新 news_result_count。披露断言的是「结论有没有用到新闻面
证据」,而计数只是「搜索命中了几条」,两者是不同命题,后两路参与时必然失真。

修复点:AnalysisResult 新增 news_evidence_present,由 news_context 是否非空
得出,pipeline 两条路径共用 src/services/empty_news.news_evidence_present()
这一个判定函数。披露改为先看有无证据;确无证据时才用计数解释原因
(None=未配置渠道,0=检索零命中)。历史重建同步恢复该字段。

回归风险:旧记录没有该字段,按计数回退推断,与该记录当时的报告表现一致,不会
追溯改变旧报告;已有用例锁住。review 只点名了本地资讯池,社交情绪属同一缺陷类,
本次一并修复并加测试。另加源码断言:任一 pipeline 路径改回只传计数即失败。

Refs #2225

* fix: 按来源登记新闻证据,不让零命中占位文本冒充证据

原问题:普通分析链路在「搜索已执行但一条证据都没拿到」时,报告不再显示零命中
披露——正是本 PR 要修的核心场景,反而比改动前更差。

根因:src/search_service.py 的 format_intel_report() 即使所有维度失败或为空,
也会输出「【XX 情报搜索结果】」标题和每个维度的「未找到相关信息」占位文本,
整段永远非空。上一版把拼好的 news_context 整段交给 news_evidence_present()
判定,于是 news_result_count == 0 时 evidence 被翻成 true,披露被吞掉,错误
状态还会经 to_dict() 持久化,继续影响历史、详情 API 与 Web。

修复点:判定改为按来源逐个登记——实时检索的真实命中数、社交情绪内容、本地
资讯池内容,任一为真才算有证据;两条 pipeline 路径都不再传拼好的整段。
news_evidence_present() 的契约随之改为接收各来源,并在文档串里写明为什么不能
传整段。

回归风险:新增反例用真实的 format_intel_report() 产出占位文本(不用 mock),
断言其不得被判成证据、且报告必须出现零命中披露。另有源码断言:谁把整段
news_context 交回判定函数即失败。上一版两条测试实际在保护该缺陷(一条名为
「任何非空 context 都算证据」,一条要求必须传入 news_context),已一并纠正。

Refs #2225

---------

Co-authored-by: Mach-Chan <zz-b240@zz-b240deMacBook-Air.local>
2026-08-22 21:27:04 +08:00
Gach-Coder
f6b719d1fe 股票实体解析增强 (#2245)
Co-authored-by: zhulinsen <42829555+ZhuLinsen@users.noreply.github.com>
2026-08-22 21:23:55 +08:00
Elvis Wang
cd1fd2229c feat: 数据库 stock_daily 新增 canonical_id 列与双写(#2207) (#2251)
* feat: add canonical_id column and dual-write for stock daily data (#2207)

* fix: unify bare index codes to index canonical_id via matched_index (review OR-COR-4f9ffc38)

* docs: update docstrings for index-aware derivation and fix orphaned inspection-failure test (review OR-COR-4f9ffc38)
2026-08-22 21:21:15 +08:00
zhulinsen
dfff177cef fix: 记录大盘复盘实际生成后端 (#2241)
* fix: report actual market review backend

* fix: preserve exhausted fallback diagnostics

* fix(review-feedback-2241): update the attempt loop to retain the current model even when the and

* fix: preserve resolved market review provider

* fix(review-feedback-2241): Resolve aliased LiteLLM providers before recording

* fix(review-feedback-2241): preserve the failed LiteLLM route's resolved provider

* fix(review-feedback-2241): preserve template fallback for primary LiteLLM exhaustion and Use the

* fix(review-feedback-2241): preserve the fallback provider for unqualified response models

* fix(review-feedback-2241): Derive exhausted aliases from the router's last deployment and

* fix(review-feedback-2241): preserve the provider for gateway-owned slash model IDs

* fix(review-feedback-2241): preserve the explicit provider for unqualified failure models

* fix(review-feedback-2241): fix several earlier route-alias and router-failure diagnostics gaps,

* fix(review-feedback-2241): 评审结论 - 代码检查 :当前整个 PR 仍有 1 个未关闭的高置信度代码 blocker。最新复核摘要:Full

* fix(review-feedback-2241): 补一组成功路径回归:同一 alias 下首个 deployment 为 openai/~...、后续 deployment 为直连

* fix(review-feedback-2241): add a regression test that injects an analyzer exposing only is

* fix(review-feedback-2241): add a regression that asserts the legacy injected-analyzer path does

* fix(review-feedback-2241): 评审结论 - 代码检查 :当前整个 PR 仍有 1 个未关闭的高置信度代码 blocker。最新复核摘要:基于当前 HEAD

* fix(review-feedback-2241): 评审结论 - 代码检查 :当前整个 PR 仍有 1 个未关闭的高置信度代码 blocker。最新复核摘要:基于完整

* fix(review-feedback-2241): add a regression test that drives call litellm
2026-08-22 21:20:59 +08:00
zhulinsen
7fd376f669 fix: sanitize market review history summaries (#2242) 2026-08-22 14:58:20 +08:00
zhulinsen
96bc532dfc fix: restore persisted runtime schedules (#2231) 2026-08-19 23:46:49 +08:00
tseliangmin-star
5d14873f17 docs: add xAI Grok LiteLLM examples and Grok Bot integration notes (#2228)
* docs: add xAI Grok LiteLLM examples and Grok Bot integration notes

* docs: link Grok Bot notes from SKILL, INDEX, and LiteLLM YAML

* docs: add Grok Bot index/openclaw pointers and restore SKILL analyzer_service path

* docs: add Grok Bot pointers in INDEX_EN and openclaw guide

* docs: prefer async Grok Bot analysis and poll market-review status

* docs: prefer async Grok Bot analysis and poll market-review status
2026-08-19 23:39:48 +08:00
mrlonely
5d3e4e374a fix: count DingTalk webhook as notification channel (#2230) 2026-08-19 22:52:10 +08:00
青玉案
b740beea2a fix: 公共 SearXNG 实例发现默认关闭 (#2226)
* fix: 公共 SearXNG 实例发现默认关闭

公共实例普遍存在限流(429)、下线(503)或未开启 JSON 输出的情况,
默认开启会让未配置任何搜索 key 的用户每次分析多耗 30~60 秒,
且新闻面最终为空却不在报告中提示(详见 #2225 的实测数据)。

- .env.example: 默认值改为 false,并说明原因与推荐做法
- docs/full-guide.md、docs/full-guide_EN.md: 同步默认值与说明
- tests: 放宽段落定位断言以适配新默认值,并新增一条测试固化该默认值

* fix: 公共 SearXNG 实例发现改在运行时层面默认关闭

按 review 意见修正:仅改 .env.example 只影响复制新模板的用户,
已有安装与 GitHub Actions(未配置 Variable/Secret 时变量为空)
仍会走 default=True 的运行时分支,继续承受 30~60 秒失败重试。

- src/config.py: parse_env_bool 的 default 由 True 改为 False(真正生效处)
- src/core/config_registry.py: 示例顺序调整为 false 优先
- .github/workflows/00-daily-analysis.yml: 变量未设置时诊断显示「默认关闭」,
  与运行时行为保持一致,不再误报为已开启
- tests: 新增三条回归测试,分别锁住运行时默认值、config.py 源码中的
  default=False、以及工作流诊断文案
- docs/CHANGELOG.md: 在 [Unreleased] 追加条目

显式设为 true 的用户行为不变。27 passed

* fix: align SearXNG public instance defaults

* fix: complete SearXNG opt-in defaults

---------

Co-authored-by: Mach-Chan <zz-b240@zz-b240deMacBook-Air.local>
Co-authored-by: ZhuLinsen <zhuls97@163.com>
2026-08-19 21:59:10 +08:00
lmx-2077
cfd6b0a5fb feat(agent): per-category tool timeouts with graceful degradation (#1890) (#2134) 2026-08-17 21:41:19 +08:00
zhulinsen
294210883c fix: 分析后无报告生成 (#2201) (#2211)
* fix(issue-2201): [bug]-分析后无报告生成

* fix(review-feedback-2211): Keep the TTM cutoff anchored to the as-of date and Propagate report

* fix(review-feedback-2211): Move the empty-watchlist check before the trading-day return

* fix(review-feedback-2211): preserve the failure status, but defer returning until after the and

* fix(review-feedback-2211): preserve the failure result, but defer returning until after the

* fix(review-feedback-2211): Handle failures in the market-review-only CLI branch
2026-08-14 22:08:51 +08:00
zhulinsen
3b98aa1d77 docs: update AIHubMix referral links to InferEra (#2182) 2026-08-10 21:18:03 +08:00
zhulinsen
396d43a4c7 docs: prepare v3.30.0 release (#2180)
* docs: prepare v3.30.0 release

* docs: clarify default Xiaohongshu handle
2026-08-09 22:05:16 +08:00
zhulinsen
071c5aa3c7 fix: stabilize Xiaohongshu share image caption (#2179) 2026-08-09 21:40:13 +08:00
Carl_Wu
40b8c6c3cd fix: 修复首页移动端个股栏触摸滚动 (#2171)
* fix: restore mobile stock sidebar scrolling

* fix: keep history stock bar shrinkable

---------

Co-authored-by: zhulinsen <42829555+ZhuLinsen@users.noreply.github.com>
2026-08-09 19:43:04 +08:00
s4kura
5698068fec fix: split level-one headings without recursion (#2176)
* fix: split level-one headings without recursion

* fix: avoid duplicate content after chunk flush
2026-08-09 19:13:57 +08:00
zhulinsen
46d5bf3472 fix: 恢复桌面端报告分享图 (#2169)
* fix: enable desktop report share images

* fix: include default share image branding

* fix(review-feedback-2169): Keep custom branding paired with its QR code

* fix: show default share image nickname

* fix(review-feedback-2169): Document the atomic branding fallback

* fix(review-feedback-2169): Remove the hard-coded social account default

* fix(review-feedback-2169): Remove the bundled account QR fallback

* fix(review-feedback-2169): update docs/share-images

* fix(review-feedback-2169): [Verification blocker] 当前 Head 的阻断型 CI

* fix(review-feedback-2169): 补一条回归:用包含超长 fenced-code 行和超长原始 URL 的完整/通用报告分别走 build share image html
2026-08-09 13:53:00 +08:00
lyl2104626211
ed848da6f0 feat: persist Agent Chat Skill selection by session (#2160)
* feat: persist Agent Chat skill selection by session

* test: satisfy ChatPage mock immutability lint

* fix: preserve implicit Skill state for legacy sessions

* fix: make top-level Skill selection authoritative

* fix: preserve session skills for invalid requests

---------

Co-authored-by: zhulinsen <42829555+ZhuLinsen@users.noreply.github.com>
2026-08-05 23:12:42 +08:00
zhulinsen
ae19329d66 fix: stabilize watchlist details and workspace state (#2126) 2026-08-05 22:45:12 +08:00