Day 6 of 7 — Building a Polymarket Market Maker Bot From Zero to Complete

In Day 4, we learned how to calculate bid and ask prices. In Day 5, we explored order management, partial fills, cancellations, and uncertain API responses.

Polymarket Trading Bot

Today, we're going to tackle another critical challenge in automated market making:

How do you protect your trading bot when it accumulates tokens and the market moves against its position?

Imagine your Polymarket bot purchases 100 YES tokens at $0.48 each.

Your strategy expects to earn money by selling those tokens at a higher price.

But before your sell order executes, the market changes:

  • Your purchase price: $0.48
  • New best bid: $0.42
  • New best ask: $0.44

Your tokens are now worth less than what you paid.

Your pricing algorithm might be working perfectly. Your API integration might be reliable. Your order manager might correctly track every transaction.

Yet your strategy is still losing money.

This is why professional market-making systems need more than a pricing algorithm. They need an inventory management and risk-control system.

In this tutorial, we'll explore:

  • Inventory risk and why it matters.
  • Adverse selection and how it affects market makers.
  • How to measure trading positions and financial exposure.
  • Inventory-aware pricing strategies.
  • Implementing risk-management logic in Python.
  • Maximum inventory, exposure, and loss limits.
  • Detecting unfavorable fills.
  • Testing risk-management systems before live trading.

By the end, you'll understand how to make your Polymarket bot aware of the risks created by its own orders.

1. Understanding Inventory Risk

Before discussing risk management, let's understand inventory.

Inventory is the collection of tokens your trading bot currently holds.

In a Polymarket binary market, this usually means YES and NO tokens.

Inventory risk occurs when the value or potential payout of those positions changes in a way that negatively affects your strategy.

A practical example

Suppose your bot purchases:

  • 100 YES tokens
  • Purchase price: $0.48 per token

Your initial investment is:

100 * 0.48 = $48 

Later, the YES token price falls to $0.43.

Its indicative market value becomes:

100 * 0.43 = $43 

Your unrealized loss is:

43 - 48 = -$5 

You've lost approximately $5 in estimated position value, even though you haven't sold anything.

This is called an unrealized loss.

Note that this simplified calculation doesn't include fees, execution costs, or the possibility that the entire position cannot be sold at the displayed price.

Why does inventory risk happen?

Market makers generally place buy and sell orders around a reference price.

They want to:

  1. Buy tokens at a lower price.
  2. Sell tokens at a higher price.
  3. Earn the difference, known as the spread.

However, buy and sell orders don't always execute equally.

Consider this situation:

Bot places BUY orders | v Buy orders execute repeatedly | v Bot accumulates YES tokens | v Sell orders remain unfilled | v Market price starts falling | v Inventory loses value 

The bot might have executed many successful purchases, but it now holds more inventory than intended.

The important lesson: A market maker must continuously monitor its inventory, not just the number of orders it has submitted.

2. What Is Adverse Selection?

Inventory risk describes the position your bot holds.

Adverse selection describes a different problem:

Your orders may be filled because another trader has information or market signals that make your quoted price unfavorable.

Let's look at an example.

The YES token is trading around $0.50.

Your bot places a sell order at $0.51.

Another trader immediately purchases your tokens.

A moment later, new information enters the market, and the YES token price rises to $0.56.

Your order was successfully executed, but you sold at a price that quickly became unfavorable.

The opposite can happen when your buy order executes just before the market price falls.

This is adverse selection.

Why can other traders have an advantage?

Other traders may react faster to:

  • Changes in the underlying cryptocurrency price.
  • Sudden increases in market volatility.
  • New information affecting the probability of an outcome.
  • Changes in liquidity and order-book depth.
  • Markets approaching their resolution time.

For example, imagine your bot is trading a Polymarket BTC Up/Down market.

BTC suddenly moves sharply upward.

The probability of the YES outcome changes, but your bot hasn't updated its quotes yet.

Your existing sell order may now be too cheap.

Another trader can purchase your tokens at your outdated price before your bot has time to react.

Why is this dangerous?

A wide spread doesn't automatically protect your market maker.

If your prices are outdated, other traders may consistently execute against your orders when those prices are unfavorable.

Your apparent spread profit can be overwhelmed by:

  • Adverse price movements.
  • Trading fees.
  • Execution costs.
  • Unfavorable order fills.

This is why market makers need to monitor both inventory and market conditions.

3. Measuring Inventory and Financial Exposure

You cannot manage risk if you don't know what your bot currently owns.

At a minimum, your system should track:

Data Purpose
YES quantity Current YES token position
NO quantity Current NO token position
Average cost Average acquisition price
Current reference price Estimated current token value
Open buy orders Potential additional inventory
Open sell orders Potential inventory reduction
Available funds Remaining buying capacity
Potential exposure Risk if outstanding orders execute

Let's use an example.

Your bot currently holds:

Position Quantity Average Cost
YES 100 $0.48
NO 40 $0.46

The total acquisition cost is:

yes_cost = 100 * 0.48 no_cost = 40 * 0.46 total_cost = yes_cost + no_cost print(total_cost) # 66.40 

Your bot spent $66.40 acquiring these positions.

However, acquisition cost alone doesn't tell you the current financial risk.

You also need to understand the current market prices and what the tokens represent.

For standard binary markets, a winning token generally pays $1 at resolution, while a losing token pays $0, subject to the specific market's resolution rules.

Before resolution, token prices change as market expectations and trading conditions change.

It's important to distinguish five different measurements:

1. Cost Basis

The amount originally paid to acquire the position.

2. Mark-to-Market Value

The estimated current value of the position using a selected market price.

3. Realized P&L

Profit or loss from completed transactions.

4. Unrealized P&L

Estimated profit or loss on positions that are still open.

5. Resolution Value

The final payout determined by the market's resolution rules.

These measurements answer different questions. A reliable trading system should not treat them as interchangeable.

4. Tracking YES and NO Inventory Separately

One simple way to measure inventory imbalance is to calculate the difference between YES and NO token quantities.

Formula:

inventory = yes_quantity - no_quantity 

Mathematically:

[
I = Q_{YES} - Q_{NO}
]

Where:

  • (Q_{YES}): Number of YES tokens held.
  • (Q_{NO}): Number of NO tokens held.
  • (I): Net token-count difference.

Example:

yes_quantity = 100 no_quantity = 40 inventory = yes_quantity - no_quantity print(inventory) # 60 

Your bot holds 60 more YES tokens than NO tokens.

This metric is useful for identifying an imbalance.

However, net token quantity is not the same as financial exposure.

Why?

Because YES and NO tokens can have different market prices.

Their payout characteristics also depend on the specific market.

Furthermore, outstanding orders can change your position even before they execute.

For better risk management, track:

  1. Net token inventory.
  2. Monetary exposure.
  3. Outstanding order exposure.
  4. Individual YES and NO position limits.
  5. Total portfolio exposure.

A balanced YES/NO token count does not automatically eliminate every type of financial risk.

5. Implementing Inventory-Aware Pricing

Now let's connect inventory management with the pricing algorithm we built in Day 4.

A basic market maker calculates its prices using:

[
Bid = Midpoint - HalfSpread
]

[
Ask = Midpoint + HalfSpread
]

Suppose:

Midpoint: $0.50 Half-spread: $0.02 

The bot generates:

Bid: $0.48 Ask: $0.52 

This is a reasonable starting point.

But what happens if your bot already holds 500 YES tokens?

Should it continue buying at the same price?

Probably not.

It should become less aggressive about accumulating additional inventory.

This is where inventory-aware pricing becomes useful.

The inventory-skew formula

A simplified inventory-adjusted reference price is:

[
P_{adjusted} = P_{mid} - kI
]

Where:

  • (P_{mid}): Original reference midpoint.
  • (I): Inventory deviation from the target.
  • (k): Inventory-skew coefficient.

When inventory exceeds its target, the adjusted reference price decreases.

This makes buying less aggressive and selling more attractive.

When inventory is below its target, the adjusted reference price can increase, encouraging the bot to rebuild its position.

Practical example

Assume:

Midpoint: $0.50 Half-spread: $0.02 Inventory deviation: 100 tokens Inventory-skew coefficient: 0.0001 

Calculate the adjusted midpoint:

[
P_{adjusted} = 0.50 - (100 \times 0.0001)
]

[
P_{adjusted} = 0.49
]

Now calculate the new quotes:

[
Bid = 0.49 - 0.02 = 0.47
]

[
Ask = 0.49 + 0.02 = 0.51
]

Compare the results:

Quote Original Inventory Adjusted
Bid $0.48 $0.47
Ask $0.52 $0.51

The bot now offers a lower buying price and a lower selling price.

This discourages additional buying while making it easier to sell existing inventory.

The coefficient is illustrative. It must be calibrated to the market, trading costs, inventory limits, and strategy.

Also, inventory should be measured against a target position, not necessarily zero. Some strategies intentionally maintain a nonzero position.

6. Writing Inventory-Aware Pricing Logic in Python

Let's implement this concept.

We'll use Python's Decimal instead of ordinary floating-point numbers.

Financial calculations can suffer from floating-point rounding errors, so Decimal is useful when working with prices and quantities.

from decimal import Decimal def calculate_inventory_aware_quotes( midpoint: Decimal, half_spread: Decimal, inventory: Decimal, inventory_target: Decimal = Decimal("0"), skew_per_token: Decimal = Decimal("0.0001"), ): if not Decimal("0") < midpoint < Decimal("1"): raise ValueError("Midpoint must be between 0 and 1") if half_spread <= 0: raise ValueError("Half-spread must be positive") if skew_per_token < 0: raise ValueError("Skew must not be negative") inventory_deviation = inventory - inventory_target adjusted_midpoint = ( midpoint - inventory_deviation * skew_per_token ) bid = adjusted_midpoint - half_spread ask = adjusted_midpoint + half_spread # Illustrative binary-token price bounds.  bid = max(Decimal("0.01"), min(bid, Decimal("0.99"))) ask = max(Decimal("0.01"), min(ask, Decimal("0.99"))) if bid >= ask: raise ValueError("Invalid quotes: bid must be below ask") return { "bid": bid, "ask": ask, "inventory_deviation": inventory_deviation, "adjusted_midpoint": adjusted_midpoint, } quotes = calculate_inventory_aware_quotes( midpoint=Decimal("0.50"), half_spread=Decimal("0.02"), inventory=Decimal("100"), inventory_target=Decimal("0"), skew_per_token=Decimal("0.0001"), ) print(quotes) 

Understanding the results

Try changing the inventory value:

Inventory Expected behavior
0 No inventory adjustment
100 Lower adjusted midpoint
-100 Higher adjusted midpoint
Very large positive or negative value Candidate prices may become invalid

One important detail: the example clamps prices to an illustrative range, but this doesn't guarantee valid quotes.

A production implementation must also validate:

  • Actual market tick size.
  • Minimum and maximum allowed prices.
  • Bid/ask relationship.
  • Market-specific order rules.
  • Inventory and exposure limits.

The function only calculates candidate quotes. It does not submit orders or guarantee profitability.

7. Why Inventory Skew Is Not Enough

Inventory-aware pricing is useful, but it cannot solve every risk problem.

Imagine your bot holds 500 YES tokens.

Suddenly, the market becomes extremely volatile.

Your inventory-skew formula adjusts the bid and ask prices.

However, the market may still be moving too quickly for those prices to be safe.

This is why professional trading systems need additional risk controls.

A. Maximum Inventory Limit

Define the maximum number of tokens your bot is allowed to hold.

For example:

MAX_YES_INVENTORY = 200 

If your bot reaches 200 YES tokens, it should stop increasing its YES position.

Possible actions include:

  • Canceling or reducing open buy orders.
  • Blocking new exposure-increasing orders.
  • Allowing permitted inventory-reducing actions.

B. Maximum Outstanding Exposure

An order doesn't need to execute to create potential risk.

Suppose:

Current YES inventory: 150 Open YES buy orders: 100 Maximum inventory: 200 

Your potential inventory is:

potential_inventory = 150 + 100 print(potential_inventory) # 250 

Even though the bot currently owns only 150 tokens, it could own 250 if all open buy orders execute.

That exceeds the configured limit.

A simplified calculation is:

[
Q_{potential} = Q_{held} + Q_{open\ buys}
]

A production risk manager must also account for:

  • Partial fills.
  • Pending cancellations.
  • Multiple outstanding orders.
  • Orders that reduce exposure.
  • Orders whose execution state is uncertain.

C. Maximum Loss Limit

Define the maximum acceptable loss for:

  • An individual market.
  • The entire strategy.
  • A trading session.

For example:

MAX_SESSION_LOSS = Decimal("50.00") 

When the configured loss threshold is reached, the bot can stop submitting new orders and enter a controlled risk-reduction mode.

The calculation should distinguish realized and unrealized P&L and include relevant fees and costs.

Define the policy before trading rather than changing it whenever the market becomes uncomfortable.

D. Stale Market Data Detection

Imagine your bot stops receiving market updates.

Its last known midpoint is $0.50, but the actual market has already moved to $0.42.

If the bot continues quoting around $0.50, it may create extremely unfavorable orders.

A stale-data rule should prevent new pricing decisions from using outdated information.

Depending on the system design, the bot may also need to cancel resting orders.

E. Market and Resolution Risk

A market approaching resolution can behave differently from one with plenty of time remaining.

For example, in a short-interval BTC Up/Down market, a sharp BTC movement can rapidly change the probability of the outcome.

Your bot should consider:

  • Time remaining until resolution.
  • Current market conditions.
  • Volatility.
  • Reliability of reference data.
  • Potential price changes before an order executes.

The same quoting parameters are not necessarily appropriate throughout every stage of a market.

8. Detecting Adverse Selection After Order Fills

How can you determine whether your bot is repeatedly getting unfavorable fills?

One useful approach is to compare the reference price at the time of a fill with the reference price after the fill.

Let's consider a BUY transaction.

Purchase price: $0.48 Reference price at fill: $0.49 Reference price 5s later: $0.45 

The reference price decreased by $0.04.

This is a warning sign that the buy order may have been adversely selected.

Calculate post-fill price movement

For a buy:

[
\Delta P = P_{after} - P_{at\ fill}
]

Using our example:

[
\Delta P = 0.45 - 0.49
]

[
\Delta P = -0.04
]

A negative value means the reference price decreased after the purchase.

For a sell, an increase in the reference price after the fill can be unfavorable because the bot sold before the market moved higher.

What should your bot record?

For every executed order, store:

Field Description
Market ID Which market was traded
Token ID Which token was traded
Side BUY or SELL
Fill price Actual execution price
Fill quantity Number of executed tokens
Reference price Price at the time of execution
Post-fill price Price after a selected interval
Inventory before Position before execution
Inventory after Position after execution
Fees Associated trading costs

You can then measure post-fill price movement over different periods:

  • 1 second.
  • 5 seconds.
  • 30 seconds.

The appropriate time intervals depend on your market and strategy.

This analysis helps answer important questions:

  • Are buy orders frequently filled before prices fall?
  • Are sell orders frequently filled before prices rise?
  • Does adverse selection become worse during volatile periods?
  • Are certain order sizes associated with worse outcomes?

Remember that reference-price movement is not the same as realized profit or loss.

You need consistent reference prices, observation windows, and enough historical observations before changing your strategy.

9. Building a Risk-Control Decision Process

A reliable market maker needs explicit rules for when it should continue quoting, reduce exposure, or stop placing orders.

Here is a simplified decision process:

 Receive Market Update | v Is Market Data Fresh? | +-----+-----+ | | No Yes | | v v Pause or Check Inventory, Cancel as Exposure and Needed Loss Limits | v Are Limits Safe? | +-----+-----+ | | No Yes | | v v Reduce Risk Calculate or Pause Adjusted Quotes | v Validate Orders | v Manage Orders 

The appropriate response depends on the specific risk condition.

For example:

Risk condition Possible response
Inventory too high Reduce buying pressure
Outstanding exposure too high Block additional exposure
Market data stale Pause new quoting
Uncertain order state Reconcile orders before submitting more
Loss threshold reached Activate predefined risk policy

One important point: canceling every order is not always the safest response.

A cancellation can race with an execution. A risk-reducing order might also be canceled unnecessarily.

Your system should define how to respond to each risk condition instead of relying on a single emergency action.

10. Testing Risk Management Before Live Trading

Never wait for a real trading loss to discover that your inventory calculations are incorrect.

Your risk-management system should be tested against specific scenarios.

Here is a practical test checklist:

Test Scenario Expected Behavior
Inventory is zero Quotes use the original reference price
Inventory exceeds target Quotes discourage additional accumulation
Inventory falls below target Quotes shift in the opposite direction
Maximum inventory reached New exposure-increasing orders are blocked
Open buy orders could exceed the limit Additional buying is restricted
Market data becomes stale New quoting pauses
Partial fill arrives Inventory and remaining quantity are updated
Duplicate trade event arrives Inventory is not counted twice
Cancellation is pending Potential exposure remains accounted for
Maximum loss reached Configured risk response activates
Candidate prices are invalid Orders are rejected before submission

You should also test situations where several problems happen simultaneously.

For example:

Current inventory is near its maximum + Market price is changing rapidly + Cancellation request times out | v Risk Manager Activated | v Reconcile Actual Order State | v Recalculate Potential Exposure | v Apply Configured Risk Policy 

This is especially important because trading systems often experience multiple events at nearly the same time.

A reliable risk manager must avoid making an uncertain situation worse by blindly submitting additional orders.

The goal isn't to build a bot that never loses money. That's impossible to guarantee.

The goal is to build a bot that understands its exposure, respects its limits, and responds predictably when something goes wrong.

Conclusion: Risk Management Is Part of the Trading Strategy

Inventory risk and adverse selection are two of the most important challenges in automated market making.

Your bot can calculate reasonable bid and ask prices and correctly manage every API request, but it can still lose money if it accumulates an unwanted position or repeatedly gets filled at unfavorable prices.

Let's summarize the most important lessons from Day 6:

  1. Track inventory continuously. Know what your bot owns and what outstanding orders could add to its position.
  2. Use inventory-aware pricing. Adjust quotes when the current position moves away from its target.
  3. Measure adverse selection. Analyze how reference prices move after order execution.
  4. Implement explicit risk limits. Monitor inventory, outstanding exposure, market-data freshness, and losses.
  5. Stop quoting when critical information is unreliable. Never let your bot trade blindly using outdated data.
  6. Test failure scenarios. Make sure your risk controls work when fills, cancellations, and market movements happen together.

Remember: inventory skew and risk limits help control exposure, but they do not guarantee profitability. Market making remains exposed to volatility, fees, execution costs, and unfavorable fills.

What's Next? Day 7 — Testing, Monitoring and Deployment

We've now covered market data, pricing, order management, inventory risk, and risk controls.

In the final article, we'll bring the project together.

We'll explore:

  • Testing the complete trading bot.
  • Measuring market-making performance.
  • Monitoring live trading operations.
  • Handling failures and unexpected behavior.
  • Preparing a safer deployment workflow.
  • Evaluating whether the strategy is actually profitable.

The final goal is to move from a collection of trading components to a complete, testable market-making system.

Continue the Project

Follow the series to learn how to build a Polymarket trading bot step by step, from market data and quote calculation to order management and risk control.