Polymarket Soccer Shock Ladder

API key required
Other

Fade sharp in-play price shocks on Polymarket soccer markets with a laddered limit-buy strategy (Roan's FIFA-quant framework). Pro skill. Currently scoped to 2026 World Cup markets. Simmer's server detects shocks in real time and emits pre-sized signals; this skill places the recovery ladder and manages the exit.

Install

openclaw skills install polymarket-soccer-shock-ladder

World Cup Shock Ladder

Fade sharp price shocks on Polymarket World Cup markets. When a market's price drops hard during a match, this skill places a laddered set of limit buys below the pre-shock price, betting on a partial recovery, then exits each fill at a fixed target.

The strategy follows @RohOnChain's published "Trade FIFA Like A Quant" framework. The heavy lifting (detecting the shock, classifying it, sizing the rungs from historical depth distributions) runs on Simmer's server in real time. This skill is the thin execution layer: it reads the pre-sized signal and works the orders.

🚨 Framework, not a guaranteed edge. Read DISCLAIMER.md before going live. Runs in dry-run by default. Edges depend on spreads, fees, latency, and whether shocks actually recover. Start in dry-run, then go live small.

Requires Simmer Pro. Shock signals are delivered over the Pro reactor stream. Upgrade at simmer.markets/dashboard if you hit a 402 on connect.

Polymarket only. The ladder is a CLOB limit-order strategy. $SIM (the LMSR venue) has no order book to rest bids on, so --venue sim is a plumbing smoke test only, not a real run.

How it works

A "shock" is a fast price drop on one outcome during an in-play match. Simmer's server watches every WC market's live order flow, detects the drop, and figures out how deep shocks like this one usually go (from its own accumulated history, bucketed by favoritism, order-book depth, match minute, and goal state). It emits a signal with the pre-shock price and the depth percentiles.

This skill turns that signal into Roan's ladder:

  1. Four limit BUY orders on the shocked outcome, priced at pre_price - depth for the 50th, 75th, 90th, and 95th depth percentiles. Deeper rungs only fill if the price keeps falling.
  2. Sizes weighted 10 / 20 / 30 / 40 percent of your per-shock stake (deeper rungs carry more, since bigger drops have more room to recover).
  3. As rungs fill, it places a SELL for each at the fill price plus a fixed recovery target (default 4 cents).
  4. Unfilled rungs are cancelled after a short TTL (default 60 seconds).

By default the skill only acts on moderate-favorite markets (pre-shock price 0.75 to 0.85), Roan's most profitable band. You can widen or change that.

Setup

  1. Install the Simmer SDK (0.17.0 or newer):

    pip install -U 'simmer-sdk>=0.17.0'
    
  2. Set your Simmer API key (Pro plan required):

    export SIMMER_API_KEY=...   # simmer.markets/dashboard → SDK tab
    
  3. Set your Polymarket wallet key (only for --live; not needed in dry-run):

    export WALLET_PRIVATE_KEY=0x...
    

    The SDK signs orders locally. External and OWS wallets work with no extra setup.

  4. Arm for shock signals (self-serve, Pro required). Shock-ladder delivery is opt-in. Turn it on for your account:

    curl -X PATCH https://api.simmer.markets/api/sdk/shock-ladder/config \
      -H "Authorization: Bearer $SIMMER_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"enabled": true}'
    

    You get {"shock_ladder_enabled": true} back. The server starts fanning World Cup shocks to your reactor pending feed within ~5 minutes. Check your state anytime with a GET to the same URL, and disarm with {"enabled": false}.

Running it

Dry-run is the default. It shows the exact ladder and exits it would place, without trading. Review that before going live.

# Dry-run, single poll (safe; good for a cron)
python shock_ladder_trader.py --once

# Dry-run, continuous (poll every 2s)
python shock_ladder_trader.py

# LIVE, single poll
python shock_ladder_trader.py --once --live

# LIVE, continuous
python shock_ladder_trader.py --live

Recommended: cron with --once. Shock signals are time-sensitive; a 1-minute cron gives reliable coverage even across restarts. A single --once run places a ladder and then manages its fills and exits over the TTL window before exiting, so one run fully handles one shock.

# crontab
*/1 * * * * cd /path/to/skill && python3 shock_ladder_trader.py --once --live

# OpenClaw cron
openclaw cron add --name "shock-ladder" --cron "*/1 * * * *" --tz UTC --session isolated \
  --message "Run: cd /path/to/skill && python3 shock_ladder_trader.py --once --live"

Configuration

All parameters are skill-side (no server config). Defaults are conservative.

SettingEnv varCLI flagDefaultNotes
Per-shock stakeSHOCK_LADDER_STAKE_USD--stake15Total USDC split across the 4 rungs.
VenueSHOCK_LADDER_VENUE--venuepolymarketsim is a smoke test only.
Order TTL(CLI only)--ttl60Seconds before unfilled rungs are cancelled.
Exit target(CLI only)--exit-cents4Cents above the fill for the exit sell.
Bucket filter(CLI only)--bucketsmoderateComma-separated favoritism bands. Empty string acts on all.
Poll cadenceSHOCK_LADDER_POLL_INTERVAL_S--interval2Fill-check / loop cadence in seconds.

Favoritism bands (set with --buckets): heavy, moderate, slight, balanced, underdog. Roan's tuning favored moderate. Example, act on moderate and slight:

python shock_ladder_trader.py --once --live --buckets moderate,slight

What happens per signal

  1. Poll GET /api/sdk/reactor/pending, keep only type: "shock_ladder" signals (so it never collides with copytrading signals on the same feed).
  2. Apply the bucket filter. Out-of-band, malformed, or unsizable signals are skipped and removed (never retried).
  3. Compute the 4 rung prices and sizes from pre_price and the depth percentiles.
  4. Place the rungs as GTC limit buys (or log them in dry-run).
  5. Poll fills; for each filled rung, place the exit sell at fill + target.
  6. Cancel any rung still unfilled at the TTL.
  7. Delete the signal so it is not reprocessed.

Notes and limits

  • Dry-run first, always. Confirm the laddered prices and sizes look right for a few real shocks before --live. Keep --stake small to start.
  • Partial fills. The v1 fill check treats a rung that has left the order book as filled and sizes its exit from the rung's intended shares. Exact partial-fill reconciliation is a planned refinement. Keep size small until you have watched it on live shocks.
  • In-testing. This is a Community / in-testing skill for the 2026 World Cup. Its bucket sizing sharpens as Simmer accumulates more observed shocks during the tournament.

Troubleshooting

"0 pending shock signals" every run

  • Normal between shocks. Shocks are rare and only fire during live matches.
  • Confirm you are armed: a GET https://api.simmer.markets/api/sdk/shock-ladder/config (with your SIMMER_API_KEY) should return {"shock_ladder_enabled": true}. If not, re-run Setup step 4. Pro plan required.
  • Pre-tournament there are no in-play shocks; expect signals once matches start.

402 on connect

  • The reactor stream is Pro-gated. Upgrade at simmer.markets/dashboard.

Orders rejected in --live

  • Check WALLET_PRIVATE_KEY is set and the wallet holds USDC on Polymarket.
  • Thin books right after a shock can reject; the resting limit design tolerates this better than a market order, but very thin markets may still not fill.

"venue=sim" warning

  • Expected. The ladder needs a CLOB; $SIM cannot run it. Use --venue polymarket.