A few weeks ago, I decided to build my own Polymarket bot. The full version took me a few weeks to complete.
I was willing to invest this effort because there are indeed inefficiencies on Polymarket. Although there are already some bots exploiting these inefficiencies for profit, it's far from enough. The opportunities in this market still far outnumber the bots.
Bot Construction Logic
The bot's logic is based on a strategy I previously executed manually. To improve efficiency, I automated it. This bot runs on the "BTC 15-minute UP/DOWN" market.
The bot runs a real-time monitoring program that can automatically switch to the current BTC 15-minute round, stream the best bid/ask via WebSocket, display a fixed terminal UI, and allow full control via text commands.
In manual mode, you can place orders directly.
buy up
buyshares up
Automatic mode runs a repeating two-leg cycle.
First, it only observes price fluctuations within the first windowMin minutes after each round starts. If either side drops sufficiently fast (a drop of at least movePct within about 3 seconds), it triggers "Leg 1," buying the side that just crashed.
After completing Leg 1, the bot will never buy the same side again. It waits for "Leg 2 (the hedge)" and only triggers it when the following condition is met: leg1EntryPrice + oppositeAsk <= sumTarget.
When this condition is met, it buys the opposite side. After Leg 2 is completed, the cycle ends, and the bot returns to the observation state, waiting for the next crash signal using the same parameters.
If the round changes during the cycle, the bot abandons the open cycle and restarts with the same settings in the next round.
The parameters for automatic mode are set as follows: auto on
· shares: The position size used for both legs of the trade.
· sum: The threshold allowed for hedging.
· move (movePct): The crash threshold (e.g., 0.15 = 15%).
· windowMin: The duration from the start of each round during which Leg 1 is allowed to execute.
Backtesting
The bot's logic is simple: wait for a violent price drop, buy the side that just finished dropping, then wait for the price to stabilize and hedge by buying the opposite side, while ensuring: priceUP + priceDOWN < 1.
But this logic needed testing. Does it really work in the long run? More importantly, the bot has many parameters (shares, sum, move percentage, window minutes, etc.). Which parameter set is optimal and maximizes profit?
My first thought was to let the bot run live for a week and observe the results. The problem was that this would take too long and only test one parameter set, while I needed to test many.
My second thought was to backtest using historical data from the Polymarket CLOB API. Unfortunately, for the BTC 15-minute UP/DOWN market, the historical data endpoint kept returning empty datasets. Without historical price ticks, the backtest couldn't detect "a crash within about 3 seconds," couldn't trigger Leg 1, and would yield 0 cycles and 0% ROI regardless of parameters.
After further investigation, I found other users encountered the same issue fetching historical data for certain markets. I tested other markets that did return historical data and concluded that for this specific market, historical data simply isn't retained.
Due to this limitation, the only reliable way to backtest this strategy was to create my own historical dataset by recording the real-time best-ask prices while the bot was running.
The logger writes snapshots to disk containing:
· Timestamp
· Round identifier (round slug)
· Seconds remaining
· UP/DOWN token IDs
· UP/DOWN best ask prices
Subsequently, the "recorded backtest" replays these snapshots and deterministically applies the same automatic logic. This guarantees access to the high-frequency data needed to detect crashes and hedging conditions.
I collected 6 GB of data over 4 days in total. I could have recorded more, but I deemed it sufficient for testing different parameter sets.
I started testing this parameter set:
· Initial balance: $1,000
· 20 shares per trade
· sumTarget = 0.95
· Crash threshold = 15%
· windowMin = 2 minutes
I also applied a constant 0.5% fee and a 2% spread to stay in a conservative scenario.
The backtest showed an ROI of 86%, turning $1,000 into $1,869 in just a few days.
Then I tested a more aggressive parameter set:
· Initial balance: $1,000
· 20 shares per trade
· sumTarget = 0.6
· Crash threshold = 1%
· windowMin = 15 minutes
Result: -50% ROI after 2 days.
This clearly shows that parameter selection is the most critical factor. It can make you a lot of money or lead to significant losses.
Limitations of Backtesting
Even with fees and spreads included, backtesting has its limitations.
· First, it only used a few days of data, which might not be enough for a comprehensive market perspective.
· It relies on recorded best-ask snapshots; in reality, orders might be partially filled or filled at different prices. Furthermore, order book depth and available volume are not modeled.
· Micro-fluctuations below the second level are not captured (data is sampled once per second). The backtest has 1-second timestamps, but a lot can happen between seconds.
· In the backtest, slippage is constant, and variable latency (e.g., 200–1500 ms) or network spikes are not simulated.
· Each leg of the trade is considered "instantly" executed (no order queuing, no pending orders).
· Fees are charged uniformly, whereas in reality fees might depend on: market/token, maker vs. taker, fee tier, or conditions.
To be pessimistic (prudent), I applied a rule: if Leg 2 fails to execute before the market closes, Leg 1 is considered a total loss.
This is deliberately conservative but doesn't always match reality:
· Sometimes Leg 1 can be closed early,
· Sometimes it ends in-the-money (ITM) and wins,
· Sometimes the loss can be partial rather than total.
While losses might be overestimated, this provides a practical "worst-case" scenario.
Most importantly, backtesting cannot simulate the impact of your large orders on the order book or attracting other traders to hunt you. In reality, your orders can:
· Disturb the order book,
· Attract or repel other traders,
· Cause non-linear slippage.
The backtest assumes you are a pure price taker with no influence.
Finally, it does not simulate rate limits, API errors, order rejections, suspensions, timeouts, reconnections, or the bot being busy and missing signals.
Backtesting is extremely valuable for identifying good parameter ranges, but it is not a 100% guarantee, as some real-world effects cannot be modeled.
Infrastructure
I plan to run this bot on a Raspberry Pi to avoid consuming resources on my main machine and maintain 24/7 operation.
But there is still significant room for improvement:
· Using Rust instead of JavaScript would provide far superior performance and processing times.
· Running a dedicated Polygon RPC node would further reduce latency.
· Deploying on a VPS close to Polymarket's servers would also significantly reduce latency.
There are certainly other optimizations I haven't discovered yet. Currently, I am learning Rust as it is becoming an indispensable language in Web3 development.














