Тема
150: Restore paired-merge strategy as layer
Identity
- Status: archived — code removed from the workspace build, restoration undesigned
- Spawned from:
paired-merge-strategy-archival(operator decision 2026-09-07) - Original module:
crates/pm-engine/src/strategies/paired_merge/(6751 LOC across six files) +crates/pm-engine/src/ports/paired_admission.rs(118 LOC) +crates/pm-engine/src/engine/paired_rediscovery.rsand its_tests.rs(1402 LOC combined) — 8271 LOC total at archive - Recovery:
git show 210f52d7:<path>for each of the following ten files (git anchor210f52d7is the commit this archival branched from, taken before the archival's own commits exist)git show 210f52d7:crates/pm-engine/src/strategies/paired_merge/mod.rsgit show 210f52d7:crates/pm-engine/src/strategies/paired_merge/config.rsgit show 210f52d7:crates/pm-engine/src/strategies/paired_merge/admission.rsgit show 210f52d7:crates/pm-engine/src/strategies/paired_merge/runtime_config.rsgit show 210f52d7:crates/pm-engine/src/strategies/paired_merge/runtime_state.rsgit show 210f52d7:crates/pm-engine/src/strategies/paired_merge/strategy.rsgit show 210f52d7:crates/pm-engine/src/ports/paired_admission.rsgit show 210f52d7:crates/pm-engine/src/engine/paired_rediscovery.rsgit show 210f52d7:crates/pm-engine/src/engine/paired_rediscovery_tests.rsgit show 210f52d7:docs/strategies/spread-mm-yield-calculator.md(the EV math behind the strategy — not a code file, removed by the same archival because a live SoT directory is the wrong home for a document about an archived strategy)
- Target taxonomy: paired YES+NO market-making,
strategy_type='paired-merge'(operator-facing name:spread-mm) - Original design: no ADR tracks this algorithm. It was designed and shipped through the openspec change
spread-mm-strategy, frozen atdocs/history/openspec/changes/2026-08-31-spread-mm-strategy/. That card is the first source for every design decision behind the strategy (mechanism, admission gate, rediscovery loop, EV model) — this plan does not repeat them - Type: restoration (recovery — real code existed, was wired end to end, and ran in production)
Context
Paired-merge quoted both legs (YES and NO) of a binary market passively, held the matched set to resolution, and took the spread as edge: YES_bid + NO_bid = 1 − spread. It was not a design sketch — it was fully wired through the config loader, pm-cli, the dashboard API, and the engine's composition root, with its own admission gate (ports::paired_admission) and an isolated periodic rediscovery loop (engine::paired_rediscovery) separate from the main strategy dispatch path.
The strategy was archived by a standalone operator decision on 2026-09-07 — not part of the ADR-024/F4 wave that archived bucket-arb, maker, maker-rebate, merge-arb, and single-token-mm. Two reasons drove it, both independent of the mechanism's own merit:
- ADR-024 already restricts pm-engine to one active strategy (
directional/Harvester). Paired-merge's own earlier openspec cycle (spread-mm-strategy) had re-widened the engine's type gate to accept a secondstrategy_typepast that restriction — a governance gap that this archival closes, not a defect in the trading logic itself. - Its presence forced unrelated engine work to keep accounting for paired-merge-specific branches: three concurrent cards (
stop-exit-residual-elimination,orderbook-freshness-observability,confirmed-coverage-forbids-unsellable-verdict) each ran into it to some degree before this archival.
The only production run of the strategy — instance spread-mm-v1 on the VPS, 22–23.07.2026 — placed 49 BUY orders and had every one of them rejected by the exchange (status='REJECTED'), leaving zero rows in positions and zero fills. The container has not existed since 23.07. (Live query against the VPS, 2026-09-07: sqlite3 …/spread-mm/data/engine.db "SELECT side, status, COUNT(*) FROM orders GROUP BY side, status" → BUY|REJECTED|49; SELECT COUNT(*) FROM positions → 0.) The archival does not clean up that instance or its config.db row — that cleanup is a separate operator decision.
Restoration scope
- A fresh implementation of the paired-quoting mechanism as an extension-layer strategy per ADR-025, not a resurrection of the removed module as a parallel in-tree branch — the removed code predates the extension points API and was wired directly into the engine's composition root and its own isolated rediscovery loop, both of which are exactly the kind of coupling ADR-025 exists to remove.
- The admission gate (
evaluate_paired_admission/filter_admitted, ~490 lines, 12 tests at archive) is the most reusable piece: it was already pure functions behind a narrow port (ports::paired_admission::PairedAdmission), not entangled with engine internals. - The paired YES/NO matcher (
resolve_pair, lived onports::market_dataat archive) and the strategy config shape (PairedMergeConfig, typed JSON understrategies.definitionper ADR-015 — same discipline asdirectional.*) restore largely as-is; the mechanism itself did not change between design and archival. - The isolated
engine::paired_rediscoveryperiodic loop needs re-justifying, not reusing verbatim — see Out of scope.
Prerequisites
- ADR-025 ACCEPTED — extension points API stabilized on the one live strategy (directional/Harvester), same general contract as every other item in this backlog.
- pm-engine SDK (openspec
unify-orders-positions-signals-events) ready as a hosting platform, same general contract as every other item in this backlog. - The zero-fill production run (49/49 BUY rejected) must be root-caused before any restoration work starts. Restoring the same admission and quoting logic without knowing why every order the strategy placed was rejected would repeat a strategy that never produced a single fill.
- The EV model backing the strategy (
docs/strategies/spread-mm-yield-calculator.mdat archive) is no longer a live document — it is recoverable at the git anchor above, but restoration must re-derive and re-validate it against forward data before any live use, per the project's standing EV-validation rule (no ride-to-resolution counterfactuals, no survivorship, resolved outcomes only). - The governance gap that let an earlier openspec cycle widen the engine's supported-type gate past ADR-024 must not repeat: any restoration proposal has to get ADR-025 acceptance (or an explicit ADR-024 amendment) before touching
SUPPORTED_STRATEGY_KINDSorSTRATEGY_TYPESagain.
Out of scope
- Restoring the strategy in its archived form without first explaining the 49/49 rejected BUY orders — that is a defect investigation, not a restoration task.
- Restoring it as a second strategy dispatched in parallel to
directionaloutside the ADR-025 extension API — that is the exact coupling this archival removed. - Reusing the isolated
engine::paired_rediscoveryperiodic-loop design unexamined. It existed as a separate cycle from the main strategy dispatch specifically because paired-merge needed to re-evaluate admission independently of per-tick strategy evaluation; whether that isolation is still the right shape once an extension API exists is a restoration-time design question, not a given.
Acceptance criteria
Strategytrait implementation (or its ADR-025 successor) green under the same contract-test pattern used fordirectional.- Admission-gate contract test ported and green (the 12 existing unit tests at archive are a starting inventory, not a target to hit exactly).
- EV model re-validated on post-restoration forward data, per the project's Harvester EV reference model, before any claim of profitability.
- A named, evidence-backed answer for why the only production run rejected 100% of its BUY orders, checked against the restored admission/sizing logic.
VERIFIEDmarkers on restored behavior per the project's silent-feature-broken guard.- ADR-025 ACCEPTED (or an ADR-024 amendment) recorded before the type gate accepts
paired-mergeagain.
Dependencies
- Hard blocker, shared with every item in this backlog: ADR-025 extension points API ACCEPTED.
- Hard blocker, shared with every item in this backlog: pm-engine SDK hosting platform ready.
- Sequencing against
merge-arb(_plans/147-restore-merge-arb-as-layer.md, priority #1 in this backlog) is an operator decision, not a technical dependency — the two strategies do not share removed infrastructure beyond the general ADR-025/SDK prerequisites above.