Skip to content

refactor(intelligence): merge the two causal-chain engines into one (P1.2) - #67

Merged
Jcstack merged 1 commit into
mainfrom
refactor/p1-merge-causal-chains
Jun 26, 2026
Merged

Jcstack merged 1 commit into
mainfrom
refactor/p1-merge-causal-chains

Conversation

@Jcstack

@Jcstack Jcstack commented Jun 26, 2026

Copy link
Copy Markdown
Owner

Closes #48. Two parallel engines existed: ImpactChainEngine (in-code templates, substring match) and CausalChainConstructor/EventImpactEngine (YAML templates, regex + LLM fallback). The latter is canonical (it drives the core trading pipeline). This makes ImpactChainEngine a thin adapter over CausalChainConstructor — one engine — while preserving its public API.

How

  • impact_chain.py: ImpactChainEngine delegates to CausalChainConstructor; build_chain bridges CausalChain→ImpactChain (bullish→positive, confidence→magnitude, order→lag). The old CHAIN_TEMPLATES is kept only to build a sector→stock map that re-binds affected_stocks — because Engine B resolves sectors only — so find_stock_impact keeps working (this was the silent-regression trap; it's defused + tested). Engine B + its callers (aggregator / investment_director / trading_loop) are untouched.
  • config/impact_chain_templates.yaml: added usd_strengthen/usd_weaken so USD→gold (the one capability the in-code engine had that the YAML lacked) is preserved — the merge is lossless.
  • tests: test_impact_chain.py rewritten to the new contract (incl. a test proving find_stock_impact returns stock-level impacts); test_usd_affects_gold restored.

Verified

Full unit suite 3811 passed; ruff clean; intelligence sweep 234 green. detect_event_type is now single-match (documented semantic shift; no caller relied on multi-match). Simulation-only; not investment advice.

Closes #48 (P1.2). There were two parallel engines: ImpactChainEngine
(in-code templates, substring match) and CausalChainConstructor/EventImpactEngine
(YAML templates, regex + LLM fallback). The latter is canonical (core trading
pipeline). Make ImpactChainEngine a thin ADAPTER over CausalChainConstructor so
there is one chain-construction engine, preserving its public API.

- impact_chain.py: ImpactChainEngine now delegates to CausalChainConstructor.
  build_chain() bridges CausalChain→ImpactChain (bullish→positive, confidence→
  magnitude, order→lag). CHAIN_TEMPLATES is retained ONLY to build a
  sector→stock map that re-binds affected_stocks (Engine B resolves sectors
  only) — so find_stock_impact keeps working. Engine B and its callers
  (aggregator/investment_director/trading_loop) are untouched.
- config/impact_chain_templates.yaml: add usd_strengthen / usd_weaken templates
  so USD→gold transmission (the one capability the in-code engine had and the
  YAML lacked) is preserved — making the merge lossless.
- tests: rewrite test_impact_chain.py to the new contract (incl. a test proving
  find_stock_impact returns stock-level impacts); restore test_usd_affects_gold.

Verified: full unit suite 3811 passed; ruff clean. detect_event_type is now
single-match (documented semantic shift; no caller depended on multi-match).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Jcstack
Jcstack merged commit 8d2cfcc into main Jun 26, 2026
6 checks passed
@Jcstack
Jcstack deleted the refactor/p1-merge-causal-chains branch June 26, 2026 13:12
daymade added a commit to daymade/ashare-ai-analyst that referenced this pull request Sep 8, 2026
* feat: v2.0 — AI-first autonomous agent architecture (+ 智能选股, Basic Auth) (Jcstack#39)

## Summary

Major **v2.0** upgrade: moves the platform from the linear
data → analysis → prediction → strategy pipeline to an **AI-first
autonomous agent**
centered on an OODA decision loop, fed by a market-intelligence
pipeline, quant signals,
and a smart stock screener, over a Redis-Streams event bus.
**Simulation-only toy
project** — no live order routing (see the disclaimer in `README.md`).

The functionality was imported from the upstream dev branch as **fresh,
clean history**
— no upstream commits, no private `.claude/` session state, no internal
docs, no secrets.

## Commits (5)

1. `feat: port AI-first autonomous agent architecture` —
`src/agent_loop/` (OODA loop,
signal aggregation, Bayesian prescreen, bull/bear debate, risk gates,
Kelly sizing,
InvestmentDirector, thesis/outcome tracking, calibration),
`src/intelligence(_hub)/`
(5-layer sources, 7-dim scoring, causal chains, NetworkX knowledge
graph),
`src/quant/` (HMM regime, signal library), `src/event_bus/` (Redis
Streams),
`src/risk/` + `src/trading/` (circuit breaker, VaR, gates, A-share
constraints),
new frontend pages, LLM gateway. Removes the legacy rule-based
recommendation flow.
2. `test: fix inherited stale assertions + add pytest-asyncio` — 32
stale assertions
corrected to current behaviour (no behavioural change); adds the missing
   `pytest-asyncio` so async tests actually run.
3. `feat: restore recommendation (智能选股) subsystem, adapted to v2` —
multi-style
screener + LLM review + T+1 overnight risk + persistence + UI, **fed
into the agent
loop's signal aggregator** (reviving a previously-dead signal source).
Drops
   `session_strategies` (superseded by the heartbeat mission router).
4. `docs: rewrite README / dev-guide / CLAUDE.md for v2.0 + CHANGELOG`.
5. `feat(web): optional HTTP Basic Auth for the dashboard/API` — gated
by
`WEB_USERNAME`/`WEB_PASSWORD` (off by default; `/health` public). Also
fixes a latent
   upstream bug (non-latin-1 `WWW-Authenticate` realm → 500).

## Verification

- ruff lint + format: clean
- `pytest tests/unit`: **3801 passed / 0 failed**
- frontend `tsc --noEmit` + `vite build`: pass
- gitleaks (repo config): no leaks
- smoke run: app boots (242 routes), `/health` ok, recommendation
endpoints respond,
the agent loop constructs with the recommendation feed wired live, Basic
Auth verified
  (no creds → 401, correct → 200, `/health` public)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Jcstack <Jcstack@users.noreply.github.com>

* fix: triage & resolve CodeQL alerts (real bugs, FPs, CI hardening) (Jcstack#40)

## Summary

Code-scan triage of the v2 import (follow-up to Jcstack#39). Fixes real issues,
clears false
positives **at the source**, hardens CI, and verifies no behaviour
change. Remaining
low-severity noise (log-injection / empty-except / cyclic-import etc.)
is triaged and
dismissed separately with rationale — none are exploitable in this app.

## What changed (45 fixes across 40 files)

- **Real bugs** (7): dead/always-true branches and dead stores CodeQL
surfaced in
trend_hunter, event_correlator, decision_pipeline, review_agent,
debate_memory,
  alternative_bars, realtime_analyzer.
- **CI hardening**: least-privilege `permissions:` on workflows;
gitleaks action pinned
  to a full commit SHA.
- **Security**: path-containment guard in `config.py` (clears
py/path-injection);
generic error responses in 7 api_v1 routes (clears
py/stack-trace-exposure).
- **False positives cleared at source**: NaN idioms →
`math.isnan`/`pd.notna`; implicit
  string-concat → explicit; `side-effect-in-assert` → extracted.

## Verification

ruff clean · `pytest tests/unit`: **3801 passed / 0 failed** · frontend
tsc + build green.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Jcstack <Jcstack@users.noreply.github.com>

* docs: add tiered post-v2.0.0 roadmap (grounded in code audit) (Jcstack#41)

## What

Adds `ROADMAP.md` — a tiered, evidence-grounded roadmap derived from a
multi-agent audit of the v2 codebase, ordered to move the project from
**"asserted"** to **"shown"**.

- **P0 — correctness bugs** (each verified against code): preflight
circuit-breaker gate is a no-op (`preflight.py:139` never reads
persisted halt state); preflight advertises a T+1 check it never runs;
`review_agent` score-cutoff mismatch.
- **P1 — dedup / dead code / honest naming**: A-share constraints
duplicated 4×; two causal-chain engines; two data-failover paths; dead
modules (`alternative_bars.py`, `get_sector_win_rates`); overstated
`Alpha158` / "causal chains" / `IC`; stub `_sector_calibration`.
- **P2 — validation & design** (highest leverage): publish an
out-of-sample A-share backtest of the v2 stack; fix the Bayesian
likelihood derivation; close `OutcomeTracker` test gaps + trading-day
horizons; validate the LLM-debate dependency.
- **P3 — adoption**: one-command quickstart; technical write-up + demo.

Every item cites `file:line` evidence.

## Why

Captures the honest state of the project post-`v2.0.0` and the
prioritized path forward, so the work is tracked rather than tribal.

## Notes

Docs-only change. Simulation-only project; **not investment advice**.

Co-authored-by: Jcstack <Jcstack@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(backtest): bridge the v2 quant stack into the backtest engine (P2.1 scaffold) (Jcstack#43)

Implements the **P2.1** scaffold from `ROADMAP.md` — the
highest-leverage item: make the **v2 signal stack backtestable**.

## Why
The audit found the v2 signal layer (`SignalLibrary` + `RegimeDetector`)
and the A-share backtest engine were **architecturally forked** — the
OODA agent loop never touched `src/backtest/`. So the v2 stack had
*never* been run out-of-sample, and its likelihood tables / thresholds
were unvalidated. This builds the bridge.

## What
- **`src/strategy/quant_v2.py`** — `QuantSignalV2Strategy`, a
`BaseStrategy` adapter. For each bar it evaluates the v2 signals on the
**expanding window `df[:i+1]` only** (no look-ahead) and fuses the
signal consensus with an optional regime gate: buys are vetoed in a bear
regime, and strength is modulated by regime confirmation. The HMM regime
is recomputed every `regime_stride` bars and carried forward to keep the
pass O(N) fits rather than O(N²).
- **`scripts/backtest_v2.py`** — runnable CLI that drives the adapter
through the existing engine (T+1, board price limits, 100-share lots,
stamp tax) and prints metrics + an **honest buy-and-hold baseline**.
Loads history from the configured data sources, or fully offline from a
local OHLCV CSV.
- **`tests/unit/test_quant_v2_strategy.py`** — contract tests + a
**no-look-ahead** test: `generate_signals(df)[:k] ==
generate_signals(df[:k])`, which can only hold if every bar depends
solely on past data.

## Verified
`ruff` clean; 6 adapter tests pass; end-to-end smoke run on a 180-bar
synthetic series produces a real equity curve (5 round-trips, Sharpe ≈
0.49, +10pp excess vs buy-and-hold).

## Not in this PR
Publishing out-of-sample results on **real** A-share data (a chosen
universe/window) is the follow-up — that needs network/data and a
maintainer decision on the test window. This PR delivers the runnable
bridge + tests.

Simulation/research only — **not investment advice**.

---
Part of Jcstack#53

Co-authored-by: Jcstack <Jcstack@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(risk): enforce circuit-breaker halt + T+1 in preflight; name review_agent score cutoffs (Jcstack#42)

Fixes the three **P0 correctness bugs** from the post-`v2.0.0` audit
(tracked in `ROADMAP.md`). Each was verified against the code before
fixing.

## P0.1 — preflight circuit-breaker gate was a no-op
`PreflightRiskCheck._check_circuit_breaker` constructed a fresh
`CircuitBreaker()` and read its in-memory `_state`, which always
initializes to `NORMAL`. It never called
`load_from_redis()`/`is_halted()`, so a **persisted halt was silently
bypassed** — the gate always passed.
**Fix:** call `breaker.is_halted()` (loads Redis state + honors active
cooldown).

## P0.2 — preflight advertised a T+1 check it never ran
The class docstring listed "T+1 sellability" but `check()` never called
`can_sell_today`.
**Fix:** add an optional `buy_date` to `check()` and a real
`_check_t1_sellability()` that blocks selling a position opened today.
Enforced when `buy_date` is supplied; passes (with honest docstring)
when it isn't. Existing positional callers are unaffected (param
defaults to `None`).

## P0.3 — review_agent score-cutoff inconsistency
Two unexplained cutoffs (`0.65` in the LLM path, `0.55` in the fallback)
and a test whose docstring said `0.65` while it exercised the `0.55`
fallback — passing by luck.
**Fix:** name both as intentional constants (`BUY_SCORE_THRESHOLD` /
`WATCH_SCORE_THRESHOLD`), document why they differ, and rewrite the test
to assert the exact boundary.

## Tests
New regression tests: halted breaker blocks an order; same-day sell
blocked by T+1 while an earlier buy passes; no-`buy_date` skips
enforcement; fallback keeps `>= WATCH` and drops below it. `ruff` clean;
affected suites green (`test_preflight`, `test_review_agent`,
`test_circuit_breaker`, `test_execution_bridge`,
`test_recommendation_service`).

Simulation-only project; **not investment advice**.

---
Closes Jcstack#44
Closes Jcstack#45
Closes Jcstack#46

---------

Co-authored-by: Jcstack <Jcstack@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(backtest): publish honest out-of-sample results for the v2 stack (P2.1) (Jcstack#59)

Closes Jcstack#53 — the first real, out-of-sample evaluation of whether the
**v2 signal/sizing stack** predicts anything. **It does not, in this
configuration** — and publishing that honestly is the point.

## Result

Over **2019–2024** (14 sector-diverse A-shares, equal-weight), the v2
strategy returned **+25.5%** vs buy-and-hold's **+351.7%**, and **beat
buy-and-hold on 0 of 14** names.

![equity
curve](https://github.com/Jcstack/ashare-ai-analyst/blob/docs/backtest-v2-results/docs/assets/backtest-v2-equity.png)

| Window | Strategy | Buy & Hold | Excess | Sharpe | Beat B&H |
|---|---|---|---|---|---|
| Full 2019–2024 | +25.5% | +351.7% | −326.2% | 0.14 | 0/14 |
| Bull 2019–2021 | +25.2% | +410.3% | −385.1% | 0.41 | 0/14 |
| Bear 2022–2024 | +4.7% | +1.1% | +3.6% | −0.19 | 8/14 |

## What it actually is

A **drawdown-limiter that caps trend upside**, not an alpha engine:
- The engine's **15% take-profit** exits every winner early (300750/CATL
ran **+1153%** buy-and-hold; the strategy captured **+58%**). This
single rule, not signal quality, drives most of the gap.
- **~40% per-trade win rate** — the entry signals aren't separating
winners from losers.
- It only "wins" in the isolated 2022–24 downturn (8/14), and even there
mean Sharpe is negative — the edge is *avoided losses*, not skill.

This confirms the project's own honest self-assessment ("machinery real,
inputs unproven") — now **measured**, not asserted.

## Contents
- `docs/backtest-v2-results.md` — full write-up (TL;DR, regime
breakdown, per-name table, why it underperforms, next steps,
methodology, reproduce, limitations).
- `docs/assets/backtest-v2-equity.png` — equal-weight equity curve.
- `scripts/backtest_v2_universe.py` — reproducible universe runner
(table + chart; board inferred from code prefix; CSV cache for offline
re-runs). `ruff` clean.
- README + docs index links.

## Reproduce
```bash
python -m scripts.backtest_v2_universe --start 20190101 --end 20241231 --chart docs/assets/backtest-v2-equity.png
```

## Follow-ups this reframes
Exit-rule study (remove/raise the 15% TP — highest signal/noise), Jcstack#54
(Bayesian likelihood), Jcstack#56 (LLM-debate validation), broaden the universe
+ walk-forward.

Docs/script only — no `src/` runtime change. Simulation only; **not
investment advice**.

Co-authored-by: Jcstack <Jcstack@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(cleanup): remove dead code + no-op calibration dimension (P1.4, P1.6) (Jcstack#60)

Closes Jcstack#50, closes Jcstack#52.

## Jcstack#50 — dead code (all verified 0 production callers before removal)
- `src/quant/alternative_bars.py` (+ test) — orphaned module.
- `RecStore.get_sector_win_rates` — unused **and** buggy (`GROUP BY
style` while the docstring claimed per-sector).
- `OvernightRiskProfile.to_context_str` (+ test) — built but never
called. (The live `to_context_str` on `IntelChain`, used by
`recommendation_service`, is untouched.)
- Deprecated `PROFESSIONAL_ANALYSIS_FRAMEWORK` /
`QUICK_ANALYSIS_FRAMEWORK` prompt constants — superseded by the v7.0
seven-dimension framework.

## Jcstack#52 — stubs behind advertised features
- `ConfidenceCalibrator` advertised a per-sector calibration dimension
that **always returned 0.0**. Removed the dead dimension + method; kept
the (now-reserved) `sector` param and documented it honestly.
**Behavior-preserving** (it contributed exactly 0.0).
- Made `LiveBroker`'s simulation-only-by-design status explicit in its
docstring + `NotImplementedError` messages.

## Verified
`ruff check` + `ruff format --check` clean; import smoke + 366 related
tests green (overnight-risk, recommendation, calibrator, broker, quant,
prediction).

No behavior change. Simulation-only project; not investment advice.

Co-authored-by: Jcstack <Jcstack@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(agent_loop): Bayesian likelihoods + trading-day horizons + debate de-risk (P2.2–P2.4) (Jcstack#61)

Closes Jcstack#54, closes Jcstack#55, closes Jcstack#56.

## Jcstack#54 — fix the Bayesian likelihood derivation
`OutcomeTracker.get_calibration_data` set `P(signal|bull) = hit-rate`
and `P(signal|bear) = 1 − hit-rate` — conflating a **posterior**
(accuracy) with a **likelihood**, and asserting a complementarity that
only holds for posteriors. Replaced with genuine **conditional
frequencies**: the realized state is reconstructed per signal from
`direction × correctness`, and `P(bullish reading | bull)` / `P(bullish
reading | bear)` are estimated as Laplace-smoothed counts. They are real
likelihoods (need not sum to 1); `log(p_bull/p_bear)` is now positive
only for genuinely informative sources.

## Jcstack#55 — trading-day T+N horizons
`t1/t3/t5 = created_at + timedelta(days=N)` used **calendar** days, so a
T+1 lookup after a Friday landed on a non-trading Saturday. Now uses
`TradingCalendar` (N sessions, with a calendar-day fallback) and gates
each horizon on *target session ≤ today*. Adds the **first dedicated
`OutcomeTracker` unit test** (the feedback heart was only covered
indirectly).

## Jcstack#56 — de-risk the LLM-debate dependency
The pipeline hard-refused buys when the debate engine was unavailable,
so live decision quality rested entirely on an unvalidated oracle. Added
opt-in `allow_degraded_buys` (**default OFF — behavior-preserving**):
when enabled, the deterministic stack issues a *damped* buy off the
Bayesian prescreen, so a missing/failed debate degrades gracefully
instead of silently dropping signals.

## Verified
`ruff` clean; **382** agent_loop / bayesian / decision / outcome /
execution tests green, including **8 new** (3 likelihood-correctness, 2
trading-day, 3 degraded-buy).

Simulation-only project; not investment advice.

Co-authored-by: Jcstack <Jcstack@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(naming): stop overstating Alpha158 / causal chains / IC (P1.5) (Jcstack#62)

Closes Jcstack#51. **Documentation/label honesty only — no behavior change.**

- **Alpha158:** `README`, dev-guide, and the `qlib_alpha` docstring now
say "Qlib custom alpha factors". It is a hand-written ~20-factor set in
the Qlib DSL, **not** Qlib's 158-factor `Alpha158` dataset handler
(which the code never instantiates).
- **"Causal chains":** `impact_chain` is documented as hand-authored,
keyword-matched transmission **templates** (a curated rule base with
fixed confidences) — not statistically inferred causality.
- **IC:** both `qlib_worker` call sites are annotated that the value is
a serial **return autocorrelation proxy**, not a true cross-sectional
Information Coefficient. The `"ic"` key is kept for config/back-compat.

`ruff` clean; imports + 69 quant/intelligence tests green.
Simulation-only; not investment advice.

Co-authored-by: Jcstack <Jcstack@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: "How It Works" overview tying architecture to backtest results (P3.2) (Jcstack#63)

Closes Jcstack#58. A short, newcomer-friendly technical write-up of the OODA
decision loop (aggregate → Bayesian prescreen → bull/bear debate → risk
gates → Kelly sizing) and the closed calibration loop, ending with the
**honest out-of-sample backtest** (no demonstrated alpha — +25.5% vs
+351.7% buy-and-hold, 0/14) and a reproduce command. Clearly separates
what is **real** (the machinery) from what is **unproven** (the inputs).
Linked from README + docs index.

A screen-recorded demo GIF still needs a manual capture — noted as a
follow-up rather than fabricated. Docs-only; simulation-only project,
not investment advice.

Co-authored-by: Jcstack <Jcstack@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(constants): single source of truth for A-share constants (P1.1) (Jcstack#64)

Closes Jcstack#47. The lot size, commission/stamp rates, max-position cap, and
board price limits were re-declared as literal defaults in **trading**,
**strategy**, **backtest**, and **risk** modules. Introduces
`src/utils/ashare_constants.py` as the canonical source; the sites now
import the shared values instead of re-hardcoding them.

- `strategy/base.py`: `PRICE_LIMITS` imported from canonical and
re-exported (so `backtest/engine`'s `from src.strategy.base import
PRICE_LIMITS` is unchanged); numeric defaults reference
`LOT_SIZE`/`COMMISSION_RATE`/`STAMP_DUTY_RATE`.
- `trading/constraints.py`: CHINEXT/STAR prefixes + numeric defaults
from canonical.
- `risk/position_sizer.py`: `LOT_SIZE` / `MAX_SINGLE_POSITION` from
canonical.

**Behavior-preserving** — all values identical. Verified: `PRICE_LIMITS`
identity-equal across the re-export chain; 249
strategy/backtest/constraint/risk tests green; ruff clean.

**Scope note:** each module's board-*detection* logic still lives
locally (the implementations genuinely diverge — decimal vs percent
limits, differing BSE handling). Unifying that logic is a deeper,
behavior-sensitive follow-up; this PR removes the duplicated
*constants*, which was the drift hazard. Simulation-only; not investment
advice.

Co-authored-by: Jcstack <Jcstack@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(demo): one-command offline quickstart via make demo (P3.1) (Jcstack#65)

Closes Jcstack#57. Lowers onboarding friction with a **zero-setup demo** — no
Docker, API keys, or network:

```bash
pip install -r requirements.txt
make demo
```

Runs the v2 backtest on a bundled sample dataset
(`scripts/sample/demo_ohlcv.csv`, 160 synthetic bars) and prints a full
performance report in one command. README gains a "30-second / no-setup"
Quick Start step above the Docker path.

Verified `make demo` runs end-to-end offline. The full Docker stack
(`make up`) is unchanged. Simulation-only; not investment advice.

Co-authored-by: Jcstack <Jcstack@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(data): unify source-health tracking on one shared router (P1.3) (Jcstack#66)

Closes Jcstack#49. There were **two overlapping failover/health mechanisms**
with no single source of truth: the daily fetcher's inline cascade
recorded **no** health, while realtime/news each built their own
`DataSourceRouter` and `/admin` created a **third, empty** one — so
health was fragmented and the dashboard showed nothing real.

## Fix — unify on a shared router, **without touching the proven
cascade**
- `source_router.py`: `get_source_router()` process-wide singleton +
`reset_source_router()`.
- `fetcher.py`: record per-source **success (on fresh data)** /
**failure (on exception)** at the 4 cascade points. *Stale data is data
lag, not a source failure → not recorded.* Recording is wrapped
defensively so it can **never break a fetch**.
- `realtime.py` / `news_fetcher.py` / `admin.py`: default to the shared
router, so all sources feed (and `/admin` reads) one health view.
- `conftest.py`: autouse reset for test isolation (health is in-memory +
shared).

## Behavior-preserving
Cascade order / retry / freshness logic is **unchanged** — only additive
health instrumentation on a hot path, wrapped to be failure-proof.
`ruff` clean; **69** data/router/realtime/news tests green (incl. **5
new** proving the singleton is shared and the fetcher records into it).

**Scope note:** making the cascade *consult* the router to skip DOWN
sources (routing-aware failover) is a deliberate Phase-2 follow-up —
higher risk on the hot path; this PR establishes the single health brain
first. Simulation-only; not investment advice.

Co-authored-by: Jcstack <Jcstack@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(intelligence): merge the two causal-chain engines into one (P1.2) (Jcstack#67)

Closes Jcstack#48. Two parallel engines existed: `ImpactChainEngine` (in-code
templates, substring match) and
`CausalChainConstructor`/`EventImpactEngine` (YAML templates, regex +
LLM fallback). The latter is canonical (it drives the core trading
pipeline). This makes `ImpactChainEngine` a thin **adapter** over
`CausalChainConstructor` — one engine — while preserving its public API.

## How
- `impact_chain.py`: `ImpactChainEngine` delegates to
`CausalChainConstructor`; `build_chain` bridges
`CausalChain`→`ImpactChain` (bullish→positive, confidence→magnitude,
order→lag). The old `CHAIN_TEMPLATES` is kept **only** to build a
sector→stock map that re-binds `affected_stocks` — because Engine B
resolves *sectors only* — so **`find_stock_impact` keeps working** (this
was the silent-regression trap; it's defused + tested). Engine B + its
callers (aggregator / investment_director / trading_loop) are
**untouched**.
- `config/impact_chain_templates.yaml`: added
`usd_strengthen`/`usd_weaken` so USD→gold (the one capability the
in-code engine had that the YAML lacked) is preserved — the merge is
**lossless**.
- tests: `test_impact_chain.py` rewritten to the new contract (incl. a
test proving `find_stock_impact` returns stock-level impacts);
`test_usd_affects_gold` restored.

## Verified
Full unit suite **3811 passed**; `ruff` clean; intelligence sweep 234
green. `detect_event_type` is now single-match (documented semantic
shift; no caller relied on multi-match). Simulation-only; not investment
advice.

Co-authored-by: Jcstack <Jcstack@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(changelog): add [2.0.1] — post-v2.0.0 hardening (Jcstack#68)

Adds the `[2.0.1]` CHANGELOG entry summarizing the 12 commits since
v2.0.0 (honest backtest, audit-driven fixes, de-duplication,
quickstart). Docs-only; precedes the `v2.0.1` tag + release.

Co-authored-by: Jcstack <Jcstack@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: retain local startup fixes on upstream v2.0.1

* build: pin Ruff to the validated lint baseline

* fix: restore real data and reliable local workbench startup

* fix: fetch market indices only on screens that use them

* fix: keep the MCP server on its supported v1 SDK

---------

Co-authored-by: JcStack <xinjiangzuiqiang@live.com>
Co-authored-by: Jcstack <Jcstack@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P1.2] Two parallel causal-chain engines in production — merge

1 participant