Skip to content

148: Restore bucket-arb strategy as layer ​

Identity ​

  • Status: restoration design (open, multiple prereq blockers)
  • Spawned from: F6 of ADR-024 pivot, 2026-05-13
  • Original module: crates/pm-bot/src/domain/strategies/bucket_arb/ (~7200 LOC, multi-file at archive)
  • Recovery: git show 619f7f0 -- crates/pm-bot/src/domain/strategies/bucket_arb/
  • Target taxonomy: multi-arb::Basket profile (per ADR-018)
  • Original ADR: none specific (was implementation of ADR-018 §multi-arb)
  • Type: restoration (largest)

Context ​

Bucket-arb captures arbitrage on neg-risk multi-outcome events with ≥3 buckets, where Σ(bucket_prices) drifts from 1.0 → can short the basket. FSM tracks N child markets simultaneously, posts coordinated basket orders, and exits на convertPositions post-resolution. Per Agent A: 63.5% production markets are neg-risk — high-value target. Largest archived strategy (~7200 LOC across multiple files).

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

  • Event-level view: Event.markets[] — children sharing neg_risk_market_id
  • Per-child: Market.tokens[0..2].best_bid/best_ask, Market.tick_size
  • Market.neg_risk (must be true)
  • Market.condition_id per child + Event.neg_risk_market_id parent
  • Cross-market Σ-price aggregation for basket valuation

Restoration scope ​

  • New module tree: crates/pm-bot/src/domain/strategies/bucket_arb/ (layer)
  • Profile Basket per ADR-018 §multi-arb
  • FSM phases (recover from 619f7f0): discovery → eligibility → entry → maintain → exit
  • Implement ScannerHooks + Strategy trait per ADR-025
  • Strategy config: multi_arb.* knobs (basket_min_n, target_sum_margin_bps, etc.)
  • Add StrategyType::BucketArb (or MultiArb) variant + dispatcher branch
  • Config schema: new crates/pm-bot/src/config/strategy/multi_arb.rs

Prerequisites (CRITICAL BLOCKERS) ​

  • EventView trait отсутствует — per F3 §8.4 gap. Strategy needs cross-market view of neg-risk event siblings (FSM tracks N child markets simultaneously). Must be added to ReadCtx extension API per ADR-025.
  • Decision::PostBucketArbBasket + BasketLegPlan removed in F4.5 — restoration adds back through ADR-025 Decision contract evolution. Atomic multi-leg placement required.
  • pm_sdk::onchain::ctf::convert_positions — shared blocker with #147 (merge-arb). Bucket-arb exit path also requires NegRiskAdapter convertPositions for collateral recovery.
  • EventView consumer in scanner: hook routing must surface event-level updates (currently scanner is market-level only per F3 finding).

Restoration prereq commit chain:

  1. Implement EventView trait + ReadCtx extension (NEW infra)
  2. Implement pm_sdk::onchain::ctf::convert_positions (shared with #147)
  3. Add Decision::PostBucketArbBasket + BasketLegPlan types
  4. Add executor handler for atomic basket placement (rollback on partial fail)
  5. Scanner event-level dispatch (route Event updates to hooks)
  6. Restoration of bucket-arb FSM itself

Out of scope ​

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

Decomposition note ​

Per ADR-018 plan was Sprint 4 (~6 sprints total); restoration is approximately Sprint 4-scale work. Consider sub-decomposition into separate backlog items per FSM phase:

  • 148a: EventView + ReadCtx extension infrastructure
  • 148b: Decision::PostBucketArbBasket + executor (atomic multi-leg)
  • 148c: FSM discovery + eligibility phases
  • 148d: FSM entry + maintain phases
  • 148e: FSM exit + convertPositions integration

Decomposition deferred — user decision pending.

Acceptance criteria ​

  • ScannerHooks impl green per contract test pattern (event-level routing tested)
  • EventView contract test
  • Atomic basket placement test (all-or-nothing semantics)
  • Convert-on-exit fork or RPC-mock test
  • VERIFIED markers per feedback_silent_feature_broken
  • New ADR (post-024) confirming restoration semantics + EventView design

Dependencies ​

  • HARD blocker: EventView trait (NEW infra commit).
  • HARD blocker: pm-sdk convert_positions (shared with #147).
  • HARD blocker: Decision::PostBucketArbBasket variant.
  • Independent of #145, #146, #149.

LOC estimate ​

  • EventView trait + ReadCtx extension: ~400-600 LOC
  • Decision variant + atomic executor: ~500-700 LOC
  • Scanner event-level dispatch: ~200-400 LOC
  • New strategy FSM: ~4000-5000 LOC (originally 7200; ReadCtx + EventView-slimmer)
  • New strategy config: ~250 LOC
  • Tests: ~1000-1500 LOC (FSM regression + atomic placement critical)
  • Total: ~6500-8500 LOC (largest restoration)

References ​

  • ADR-024, ADR-025, ADR-018 §multi-arb
  • 619f7f0 (archive reference)
  • .tmp/audit-reports/A-data-model.md (neg-risk 63.5% distribution)
  • .tmp/audit-reports/F-extension-design.md §8.4 (EventView gap)
  • docs/architecture/polymarket-data-model.md (Event ↔ Market relations)
  • docs/strategies/bucket-arbitrage.md (legacy FSD)