Prediction-market trading looks simple at first: choose YES or NO, place an order, and wait for the market to resolve.
In practice, short-duration markets can behave very differently.
After Polymarket introduced TWAP-style execution and liquidity behavior, I noticed an interesting pattern in some markets: when the best ask begins moving in one direction, that movement can persist for a period of time instead of immediately reverting.
That creates an interesting trading opportunity.
Instead of trying to predict the final outcome directly, we can trade the momentum of the token price.
The problem is that momentum does not always continue.
Sometimes the market reverses sharply.
This is where a hedge layer becomes important.
The result is a strategy that combines:
- Momentum detection
- Aggressive token accumulation
- Short-term price persistence
- Directional positioning
- Automatic hedging
- Arbitrage-style risk reduction
- Position and exposure management
I call this approach the Polymarket Momentum Arbitrage Bot.
In this tutorial, we will build the strategy from scratch using Python.
What Is the Polymarket Momentum Arbitrage Bot?
The core idea is simple:
When the best ask starts moving consistently in one direction, follow the momentum while maintaining a hedge against reversal.
Suppose a YES token is trading like this:
$0.42
$0.43
$0.45
$0.47
$0.49
$0.52
The important information isn't simply that YES is now $0.52.
The important information is that the market has been repricing YES continuously in the same direction.
This can indicate that aggressive buyers are consuming liquidity.
Our bot detects this behavior and increases its exposure.
But imagine the price instead does this:
$0.42
$0.44
$0.47
$0.50
$0.53
$0.46
$0.40
The momentum signal was correct temporarily, but the market eventually reversed.
Without risk management, the bot could give back most of its profits.
Therefore, the strategy needs two layers:
Market Data
│
▼
Momentum Detector
│
┌──────────┴──────────┐
│ │
▼ ▼
Momentum Position Hedge Position
│ │
└──────────┬──────────┘
▼
Risk Manager
│
▼
Execution
The momentum layer tries to profit from continuation.
The hedge layer protects the account when continuation fails.
How the Strategy Works
The strategy can be divided into five stages:
- Collect order-book data
- Detect momentum
- Enter the momentum position
- Monitor for continuation or reversal
- Hedge or exit when necessary
Let's examine each part.
1. Collecting Best Ask Data
The first piece of information we need is the current best ask.
For a token, define:
best_ask
as the lowest price at which someone is currently willing to sell.
We don't want to look at only one observation.
Instead, maintain a rolling history:
price_history = [
0.420,
0.423,
0.428,
0.435,
0.442,
]
This allows us to calculate:
- Price change
- Momentum
- Momentum acceleration
- Short-term volatility
- Direction
- Reversal probability
A simple momentum calculation is:
momentum = current_price - price_n_periods_ago
For example:
momentum = 0.442 - 0.420
which gives:
+0.022
or approximately:
+5.24%
2. Why Best Ask Momentum Matters
A common mistake when building a prediction-market bot is looking only at the current price.
For momentum trading, the path of the price is often more important.
Compare these two sequences.
Market A
0.42
0.43
0.44
0.45
0.46
0.47
Market B
0.42
0.47
0.43
0.48
0.46
0.47
Both markets may currently be around $0.47.
But their microstructure is very different.
Market A has persistent upward movement.
Market B is oscillating.
Our bot should prefer Market A.
This is why the bot maintains a rolling price window.
3. Building a Momentum Detector
Let's create a simple Python momentum detector.
from collections import deque
class MomentumDetector:
def __init__(self, window_size=10):
self.prices = deque(maxlen=window_size)
def update(self, price):
self.prices.append(price)
def momentum(self):
if len(self.prices) < 2:
return 0.0
return self.prices[-1] - self.prices[0]
def direction(self):
momentum = self.momentum()
if momentum > 0:
return "UP"
if momentum < 0:
return "DOWN"
return "FLAT"
Now we can continuously update the detector:
detector.update(best_ask)
print(detector.momentum())
print(detector.direction())
However, raw price difference is not enough.
We also want to know whether the movement is consistent.
Measuring Momentum Strength
A stronger signal occurs when most observations move in the same direction.
For example:
0.42
0.43
0.44
0.45
0.46
has strong directional consistency.
While:
0.42
0.45
0.41
0.46
0.44
does not.
We can calculate the percentage of positive price changes.
def momentum_strength(prices):
if len(prices) < 2:
return 0.0
changes = [
prices[i] - prices[i - 1]
for i in range(1, len(prices))
]
positive = sum(1 for x in changes if x > 0)
return positive / len(changes)
If the result is:
0.90
then 90% of the observed movements were upward.
That is a much stronger momentum signal than 0.55.
Combining Momentum Signals
A production strategy shouldn't depend on one number.
We can combine several signals:
Price momentum
+
Directional consistency
+
Recent acceleration
+
Order-book pressure
+
Minimum liquidity
=
Momentum signal
For example:
def calculate_signal(prices):
if len(prices) < 10:
return 0.0
momentum = prices[-1] - prices[0]
changes = [
prices[i] - prices[i - 1]
for i in range(1, len(prices))
]
positive_ratio = sum(
1 for x in changes if x > 0
) / len(changes)
return momentum * positive_ratio
The exact formula should be optimized through backtesting rather than assumed to be profitable.
4. Entering a Momentum Position
Suppose the YES token is showing strong momentum.
Our bot can start accumulating YES.
For example:
if signal > ENTRY_THRESHOLD:
buy_yes()
But blindly buying the entire desired position is dangerous.
Instead, use incremental execution.
For example:
Signal strength Position size
Weak 0
Medium 10%
Strong 25%
Very strong 50%
Extreme 100%
This prevents one noisy observation from creating a large position.
A simple position-sizing function:
def calculate_position_size(signal, max_position):
if signal <= 0:
return 0
normalized = min(signal / 0.05, 1.0)
return max_position * normalized
The exact thresholds depend on the market and should be determined empirically.
Why We Don't Simply Go All-In
This is one of the most important design decisions.
Momentum can be correct and still fail.
Consider:
YES
0.40
0.43
0.46
0.50
0.54
The momentum signal looks excellent.
But then:
0.54
0.48
0.41
A strategy that entered aggressively at $0.54 can lose a significant amount.
Therefore:
Momentum determines the direction of the trade, but risk management determines how much capital is exposed.
5. The Hedge Layer
This is where the strategy becomes more interesting.
Prediction markets provide complementary outcomes.
For a binary market:
YES + NO ≈ $1
depending on market conditions, fees, spread, and execution.
That relationship allows us to construct a hedge.
Suppose our bot has accumulated YES:
YES position = 100 shares
Average YES price = $0.52
If momentum suddenly reverses, we can increase exposure to the opposite side.
For example:
YES position
│
▼
Momentum reversal
│
▼
Buy NO
│
▼
Reduce directional exposure
This does not magically eliminate risk.
The hedge has its own execution cost and can lock in losses.
The objective is instead to control the downside when the original momentum thesis becomes invalid.
Detecting a Reversal
A reversal detector can monitor several conditions.
For example:
def reversal_detected(prices):
if len(prices) < 5:
return False
recent = prices[-5:]
return (
recent[-1] < recent[-2]
and recent[-2] < recent[-3]
)
This detects three consecutive downward movements.
A stronger implementation can use:
- Momentum crossing zero
- Moving-average crossover
- Price drawdown
- Order-book imbalance
- Spread expansion
- Volume changes
- Consecutive aggressive trades
For example:
if momentum < 0 and drawdown > MAX_DRAWDOWN:
hedge_position()
6. Hedge Ratio
We don't necessarily want to hedge 100% immediately.
Instead, define a hedge ratio.
hedge_ratio = 0.50
If we have:
100 YES shares
we could target:
50 NO shares
when a reversal occurs.
A stronger reversal could increase the hedge:
Weak reversal 25%
Medium reversal 50%
Strong reversal 75%
Extreme reversal 100%
This creates a dynamic hedge.
Dynamic Hedge Example
def calculate_hedge_ratio(reversal_strength):
if reversal_strength < 0.2:
return 0.0
if reversal_strength < 0.4:
return 0.25
if reversal_strength < 0.7:
return 0.50
if reversal_strength < 0.9:
return 0.75
return 1.0
Then:
target_hedge = yes_position * hedge_ratio
The execution engine can buy the difference between the current hedge and target hedge.
7. Turning the Hedge Into Arbitrage
This is the reason I use the term momentum arbitrage.
The bot isn't performing traditional risk-free arbitrage.
Instead, it attempts to exploit two related market behaviors:
Momentum continuation
+
YES/NO relationship
+
Dynamic hedging
=
Risk-managed momentum arbitrage
The bot initially takes directional exposure because the price is moving.
If the momentum continues, the position can become increasingly valuable.
If momentum reverses, the opposite outcome becomes more attractive as a hedge.
The strategy therefore tries to convert short-term directional information into a controlled pair of positions.
8. Example Trade
Imagine a market begins at:
YES = $0.40
NO = $0.60
The bot observes:
0.40
0.41
0.42
0.44
0.46
0.48
Momentum is strong.
The bot begins buying YES.
Suppose the average execution price becomes:
YES average = $0.45
The market continues:
0.48
0.51
0.54
0.57
The momentum trade is working.
Now imagine the market reverses:
0.57
0.54
0.50
0.46
The bot detects the reversal.
Instead of continuing to buy YES, it starts increasing its NO hedge.
The position becomes:
YES = 100
NO = 50
If the reversal continues, the hedge offsets part of the directional loss.
This is much safer than simply holding the original YES position.
9. The Trading State Machine
A production bot should not make decisions from independent if statements.
A state machine is easier to reason about.
┌───────────┐
│ IDLE │
└─────┬─────┘
│
Momentum detected
│
▼
┌───────────┐
│ ENTERING │
└─────┬─────┘
│
Position filled
│
▼
┌───────────┐
│ MOMENTUM │
└─────┬─────┘
│
┌──────────┴──────────┐
│ │
Momentum continues Reversal
│ │
▼ ▼
Add position Hedge
│ │
└──────────┬──────────┘
│
Exit condition
│
▼
┌───────────┐
│ EXIT │
└───────────┘
Python implementation:
from enum import Enum
class BotState(Enum):
IDLE = "IDLE"
ENTERING = "ENTERING"
MOMENTUM = "MOMENTUM"
HEDGING = "HEDGING"
EXITING = "EXITING"
Then:
class MomentumBot:
def __init__(self):
self.state = BotState.IDLE
self.yes_position = 0
self.no_position = 0
def process_signal(self, signal):
if self.state == BotState.IDLE:
if signal > ENTRY_THRESHOLD:
self.state = BotState.ENTERING
elif self.state == BotState.MOMENTUM:
if signal < REVERSAL_THRESHOLD:
self.state = BotState.HEDGING
This becomes much easier to extend as the strategy grows.
10. Order Execution
Signal generation and execution should be separate components.
A clean architecture looks like this:
Market WebSocket
│
▼
Market Data Engine
│
▼
Feature Calculator
│
▼
Momentum Strategy
│
▼
Risk Manager
│
▼
Order Manager
│
▼
Polymarket CLOB
The strategy should answer:
"What should I do?"
The order manager should answer:
"How do I execute it?"
This separation is extremely important.
11. Order Manager
A simple interface could look like:
class OrderManager:
def buy(self, token_id, price, size):
pass
def sell(self, token_id, price, size):
pass
def cancel(self, order_id):
pass
def open_orders(self):
pass
The strategy doesn't need to know the details of authentication, signing, retries, or order IDs.
It only calls:
order_manager.buy(
token_id=yes_token,
price=best_ask,
size=position_size
)
12. Never Assume Orders Filled
One of the biggest mistakes in automated trading systems is treating a submitted order as a filled order.
These are different states:
Order created
↓
Order accepted
↓
Order partially filled
↓
Order fully filled
Your internal position should only change according to actual execution.
For example:
class Position:
def __init__(self):
self.quantity = 0
self.cost = 0
def on_fill(self, quantity, price):
self.cost += quantity * price
self.quantity += quantity
@property
def average_price(self):
if self.quantity == 0:
return 0
return self.cost / self.quantity
This prevents your strategy from believing it owns tokens that were never actually filled.
13. Position Manager
The bot should maintain separate positions for YES and NO.
class Portfolio:
def __init__(self):
self.yes = Position()
self.no = Position()
@property
def total_position(self):
return self.yes.quantity + self.no.quantity
We can then calculate net directional exposure.
For example:
net_exposure = (
self.yes.quantity
- self.no.quantity
)
A positive number means the portfolio is directionally YES-heavy.
A negative number means it is NO-heavy.
14. Risk Management
Momentum strategies need strict risk controls.
At minimum, implement:
Maximum position
MAX_POSITION = 1000
Maximum order size
MAX_ORDER_SIZE = 100
Maximum drawdown
MAX_DRAWDOWN = 0.05
Maximum daily loss
MAX_DAILY_LOSS = 0.10
Maximum hedge exposure
MAX_HEDGE_RATIO = 1.0
The risk manager should be able to override the strategy.
For example:
if daily_loss > MAX_DAILY_LOSS:
trading_enabled = False
A strategy can be profitable while the infrastructure around it is unsafe.
Risk management protects against that.
15. Avoiding False Momentum Signals
Not every price increase is momentum.
For example:
$0.40
$0.42
$0.44
$0.41
$0.40
The initial increase was not persistent.
We can reduce false signals by requiring:
Minimum price movement
+
Minimum persistence
+
Minimum directional consistency
For example:
MIN_MOMENTUM = 0.02
MIN_CONSISTENCY = 0.70
Then:
if (
momentum >= MIN_MOMENTUM
and consistency >= MIN_CONSISTENCY
):
enter_trade()
These values should be treated as parameters for testing, not universal constants.
16. Cooldown After a Reversal
A useful improvement is a cooldown period.
Suppose the bot enters YES, detects a reversal, hedges, and exits.
Immediately entering another YES position could cause the bot to repeatedly trade noise.
Instead:
cooldown_until = current_time + 30
During cooldown:
if current_time < cooldown_until:
return
This reduces overtrading during unstable periods.
17. Avoiding Overtrading
Execution costs matter.
Even if the strategy has a positive theoretical edge, excessive trading can destroy the edge through:
- Spread
- Slippage
- Fees
- Failed orders
- Partial fills
- Latency
Therefore, the expected edge should exceed estimated execution costs.
Conceptually:
Expected edge
>
Spread
+ Slippage
+ Fees
+ Safety margin
If it doesn't, the bot should do nothing.
Doing nothing is a valid trading decision.
18. Event-Driven Architecture
For short-duration prediction markets, polling can introduce unnecessary latency.
An event-driven design is preferable.
async def market_data_loop():
async for update in websocket:
await strategy.on_market_update(update)
The strategy can react immediately to order-book changes.
A simplified structure:
class TradingEngine:
async def on_market_update(self, update):
self.market.update(update)
signal = self.strategy.calculate_signal(
self.market
)
decision = self.risk_manager.validate(
signal,
self.portfolio
)
if decision.allowed:
await self.executor.execute(decision)
This architecture also makes the system easier to test.
19. Complete Strategy Skeleton
Putting the main pieces together:
class MomentumArbitrageBot:
def __init__(
self,
strategy,
risk_manager,
order_manager,
):
self.strategy = strategy
self.risk_manager = risk_manager
self.order_manager = order_manager
self.yes_position = 0
self.no_position = 0
async def on_market_update(self, market):
signal = self.strategy.calculate(market)
decision = self.strategy.decide(
signal=signal,
yes_position=self.yes_position,
no_position=self.no_position,
)
if not decision:
return
if not self.risk_manager.allow(decision):
return
await self.execute(decision)
async def execute(self, decision):
if decision.action == "BUY_YES":
await self.order_manager.buy(
token_id=decision.token_id,
price=decision.price,
size=decision.size,
)
elif decision.action == "BUY_NO":
await self.order_manager.buy(
token_id=decision.token_id,
price=decision.price,
size=decision.size,
)
This is intentionally simplified.
A real implementation needs robust handling for authentication, order signing, retries, fills, cancellation, reconciliation, and exchange/API errors.
20. Backtesting the Strategy
Before putting real capital behind the bot, build a replay engine.
Store historical observations:
timestamp
token
best_bid
best_ask
spread
volume
position
Then replay them chronologically.
for tick in historical_ticks:
strategy.update(tick)
decision = strategy.decide()
if decision:
simulator.execute(decision)
Track:
Total PnL
Win rate
Average trade
Maximum drawdown
Sharpe ratio
Profit factor
Number of trades
Average holding time
Hedge frequency
Slippage
The most important metric is not simply win rate.
A strategy with:
92% win rate
can still lose money if the remaining 8% of trades produce huge losses.
This is particularly important for momentum strategies.
21. Test Reversal Scenarios
Don't only backtest periods where momentum works.
Create explicit stress tests.
Scenario 1 — Strong continuation
0.40 → 0.45 → 0.50 → 0.60
Expected:
Momentum position profitable
Scenario 2 — Immediate reversal
0.40 → 0.45 → 0.50 → 0.42
Expected:
Hedge activates
Scenario 3 — Choppy market
0.40 → 0.43 → 0.41 → 0.44 → 0.42
Expected:
Few or no trades
Scenario 4 — Liquidity disappears
Best ask: 0.45
↓
Large spread
↓
0.60
Expected:
Risk manager blocks aggressive execution
These scenarios are often more valuable than simply looking at historical total PnL.
22. Parameter Optimization
The strategy contains several parameters:
MOMENTUM_WINDOW
ENTRY_THRESHOLD
REVERSAL_THRESHOLD
MIN_CONSISTENCY
MAX_POSITION
HEDGE_RATIO
COOLDOWN
MAX_DRAWDOWN
Don't optimize everything against one historical period.
That can create overfitting.
A better process is:
Historical data
│
▼
Training period
│
▼
Parameter selection
│
▼
Validation period
│
▼
Out-of-sample test
│
▼
Paper trading
│
▼
Small live deployment
The goal isn't to find the perfect parameter set.
The goal is to find parameters that remain reasonably stable across different market conditions.
23. Observability
A production trading bot should explain why it traded.
Every decision should be logged.
For example:
[12:01:04]
YES best ask: 0.472
Momentum: +0.031
Consistency: 0.86
Signal: 0.0267
Action: BUY_YES
Size: 25
Reason: Strong upward momentum
And when a hedge activates:
[12:01:11]
YES momentum: -0.018
Drawdown: 4.1%
Reversal strength: 0.73
Action: BUY_NO
Hedge ratio: 0.75
Reason: Momentum reversal
These logs make debugging dramatically easier.
24. Reconciliation
Never assume your internal state is correct forever.
A production system should periodically reconcile:
Internal position
vs
Actual exchange position
If the bot believes:
YES = 500
but the exchange says:
YES = 425
the system must detect and resolve the difference.
Possible causes include:
- Partial fills
- Cancelled orders
- Network failures
- Duplicate execution events
- Process restarts
- WebSocket disconnects
Position reconciliation is essential for automated trading.
25. Handling WebSocket Disconnects
Short-duration trading strategies are particularly sensitive to stale data.
If the WebSocket disconnects:
Market data stops
↓
Price becomes stale
↓
Momentum calculation becomes invalid
↓
Bot may trade on old information
Therefore:
if market_data_age > MAX_DATA_AGE:
trading_enabled = False
When the connection is restored:
Reconnect
↓
Resubscribe
↓
Refresh order book
↓
Reconcile positions
↓
Validate market state
↓
Resume trading
Never automatically resume trading using stale state.
26. The Most Important Part: The Bot Should Know When Not To Trade
A good momentum bot is not constantly buying.
It should be selective.
The ideal flow is:
No momentum
↓
Do nothing
Strong momentum
↓
Enter gradually
Momentum continues
↓
Manage position
Momentum weakens
↓
Reduce exposure
Momentum reverses
↓
Hedge
Risk becomes excessive
↓
Exit
Market becomes unstable
↓
Stop trading
This is much more robust than:
if price_up:
buy()
27. Complete High-Level Architecture
The final system can look like this:
┌─────────────────────┐
│ Polymarket CLOB │
└──────────┬──────────┘
│
WebSocket
│
▼
┌─────────────────────┐
│ Market Data │
│ Engine │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Feature Calculator │
│ │
│ Momentum │
│ Consistency │
│ Volatility │
│ Order Book Pressure │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Momentum Strategy │
└──────────┬──────────┘
│
┌─────────┴─────────┐
│ │
▼ ▼
Momentum Reversal
Layer Layer
│ │
└─────────┬─────────┘
▼
┌─────────────────────┐
│ Risk Manager │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Order Manager │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Polymarket Execution│
└─────────────────────┘
Conclusion
The interesting part of building a Polymarket trading bot isn't simply connecting Python to an exchange and sending orders.
The real challenge is identifying temporary market behavior that can be converted into a systematic edge.
The Momentum Arbitrage Bot focuses on one particular behavior:
When the best ask begins moving persistently in one direction, that movement can sometimes continue long enough to trade.
The bot attempts to capture that movement by:
- Monitoring the order book
- Maintaining a rolling best-ask history
- Measuring momentum
- Measuring directional consistency
- Entering positions incrementally
- Monitoring for continuation
- Detecting reversals
- Building an opposite-side hedge
- Controlling position size
- Exiting when the market invalidates the signal
The hedge layer is especially important because momentum is not guaranteed to continue.
A strong strategy isn't one that predicts every move correctly.
It's one that can capture favorable moves while controlling what happens when the prediction is wrong.
The next step is turning this architecture into a complete Python implementation with a real-time Polymarket CLOB client, WebSocket market-data processing, momentum calculation, position management, hedge execution, and backtesting.
And as with any automated trading strategy, historical performance is not a guarantee of future results. The thresholds, hedge ratios, and execution rules should be validated against realistic historical and live-market conditions before risking significant capital.
🤝 Collaboration & Contact
If you’re interested in building trading bots, buy trading bots, collaborating, exploring strategy improvements, or discussing about this system, feel free to reach out.
I’m especially open to connecting with:
Quant traders
Engineers building trading infrastructure
Researchers in prediction markets
Investors interested in market inefficiencies
📌 GitHub Repository
This repo has some Polymarket several bots in this system.
You can explore the full implementation, strategy logic, and ongoing updates about 5 min crypto market here:
Benjam1nCup
/
Polymarket-trading-bot-python-V2
polymarket trading bot polymarket arbitrage bot polymarket bot polymarket trading bot polymarket arbitrage bot polymarket bot polymarket trading bot polymarket arbitrage bot polymarket bot polymarket trading bot polymarket arbitrage bot polymarket bot polymarket trading bot polymarket arbitrage bot polymarket bot polymarket trading bot
Polymarket Trading Bot | Polymarket Arbitrage Bot | Polymarket TWAP Trading Bot
An open-source and Strong Strategy collection of Polymarket trading bot and Polymarket arbitrage bot and Polymarket TWAP trading bot in Python for high-performance automated trading on polymarket crypto 5min and 15min markets.
Features
-
Explosive growth of Polymarket with surging trading volume and new short-term markets
-
Increasing dominance of automated bots and AI in 5-minute and 15-minute crypto prediction markets
-
Higher profitability potential through advanced arbitrage and market-making strategies
-
Stronger edge for Python-based bots with real-time orderbook intelligence and low-latency execution
-
Continuous evolution of sniper, ladder, stair, momentum, and copy trading strategies
-
Scalable daily profits as prediction markets move toward hundreds of billions in annual volume
-
Full future-proof architecture for new features, contracts, and high-frequency trading environments
Included Trading Bots
Designed for arbitrage, directional strategies, and ultra-short-term markets (including 5-minute and 15-minute rounds), this bot framework provides a robust…
💬 Get in Touch
If you have ideas, questions, or would like to collaborate or want these trading bots, don’t hesitate to reach out directly.
Feedback on your repo (based on your description & strategy)
Contact Info
Telegram
https://t.me/BenjaminCup
tags: polymarket,trading,bot,architecture,tutorial,TWAP


Top comments (0)