Changelog

Mises à jour du site et historique complet du bot (Git).

Site (mars 2026)

  • Page Changelog ; navigation.
  • Indicateur de fraîcheur (GOOD/WARN/STALE), gestion des null.
  • Dashboard : cartes métriques, tableau marché triable, barres par régime.
  • Accueil : hero, Pourquoi / Comment l’observabilité, modèle par tier.
  • Demande Tier2 : POST /api/tier2-request, rate limit.
  • Bannière compliance par langue (cookie).
  • Admin : statut snapshot + JSON brut (Tier 3).
  • SEO : OG/twitter, robots, sitemap, JSON-LD.
  • Error boundaries (error.tsx, global-error.tsx).

Changelog — moteur de trading (Git)

Chaque commit du dépôt bot (KRAKENBOTMAART). Date/heure = horodatage Git du committer. La description suit le message de commit ; le même texte s’affiche dans toutes les langues jusqu’à ajout de traductions manuelles.

Instantané généré: jeudi 19 mars 2026 à 17:05:14
415 commits
/Users/raymonddavelaar/KRAKENBOTMAART

  1. c277248
    changelog: TSL exit overhaul + stale reconcile (6040fe9..328ed8d)
  2. 328ed8d
    exit_lifecycle TSL: SL first, at breakeven cancel SL and place Kraken trailing-stop; no TP
  3. 0cda8cd
    ExitConfig use_trailing_stop; TSL time_stop_secs=900 use_maker_tp=false no TP
  4. fb49917
    Add add_trailing_stop_order in auth_ws (trailing-stop, pct trigger)
  5. 90bf2a8
    ATR-based sl_bps and trail_distance_bps=sl_bps in strategy_selector; vol_at_hold from ignition
  6. 1045e40
    Add rolling_vol_15m_bps to IgnitionMetrics and vol_at_hold_bps(max_hold_secs)
  7. 6040fe9
    Startup stale-order reconcile in live_runner (exec + full run)
  8. c0e45dd
    fix: add Kraken application-level ping keepalive to public WS feeds
    Plus dans le corps du commit
    WS protocol Ping/Pong alone is insufficient with tokio-tungstenite
    split streams. Add periodic {"method":"ping"} every 30s per Kraken
    WS v2 docs to keep connections alive and flush queued protocol pongs.
  9. 9c99d61
    fix: three blocking issues — regime, ping/pong, ownOrders
    Plus dans le corps du commit
    1. Remove global CHAOS regime from system_live_ready gate.
     Per-pair regime already blocks individual chaotic symbols; the global
     mean over 629 symbols (most illiquid) was a broken systemic blocker.
    
    2. Respond to WS Ping with Pong in ticker, trade, and L2 feeds.
     After ws.split() the write half was never used, so Pong was never
     sent. Kraken timed out connections after ~60s.
    
    3. Remove invalid ownOrders subscribe on ws-auth (not a WS v2 channel).
     Feed own_orders_cache from executions channel with snap_orders=true,
     which is the v2 replacement per Kraken docs.
  10. dab68ea
    fix: clear pending list after successful L3 subscribe retry
    Plus dans le corps du commit
    Minor: pending wasn't cleared after final retry where all symbols
    were acked, causing false L3_SUBSCRIBE_INCOMPLETE warning.
  11. d22d24d
    fix: L3 single subscribe per connection + retry rate-limited symbols
    Plus dans le corps du commit
    Per Kraken WS v2 docs: one subscribe request per connection (up to
    200 symbols), rate counter 200/s standard. Rate-limited symbols get
    automatic retry with 2s backoff (max 5 retries). Connections spaced
    6s apart to let account-wide rate counter recover.
    
    No dataset reduction. All 638 symbols across 4 connections.
  12. aaf10ad
    fix: L3 sequential connection startup + slower pacing for rate limits
    Plus dans le corps du commit
    Kraken L3 snapshot rate limit is account-wide, not per-connection.
    Previous parallel startup exhausted the rate budget immediately.
    
    Changes:
    - Connections started sequentially (wait for subscribe to complete)
    - 5s delay between connection setups
    - L3 subscribe batch: 25→10 symbols (reduce per-batch credit usage)
    - L3 batch delay: 200ms→1000ms (respect snapshot rate limit)
    - 200 symbols × 10/batch × 1s = 20s per connection, ~80s total setup
  13. 2199990
    fix: L3 multi-connection for full symbol coverage (revert universe cap)
    Plus dans le corps du commit
    The previous approach wrongly capped L3 to 200 symbols. The correct
    fix: keep all symbols, split across multiple ws-l3 connections
    (max 200 per connection per Kraken docs). Each connection gets its own
    fresh auth token + paced batched subscribe. All events fan into a
    single processing loop.
    
    638 symbols → 4 connections × ≤200 symbols each → full L3 coverage.
  14. 188a91e
    fix: L3 subscribe rate limiting + universe cap at 200 symbols
    Plus dans le corps du commit
    Root cause: L3 client subscribed 638 symbols in a single message,
    exceeding Kraken's 200-symbol-per-connection limit. Only ~44 symbols
    received data, causing frac_l3=0.365.
    
    Changes:
    - L3 client: paced batched subscribe (25 symbols/batch, 200ms delay)
     with ACK/error accounting per batch
    - Universe: hard default l3_limit=200 (Kraken documented max)
    - ingest_runner + live_runner: apply 200 cap when no env override set
  15. e0f38b8
    fix: process book messages during subscribe drain loop
    Plus dans le corps du commit
    The subscribe phase drain loop consumed book snapshot messages from
    the WS stream without processing them into books/stats, causing
    symbols_with_snapshot=0 in the health log despite books being active.
    Now book messages are also processed during ACK draining.
  16. b66fcac
    fix: L2 subscribe rate limiting + full subscribe observability
    Plus dans le corps du commit
    Root cause: L2 book feed sent 64 subscribe batches (638 symbols/10 per batch)
    in a tight loop without delay, exceeding Kraken WS rate limit. ~134 symbols
    (biased U-Z) never received snapshots, causing frac_l2=0.884 < 0.90 threshold,
    all epochs degraded, engine locked in ExitOnly.
    
    Changes:
    - Paced subscribe: 25 symbols/batch with 100ms inter-batch delay (26 msgs)
    - Subscribe ACK/error accounting: parse WsSubscribeResponse, log per-batch
    - Snapshot wait window (15s) + missing-snapshot repair with retry
    - Execution-universe-first validation: log exec L2 coverage, error if incomplete
    - Reconnect: full paced resubscribe, coverage re-measurement
    - Per-symbol observability: snapshot/delta/write tracking, 60s health summary
  17. 420dca4
    phantom position auto-correction: exchange balance is truth
    Plus dans le corps du commit
    When fresh WS snapshots confirm exchange_base_total=0 and no open
    reduce orders exist, the reconcile layer now automatically corrects
    the DB position to flat (RECONCILED_FLAT) instead of halting forever.
    
    Changes:
    - position_reconcile: add CorrectedPhantomToFlat decision variant
    - reconcile_symbol_durable: auto-correct phantom via close_dust_position
    - protection_flow: handle CorrectedPhantomToFlat as dust (no exit)
    - position_monitor: treat CorrectedPhantomToFlat same as SkipNoRealPosition
  18. 11dd384
    harden exit lifecycle status-event persistence
  19. 5d02191
    fix schema approx_row_estimate correlation alias
  20. db75658
    fix evidence query column qualification
  21. 6a65fd0
    db inventory source sanity check probe
  22. 44a4dd3
    fix bot activity probe: remove CTE scope
  23. cf36992
    fix bot activity probe CTE name
  24. ba730a5
    bot activity last 24h (execution_orders/fills/realized_pnl)
  25. ef2ea35
    duty cycle last24h on krakenbot ticker/trade samples
  26. 7b7ecec
    introspect ticker_samples columns
  27. 3d0b73c
    check ticker_samples presence 2026-03-18
  28. 7963538
    check trade_samples presence 2026-03-18
  29. 1c9079c
    introspect trade_samples columns
  30. f7c6319
    check DB has 2026-03-18 market/execution rows
  31. 8d681c7
    check execution fills/order timestamps last 24h
  32. 5275f27
    timestamp unit check v2 (ms vs seconds)
  33. dcf7f46
    timestamp unit check for ingest
  34. 38c6264
    data plane integrity probe v4
  35. e16028a
    add data plane integrity probe v3
  36. 427d4fd
    fix duty cycle in data plane integrity v2
  37. b528ecb
    add data plane integrity probe last 24h
  38. 1f2740a
    introspect public.trades columns
  39. 5fbba0c
    list potential trade tables
  40. 8292676
    fix v2 multi-run window anchored to latest data
  41. 01252f4
    fix v2 multi-run median calc
  42. 496305d
    fix v2 data_period FROM clause
  43. 7653a56
    v2 multi-run blocker context validation
  44. 936d0d2
    add multi-run context validation probe
  45. 9c4de56
    add confidence/recommended to join coverage probe
  46. d0a2d98
    fix order-join probe: count per candidate id
  47. d6e197d
    feat(alerts): pushover on ingest degraded + snapshot insert failures
  48. 87388e2
    probe v2: broaden exit-regime search
  49. ea248f7
    fix probe 35: single union query
  50. 0fc8370
    probe exit_regime_not_allowed string in candidates json
  51. 078b65e
    feat(alerts): add pushover +/-3% realized PnL triggers
  52. d7a2d7e
    fix exit-regime probe example query
  53. f2fee69
    feat(alerts): send pushover on data_stale/readiness/hard-blocked
  54. 85398ab
    add exit-regime and order-join probes
  55. f88dbc4
    introspect trading_funnel_events columns
  56. e107b4c
    fix 04: replace fill_probability with fill_flag proxy
  57. 5d5f00b
    feat(alerts): add Pushover client and config
  58. a8e5255
    fix spread/edge analyses: use public.fee_tiers
  59. 4db0cb0
    fix fees join to public.fee_tiers
  60. 3a4e909
    introspect public.fee_tiers columns
  61. 9dad3e2
    list fee tables
  62. c1aeea4
    rewrite candidate_decision_chain with real sources
  63. ace49fd
    introspect market snapshot columns
  64. cdc6a59
    list price/outcome tables
  65. d904c07
    probe overlap orders with fills vs candidates
  66. f4152ed
    fix probe semicolon
  67. 995d606
    fix probe: add FROM joined
  68. b8b378d
    probe candidate->order->fills join on eval+symbol
  69. e892689
    find latest run with candidates and fills
  70. 1b75ff5
    count rows for run_id=57
  71. 18329dd
    join coverage probe run_id=57
  72. 911ba12
    find latest run with fills
  73. f1c685c
    check fills join keys
  74. 121603c
    fix join probe: align run_id
  75. 51b763e
    probe candidate->order join keys
  76. c750e37
    fix join probe: remove taker_fee
  77. ab9d16f
    probe join coverage candidate->order->fills->pnl
  78. 8f4a1ee
    probe shadow_trades exit_reason
  79. 62f47c4
    introspect shadow_trades columns
  80. d769080
    introspect shadow_exit_analysis columns
  81. 4cc8649
    probe runtime_notrade_state samples
  82. 4dfe6da
    probe runtime_market_data_state non-numeric run_id
  83. 95e3419
    fix runtime_market_data_state probe cast
  84. 232090f
    fix runtime_market_data_state probe regex
  85. 43b6e1d
    probe runtime_market_data_state dominant reasons
  86. 4004c19
    probe execute candidate exit plan json
  87. b142df5
    probe runtime_notrade_events details_json
  88. 46bf1e8
    add extra introspection for readiness sources
  89. 565eb9d
    probe candidate rejection reasons
  90. 9d696c6
    add DB source introspection SQL
  91. 4a6cd0c
    add DB tuning analysis pipeline (SQL + runner + docs)
  92. 34669fc
    docs(changelog): record Phase A observability snapshots commit
  93. 0143d40
    feat(observability): complete Tier2/Admin read-model snapshot exports
  94. 6398d72
    refine invariant B classification and wire v2 safety report
  95. 3203f79
    Add safety invariants v2 read-only analysis and CLI report
    Plus dans le corps du commit
    Introduce InvariantKind/InvariantStatus classification SSOT in safety_invariants_v2 and a CLI report to render per-symbol A/B invariant statuses without changing runtime behavior.
  96. a158ede
    Add low-cardinality observability for reconcile, remediation, and ACK
    Plus dans le corps du commit
    Log RECONCILE_DECISION, REMEDIATION_* events, and ACK_* lifecycle around the new authority/remediation layer without changing correctness.
  97. 7634745
    Cover reserved-funds replacement ACK timeout path in tests
    Plus dans le corps du commit
    Add a dedicated test that simulates replacement ACK timeout, asserting evidence write, symbol hard block, and error outcome.
  98. 3172823
    Add tests for reserved-funds replacement ACK success/failure
    Plus dans le corps du commit
    Introduce minimal ACK source and hard-block hooks to unit-test cancel→update→submit→ACK behavior. Prove replacement happens in the same remediation cycle and that ACK failure triggers evidence + hard block.
  99. 3d3df36
    Relax readiness log test for blocked path
    Plus dans le corps du commit
    Allow blocked readiness tests to pass when only READINESS_GATE_DECISION is logged, keeping assertions stable across environments.
  100. 9564b11
    Route all exit/amend paths through reserved-funds remediation
    Plus dans le corps du commit
    Unify reserved-funds cancel→ownOrders update→re-reconcile gating across protection, position monitor, and ignition exit. Add tests for remediation sequencing and uncertainty hard-block semantics, and document the reconcile/remediation authority.
  101. 9855097
    feat(execution): remediate reserved-funds conflicts via cancel+reconcile
    Plus dans le corps du commit
    Infer reduce classification when reduce_only is missing, add ownOrders update sequencing, and perform deterministic cancel cleanup for duplicate/conflicting orders before retrying protection submits.
  102. e23f09f
    chore(git): ignore observability_export artifacts
    Plus dans le corps du commit
    Prevent generated observability exports from dirtying the working tree on local and server checkouts.
  103. 8b8a829
    feat(execution): gate exits/protection on fresh balances+ownOrders
    Plus dans le corps du commit
    Introduce a central position/order reconcile choke point with safety-first freshness rules (balances/ownOrders <= 5s) and fail-closed HALT decisions. Wire all exit/protection submit paths to respect reconcile, allowing only cancel-only cleanup without fresh snapshots.
  104. 88cdeb4
    feat(exchange): add WS ownOrders inventory cache
    Plus dans le corps du commit
    Subscribe to private WS v2 ownOrders on the shared hub and maintain an in-memory open-orders snapshot for reconcile.
  105. 20eaaf1
    fix(execution): bound execution-only ticker bootstrap to open positions
    Plus dans le corps du commit
    Limit the execution-only ticker WS subscription set to symbols with open positions so price_cache warms quickly for emergency protection without subscribing to the full USD universe.
  106. 3a56086
    fix(execution): retry emergency protection with SLA and market escalation
    Plus dans le corps du commit
    Add a background emergency retry loop with warn/escalate/hard SLA thresholds, and introduce a market-close escalation path to avoid leaving exposure pending when price_cache prerequisites do not arrive in time.
  107. 95a252d
    fix(execution): add global entry halt when emergency protection pending
    Plus dans le corps du commit
    Introduce a fail-closed entry_halt gate in live runner loops and set it at startup when unprotected exposure cannot be immediately protected (Pending/HardFail/CriticalInvariantBreach).
  108. b89e6b9
    fix(execution): classify protection prereqs and insufficient-funds breaches
    Plus dans le corps du commit
    Introduce Pending/HardFail/CriticalInvariantBreach outcomes for emergency protection, fail-closed when the private WS hub is not ready, and classify Insufficient funds into explicit invariant breach types.
  109. 41b76fa
    fix(exchange): add disconnect-safe readiness for private WS hub
    Plus dans le corps du commit
    Expose is_ready()/wait_ready() and a generation counter so execution paths can fail-closed when the shared ws-auth sender is not yet usable or has disconnected.
  110. 89bb7b8
    fix(execution): correct closed_qty on position sign flips
    Plus dans le corps du commit
    Cap realized close quantity at the pre-fill position when a fill crosses through zero, and decode nullable parent_order_id as Option<i64> to prevent NULL decode crashes during fill processing.
  111. fd3733a
    fix(cli): run report-safety-invariants on decision DB
    Plus dans le corps du commit
    The invariants audit reads execution tables (positions/execution_orders), so dispatch it via the decision pool instead of ingest.
  112. 44e5328
    fix(cli): expose report-safety-invariants mode
    Plus dans le corps du commit
    Include report-safety-invariants in cli_mode detection so the audit report runs without starting other long-running modes.
  113. 911dcc8
    feat(analysis): add hard safety invariants audit report
    Plus dans le corps du commit
    Add `krakenbot report-safety-invariants [hours]` to correlate DB exposure/exit coverage with journalctl evidence and print Invariant A/B breach rows including min_order_size from the instrument cache.
  114. 2e59baa
    fix(execution): preserve partial fills and correct realized pnl
    Plus dans le corps du commit
    Use a deterministic per-fill idempotency key (not exchange order_id) so partial fills are not dropped, treat any order with parent_order_id as an exit order, and persist realized PnL only on position reductions (linked to the root entry order).
  115. 11b7152
    diag: log exchange_balances persist success
    Plus dans le corps du commit
    Logs EXCHANGE_BALANCES_PERSIST_OK with row count and ENJ presence so we can
    prove DB-first holdings are being written from the balances snapshot.
  116. f140b2d
    diag: log private WS subscribe acks and balances snapshots
    Plus dans le corps du commit
    - Log subscribe ACK/result/error for balances + executions
    - Log first balances snapshot (asset count + ENJ presence)
    - Log first executions batch size
    
    Purpose: prove why balances snapshot is missing and enable DB-first holdings.
  117. 942bb12
    fix: do not drop early balances/executions snapshots on subscribe
    Plus dans le corps du commit
    The private WS hub previously drained two messages after subscribe, which could
    silently discard the initial balances snapshot. We now drain for up to 3s while
    still processing/broadcasting balances and executions messages.
  118. 4501f03
    fix: persist exchange_balances from balance_cache
    Plus dans le corps du commit
    Use the balance_cache (already fed by the shared private WS hub) as the
    source for writing krakenbot.exchange_balances. This avoids relying on
    broadcast message delivery timing and keeps holdings DB-first.
  119. 06012e8
    feat: persist exchange balances for DB-first exposure discovery
    Plus dans le corps du commit
    - Add krakenbot.exchange_balances table (additive migration)
    - Persist private WS balances snapshots into DB via existing PrivateWsHub (no new WS)
    - Use exchange_balances as primary holdings source in exposure_reconcile; executions remains supplemental
  120. 39b19ab
    feat: add Kraken WS v2 implementation checklist rule
  121. 07d9243
    feat: infer holdings from executions channel
    Plus dans le corps du commit
    - Add execution_holdings_cache (net per symbol) updated from private WS executions
    - Use executions-derived holdings for exposure reconciliation (no balances required)
    - Feed holdings cache in private_ws_hub
  122. 9772946
    chore: log ENJ balance sync skips
    Plus dans le corps du commit
    Adds targeted diagnostic log when an ENJ-like balance asset cannot be mapped
    into a tradable symbol during balance-driven reconcile.
  123. 0206e76
    fix: allow emergency protection without ticker price cache
    Plus dans le corps du commit
    - When avg_entry_price is known, compute stop from entry only (no price_cache needed)
    - For balance-discovered positions, recover avg_entry_price from positions table when available
    
    This enables protecting manual/external holdings (e.g. ENJ) even in execution-only mode.
  124. cd9b14a
    fix: protect manual positions without REST
    Plus dans le corps du commit
    - Revert REST usage in protection flow (strictly forbidden)
    - Normalize balance asset codes (X*/Z*/XX*/XZ*) to AAA/USD symbols for balance-driven reconcile
    - Delay startup protection briefly so price_cache has snapshots before computing stops
  125. 996ad39
    fix: autopsy script use DECISION_DATABASE_URL when set
    Plus dans le corps du commit
    Execution data (orders, fills, positions) lives in DECISION DB when
    physical separation is configured; ingest DB has no execution data.
  126. f128b05
    fix: do not close positions from stale balance check
    Plus dans le corps du commit
    The total_open_notional stale check (balance < 1% of DB position) was
    calling close_dust_position, which incorrectly zeroed open positions
    when balance_cache was stale (race with fill, or asset not in snapshot).
    
    Root cause of ENJ showing base_position=0 in DB while 349 ENJ on Kraken:
    fill processed -> position 244.72; balance_cache not yet updated ->
    stale check triggered -> close_dust_position -> position zeroed.
    
    Fix: only exclude from exposure total, do not mutate positions table.
  127. b17709c
    feat: balance-driven position discovery — protect manual/external positions
    Plus dans le corps du commit
    - Add load_positions_from_balance() to discover positions on Kraken not in DB
    - Merge balance positions into reconcile_exposure_at_startup and run_periodic_reconcile
    - Extend check_symbol_execution_lock to block entry when balance > 0 and no DB position
    - Fixes unprotected positions from manual trades (e.g. ENJ)
  128. 94b9883
    feat: add Deep Trade Autopsy script (12h live execution analysis)
  129. 5b523fa
    fix: handle Kraken exec_type filled/partial_fill in ws_handler (was falling through to UNHANDLED)
  130. df50ed8
    docs: EXIT_SYSTEM_INVENTORY + exit strategy debug/diagnose scripts
  131. fa5b54c
    scripts: add exit_strategy_inference.sql for SL-based strategy counts
  132. aa5f9f9
    changelog: add entry for 7e9ca71 (doc sync)
  133. 7e9ca71
    docs: alle leidende docs up-to-date (exit, compounding, markers)
    Plus dans le corps du commit
    - CHANGELOG_ENGINE: exit runtime, sectie 2025-03-17
    - DOC_CONSISTENCY_REPORT: exit, portfolio, compounding sync met SSOT
    - LIVE_RUNBOOK: exit lifecycle markers, order→exit flow
    - LOGGING: EXIT_PLAN_CREATED, EXIT_ORDER_ACKED, POSITION_MONITOR_*
  134. 35071f9
    docs: ENGINE_SSOT + ARCHITECTURE — exit, compounding, capital up-to-date
    Plus dans le corps du commit
    - Exit: post-fill exit_lifecycle (SL+TP) + position_monitor (trail, TP) in live runner
    - Compounding: capital_allocator.update_equity(live_eq) per evaluation
    - Portfolio allocation: live equity; allocated_quote nog niet uit positions
    - Persistent ingest, execution attach: server bewezen
  135. 5b48b3f
    changelog: add entry for 9bd11b1 (L2/L3 epoch validity)
  136. 9bd11b1
    fix: L2 cold-start + L3 partial (70%) for epoch validity
    Plus dans le corps du commit
    - L2 cold-start: criteria_l2_ok=true when >50% l2=0 (warmup)
    - L3 partial: criteria_l3_ok=true when frac_l3>=70% (Kraken rate limits)
    - EPOCH_VALIDITY_COMPUTED: add l2_cold_start, frac_l2 to log
  137. 6738335
    changelog: add entries for 967d1c6, 97cf3d3
  138. 967d1c6
    fix: L3 cold-start relaxation + shadow persistence for engine_mode_blocked
    Plus dans le corps du commit
    - ingest_epoch: criteria_l3_ok=true when >50% l3_count=0 (systemic cold start)
    - ingest_epoch: EPOCH_VALIDITY_COMPUTED logging (ok_*, frac_*, l3_cold_start)
    - strategy_pipeline: insert_shadow_trades_engine_mode_blocked()
    - docs/BOTTLENECK_FIXES_2025-03-17.md
  139. 97cf3d3
    changelog: add entry for 764eebe (restart doctrine)
  140. 764eebe
    feat(restart): unbounded execution + lineage_break NoNewEntries + RESTART_DOCTRINE
    Plus dans le corps du commit
    - LIVE_VALIDATION_RUNTIME_MINUTES=0: unbounded loop (100y deadline)
    - lineage_break: NoNewEntries i.p.v. ExitOnly (1 cycle milder)
    - LINEAGE_BREAK_GRACE + LINEAGE_POST_SWITCH_CYCLE logging voor meetmethode
    - docs/RESTART_DOCTRINE.md: doctrine, productiebeleid, meetmethode
    - L3_SCHAALBEPERKINGEN: incremental refresh + RESTART_DOCTRINE ref
    - systemd: comment LIVE_VALIDATION_RUNTIME_MINUTES=0 voor productie
  141. 9637371
    fix: partial fill SL — cancel+replace for full cum_qty, balance-based market exit
    Plus dans le corps du commit
    - Add get_open_sl_exchange_id_for_entry to cancel prior SL before placing new
    - Replace amend with cancel+replace on additional partial (amend unreliable, leaves dust)
    - Use balance_cache for TP internal market exit (was qty_f64)
    - Trailing amend and time stop use protected_qty
  142. 6e8e634
    fix: exposure reconciliation + L3 hard block bottleneck
    Plus dans le corps du commit
    Exposure (phantom positions):
    - total_open_notional now reconciles DB positions with exchange balance
    - If balance_cache says base asset < 1% of DB position, treat as stale
    - Exclude from exposure and close in DB (base_position=0)
    - Fixes GLOBAL_EXPOSURE_BLOCK when DB has positions not on exchange
    
    L3 hard blocks (589/642 symbols blocked after restart):
    - clear_l3_resync_blocks at startup: reset all l3_resync_limit_reached
     blocks so we start fresh (previous blocks were from rate-limited sessions)
    - Global cold-start: when >50% of L3 symbols have 0 rows, skip per-symbol
     blocks (Kraken rate-limiting, not symbol-specific failure)
    - Reset prev_count when hard_block_until expired: symbol gets fresh chance
     instead of immediate re-block
    - Applied in ingest_runner, live_runner (combined), run_execution_only
  143. 69da0df
    perf: persistent instrument WS — single connection, live updates
    Plus dans le corps du commit
    Replace the open/snapshot/close pattern with a single persistent WS
    connection to Kraken's instrument channel:
    
    - preload_all() connects once, receives the full snapshot (1490 pairs),
     populates the cache, then hands off the read stream to a background
     tokio task
    - The background task keeps the connection alive and processes `update`
     messages, so the cache stays fresh when Kraken changes instrument
     rules (qty_min, price_increment, status, etc.)
    - On disconnect: automatic reconnect after 10s with full re-snapshot
    - get_instrument_constraints() reads from cache only — zero WS calls
    - Removed the legacy per-symbol fetch_instruments_v2 (no callers left)
    
    Net result: exactly 1 WS connection for instruments (kept alive),
    down from N connections (opened and closed per symbol per call).
  144. b9f69a0
    perf: cache instrument constraints — single WS connection at startup
    Plus dans le corps du commit
    get_instrument_constraints opened a new WebSocket per call, fetching
    the full 600+ pair snapshot each time only to extract one symbol.
    During startup reconcile this meant 5+ concurrent WS connections for
    the same data, triggering Kraken rate limits.
    
    Now: preload_all() fetches all pairs once via a single WS connection
    and caches them in-memory. Runtime callers read from cache with zero
    network overhead. Cache-miss falls back to single-symbol WS fetch and
    backfills the cache.
    
    Called at both startup paths (run_execution_live + run_execution_only)
    before exposure reconciliation, so protection_flow and dust detection
    have constraints available immediately.
  145. d8c099d
    fix: use notional-based dust threshold instead of per-symbol WS call
    Plus dans le corps du commit
    get_instrument_constraints opens a WS per symbol — too expensive for
    the hot path (total_open_notional runs every evaluation cycle). Replace
    with a simple $5 notional threshold: positions below this are untradeable
    dust regardless of exchange-specific minimums.
  146. 47eacfd
    fix: unblock execution — dust exposure exclusion, protection retry, shadow persistence
    Plus dans le corps du commit
    Proven bottlenecks from live data (100% order block rate):
    1. Dust positions (CFG, RARI, XPL) below exchange min order qty consumed
     ~$9 of $51 exposure budget despite being untradeable. Now excluded
     from total_open_notional, consistent with EXPOSURE_DUST_SKIP.
    2. Protection flow failed with "Insufficient funds" for NPC + TRIA
     because DB qty exceeded actual exchange balance. Added balance-cache
     retry: on Insufficient funds, fetches real available qty and retries.
    3. Dust positions in DB kept status=open forever. Now auto-closed to
     base_position=0 during startup reconciliation when below min order.
    4. V2 pipeline shadow trades were only logged, not persisted. Added
     insert_v2_shadow_trade for edge_floor_block and risk_gate skips.
  147. 58a821c
    observability: no-trade reason tracing — per-symbol, per-strategy, shadow-equivalent logs + CLI
    Plus dans le corps du commit
    5 observability additions (no strategy logic changes):
    
    1. V2_PIPELINE_SCOPE log: tradable_in_report, exec_allowed_count, tradable_in_scope,
     filtered_by_universe at pipeline entry (strategy_pipeline.rs)
    
    2. NO_TRADE_DOMINANT_REASON log per evaluation when no Execute outcomes: single
     dominant reason (data_runtime_not_ready / no_positive_edge_any_symbol /
     tradable_filtered_by_universe_or_freshness / all_candidates_skip_economics)
     with tradable_in_report, exec_allowed count, skip count (live_runner.rs)
    
    3. SYMBOL_ROUTE_DECISION log per symbol after route analysis: outcome (TRADABLE
     with route details or NO_TRADE with reason), edge, score, valid/total candidates,
     top 3 reject reasons (route_selector.rs + route_selector_v2.rs)
    
    4. report-no-trade-reasons CLI: per-symbol decision table, per-strategy funnel
     (in_scope, tradable, no_trade, top 3 reasons), aggregate reject histogram,
     near-miss symbols (analysis_commands.rs + commands.rs)
    
    5. V2_SHADOW_BLOCKED log for every pipeline Skip (edge_floor or risk_gate):
     symbol, blocker_reason, route_family, edge, confidence, spread, expected_move
     (strategy_pipeline.rs)
  148. 742db2f
    development rules: expliciet dat alle wijzigingen ook op de server moeten staan
  149. e18fba6
    changelog: volgorde nieuwste bovenaan; bf87455 toegevoegd aan lijst
  150. bf87455
    changelog: add entry for d003854
  151. d003854
    changelog: volledig chronologisch (oudste eerst), alle 264 commits herleidbaar vanaf e4182b3
  152. e8a8486
    changelog: add entry for 20f2f4c
  153. 20f2f4c
    changelog: add entry for 51abf53
  154. 51abf53
    changelog: add entry for d9734b9
  155. d9734b9
    changelog: add entry for bfadca3
  156. bfadca3
    changelog: add entry for ea94e6e (changelog + RAG spec)
  157. ea94e6e
    changelog: document commits 8dceb90..8a4261a (execution, exit, hub, recv_timeout); add RAG backend spec doc
  158. 8a4261a
    fix: recv_timeout Hub use remaining time on Lagged retry
    Plus dans le corps du commit
    Track deadline once and use saturating_duration_since(Instant::now())
    for each retry so total wait never exceeds requested timeout. Prevents
    nearly-doubled wait for exit monitor (250ms) and SL trailing under load.
  159. 157c238
    feat: hub-aware protect_exposure and ignition_exit via PrivateMsgSource
    Plus dans le corps du commit
    protection_flow: accept hub: Option<&PrivateWsHub> — when provided reuses the
    shared private WS connection instead of opening a new one.
    ignition_exit: switch from UnboundedReceiver to PrivateMsgSource so the hub
    broadcast path is supported and WS disconnect no longer hard-fails (logged, not
    bailed).
    live_runner: pass Some(&private_hub) to protect_exposure and ignition_exit at
    all four call sites.
  160. 6d91c91
    execution: protection after exit fill + exit_lifecycle on PrivateMsgSource
    Plus dans le corps du commit
    - exposure_reconcile: load_open_exit_qty_per_symbol uses remaining qty
     (quantity_base - cum_qty_base) so partial fills on exit orders are
     reflected; add ensure_protection_after_exit_fill() and call
     protect_exposure for remainder when needed (skip dust).
    - ws_handler: HandleResult::Applied gains exit_fill_symbol for exit-order
     fills so runner can trigger protection for remainder.
    - runner: on exit_fill_symbol set, call ensure_protection_after_exit_fill
     (pool, run_id, symbol, api_key, api_secret, hub).
    - exit_lifecycle: take PrivateMsgSource instead of UnboundedReceiver,
     use recv_timeout() throughout so hub and dedicated paths both work.
    - Add private_msg_source module and wire in mod.rs.
    - deterministic_proof: match HandleResult::Applied with exit_fill_symbol.
  161. 5ce3547
    fix: use UnboundedReceiver directly in exit_lifecycle (no PrivateMsgSource)
    Plus dans le corps du commit
    Replace PrivateMsgSource abstraction with tokio::sync::mpsc::UnboundedReceiver<PrivateV2Response>
    directly — matches the server's runner.rs call sites and avoids the untracked
    private_msg_source.rs dependency. All recv calls use tokio::time::timeout wrapping.
  162. 20bd5a9
    feat: maker TP exit — dual-order monitoring with 30s fill-check
    Plus dans le corps du commit
    Place a post_only limit order at TP price alongside the SL on exchange.
    WS executions channel determines which order fills first:
    - TP fill: cancel SL, exit_reason=tp_maker
    - SL fill: cancel TP limit (cleanup)
    - 30s fill timeout or time_stop/panic: cancel TP + cancel SL, market exit
     on balance-size from WS balance_cache (no REST)
    
    Activated for TSL and MakerLadder strategies; graceful degradation to
    internal TP check on post_only rejection. All via WS only; no REST calls.
  163. 730e7a5
    feat: model maker TP exit in fee and route expectancy
    Plus dans le corps du commit
    Add use_maker_tp to ExitConfig; model exit fee as maker_bps when TP is
    placed as a post_only limit order. Update fee_realism.rs, route_selector_v2.rs,
    and route_expectancy.rs so TakeProfit routes reflect the lower exit cost
    in expected_net_edge_bps, reducing taker fee drain and improving tier economics.
  164. da16a5c
    observability: demo_trades uit echte fills, tier2_* + admin snapshots (100% oplevering)
  165. 1fedf48
    feat: single persistent private WS hub — 1 connection for all consumers
    Plus dans le corps du commit
    Architecture:
     Auth WS (1 connection, stays open hours/days)
     ├── subscribe: executions + balances
     ├── order submit / amend / cancel (via shared Arc<sender>)
     ├── execution reports → broadcast to position_monitor + others
     ├── balance updates → balance_cache (live equity)
     └── auto-reconnect on disconnect
    
    Before: 3 separate private WS connections (balance_feed, position_monitor,
    main execution). Each consumed a Kraken connection slot, causing persistent
    429 rate limit errors.
    
    After: 1 shared connection. PrivateWsSender uses interior mutability
    (parking_lot::RwLock) for hot-swap on reconnect. Broadcast channel
    distributes incoming messages to all consumers.
    
    Also includes: breakeven floor + 0.7% trail for exit management.
  166. 8a5f5ee
    fix: breakeven floor, tighter trail, staggered WS startup
    Plus dans le corps du commit
    Exit management:
    - Trail distance capped at 0.7% from high watermark (was 1.5%)
    - Breakeven floor at 30bps: SL never goes below entry once in profit
    - TP lowered to 200bps (was 250) for faster capture
    - Applied to all three exit paths (monitor, lifecycle, ignition)
    
    WS reliability:
    - Staggered WS connections at startup (3s delays) to avoid 429
    - Initial backoff raised to 5s (was 1s), max to 120s (was 60s)
  167. c1b67e9
    fix: breakeven floor + 0.7% trail — stop giving back profit
    Plus dans le corps du commit
    All three exit paths (position_monitor, exit_lifecycle, ignition_exit)
    now enforce:
    - Trail distance capped at 0.7% from high watermark (was 1.5%)
    - Breakeven floor: once peak profit exceeds 30 bps, SL is floored
     at entry price — profit move is never fully surrendered
    - TP lowered to 200 bps (was 250) for faster profit capture
    
    Before: TRIA peaked +121 bps but SL trailed to 0.03122 (below entry
    0.03131). Profit was surrendered entirely.
    After: SL would be max(0.03169*0.993, 0.03131) = 0.03147 = +0.5%
    above entry, locking in profit.
  168. 9f79b88
    fix: feed position symbols through main ticker WS, remove separate WS
    Plus dans le corps du commit
    The separate position ticker WS was hitting Kraken's 429 rate limit
    (too many concurrent WS connections). Now position symbols are added
    to the main ticker WS subscription at startup, ensuring price_cache
    always has data for open positions without an extra connection.
  169. a6cb13c
    fix: tighten position monitor trail — 1.5% distance, immediate activation
    Plus dans le corps du commit
    Was: 3% trail distance, only activates after 1.2% profit (40% of 300bps).
    Now: 1.5% trail distance from high watermark, activates immediately
    when position is in any profit. Much tighter risk control — SL stays
    close to current price instead of sitting far below entry.
    
    For TRIA at +0.25%: SL moves from 0.03057 (-2.4%) to ~0.03092 (-1.5%).
  170. d2940d8
    fix: add periodic status logging to position monitor
    Plus dans le corps du commit
    Logs PnL, current price, SL price, and highest price for each
    monitored position every 60s for diagnostics.
  171. c42357a
    fix: position monitor gets own ticker WS for position symbols
    Plus dans le corps du commit
    The main ticker WS only subscribes to evaluation-universe symbols,
    leaving active position symbols (TRIA, NPC, etc.) without price data
    in the price_cache. The monitor now spawns a dedicated public ticker
    WS that subscribes specifically to symbols with open positions,
    feeding price_cache so trail/TP evaluation actually works.
  172. 247194a
    feat: active position monitor — trail SL + TP for all open positions
    Plus dans le corps du commit
    Background task that actively manages ALL positions (not just current-run trades):
    - Persistent private WS for order amendments
    - Reads prices from price_cache (no REST)
    - Trails SL when price moves >40% of SL distance in favorable direction
    - Takes profit at market when unrealized PnL exceeds 250 bps
    - Detects SL triggers/fills via executions channel
    - Refreshes position list from DB every 30s
    - Auto-reconnects on WS disconnect
    
    Fixes the gap where reconciled/legacy positions only got a static
    emergency SL with no trailing, TP, or active management.
  173. 6bbffb6
    fix: live equity, dust detection, SL trigger detection, exposure calc
    Plus dans le corps du commit
    - Live equity via WS balances channel (balance_cache + balance_feed):
     replaces static EXECUTION_EQUITY_QUOTE, enables compounding.
     CapitalAllocator and exposure gate use live equity each cycle.
    - Dust detection: positions below exchange min_order_size are marked
     as dust and skipped in protection_flow (no more failed SL attempts).
    - protection_flow uses price_cache instead of REST for bid/ask.
    - Exposure calculation uses positions × live prices instead of stale
     execution_orders records that can strand in non-terminal status.
    - Critical: SL trigger detection now recognizes "triggered" status
     and exec_type from Kraken WS — stop-loss orders transition to
     "triggered" (not "filled") when the trigger price is hit.
     This was causing the bot to miss SL executions and fall through
     to time_stop, wasting hold time and creating orphan exposure.
  174. e9e7a80
    feat: 24h blocklist for NL-restricted symbols
    Plus dans le corps du commit
    Symbols rejected with "restricted for NL" or "Invalid permissions" now
    enter quiet mode for 24 hours instead of the default ~2 minutes. Stops
    wasting evaluation cycles on permanently unavailable pairs.
  175. 504f430
    fix(critical): SL order ID mismatch — match on cl_ord_id, not first ACK
    Plus dans le corps du commit
    wait_for_next_add_order_result was picking up stale entry-order ACK
    instead of the SL order response, causing the exit lifecycle to monitor
    the wrong exchange order. Fixed by matching on result.cl_ord_id.
    
    Also: amend_stop_loss_trigger now passes order_qty (Kraken API requires
    it), and add_stop_loss_order now logs the sent JSON.
  176. 6baef85
    fix: start ticker WS in execution-only mode for price_cache
    Plus dans le corps du commit
    The SL-only exit model needs live prices from price_cache for trailing
    and internal TP checks. The execution-only loop was missing the ticker
    WS that feeds this cache.
  177. 8dceb90
    refactor: SL-only exit model — no TP on exchange, trail SL trigger_price
    Plus dans le corps du commit
    Eliminates double-execution risk by keeping only the stop-loss on Kraken.
    TP decisions are made internally using in-memory price cache (no REST).
    SL is trailed via amend_order trigger_price (max 1/sec).
    Both exit_lifecycle.rs and ignition_exit.rs converged to same model.
  178. ba47407
    docs: add exit lifecycle horizon-aware SL/TP fix to changelogs
  179. 64a9dd9
    fix: use horizon max_hold_secs for TSL routes instead of 0
    Plus dans le corps du commit
    Trailing stop routes set max_hold_secs=0 in the route candidate (no fixed
    time limit). The exit config builder needs the horizon's actual hold time to
    set a proper time_stop, so use horizon.max_hold_secs() from the pipeline
    and guard against 0 as fallback.
  180. 1fe5f03
    fix: make exit SL/TP/time_stop horizon-aware instead of fixed values
    Plus dans le corps du commit
    The TSL exit config had SL=-60bps and time_stop=30s, which for breakout
    continuation/medium routes (expected hold: 600s, expected move: 100-300bps)
    meant the stop loss was inside the spread and positions were dumped after
    30 seconds before the breakout could develop.
    
    Changes:
    - Add max_hold_secs and expected_move_bps to Outcome, wire from V2 pipeline
    - Scale SL to 50% of expected_move (min 100, max 500 bps)
    - Scale TP to 150% of expected_move (min 150, max 1000 bps)
    - Set time_stop from route horizon instead of fixed 30s
    - Fix direction bug: SL/TP now correctly flip for short positions
  181. 3b9cf89
    fix: restore TSL capital protection gating for BreakoutContinuation
    Plus dans le corps du commit
    VolatilityTrailingStop was unconditionally allowed for breakout routes,
    contradicting the exit regime's capital protection doctrine which
    restricts TSL in volatile/reversal-prone markets.
    
    Root cause: the V2 regime matrix generates TSL candidates for
    VolatilitySpike breakouts, but the V2 admission override only handled
    EdgeNegative and MoveBelowFees — not ExitRegimeNotAllowed. This
    caused every VolatilitySpike breakout to be permanently blocked,
    which prompted the unconditional TSL workaround.
    
    Proper fix:
    1. Restore TSL gating for BreakoutContinuation (TSL only in non-
     volatile conditions, consistent with PullbackContinuation)
    2. Add ExitRegimeNotAllowed to V2 admission override conditions
    
    Now V1 capital protection is the default (TSL blocked in explosive
    markets), while V2 can override with quantitative justification
    when multi-scenario tail math is positive.
  182. 1708761
    fix: add 1% tolerance to final notional check for step-size rounding
    Plus dans le corps du commit
    After instrument step-size normalization, the final qty*price can
    be fractionally below the minimum notional (e.g., $9.9999 < $10.00)
    due to floating point truncation. This blocked orders that were
    effectively at minimum. Adding 1% tolerance prevents false rejections
    while still catching genuinely undersized orders.
  183. 0ab6a7e
    fix: use Kraken-compliant Short UUID for cl_ord_id
    Plus dans le corps du commit
    Kraken WS v2 accepts cl_ord_id in three formats: Long UUID (36
    chars with hyphens), Short UUID (32 hex chars), or Free text (max
    18 chars). The previous format "kb" + prefix + UUID simple = 35
    chars didn't match any valid format, causing
    EGeneral:Invalid arguments:cl_ord_id rejections on every order.
    
    Now generates pure UUIDv4 simple (32 lowercase hex chars) which is
    a valid Short UUID format. Also fixes the same issue in probe
    modules and intent.rs fallback path.
  184. 436a072
    fix: recalibrate exit regime thresholds for enriched vol_proxy
    Plus dans le corps du commit
    The exit regime selector used HIGH_VOL_THRESHOLD=2.5 and
    ORDERLY_VOL_MAX=2.0, calibrated for legacy microprice deviation
    (0-10 bps). With ignition-enriched vol_proxy (rolling realized vol,
    10-100 bps), every pair was classified as "volatile", blocking
    VolatilityTrailingStop for BreakoutContinuation and PullbackContinuation.
    
    This contradicted the regime route matrix which specifies TSL for
    vol_spike routes, causing all candidates to get ExitRegimeNotAllowed.
    
    Recalibrated to: HIGH_VOL=50, IMPULSE=3.0, ORDERLY_VOL_MAX=40.
    Also: TSL is now always allowed for BreakoutContinuation (natural fit).
  185. c4b5bef
    fix: add medium-horizon routes for vol_spike and trend regimes
    Plus dans le corps du commit
    VolatilitySpike previously only had Short routes where sqrt(3)=1.73x
    time factor couldn't produce moves that clear the 70 bps taker fee
    threshold. Medium horizon with sqrt(10)=3.16x enables breakout
    continuation to reach 95+ bps expected moves for vol_proxy >= 20 bps.
    
    Also adds medium BreakoutContinuation/MakerFirst to TrendContinuation
    and medium PumpFade/MakerFirst to VolatilitySpike for better fee
    efficiency (55 bps maker vs 70 bps taker roundtrip).
  186. a9ff78c
    fix: momentum routes use sqrt-time scaling + reactive vol_proxy
    Plus dans le corps du commit
    Two critical fixes for the edge calculation chain:
    
    1. enrich_with_ignition now uses max(rolling_vol_5m, rolling_vol_30m)
     instead of only 30m — reactive to recent spikes while keeping
     the longer-window floor.
    
    2. Momentum path builders (pullback, breakout, pump_fade, dump_reversal)
     now use sqrt(hold_minutes) as time factor instead of the fixed
     horizon_factor (0.4/1.0/2.2). vol_proxy is a per-minute metric;
     expected cumulative moves scale as sqrt(time) per volatility theory.
     This produces realistic 40-120 bps expected moves for active markets
     instead of the previous 10-15 bps that could never clear fees.
    
    PSC (spread capture) keeps its existing horizon_factor logic unchanged.
  187. c1a6f3d
    fix: V2 adaptive engine now uses ignition-enriched vol_proxy
    Plus dans le corps du commit
    The V2 engine was using raw microprice_deviation_bps (~2-5 bps) as
    vol_proxy instead of rolling_vol_30m_bps (50-500 bps in active markets).
    This made expected_move_bps perpetually too low to clear the 55 bps fee
    threshold, resulting in tradable=0 even during high-volatility markets.
    
    Now bootstraps ignition metrics and enriches features (vol_proxy,
    trend_strength, impulse_magnitude) before regime classification and
    expected path building, matching V1 behaviour.
  188. 96ab18b
    throughput: fix execution reliability + unlock trade frequency
    Plus dans le corps du commit
    - Fix critical bug: Kraken add_order WS method response errors were
     only logged (warn) but never handled — order stayed in PendingSubmit
     for 60s timeout instead of immediately transitioning to Rejected.
     Now: on_reject + tracker update + funnel event + quiet mode + break.
    
    - Widen route map: Quiet state adds PullbackContinuation, Compression
     adds BreakoutContinuation. All existing edge/confidence/economics
     gates still apply — only the candidate pool expands.
    
    - Wire EDGE_ENGINE_V2 config flag into live pipeline. When enabled,
     run_adaptive_route_analysis (V2 admissibility with tail-positive
     and best-scenario gates) replaces static V1 route analysis.
    
    - Allow up to 2 executes per evaluation cycle (was 1). Each candidate
     individually passes symbol lock, capital allocator, exposure gate,
     and choke. No risk gates bypassed.
  189. 88c3db6
    fix: align observability snapshot contract with L3 availability semantics
    Plus dans le corps du commit
    Add l3_symbol_count alongside total l3_count and propagate it through stats queries, export generation, and the snapshot schema. This makes L3 availability percentage derivation explicit and consistent with the documented contract.
  190. 14ed8d6
    fix: unblock execution sizing and surface signal blocker reasons
    Plus dans le corps du commit
    Always derive market-order quantity from intended quote notional and upscale to satisfy min notional/cost constraints, reducing submit rejections like FINAL_NOTIONAL_TOO_SMALL and MARKET_ORDER_COST_MIN. Also persist signal-stage reason on discovered outcomes so funnel blocker attribution no longer falls back to unknown.
  191. daf8ecf
    fix: classify no-outcome blockers and expose signal reasons
    Plus dans le corps du commit
    Classify empty pipeline cycles into explicit funnel blocker reasons (no_tradable_routes, no_feature_complete_symbols, no_pipeline_outcomes) and print signal blocker reason counts in report-trading-throughput. This makes throughput bottlenecks visible before admission/sizing stages.
  192. f91ac7d
    fix: add run-id fallback for V2 unlock section
    Plus dans le corps du commit
    Use run_symbol_state as fallback when freshest active run_id is unavailable on the decision DB so report-trading-throughput can still compute V2 unlock metrics.
  193. 12fd7ba
    fix: ensure funnel telemetry flows during no-trade cycles
    Plus dans le corps du commit
    Emit signal/admission funnel events for no-outcome and skip-only evaluation cycles in execution-only mode, so throughput reporting no longer stays empty when no order is submitted. Also extend V2 unlock reporting with regime/route-family edge distribution splits.
  194. 41d2efa
    feat: add throughput funnel telemetry and report-trading-throughput
    Plus dans le corps du commit
    Build a DB-backed trading funnel telemetry layer across signal, admission,
    capital, execution, and fill stages. Add structured events and persistence via
    trading_funnel_events, and introduce report-trading-throughput with 2h funnel
    metrics, drop-off analysis, regime/route-family breakdowns, and V2 unlock
    estimates (extra trades/hour, extra fills/hour, edge distribution).
    
    Also wire route_family through pipeline outcomes and use a combined
    priority score (ignition + tail asymmetry + freshness bonus) for V2 ranking.
  195. acd92f3
    docs: align doctrine docs with live ignition trading
    Plus dans le corps du commit
    - Document event-driven evaluation wake (EVAL_WAKE_IGNITION)
    - Document mid-trade exit re-routing via ExitMode switching
    - Document capital allocation gate and log evidence
  196. 27199f5
    feat: doctrine-level trading architecture — 3 critical categories
    Plus dans le corps du commit
    1. Mid-trade re-routing: exit lifecycle is now a state-driven decision
     loop. ExitMode (Trailing, AggressiveTrail, ContinuationHold,
     ExhaustionExit, ImmediateExit) is re-evaluated every 250ms based on
     live ignition state. Mode transitions trigger trail tightening,
     time-stop extension (Continuation +120s), or immediate market exit
     (state regression to Quiet). All mode changes are logged as
     EXIT_MODE_REROUTED events.
    
    2. Event-driven evaluation: IgnitionMetricsStore now fires
     transition_notify when metrics cross ignition thresholds on minute
     boundaries. The evaluation loop uses tokio::select! to wake
     immediately on ignition events instead of waiting for the full
     sleep interval. This captures alpha from state transitions without
     latency loss.
    
    3. Capital allocation doctrine: new CapitalAllocator with per-regime
     slot limits (Ignition:2, Expansion:2, Continuation:2, Other:1),
     regime-bucketed capital budgets (30/25/25/10%), edge-weighted
     sigmoid sizing, liquidity-aware scoring, drawdown throttling, and
     symbol deduplication. Wired into both live and execution-only loops
     with register/release lifecycle.
  197. 1e7fbc6
    fix: comprehensive trading execution fixes — 8 critical bugs resolved
    Plus dans le corps du commit
    1. Pass live IgnitionMetricsStore to exit context (was empty/new store)
    2. Fix market order qty: notional→base conversion via best bid/ask
    3. Fix cost_min bypass for market orders (price=None skipped check)
    4. Add minimum notional gate ($10) in submit_order pre-submission
    5. Make equity configurable via EXECUTION_EQUITY_QUOTE (was hardcoded 1000)
    6. Persist IgnitionStateMachine across exit loop iterations (hysteresis)
    7. Persist panic exit orders to DB + update SL/TP status on panic
    8. Track last_known_price for accurate EXIT_COMPLETED realized PnL
  198. bedf58c
    doctrine compliance audit: observability, structural fixes, validation report
    Plus dans le corps du commit
    Phase 1 — Observability:
    - Add IGNITION_STATE_TRANSITION per-symbol event with metrics (state.rs)
    - Add TRADING_ENTRY_DECISION event before order submission (live_runner.rs)
    - Replace ORDER_SUBMITTED_TO_EXCHANGE with ORDER_LIVE_SUBMIT including
     all required fields: side, price, qty, strategy_context, ignition_state
    - Add EXIT_PLAN_CREATED with exit_mode, sl_price, tp_price, trailing_enabled,
     trail_distance_bps to both ignition_exit and exit_lifecycle
    - Add TRAIL_UPDATE event with highest_price, new_tp, amend_success, retry_count
    - Add EXIT_COMPLETED to all exit paths (panic_pnl, market_fill_timeout)
    
    Phase 3 — Structural fixes:
    - Log exit order insertion failures instead of silent discard (7 sites)
    - Conservative fallback on symbol_lock_check failure (block instead of allow)
    - Consolidate symbol execution lock: check ALL open orders regardless of side
    - Full EXIT_COMPLETED coverage on all termination paths
    
    Phase 4 — Validation CLI:
    - New report-trading-doctrine-alignment command with 10 diagnostic sections:
     trades per state/strategy, edge capture, exit reasons, exit order persistence,
     trail efficiency, re-entry violations, position check, safety state, compliance
  199. 4686ad6
    production safety: implement ignition trading architecture v1
    Plus dans le corps du commit
    14 structural risks identified in audit, all addressed across 5 phases:
    
    Phase 1 - Safety Foundation (R1-R7, R9):
    - Persist exit orders (SL/TP/trail/time-stop) to execution_orders with parent_order_id
    - Enforce MAX_OPEN_NOTIONAL_PCT_OF_EQUITY (10%) as hard gate before submission
    - Enable circuit breakers: drawdown $50 default, 10 orders/min rate limit
    - Wire panic PnL exit (-200bps) in ignition trailing loop
    - Re-enable cl_ord_id in kraken_adapter for order correlation
    - Fix partial fill under-protection: track cum_qty and amend SL qty
    - Sync symbol_safety_state hard_block from ingest to decision pool
    - Market fill timeout returns Err and triggers protect_exposure
    
    Phase 2 - Exit Robustness (R8, R10):
    - amend_order retry (3x, 500ms backoff) with cancel+replace fallback
    - WS disconnect during exit marks orders suspect and returns Err
    
    Phase 3 - Latency Reduction (R12):
    - Adaptive evaluation interval: 60s when ignition-active, base otherwise
    - IGNITION_ADAPTIVE_INTERVAL_ENABLED env var (default true)
    
    Phase 4 - Telemetry (R13, R14):
    - Unified LIVE_DATA_MAX_AGE_SECS via env var across all freshness checks
    - Structured EXIT_COMPLETED event with hold_secs, pnl_bps, exit_reason
    
    Phase 5 - Edge Optimization:
    - Reduce trail check interval from 1000ms to 250ms
  200. 4f1e3d8
    contract: all CLI reads use ingest pool + freshest_active_run_id
    Plus dans le corps du commit
    Structural audit fix: CLI analysis/report commands now use
    pools.ingest() instead of pools.decision() for raw data reads.
    All 25 latest_run_id() calls in analysis_commands.rs replaced with
    resolve_run_id() which prefers freshest_active_run_id (active ingest
    data within 5 min) and falls back to latest_run_id.
    
    Contract: ingest pool for market data, decision pool for execution,
    freshest_active_run_id for current run resolution.
  201. 1a0b244
    fix: propagate correct run_id through entire readiness/pipeline chain
    Plus dans le corps du commit
    run_readiness_analysis now prefers freshest_active_run_id over
    latest_run_id. run_strategy_pipeline uses run_readiness_analysis_for_run
    with the caller-provided run_id instead of re-querying latest_run_id.
    This ensures the entire one-shot chain uses the same fresh run_id
    from the active ingest, not the highest-ID execution_live run.
  202. 49ca7be
    fix: use freshest active run_id for one-shot commands instead of MAX(id)
    Plus dans le corps du commit
    latest_run_id() returns the highest run ID, which may be an
    execution_live run with no fresh data. The active ingest runner
    writes to an older run_id. New freshest_active_run_id() finds
    the run with ticker data within the last 5 minutes. Falls back
    to latest_run_id() when no fresh run exists.
  203. d99bcf9
    fix: use ingest pool for readiness/pipeline in one-shot commands (physical separation fix)
    Plus dans le corps du commit
    run_execution_once and run_proof were passing the decision pool to
    run_readiness_analysis and run_strategy_pipeline, which query
    ticker_samples/trade_samples/pair_summary_24h — tables that only
    exist in the ingest database under physical separation. This caused
    data_stale=true and tradable_count=0 for all one-shot CLI commands.
    
    Now: ingest pool for data reading, decision pool for execution writes.
  204. 0db331d
    docs+scripts: classify SSOT/current/background, update DOC_INDEX, mark legacy scripts
  205. 82f4494
    fix: persist ignition metadata (state, edge_before/after, trail_mode) to execution_orders
    Plus dans le corps du commit
    Added update_ignition_metadata() to stamp ignition columns right after
    order creation. The trade flow report now correctly identifies ignition
    trades. Extended IgnitionExitContext with edge_before/after_boost fields.
  206. 980f361
    diag: add ignition state distribution + trading summary logging
    Plus dans le corps du commit
    Logs per-state counts (Quiet/Compression/Ignition/Expansion/etc.) and
    a summary of how many symbols were boosted and admitted by ignition
    trading logic per evaluation cycle.
  207. 5c17fbf
    fix: pass ingest pool to ignition bootstrap (physical separation fix)
    Plus dans le corps du commit
    The ignition bootstrap queries trade_samples_p_default which lives in
    the ingest database, but the live runner was passing pools.decision().
    With physical separation active this yielded 0 trades and no ignition
    state classifications. Now passes pools.ingest() via a new
    run_v2_route_analysis_with_ingest() function.
  208. 8b6bad3
    feat: enable ignition trade flow — candidate boost, edge relaxation, trailing exit, telemetry
    Plus dans le corps du commit
    Translate validated Ignition state signals into actual trade flow:
    - IGNITION_TRADING_ENABLED flag (default false) controls all new behavior
    - Post-evaluation candidate-level expected_move boost (factor 1.35, not feature-level)
    - Negative-edge gate relaxed to -5 bps for Ignition/Expansion states only
    - Ignition-aware entry prioritization (ignition_admitted candidates ranked higher)
    - Event-driven trailing-stop exit (ignition_exit.rs) with state-aware time stops
     (Ignition: 120s, Expansion: 180s, Continuation: 300s)
    - Trail activation at +25 bps, tightens at +50 bps, exhaustion detection
    - amend_order integration with instrument price normalization
    - report-ignition-trade-flow CLI report (trades per state, edge distribution, MFE/MAE, winrate)
    - Migration: ignition_state + metadata columns on execution_orders
    - All 59 tests pass
  209. f25ebde
    feat: add report-ignition-followthrough — distribution, MFE/MAE, time-to-move
    Plus dans le corps du commit
    New CLI command shows for symbols in Ignition/Expansion state:
    - Follow-through distribution per window (1m/5m/10m/15m) with p50/p75/p90/max
    - Directional MFE/MAE (max favorable/adverse excursion) per symbol
    - Time-to-move analysis (+25/+50/+100 bps thresholds with hit rates)
    - Route relevance hint based on where p90 edge concentrates
  210. 63dfe4d
    feat: add ignition/state-transition engine with rolling metrics, state machine, and route filtering
    Plus dans le corps du commit
    New module src/ignition/ implements:
    - Per-symbol 1m candle ring buffer (IgnitionMetricsStore) with on_trade delta-update
    - Rolling metrics: vol acceleration, range expansion, signed persistence, density shift
    - Explicit state machine (Quiet/Compression/Ignition/Expansion/Continuation/Exhaustion)
     with hysteresis (min 2m state duration, 1m cooldown, confidence floor 0.6)
    - State → route family mapping (e.g. Ignition → breakout+fade, Quiet → passive only)
    - Feature repair: when IGNITION_ENABLED, vol_proxy and trend_strength are replaced by
     rolling realized values from trade-derived candles
    - Diagnostic CLI: report-ignition-state with state distribution, top movers, route
     activation, active vs quiet metric comparison, avg move after ignition trigger
    
    All behind IGNITION_ENABLED feature flag (default off = zero behaviour change).
    8 unit tests covering metrics computation, state classification, and hysteresis.
  211. 278a40e
    fix: use Krakenbot's own trade_samples for realized vol, not external candles
    Plus dans le corps du commit
    The public.candles table was populated by the old KapitaalBot Python
    service which is no longer running (73h stale). Switch to synthesizing
    1-minute close prices from krakenbot.trade_samples_p_default which has
    live data (637 symbols, sub-second freshness).
  212. 8c9a3c0
    fix: anchor candle window on latest available data, not now()
    Plus dans le corps du commit
    Candle data may be hours old; using now() as anchor yields empty
    results. Use max(timestamp_ms) from candles table instead.
  213. a2ebd96
    fix: route realized-vol-diagnostic to ingest pool (candles in public schema)
    Plus dans le corps du commit
    The candles table lives on the ingest DB, not the decision DB.
    Route the report-realized-vol-diagnostic command through pools.ingest()
    so the public.candles query resolves correctly.
  214. 948aa84
    feat: add realized-vol diagnostic CLI (Phase 1 what-if analysis)
    Plus dans le corps du commit
    New command `report-realized-vol-diagnostic [run_id]` computes realized
    volatility (median c2c 1m returns) and trend strength from candle data,
    substitutes into MarketFeatures in-memory, re-runs V1 route analysis,
    and reports the tradable frontier delta with full risk checks (R1-R4).
    
    No production code modified — diagnostic only.
  215. e144335
    feat(diagnostics): enhance report-adaptive-edge with full decision output
    Plus dans le corps du commit
    - Make evaluate_pair_adaptive/AdaptivePairResult public for diagnostics
    - Add optional run_id CLI argument (report-adaptive-edge [run_id])
    - Report sections: regime distribution, candidate stats with admission
     reason breakdown, profitability map, top-20 admitted candidates table
    - Auto-conclusion logic based on frontier quality
  216. 5046f2e
    feat(edge_engine): adaptive edge extraction architecture (EDGE_ENGINE_V2)
    Plus dans le corps du commit
    Implement regime-based adaptive trade engine that classifies market
    state per symbol and conditionally selects routes, fee models, exit
    paths, and admissibility logic — all feature-flagged behind
    EDGE_ENGINE_V2 (default false, zero behaviour change when off).
    
    Modules:
    - market_regime: 4-regime classifier (SpreadFarming/VolatilitySpike/
     TrendContinuation/MeanReversion) from MarketFeatures
    - route_selector_v2: regime-filtered candidate matrix + orchestration
    - move_distribution: probabilistic edge (p50/p75/p90/tail) +
     AdaptiveEdgeTrace explainability per candidate
    - fee_realism: maker fill probability blended fees + inventory unwind
    - exit_path: component exit drag (latency/adverse/sweep/timeout) +
     trailing stop path simulator with ATR bands
    - admissibility: tail-aware gate allowing median-negative but
     tail-positive trades in asymmetric regimes
    - diagnostics: report-adaptive-edge CLI command
    - shadow_compare: V1 vs V2 side-by-side comparison CLI
    
    Integration: minimal hooks in 4 existing files (main.rs mod decl,
    config bool, cli string match, analysis_commands handler).
    No edits to route_engine, analysis, execution, or edge modules.
    17 unit tests, all passing.
  217. 13c1427
    fix(script): remove set -e so background job and kill do not abort script
  218. 769b2ac
    revert: remove stdin redirect and debug; use plain background run
  219. 35da3e2
    debug: log REPORT_PID to .debug file
  220. ec416ca
    fix(script): rm stale no_progress at start; start report with stdin from /dev/null
  221. 114ac64
    fix(build): add stats_queries::realized_exit_durations_for_run, RouteCandidate.trace, exit_feasibility/route_selector for guarded reports
  222. a443da0
    feat(cli): guarded report runner — file-based, fail-fast 2min, heartbeat, PHASE_* markers, diagnostic+strace
  223. 83bc640
    feat(observability): systemd timer for snapshot export for website
    Plus dans le corps du commit
    - krakenbot-observability-export.service: oneshot export to OBSERVABILITY_EXPORT_DIR
    - krakenbot-observability-export.timer: every 2 min
    - OBSERVABILITY_EXPORT_SETUP.md: install and deploy instructions
  224. ab2bb52
    feat(cli): report-edge-chain — edge decomposition, stage comparison, lane classification, decision stage-fix vs lane-split
  225. 4b88b0e
    feat(cli): report-edge-forensic for selected symbols — market context, candidate trace, kill-point, verdict
  226. 45086a6
    feat(route): net_edge_before_l3_penalty + report-edge-autopsy sample trace (gross_move, fee, exit_drag, wait_risk, capture, net_raw, net_final, zero_reason, invalid_shortcut)
  227. 32d965c
    fix(cli): economics reports use live fee provider; log maker_bps/taker_bps/source; warn on bootstrap
  228. 2e724a0
    feat(cli): report-edge-autopsy — funnel, component-kill, distribution, exact-zero, top 20 near-tradable
  229. b080c7a
    feat(cli): report-confidence-threshold-sensitivity — edge>0 only, MIN_CONFIDENCE, tradable at current/-10%/-20%/-30%, per route_type/horizon
  230. 1c214e8
    feat(cli): report-edge-reject-split — edge≥0 reject split by reason, route_type, horizon, top 10
  231. f727f04
    feat(cli): report-feature-incompleteness + economics-run (universe, near-miss, hard conclusion)
    Plus dans le corps du commit
    - report-feature-incompleteness [run_id]: cause split (no_l2, spread_null, micro_null, insufficient_snapshots, pipeline_state), top 50 incomplete, oorzaakverdeling
    - report-route-economics: Universe section, Near-miss frontier (edge bands), Harde conclusie
    - CHANGELOG: Model input kwaliteit + economics-run section
  232. 7d6cb5d
    fix(exec): add STATE_SYNC_OK/REQUIRED gate in execution-only loop for formal log proof
  233. f7ac5cc
    feat(route): bind execution universe to feature-complete symbols
    Plus dans le corps du commit
    - CurrentRunMarketRow: l2_count, is_feature_complete (spread+micro NOT NULL, l2_count>=50)
    - Route analysis: filter to feature-complete only; FEATURE_FILTER_APPLIED, FEATURE_INCOMPLETE_SYMBOLS
    - CLI report-feature-completeness [run_id]; docs/FEATURE_COMPLETENESS_CONTRACT.md
    - CHANGELOG + CHANGELOG_ENGINE updated
  234. a6b18aa
    feat(exec): STATE_SYNC_REQUIRED/STATE_SYNC_OK gate + check-decision-state CLI + STATE_SYNC_CONTRACT.md
  235. 14a0f38
    fix(observability): always log FEATURE_COVERAGE_L2 for proof (including status=no_state)
  236. 6ee5dbc
    fix(dual-DB): schema sync, spread_std_bps sanitization, sync proof, gen_mismatch in logs
    Plus dans le corps du commit
    - run_symbol_state: explicit column list (19 cols), SELECT numerics as ::text, parse_decimal_for_sync (NaN/Inf→NULL), post-sync rowcount ingest vs decision
    - full/incremental refresh: spread_std_bps CASE to NULL when NaN/Inf/non-finite
    - migration 20260314100000: ADD COLUMN IF NOT EXISTS for generation_id, l3_n_* on run_symbol_state (run on both DBs)
    - live_runner: INGEST_DECISION_SYNC_VISIBLE logs visible_generation_id and gen_mismatch=0|1
  237. 584b256
    feat(observability): add FEATURE_READY_SIGNAL and FEATURE_COVERAGE_L2 to execution-only evaluation loop
    Plus dans le corps du commit
    - execution-only path had no feature readiness gate
    - added l2_raw_feature_ready check before refresh
    - log FEATURE_READY_SIGNAL ready=true/false
    - after sync log FEATURE_COVERAGE_L2
    - skip evaluation cycle if L2 coverage < threshold
    - aligns execution-only behaviour with combined runtime path
  238. 2229057
    fix(universe): only status online/limit_only in pool; no status = exclude
  239. dbd2b01
    feat(pipeline): Model Input Pipeline Hardening + L2 feature lineage CLI
    Plus dans le corps du commit
    - DEEL 1: FEATURE_COVERAGE_L2 log + ROUTE_ENGINE_SKIP if coverage_pct_spread < 60%
    - DEEL 2: FEATURE_READY_SIGNAL + refresh gate (l2_raw_feature_ready) before refresh
    - DEEL 3: MarketFeatures l2_spread_missing/l2_micro_missing; path fallback confidence*0.6, move*0.5; ROUTE_FEATURE_FALLBACK_USED
    - DEEL 4: Direction fallback (density-based confidence 0.05..0.45) when micro missing; DIRECTION_FALLBACK_USED
    - DEEL 5: Telemetry FEATURE_COVERAGE_L2, FEATURE_READY_SIGNAL, ROUTE_FEATURE_FALLBACK_USED, DIRECTION_FALLBACK_USED, MISSING_FEATURE_SYMBOL_COUNT
    - DEEL 6: report-route-economics / report-direction-signal exit(2) when coverage < 60% (ECONOMICS INVALID)
    - CLI: report-l2-feature-lineage for raw L2 vs run_symbol_state diff
    - Docs: MODEL_INPUT_PIPELINE_HARDENING.md, L2_FEATURE_LINEAGE_DEBUGGING.md
    - CHANGELOG.md + docs/CHANGELOG_ENGINE.md updated
  240. 2298649
    fix: add l2_raw_feature_ready and l2_feature_coverage_from_state to stats_queries (required by live_runner)
  241. 88b7cf4
    feat(universe): override-only caps, unbounded default, FX excluded, explicit logging
    Plus dans le corps du commit
    - Config: *_OVERRIDE env only; Option<usize> limits; no default 200/50
    - UniverseManagerConfig + select_layer: cap only when Some(n)
    - universe_source: exclude FX pairs (EUR/USD, GBP/USD, etc.); USD crypto only
    - Observe: select_l2/l3_subset take Option<usize>; None = all
    - Startup logs: UNIVERSE_SCOPE usd_only fx_pairs_excluded; UNIVERSE_LIMIT_OVERRIDE_ACTIVE/INACTIVE
    - docs/UNIVERSE_CAPS_AND_CONFIG.md, LOGGING.md updated
  242. e826ebe
    fix(dual-DB): read run_symbol_state from ingest after refresh when physical separation; log sync result
    Plus dans le corps du commit
    - After refresh, universe state (per_symbol_counts, l2/l3_stats, raw_counts) now read from ingest pool when pools.is_physical_separation(), so symbol_count is correct regardless of sync.
    - Sync result logged: RUN_SYMBOL_STATE_SYNC ingest→decision (synced_rows) or RUN_SYMBOL_STATE_SYNC failed.
    - Applied in: initial warmup, periodic universe refresh, L3 resync block, pinned/exit-only L3 checks.
    - Fixes symbol_count=0 in UNIVERSE_REFRESH_STATE_TIMING when using separated ingest/decision DBs.
  243. 34936e4
    cli: report-path-source-inspect for expected_move_bps/confidence source inspection
    Plus dans le corps du commit
    - Add report-path-source-inspect CLI: upstream counts (micro/spread) and per-candidate path inputs -> output sample.
    - Doc: RAPPORT_EXPECTED_MOVE_BPS_SOURCE_INSPECTION.md with trace per route and fix direction.
  244. 1ca5b9d
    chore: CHANGELOG + CHANGELOG_ENGINE for LIVE_USE_OWN_RUN_ONLY warmup poll (capacity-test)
  245. c460ac7
    cli: add report-direction-signal (continuation/reversal/confidence, expected_move_bps, by route_type and horizon)
  246. 8c862fe
    db: run_symbol_state VerifyMode, detailed_differences, debug_refresh_diff, run_replay_compare for CLI verify/debug/replay
  247. e69d0c0
    cli: add report-route-economics for V2 candidate frontier and economics tuning
  248. a9778a1
    live_runner: LIVE_USE_OWN_RUN_ONLY poll raw tables after flush (max 90s) for sufficient warmup data
  249. 6c72ab5
    live_runner: flush writer before warmup check when LIVE_USE_OWN_RUN_ONLY (ensure symbol_count > 0)
  250. 7cc7201
    docs: risk 3 dicht — verify-refresh-equivalence 114 WithinTolerance; controleplan + correctness-proof updated
  251. 4f10b00
    verify: allow WithinTolerance with up to 30 symbol differences when max diffs within relative tolerance (e.g. ingest growth between full and inc)
  252. a777f9d
    verify: use watermarks -1 so id > -1 includes all rows (incl. id=0 if any)
  253. 0f27b96
    LIVE_USE_OWN_RUN_ONLY: integrated run for clean 400-symbol capacity test
    Plus dans le corps du commit
    - Config LIVE_USE_OWN_RUN_ONLY: warmup uses data_run_id=run_id, 60s warmup; epoch binding only to own run.
    - epoch_queries: select_valid_epoch_for_run, current_epoch_for_exit_only_for_run; EpochBindingReason::OwnRunOnly.
    - run_l3_capacity_test.sh: EXECUTION_UNIVERSE_LIMIT, LIVE_USE_OWN_RUN_ONLY; doc integrated run usage.
  254. ceba7a4
    verify: relative tolerance (10%) for large numerics; NULL vs value no longer as 0 vs value for diff
  255. e5c878d
    fix: incremental refresh match full — STDDEV sample (n-1), L3 per-metric counts for AVG
    Plus dans le corps du commit
    - STDDEV: use sample variance (n-1) and NULL when n<=1 to match PostgreSQL STDDEV()
    - L3 averages: add l3_n_efit, l3_n_refill, l3_n_cancel, l3_n_qt so merge uses COUNT(column)
     and matches AVG() (which ignores NULLs); migration + full/incremental/sync updated
  256. f520537
    fix: verify-refresh-equivalence snapshot via float8 to avoid Decimal decode errors
  257. 81fa498
    EXECUTION_UNIVERSE_LIMIT, evaluation scaling metrics, pair_summary_24h FK fix
    Plus dans le corps du commit
    - Config: EXECUTION_UNIVERSE_LIMIT (env, default 200); use for pool/fetch in live + ingest. Test 200/300/400 without rebuild.
    - Live runner: EVALUATION_SCALING log (evaluation_symbol_count, evaluation_duration_ms, route_build_duration_ms, pipeline_duration_ms); route vs pipeline timing split.
    - pair_summary_24h FK: ensure_observation_run_on_pool(ingest, run_id) before persist when run created on decision DB.
    - LOGGING.md: EVALUATION_SCALING, EXECUTION_UNIVERSE_LIMIT.
  258. 58c9692
    scripts: L3 capacity test usage 5/50/400, target scale 400
  259. c13576a
    chore: CHANGELOG + CHANGELOG_ENGINE for incremental refresh (9863d64)
  260. 9863d64
    feat(refresh): incremental/watermark refresh for run_symbol_state (risk 3 closure)
    Plus dans le corps du commit
    - Add refresh_watermarks table (run_id, table_name, last_id); migration 20260313160000.
    - First refresh per run_id = full scan; subsequent = only id > watermark per raw table.
    - Merge deltas (counts + running avg/var for L2/L3) in SQL; set generation_id on all rows.
    - Log REFRESH_INCREMENTAL with delta_ticker_rows, delta_trade_rows, delta_l2_rows, delta_l3_rows.
    - Doc: REFRESH_INCREMENTAL_DESIGN (design + proof); CONTROLEPLAN risk 3 dicht on incremental.
  261. 927b1de
    fix: add BufRead import for resource_telemetry (Linux reader.lines())
  262. 9b3de1a
    Docs: refresh bounded-proof, writer metrics, telemetry, L3 scale and multi-WS design
    Plus dans le corps du commit
    - REFRESH_COMPLEXITY, L3_SCHAALBEPERKINGEN: cap required, REFRESH_MAX_DURATION_SECS
    - REFRESH_INCREMENTAL_DESIGN.md: incremental refresh options (watermark/delta)
    - WRITER_PARALLEL_DESIGN.md: optional dedicated L3 / sharded writer
    - L3_MULTI_WS_INGEST_DESIGN.md: multi-WS ingest when Kraken symbol limit applies
    - LOGGING: WRITER_METRICS, resource telemetry and event-loop proxy
  263. 1f8c671
    Bottleneck work: refresh cap+timeout, writer metrics+L3 batch, telemetry, l3-subscribe-test
    Plus dans le corps du commit
    - Refresh: REQUIRE_INGEST_MAX_RUN_DURATION, REFRESH_MAX_DURATION_SECS (60s), ingest_runner exit when required and cap missing
    - Writer: WriterSender with pending count, WRITER_METRICS every 10s, L3 batching (flush at 50 or interval)
    - Telemetry: RESOURCE_TELEMETRY_INTERVAL_SECS, observability/resource_telemetry (RSS on Linux), spawn in ingest + live_runner
    - L3: l3-subscribe-test CLI (L3_SUBSCRIBE_TEST_SYMBOLS, L3_SUBSCRIBE_TEST_DURATION_SECS)
  264. e2288b3
    controleplan: fix-or-prove-close uitkomst na 15-min run (server 2d1cd83)
    Plus dans le corps du commit
    Beslistabel ingevuld: 1 dicht, 3 blocker (cap niet gezet), 4 dicht, 5 dicht, 11 dicht.
    Conclusie: Blocker(s) eerst — INGEST_MAX_RUN_DURATION_HOURS op server zetten.
  265. 2d1cd83
    fix-or-prove-close: env logging, ingest cap warn, evaluation/sync timing, controleplan
    Plus dans le corps du commit
    - main: log DECISION_DATABASE_URL and INGEST_MAX_RUN_DURATION_HOURS at startup (no silent default)
    - ingest_runner: warn when INGEST_MAX_RUN_DURATION_HOURS not set
    - REFRESH_COMPLEXITY: recommend cap 6/24h for production
    - live_runner: EVALUATION_CYCLE_DURATION_MS and SYNC_LAG_MS for contention/sync-lag measurement
    - CONTROLEPLAN_SYSTEEMRISICOS: add Fix-or-prove-close uitkomst (drempels, beslistabel, conclusie)
    - scripts/check_stale_decision_logs.sh: check gen_mismatch and ROUTE_FRESHNESS in logs (risico 1)
  266. 8e48016
    Docs: dual-DB epoch dual-write fix + 15min validation passed (section E)
  267. 70f4c25
    Dual-DB gate: use synced cycle for gate (avoid ingest overwrite race); log visible=cycle for validation
  268. 7f31d23
    Generation gate: use visible_gen from right after sync (avoid ingest overwrite race)
  269. a75c126
    Fix dual-write epoch/snapshot: ensure observation_run on decision DB (FK); log dual-write errors
  270. b3b1a4e
    Rapport: dual-DB uitvoering — tweede instance gebouwd, 15min validatie, resultaat
  271. e380072
    Script: set decision instance password from DATABASE_URL (server-side)
  272. 857f458
    Dual-DB: plan tweede instance, doc-definitie, validatiescript sync/generation checks
  273. 4b23919
    Docs: validatierapport single/dual-DB; DOC_INDEX, CHANGELOG, README bijgewerkt
  274. a653d6f
    Validatie: deprecated-check alleen bij groei tussentijds; rapport B/C ingevuld; scriptfix
  275. edb24d7
    Refresh-schaalbaarheid: run-duur cap (INGEST_MAX_RUN_DURATION_HOURS) + 15min validatiescript + db_full_reset_ingest
  276. 1a845a0
    Docs: flow schema's tonen dubbele DB (DB Ingest vs DB Decision) expliciet
  277. b3dfee8
    Docs SSOT: state-first, partition, generation in ENGINE_SSOT, DOC_INDEX, ARCHITECTURE, CHANGELOG_ENGINE, LIVE_RUNBOOK, LOGGING, README
  278. 5cc8322
    DB architecture: partition cutover, generation contract, sync gate, refresh O(rows) doc
    Plus dans le corps du commit
    - Raw ingest: L3 + ticker/trade/l2 cutover to partitioned tables (migrations 20260313120000–40000)
    - run_symbol_state: generation_id + sequence; RefreshOutcome; state_generation_id; sync copies gen
    - live_runner: cycle_generation_id, INGEST_DECISION_SYNC_VISIBLE log, EXECUTION_BLOCKED_GENERATION_MISMATCH gate
    - docs: REFRESH_COMPLEXITY_AND_GENERATION (O(rows) proof), EXECUTION_REPORT/DB_ARCHITECTURE updated
    - CHANGELOG: 2025-03-13 partition cutover, generation contract, sync gate
  279. a240cc0
    docs: add PG DBA autovacuum proposal (table settings, l3 policy, refactor block)
  280. 813ad46
    refactor(db): state-first query boundary + SQL type safety in live paths
    Plus dans le corps du commit
    - Explicit SQL casts: ::bigint for COUNT/MAX(epoch_id); ::text for aggregates
     mapped to Decimal (ingest_epoch, proof_runner, exposure_reconcile,
     deterministic_proof, stats_queries).
    - Remove direct NUMERIC→Decimal in critical/live paths: use ::text + parse
     in exposure_reconcile, fills_ledger, positions, stats_queries.realized_pnl,
     proof_runner MAX(expected_edge_bps).
    - Add STATE_FIRST_AND_SQL_TYPE_SAFETY_DELIVERABLE.md (live paths, casts,
     Decimal fixes, error-family confirmation).
  281. 94397fa
    fix(db): run_symbol_state NUMERIC decode — read as text, parse to Decimal (avoid NaN/overflow)
  282. 38e263f
    chore(dba): add PostgreSQL DBA health scan script and report template
  283. 7b7b36e
    fix(db): cast SUM() to bigint in run_raw_counts_from_state (Option<i64> vs NUMERIC)
  284. bef764e
    feat(execution): L3 soft at system level — l3_integrity out of system_live_ready AND
    Plus dans le corps du commit
    Deel 2: L3 no longer systemically blocks execution; symbol-level safety unchanged.
    
    - data_integrity: system_live_ready() no longer ANDs l3_integrity
    - l3_integrity still computed and logged in DATA_INTEGRITY_MATRIX
    - hard_blocked symbols still filtered from exec_allowed (symbol-level)
    - Verified: no second systemic L3 choke in codebase
    
    See docs/ROUTE_HOT_PATH_STATE_PROOF.md (Deel 2 section).
  285. 1675ff9
    feat(route): hot path state-only — analyze_run_from_state, no raw/legacy readers
    Plus dans le corps du commit
    Deel 1: Route hot path depends only on run_symbol_state + run_by_id.
    
    - stats_queries: RunSymbolStateRow + run_symbol_state_rows_for_run (single SELECT)
    - current_run_analysis: analyze_run_from_state (state rows + run_by_id only; same formulas as provisional)
    - route_selector: run_v2_route_analysis uses analyze_run_from_state (no fallback to analyze_run)
    
    Readers no longer in route hot path:
     pair_summaries_for_run, l2_symbol_stats_for_run, l3_symbol_stats_for_run,
     symbol_trade_counts, symbol_avg_spread_l2
    
    See docs/ROUTE_HOT_PATH_STATE_PROOF.md.
  286. 0679f49
    feat(route_engine): exit regime architecture — kapitaalbescherming primair
    Plus dans le corps du commit
    - ExitRegime enum + exit_mode_to_regime; select_allowed_exit_regimes(route,horizon,features,path)
    - RouteCandidate: exit_regime, capture_factor; filter matrix on allowed regimes
    - EXIT_REGIME_ALLOWED/REJECTED/CHOSEN/EXPECTANCY_APPLIED logging
    - Pipeline: v2_exit_strategy_from_route(..., exit_regime); no legacy fallback
    - docs/EXIT_REGIME_ARCHITECTURE.md + CHANGELOG
  287. 28fc6e7
    docs: CHANGELOG 2025-03-13 Route Engine Evolution (Live Edge Machine)
    Plus dans le corps du commit
    - Added/Changed/Internal per module (l3_quality, run_metrics, fill_probability,
     route_expectancy, expected_path, route_selector, sizing, strategy_pipeline,
     universe, ingest_runner, live_runner, readiness_gate, run_symbol_state, data_integrity)
    - Validatie: cargo check + cargo test (29 passed)
  288. 6b47325
    feat(route): Fase 5 run metrics, ROUTE_RUN_METRICS log, cost/ROI stubs, run_symbol_state refresh log
    Plus dans le corps du commit
    - run_metrics: RouteRunMetrics, market_cost_from_counts/edge_roi stubs
    - route_selector: aggregate and log ROUTE_RUN_METRICS (valid_route_count, avg_edge_velocity, neutral_path_ratio, l3_quality_avg)
    - run_symbol_state: RUN_SYMBOL_STATE_REFRESH duration log; l2 CTE filter spread_bps IS NOT NULL
  289. 5a66d1e
    feat(route): Fase 4 opportunity_score in universe, refresh timing logs
    Plus dans le corps du commit
    - SymbolQualityInputs.opportunity_score; L3Ingest/Execution layer boost
    - ingest_runner/live_runner: opportunity_score: None in quality map
    - UNIVERSE_REFRESH_STATE_TIMING logs (refresh_ms, state_reads_ms, total_ms, symbol_count)
  290. 5a3c006
    feat(route): Fase 3 dynamic sizing (size_quote_v2), edge velocity in pipeline
    Plus dans le corps du commit
    - sizing: size_quote_v2 with sigmoid(edge) * confidence * liquidity_score * capital_availability
    - strategy_pipeline v2: use size_quote_v2 for route sizing (expected_net_edge_bps, confidence, spread_bps)
  291. 0fb9229
    feat(route): Fase 1 L3 soft + Fase 2 path model
    Plus dans le corps du commit
    Fase 1 — L3 economisch veilig:
    - l3_quality module: compute_l3_quality_score (floor when no L3)
    - MarketFeatures.l3_quality_score, fill_prob Hybrid/Fallback, floor 0.05
    - route_expectancy: confidence *= l3_quality_score, edge lerp penalty, no L3 hard block
    - validate_candidate: confidence/fill_prob soft gate (no reject)
    - data_integrity doc: L3 does not invalidate routes
    
    Fase 2 — Direction & path:
    - DirectionalBias enum, ExpectedPath base_move/upside_tail/downside_tail/move_confidence
    - expected_path: directional_bias_from_features, horizon elasticity (no PathNotHorizonConsistent reject)
    - route_expectancy: time_efficiency = move/sqrt(duration), edge_velocity_bps_per_sec
  292. 4dd0228
    fix: L2 count in run_symbol_state includes all rows (remove spread_bps IS NOT NULL filter)
  293. e2be75d
    fix: refresh run_symbol_state before L3 safety checks (resync, hard-block, ExitOnly)
  294. 873f915
    db: add run_symbol_state to retention cleanup and CLI output
  295. 334f9fb
    execution: wire run_symbol_state refresh and state reads in live and ingest runners
  296. 608d3bc
    db: add refresh_run_symbol_state and from_state read functions
  297. 6082039
    db: add run_symbol_state table and index (hot-path state)
  298. af6ec1f
    DB optimization: raw table indexes, retention cleanup, drop execution_event_buffer, truncate script
  299. 11e40f8
    Add L3 netto edge report: SQL queries, shell script, rapport template
  300. 0e4cb70
    route-engine: economic calibration — move distribution, relative move, vol-scale, momentum, capital velocity
    Plus dans le corps du commit
    - ROUTE_MOVE_DISTRIBUTION: per (route_type, horizon) p50/p75/p90/p95/p99/avg (confidence>0.10)
    - Relative move: move_valid = expected_move_bps/fee_bps > 0.85; path MIN_MOVE_BPS check removed
    - Vol-scaled move: vol_reference = run p75 vol_proxy; vol_scale clamp(0.7,2.5); all builders
    - Momentum boost 1.15–1.35 for Breakout/Pullback when trend_strength/vol_expansion/trade_density above thresholds
    - time_adjusted_score *= sqrt(capital_velocity_norm); capital_velocity = move_bps/hold_secs
    - ROUTE_ECONOMIC_PRESSURE_STATS: valid_move_ratio, avg_relative_move_score, top_relative_move_symbol
  301. 900cdb2
    route-engine: Breakout fallback-band confidence uplift
    Plus dans le corps du commit
    - When 0.20 < vol_expansion <= 0.35, trend_strength > 0.20 and direction set:
     add BREAKOUT_FALLBACK_CONFIDENCE_UPLIFT (0.10) so confidence reaches
     MIN_CONFIDENCE and paths pass path_confidence_too_low -> move_below_fees or valid
    - No move/other routes/MIN_MOVE_BPS/safety changes
  302. 483786a
    route-engine: BREAKOUT direction trace (guarded) + direction fallback
    Plus dans le corps du commit
    - BREAKOUT_DIRECTION_TRACE: log symbol, micro_bps, trend_strength, vol_expansion,
     builder/validated direction, rejection (guarded by ROUTE_V2_PATH_DIAG, max 8 per run)
    - reset_breakout_trace_count() at start of run_v2_route_analysis when diag on
    - Breakout direction fallback: vol_expansion in (0.20, 0.35] and trend_strength > 0.20
     -> set direction from micro_direction/micro_bps (breakout_direction_from_features)
    - No move/confidence/safety changes; only Breakout direction
  303. 0dcdb8d
    tune(route_engine): Breakout mid-band (0.35–0.5) base_move +20% (0.9 -> 1.08)
    Plus dans le corps du commit
    Single small step: more paths clear move_below_fees. Diag stays on for calibration.
  304. 41e837f
    fix(route_engine): Breakout vol_expansion threshold 0.5 -> 0.35 for direction/move/confidence
    Plus dans le corps du commit
    So more pairs get Up/Down instead of Neutral; diag showed 100% Neutral for breakout.
  305. 8765fbd
    feat(route_engine): v2 path diag guard + direction stats + almost-tradable ranking + route_family impact
    Plus dans le corps du commit
    - ROUTE_V2_PATH_DIAG env guard: ROUTE_PATH_DIRECTION_STATS and
     ROUTE_ALMOST_TRADABLE_RANKING (top 20) only when guard set
    - V2_ROUTE_ANALYSIS_COMPLETE extended with route_family counts
     (pullback, breakout, pump_fade, passive) for impact reporting
    - Path heuristic: micro_direction band 0.5 -> 0.25 bps so more
     paths get Up/Down, fewer path_not_directional
  306. ea159d1
    feat(route_engine): Route Decision Engine v2 — market-first, path-first
    Plus dans le corps du commit
    Replace strategy-first economic decisioning with market-first route
    engine. Per pair: market features → expected path per horizon → route
    candidates (route × horizon × entry × exit) → expectancy → winner or
    explicit NoTrade.
    
    New modules (src/route_engine/, 7 files, 1282 LOC total):
    - types.rs: RouteType, EntryMode, ExitMode, RouteHorizon, ExpectedPath,
     RouteCandidate, RouteSelection, V2RouteReport
    - market_features.rs: MarketFeatures from CurrentRunMarketRow
    - expected_path.rs: first-class ExpectedPath with path_source,
     heuristic builders per (route_type, horizon), validation
    - route_expectancy.rs: expected_net_edge_bps, time_adjusted_score,
     full cost decomposition, max_hold_secs invariant
    - route_selector.rs: candidate matrix, winner selection, orchestration
    - shadow.rs: mandatory counterfactual logging with numeric why_lost
     deltas (alt vs winner edge/confidence/score)
    - mod.rs: module wiring
    
    Integration:
    - live_runner.rs: v2 route analysis replaces readiness+v1 pipeline
    - strategy_pipeline.rs: run_strategy_pipeline_v2 bridges v2 routes
     to existing execution layer via legacy strategy mapping
    
    Shadow output shows winner + all alternatives with concrete numeric
    differences explaining why each alternative lost.
    
    Docs: design document + 12 investigation reports + changelog updates.
  307. 650a909
    fix(readiness): Volume strategy EdgeNegative bypass + check_entry_readiness lookup
    Plus dans le corps du commit
    - readiness_gate: Volume strategy was blocked by general EdgeNegative
     check before its own -5 bps floor could apply. Now excluded from
     general EdgeNegative (mirrors existing SurplusBelowFloor exemption).
    - check_entry_readiness: lookup found first PairReadinessRow for symbol
     regardless of strategy, causing StrategyMismatch. Now matches both
     symbol AND selected_strategy.
  308. 939d1fd
    fix(execution): max_epoch_age_secs race with evaluation_interval_secs
    Plus dans le corps du commit
    max_epoch_age_secs was (freshness*2).max(300) which equals the 300s evaluation
    interval — leaving zero margin for processing jitter. Epochs produced every 300s
    are 300-310s old at evaluation time, causing global_liveness=false permanently.
    
    Changed to (freshness*2).max(evaluation_interval + 120), giving 420s with default
    config. This applies to both the normal and execution-only loops.
  309. a25b35c
    epoch validity: exclude hard_blocked symbols from entry-validity set
    Plus dans le corps du commit
    Analogous to the earlier pinned exclusion: symbols that are hard_blocked
    (permanently lacking L3 data because the exchange does not provide L3 for them)
    are already excluded from trading by the per-symbol safety mechanism. They should
    not count against the 90% entry-validity threshold.
    
    Entry-validity set is now: execution_symbols \ (pinned ∪ hard_blocked).
    
    Without this fix, 4/23 non-pinned execution symbols with permanent l3_count=0
    (ATH/USD, AVNT/USD, CC/USD, DOT/USD — Kraken does not provide L3 for them)
    caused every epoch to be degraded (17.4% > 10% tolerance), keeping the engine
    permanently in ExitOnly mode even though system_live_ready=true.
    
    EPOCH_VALIDITY_COMPUTED now logs hard_blocked_excluded_count alongside
    pinned_excluded_count and entry_validity_count.
  310. f47cac4
    fix(execution): l3_integrity checked global hard_blocked list instead of exec-set intersection
    Plus dans le corps du commit
    Two bugs fixed:
    
    1. `list_hard_blocked_symbols()` returns ALL globally hard_blocked symbols (from all
     runs/processes). The code passed `!hard_blocked.is_empty()` as `hard_blocked_any_exec_symbol`,
     meaning any hard_blocked symbol anywhere — even outside the execution set — made
     l3_integrity=false and blocked the entire engine. Fixed: only count symbols actually
     present in the execution set (BTreeSet::remove returns true only if present).
    
    2. l3_integrity was binary: any single hard_blocked exec symbol → system down.
     This is overly aggressive when a minority of symbols permanently lack L3 data on
     the exchange (Kraken does not provide L3 for all pairs). Hard_blocked symbols are
     already removed from exec_allowed and will not trade; universe_viability separately
     checks if enough symbols remain. Changed to fraction-based: l3_integrity=false only
     when >50% of exec symbols are hard_blocked (systemic L3 failure), not when a few
     illiquid symbols lack L3.
    
    Observability: DATA_INTEGRITY_MATRIX now logs hard_blocked_exec_count and
    total_exec_before_filter. EXECUTION_SYMBOL_FILTERED only logged for symbols
    actually in the execution set.
  311. cea8bcc
    docs: CHANGELOG_ENGINE correct commit hash for observability export
  312. 61f96fa
    Observability read-model export voor KapitaalBot-Website
    Plus dans le corps du commit
    - docs/OBSERVABILITY_SNAPSHOT_CONTRACT.md: versioned contract 1.0
    - src/db/read/observability_queries: order/fill/regime/strategy counts, latest_epoch_summary
    - src/observability: snapshot DTOs + export naar JSON (public_* families)
    - CLI: export-observability-snapshots (OBSERVABILITY_EXPORT_DIR)
    - CHANGELOG + CHANGELOG_ENGINE bijgewerkt
    
    Website BFF leest alleen deze snapshots; geen directe DB-queries.
  313. a6fa319
    epoch validity: entry-validity set = execution \ pinned (pinned excluded from 90% rule)
    Plus dans le corps du commit
    - compute_epoch_criteria_and_status uses only non-pinned execution symbols for 90% check
    - Pinned stay in scope/snapshot/universe; illiquid pinned no longer block entry gate
    - EPOCH_VALIDITY_COMPUTED log: entry_validity_count, pinned_excluded_count, status
    - symbol_count in EpochCriteria = entry-validity set size
    - docs/EPOCH_ENTRY_VALIDITY_SET.md: design + live validation checklist
  314. 70857f2
    feat(execution): explicit epoch binding policy (LatestValid / PreferCurrentLineage) + EXECUTION_EPOCH_BOUND logging
  315. 5086221
    docs: TRADABLE_COUNT_NEXT_STEPS + run71 degraded symbols diagnostic script
  316. f6a603b
    feat(epoch): valid epoch when ≥90% symbols meet criteria (fix run 71 degraded)
  317. 29e4117
    chore: CHANGELOG_ENGINE commit hash for execution-only fix
  318. 2e525b4
    fix(config+execution): EXECUTION_ONLY=1 parse + first-binding lineage break
    Plus dans le corps du commit
    - config: parse_bool_env() accepts 1/0/true/false so systemd EXECUTION_ONLY=1
     enables split mode (no own ingest, bind to ingest epochs).
    - live_runner: lineage_break_detected only when previous lineage differs from
     current; set last_bound_lineage_id after every binding. Fixes spurious
     ExitOnly on first epoch binding.
    
    CHANGELOG + CHANGELOG_ENGINE updated.
  319. 158d285
    chore: correct commit hash in CHANGELOG_ENGINE
  320. 9a1a48c
    fix(analysis): capturable move strategy-aware in readiness report
    Plus dans le corps du commit
    CapturableMoveInputs used pre-loop maker-oriented expected_move_bps for
    all strategies; Momentum received systematically underestimated capturable
    move -> SurplusBelowFloor/EdgeNegative. Now: capturable move computed per
    strategy with strategy_move_bps inside the loop; NoTrading branch keeps
    maker expected_move_bps.
    
    CHANGELOG + CHANGELOG_ENGINE updated.
  321. 13781a8
    fix(fill_prob): calibrate for Kraken liquidity levels
    Plus dans le corps du commit
    The fill probability model was calibrated for high-volume exchanges
    (5 trades/sec normalization, 40s fill reference). On Kraken, even
    liquid pairs like EUR/USD only do 0.5 trades/sec, giving fill_prob
    of 7% — making edge permanently negative.
    
    Changes:
    - Fallback density normalization: 5.0 → 0.5 (Kraken-realistic)
    - L3 fill time reference: 40s → 300s (maker orders rest for minutes)
    
    EUR/USD expected fill_prob: 0.07 → 0.70 (with trade_density=0.53)
  322. 0dcd344
    fix(edge): strategy-aware edge and expected_move calculations
    Plus dans le corps du commit
    The economic model was originally built for market making (maker) and
    applied fill_probability uniformly to all strategies. This penalized
    momentum/taker strategies where fill is instant (market order).
    
    Changes:
    - Momentum edge: no fill_prob discount, costs = 2*taker_fee + slippage
    - Maker edge: fill_prob * (move - maker_fee - taker_fee - slippage)
    - Momentum expected_move: vol*1.2 + micro*1.0 + spread*0.1 (directional)
    - Maker expected_move: spread*0.8 + vol*0.4 + micro*0.6 (spread capture)
  323. 8693309
    feat(warmup): skip 60s warmup when ingest is already producing epochs
    Plus dans le corps du commit
    If a valid ingest epoch exists in the DB, the execution runner now
    reduces warmup from 60s to 5s (WS connect only) and uses the ingest
    run_id for initial universe selection data. This avoids redundant
    waiting when run-ingest is already running continuously.
  324. 6ba5998
    fix(fees): parse Kraken TradeVolume nested fee object correctly
    Plus dans le corps du commit
    The TradeVolume API returns fees as nested objects:
     fees["XETHZUSD"]["fee"] = "0.3500" (percentage)
    
    The code called as_str() on the object directly, which returned
    None, silently falling back to hardcoded defaults (20/26 bps).
    Actual account fees (18/35 bps) were never read from the API.
    
    Fix: navigate into nested object with .get("fee"), parse the
    percentage string, convert to bps with * 100 (not * 10_000).
  325. 1a3909e
    fix: readiness gate economic model bugs + complete exposure protection logging
    Plus dans le corps du commit
    Root cause fixes for 0/200 tradable pairs:
    
    1. Regime metric mapping: spread_stability measured width not stability
     (divisor 20→80), midprice_volatility threshold too aggressive (divisor
     10→40). Most altcoins falsely tagged CHAOS.
    
    2. Slippage volatility multiplier: vol_proxy * 5.0 used raw bps input
     (e.g. 15 bps → 75 bps slippage). Changed to * 0.3 for sane values.
    
    3. Edge formula: fill_prob * move - costs → fill_prob * (move - costs).
     Costs only incurred when filled, not every attempt.
    
    4. Capturable move: removed fill_probability discount (double-counted
     with edge formula). Capturable move = execution quality given fill.
    
    5. Bootstrap fees: 35/35 → 25/40 bps (match Kraken base tier).
    
    Also includes previously uncommitted exposure protection causal logging
    (exit_lifecycle, exposure_reconcile, runner, ws_handler).
    
    DB cleanup: zeroed phantom positions (ETH/USD, EUR/USD) and expired
    stale orders that caused "Insufficient funds" protection failures.
  326. 475f5ab
    feat: causal exposure validation + maker/taker route evaluation
    Plus dans le corps du commit
    - Exposure: validate per-symbol UNPROTECTED→STARTED→outcome chain in validate script
    - Maker/taker: evaluate both execution modes per route, choose on net edge
    - Logging: ROUTE_MAKER_EVALUATED, ROUTE_TAKER_EVALUATED, ROUTE_EXECUTION_MODE_CHOSEN
    - slippage_estimator: estimate_slippage_for_entry_mode(maker)
    - readiness_gate: slippage_for_entry_mode()
    - EDGE_NEGATIVE_HARD_BLOCK uses chosen_edge_bps (max of maker/taker)
  327. e3554fe
    feat(fees): live fee model via Kraken TradeVolume API
    Plus dans le corps du commit
    STAP 1 — Live fee source:
    - auth_rest: get_trade_volume() via POST /0/private/TradeVolume
    - LiveFeeProvider: fetch maker/taker fees, volume_30d from account
    - Refresh at startup + periodiek (24h)
    - Fallback: cached of bootstrap met expliciete FEE_MODEL_FALLBACK_USED log
    
    STAP 2 — Fees als pure input:
    - FeeTier.fee_source (live|cached|bootstrap)
    - ROUTE_FEE_PROFILE: fee_entry_bps, fee_exit_bps, total_fee_bps, fee_source
    - ROUTE_EDGE_BREAKDOWN: fee_source toegevoegd
    - tier_from_provider_or_bootstrap() i.p.v. hardcoded current_fee_tier
    
    STAP 4 — Traceability:
    - FEE_MODEL_REFRESH_STARTED, FEE_MODEL_REFRESHED, FEE_MODEL_REFRESH_FAILED
    - FEE_STATE_ACTIVE, FEE_DIFFERENTIAL
    - log_fee_state_active() bij startup
    
    STAP 5 — Geen hardcoded defaults als waarheid:
    - Bootstrap (35 bps) alleen bij no prior fetch, met expliciete log
    - Alle fee-consumers via LiveFeeProvider of tier_from_provider_or_bootstrap
    
    STAP 6 — Server validatie:
    - validate_live_engine_server.sh: FEE_STATE_ACTIVE vereist
    - Fee model counters in output
  328. 1d8155a
    feat: auto-protect unprotected exposure with real stop/market orders
    Plus dans le corps du commit
    - Add protection_flow.rs: places emergency stop-loss (with market fallback)
     for any unprotected position; persists order to execution_orders so
     symbol-execution lock detects it immediately.
    - Startup reconcile (both live and execution-only paths) now calls
     protect_exposure() for each unprotected position instead of only logging.
    - Periodic reconcile returns unprotected records; live_runner triggers
     protect_exposure() for any newly detected unprotected positions.
    - detect_partial_fill_protection updated: checks for existing exit orders
     and logs EXPOSURE_PROTECTION_CONFIRMED or EXPOSURE_PROTECTION_STARTED
     (reason=exit_lifecycle_triggered) accurately.
    - New log markers: EXPOSURE_PROTECTION_STARTED, EXPOSURE_PROTECTION_CONFIRMED,
     EXPOSURE_PROTECTION_FAILED, EXPOSURE_PROTECTION_FALLBACK_MARKET,
     EXIT_PLAN_RECONCILED, EMERGENCY_STOP_PERSISTED.
  329. 3db29f2
    feat(safety): proof mode isolation + exposure auto-protection + symbol lock
    Plus dans le corps du commit
    STAP A — Proof mode isolation:
    - PROOF_MODE_STATE log at start of every pipeline run (active=true/false)
    - PROOF_MODE_BYPASS_APPLIED at system gate and per-pair gate
    - EDGE_NEGATIVE_HARD_BLOCK: hard guard — when proof_mode=false, any route with
     expected_edge_bps < 0 is blocked before plan_execution; log + Skip outcome
    
    STAP B — Exposure auto-protection:
    - exposure_reconcile.rs: new module
     - reconcile_exposure_at_startup(): load positions, check exit orders,
     log EXPOSURE_DETECTED / EXPOSURE_UNPROTECTED / EXPOSURE_PROTECTION_CONFIRMED
     / RECONCILE_POSITION_STATE
     - check_symbol_execution_lock(): per-symbol guard before new entry;
     blocks if active exit orders, open entry orders, or unprotected net position;
     logs SYMBOL_EXECUTION_LOCK_ACQUIRED / SYMBOL_EXECUTION_LOCK_RELEASED
     - detect_partial_fill_protection(): called on every fill (incl. partial);
     logs PARTIAL_FILL_PROTECTION_UPDATE, EXPOSURE_UNPROTECTED if no exit orders
     - run_periodic_reconcile(): lightweight periodic position/exit order mismatch check
    
    - live_runner.rs:
     - reconcile_exposure_at_startup() called at startup before evaluation loop
     - check_symbol_execution_lock() called before every order submit
     - run_periodic_reconcile() called every 10 evaluations
    
    - ws_handler.rs:
     - detect_partial_fill_protection() called on every entry fill (partial + full)
     - ORDER_FILL log extended with partial=true/false
    
    Logging additions:
     PROOF_MODE_STATE, PROOF_MODE_BYPASS_APPLIED, EDGE_NEGATIVE_HARD_BLOCK
     EXPOSURE_DETECTED, EXPOSURE_UNPROTECTED, EXPOSURE_PROTECTION_STARTED
     EXPOSURE_PROTECTION_CONFIRMED, PARTIAL_FILL_PROTECTION_UPDATE
     RECONCILE_POSITION_STATE, SYMBOL_EXECUTION_LOCK_ACQUIRED, SYMBOL_EXECUTION_LOCK_RELEASED
  330. 3eb3a81
    fix(pipeline): ROUTE_SELECTED shows effective proof-capped size and proof_mode flag
  331. 457c92f
    fix(pipeline): cap proof mode trade size + log ROUTE_PROOF_ENTRY_FORCED on normal path
    Plus dans le corps du commit
    Previously, when proof mode bypassed readiness checks and the normal risk+queue
    path returned Execute, the full sizing (e.g. 20 EUR) was used instead of the
    proof cap (ROUTE_PROOF_MAX_ORDER_QUOTE). The ROUTE_PROOF_ENTRY_FORCED log was
    only emitted in the fallback Skip→Execute conversion, not here.
    
    Fix:
    - Introduce `proof_mode_active` flag in execution loop (proof.enabled && execute_count==0)
    - Cap `effective_size_dec` to `route_proof.max_order_quote` when proof_mode_active
    - Emit ROUTE_PROOF_ENTRY_FORCED immediately when Execute is produced with proof cap
    - This ensures ROUTE_PROOF_MAX_ORDER_QUOTE=5 is always respected regardless of path
  332. a1cc39d
    feat(engine): PAIR_FILTER_RESULT, INGEST_WARMUP_STATE, proof auto-disable, latency split
    Plus dans le corps du commit
    strategy_pipeline.rs:
    - PAIR_FILTER_RESULT log for every pair entering/failing pair_allowed_for_ranking
     (result=allowed|blocked, fill_time_ms, spread_bps, strategy)
    - RouteRecord: add spread_net_bps field (pre-computed per strategy)
    - ROUTE_EDGE_BREAKDOWN: add spread_net_bps, spread_role, break_even_shortfall_bps
     per route (enables spread attribution in no-trade forensics)
    
    ingest_runner.rs:
    - INGEST_WARMUP_STATE state=warming_up logged at WS start
    - INGEST_WARMUP_STATE state=live_ready|degraded_warmup logged after warmup period
    - INGEST_WARMUP_STATE repeated in heartbeat when valid_epochs=0
     (explicitly distinguishes warming-up from stream_unhealthy)
    - INGEST_STREAM_HEALTH extended with warmup_state field
    
    live_runner.rs:
    - ROUTE_PROOF_DISABLED: auto-disable proof mode after first successful order submit
     (proof_lifecycle_proven flag; subsequent cycles run without forcing)
    - PIPELINE_LATENCY_PROFILE: log pipeline logic latency (ms) per evaluation
     (splits pure logic cost from DB/IO; complement to ROUTE_LATENCY_PROFILE)
    - proof_total_forced_trades counter for diagnostic reporting
  333. 30df0f6
    feat(ingest): IngestStreamHealth with gap detection and lineage recovery
    Plus dans le corps du commit
    - Add IngestStreamHealth struct tracking:
     - last_message_ts: most recent ticker/trade timestamp
     - consecutive_valid_epochs: epoch continuity counter
     - gap_detected: boolean flag for active gap state
     - gap_start_ts: timestamp when gap started
     - reconnect_count: reconnect event counter
    
    - At each heartbeat, query DB for last ticker/trade timestamp
     and compute last_msg_age_secs to assess stream liveness
    
    - Log INGEST_STREAM_HEALTH every heartbeat:
     healthy, last_message_age_secs, valid_epochs, gap, reconnects
    
    - Log INGEST_GAP_DETECTED when last_msg_age_secs > 120
    - Log INGEST_LINEAGE_RECOVERED when stream recovers after gap
    - Track consecutive_valid_epochs in stream_health (reset to 0 on EPOCH_DEGRADED)
    
    DATA_FRESHNESS_STATE can now rely on INGEST_STREAM_HEALTH markers
    instead of arbitrary time-window heuristics
  334. 027e9b9
    feat(engine): route transparency, pair classifier, ORDER_ID_CORRELATION_FALLBACK
    Plus dans le corps du commit
    pair_classifier.rs (new):
    - PairClass enum: MajorFx, CryptoMajor, CryptoMid, Altcoin, Unknown
    - classify_pair(): categorizes symbols by base asset
    - edge_model_justification(): regime+strategy context for each pair class
    
    strategy_pipeline.rs:
    - PAIR_CLASSIFICATION log per selected route
    - EDGE_MODEL_JUSTIFICATION log per selected route
    - ROUTE_DECISION_SUMMARY: full economics (edge, fill_prob, fees, spread_net, slippage)
    - ROUTE_PARAMETER_PROFILE: SL/TP/time_stop, maker_or_taker, spread breakdown per route
    - COUNTERFACTUAL_BEST_ROUTE: top 5 non-executed routes when execute_count=0
    - RouteRecord extended with entry_strategy, exit_strategy, spread_bps fields
    
    runner.rs:
    - ORDER_ID_CORRELATION_FALLBACK_USED logged when exchange bridge not found
     (HandleResult::Unknown → bridge correlation failed → event was unroutable)
  335. 1022953
    fix(engine): pair filter removed spread gate, instrument-driven exit price precision
    Plus dans le corps du commit
    pair_allowed_for_ranking refactored:
    - Remove spread_bps > 2.0 global gate (spread is route feature, not global blocker)
    - Remove fill_time=None blocking (None = no L3 data = not technically blocked)
    - Only hard blocker: fill_time >= 60s when explicitly known
    - All 42 L3 symbols including EUR/USD and crypto-majors now pass this filter
    
    exit_lifecycle.rs precision fix:
    - Replace hardcoded 5-decimal rounding with get_instrument_constraints() per symbol
    - normalize_order() uses price_increment from Kraken instrument metadata
    - InstrumentConstraints::fallback_5dp() added for resilience when API call fails
    - Log PRECISION_SOURCE=instrument_metadata and PRECISION_NORMALIZATION_APPLIED per exit
  336. 576dfea
    fix(engine): structural data freshness, blocker masking fix, spread strategy-aware
    Plus dans le corps du commit
    Data freshness (structural, not workaround):
    - Remove MAX_RUN_AGE_FOR_READINESS_SECS (48h run-age logic removed)
    - Add LIVE_DATA_MAX_AGE_SECS=60 based on actual last_ticker_ts/last_trade_ts
    - Remove live_freshness_secs parameter from run_readiness_analysis_for_run
    - Log DATA_FRESHNESS_STATE with mode=live/historical on every readiness check
    
    Blocker masking fix (check_entry_readiness):
    - Return actual primary_blockers.first() instead of hardcoded DataStale
    - Fallback to DataStale only when report.data_stale=true or primary_blockers is empty
    - Prevents economic blockers (RegimeChaos, EdgeNegative) from showing as DataStale
    
    Spread strategy-aware (cost_breakdown.rs):
    - Rename spread_cost_bps → spread_net_bps in CostBreakdown
    - Add spread_net_for_strategy(): Liquidity/Volume=-spread*0.5 (income), Momentum=+spread*0.5 (cost)
    - strategy_readiness_report uses spread_net_for_strategy per candidate
    - Log SPREAD_ROLE with spread_bps, spread_net_bps, role per evaluation
    
    live_runner.rs updated to call run_readiness_analysis_for_run without freshness parameter.
  337. df17f9e
    fix(engine): strategy-aware entry filter, slippage maker/taker split, sizing ratios
    Plus dans le corps du commit
    entry_filter.rs:
    - Add max_spread_bps and spread_is_income fields to EntryFilterConfig
    - Add entry_filter_for_strategy(): Momentum uses spread as UPPER bound (taker cost),
     Liquidity/Volume use it as LOWER bound (maker income)
    - Momentum: max_spread=30bps, max_fill_time=5s; Liquidity: max_fill_time=40s; Volume: default
    
    slippage_estimator.rs:
    - MAKER_SPREAD_FACTOR=0.05 (posting costs no spread crossing)
    - TAKER_SPREAD_FACTOR=0.50 (taker pays half-spread)
    - base_slippage_for_strategy(): Momentum=3-4bps, Liquidity=0.8-1.2bps, Volume=2.0bps
    
    sizing.rs:
    - equity_ratio_for_strategy(): Liquidity=0.05, Momentum=0.10, Volume=0.08
    - max_trade_ratio_for_strategy(): Liquidity=0.20, Momentum=0.25, Volume=0.40
    - size_quote_from_edge() now takes SelectedStrategy parameter
    
    Pipeline uses per-strategy entry filter and sizing ratios at both ranking and execution stages.
  338. 666fec4
    fix(engine): wire exit_config_for_exit_strategy and per-strategy SL/TP/time_stop
    Plus dans le corps du commit
    Three dead-code wiring gaps fixed:
    1. runner.rs: call exit_config_for_exit_strategy(selected_exit_strategy) instead of
     ExitConfig::default(); add ORDER_ID_CORRELATION_MODE log
    2. exit_lifecycle.rs: use config.time_stop_secs instead of hardcoded WAIT_TP_OR_TIMESTOP_SECS=90
    3. strategy_pipeline.rs: call momentum_execution_mode() for Momentum candidates to determine
     OrderIntent::TakerEntry vs MakerEntry; pass exit_strategy through Outcome
    
    Per-strategy ExitConfig values in strategy_selector.rs:
    - Liquidity→MakerLadder: SL=-20, TP=30, time=120s
    - Momentum→TSL: SL=-60, TP=150, time=30s
    - Volume→TimeDecay: SL=-25, TP=50, time=60s
    
    New OrderIntent::TakerEntry variant added; ExecutionIntent::TakerEntry matched accordingly.
    Outcome struct extended with exit_strategy field; live_runner.rs wired accordingly.
  339. 5107b39
    docs: full lifecycle proof report (EXIT_LIFECYCLE_COMPLETED confirmed live)
    Plus dans le corps du commit
    Full lifecycle proven on commit 6c14a4b, run_id=55, order_id=23:
    - ROUTE_SELECTED → ORDER_ACK → FILL_LEDGER_COMMITTED
    - EXIT_PLAN_CREATED → EXIT_RUNTIME_PLAN_ARMED
    - EXIT_REASON_TRIGGERED (time_stop) → EXIT_ORDER_SUBMITTED (market)
    - EXIT_LIFECYCLE_COMPLETED (market exit filled)
    - signal_to_submit=361ms, fill_to_exit_submit=7ms
  340. 6c14a4b
    fix: round SL/TP prices to 5 decimals + alphanumeric cl_ord_id for exit orders
    Plus dans le corps du commit
    Kraken rejects exit orders with too many decimal places (EOrder:Invalid price).
    Round stop_trigger_price and tp_price to 5 decimal places before submission.
    Also fix cl_prot/cl_tp id format to alphanumeric (no hyphens) for Kraken WS v2.
  341. 30c0b61
    fix: replay buffered execution events after add_order ACK populates bridge
    Plus dans le corps du commit
    Buffered events (pending_new, new, trade, filled) were released but not
    replayed through handle_execution_report. After ACK populates the
    exchange_bridge, replay each buffered event. If a fill is detected during
    replay, immediately trigger the exit phase (EXIT_PLAN_CREATED, etc.).
    
    This completes the full lifecycle: ORDER_ACK → FILL_LEDGER_COMMITTED →
    EXIT_PLAN_CREATED → EXIT_RUNTIME_PLAN_ARMED → EXIT_ORDER_SUBMITTED.
  342. abbfd5d
    fix: populate tracker bridge from add_order WS ACK + use exchange order_id for fills
    Plus dans le corps du commit
    Three combined fixes:
    1. Skip cl_ord_id in add_order (Kraken WS v2: not supported for this account type)
    2. On WS_METHOD_ACK for add_order: extract exchange order_id, call tracker.on_ack()
     and state_machine::on_ack() to populate the exchange_bridge for fill correlation
    3. Release buffered events for the exchange order_id after bridge is populated
    4. Keep WS_ADD_ORDER_SENT debug log and WS_METHOD_* logging for diagnostics
  343. 0fafab9
    test: temporarily remove cl_ord_id to test base order validity
  344. cdba3c9
    debug: log WS_ADD_ORDER_SENT JSON for cl_ord_id invalid arguments diagnosis
  345. 5f15fdc
    fix: cl_ord_id alphanumeric-only format for Kraken WS v2 compliance
    Plus dans le corps du commit
    Kraken WS v2 rejects cl_ord_id with hyphens (EGeneral:Invalid arguments:cl_ord_id).
    New format: "kb" + first char of prefix + UUID simple (32 hex) = 35 chars total.
    All alphanumeric, no hyphens, satisfies Kraken's character and length requirements.
  346. 89387c7
    fix: truncate cl_ord_id to 36 chars (Kraken EGeneral:Field cl_ord_id max)
    Plus dans le corps du commit
    Kraken WS v2 rejects add_order with cl_ord_id longer than 36 chars.
    Format "kb-strat-{uuid32}" = 41 chars exceeds limit.
    Now truncates to 36 chars while keeping kb-prefix and UUID entropy (≥27 hex chars).
  347. 6f431ed
    fix: log WS method responses (add_order ack/error) to diagnose silent order rejection
    Plus dans le corps du commit
    Previously all non-executions/non-ownOrders WS messages were silently
    dropped in `_ => {}`. This made it impossible to see Kraken error
    responses (e.g. EOrder:Unknown symbol) to add_order requests.
    
    Now logs WS_METHOD_ACK for successful method responses and
    WS_METHOD_ERROR for error responses.
  348. 4b3f63b
    fix: skip shadow_trade insert for NoEntry direction (LONG/SHORT constraint)
    Plus dans le corps du commit
    EntryDirection::NoEntry previously mapped to "UNKNOWN" which violates
    the shadow_trades_direction_check constraint (LONG/SHORT only).
    Skip shadow trade inserts entirely when direction is NoEntry.
    
    Also updates lifecycle proof report with session 9 evidence.
  349. 48dbc9e
    fix: bypass choke system_live_ready gate in ROUTE_PROOF_MODE
    Plus dans le corps du commit
    The choke_decide call in run_execution_once used system_live_ready
    (which is false in proof mode) as readiness_allows, causing Halt.
    
    In proof mode with ignore_regime_block, readiness_allows is set to true
    so the choke allows the order through.
  350. 4b8df30
    fix: proof mode bypasses pair_allowed_for_ranking and entry_filter gates
    Plus dans le corps du commit
    Track proof_candidate_forced flag; bypass all per-pair gates (readiness,
    pair_allowed_for_ranking, entry_filter) for exactly one candidate when
    ROUTE_PROOF_MODE=true && ROUTE_PROOF_IGNORE_REGIME_BLOCK=true.
  351. 0a232fc
    fix: ROUTE_PROOF_MODE bypasses per-pair readiness gate for first candidate
    Plus dans le corps du commit
    When route_proof.enabled && ignore_regime_block, the readiness-drop for
    non-StrategyMismatch blockers is bypassed for the very first candidate.
    This allows one proof candidate to reach the ROUTE_PROOF_ENTRY_FORCED logic.
    
    Logs ROUTE_PROOF_READINESS_BYPASSED when bypass applies.
  352. 3f4e780
    fix: wire route_proof_config_from_env() into run_execution_once pipeline config
    Plus dans le corps du commit
    run_execution_once used StrategyPipelineConfig::default() which has
    route_proof.enabled=false. Now reads ROUTE_PROOF_MODE env at startup so
    run-proof and run-execution-once both honour the env-based proof config.
  353. 1ca9868
    fix: ROUTE_PROOF_MODE bypasses system_live_ready gate (ignore_regime_block)
    Plus dans le corps du commit
    When ROUTE_PROOF_MODE=true and ROUTE_PROOF_IGNORE_REGIME_BLOCK=true, the
    readiness_system_block early-return is skipped so the pipeline can evaluate
    routes and force one micro trade for lifecycle proof.
    
    Logs readiness_system_block_bypassed instead of readiness_system_block when
    bypassed, so the blocker reason is still visible in logs.
  354. d0c6b93
    fix: pass live_freshness_secs=120 in run_readiness_analysis
    Plus dans le corps du commit
    A persistent ingest run never sets ended_at. With live_freshness_secs=None
    the data_stale gate always returned true for running ingest runs, blocking
    execution despite fresh data being available.
    
    Now: if last_ticker_ts or last_trade_ts is within 120s → data_stale=false.
    Ended runs still gated by MAX_RUN_AGE_FOR_READINESS_SECS (48h).
  355. 5f36350
    feat: persistent ingest service + economic forensics + foreground proof runner
    Plus dans le corps du commit
    Ingest (FASE 1):
    - heartbeat interval 300s → 60s
    - add INGEST_READY_FOR_EXECUTION_ATTACH after first valid epoch
    - systemd services: Restart=always, RestartSec=5/10
    
    Economic forensics (FASE 3/4):
    - collect RouteRecord during strategy pipeline loop
    - dump_route_forensics() when execute_count=0: ROUTE_EDGE_BREAKDOWN (top 5),
     ROUTE_FRONTIER, EDGE_ATTRIBUTION, BREAK_EVEN_THRESHOLD
    - write /tmp/edge_frontier_<run_id>.json artifact
    
    Foreground proof runner (FASE 2):
    - new run-proof command: loops run-execution-once with 20min timeout
    - prints PROOF_STATUS every 2s (eval_count, routes_evaluated, execute_count,
     highest_edge_bps, dominant_blocker)
    - auto-stops: lifecycle proven / ECONOMICALLY_BLOCKED / DATA_BLOCKED / timeout
    - dumps /tmp/route_analysis_<run_id>.log on exit
  356. ffa99bf
    fix: self-managed fail-fast migrations + fix duplicate version 20260310120000
    Plus dans le corps du commit
    - rename 20260310120000_execution_schema.sql → 20240308200000_execution_schema.sql
     to resolve duplicate version collision (two files sharing version 20260310120000)
    - db::create_pool now runs sqlx::migrate!() on every startup and aborts if it fails
    - removed warn-and-continue behavior; startup is blocked on DB_MIGRATIONS_FAILED
    - removed SKIP_MIGRATIONS escape hatch
    - logs: DB_MIGRATIONS_STARTED, DB_MIGRATIONS_APPLIED, DB_MIGRATIONS_UP_TO_DATE, DB_MIGRATIONS_FAILED
  357. acde7ca
    Docs: server migration + proof-run (EXECUTION_ENABLE + ROUTE_PROOF_MODE) sectie 8
    Plus dans le corps du commit
    - Migratie 20260311100000 handmatig toegepast op server (exit latency kolommen)
    - Proof-run uitgevoerd: commit 23e1396, run_id 54, no order (system_live_ready=false)
    - Rapport sectie 8: commando, uitkomst, conclusie
  358. 23e1396
    Docs: add multiregime route proof (Fase 6) and server validation checklist
  359. 8348ff6
    Fase 2-5: Route discovery, proof mode, latency KPI, equity markers
    Plus dans le corps du commit
    Fase 2: Route discovery proof (multiregime)
    - ROUTE_DISCOVERY_STARTED, ROUTE_EVALUATED (per candidate), ROUTE_SELECTED, ROUTE_UNIVERSE_EMPTY
    - Track route_highest_edge_bps and route_blocker_counts for empty-universe log
    
    Fase 3: ROUTE_PROOF_MODE lifecycle force
    - RouteProofConfig + route_proof_config_from_env() (ROUTE_PROOF_MODE, MIN_EDGE_BPS, MAX_ORDER_QUOTE, etc.)
    - Pipeline: force one Execute with capped size when proof mode and no execute
    - ROUTE_PROOF_ENTRY_SUBMITTED, ROUTE_PROOF_FILL_DETECTED in runner; EXIT_LIFECYCLE_COMPLETED in exit_lifecycle
    - live_runner: pipeline_config.route_proof from env
    
    Fase 4: Latency KPI gates
    - log_route_latency_profile() in db/order_latency: signal→submit, submit→ack, fill→exit_submit
    - ROUTE_LATENCY_PROFILE and LATENCY_EDGE_PENALTY_APPLIED; called after exit phase in runner
    
    Fase 5: Balance/compounding markers
    - SIZE_FROM_NEW_EQUITY at pipeline sizing; EQUITY_UPDATED_AFTER_TRADE on exit completion (tp_filled + market exit)
  360. 030518c
    Fase 1: Universe/pinned state hard fix + recovery expand
    Plus dans le corps du commit
    - UniverseValidationError (PinnedSymbolMismatch + Other) in universe crate
    - validate_snapshot returns Result<(), UniverseValidationError> for downcast
    - L2 layer: force-include pinned symbols (same as L3/Execution) so pinned in pool get into L2
    - live_runner: UNIVERSE_VALIDATION_STARTED, UNIVERSE_VALIDATION_OK, UNIVERSE_PINNED_SYMBOL_MISMATCH, UNIVERSE_RECOVERY_EXPAND
    - On PinnedSymbolMismatch: extend pool_candidates with missing pinned, retry build_snapshot once
    - Initial snapshot and loop refresh both use recovery; no more hard abort on pinned symbol missing from L3/execution
  361. bf5de99
    docs: lifecycle proof report 2026-03-11 (server a347ae4, chain not yet proven; blockers external)
  362. a347ae4
    Route-based engine: exit loop, timing instrumentation, route model, balance sizing, allocator, ingest heartbeat, lifecycle proof markers
    Plus dans le corps du commit
    - Exit loop in live runner: run_post_fill_exit_phase (SL+TP+time_stop/market)
    - Timing: execution_order_latency exit columns, update_on_exit_phase, exit latency report
    - Route model: SelectedExitStrategy, TradeRoute, candidate_routes_for_regime, PairReadinessRow.exit_strategy
    - Route-based edge: readiness per route; exit_config_for_exit_strategy
    - Balance-based sizing: EquitySource (Fixed/FromBalance), resolve_equity_quote
    - Capital allocator: next_equity_after_pnl, MAX_TRADE_PCT/MAX_OPEN_NOTIONAL caps
    - Ingest: INGEST_HEARTBEAT every 300s
    - Lifecycle proof markers: EXIT_PLAN_CREATED, EXIT_RUNTIME_PLAN_ARMED, EXIT_REASON_TRIGGERED, EXIT_ORDER_SUBMITTED, EXIT_MAKER_FALLBACK_TO_MARKET
  363. cbdc451
    docs: single source of truth, centralise and clean documentation
    Plus dans le corps du commit
    - Add ENGINE_SSOT.md as single source of truth (status matrix, references)
    - Add ARCHITECTURE_ENGINE_CURRENT.md with Mermaid diagrams
    - Add LIVE_RUNBOOK_CURRENT.md (ingest, execution attach, markers)
    - Add VALIDATION_MODEL_CURRENT.md (proof types, economic vs data/attach blocked)
    - Add CHANGELOG_ENGINE.md (git-based, per subsystem)
    - Add DOC_INDEX.md (lead vs historical docs)
    - Add DOC_AUDIT_RESULT.md and DOC_CONSISTENCY_REPORT.md
    - Move superseded docs to docs/superseded/ with SUPERSEDED banner
    - Update README to point to SSOT and DOC_INDEX; remove broken doc refs
    - Add systemd/README.md for ingest and execution units
    - Remove DEPLOY_REPORT_*.md (time-bound, no SSOT value)
    - Update CHANGELOG.md with 2025-03-11 entry
    
    No runtime or strategy changes; git-only codeflow.
  364. 5cb496b
    fix(scripts): handle unset SKIP_RUN in live validation
    Plus dans le corps du commit
    Keep the hardened validation script compatible with set -u by guarding the optional SKIP_RUN flag explicitly. This preserves fail-fast proof checks without crashing before runtime validation starts.
  365. cff7b72
    fix(scripts): fail validation when proof target is unmet
    Plus dans le corps du commit
    Require warmup, minimum evaluation counts, and proof-target-specific runtime evidence before the live server validation script can report success. Label blocked runs explicitly as DATA_BLOCKED or ECONOMICALLY_EMPTY so short or empty runs no longer masquerade as proof.
  366. dcdf2de
    docs: add live validation runs analysis (run_id=32-34, commit b4ad93b)
    Plus dans le corps du commit
    Documents the complete live validation attempt with findings:
    - Epoch contract proven: status=valid from epoch 3 onward (run_id=34)
    - Universe selection bug fixed (two commits: 128f314, b4ad93b)
    - DATA_INTEGRITY_MATRIX system_live_ready=true proven
    - ENGINE_MODE transitions: ExitOnly→NoNewEntries (correct)
    - NO_ORDER diagnosis: ECONOMIC_GATING (EdgeNegative/RegimeChaos) vs DATA_RUNTIME
    - Live order lifecycle pending: requires non-CHAOS market window for Execute decision
  367. 0715310
    fix(execution): restore multistrategy live candidate flow
    Plus dans le corps du commit
    Infer maker direction from microstructure inputs, fan out regime-driven strategy candidates, and carry the winning strategy into execution. Lower the default risk floor so pipeline-sized orders can pass the risk gate and emit runtime logs that prove candidate fan-out and sizing decisions.
  368. b4ad93b
    fix(universe): lower MIN_EXECUTION_TICKER_ACTIVITY to 10 (= epoch minimum)
    Plus dans le corps du commit
    Threshold of 20 was too strict for the 60-second warmup window, resulting
    in only 2 symbols qualifying for execution (run_id=33, symbol_count=2).
    
    10 = MIN_TICKER_PER_SYMBOL matches the epoch completion criterion exactly.
    Liquid symbols (ETH/USD, EUR/USD, ADA/USD etc.) accumulate 10+ tickers in
    the 60s warmup; structurally illiquid symbols (1INCH/USD: 1 ticker/25 min,
    AIXBT/USD: 1 ticker/25 min) remain filtered out.
  369. 128f314
    fix(universe): filter illiquid symbols from Execution layer selection
    Plus dans le corps du commit
    Symbols with ticker_samples < MIN_EXECUTION_TICKER_ACTIVITY (20) are
    now excluded from the Execution universe layer, unless they are pinned.
    
    Root cause: the execution universe was selecting symbols like 1INCH/USD
    (1 ticker per 25 min), ACH/USD (0.08/min), AIXBT/USD (0.17/min) that
    can never satisfy the epoch completion criterion of MIN_TICKER_PER_SYMBOL=10
    per epoch window (5 min). This caused epochs to remain perpetually degraded
    → ENGINE_MODE=ExitOnly → no Execute decisions possible.
    
    The threshold (20) = 2× the epoch minimum to account for the warmup-to-epoch
    timing gap. Pinned symbols always bypass this filter.
    
    Run_id=32 diagnosis (5 eval cycles, 0 orders, all epochs degraded):
     criteria_ticker_ok=false for epochs 1-5 due to 8 illiquid execution symbols
     EdgeNegative + RegimeChaos as secondary economic blockers
  370. f646c2e
    docs: label run-deterministic-proof as internal exerciser, distinguish from live validation
  371. 3112cbd
    Add run-deterministic-proof: live edge-case validation
    Plus dans le corps du commit
    Exercises three deterministic lifecycle paths against the real DB
    without requiring exchange connectivity:
    
    CASE 1 (OOO): Fill arrives before ACK
     - ORDER_EVENT_BUFFERED (fill buffered, bridge not yet populated)
     - ACK arrives → bridge populated → ORDER_EVENT_OUT_OF_ORDER
     - Buffered fill released; buffer empty after ACK
    
    CASE 2 (Idempotency): Duplicate fill
     - fills_ledger::process_fill called twice, same exchange_trade_id
     - Second call: was_duplicate=true, FILL_DUPLICATE_IGNORED
     - DB verified: exactly 1 fill row per exchange_trade_id
    
    CASE 3 (Reconcile): WS disconnect → Suspect → ReconcileRequired → Recovered
     - handle_ws_disconnect → ORDER_SUSPECT in tracker + DB
     - start_reconcile → ORDER_RECONCILE_REQUIRED in tracker + DB
     - reconcile_from_snapshot (status=open) → ORDER_RECONCILED, AckedOpen
     - RECONCILE_COMPLETE
    
    All proof orders cleaned up from DB after run.
    CLI: krakenbot run-deterministic-proof
  372. 32aae98
    docs: update deterministic engine deliverable with OrderTracker wiring status
  373. c4edd1c
    Wire OrderTracker + ws_handler into live execution loop
    Plus dans le corps du commit
    Replaces all direct DB-correlation bypasses in submit_and_wait_for_execution_reports
    with the deterministic OrderTracker/ws_handler flow:
    
    - DB-first: order persisted (PendingSubmit) before exchange submit
    - OrderTracker registered immediately after DB persist
    - All WS events (ACK/FILL/CANCEL/REJECT) routed through ws_handler::handle_execution_report
    - cum_qty_before looked up from tracker state (no local HashMap)
    - order_qty_base read from tracker (no caller-passed param)
    - FILL -> fills_ledger::process_fill (atomic tx: fill + position + PnL)
    - WS disconnect -> ws_handler::handle_ws_disconnect -> orders -> Suspect
    - Reconnect -> order_reconcile::start_reconcile -> ReconcileRequired
    - ownOrders snapshot -> reconcile_from_snapshot
    - Out-of-order events buffered by exchange_order_id, released on ACK
    - Duplicate events idempotently ignored (fills: ON CONFLICT DO NOTHING)
    
    Removes: pending_ack_order_id hack, cum_qty_per_order local map,
    inline ACK/FILL/CANCEL/REJECT handlers, direct DB-correlation in loop.
  374. 3b98ea8
    feat(execution): deterministic execution engine
    Plus dans le corps du commit
    DB-first order lifecycle, 13-state machine, thread-safe OrderTracker,
    ledger-based fill accounting, reconnect/reconcile, no f64 for price/qty.
    
    New modules:
    - order_state.rs: 13 states (Created, PendingSubmit, AckedOpen,
     PartiallyFilled, Filled, Canceled, Rejected, Suspect,
     ReconcileRequired, UnknownExchangeOrder, LostOrRejected, Error, Reconcile)
    - order_tracker.rs: DashMap primary + bridge; parking_lot event buffer
    - order_buffer.rs: BufferedEvent for out-of-order WS events
    - fills_ledger.rs: single-transaction fill+position+PnL+micro-alpha
    - order_reconcile.rs: WS disconnect → Suspect → ReconcileRequired → recovered
    - ws_handler.rs: cl_ord_id-first correlation; never symbol/side matching
    
    Key fixes:
    - cl_ord_id uses UUID v4 (not timestamp_nanos)
    - on_ack/on_amend/execution_report_to_event: Decimal not f64
    - PartialFill → PartiallyFilled state rename + backward-compat from_str
    - runner.rs fill loop: Decimal throughout
    - UNIQUE(order_id, exchange_trade_id) on fills for idempotency
    
    New migration: 20260310140000_deterministic_execution_engine.sql
    - +21 pre-trade audit + lifecycle fields on execution_orders
    - +6 micro-alpha fields on fills
    - execution_event_buffer table for buffered/unknown events
    - Dedupe indexes on fills + order_events
    
    Deps: dashmap = "5", parking_lot = "0.12" added to Cargo.toml
  375. 0d29b83
    fix(db): correct query_scalar return type from (i64,) to i64
    Plus dans le corps du commit
    query_scalar returns a single scalar value (i64), not a tuple (i64,).
    The (i64,) type caused a RECORD/INT8 mismatch at decode time, silently
    failing create_epoch and insert_execution_universe_snapshot on every
    call → all epochs stuck at 'pending', INGEST_EPOCH_STARTED never logged.
    
    Fix both create_epoch and insert_execution_universe_snapshot.
  376. 8de7afe
    fix(epoch): add sqlx json feature for JSONB binding; surface create_epoch errors
    Plus dans le corps du commit
    - Cargo.toml: add 'json' feature to sqlx (required for serde_json::Value → JSONB)
    - live_runner.rs: convert both `if let Ok(create_epoch)` to match arms with
     ERROR-level INGEST_EPOCH_CREATE_FAILED logging on failure
    - live_runner.rs: Err arm already present for snapshot insert (kept)
    
    Root cause: without sqlx json feature, serde_json::Value binding to JSONB
    columns silently fails at runtime; create_epoch itself likely OK but snapshot
    insert fails → epoch stays pending → execution always skips evaluation.
  377. 9c460d6
    fix(execution): log EXECUTION_UNIVERSE_SNAPSHOT_INSERT_FAILED when snapshot insert fails
  378. 571d1d4
    fix(db): bind JSONB for execution_universe_snapshots via sqlx::types::Json
  379. cae54c1
    feat(epoch+split): FASE 1 validation script/runbook, FASE 2 ingest/execution split
    Plus dans le corps du commit
    FASE 1:
    - scripts/validate_epoch_contract_fase1.sh: server validation of epoch contract (clean tree, run live N min, grep evidence)
    - docs/EPOCH_CONTRACT_FASE1_VALIDATION_RUNBOOK.md: runbook + hard conclusion template
    - live_runner: NO_ORDER_ECONOMIC_GATING / NO_ORDER_DATA_RUNTIME logging for no-order diagnosis
    
    FASE 2:
    - config: execution_only (EXECUTION_ONLY) for split mode
    - run_execution_only_loop: execution without ingest; binds to ingest epochs/snapshots only
    - run-execution-live when EXECUTION_ONLY=1: no run/lineage/WS ingest; only epoch reader + evaluation loop
    - run-ingest: new CLI mode; ingest_runner.rs (public WS, universe, epoch/snapshot, L3 health; no execution)
    - systemd: krakenbot-ingest.service, krakenbot-execution.service (EXECUTION_ONLY=1)
    - docs/EPOCH_SPLIT_DELIVERABLE.md: commits, paths, validation proof, systemd install
    
    Git-only deployment; no scp. Server: run validate_epoch_contract_fase1.sh for FASE 1 proof; then deploy split.
  380. 0d433c3
    feat(epoch): hard ingest/execution epoch contract — migration, lineage, epochs, snapshots, data-integrity matrix, engine modes, lineage break, STRATEGY_REJECTED/BLOCKER_DISTRIBUTION/NO_ORDER_SYMBOL_DROP
    Plus dans le corps du commit
    - Migration: ingest_lineage, ingest_epochs, execution_universe_snapshots
    - Epoch lifecycle: create_lineage at start, create_epoch + update_epoch_status on universe refresh, execution-centric criteria (valid = exec+pinned ok)
    - Epoch reader: current_valid_epoch_id (entries), current_epoch_for_exit_only (exit-only), execution_universe_snapshot_by_id
    - Live runner: bind cycle to one epoch, load snapshot, EXECUTION_UNIVERSE_BOUND, DATA_INTEGRITY_VERIFIED
    - Data integrity matrix: global_liveness, snapshot_coherence, market_freshness, l3_integrity, universe_viability (EXECUTION_MIN_UNIVERSE_SIZE)
    - Engine modes: Normal/NoNewEntries/ExitOnly/Halt, ENGINE_MODE_ENTER/EXIT, no new entries when degraded/lineage break
    - Lineage break: last_bound_lineage_id, INGEST_LINEAGE_CHANGED, EXECUTION_INGEST_LINEAGE_BREAK, EXECUTION_EMERGENCY_EXIT_ONLY
    - No-order diagnose: STRATEGY_REJECTED, BLOCKER_DISTRIBUTION, NO_ORDER_SYMBOL_DROP (Outcome.symbol)
    - Docs: docs/INGEST_EXECUTION_EPOCH_CONTRACT.md with contract, validation checklist, no-order reporting
  381. 778b788
    Safety: pinned invariants, WS reconcile logs, latency heatmap
  382. e57a816
    Docs: universe audit current state (200/60/50/30); repo policy remote-execution-ssh; config/live_runner/runner safety and runtime fixes
  383. f805468
    Docs: reference development rules and Rust development mode in DEVELOPMENT_RULES.md
  384. 1286f1e
    Add repo policy: Rust development mode (one change, cargo check, focused commits)
  385. ca3b504
    Fix duplicate Decimal import and unwrap_or in stats_queries
  386. c0ae3a4
    Allow skipping/failing migrations gracefully via env
  387. 8c99445
    Fix shadow evaluator dependencies (uuid + FromRow) for server builds
  388. e534f54
    Add shadow trade evaluator and blocker matrix
  389. 0b44fea
    Fix ws_safety_report parsing (strip ANSI, avoid duplicate zero output)
  390. a248b17
    Make ws_safety_report.sh executable for server proof bundle
  391. c8b471c
    Fix run-execution-live shutdown timing by capping sleep to deadline
    Plus dans le corps du commit
    Ensure evaluation loop sleeps never overshoot the runtime deadline, so runtime window shutdown logs and post-run proof bundle steps reliably execute within configured runtime.
  392. c12d026
    Add safety integrity report CLI over symbol_safety_state and latency
    Plus dans le corps du commit
    Introduce report-safety-integrity command that summarizes symbol safety modes and submit->ack latency from execution_order_latency over the last 24h.
  393. 52262e9
    Document WS-native safety stack and add ws_safety_report script
    Plus dans le corps du commit
    Extend logging and server validation docs with ORDER_LATENCY, PRIVATE_STREAM_SILENT, QUIET_MODE, EXIT_ONLY and HARD_BLOCK events, and add a ws_safety_report.sh helper to summarise WS safety signals from logs and DB.
  394. dd48798
    Add WS-native safety extensions (latency, watchdog, exit-only, hard-block)
    Plus dans le corps du commit
    Introduce T0–T3 order latency persistence + ORDER_LATENCY logs, add private WS silent watchdog with reconnect, and persist per-symbol safety state for quiet-mode, exit-only (pinned + invalid L3), and L3 resync hard-blocking.
  395. c182d4e
    Fix universe pinning: use base_position in positions table
  396. 068b67b
    Add dynamic universe manager with atomic snapshot rotation
    Plus dans le corps du commit
    - Add UniverseManager: atomic snapshot build/validate/swap, two-phase apply overlap, pinned symbols, rotation diff logs
    - Fetch pool symbols from Kraken AssetPairs (/USD) for 150–200 pool
    - Live runner: ticker/trade on pool, select L2/L3/exec after warmup, periodic refresh with rollback guard
    - Pipeline: optional execution-universe filter
    - Config: UNIVERSE_* limits and refresh controls
  397. 5d897ad
    L3 capacity test: plan, run script, report script
    Plus dans le corps du commit
    - docs/L3_CAPACITY_TEST_PLAN.md: degradation metrics, acceptance criteria, test matrix (L3=5/15/25/50), capacity table template, advice
    - scripts/run_l3_capacity_test.sh: one run with OBSERVE_SYMBOL_LIMIT_L3, fixed runtime/interval; optional MONITOR_RESOURCES=1
    - scripts/l3_capacity_report.sh: parse log + DB for run_id report (warmup, eval cycles, drift, samples)
  398. 985ff06
    TDD gaps + server validation + universe audit (git-only codeflow)
    Plus dans le corps du commit
    - live_runner: DATA_WARMUP_START/COMPLETE/INSUFFICIENT, min data requirements, execute_count/skip_count/primary_blockers in LIVE_EVALUATION_COMPLETED
    - state_machine: ORDER_SUBMIT/ACK/FILL/REJECT/CANCEL in logs for lifecycle proof
    - pipeline: UNIVERSE_VALIDATION log (symbols_seen, symbols_with_l3, funnel counts)
    - docs: ENGINE_TARGET_STATE, MODEL_CALIBRATION, SERVER_VALIDATION_LIVE_ENGINE, UNIVERSE_AND_DIVERSITY_AUDIT
    - scripts: validate_live_engine_server.sh (clean tree, build, run-execution-live 2+ cycles)
    - runbook: server validation section
  399. fa69932
    fix: live execution scheduling guard + min one evaluation; LIVE_EVALUATION_SCHEDULED/STARTED/COMPLETED/SKIPPED; runbook config
  400. 838c8c3
    feat: run-execution-live — long-running live validation with WS data + periodic pipeline
  401. a5f367a
    log primary_blockers in readiness_system_block root cause
  402. fd73aec
    fix: root cause of system_live_ready=false — log tradable_count/data_stale/regime; add end-current-run to set ended_at for execution
  403. 82d88a8
    fix: multiregime execution pipeline — per-pair strategy in readiness check, funnel drop counts, no_pair guard
  404. b183ae9
    audit: instrument constraints (execution layer) + execution pipeline funnel + EXECUTION_ENABLE
    Plus dans le corps du commit
    - docs/EXECUTION_INSTRUMENT_CONSTRAINTS_AUDIT.md: where constraints live (runtime Kraken only), submit_order normalizes, amend_order/stop-loss gaps
    - docs/EXECUTION_PIPELINE_AUDIT.md: funnel logging, no test-forcing, lifecycle checks
    - Pipeline: EXECUTION_PIPELINE_FUNNEL + EXECUTION_PIPELINE_CANDIDATE logging
    - Config: execution_enable (EXECUTION_ENABLE); runner gate; main EXECUTION_ENGINE_START
    - git-only-codeflow + EXECUTION_AUDIT: EXECUTION_ENABLE for run-execution-once
  405. c2e5c8b
    chore: make start_live_validation_engine_server.sh executable
  406. 212beee
    fix: validate_execution_on_server.sh load ~/.cargo/env when cargo not in PATH (ssh non-interactive)
  407. a30e38c
    fix: execution_report_to_event use incremental fill (cum_qty_before); AmendOrderParams skip order_id when None
  408. 5191d7c
    Doc: server validation steps for execution in git-only-codeflow rule
  409. 00ea2f5
    Validate script: fail on dirty tree, add SKIP_RUN, server instructions
  410. b793739
    Execution layer: migration, execution/*, db execution tables, runner, audit fixes, server validation script
    Plus dans le corps du commit
    - migrations/20260310120000_execution_schema.sql (orders, events, fills, positions, realized_pnl)
    - src/execution: intent, choke, order_state, kraken_adapter, state_machine, runner, exit (submit_exit_order)
    - src/db: execution_orders, order_events, fills, positions, realized_pnl
    - exchange: amend_order (messages + auth_ws), ExecutionReport.cl_ord_id
    - CLI run-execution-once; correlation by cl_ord_id/exchange_order_id; data array or object; reconcile path
    - positions: avg_entry only when increasing; realized_pnl on partial/full close
    - logging: order_ack cl_ord_id, order_submit queue_decision
    - docs/EXECUTION_AUDIT.md; scripts/validate_execution_on_server.sh (git-only codeflow)
  411. fd9c0cb
    fix: decode AVG(a) as Decimal, PERCENTILE_CONT p50/p95 as f64 in persist_pair_summary_24h
  412. 5d2bc17
    fix: decode p50/p95/a as f64 in persist_pair_summary_24h (PostgreSQL PERCENTILE_CONT returns FLOAT8)
  413. e64ed78
    fix: observe lifecycle — fractional OBSERVE_DURATION_HOURS + SIGTERM for clean ended_at
  414. e157c5a
    Pro engine: readiness, cost/surplus, live validation, post-trade layers, deploy /srv/krakenbot
  415. e4182b3
    Initial Kraken bot implementation

Changelog complet du site : docs/CHANGELOG_FINALISATIE.md