Skip to content

v0.1.15 — data that says what it is

Latest

Choose a tag to compare

@warren618 warren618 released this 09 Sep 07:49

Rolls up 551 commits / 162 merged pull requests since 0.1.14, from 35
contributors.

The theme of this cycle is data that says what it is. A price frame now
carries the adjustment caliber it was served under. A factor propagates the
gaps in its inputs instead of filling them. A loader that cannot serve a
market no longer claims it, and a source that fails to refresh is an error
rather than a smaller portfolio. Three of those were the same bug wearing
different clothes: a default that silently substitutes a plausible value for
a missing one, which is indistinguishable downstream from a real
observation. Three new markets, a fourteenth broker and fifteen Quant
Library additions land alongside.

Added

  • UK equity market (#1206, thanks @cgycorey) — LSE .L and .IL
    symbols end to end: market data, Yahoo-backed financial statements and
    indicators, trade-journal inference into uk_equity, and its own engine
    path. SDRT is modelled as what it actually is — a 0.5% duty on the
    purchase side only, not a symmetric round-trip cost. Charging both
    sides overstated the cost of every round trip by half, which is exactly
    the size of edge a mean-reversion strategy lives on.
  • Zerodha Kite Connect (#1193, thanks @ashutoshsinghpr7) — a fourteenth
    broker connector for Indian equities. Kite exposes no runtime paper/live
    discriminator — no account-id format, host separation, demo flag or trade
    environment — so under the red line it is capped at paper plus read-only:
    place_order and cancel_order hard-refuse any non-paper config at the
    first line, and no *-live-trade profile exists to select. That now
    covers Longbridge, Dhan, Shoonya and Zerodha; Trading212 goes further and
    refuses all order placement including paper.
  • Read-only multi-broker portfolio (#1072, thanks @goatyyc; onboarding
    contracts in #1250) — one aggregated snapshot across every enabled
    connection instance, reachable four ways: the Web /portfolio page, REST
    under portfolio_routes.py, the portfolio_summary agent tool, and
    vibe-trading portfolio show | refresh | sources. Three design points
    that are not incidental: a source that fails to refresh is an error
    excluded from the totals
    (status error, last_success_at,
    complete=false), never a carried-forward cache, so a partial snapshot
    cannot read as a smaller portfolio; every remote_mcp read passes
    interactive_oauth=False, so aggregation can never pop an auth prompt;
    and analysis_context() carries risk_xray_args so portfolio_risk_xray
    is fed rather than reimplemented. Eligibility is one rule —
    is_portfolio_connection_profile requires readonly and account.read
    and positions.read — under which the IBKR official-MCP profile, which
    advertises only discovery, is correctly not a portfolio source.
  • Quant Library, fifteen additions (thanks @santhreal) — Heston (1993)
    stochastic volatility option pricing (#1195); Hierarchical Risk Parity
    allocation (#1196); Archimedean and Gaussian copula analytics (#1197);
    market-microstructure analytics — VPIN, Roll spread, Amihud illiquidity,
    Kyle's lambda (#1198); analytical single-barrier options with cash-rebate
    handling (#1163) plus finite-difference sensitivity Greeks for them
    (#1203); an ISDA standard-model single-name CDS valuation engine with
    hazard-rate conversions (#1164); Key Rate Duration decomposition across
    benchmark tenors (#1165); the Vasicek credit portfolio loss model and
    spread DV01 (#1167); cross-sectional factor Information Coefficient time
    series (#1166); Ornstein-Uhlenbeck exact calibration with half-life
    (#1161); drawdown distribution analytics with the Ulcer and Pain indices
    (#1162); Corrado rank and Cowan generalized sign non-parametric
    event-study tests (#1160); multi-factor risk decomposition with Euler
    marginal contributions, reporting unmatched_weight rather than
    normalising it away (#1159); and group-purged k-fold cross-validation for
    panel data (#1168). The layer's rule holds throughout: a formula has
    exactly one implementation, and skills import it rather than carrying the
    source in a SKILL.md code block.
  • Brazilian Portuguese locale (#1327, thanks @nandofmike) — the eighth
    shipped language, landing with full key parity. The parity suite globs
    locales/*.json instead of listing languages, so a new locale is covered
    the day its file lands; German had previously shipped with perfect parity
    and zero coverage because that list was hand-written.
  • Nobitex and Wallex (#1263, thanks @Emad211) — read-only Iranian
    exchange sources on keyless public UDF endpoints, explicit-source only.
    Both sit in _NO_NETWORK_FALLBACK_SOURCES and are barred from the crypto
    fallback chain on purpose: they are the only Toman-quoted sources and
    declare markets={"crypto"} purely to be reachable, so degrading an
    unavailable BTCIRT request into that chain would hand back a
    USDT-quoted series as if it were Toman.
  • Binance USD-M evidence path (#1229, #1230, thanks @honginp; #1248,
    thanks @lorenzozanee) — read-only USD-M account snapshots, drift evidence
    artifacts comparing simulated fills against recorded ones, and
    deterministic tolerance calibration derived from those recordings rather
    than assumed. The point is that the tolerance has a provenance: it is
    measured against real fills, not chosen.
  • Offline evals harness (#1271, thanks @AirHua-byte) — verifies a run's
    persisted artifacts (prompt preservation, tool calls and results,
    identity, evidence, provider usage, iteration budget, RunManifest
    integrity) against a versioned case/verdict schema, with no LLM, network,
    broker or MCP call: it reads files only. Absent instrumentation emits
    NOT_EVALUABLE rather than passing, which is the whole point — a missing
    field must not read as good behaviour.
  • Anthropic prompt caching (#1366, thanks @averatec0773) — an explicit
    cache breakpoint on the system block so the static tools+system prefix is
    read from cache instead of resent at full price, with cache usage recorded
    in the usage envelope. Native adapter only, behind
    VIBE_TRADING_ANTHROPIC_PROMPT_CACHE; set it to 0 if a compatible proxy
    rejects the cache_control request parameter.
  • Per-market data-source priority (#1231, thanks @sambazhu) — a Settings
    card and MARKET_DATA_ORDER_* env overrides that reorder a market's
    fallback chain. Validated as a multiset permutation of the default, so
    reordering passes while adding, dropping or duplicating a source is
    refused and the default stands. The override is applied as a setitem on
    the same dict object, because runner.py and market_data.py import
    FALLBACK_CHAINS by name and a rebind would leave them on the old order.
  • Calendar-triggered partial rebalancing (#1277, thanks @thisisjun786)
    and a data window separated from the evaluation window, so warm-up
    bars stop counting as evaluation and a strategy is not graded on the bars
    it needed to become defined.
  • Agent-confirmed scheduled research (#1187, thanks @AirHua-byte) and
    each monitor's latest verdict rendered on the job list (#1156, thanks
    @he-yufeng), so a recurring job's state is legible without opening it.
  • Swarm replay and retry (#1158, thanks @cgycorey; vibe-trading swarm retry --resume in #1194, thanks @SiMinus) — resume a failed or cancelled
    DAG run while keeping the artifacts of tasks that already completed,
    rather than paying for the whole graph again.
  • Order-plan rejection reporting (#1245, thanks @lorenzozanee) — an _on_plan_rejected hook plus a run-level report of
    the trades the engine wanted but could not take. A rejected plan
    previously vanished, so a strategy that was being silently throttled by
    buying power looked identical to one that simply had no signal.
  • Read-only Binance-connector crypto identity (#1242, thanks
    @pengpengyi92) and public venue-catalog pair resolution, so a crypto pair
    resolves without requiring a broker connection at all.
  • Multi-file drag-and-drop and paste uploads in the Web chat (#1179,
    thanks @AirHua-byte).

Fixed

Missing data stops being filled with plausible values

  • The Alpha Zoo NaN contract, enforced at the registry (#1377, closes
    #1376, thanks @cgycorey) — factors/base.py states the contract in its own
    header: NaN is preserved through warm-up and missing data, no silent
    fillna(0). An alpha that branches with np.where on a comparison
    violates it without looking like it does, because a NaN comparison is
    False and the ternary falls through to its constant. Blanking every
    declared input on one bar across the whole zoo, 84 of 462 alphas returned
    a number anyway
    — and since dropna() is the mechanism that keeps a gap
    out of an IC, each fabricated ±1 was consumed as a real signal.
    Registry.compute now masks the output to NaN wherever a declared
    dependency is missing on that bar, and the same sweep afterwards finds
    none. Individually fixed on the way there: gtja191_004 (#1371),
    gtja191_003 and gtja191_059 (#1372), gtja191_069 (#1373), gtja191_019 and
    gtja191_086 (#1379), alpha101_007 and alpha101_051 (#1380), alpha101_009
    and alpha101_046 (#1381), alpha101_021 and alpha101_024 (#1382),
    alpha101_010/023/027 (#1383), and alpha101_049's warm-up mask (#1374) —
    thanks @Shizoqua. Stated rather than quietly carried: the warm-up half
    is still open. Measured the same way, 52 alphas still emit a value inside
    their own declared min_warmup_bars
    (gtja191_154 declares 198 and
    emits at row 0), because on those bars the inputs are present and only the
    rolling window is undefined. A registry-level warm-up mask would also blank
    rows for alphas whose declaration merely over-declares by two or three
    bars, so that half needs a direction call rather than 52 pull requests.
  • pct_change() forward-fills by default (#1397, #1398, #1399, #1400,
    thanks @Shizoqua; #1172) — pandas still defaults pct_change() to
    forward-filling, so a missing close becomes a 0.0% return that never
    happened
    , and the next bar reports a two-day move as one day. Four PRs
    each fixed one call site; sweeping the skills exhaustively found 24 sites
    across 10 files
    , two of them inside the very files those PRs were
    editing. The class is closed repo-wide now: the SDM strategy template
    (#1397), cross-market volatility weights (#1398), multi-factor TopN
    selection which now excludes assets with no factor observations rather than
    ranking them at zero (#1399), and sentiment-factor orthogonalization which
    no longer regresses on zeros it filled in itself (#1400). Alpha Zoo returns
    stopped forward-filling gaps in #1172.
  • Survivorship-bias disclosure reaches the report (#1289, thanks
    @bonyohana) — the flag was computed and then not rendered in the HTML bench
    report, which is the same failure mode one level up: a caveat that exists
    in the data and not in what the reader sees.

Prices say which caliber they are

  • Adjustment caliber stamped on served frames (#1317, closes #1301,
    thanks @he-yufeng) — a frame now carries what its prices actually mean.
    A caliber is recorded only where it was measured against a live payload or
    pinned by the loader's own endpoint choice; anything unmeasured resolves to
    unknown on purpose, because origin-side adjustment is invisible from
    loader code — Yahoo serves split-adjusted quotes with zero adjustment logic
    in this repo.
  • The loaders that still booked a dividend as a loss (#1287, #1288,
    thanks @he-yufeng; #1320, thanks @lorenzozanee) — Yahoo, FMP and Tiingo
    paths serve adjusted prices, and an unresolved symbol now retries down the
    fallback chain instead of stopping at the first source that lacks it.
  • Explicit sources answer as themselves (#1276, #1342, #1185, #1316,
    thanks @lorenzozanee, @he-yufeng, @cgycorey) — FMP moved to the Stable
    endpoint and no longer silently falls back when named explicitly; stooq's
    anti-bot challenge is flagged rather than parsed as data, which is the
    difference between "no data" and "a JavaScript page interpreted as prices";
    codes, date range and source are validated before the fetch rather than
    after it; and symbol search is aligned with fetch for FX pairs and indexes,
    so the thing you searched is the thing you get.
  • Tencent history pagination (#1154, thanks @BigFishEmily) — long ranges
    were silently truncated to the first page.

Futures reach the right engine, and now have a source at all

  • Classification (#1369, #1389, thanks @Shizoqua; #1396, thanks
    @he-yufeng; and 349bf202 on the global side) — digit-prefixed and
    month-code-colliding product codes, Tushare's own exchange spellings
    (SHF/CZC/CFX/GFE), case folding at extraction rather than per
    lookup, and dated global contracts carrying their venue (CL2412.NYMEX,
    ESZ4.CME) each fell through every pattern to the a_share default. A US
    crude contract was therefore run under T+1, with no shorting, settled in
    CNY.
  • Coverage (#1395) — the loader chain was the last link, and it was
    empty. It named tushare and akshare, and neither implemented a
    futures endpoint
    : tushare._fetch_daily_frame branches on ETF / index /
    HK and sends everything else to daily(), the A-share equity endpoint,
    while akshare._fetch_one ends with # Default: try A-share. AKShare now
    serves Chinese contracts off the token-free Sina daily endpoints, dated and
    main-continuous, verified live across SHFE (RB2601, RB0), CFFEX (IF2512,
    T2512), GFEX (SI2601) and ZCE (MA2605). A global contract returns empty and
    reaches local rather than being priced as an equity. Trimming tushare's
    markets set alone would not have worked: resolve_loader walks the chain
    and never consults markets, so tushare would have stayed first in line.
    The main continuous contract (RB0) is routed too — a dated contract lives
    about one year, so any backtest longer than a contract cycle has to name
    the rolled series.
  • China-futures margin provenance (306aca2c, closes #1393) — a run now
    states which products were priced on a generic default instead of a table
    entry. rb's real margin rate is 0.10, the same number as the default, and
    "looked up" must not read like "assumed".

Live trading fails closed

  • Broker reads that are not answers (#1212, #1232, thanks @he-yufeng;
    #1244, #1254, thanks @cgycorey) — a position read returning an API error,
    and error envelopes arriving mid halt-sweep, were both treated as
    successful empty results. The sweep now latches only the episodes it
    actually swept, under per-episode latch files, and only after attempted
    side effects.
  • State that has to survive a restart (#1233, thanks @he-yufeng; #1213,
    #1221, #1222, thanks @Elfsa-Miranda) — the flatten latch persists across
    runner restarts, and unresolved Alpaca submissions are owned before broker
    writes and recovered by exact client ID, including fills, so a restart
    mid-submit cannot produce a duplicate or an orphan.
  • Order sizing and timing (#1312, #1361, #1253, thanks @he-yufeng and
    @cgycorey; #1209) — buy-limit orders are sized at the worse of quote and
    limit on both transports, market-triggered ticks are skipped while the
    market is closed, and Alpaca position quantity is signed by side before it
    reaches the mandate gate.
  • The mandate gate narrows in both directions (#1285, thanks @Jackzigen)
    — the change enforcing narrowing semantics for non-numeric and list
    adjustments also rejected "none" as an invalid type, which made the most
    conservative mandate unsubmittable. Nine CI checks were green over it: the
    eight new tests were all raises assertions, and a one-way suite cannot
    see that something which should have been allowed was refused.
  • Four connector gaps from the #1207 minor batch (#1388, thanks
    @he-yufeng) — an MT5 order above the symbol's volume cap was silently
    clamped and sent while the below-minimum side errored (it fails closed
    naming the cap now); an eToro limit order with no limit_price went out as
    an MIT with no TriggerRate and rested untriggered while reading as
    accepted; futu K-lines inherited an undeclared adjustment caliber; and bare
    BTCUSDT was routed through A-share rules — T+1 and no shorting, on a
    perpetual.
  • Report audit fails closed when nothing was verified (#1362, thanks
    @cgycorey) — an audit that checked zero claims returned PASS.

Backtest execution semantics

  • Options (#1299, #1305, thanks @cgycorey; #1306, thanks @he-yufeng;
    #1177) — signals fill on the next bar rather than on the date they were
    computed from, which had let a strategy trade at the price that generated
    its own signal; HV warm-up bars use the configured default IV instead of
    an undefined one; short option legs hold margin and gate opens on buying
    power, so a naked short could no longer sell premium it could never cover;
    and an explicit expiration is validated against the dates actually
    available rather than silently resolved to a neighbour.
  • Shorts, rebalancing and accounting (#1298, #1344, #1281, thanks
    @cgycorey) — 1x shorts liquidate on adverse moves in non-strict mode; an
    over-committed rebalance basket scales to fit instead of aborting on a
    commission overdraft; and requested target changes are separated from
    executed rebalance fills (closes #1275), so the rebalance count shown to a
    reader is the number of fills rather than the number of intentions.
  • Market rules through composite state (#1309, #1332, #1370, thanks
    @he-yufeng and @Shizoqua) — A-share and India rules are enforced through
    composite state rather than per-engine, a halted position marks at the last
    traded close instead of entry cost, and the sub-engine's active symbol is
    synced before every composite dispatch rather than once per bar.
  • Perpetuals and forex (#1307, thanks @he-yufeng; #1226, thanks
    @P1Piyush) — funding settles by bar span on 8h+ intervals rather than once
    per bar, and forex metals are treated as metals for pip size, lot size and
    spreads. A 50-ounce gold order had been rounded down to zero.
  • Symbol routing into the engines (#1351, thanks @cgycorey) — dotted US
    class shares (BRK.B.US, BF.B.US) fetched an empty chart.
  • Cross-market annualization (#1239, thanks @cgycorey) — risk x-ray
    honours bars_per_year=None instead of assuming a trading calendar that
    does not apply.
  • ML walk-forward label leakage (#1392, thanks @Shizoqua) — future labels
    were not purged from the training window in the example pipeline.
  • Intraday fundamentals lookahead (closes #1387) — an announcement date
    carries no time of day, so on an intraday frame a filing became visible
    from the first bar of its own announcement day: a full session of
    lookahead. Sub-daily frames are refused now, with
    fundamental_subdaily="next_day" as the explicit opt-in.

Shadow account

  • (#1217, #1310, #1311, #1314, #1356, thanks @he-yufeng) — PnL is derived
    from runner metrics and fails closed when unknown rather than reporting a
    number it cannot support; attribution is scoped to the pool currency with
    mutually exclusive buckets; short lots are modelled in FIFO pairing with
    legs restated across corporate actions; the result cache is keyed by window
    and journal hash rather than by shadow alone, so editing a journal no
    longer returns the previous answer; and cash-dividend journal rows are
    parsed and booked into real PnL.

Grounding gates

  • Both directions (#1326, #1338, #1375, #1378, thanks @cgycorey) —
    full-width brackets no longer split a clause; analysis metrics are gated on
    a completed analysis result (#1336); a metric claim formatted as a
    generic | Metric | Value | table no longer escapes the gate that rejects
    the same claim in prose; and the mirror-image error is fixed too — a price
    word used as a formula variable is not a price claim, so close/SMA50 > 1
    is no longer read as an asserted price (#1354).
  • Vocabularies completed on both sides (#1346, #1357, thanks @saju01) —
    percentage-point deltas (3.6pp, 3.6 个百分点, 250 基点) were read as
    quoted prices, and the adjacent-CJK boundary is now exercised directly.
    The English gate had been the leaking side: \bclose\b does not match
    "closed", so a fabricated The stock closed at 412.35. passed while the
    identical Chinese claim was correctly rejected. Both vocabularies are
    complete now and a language-parity suite asserts the two reach the same
    verdict per case instead of checking each in isolation.
  • Identity (#1265, #1280, #1282, thanks @aminak58; #1384, thanks
    @laiyierjiangsu; #1269, thanks @AirHua-byte; #1333, thanks @Shizoqua) —
    bare joined crypto pairs (BTCUSDT) went unrecognised while spot platinum
    resolved to a crypto pair that does not exist; FX, metals and futures
    symbols route through the right identity path (GC=F and XAUUSD had been
    classified as China A-shares); search_symbol("XAUUSD") returned exactly
    one candidate — a Swedish Bitcoin ETP — which then locked the run's
    instrument identity, so a metal or FX query is an exact instrument
    assertion now; a pasted broker code (HK.00700, US.AAPL, SH.600519)
    scanned as no symbol at all; market context stated earlier in a
    conversation can narrow resolution behind
    VIBE_CONTEXTUAL_IDENTITY_CONSTRAINTS; and generic evidence timestamps are
    preserved rather than dropped.

The agent loop keeps what it still needs

  • (#1341, #1358, thanks @saju01; #1349, thanks @he-yufeng; #1352, thanks
    @cgycorey) — the loop discarded tool results the model was still using, and
    microcompaction cleared pairs it then could not reconcile. A session that
    had successfully fetched fundamentals, fund flow, margin and research data
    had those results cleared to free context, and the de-duplication ledger
    went on refusing to re-fetch them: 44 blocked retries in one run, ending
    in "fundamental data not retrieved" for data the model had already
    received. Clearing a result now re-opens that tool, the ledger keys on
    arguments rather than tool name, results are reconciled by exact call
    identity, and the token budget counts tool-call arguments instead of
    scoring a 100 KB payload as ten tokens.
  • The no-progress budget counts observations, not activity (#1363,
    thanks @be-student) — a run that keeps issuing tool calls without a new
    successful observation stops after NO_PROGRESS_LIMIT iterations with a
    visible recovery answer and a failed terminal state. An identical call
    identity is refused from its second failure, not its first: only a
    successful mutating call clears the ledger and a research run may have
    none, so refusing on the first failure made a transient rate limit or
    timeout permanent for the whole run.
  • Run-stall watchdog and bash tree-kill timeout (#1169, thanks @wiliao)
    — plus a compaction verification ledger, so a stalled run is detected
    rather than waited on.

Providers and transports

  • (#1225, thanks @he-yufeng; #1246, #1247, thanks @lorenzozanee; #1330,
    thanks @averatec0773; #1334, #1348, thanks @Shizoqua; #1182, thanks
    @AirHua-byte) — real token usage is requested on streamed calls instead of
    being estimated; a temperature rejection self-heals on the
    OpenAI-compatible path, and the Anthropic self-heal strips the relocated
    extra_body.temperature rather than only the original position; stream
    retry delays escalate and honour Retry-After; Codex token usage and
    response model metadata are propagated rather than discarded; and shared
    HTTP clients survive cleanup instead of being closed out from under an
    in-flight request.
  • Dependency caps that were load-bearing (#1313, thanks @he-yufeng;
    4abd6cc2) — fastmcp is capped below 4.0.0 until the constructor/snapshot
    adaptation lands, and mcp below 1.30, which broke IBKR OAuth discovery. The
    second was diagnosed as a red CI on tests nothing in the diff had touched;
    adapting the fixtures would have hidden a genuinely broken broker path.

Web UI, CLI and surfaces

  • (#1257, #1258, #1259, thanks @iagop03; #1319, thanks @guestccc) — a stale
    streamingSessionId is cleared on session reopen, the main region scrolls
    and long run prompts collapse, a non-backtest run points at Studio instead
    of a blank dashboard, and the Vite dev server proxies /api so a fresh
    checkout works without a manual proxy step.
  • Session recovery across restart (#1180, thanks @AirHua-byte; #1340,
    thanks @averatec0773) — streamed assistant text is checkpointed to
    partial_response.json at ≥0.25s intervals with an atomic os.replace,
    and every pending or running attempt on disk is reconciled at service
    construction into an explicit interrupted transcript entry. The reply is
    committed before attempt.json so recovery can finish either side of that
    write. The attempt start persists too, so the elapsed clock survives
    navigation, reload and history.
  • Windows and desktop (#1261, thanks @Emad211; #1284, thanks
    @lorenzozanee; #1359, thanks @birdxs) — cross-platform locks and path
    handling, EPERM handled in the desktop updater's shutdown checks, and
    Docker actions moved to Node.js 24 compatible versions.
  • Swarm and MCP plumbing (#1175, #1331, #1176, #1335, thanks @Shizoqua;
    #1210, thanks @pengpengyi92; #1173; #1345, thanks @cgycorey) —
    cancel_run() is threaded into the in-flight worker rather than only
    marking the DAG; goal sessions are isolated per MCP connection rather than
    per process; goal_id/expected_goal_id default to the current goal;
    beginTime/endTime are forwarded through get_research_reports; worker
    retries use bounded backoff; the MCP stdio registry path has coverage; and
    investment-committee researchers get the fundamental data panel their
    prompts already assumed (#1343).
  • Portfolio OAuth lifecycle (#1211, #1251, thanks @goatyyc) — the
    reconnect lifecycle is isolated, and reconnect failures are classified
    rather than collapsed into one opaque error.
  • IBKR (#1186, thanks @sykuang; #1190, thanks @goatyyc; #1178, thanks
    @c020627) — public MCP OAuth flow support, verified read tools enabled, and
    the official read-only seed pointed at /mcp-public.
  • Futu HKD valuations (#1228, thanks @JaxonHu1024) and futu
    connection_state handling.
  • Feishu QR login credentials persist (#1188, thanks @AirHua-byte).
  • Persistent memory cleans up after itself (#1174, thanks @Shizoqua) —
    garbage collection and compression left the FTS index and the semantic
    links pointing at entries that no longer existed, so a cross-session recall
    could surface a row whose body had been collected.
  • Strategy store and discovery (#1347, thanks @ethanstoner; #1368,
    thanks @Shizoqua) — created_at ties are broken so history is really
    newest-first, and position size is resolved per regime rather than once for
    the whole run.
  • Valuation and credit refuse non-finite inputs (#1184, #1215, #1386,
    thanks @Robin1987China) — comps, three-statement inputs and every
    credit-risk function reject non-finite values rather than propagating them.
    The three Archimedean copula CDFs returned wrong extreme values under
    strong dependence — Clayton 0.0, Gumbel 1.0, Frank inf — and are
    computed in log space now, verified against a 2500-digit reference across
    θ ∈ [-5000, 5000] (closes #1385). Note that the first fix covered only
    θ > 0; θ < 0 overflowed to nan from |θ| ≈ 355 and the rewrite dipped
    below the Fréchet bound as θ → 0, both caught before merge.
  • Cross-validation edges (#1204, #1220, thanks @santhreal and
    @he-yufeng) — multi-segment test sets are supported, empty training
    partitions are guarded, and rows between combinatorial test blocks are
    pinned as still trainable.
  • Skill and documentation links (#1252, #1328, thanks @ethanstoner;
    #1189, thanks @youngjincho02-arch) — a skill-relative reference link
    resolves in read_file, the skill-name prefix is dropped so links resolve
    on GitHub, and the historical_var quantile formula in the risk-analysis
    skill is corrected. Verification note for the first two: the agent reads
    through ReadFileTool rooted at src/skills/, not through GitHub's
    renderer, so the two conventions had to be checked against the real
    consumer.
  • Dependencies (#1321, #1322, #1323, #1324) — dockerhub-description
    4.0.2 → 5.0.0, docker/login-action 3.7.0 → 4.6.0, 15 npm minor/patch
    updates and 43 pip minor/patch updates, via Dependabot.

Changed

  • The futures fallback chain is now ["akshare", "local"].
    resolve_loader walks FALLBACK_CHAINS and never consults a loader's
    markets set — the only .markets read is the explicit-source fallback —
    so trimming that set alone would have left tushare first in line,
    constructed and available on any TUSHARE_TOKEN, returning empty frames
    before akshare was ever asked.
  • tushare no longer declares the futures market. Serving it needs
    pro.fut_daily, which sits behind a points tier nothing here implements.
  • China futures main continuous contracts (RB0, IF0) now classify as
    futures rather than falling to the a_share default. The rule is
    generated from the product whitelist rather than from a width heuristic,
    so an ordinary ticker ending in 0 keeps its own market.
  • test_release_version_consistency.py did the job it was written for. The
    bump missed the app.version mock in
    frontend/src/components/layout/__tests__/Layout.test.tsx, and the guard
    named it — that site has been in its enumeration since it shipped with
    0.1.14, which is the point of enumerating declaration sites rather than
    spot-checking the ones somebody remembers. The locale check globs its
    directory, so pt-BR.json was covered the day it landed without anyone
    extending the test. The version named in a test_cli_update.py comment is
    de-versioned here — a version string in a comment has no guard at all and
    goes stale at the next bump.
  • Reader-facing counts were re-measured against the code rather than
    incremented by hand: 74 MCP tools, 107 registry tools, 10 backtest engines,
    90 bundled skills, 462 alphas, 27 data sources, 30 swarm presets, 14 broker
    connectors. None had drifted this cycle.