Skip to content

147: Restore merge-arb strategy as layer ​

Identity ​

  • Status: restoration design (open, prereq blockers)
  • Spawned from: F6 of ADR-024 pivot, 2026-05-13
  • Original module: crates/pm-bot/src/domain/strategies/merge_arb.rs (~1843 LOC at archive)
  • Recovery: git show 619f7f0:crates/pm-bot/src/domain/strategies/merge_arb.rs
  • Target taxonomy: binary-arb::JitterCapture profile (per ADR-018)
  • Original ADR: ADR-003 (merge-arb-strategy, ACCEPTED → ARCHIVED-BY-024)
  • Type: restoration

Context ​

Merge-arb captures jitter on 50/50 binary markets by accumulating both YES and NO inventory at sub-mid prices, then calling on-chain mergePositions for collateral recovery + maker rebate. For neg-risk markets (~63.5% of production per Agent A), NegRiskAdapter.convertPositions is the analogue path.

Data model needs (from polymarket-data-model.md) ​

  • Market.tokens[0..2].best_bid/best_ask — both sides
  • Market.neg_risk — gates merge vs convert path
  • Market.condition_id — required for both mergePositions and convertPositions
  • Market.tick_size — both sides
  • Per-side orderbook depth for accumulation pacing

Restoration scope ​

  • New module: crates/pm-bot/src/domain/strategies/merge_arb.rs (layer)
  • Profile JitterCapture per ADR-018 §binary-arb
  • Implement ScannerHooks + Strategy trait per ADR-025
  • Strategy config: merge_arb.* knobs (entry_band, accumulation_cap, merge_threshold, etc.)
  • Add StrategyType::MergeArb variant back + dispatcher branch
  • Config schema: new crates/pm-bot/src/config/strategy/merge_arb.rs (recover from 619f7f0)

Prerequisites (CRITICAL BLOCKERS) ​

  • pm_sdk::onchain::ctf::convert_positions отсутствует (per Agent B finding). NegRiskAdapter convertPositions(bytes32 conditionId, uint256 amount) semantics required per Agent A Q-A2. Hard blocker until implemented in pm-sdk.
  • NEW Decision variant: Decision::CallConvertPositions { condition_id, amount } — extension Decision enum must route to NegRiskAdapter executor.
  • NEW Executor handler: route CallConvertPositions to pm_sdk::onchain::neg_risk_adapter.
  • Existing Decision::CallMergePositions (standard CTF path) for non-neg-risk subset.

Restoration prereq commit chain:

  1. Implement pm_sdk::onchain::ctf::convert_positions (or under pm_sdk::onchain::neg_risk_adapter) — NegRiskAdapter contract call wrapper
  2. Add Decision::CallConvertPositions variant
  3. Add executor handler routing to NegRiskAdapter
  4. Restoration of merge-arb strategy itself

Out of scope ​

  • Не модифицировать core engine за пределами ScannerHooks + Decision variant + executor handler.
  • Не trash strategy isolation invariant.

Acceptance criteria ​

  • ScannerHooks impl green per contract test pattern
  • convertPositions integration test against fork (или mocked Polygon RPC)
  • Neg-risk-aware merge gating: standard CTF path для non-neg-risk, NegRiskAdapter для neg-risk
  • VERIFIED markers per feedback_silent_feature_broken
  • ADR-003 amendment OR new ADR confirming restoration semantics + neg-risk path

Dependencies ​

  • HARD blocker: pm-sdk convert_positions implementation (separate commit/PR).
  • Independent of #145, #146, #148, #149.

LOC estimate ​

  • pm-sdk convert_positions wrapper: ~150-250 LOC + tests
  • Decision variant + executor: ~200-300 LOC
  • New strategy: ~1500-2000 LOC (originally 1843; ReadCtx-slimmer)
  • New strategy config: ~150 LOC
  • Tests: ~500-800 LOC (fork test for convertPositions critical)
  • Total (prereq + strategy): ~3000-4000 LOC

References ​

  • ADR-024, ADR-025, ADR-003
  • 619f7f0 (archive reference)
  • .tmp/audit-reports/A-data-model.md Q-A2 (convertPositions semantics)
  • .tmp/audit-reports/B-pm-sdk.md (convert_positions gap)
  • docs/architecture/polymarket-data-model.md (neg_risk distribution)