DEV Community

Jonathan Peterson
Jonathan Peterson

Posted on

I tapped into a public WebSocket feed and found a consistent pricing gap on Polymarket hiding in plain sight.

Check it out: https://github.com/JonathanPetersonn/oracle-lag-sniper

Here’s the setup:

Two live data streams running side by side:

  • One from the Chainlink oracle that settles Polymarket’s 15-minute crypto markets
  • One from the Polymarket order book itself

And the difference between them is where things get interesting.

The oracle updates almost instantly.
The order book takes ~55 seconds to catch up.

That means for nearly a full minute, tokens are trading on stale information — even though the “true” settlement price is already publicly available.

Anyone can access that feed. It’s not hidden.
It just consistently runs ahead of the market it decides.

At first, it feels like a bug.

It’s not.

It’s just a rare case where a market inefficiency is clean, measurable, and sitting out in the open.

So I built a bot to act on it — about 1,400 lines of Python.

The strategy itself is almost boring:

On every oracle tick, check three conditions:

price moved > 0.07% from market open  
time remaining > 5 minutes  
token price < $0.62  
Enter fullscreen mode Exit fullscreen mode

If all three are true → buy.

Then wait for settlement: either $1 payout or a loss.

Simple logic. The real challenge was making it actually work in real time.

This isn’t a script you run once — it’s a system that has to stay alive.

You need one process that:

  • Maintains a live WebSocket with sub-second updates
  • Tracks 16 overlapping markets
  • Evaluates signals on every tick
  • Places orders without blocking the feed
  • Sends Telegram alerts
  • Persists state for crash recovery
  • Monitors itself for failures

All at once.

Since it’s almost entirely IO-bound, asyncio was the natural fit.

Seven concurrent tasks, one event loop:

tasks = [
    oracle.run(shutdown),
    market_lifecycle_loop(...),
    signal_evaluation_loop(...),
    telegram.run(shutdown),
    state_persist_loop(...),
    redeem_loop(...),
    sanity_check_loop(...),
]
await asyncio.gather(*tasks)
Enter fullscreen mode Exit fullscreen mode

No threads. No locks. Just coordinated async tasks.

The hardest bug I hit wasn’t logic — it was silence.

Zombie WebSocket connections.

Everything looked healthy:

  • Connection open
  • Ping/pong working
  • No exceptions

But price data had stopped flowing.

The bot just… idled. Quietly. Indefinitely.

Timeouts didn’t catch it because heartbeat frames were still coming in.

The fix was subtle: track the timestamp of the last real price update using a monotonic clock. If that goes stale, kill the connection and force a reconnect.

That one took multiple evenings to track down.

Another painful lesson: silent failures outside the core system.

I once ran the bot for 20 minutes thinking everything was perfect — only to realize all Telegram notifications were failing due to a wrong chat ID.

No errors. No warnings. Just missing visibility.

Now the bot validates everything upfront:

  • Calls Telegram getMe
  • Sends a test message
  • Verifies API keys

If anything is wrong, it fails immediately:

ERROR: Telegram chat_id=123456 is invalid.  
Tip: send a message to your bot, then use getUpdates to find your chat_id.
Enter fullscreen mode Exit fullscreen mode

On the data side:

  • 8,876 resolved markets
  • 146,000 price points
  • 5,017 trades triggered
  • 61.4% win rate across BTC, ETH, XRP, SOL

I tried hard to break it:

  • Out-of-sample date splits
  • Parameter sweeps
  • Doubled fees
  • Day-by-day analysis

It held up every time.

But let’s keep expectations grounded.

Liquidity is thin.
Execution won’t be perfect.
Other bots are already competing.
And this edge will compress as it gets discovered.

This isn’t a magic money machine.

It’s a small, repeatable inefficiency — and a good example of what happens when public data moves faster than the market built on top of it.

I’ve open-sourced the whole thing:

  • Bot
  • Backtests
  • Data
  • Live demo mode (no API keys needed)

https://github.com/JonathanPetersonn/oracle-lag-sniper

If you’ve built similar real-time asyncio systems, I’d love to hear how you approached it.

This pattern — many long-lived, stateful tasks sharing a single event loop — feels common, but surprisingly under-documented in the wild.

Top comments (0)