DEV Community

BlueWhale-Quant-Lab
BlueWhale-Quant-Lab

Posted on • Originally published at github.com

The Polymarket cross-cycle sandwich: a structural arbitrage in Up/Down markets

Search "Polymarket arbitrage" and you mostly find cross-venue bots (Kalshi vs Polymarket) or the textbook buy-YES-and-NO trade. There's a different edge hiding inside Polymarket's own crypto Up/Down markets — the cross-cycle sandwich. Here's the strategy from first principles, with an open-source scanner.

The key observation

Polymarket runs Up/Down markets on the same asset across cycle lengths: 5m, 15m, 1h, 4h, 1d. Two cycles of different lengths can end at the same wall-clock time (t_end). They then settle against the same final price P — but because they opened at different times, each has its own price to beat (PTB / strike).

That difference in strikes, for the same asset settling on the same P, is the opportunity.

The trade

Two same-t_end contracts with PTB_low < PTB_high. Buy:

  • UP (Yes) on the low-PTB contract, and
  • DOWN (No) on the high-PTB contract.
Where P lands UP wins (P>PTB_low) DOWN wins (P<PTB_high) legs paid
P > PTB_high yes no 1
PTB_low < P < PTB_high yes yes 2 (sweet)
P < PTB_low no yes 1

At least one leg always pays $1/share. So if the two legs cost total_cost < $1.00 per share, you collect ≥ $1 against a < $1 cost in every region — a structural arbitrage — and in the middle band you're paid on both (~2×).

The trap: the death sandwich

Reverse the direction (UP on the high strike, DOWN on the low) and the middle band — the region you most want — loses both legs. Direction is everything, which is why a reliable price-to-beat is non-negotiable.

The 5-minute pruning trick

Only 5m cycles opening at :10/:25/:40/:55 end on a 15-minute boundary and can share a t_end with the bigger cycles. The other ~67% can never pair — prune them before scanning.

The scanner

from polymarket_sandwich import find_pairs, settle_pair
pairs = find_pairs(contracts)          # cross-cycle, pruned, direction-correct, ranked
best  = pairs[0]
best.total_cost                        # e.g. 0.99 -> arbitrage
best.sweet_band                        # (PTB_low, PTB_high)
settle_pair(best, 70060, shares=10)    # {'sweet': True, 'winners': 2, 'pnl': 10.1}
Enter fullscreen mode Exit fullscreen mode

Repo (free, MIT, 13 tests, worked example):
https://github.com/BlueWhale-Quant-Lab/polymarket-cross-cycle-sandwich-arbitrage

The honest part

With cost < 1 and both legs filled, the pair can't lose. The real risk is execution — getting one leg but not the other, the price moving between your two orders, slippage, rate limits. That's the engineering, not the payoff, and it's what the rest of the toolkit (price-to-beat, live feed, order book, execution, hedge guard, rate limiter) and the complete multi-route engine handle.

Takeaway

Same asset, same settlement time, two different strikes → UP on the low strike, DOWN on the high strike; under $1 total and you've got a structural edge, with the band between strikes as your double. Get the direction from a real PTB, and respect that the hard part is execution.

Top comments (0)