Commit Graph
100 Commits
Author SHA1 Message Date
Haozhe Wu 2a5557edb6 chore(release): 0.1.10 — version sync + wheel packaging + Docker weasyprint libs (#271)
* chore(release): 0.1.10 — version sync, wheel packaging, Docker weasyprint libs

Release-prep for 0.1.10 (bundles the global data layer + everything since 0.1.9):

- version 0.1.9 -> 0.1.10 synced across all surfaces: pyproject, frontend
  package.json/lock, sidebar footer + i18n, SKILL.md, Dockerfile LABEL. REST
  API + MCP server now derive the version from cli._version (were hardcoded
  "5.0.0" / unset).
- pyproject package-data ships skill example scripts (skills/**/*.py) so the
  wheel + sdist include them; fastmcp floor -> 2.14.0 (first release where the
  MCP version= kwarg applies).
- Dockerfile installs weasyprint native libs (Pango/HarfBuzz/Fontconfig/Cairo/
  gdk-pixbuf + DejaVu) so PDF reports render in the image; .dockerignore *.md
  -> /*.md (root-anchored, skill markdown still ships).
- README news (5 locales): 06-19 reframed as the v0.1.10 release; 06-19/06-18
  trimmed.

Verified: clean-room pip install imports all new loaders/tools; wheel + sdist
carry the eastmoney/sec-edgar skills incl. UTF-8 references + scripts; 4167
backend tests pass (weasyprint PDF path covered on CI).

* docs(release): credit v0.1.10 cycle contributors across READMEs

Refresh the Contributors section in all 5 READMEs from the v0.1.9 cycle to
the v0.1.10 cycle (PRs #162–#260): @Hinotoi-agent, @mvanhorn, @gyx09212214-prog,
@BillDin, @Robin1987China, @LemonCANDY42, @zwrong, @forge-builder, @skloxo,
@LeeCQiang, @KaiLuettmann, @ngoanpv, @ShahNewazKhan, @sambazhu, @bhlt, @octo-patch.
2026-06-19 20:54:20 +08:00
Haozhe Wu adfbc82303 feat(data): global data layer — 8 new sources + 18 read-only data tools (#270)
- 8 loaders (eastmoney/sina/stooq/yahoo free + finnhub/alphavantage/tiingo/fmp
  optional-key); registry now 18 sources, fallback chains re-ordered by IP-ban
  risk behind a shared throttled HTTP gate
- 18 read-only data tools (fund flow, dragon-tiger, northbound, margin, block
  trades, shareholder count, lockup, sector, research, news, SEC filings,
  financial statements, options chain, stock profile, screener, search, FRED
  macro, iwencai), exposed over MCP
- 3 shared clients (eastmoney/yahoo/sec_edgar); consolidated data-routing skill
  + per-source eastmoney/sec-edgar skills; read_file skill-ref resolution fix
- docs: 5 READMEs (Data Sources & Smart Fallback section + counts + 06-19 news),
  .env.example, SKILL.md

1200 tests green; live e2e LLM-routing + data smoke verified.
2026-06-19 18:19:12 +08:00
Haozhe Wu ca9ded74fd docs: remove exposed scam discord invite code 2026-06-19 00:08:09 +08:00
Haozhe Wu 84bbf871c5 docs: add June 18 news (Research Autopilot Phase 1 + local Data Bridge loader + Discord security notice) 2026-06-18 23:54:02 +08:00
Haozhe Wu 0d0ef439bb chore: finish proposal_id hardening + document MCP initTimeout (#264)
Completes work the 2026-06-18 self-merged batch left half-applied:
- Tighten the mandate proposal_id relay/preview parsers in api_server.py
  and cli/_legacy.py to the strict mp_[0-9a-f]{32} format already enforced
  by commit.py (#256), so the relay and the commit gate agree on format.
- Document the MCP initTimeout config key (shipped in #263) in README and
  wire its base-default passthrough in config/loader.py.
- Add the registered pytest.mark.unit marker to test_mandate_commit_security.py.
2026-06-18 23:36:32 +08:00
Haozhe Wu e7a95106da fix(autopilot,loaders): repair zero-review runtime defects in #260 + #252 (#266)
#260 Research Autopilot shipped two reproduced runtime-breaking bugs:
- run_research_autopilot never received the host session_id (it read only
  kwargs, which the LLM cannot know), so every call errored 'session_id is
  required'. Inject default_session_id via build_registry like the goal tools.
- generate_backtest_config returned a run_dir under ~/.vibe-trading/runs,
  which safe_run_dir rejected, breaking the advertised write_file -> backtest
  handoff. Add ~/.vibe-trading/runs to the allowed run roots.
Also validate the derived backtest source against VALID_SOURCES (fall back to
'auto' with a warning) and document the goal-replace semantics.

#252 local loader silently emptied tz-aware CSV/Parquet inputs (a tz-naive vs
tz-aware Timestamp comparison raised TypeError that was swallowed). Normalize
the timezone in _normalize_columns.

Adds registry-level + end-to-end tests for both autopilot tools and a tz
regression test for the loader.
2026-06-18 23:36:27 +08:00
Haozhe Wu 83a33dfef1 docs(security): document official Discord + wallet-impersonation warning
Adds an 'Official channels & impersonation' section pointing to the only
official Discord (HKUDS server) and warning that any wallet 'verification'
prompt is a scam, after the 2026-06-18 impostor-Discord incident.
2026-06-18 22:33:34 +08:00
Haozhe Wu b20af90b8d docs: point community Discord to the official HKUDS server
Re-add the Discord badge (5 READMEs) and an issue-template contact removed
in 8346def, now pointing at the org-controlled HKUDS community server
(discord.gg/6TdQnT5xcF, guild 1296348098003734629, ~1.9k members) — the same
server linked from the HKUDS org profile and LightRAG — instead of the
compromised invite. Keeps GitHub Discussions as a second contact.
2026-06-18 22:18:36 +08:00
Haozhe Wu 8346def2aa fix(security): remove compromised Discord invite from README + issue template
The discord.gg/2vDYc2w5 invite (in all 5 READMEs and the issue-template
contact links) resolves to a "Vibe-Trading" Discord server we no longer
control. It runs a fake Collab.Land wallet "verification" via
collab.layers-land.com — a throwaway domain registered 2026-06-18 — that
phishes users' crypto wallets. Remove every public pointer to it and replace
the issue-template community link with GitHub Discussions.
2026-06-18 22:13:12 +08:00
Haozhe Wu 69545dbabd fix(live): extend Robinhood OAuth init timeout (#263) 2026-06-18 21:42:28 +08:00
Haozhe Wu d6bf209303 fix(provider): parse DeepSeek DSML tool calls
Fixes #261.
2026-06-18 21:38:57 +08:00
Haozhe Wu 1387b213d6 docs: update June 16 and 17 news 2026-06-17 01:30:35 +08:00
Haozhe Wu 7aa6b1d960 test: cover assistant-prefill handoff paths (#251) 2026-06-17 01:19:52 +08:00
Haozhe Wu ecbbd72eff fix: make harmonic backend optional (#250) 2026-06-17 01:11:51 +08:00
Haozhe Wu f63849e55e docs: news for 06-15 — web_search multi-engine resilience + Web UI run-continuity (#229) 2026-06-15 18:38:37 +08:00
Haozhe Wu f0ba648b1f fix(agent): make Stop responsive mid-stream and between tools (#229) (#235)
Symptom 2 of #229: clicking Stop during a run did nothing, or only took
effect after a long delay. The cancel signal (_cancelled bool) was polled
only at the top of the ReAct iteration loop, so a Stop pressed while the
agent was mid-LLM-stream or executing tools was ignored until the entire
current iteration — the whole streamed answer plus all its tool calls —
finished and the next iteration check ran.

Add cooperative cancel checkpoints at the granularity the user actually
waits on:

- Switch _cancelled to a threading.Event (_cancel_event) so the flag is
  safe to poll from the streaming thread.
- Thread a should_cancel predicate into ChatLLM.stream_chat; the per-chunk
  loop breaks as soon as it is set, so a live stream stops at the next
  token instead of running to completion.
- After the stream returns, check the flag before processing tool calls:
  a cancel during streaming ends the run immediately and discards the
  partial turn (no tools run).
- Check the flag at the top of _process_tool_calls and between tool batches
  in _batch_execute, so a multi-tool turn stops after the current tool
  rather than launching the rest.

Limitation (documented in the PR): a connection that hangs before emitting
any chunk leaves the worker blocked in a socket read, which Python cannot
interrupt cooperatively from another thread; that case stays bounded by the
existing client timeout (TIMEOUT_SECONDS). Cancel now lands promptly in all
the normal "working" states (tokens streaming, tools running).

Tests: chat-level should_cancel breaks the chunk loop (and absence is a
no-op); loop-level cancel mid-stream ends the run as cancelled and skips
tool execution. Full suite: 3199 passed, 2 skipped.
2026-06-15 17:41:43 +08:00
Haozhe Wu 9b9e9f50a0 fix(web): reconnect SSE when returning to a running session (#229) (#234)
Symptom 1 of #229: navigating away from the chat page and back often made
the agent "stop working". The page run is server-side and the frontend only
subscribes via SSE; the unmount cleanup tore down that stream, but the
navigation effect only (re)subscribed when the URL session differed from the
store session. Returning to the SAME session hit neither branch, so SSE was
never reopened — in-flight events stopped arriving and the run looked frozen
until the 90s safety timeout flipped it to idle.

The backend already buffers events per session and replays them on connect
(GET /sessions/{id}/events?replay=active), and setupSSE already requests it;
the only defect was the missing re-subscribe on same-session remount.

Add that branch: re-hydrate like a reload — reset the transient streaming
view first (so replay=active rebuilds the in-flight turn from the ring buffer
instead of duplicating deltas onto the preserved text), refresh committed
history (covers an attempt that finished while away), then re-subscribe.

Symptom 2 (Stop ineffective during a hung/long turn) is cooperative-cancel
granularity in the agent core and is tracked separately.

Verified: tsc clean, frontend build passes, existing store/useSSE/Runtime
tests green (42).
2026-06-15 17:40:56 +08:00
Haozhe Wu 541c4e5d8c ci: bump first-party actions to Node 24 (checkout/setup-python/setup-node v6) (#233)
GitHub forces Node 20 actions onto the Node 24 runtime starting 2026-06-16
and removes Node 20 from runners on 2026-09-16. Bump the three first-party
actions to their latest Node-24-native majors across all workflows:

- actions/checkout v4 -> v6
- actions/setup-python v5 -> v6
- actions/setup-node v4 -> v6

Inputs are unchanged (node-version "20" is the frontend JS runtime, not the
action runtime). Third-party cloudflare/wrangler-action is out of scope.
2026-06-15 17:08:04 +08:00
Haozhe Wu 04480e8a03 fix(tools): make web_search resilient via multi-engine fallback (#232)
web_search relied on ddgs's default 'auto' backend, which leads with
DuckDuckGo — heavily rate-limited from cloud/shared IPs. When DDG
threw, the whole tool returned ❌ (issue #231) even though Google/
Bing/Brave/Mojeek/Yahoo were reachable.

- Pass an explicit ordered backend list so ddgs falls through a
  throttled engine to the next; override via VIBE_TRADING_SEARCH_BACKENDS
- Retry transient failures with linear backoff (3 attempts)
- Treat "No results found" as an ok+empty envelope, not an error
- On persistent failure, return an actionable message (retry / set
  backend env / use read_url) instead of a bare exception string
- Report the backend list used in the ok envelope

Tests: new tests/test_web_search_tool.py (6 cases, mocked ddgs) cover
multi-backend pass-through, env override, retry-then-succeed,
no-results, persistent-failure message, and max_results cap.
2026-06-15 17:01:32 +08:00
Haozhe Wu 59d98a741b docs: news for 06-14 — per-run token usage + progressive Run Detail charts 2026-06-14 23:03:30 +08:00
Haozhe WuandLanre Shittu ccfe50fff2 fix(loaders): land RSSHub event/sentiment provider with explicit feed config
Builds on @Shizoqua's verified provider core (#201) and resolves the three
open-box blockers from review:

- No built-in feed catalogue: the shipped DEFAULT_FEEDS routes (/stock/notice,
  /stock/news) don't exist in RSSHub, so a default run scored every bar 0.0.
  Feeds are now declared explicitly in config (event_feeds = list of
  {name, route_template, event_type, code_style}); DEFAULT_FEEDS is empty, so
  there is nothing to silently rot as RSSHub routes evolve.
- Per-symbol code formatting: a feed declares a code_style
  (raw / bare / exchange_prefix), so dotted backtest codes (600519.SH) are
  rewritten to the form a route needs (e.g. SH600519 for xueqiu-style routes).
- No silent all-zero scores: each fetch failure is logged per feed/code, and
  query_events raises EventProviderError when every fetch failed (configured
  but fully unreachable) instead of returning an all-zero event_score that
  reads as "sentiment considered, no signal". A reachable feed that returned
  no items stays a legitimate empty result.

PIT discipline, defusedxml hardening, the pluggable scorer, and the
engine/runner wiring are otherwise unchanged. Adds 12 tests for the code
styles, config parsing/validation, and the loud-failure path.

Thanks @Shizoqua (#201) for the provider core.

Co-authored-by: Lanre Shittu <136805224+Shizoqua@users.noreply.github.com>
2026-06-14 06:50:02 +08:00
Haozhe Wuandskloxo f470947c81 feat(loaders,i18n): add baostock/tencent A-share sources + zh-CN frontend i18n
A-share data sources (free, no-auth, bypass eastmoney CDN IP blocks):
- baostock loader (TCP protocol) + tencent loader (HTTP), both wrapped with
  cached_loader_fetch like the mootdx/akshare loaders so they honor the opt-in
  local cache
- registered into the a_share fallback chain (tushare -> mootdx -> baostock ->
  tencent -> akshare); auto-detect default source stays tushare
- pyproject [ashare] extra for the optional baostock dependency

Frontend i18n (English default, sidebar toggle to Chinese, persisted):
- i18next + react-i18next + browser-languagedetector; first visit defaults to
  English (localStorage-only detection, no navigator sniff), the toggle persists
  the choice across reloads
- en / zh-CN locales at full key parity, including the Runtime page
- i18n initialized in the test setup so useTranslation resolves real strings;
  restored the exported METRIC_LABELS so formatters consumers/tests keep working

Thanks @skloxo (#217) for the i18n layer and the A-share data sources.

Co-authored-by: skloxo <skloxo@users.noreply.github.com>
2026-06-14 06:50:02 +08:00
Haozhe Wu 4207392433 docs: news for 06-13 — resume past session by ID from the CLI (#218) 2026-06-14 00:12:03 +08:00
Haozhe Wuandzwrong 4f70258d40 fix(trace): rebase trace offload onto current main
Co-authored-by: zwrong <1003912034@qq.com>
2026-06-13 01:21:24 +08:00
Haozhe Wu 4c4565a50f fix(web): clear partial stream text on provider retry 2026-06-13 01:02:00 +08:00
Haozhe Wu 65660e4087 docs: news for 06-12 — provider reliability overhaul (capability layer, provider doctor, streaming liveness) 2026-06-12 23:41:25 +08:00
Haozhe Wu fa2ad4b664 fix(providers): capability-gated payloads, explicit stream errors, liveness + provider doctor
A cluster of provider reports traced to one root: every OpenAI-compatible
provider shared a single shim that applied DeepSeek/Kimi/Gemini quirks
globally and silently swallowed stream failures.

- Provider capability layer (capabilities.py): reasoning capture/replay,
  Gemini thought signatures, Kimi User-Agent (version from package
  metadata), OpenRouter reasoning body — gated per provider, no
  cross-contamination
- Optional native DeepSeek adapter via VIBE_TRADING_DEEPSEEK_ADAPTER
  (auto/native/openai-compatible) + 'deepseek' extra (langchain-deepseek)
- ChatLLM.stream_chat: silent fallback to chat() removed; failures raise
  ProviderStreamError with redacted provider/model context; one retry for
  transient errors in AgentLoop + swarm worker (deterministic 4xx fail fast)
- Empty model response diagnosed as empty_model_response instead of
  'reached max iterations'
- reasoning_delta SSE event (throttled to 1/s, first chunk immediate) +
  frontend 'Reasoning…' indicator; reasoningActive reset on safety timeout
- SSE heartbeats carry no replay id; replay falls back to full buffer on
  unknown last-event-id; read-only tools get a hard timeout
  (VIBE_TRADING_TOOL_TIMEOUT_SECONDS) instead of hiding behind heartbeats
- 'vibe-trading provider doctor' prints redacted provider/model/package/
  proxy diagnostics; preflight includes the same hint
- kimi-k2.x temperature=1 requirement auto-applied; Kimi path verified
  end-to-end against the live API (kimi-k2.6 tool calls + strict
  multi-turn reasoning replay)

Refs #208 #203 #195 #204
2026-06-12 23:41:19 +08:00
Haozhe Wu c390aa8462 docs: news for 06-10/06-11 — swarm market-data hardening, Docker Ollama + persistence
Two entries across all 5 READMEs (3 visible + earlier rotated into
details): 06-11 swarm loader-backed get_market_data rollout (#198/#199/
#200, bare-ticker grounding, 48 tools) bundled with Docker state
persistence (#197); 06-10 host-side Ollama reachability (#196).
2026-06-11 13:34:12 +08:00
Haozhe Wu 2ba6ae2f0c fix(docker): persist user agent state across container rebuilds
All user-level agent state lives under ~/.vibe-trading in the container
(persistent memory, sessions.db search index, user-created skills,
shadow accounts, hypothesis registry, broker connector config), but
docker-compose only mounted volumes for web sessions and backtest runs.
Every `docker compose up --build` therefore recreated the container and
silently wiped the user's memory store (#197). Swarm run history and
uploads were lost the same way.

- Mount vibe-home -> /home/vibe/.vibe-trading, plus vibe-swarm-runs and
  vibe-uploads.
- Pre-create /home/vibe/.vibe-trading owned by the service user in the
  Dockerfile so the named volume inherits writable permissions (a named
  volume mounted onto a missing path is created root-owned and the
  non-root user cannot write).
- README: document what persists across updates.

Verified end-to-end: memory written via PersistentMemory in one
container is intact in a fresh container on the same volume; the
counterfactual run without the volume reproduces the wipe.

Fixes #197.
2026-06-11 13:17:04 +08:00
Haozhe Wu dfd71b4bc2 feat(swarm): ground bare US tickers mentioned in swarm prompts
Grounding previously only matched loader-suffixed symbols (NVDA.US),
so an investment_committee run on "long or short on NVDA" got no
pre-fetched ground-truth prices and workers fell back to ad-hoc
scripts (#198, fix direction 5).

Promote bare all-caps tokens to <TOKEN>.US under guards:
- 2-5 uppercase letters only, dotted compounds rejected on both sides
  (FOO.USDA promotes neither side; sentence-ending "NVDA." still works)
- stopword list for finance/English acronyms (ETF, CEO, GDP, USD,
  bare crypto symbols, index providers like MSCI, ...)
- suffixed matches are blanked before the bare scan, so BTC-USDT can
  never leak a bogus BTC.US
- promotions sort after explicit symbols, so explicit symbols win the
  max-symbols cap; the per-symbol fetch remains the final validator

Closes the last open item of #198.
2026-06-11 12:22:51 +08:00
Haozhe Wu d7d1654f32 fix(swarm): expose get_market_data across all market-data presets
PR #199 added the loader-backed get_market_data tool to
investment_committee and equity_research_team. Extend it to the 16
remaining workers (across 11 presets) whose skills include tushare,
yfinance, or okx-market, so every OHLCV-capable worker gets the
loader-first path and the worker prompt policy.

Add a regression test locking the convention: any preset worker with a
market-data skill must whitelist get_market_data.

Part of #198.
2026-06-11 12:14:15 +08:00
Haozhe Wu a80b4431bf fix(web): arm SSE safety timeout so a no-event turn can't hang forever
The streaming safety timeout guarded on lastEventRef.current being truthy, but
it starts at 0 and only real SSE events (not the keep-alive heartbeat) update
it. A turn whose very first event never arrives — e.g. the LLM provider hangs
before emitting a token — left it at 0, so the guard short-circuited and the UI
stayed on "Agent is working…" indefinitely. Arm the clock on entering the
streaming state so the timeout fires after sse_timeout and resets to idle with a
notice. Turns issue #195's hang into a recoverable timeout.
2026-06-09 20:33:24 +08:00
Haozhe Wu f735e4ca51 fix(backtest): make mootdx & futu first-class config sources
Two whitelists/maps had drifted as mootdx and futu loaders were added:

1. BacktestConfigSchema._VALID_SOURCES rejected an explicit
   source="mootdx"/"futu" even though both are registered loaders (and in
   FALLBACK_CHAINS), while src/tools/backtest_tool.py already allowed them.
   Fix: one canonical VALID_SOURCES in backtest.loaders.registry, consumed by
   both backtest.runner and src.tools.backtest_tool so they cannot diverge.

2. metrics annualisation maps (_TRADING_DAYS / _BARS_PER_DAY) lacked mootdx and
   futu, so intraday backtests on them fell back to bars_per_day=1 and
   mis-annualised vol/Sharpe. Fix: add both as equity sources (252 days, 240-min
   session, mirroring tushare/akshare).

Regression tests: mootdx/futu accepted by the config schema, every registered
loader covered by VALID_SOURCES, and intraday bars_per_year correct for both.
2026-06-09 19:17:21 +08:00
Haozhe Wu bbd8466ded fix(web): surface auth-required reason on remote 403 (#191) (#192)
Chat sends from a non-loopback client (another machine, VM host, LAN
device) without API_AUTH_KEY returned 403 on every sensitive endpoint,
but the UI only showed a generic "Failed to send message, please retry."
The send path now surfaces AUTH_REQUIRED_MESSAGE so users know to set
API_AUTH_KEY (and enter it once in Settings) or browse via localhost.

Also document the localhost-vs-LAN rule in the web-UI setup and add a
2026-06-09 news entry across all 5 locale READMEs.
2026-06-09 11:10:02 +08:00
Haozhe Wu 6b37c040c1 revert: drop GHCR pre-built image path (#187)
The Path E "pull a pre-built image, no clone" deploy path was open-box
broken: the GHCR package defaults to private, so anonymous `docker pull`
fails, and the path was never verified end-to-end against the main repo
(the contributor tested a self-built image, not ghcr.io/hkuds/vibe-trading).

Removes the publish workflow, docker-compose.prod.yml, the Dockerfile
VERSION arg, and the README Path E section/table row/tree note. The
2026-06-08 news entry is rewritten to cover only the Gemini 3.x
dict-path fix (#184). Also drops the now-orphan edge-build paths-ignore.

If a buildless deploy path is wanted later, do it properly: make the
package public, verify an anonymous pull works, then document it.
2026-06-08 10:45:25 +08:00
Haozhe Wu 4389246350 docs(readme): add 2026-06-07 + 2026-06-08 news in 5 locales
06-07 live swarm status in chat (#188) + preset routing (#189); 06-08 one-pull GHCR image (#187) + Gemini 3.x dict-path fix (#184). Older 06-05/06-04 folded into Earlier news to keep 3 visible.
2026-06-08 10:29:02 +08:00
Haozhe Wu 7dd9e58870 ci(docker): skip GHCR edge build for doc-only main pushes
Release builds still always fire via the release trigger; only the edge-on-main-push build is skipped when a commit touches nothing but .md / docs/ / wiki/.
2026-06-08 10:29:02 +08:00
Haozhe Wu b31e9b4af1 docs(readme): add 2026-06-06 alpha compare news in 5 locales 2026-06-07 01:08:47 +08:00
Haozhe Wu b110bdc3b2 feat(factors): add alpha compare across CLI, REST, Web UI and agent tool
Head-to-head comparison of hand-picked Alpha Zoo alphas, sharing one core
(compare_runner.compare_alphas) across four surfaces:

- CLI: `vibe-trading alpha compare <id1> <id2> ... --sort ir`
- REST: POST /alpha/compare + SSE progress/result stream
- Web UI: Compare view in Alpha Zoo (catalogue multi-select -> ranked table)
- Agent: read-only `alpha_compare` tool (now 47 tools)

Adds a backward-compatible `only=` subset filter to run_bench so comparing
a few alphas no longer benches the whole zoo. Ranks by IC mean/std, IR,
IC-positive ratio or sample count, each with its gap to the leader.

39 new backend tests; frontend tsc + 197 vitest + build green.
2026-06-07 01:08:42 +08:00
Haozhe Wu 9b9e83fe6c docs(readme): add 2026-06-05 news (Dhan/Shoonya, Gemini, docstrings, tests) in 5 locales 2026-06-05 21:52:59 +08:00
Haozhe Wu 36c5a4457c fix(deps): patch 7 Dependabot alerts (react-router XSS/DoS, vitest RCE)
Bump react-router-dom ^7.1.0 -> ^7.15.0 (resolves to 7.17.0), clearing 6 react-router advisories (XSS, open redirect, DoS, turbo-stream arbitrary constructor; patched across 7.13.2-7.15.0).

Bump vitest + @vitest/coverage-v8 ^3.2.6 -> ^4.1.0 (resolves to 4.1.8), clearing the critical Vitest UI-server arbitrary file read/exec (patched in 4.1.0) and aligning the coverage peer dependency on the v4 line. Frontend suite (197 tests) green on vitest 4; production build passes.
2026-06-05 20:56:40 +08:00
Haozhe Wu c2257fe6f3 fix(wiki): make footer counter all-time cumulative, not a 30-day window
Sum the full visits history instead of windowing to 30 days, so the agent/human counts grow monotonically since launch (data is never GC'd).
2026-06-05 14:54:43 +08:00
Haozhe Wu ee28390a2c fix(wiki): cache-bust main.js so browsers load the traffic-counter JS immediately
main.js is browser-cached up to 4h; the fresh HTML still pointed at the unversioned /main.js, so returning visitors kept running the pre-counter JS and the footer never populated. Versioned query forces an immediate fetch of the new module.
2026-06-05 14:47:39 +08:00
Haozhe Wu cb82c6262c fix(wiki): align live-trading copy with shipped Robinhood Agentic support; hide footer counter until data loads
The wiki still claimed 'no live trading / does not execute live trades', which contradicts the shipped opt-in bounded Robinhood Agentic channel (and the README). Aligns home, docs, and alpha-library copy with the README's framing. Also fixes a CSS specificity bug where .footer-traffic{display:flex} overrode the [hidden] attribute, showing placeholder dashes before /api/stats data arrived.
2026-06-05 14:39:55 +08:00
Haozhe Wu e2a42aa4ab fix(wiki): cache PyPI installs in D1, drop redundant GitHub call from /api/stats
pypistats/GitHub calls from the Worker intermittently rate-limit, blanking the footer. PyPI now reads through a D1 last-good cache so it never goes empty; GitHub stars are dropped here since main.js already loads them client-side.
2026-06-05 14:14:07 +08:00
Haozhe Wu 6fac55d795 fix(ci): deploy wiki in config mode so Pages Functions compile
pages deploy <dir> from repo root uploads functions/ as static files and never compiles them, so /api/stats (the agent-vs-human counter) was never live. Run wrangler from wiki/ (workingDirectory) with the wrangler.toml present so functions/ is detected and compiled.
2026-06-05 14:10:04 +08:00
Haozhe Wu 25b6ee5d72 feat(wiki): add agent-vs-human traffic counter to vibetrading.wiki
First-party, anonymous analytics for the public wiki — no client-side telemetry in the pip package. Pages Function middleware classifies each page request (AI agent / bot / human) by User-Agent server-side and counts it into D1; /api/stats serves the footer's aggregate counts alongside public PyPI install and GitHub star numbers.
2026-06-05 14:03:17 +08:00
Haozhe Wu 1cd4ba19df docs(readme): add 2026-06-04 opt-in data cache news entry across 5 locales 2026-06-04 23:40:06 +08:00
Haozhe Wu 2c2c072319 docs: add trendshift badge to readmes 2026-06-04 10:43:21 +08:00
Haozhe Wu d1309b4f73 docs(readme): add 2026-06-03 community-triage news entry across 5 locales
Adds the 2026-06-03 News entry (trace call_id correlation #168, dead-doc
reference cleanup #166, langchain-community install-warning clarification
#167, Gemini thoughtSignature scoped to help-wanted #170) to the English,
Chinese, Japanese, Korean, and Arabic READMEs, and collapses the 2026-05-31
entry into the "Earlier news" section to keep three latest entries visible.
2026-06-03 23:39:18 +08:00
Haozhe Wuandzwrong 65a14c632e feat(loop): add call_id to trace entries for tool-call correlation
Adds the tool call_id to the tool_call and tool_result trace records so a
result can be correlated back to its originating call when replaying a trace.
Keeps the existing [:200] truncation on serialized args to avoid bloating
trace files with large tool inputs.

Equivalent to #168 (arg truncation preserved).

Co-authored-by: zwrong <1003912034@qq.com>
2026-06-03 22:59:25 +08:00
Haozhe Wu b5efee66ab docs: remove dead docs/live-trading/SPEC.md references from source comments
The live-trading source files referenced docs/live-trading/SPEC.md in
docstrings, comments, and tests, but docs/ is gitignored (internal planning),
so external contributors hit a dead path when they grep / ls-tree for it.
Genericize the references to "the live-trading SPEC"; docs/ stays internal.

Closes #166
2026-06-03 22:59:21 +08:00
Haozhe Wu 0df32f2eb3 feat(live): direct-SDK bounded-autonomy order gate + HK/CN universe
Function-based mandate gate for direct-SDK live orders mirroring the MCP gate:
mandate load + schema/expiry check, kill-switch halt, quantity→notional pricing,
fail-closed pre-trade checks via check_mandate, per-broker audit, and a daily
count consumed only on a confirmed placement. Shared daily counter (order_guard
now delegates to it). AssetClass gains HK_EQUITY/CN_EQUITY and OrderIntent gains
an explicit asset_class so the mandate gate buckets HK/A-share correctly. The 6
connector classification maps are wired into the live registry for fail-closed
WRITE coverage. Covered by two adversarial audit rounds; full suite 2979 passed.
2026-06-02 12:52:14 +08:00
Haozhe Wu 8563d5e78b feat(trading): add 6 SDK broker connectors with paper+live order placement
Tiger/Longbridge/Alpaca/OKX/Binance/Futu connectors under a new broker_sdk
transport — read-only account/positions/orders/quote/history + paper-account
order placement, plus bounded mandate-gated order placement for five of them.
Longbridge is paper + read-only only (its API exposes no runtime paper/live
discriminator). Per-broker structural paper/live guards: Tiger 17-digit account,
Alpaca/Binance host separation, OKX flag + uid pin, Futu trd_env via get_acc_list.
New trading_place_order/trading_cancel_order tools; generic period/limit wired
through the tool and CLI. News updated across all 5 READMEs.
2026-06-02 12:51:50 +08:00
Haozhe Wu ede8a8b33b fix(ui): clarify dev-jargon labels, drop drift-prone chip counts, add Compare loading state
Addresses UX feedback from #163: rename systems jargon leaking into chrome
(ev -> evidence, Run bench -> Run benchmark, Decay -> Decay (days), Compact
context -> Summarize conversation), add a hint for Reasoning effort, remove
hardcoded skill/preset/tool counts that go stale under frequent releases, and
show a skeleton on the Compare route while run data loads.
2026-06-02 10:37:20 +08:00
Haozhe Wu 15c7a9b577 docs(release): credit v0.1.9 cycle contributors across READMEs
Refresh the Contributors section in all 5 READMEs from the v0.1.8 cycle
list to the v0.1.9 cycle (PRs #124–#160): @toanalien, @ai7eam-dev,
@shadowinlife, @DoubleSky123, @ArthurXi, @omcdecor-cyber, @Soli22de,
@ruok808, @faizack, @fightZy, @lcwSeven, @Teerapat-Vatpitak.
2026-06-01 23:07:06 +08:00
Haozhe Wu 5a06371a94 chore(release): 0.1.9
Roll-up release since 0.1.8 — connector-first broker profiles (IBKR +
Robinhood Agentic Trading), Research Goal runtime, swarm reconcile/MCP
keepalive + worker MCP tools + strict alpha-bench + retry_run, the
agent/cli/ package refactor, the mootdx A-share loader, a robustness pass,
and the session/version fixes (#150 #156 #157 #158 + UI indicator).

Version → 0.1.9 across pyproject, SKILL.md, frontend APP_VERSION, and the
wiki docs version selector. MCP tool count corrected to 36 (adds retry_run)
across all 5 READMEs, with the ar/ko tool lists rebuilt from a stale 22 and
the ar skill count fixed 75→77. CHANGELOG [0.1.9] + 0.1.9 news entry added
to all 5 READMEs. Home backtest card data-source count corrected 3→7.
2026-06-01 23:00:43 +08:00
Haozhe Wu df70288dee fix(cli): derive version from pyproject, drop hardcoded fallback (#156)
The shipped 0.1.8 wheel reported 0.1.7 because a hardcoded constant was
not bumped on release. The metadata path already fixed installed packages;
this removes the remaining hardcoded fallback so an un-installed checkout
reads the version straight from pyproject.toml — making pyproject the one
and only place the version is written, with no constant left to drift.

- _version: tomllib-based pyproject fallback (>=3.11 guaranteed), "unknown"
  sentinel only if the tree is moved away from its pyproject.toml
- tests: pyproject fallback matches declared version, resolved-not-unknown,
  and a guard against reintroducing any hardcoded version literal
2026-06-01 22:39:57 +08:00
Haozhe Wuandai7eam-dev d2f853233f feat(swarm): retry failed/stale/cancelled runs (HTTP + MCP)
Completes the swarm-retry capability extracted from #159 and makes it
usable end-to-end instead of dead API surface.

- api_server: POST /swarm/runs/{id}/retry — reconciles a stale "running"
  run first, refuses a genuinely active run (409), else re-launches a new
  run with the original preset + user_vars
- mcp_server: retry_run(run_id) tool so the list_runs -> spot-failure ->
  retry loop works from OpenClaw/Claude Desktop (the frontend has no swarm
  dashboard; MCP + HTTP are the real consumers)
- api.ts: retrySwarmRun client method (parity with cancelSwarmRun)
- tests: missing-run / running-refused / happy-path relaunch, MCP well-known
  tool name, and HTTP traversal rejection for the new endpoint
- docs: bump current MCP tool count 35 -> 36 across the 5 READMEs (ar/ko
  prose still carries a stale 22-tool list — tracked separately)

Co-authored-by: ai7eam-dev <ai.7eam@gmail.com>
2026-06-01 22:39:57 +08:00
Haozhe Wuandai7eam-dev a0da6c7727 fix(ui): reliable session running-status indicator
Scoped extraction of the UI running-status indicator from #159 (the
remaining commits there overlapped with #157/#158 or touched the
protected providers/ module).

- useSSE: add attempt.created/started, message.received, session.created,
  tool_heartbeat, llm_usage to knownTypes so a mid-stream reconnect/replay
  still surfaces the working state
- agent store: track streamingSessionId, preserved across switchSession so
  the sidebar spinner persists when navigating away from a running session
- Agent.tsx: handle attempt.created/started (re-affirm streaming on reconnect
  and page reload), keep streaming alive on tool_heartbeat, add a persistent
  pulse bar, reword "Thinking…" to "Agent is working…"
- Layout: replace the dead-code (status === "failed") dot with a spinning
  Loader2 for the actively streaming session
- index.css: pulse-slide keyframe for the running bar

Co-authored-by: ai7eam-dev <ai.7eam@gmail.com>
2026-06-01 22:39:57 +08:00
Haozhe Wu 1e3bd7a8c0 chore(trading): clean connector docs and on-ramp mapping
Refresh multilingual MCP tool counts and centralize broker-to-connector on-ramp profile resolution so the frontend no longer duplicates broker profile fallbacks.
2026-06-01 00:27:38 +08:00
Haozhe Wu 52f5059159 feat(trading): add connector-first broker profiles
Add unified trading connector profiles and tools for IBKR and Robinhood. Gate live runner behavior by connector capability, preserve IBKR local read-only config, and update CLI, MCP, web UI, tests, skill docs, and multilingual README news.
2026-06-01 00:17:28 +08:00
Haozhe Wu 4e009c7769 docs: news 2026-05-30 — backtest/agent-loop/session robustness pass 2026-05-30 21:45:17 +08:00
Haozhe Wu 168122311a fix(backtest): emit clean JSON for signal-engine source errors
Follow-up to #149. Source-level AST validation (circular self-import,
unsafe imports, decorators, top-level statements) raises ValueError from
_load_module_from_file, but the call site was unguarded so it surfaced
as a raw Python traceback -- contradicting #149's stated goal of
actionable JSON errors for the circular-import case. Wrap the call and
emit a clean JSON envelope, unifying all source-validation errors with
the runner's JSON error contract.
2026-05-30 11:46:07 +08:00
Haozhe Wu 7c242b934c fix(loop): guard wrap-up nudge to mid-iterations only
Follow-up to #148. The wrap-up nudge fired at iteration == wrap_up_at
unconditionally, which on tiny budgets (max_iter<=2 -> wrap_up_at==1)
injected a '[SYSTEM] wrap up' user message on the very first iteration,
displacing the active research-goal context as the most recent user
message and breaking test_agent_goal_context. It also conflicted with
goal-continuation by telling the agent to stop early.

Now the nudge only fires for 1 < iteration < max_iterations: never on
the first iteration, and never on the last (the forced text-only path
already guarantees a final answer there).
2026-05-30 11:46:07 +08:00
Haozhe Wu 108bfae960 feat(live): bounded-autonomy live trading channel (Robinhood Agentic Trading)
Add an opt-in, read-only-by-default live execution channel via Robinhood
Agentic Trading (remote MCP, OAuth). The agent trades autonomously only inside
a user-committed mandate (symbol allowlist, order size, exposure, leverage,
instrument, daily cap):

- Mandate model + read-only protected store; commit is the ONLY writer
  (no set_mandate tool reachable from the agent loop)
- Pre-trade enforcement gate (fail-closed) + order-intent extractor + quote pricing
- 3-tier read/write tool classification (default-deny) + OAuth token cache
- Filesystem kill switch (LLM-independent) + preemptive flatten on trip
- Mandate auto-expiry, checked proactively every runtime tick
- Persistent autonomous runtime: scheduler, durable job store, liveness,
  market/interval triggers, crash-recovery reconciliation
- Consent UX (propose/commit/adjust) across CLI and Web UI
- Compliance-grade audit ledger with credential/PII redaction

Read-only/fail-closed by default; protected agent core (src/agent, src/session)
untouched. Paper/mock-tested only; live verification is the community's
(experimental, use at your own risk). Updates all 5 README languages with the
bounded-autonomy disclaimer.
2026-05-29 18:00:08 +08:00
Haozhe Wu f0c799d5b4 docs: news 2026-05-28 — swarm DAG gating + strict alpha bench + worker MCP 2026-05-28 16:40:05 +08:00
Haozhe Wu 46e49626b9 fix(swarm): wire task_blocked into CLI live panel + carry agent_id
Follow-up to #145. The new task_blocked event was emitted without
agent_id, so the CLI live panel (cli/_legacy.py) could not attach it
to the per-agent row — blocked agents stayed visually "pending" until
the run finalized. Three small fixes:

- runtime.py: pass agent_id=task.agent_id on the task_blocked emit
- cli/_legacy.py: handle task_blocked in the live event loop
- models.py: extend SwarmEvent docstring to mention task_blocked
- test_swarm_dag_gating.py: assert agent_id is present on the event
2026-05-28 15:32:04 +08:00
Haozhe Wu bc63784752 docs: bump skill count 75→77 and recommend mootdx as no-token A-share fallback
- 5 READMEs had "75 skills" / "75 个 skills" / similar wording stale
  in 7+ places each; actual `agent/src/skills/` count is 77 after
  today's mootdx skill. Bumped every mention to 77.
- The "no-API-key" tip recommended AKShare as the free A-share
  fallback, but mootdx is now the more reliable option (TCP-direct,
  no IP throttle). Reworded the tip in all 5 languages: mootdx
  preferred, AKShare as broader-coverage backup.
2026-05-27 20:06:41 +08:00
Haozhe Wu 6b1b180f66 docs: news 2026-05-27 — mootdx loader + CCXT proxy + output polish
Add the 2026-05-27 entry to all 5 READMEs and rotate the
2026-05-24 Research Goal runtime entry into Earlier news so the
top-3 stays at 5-27 / 5-26 / 5-25 per the news convention.
2026-05-27 20:00:01 +08:00
Haozhe Wu d542038d36 fix(render): drop markdown HR separators and bump valid_sources
User report (#139): example output shows ugly full-width horizontal
lines instead of clean tables. Root cause is the LLM emitting `---`
between sections, which Rich (CLI) and react-markdown (web) both
render as a full-width separator. Fixed at three layers:

1. System prompt (context.py): explicit rule to use markdown pipe
   tables for multi-row data and `##` headings instead of `---`
   horizontal rules. Also corrected stale "5 data sources" -> "7"
   to reflect mootdx + futu.
2. CLI render_answer (transcript.py): strip standalone `---` /
   `***` / `___` lines as defense-in-depth so historical answers
   and the occasional rule-breaking output render cleanly.
3. Frontend MessageBubble.tsx: add `prose-hr:hidden` so any `<hr>`
   that slips through doesn't draw a line.

Also fixed backtest_tool valid_sources whitelist: `source="mootdx"`
and `source="futu"` were rejected by the validator even though both
loaders were registered. Added both to the allowed set.

Tests: +1 transcript regression covering the HR strip; existing
2385 tests stay green.
2026-05-27 19:53:46 +08:00
Haozhe Wu e4e2a9d9c2 feat(loaders): add mootdx A-share OHLCV loader
Mootdx talks the native 通达信 binary protocol over TCP, bypassing
akshare's East Money HTTP scrape that periodically throttles. No API
token, no per-IP rate limit.

Scope: A-share OHLCV.
- Daily 1D/1W/1M via get_k_data (native date range).
- Intraday 1m/5m/15m/30m/1H via bars() with 25-page walk-back
  pagination (~10y daily, ~5y 1H, ~3mo 1m of lookback).
- SH / SZ supported. 北交所 (BJ) returns empty upstream, so the
  loader detects BJ symbols and skips with a clear warning rather
  than swallowing the failure.
- Mootdx extended market (futures/options) is upstream-broken in
  v0.11.7 — falls through to tushare/akshare for those markets.

Fallback chain becomes a_share: [tushare, mootdx, akshare] so
mootdx serves as the no-token, no-scrape middle tier between
tushare (auth-gated) and akshare (HTTP scrape).

Optional dependency: pip install mootdx && pip install 'httpx>=0.28.1'.
Mootdx pins httpx<0.26 in setup.py but only uses forward-compatible
httpx APIs; the second install restores the modern httpx that the
rest of the project (MCP server, fastmcp) needs.

Closes #107.
2026-05-27 19:43:24 +08:00
Haozhe Wu 3bd356c1f6 fix(deps): bump langgraph for CVE-2026-28277 2026-05-26 00:55:58 +08:00
Haozhe Wu 83801bad46 docs: refresh multilingual news 2026-05-26 00:49:02 +08:00
Haozhe Wu acab70c556 fix(goal): close research goal lifecycle 2026-05-26 00:43:28 +08:00
Haozhe Wu 6b8241a356 fix(wiki): route docs rewrites to docs app 2026-05-25 22:43:33 +08:00
Haozhe Wu 8b2273c6de fix(ui): streamline composer goal workflow
Move Research Goal entry into the chat composer: the + menu now switches the
normal input into goal mode, sends create the session goal directly, and the
active goal stays above the input as an inline chip with expandable criteria
and evidence details.

Stabilize the local dev launcher used for browser smoke tests by wiring custom
backend ports into VITE_API_URL, launching long-lived process groups, cleaning
up child processes reliably, and reporting actual frontend/backend status.
2026-05-25 00:41:31 +08:00
Haozhe Wu 2a18d33c23 feat(goal): add research goal runtime
Add the session-scoped Research Goal layer across backend, CLI, API/MCP,
SSE, and Web UI. Goals now persist claims, acceptance criteria, evidence,
budgets, and completion policy; agent tools and /goal can create goals and
attach evidence; REST/MCP expose snapshots and evidence writes; SSE keeps
chat clients fresh.

Also carry the 2026-05-24 transplant cleanup: replay-all/session-id/upload
wiring, report-worthy Full Report gating, English-only Web UI copy, and audit
fixes for verified evidence, live-trading risk tiers, CLI session linkage,
goal ledger cleanup, replay-all hookup, and frontend snapshot races.
2026-05-24 23:55:00 +08:00
Haozhe Wuandykykj 74614f0e7c feat(cli): refresh interactive terminal UI
Refresh the interactive terminal transcript, activity rail, and prompt visuals. Document the 2026-05-23 CLI update in README and keep generated CLI screenshots out of git.

Co-authored-by: ykykj <140159248+ykykj@users.noreply.github.com>
2026-05-24 00:17:45 +08:00
Haozhe Wu 1404eda72b docs(readme): add 2026-05-22 swarm news 2026-05-22 23:45:14 +08:00
Haozhe Wu cac3f0b5ed fix(swarm): unified reconcile + MCP keepalive + heartbeat coverage (#132)
Symptoms reported in #132 (WorkBuddy + DeepSeek MCP):
- MCP run_swarm blocked 30 min then errored, losing run_id
- get_swarm_status / get_run_result showed stale snapshot while tasks/*.json
  had real progress
- Host crash left runs "running" forever with no recovery path
- list_swarm_runs / SSE rendered the same stale snapshot

Architecture:
- store.reconcile_run(write=True): single read-side entry point. Hydrates
  live task files, recovers terminal-but-not-finalized state (host crashed
  between last layer sync and finalize), and reaps stale running runs whose
  events.jsonl has gone silent past compute_stale_threshold.
- Threshold is per-run: max(60, min(heartbeat_interval*10, retry_ceiling))
  — fast detection when heartbeats work, retry-budget-clamped when not.
- HeartbeatTimer wraps registry.execute, llm.stream_chat, AND grounding
  fetch so no normal execution phase can produce >heartbeat_interval of
  silence. Slow first-token / reasoning-mode / pure tool-call responses
  no longer trip the reaper.
- run_swarm emits ctx.report_progress every poll with elapsed seconds, so
  MCP transport stays alive via progress notifications; first frame is
  fixed-format ``swarm_started run_id=<id>`` so transport-drop clients can
  still recover the handle.

Endpoints updated to use reconcile_run:
- MCP: get_swarm_status, get_run_result, list_runs, run_swarm polling
- API: /swarm/runs, /swarm/runs/{id}, /swarm/runs/{id}/events SSE
- SwarmTool: polling + budget-out path (no longer cancels on timeout)

Other fixes:
- SWARM_HEARTBEAT_INTERVAL_S parses with ValueError fallback in worker
  (was crashing import on bad config)
- reaper derives run terminal status from task statuses (no more
  hardcoded "failed" on a run whose tasks all completed)
- serialize_task surfaces started_at / completed_at / depends_on / blocked_by
- reap_stale_runs() MCP tool for explicit user-triggered cleanup

Tests: 22 new regressions in tests/test_swarm_status_hydration.py covering
hydrate, reconcile, terminal recovery, stale reap, MCP keepalive cadence,
auto-recovery on read, threshold math, env var robustness, heartbeat
wiring around LLM + tool execution.

Verification:
- targeted suite: 22 passed
- full swarm/mcp suite: 169 passed, 4 skipped, 0 failed
- compileall + import smoke clean
2026-05-22 11:56:29 +08:00
Haozhe Wu e9c0000f5f chore(providers): refresh default model ids 2026-05-22 10:04:26 +08:00
Haozhe Wu 1edf27e040 ci: switch syntax-check from cli.py to cli/ package
The CLI was split into a package in b5a44a1; the workflow still
ran ``python -m py_compile cli.py`` against the now-renamed file
and failed with "No such file or directory: 'cli.py'".

Use ``python -m compileall -q cli`` so every module under the
package (``main.py``, ``_legacy.py``, ``commands/``, ``components/``,
``utils/``, ``_version.py``, ``__main__.py``) is checked together.
2026-05-22 00:04:00 +08:00
Haozhe Wu b5a44a1e56 refactor(cli): split agent/cli.py into agent/cli/ package
Restructures the 3216-line agent/cli.py into a focused package
while preserving every existing surface verbatim.

Legacy compatibility:
- _legacy.py shim keeps every subcommand (serve, run, hypothesis,
  alpha, memory, provider, swarm, sessions, ...) reachable through
  the same CLI flags and console-script entry.
- Public symbols (cmd_*, _INIT_ENV_PATH, Confirm, AGENT_DIR, ...)
  re-exported through cli.<name>; `from cli import *` exposes 76
  symbols (was 1).
- Package-level cli.RUNS_DIR / cli.SESSIONS_DIR monkeypatches now
  propagate into _legacy before any cmd_* runs.

New scaffolding:
- agent/cli/main.py interactive REPL front door
- agent/cli/commands/ slash router + handlers (15 commands)
- agent/cli/components/ reusable Rich UI building blocks
- agent/cli/{theme,intro,onboard,input,completer,stream}.py
- agent/cli/__main__.py restores `python -m cli`
- agent/cli/_version.py reads importlib.metadata.version() so
  --version output and the banner stay aligned with pyproject.toml

Production wiring:
- New FastAPI middleware in api_server.py serves
  frontend/dist/index.html on browser navigation to /runs/{id}
  and /correlation, regex-narrowed so /runs/{id}/code and
  /runs/{id}/pine API routes stay reachable.
- frontend/vite.config.ts dev proxy mirrors the same narrowing.
- scripts/dev backend starts via package-safe
  `python -c 'import cli; cli.main(...)'`.

Chat-gate narrowed: chat --help / chat -h / chat --max-iter <bad> /
chat extra correctly reach legacy argparse instead of being
swallowed by the new REPL.

Tests: 2271 passed, 43 skipped (focused CLI/SPA test files:
test_cli_interactive.py 67 + test_spa_deep_link.py 13 + existing
CLI memory/init/hypothesis 48 = 128).
2026-05-22 00:00:47 +08:00
Haozhe Wu 34a30fb58d docs(readme): News — Hypothesis Registry CLI (2026-05-20)
Adds a 2026-05-20 entry to all 5 READMEs (EN/ZH/JA/KO/AR) covering the
`vibe-trading hypothesis {list,show,invalidate}` subcommands shipped in
dda06ae. Pushes the 2026-05-17 Alpha Zoo v1 entry into the collapsed
"Earlier news" block to preserve the 3-latest-visible convention.
2026-05-20 22:32:14 +08:00
Haozhe Wu dda06ae351 feat(cli): hypothesis registry — list / show / invalidate
Adds `vibe-trading hypothesis {list,show,invalidate}` subcommands that wrap
the existing HypothesisRegistry storage. List prints a Rich table (or JSON
with --json), supports --status filter and --limit. Show prints a Rich
detail panel including linked run cards. Invalidate flips status to
rejected and optionally records an invalidation_notes message; existing
notes are preserved when --note is omitted.

Closes the roadmap P2 backlog item "Add CLI workflows for listing,
editing, and reviewing hypotheses" on the CLI side; Web workflow waits
on the v0.2.0 IA landing.

22 new tests cover wiring, list/show/invalidate paths, JSON output,
status filter, limit, missing-id errors, and note persistence.
2026-05-20 21:36:22 +08:00
Haozhe Wu 008c0839cd docs(readme): add AI-Trader to ecosystem 2026-05-20 20:40:33 +08:00
Haozhe Wu a1e83aded7 feat: live tool feedback + graceful CLI cancel
Tool calls now emit a 3s heartbeat plus structured per-stage progress
via emit_progress(). run_backtest, read_document, read_url opted in;
all other tools get heartbeat-only feedback automatically.

CLI Rich Live dashboard: Unicode spinner, ASCII progress bar, ETA,
per-tool rows (up to 3 parallel) keyed by tool name. Color discipline
restored (yellow reserved for cancelled/warning; running stays cyan).
0.25s throttle on tool_progress; no-rich path coalesces dots and
progress lines with per-tool gating.

Frontend: new ToolProgressIndicator with rAF coalescing, ARIA
role=status + hidden <progress>, ProgressRing SVG when determinate,
parallel-tool stacking with overflow + truncation. Extracted reusable
primitives ProgressBar.tsx and lib/tools.ts (shared tool-name i18n).

CLI Ctrl+C: first press calls agent.cancel() for graceful exit
(current step finishes, trace closes cleanly); second within 2s
force-quits. Backend POST /sessions/{id}/cancel was already wired.

Also fixes 2 pre-existing baseline test failures:
test_fundamental_filter_example (path resolved via __file__) and
test_openai_codex (pytest.importorskip for optional dep).

Tests: 2174 -> 2176 pass.
2026-05-19 16:19:42 +08:00
Haozhe Wu 0c39711432 docs(readme): add one-line star CTA below star history (5 locales) 2026-05-19 13:35:45 +08:00
Haozhe Wu 8a7a658260 fix(cli): vibe-trading init writes to ~/.vibe-trading/.env
Help text and docstring promised ~/.vibe-trading/.env but the
implementation wrote to <AGENT_DIR>/.env, which lives inside
site-packages for pip-installed users and gets wiped on upgrade.

Align the implementation with the documented behavior, mkdir -p
the parent, and chmod 600 the file since it holds API keys.
2026-05-18 16:10:36 +08:00
Haozhe Wu 37fb6e2f4e fix(frontend): show Thinking… placeholder between Send and first SSE event (#128)
Closes #127

Before this change, the live streaming area in Agent.tsx only rendered
when streamingText was non-empty or toolCalls had at least one entry.
The gap between handleSubmit setting status="streaming" and the first
SSE event arriving left the chat area visually blank, making the UI
look unresponsive on slow networks or long agent "thinking" time.

Add a Thinking… placeholder that renders exactly during that gap
(status === "streaming" && !streamingText && toolCalls.length === 0)
and is implicitly hidden once the first SSE event populates either
streaming state.
2026-05-18 15:29:49 +08:00
Haozhe Wu 8a6c578a76 chore: cleanup pass — 3 latent bug fixes + 913 LOC net delete
Bug fixes
- CompositeEngine no longer misroutes bare Chinese-futures codes like
  RB2410 to GlobalFuturesEngine. _is_china_futures moved into a shared
  _market_hooks module with a case-normalized product table and a
  non-CN exchange guard. 9 new regression cases (RB2410 / rb2410 /
  RB2410.SHFE / CL.NYMEX / M2412.CBOT / CF2412.ICE / AU2412.COMEX /
  FG2412.EUREX / bare CN-collision still routes Chinese).
- Session FTS5 indexes now persist timestamps so cross-session search
  can sort by date. Same path also fixes a re-upsert that was
  wall-clocking every session's started_at on title-rename.
- Vite dev-mode proxy gained the missing /alpha entry; AlphaZoo page
  resolves on npm run dev.

Cleanup
- Deleted swarm/api_models.py + swarm/mailbox.py + SwarmDashboard.tsx
  (dead modules / orphaned UI after swarm-via-agent flow).
- Deleted SessionService.resume_attempt + get_attempts + get_attempt
  + SessionStore.list_attempts + Attempt.mark_waiting_user chain
  (half-baked human-in-the-loop from initial commit).
- Deleted ChatLLM.achat (async sibling never called) and
  _extract_balanced_json (only-tested helper).
- Deleted american_exercise_value in options_portfolio.py
  (driver inlines bs_price logic).
- Deleted _compute_ic_series_reference in factor_analysis_core.py
  (kept-as-reference, no callers).
- Collapsed MEMORY_TYPES duplication between cli.py and
  memory.persistent; restored set-equality invariant assert.
- Deleted runSwarm (~197 LOC), swarmDash state, swarmCancelRef
  write-only ref (orphaned by new agent-driven swarm flow).
- Dropped 76 unused 'vw = vwap(...)' boilerplate lines across
  gtja191 alphas + their now-unused vwap imports.
- Cleared 11 F401 unused-import sites in tools/, agent/, providers/.

Test infrastructure
- test_e2e_harness_v2.py (real-LLM e2e) gated behind
  VIBE_TRADING_RUN_LIVE_E2E so CI no longer flips shape based on
  env-key presence. CI workflow now has explicit --ignore.
- test_auto_compact_with_long_history: monkeypatch TOKEN_THRESHOLD so
  the test deterministically triggers compact instead of relying on
  unrelated LLM behaviour.
- test_compact_tool_explicit: assertion corrected — compact tool is
  special-cased in loop._process_tool_calls and does NOT emit
  tool_call events. Assert on the 'compact' event instead.
- TestSwarmE2E: per-test @pytest.mark.timeout overrides matching the
  internal 300s poll deadlines; cancel test now polls for terminal
  state instead of fixed sleep(3).

Tooling
- ruff per-file-ignores for factors/zoo/** (3783 → 0 F401 noise);
  F841 stays active in zoo to catch real formula bugs.
- frontend tsconfig: noUnusedLocals + noUnusedParameters as
  regression guards.
- highlight.js hoisted to direct dependency (was transitive via
  rehype-highlight).
- Vite proxy: dropped unused /run, /skills, /health, /api, /system;
  added /alpha.

Annotations
- direction parameter renamed to _direction in 6 calc_commission
  signatures (reserved for future borrow-fee asymmetry).
- _MARGIN_PER_CONTRACT in global_futures.py annotated as reference
  data.
- Backtest engines' _calc_pct_change variants cross-reference each
  other in docstrings.

Docs
- 2026-05-18 news entry added to README.md / README_zh.md /
  README_ja.md / README_ko.md / README_ar.md; 2026-05-15 entry
  rolled into the Earlier news <details>.
- Contributors heading: v0.1.7 cycle → v0.1.8 cycle in all 5
  READMEs.

126 files changed, +363 / -1276.
2026-05-18 15:16:24 +08:00
Haozhe Wu d2c94396aa chore: move tweet draft out of public wiki
wiki/research-lab/posts/alpha-191-in-2026.tweet.md was an internal
marketing draft (pre-flight checklist + draft tweet text) that should
not have been committed to a public path. Moved to docs/alpha-zoo/
(gitignored) and removed from main.

Note: the file remains in git history at commit 137689c. If history
scrub is required, a force-push rewrite is needed (separate decision).
2026-05-17 20:23:44 +08:00
Haozhe Wu 07e1adcbba chore(0.1.8): version, CHANGELOG, packaging, News across 5 READMEs, SKILL.md
Release prep for Alpha Zoo v1. Wraps up the 0.1.8 main-branch
content so PyPI + ClawHub publish can run from a clean checkout.

Version
- pyproject.toml: 0.1.7 → 0.1.8
- wiki/docs/content.js: DOCS_DEFAULT_VERSION 0.1.7 → 0.1.8 + 0.1.7
  retained in the version dropdown
- agent/SKILL.md frontmatter: 0.1.7 → 0.1.8

Packaging
- pyproject.toml [tool.setuptools.package-data] adds the new
  src.factors zoo asset globs (zoo/**/*.yaml, zoo/**/*.md,
  zoo/**/NOTICE) alongside the existing src and backtest entries
- MANIFEST.in adds `recursive-include agent/src/factors *.yaml *.md`
  and `include NOTICE`

CHANGELOG.md (first changelog file in the repo)
- Full 0.1.8 entry covering the Alpha Zoo (4 zoos × 452 alphas),
  Web UI + 4 REST routes, CLI subcommand suite, safety floor
  (AST purity gate + lookahead test + pytest-socket + grep gates),
  community governance (CONTRIBUTING + DCO + NOTICE + per-zoo
  LICENSE.md), and the IC/IR core extraction.
- Known limitations subsection: btc-usdt is single-asset; cross-
  sectional IC needs ≥2 instruments; crypto-majors basket planned
  for 0.2.
- [Unreleased] anchor at top for the next cycle.

agent/SKILL.md (ClawHub package descriptor)
- Frontmatter description gains Alpha Zoo + 75 skills (was 74)
- New `### Alpha Zoo (452 pre-built alphas)` section with the
  4-zoo breakdown + CLI / REST / signal-engine entry points
- Skill count 74 → 75 wherever it appears
- Trademarked-name string intentionally absent (CI gate b enforces)

5 READMEs (EN/ZH/JA/KO/AR) per `docs/alpha-zoo/readme-update-spec.md`
- Roadmap: Alpha Zoo `Planned` → `Shipped 0.1.8` with expanded
  description (452 alphas, 4 zoo sources, CLI + agent + Web UI)
- Skill count 74 → 75 (Tool category 10 → 11, `alpha-zoo` added)
- Agent tools count 29 → 31 (alpha_zoo_tool, alpha_bench_tool)
- Project structure tree: new `factors/` block (base / registry /
  bench_runner / zoo), new `api/` block (alpha_routes), new
  top-level `tools/` block (ci_grep_gates.sh). Frontend pages list
  expanded (AlphaZoo, Correlation, Settings)
- Quick Example: second snippet showing `alpha bench` one-liner
- New "What You Can Do" row for the bench-a-pre-built-alpha-zoo path
- New `<details>` block under Detailed Capabilities listing the
  4 zoos + counts + sources + license stance
- Examples → Strategy & Backtesting: 3 snippets (bench, list+show,
  multi-factor compose via ZooSignalEngine.from_zoo)
- API Server route table: 4 `/alpha/*` rows (list, get, bench,
  bench stream SSE)
- CLI Reference: alpha subcommand introduced in the top block;
  single-run examples block extended with 3 alpha commands

`### Key Features` 4-card grid (#7) and Demo section (#13) are
intentionally NOT touched here — they need layout redesign + new
GIFs/screenshots; design notes in docs/alpha-zoo/readme-redesign-7-13.md
will be implemented in a follow-up PR.

969 tests pass; CI grep gates green (no yaml.load misuse, no
trademarked-name leak, no per-stock-code data in wiki); TypeScript
clean on the frontend.
2026-05-17 20:19:04 +08:00
Haozhe Wu 137689c5c1 feat(wiki): Alpha Library auto-render + "Alpha 191 in 2026" blog
wiki/scripts/build_alpha_library.py (475L)
- Reads manifest.json produced by `vibe-trading alpha export-manifest`
- Emits per-alpha pages (content/<zoo>/<id>.html) and per-zoo
  overview tables (content/<zoo>/index.html) — 452 + 4 = 456 pages
- Jinja2 with select_autoescape(["html","xml"]) mandatory; no |safe
  on user-derived content; strict CSP on every generated page
  (default-src 'self'; style-src 'self' 'unsafe-inline';
   script-src 'none'; img-src 'self' data:)
- Emits content/index.json for the landing page to hydrate per-zoo
  counts via fetch()
- Friendly exit when manifest.json missing
- End-of-main self-test compares manifest formula content against
  rendered HTML to verify the escape pipeline; prints
  "html escape ok" or "html escape FAIL"

wiki/alpha-library/index.html (rewritten from 78-line shell to 192L)
- Hero + 4 zoo cards (Kakushadze 101 Formulaic Alphas, qlib158,
  gtja191, academic) + stats grid + 3-line CLI quickstart + GitHub
  footer
- Per-zoo counts hydrated at runtime from content/index.json
- Loose CSP (script-src 'self' 'unsafe-inline') only on this landing
  page to keep the site theme-init inline script and the inline
  fetch() — generated /content/** pages stay on script-src 'none'

wiki/research-lab/index.html
- Post grid replacing the empty hero stub; links to the new blog
- One-line lede explaining the section

wiki/research-lab/posts/alpha-191-in-2026.html (~675L, ~2400 words)
- 6-section deep-dive: TL;DR / Background / Method / Findings /
  Caveats / Reproduce
- Numbers from a real bench run on CSI 300 / 2018-2025: 10 alive,
  15 reversed, 165 dead (of 190 tested, 1 skipped). Top 5 by IR:
  gtja191_171 / _111 / _163 / _002 / _054. Theme survival rates:
  microstructure 22%, reversal 11%, volatility 8%, volume 5%,
  momentum 2%, liquidity 0%
- Survivorship-bias caveat for cross-zoo readers (the current
  constituent list of any index is a forward-biased proxy for the
  point-in-time constituents during the window)
- Strict CSP (script-src 'none'); no MathJax/KaTeX — formula_latex
  rendered as <code class="formula">; aggregate stats only (no
  per-stock per-date values, Tushare ToS)

wiki/research-lab/posts/alpha-191-in-2026.tweet.md
- HKUDS-style draft tweet (Chao Huang voice): emoji-decorated
  project name, real numbers (10 / 15 / 165), 8-year OOS qualifier,
  GitHub + pip links, ≤2 hashtags. READY to post once the release
  goes live.

.gitignore adds wiki/alpha-library/manifest.json and
wiki/alpha-library/content/ as generated, never-checked-in artifacts.
2026-05-17 20:04:24 +08:00
Haozhe Wu d77c64e2ce feat(alpha-zoo): bench runner + CLI + Web UI + 4 REST routes
End-to-end product surfaces over the factor framework: a CLI for
power users, an agent-tool layer for natural-language workflows,
a REST + SSE API for browser clients, and the Web UI itself.

CLI (vibe-trading alpha <subcommand>)
- list / show / bench / compare / export-manifest with security
  guards (path-traversal, alpha_id allowlist before any FS read)
- agent/cli.py wired (7-line glue), all logic in cli_handlers.py
- alpha bench: requires --zoo (or --yes); drives bench_runner with
  an on_progress callback feeding a Rich Progress bar; prints ETA
  banner; emits stderr summary "✓ Bench complete. N tested, M
  skipped. Report: <path>"; returns exit 1 on status != ok
- alpha list: --limit N / --json flags; TTY next-step hints
- alpha show: --brief; Syntax(word_wrap=True) so source survives
  narrow terminals
- TUSHARE_TOKEN errors append a 3-step fix banner
- ConstantInputWarning silenced at module level

Agent tools (auto-discovered via BaseTool.__subclasses__)
- AlphaZooTool: list_alphas / get_alpha / health
- AlphaBenchTool: orchestrator with Jinja2 autoescape HTML report +
  strict CSP meta. Module-level run_alpha_bench() reused by CLI

Universe loader (agent/src/tools/alpha_bench_tool.py)
- csi300 → Tushare index_weight + pro.daily, in-house panel build
  because the bundled tushare loader drops `amount`. Parallel fetch
  via ThreadPoolExecutor(max_workers=4); cache at ~/.vibe-trading/
  cache/<universe>_<start>_<end>.pkl with sha256 sidecar integrity
  guard
- sp500 → Wikipedia constituent list (UA-spoofed; bare pd.read_html
  returns 403) → yfinance loader; surfaces survivorship_bias=True
  in panel._meta because the constituent set is point-in-NOW
- btc-usdt → bundled okx loader; fails fast (ValueError) on single-
  asset cross-sectional IC degeneracy

REST API (agent/src/api/alpha_routes.py + agent/api_server.py +6 lines)
- GET /alpha/list?zoo=&theme=&universe=&limit= — paginated catalogue
- GET /alpha/{alpha_id} — meta + source code via Registry.get_source
- POST /alpha/bench — kicks off asyncio.create_task running the
  bench in asyncio.to_thread; in-memory job state with threading.Lock
  + 1-hour TTL + asyncio.Semaphore(2) concurrency cap (429 on
  saturation)
- GET /alpha/bench/{job_id}/stream — Server-Sent Events emitting
  progress / result / done / error events. Heartbeat ": ping\n\n"
  every 15s so reverse proxies don't close idle streams. Unexpected
  exceptions are sanitised to "internal error; see server logs"

Web UI (frontend/src/pages/AlphaZoo.tsx ~950 LOC, 3 sub-views)
- Browse: 4 gradient zoo cards (qlib158 / Kakushadze 101 / gtja191 /
  academic) + filter bar (zoo, theme, universe, id substring) +
  paginated table (50 rows + Load more)
- Detail: formula_latex in <code> block (no MathJax under strict
  CSP); metadata table; collapsible <details> source code; "Run
  bench" CTA prefilled
- Bench: form → SSE-streamed progress bar + currently-computing
  alpha id → Alive/Reversed/Dead/Skipped stat cards + Top-5-by-IR +
  Most-Reversed tables + by-theme echarts bar chart
- New /alpha-zoo route triplet wired in router.tsx; Layout.tsx
  nav entry with the Layers icon
- Raw EventSource pattern (useSSE hard-codes agent-stream event
  types and would drop our progress/result/done/error)
- a11y: htmlFor/id pairs on every label, aria-hidden on decorative
  lucide icons, sr-only table caption, CategoryBadge component
- BTC universe fail-fast surfaces as inline form error with hint
  to try sp500/csi300

Registry singleton via get_default_registry() so API requests don't
each pay the 452-module AST scan. compute_ic_series vectorised via
pandas corrwith(method="spearman"); ~5-10x speedup, numerically
identical to the prior scipy loop within 1.1e-16.

VWAP 千元 fix: empirical Tushare probe against 000001.SZ confirmed
daily.amount is in 千元 (not raw CNY). base.vwap(EQUITY_CN) now does
safe_div(amount * 1000.0, volume * 100.0 + 1.0) — previously
~1/1000th of true vwap. Affects factors mixing vwap with price
additively (alpha101_093 etc.).

969 tests pass; registry health 452 loaded / 0 failed; CI grep
gates green; TypeScript clean.
2026-05-17 20:04:02 +08:00
Haozhe Wu 94c2a8ffb2 feat(factors): port 452 alphas across 4 zoos
Implements four production-ready alpha catalogues, each in its own
agent/src/factors/zoo/<zoo>/ subdirectory with a per-zoo LICENSE.md
declaring provenance and treating the formulas as mathematical
content (paper prose, tables, and figures are not reproduced):

qlib158 — 154 alphas
  Port of Microsoft Qlib's Alpha158 handler.py feature definitions
  (github.com/microsoft/qlib @ d5379c5, Apache-2.0). Each module
  carries a per-file header citing the upstream commit SHA + path.
  Includes the upstream NOTICE per Apache-2.0 §4(d). 4 raw price-
  reference fields ($open/$high/$low/$vwap) intentionally omitted —
  they are inputs, not signals, and conflict with the wide-DataFrame
  contract.

alpha101 — 101 alphas
  Implementation of Kakushadze (2015) "101 Formulaic Alphas"
  (arXiv:1601.00991), written from the paper appendix. 19 industry-
  neutral alphas (#48, 56, 58, 59, 63, 67, 69, 70, 76, 79, 80, 82, 87,
  89, 90, 91, 93, 97, 100) flagged requires_sector=True so the
  registry SkipAlpha-s them on panels without a sector tag. Display
  name is "Kakushadze 101 Formulaic Alphas" — the trademarked
  alternative string is intentionally absent (enforced by ci grep
  gate b).

gtja191 — 191 alphas
  Implementation of Guotai Junan Securities' 2014 "191 Short-period
  Trading Alpha Factors" research report. Ambiguous formulas
  (MAX/MIN set vs ts, sign convention, SMA(x,n,m) → x.ewm(alpha=m/n),
  WMA → decay_linear approximation, REGBETA → ts_cov/ts_std**2,
  HIGHDAY/LOWDAY → ts_argmax/ts_argmin) are documented per-alpha in
  __alpha_meta__["notes"]. Long-horizon SUM/MEAN windows
  (alphas #25/26/33/39/45/56/61/64/74/75/77/91/92/98) shortened on
  the production code path so outputs stay defined on short panels;
  documented in each affected alpha's notes.

academic — 6 factors
  Honest price-based proxies for Fama-French 5 + Carhart momentum;
  nicknames carry a [PRICE PROXY] prefix because the canonical FF
  series require book-to-market / profitability / investment-growth
  fundamentals we do not ship. Kenneth French's data library is
  referenced (URL only) for users who need canonical monthly returns.
  Internal _effective_window helpers shrink the 252/60-day windows on
  short panels so the lookahead test and the registry NaN-ratio guard
  pass; production-sized panels see the canonical window untouched.

Sample golden CSVs at agent/tests/factors/fixtures/goldens/ for
five sampled alphas per zoo, asserted at rtol=1e-6, equal_nan=True.
ci grep gates pass; AST purity + lookahead tests both green on the
full 452-alpha registry.
2026-05-17 20:03:28 +08:00
Haozhe Wu 1676eaa328 feat(factors): Alpha Zoo framework — registry, operators, safety gates
New module agent/src/factors/ providing the foundation for the Alpha
Zoo:

- base.py: 19 cross-sectional + time-series operators on wide
  pd.DataFrame panels (rank/scale/ts_*/delta/decay_linear/signed_power/
  safe_div + market-aware vwap). NaN propagates strictly; delta(d>=1)
  enforced at the operator layer; no negative shifts allowed.
- registry.py: AST-extracts __alpha_meta__ from each zoo .py module
  without importing it; pydantic AlphaMeta (extra="forbid",
  frozen=True). Lazy importlib.import_module on compute(); filesystem-
  loader fallback for plugin/test trees. Sanity gates reject +/-inf,
  NaN ratio > 95%, and shape mismatches.
- factor_analysis_core.py: IC/IR + layered backtest math extracted
  from agent/src/tools/factor_analysis_tool.py so it's reusable from
  CLI, bench tool, and API.
- bench_runner.py: per-alpha compute → IC vs forward return →
  alive/reversed/dead categorisation (mean IC > 0.02, pos_ratio >=
  0.55, |t-stat| > 2). Shared by CLI driver and API worker.

Multi-factor skill gains a panel-style ZooSignalEngine.from_zoo()
factory (cross-sectional standardisation + weighting + optional
top-N / bottom-N long-short conversion). The legacy per-symbol
example_signal_engine.py is left untouched. SKILL.md edits in
multi-factor and factor-research cross-reference the new zoo.

Safety floor:
- test_alpha_purity.py: AST allowlist scan of every zoo/**/*.py
  module. Allowed imports: pandas, numpy, scipy.*, src.factors.base,
  __future__, typing, math, dataclasses. Banned names: os/sys/
  subprocess/socket/urllib/requests/httpx/pathlib/Path/open/eval/exec/
  compile/__import__, plus dunder-access via getattr(_, "__*"),
  breakpoint, input, help, memoryview, globals, locals, vars, and
  the __class__/__bases__/__mro__/__globals__/__builtins__ chain.
  Module-level statements limited to docstring + imports + functions
  + ALPHA_ID/__alpha_meta__ assignments with literal RHS.
- test_lookahead.py: 300-row synthetic panel; corrupt rows >= 270;
  assert factor at probe row 260 unchanged within 1e-9.
- test_base.py + test_registry.py + conftest.py with opt-in
  pytest-socket integration that hard-fails outbound network during
  the factors test suite.
- tools/ci_grep_gates.sh: rejects yaml.load( without safe_load,
  any trademarked-name leak in shipped artifacts, and any per-stock-
  code data leak in wiki/**/*.{json,csv,html}.

Governance:
- CONTRIBUTING.md with the Developer Certificate of Origin trailer
  for community PRs and the alpha PR reviewer checklist.
- NOTICE at the repo root with Apache-2.0 attribution for the
  upstream qlib NOTICE and the formulas-as-mathematical-content
  stance for Kakushadze / GTJA / Fama-French sources.
2026-05-17 20:03:02 +08:00
Haozhe Wu 6d7d365892 refactor(loaders): extract bounded retry/budget pattern into base.py
ccxt_loader and okx each carried their own copy of the same 25-line
retry+budget loop introduced in #121. Hoist the pattern into two
helpers — check_budget() and retry_with_budget() — so any future loader
(yfinance, akshare, tushare) inherits the same guarantees: per-call
transient retries, a wall-clock budget, non-transient exceptions left
unwrapped, original exception preserved as __cause__.

ccxt_loader / okx now declare only what is loader-specific (the
exception class for transient errors and the budget env var); the
scheduling, sleep math, and TimeoutError wrapping live in base.py.

Adds 11 helper-level tests + the 11 pre-existing integration tests
still pass against the refactored loaders.

Follow-up to #121.
2026-05-17 00:13:28 +08:00
Haozhe Wu fd9e5fc689 refactor(swarm): _is_error_result via JSON parse with substring fallback
The substring head-match (a) false-positived on nested status fields and
(b) false-negated when the error envelope sat past the 160-char head.
Parse the result as JSON and check only the top-level status field; fall
back to the original substring scan when the payload is truncated /
unparseable so the classifier never raises on the worker hot path.

Adds 7 unit tests pinning the new behavior — nested-status non-match,
past-head match, truncated fallback, non-error statuses (degenerate /
warning) correctly classified as non-error.

Follow-up to #119.
2026-05-17 00:13:19 +08:00