Day 12 of Building a Polymarket Market Maker Bot with Python
In Day 11, we explored how to build a responsive Polymarket market maker using WebSockets, asynchronous Python, and real-time order book updates.
We learned how to receive market data, maintain a local order book, detect stale information, and react to market changes.
But there is another challenge that becomes increasingly important as a market maker starts trading:
How do you manage inventory when your bot keeps accumulating positions?
Imagine your bot has been running for several hours.
It has purchased:
- 150 YES tokens
- 40 NO tokens
More orders continue to get filled, and the portfolio becomes increasingly unbalanced.
Your bot might still be calculating bid and ask prices correctly, but its financial exposure is growing.
This is where inventory management and portfolio risk control become essential.
A professional market-making system should not only decide when to place orders. It should also understand how each order affects the entire portfolio.
In this article, we will build the foundation of an inventory-aware risk management system.
We will cover:
- Understanding inventory risk in binary markets.
- Tracking YES and NO positions.
- Calculating cost basis and mark-to-market value.
- Building inventory-aware quoting logic.
- Managing order sizes and available capital.
- Understanding different hedging strategies.
- Managing exposure across multiple markets.
- Implementing a Python portfolio risk manager.
- Testing different portfolio risk scenarios.
Let's start with the problem.
1. Why Inventory Management Matters
A market maker attempts to earn the difference between buying and selling prices.
For example:
Buy YES: $0.47
Sell YES: $0.50
Gross spread: $0.03 per token
However, a market maker cannot always buy and sell immediately.
Sometimes buyers are available, but sellers are not.
Sometimes sellers are available, but buyers are not.
As a result, the bot accumulates inventory.
Think about a traditional electronics shop.
You purchase 100 phones because you expect customers to buy them. But demand suddenly decreases.
Now you have 100 phones sitting in your warehouse.
Your capital is tied up, and the market value of your inventory could decrease.
A market maker faces a similar problem, except its inventory consists of prediction-market positions.
For example:
yes_inventory = 150
no_inventory = 40
The bot has accumulated significantly more YES tokens than NO tokens.
If it continues submitting YES buy orders without considering existing inventory, it may increase its exposure unnecessarily.
The important principle is:
A market maker must manage both its orders and the inventory those orders create.
2. Understanding YES and NO Positions
Polymarket binary markets generally have two possible outcomes:
- YES: pays $1 per winning share if YES wins.
- NO: pays $1 per winning share if NO wins.
The losing outcome generally pays $0 per share.
Always verify the specific market's resolution rules.
Let's assume our bot currently holds:
| Position | Quantity | Average cost |
|---|---|---|
| YES | 120 | $0.46 |
| NO | 40 | $0.48 |
The bot owns more YES tokens than NO tokens.
However, simply calculating the difference:
120 - 40 = 80
does not describe the complete financial exposure.
We also need to understand the acquisition cost and potential resolution payouts.
Calculate acquisition cost
YES position:
120 × $0.46 = $55.20
NO position:
40 × $0.48 = $19.20
Total acquisition cost:
$55.20 + $19.20 = $74.40
Calculate possible resolution payouts
| Scenario | YES payout | NO payout | Total payout |
|---|---|---|---|
| YES wins | $120 | $0 | $120 |
| NO wins | $0 | $40 | $40 |
The original acquisition cost was $74.40.
Therefore:
| Scenario | Payout | Result before costs |
|---|---|---|
| YES wins | $120 | +$45.60 |
| NO wins | $40 | -$34.40 |
This is why a portfolio risk manager should evaluate both possible outcomes.
It should not simply look at the number of tokens currently held.
It should understand what happens to the entire portfolio under different resolution scenarios.
3. Four Important Inventory Metrics
Before implementing risk management, let's identify the information our bot needs to track.
3.1 Token quantity
The simplest metric is the quantity of each position.
yes_quantity = 120
no_quantity = 40
This tells us how many tokens the bot owns.
However, quantity alone does not tell us how much capital has been committed.
3.2 Cost basis
Cost basis represents the acquisition cost of the current inventory.
For a simplified example:
yes_cost = 120 * 0.46
no_cost = 40 * 0.48
total_cost = yes_cost + no_cost
print(total_cost)
Output:
74.4
In a real trading system, cost basis should be calculated from actual fills.
You should account for:
- Partial fills.
- Multiple purchase prices.
- Selling inventory.
- Trading fees.
- Position adjustments.
Do not simply multiply current inventory by the latest market price and call it cost basis.
3.3 Mark-to-market value
Mark-to-market value estimates the current value of the inventory.
Suppose the current prices are:
yes_quantity = 120
yes_price = 0.52
no_quantity = 40
no_price = 0.45
Calculate the estimated value:
yes_value = yes_quantity * yes_price
no_value = no_quantity * no_price
portfolio_value = yes_value + no_value
print(portfolio_value)
Output:
80.4
The estimated current value is $80.40.
Compared with the original $74.40 acquisition cost, the difference is:
$80.40 - $74.40 = $6.00
This is an unrealized valuation difference before fees and other adjustments.
One important limitation: displayed market prices do not guarantee that the entire position can be sold at those prices.
A large sell order can consume available liquidity and receive progressively worse prices.
3.4 Resolution scenario value
The fourth metric evaluates what happens when the market resolves.
For a standard binary market:
yes_wins_payout = yes_quantity * 1
no_wins_payout = no_quantity * 1
Using our example:
YES wins: $120
NO wins: $40
Comparing these payouts against acquisition cost helps us understand the portfolio's potential resolution outcomes.
For multiple markets, the risk manager should calculate these values separately for each market and then aggregate the relevant scenarios.
4. Building Inventory-Aware Quoting Logic
Now let's connect inventory management with the market-making strategy.
A basic market maker calculates prices using the midpoint.
For example:
Best bid: $0.48
Best ask: $0.52
Midpoint: $0.50
With a half-spread of $0.02:
Bid: $0.48
Ask: $0.52
But what happens when the bot already owns too many YES tokens?
Continuing to quote the same prices may attract additional YES purchases and increase exposure.
Instead, the bot should adjust its prices according to existing inventory.
This technique is called inventory skew.
The principle is simple:
- Too much YES inventory: reduce the attractiveness of buying more YES.
- Too little YES inventory: make buying YES more attractive.
- Inventory near the target: maintain normal quoting behavior.
A simplified formula is:
Adjusted Midpoint =
Market Midpoint
- (Current Inventory - Target Inventory) × Skew Factor
Mathematically:
[
P_{adjusted}=P_{mid}-(I-I_{target})k
]
Where:
- (P_{mid}): Current market midpoint.
- (I): Current inventory.
- (I_{target}): Desired inventory.
- (k): Inventory adjustment factor.
Practical example
Market midpoint: $0.50
Current inventory: 120
Target inventory: 50
Skew factor: 0.0001
First, calculate inventory deviation:
120 - 50 = 70
Calculate the adjustment:
70 × 0.0001 = 0.007
Adjusted midpoint:
0.50 - 0.007 = 0.493
With a half-spread of $0.02:
Bid: $0.473
Ask: $0.513
The bot now has a lower bid and a higher ask.
This makes it less aggressive about purchasing additional YES inventory.
It also makes its sell quote more attractive relative to the original quote.
The skew factor must be calibrated to the market, position scale, volatility, and strategy. This is an educational formula, not a complete pricing model.
5. Implementing Inventory-Aware Pricing in Python
Let's implement the previous formula.
We will use Python's Decimal type for financial calculations instead of ordinary floating-point values.
from decimal import Decimal
def calculate_inventory_quotes(
midpoint: Decimal,
current_inventory: Decimal,
target_inventory: Decimal,
half_spread: Decimal,
skew_factor: Decimal,
):
inventory_deviation = (
current_inventory - target_inventory
)
adjustment = inventory_deviation * skew_factor
adjusted_midpoint = midpoint - adjustment
bid = adjusted_midpoint - half_spread
ask = adjusted_midpoint + half_spread
return {
"bid": bid,
"ask": ask,
"adjusted_midpoint": adjusted_midpoint,
"inventory_deviation": inventory_deviation,
}
result = calculate_inventory_quotes(
midpoint=Decimal("0.50"),
current_inventory=Decimal("120"),
target_inventory=Decimal("50"),
half_spread=Decimal("0.02"),
skew_factor=Decimal("0.0001"),
)
print(result)
Expected output:
{
'bid': Decimal('0.4730'),
'ask': Decimal('0.5130'),
'adjusted_midpoint': Decimal('0.4930'),
'inventory_deviation': Decimal('70')
}
This is a basic inventory-aware pricing function.
A production implementation should additionally:
- Validate the market's minimum tick size.
- Ensure prices remain within valid market bounds.
- Check available balances.
- Account for existing open orders.
- Enforce maximum position limits.
- Check market status.
- Reject stale market data.
- Recalculate quotes after relevant fills.
Most importantly, inventory skew should never replace hard risk limits.
6. Managing Order Size and Available Capital
Another important question is:
How much should the bot buy or sell?
A market maker should not necessarily use the same order size in every situation.
Imagine:
Target YES inventory: 100
Current YES inventory: 20
The bot has room to increase its YES position.
But consider:
Target YES inventory: 100
Current YES inventory: 95
The bot should be more careful about submitting another large buy order.
We can implement a simple capacity calculation.
from decimal import Decimal
def calculate_buy_capacity(
current_inventory: Decimal,
target_inventory: Decimal,
max_order_size: Decimal,
):
remaining_capacity = max(
Decimal("0"),
target_inventory - current_inventory
)
return min(
remaining_capacity,
max_order_size
)
capacity = calculate_buy_capacity(
current_inventory=Decimal("20"),
target_inventory=Decimal("100"),
max_order_size=Decimal("30"),
)
print(capacity)
Output:
30
Now consider inventory of 95 tokens:
capacity = calculate_buy_capacity(
current_inventory=Decimal("95"),
target_inventory=Decimal("100"),
max_order_size=Decimal("30"),
)
print(capacity)
Output:
5
The bot can consider buying only five additional tokens under this simplified rule.
Why open orders matter
There is another important detail.
Imagine the bot currently owns 80 YES tokens and already has three unfilled buy orders:
Order 1: 10 YES
Order 2: 10 YES
Order 3: 10 YES
If all three orders fill:
80 + 10 + 10 + 10 = 110 YES
The bot must account for potential future fills.
A simplified projected inventory calculation is:
projected_inventory = (
current_inventory
+ open_buy_quantity
+ new_order_quantity
)
However, a production system must also account for:
- Partially filled orders.
- Sell orders.
- Reserved balances.
- Cancellation requests.
- Orders with uncertain exchange status.
- Concurrent fills.
A cancellation request does not necessarily mean the exchange has stopped executing that order.
Always reconcile the actual order state before assuming the remaining quantity is unavailable for execution.
7. Understanding Hedging Strategies
Inventory management helps control risk before positions become too large.
Hedging provides another way to reduce unwanted exposure.
However, buying the opposite outcome does not automatically make a position profitable or risk-free.
Let's examine four approaches.
7.1 Reduce exposure through quoting
The simplest approach is to modify the bot's quotes.
If it owns too much YES inventory:
- Make YES buy orders less aggressive.
- Make YES sell orders more competitive.
- Cancel unnecessary risk-increasing YES buy orders.
- Allow natural trading activity to reduce inventory.
This approach can reduce exposure without immediately executing another trade.
7.2 Sell excess inventory
Suppose:
Current YES inventory: 150
Target YES inventory: 80
The bot may decide to sell some YES tokens.
Before selling, it should evaluate:
- Current bid price.
- Available order book depth.
- Expected fees.
- Slippage.
- Potential realized loss.
- Whether gradual execution is preferable.
Selling an entire position immediately can create unnecessary market impact.
7.3 Buy the opposite outcome
Suppose the bot owns:
100 YES
It considers purchasing:
100 NO
In a standard binary market, equal YES and NO quantities create a matched resolution payout.
For example:
100 YES + 100 NO
If YES wins:
YES payout = $100
NO payout = $0
If NO wins:
YES payout = $0
NO payout = $100
Now calculate the combined acquisition cost:
100 YES × $0.46 = $46
100 NO × $0.48 = $48
Total cost = $94
The standard combined resolution payout is $100.
The difference is $6 before applicable fees and other costs, assuming the market follows the standard binary payout structure.
This illustrates why opposite-outcome positions can sometimes be useful.
However, the bot must evaluate the actual purchase prices, execution costs, settlement rules, and available liquidity.
7.4 Hedge through another market
A more advanced strategy may use related markets.
For example:
- BTC Up/Down.
- ETH Up/Down.
- SOL Up/Down.
- Another BTC-related prediction market.
A position in one market might offset some exposure in another.
However, correlation does not guarantee a hedge.
Two markets can:
- Move differently.
- Have different resolution times.
- React differently to the same event.
- Have different liquidity conditions.
This creates basis risk.
A cross-market hedge should be evaluated using the relationship between the underlying positions, not simply because their prices appear correlated.
8. Managing Exposure Across Multiple Markets
A more advanced market maker may operate in several markets simultaneously.
For example:
portfolio = {
"BTC": {
"yes": 100,
"no": 40,
},
"ETH": {
"yes": 60,
"no": 20,
},
"SOL": {
"yes": 30,
"no": 50,
},
"XRP": {
"yes": 80,
"no": 10,
},
}
Each market has its own inventory.
But managing each market independently is not enough.
Suppose the bot has large positions in several markets that are exposed to similar market conditions.
A sudden cryptocurrency price movement could affect several positions simultaneously.
This is called concentration risk.
A portfolio-level risk manager should track:
- Exposure per market.
- Total capital committed.
- Exposure by underlying asset.
- Potential fills from open orders.
- Correlated positions.
- Potential losses under defined scenarios.
- Available capital for new orders.
Example limits:
market_limits = {
"BTC": 100,
"ETH": 75,
"SOL": 50,
"XRP": 50,
}
max_total_capital = 250
These are illustrative limits, not recommended trading allocations.
The important principle is:
An individual market strategy should not be allowed to ignore the portfolio's overall risk limits.
Even if the BTC strategy wants to submit a new order, the portfolio risk manager may reject it because the total exposure is already too high.
9. Implementing a Simple Portfolio Risk Manager
Let's connect the previous concepts into a small Python implementation.
Our class will evaluate whether a proposed buy order would exceed the maximum allowed inventory.
from decimal import Decimal
class InventoryRiskManager:
def __init__(
self,
target_inventory: Decimal,
max_inventory: Decimal,
):
self.target_inventory = target_inventory
self.max_inventory = max_inventory
def can_buy(
self,
current_inventory: Decimal,
open_buy_quantity: Decimal,
new_order_quantity: Decimal,
):
projected_inventory = (
current_inventory
+ open_buy_quantity
+ new_order_quantity
)
return projected_inventory <= self.max_inventory
risk_manager = InventoryRiskManager(
target_inventory=Decimal("100"),
max_inventory=Decimal("120"),
)
allowed = risk_manager.can_buy(
current_inventory=Decimal("90"),
open_buy_quantity=Decimal("20"),
new_order_quantity=Decimal("15"),
)
print("Order allowed:", allowed)
Output:
Order allowed: False
Why?
The projected inventory would become:
90 + 20 + 15 = 125
But the maximum allowed inventory is 120.
Therefore, the order is rejected.
This demonstrates the basic idea of a risk gate.
The strategy proposes an order, but the risk manager decides whether it is allowed.
A production implementation needs more than this example.
It should also validate:
- Current YES and NO balances.
- Available capital.
- Open orders and partial fills.
- Reserved funds.
- Cost basis.
- Mark-to-market exposure.
- Resolution scenario exposure.
- Market-specific limits.
- Portfolio-wide limits.
- Market data freshness.
- Exchange order-state consistency.
The risk manager should be independent of the strategy so that every order passes through the same validation process.
10. Scenario Analysis: What Happens If Orders Fill?
A professional risk system should not only evaluate the current portfolio.
It should also calculate what could happen next.
Let's return to our original example:
YES inventory: 120
NO inventory: 40
YES average cost: $0.46
NO average cost: $0.48
Total cost: $74.40
Resolution scenarios:
| Scenario | Payout | Result before costs |
|---|---|---|
| YES wins | $120 | +$45.60 |
| NO wins | $40 | -$34.40 |
Now imagine the bot has additional open buy orders:
YES buy orders: 30 tokens
NO buy orders: 20 tokens
If all orders fill:
YES inventory: 150
NO inventory: 60
The risk manager should evaluate this projected position before allowing the orders to remain active.
But there is another important consideration.
Orders do not necessarily fill together.
The bot should evaluate different execution scenarios:
Scenario A: No orders fill
The portfolio remains unchanged.
Scenario B: Only YES buy orders fill
YES exposure increases.
Scenario C: Only NO buy orders fill
NO exposure increases.
Scenario D: All buy orders fill
Both positions increase.
Scenario E: Market liquidity disappears
The bot may be unable to reduce its inventory at a reasonable price.
Scenario F: The market resolves
The portfolio receives its final settlement payout according to the market rules.
This is the foundation of scenario-based risk analysis.
Example risk rules:
if projected_yes_inventory > max_yes_inventory:
reject_yes_buy_orders()
if projected_no_inventory > max_no_inventory:
reject_no_buy_orders()
if total_exposure > max_portfolio_exposure:
reduce_risk_increasing_orders()
if market_data_is_stale:
stop_submitting_new_quotes()
if order_state_is_uncertain:
reconcile_exchange_orders()
These checks help prevent the bot from increasing risk when its portfolio information is incomplete or outside predefined limits.
11. Recommended Portfolio Risk Architecture
At this point, our market maker needs separate responsibilities for strategy, risk management, execution, and portfolio tracking.
A practical architecture could look like this:
Market Data
|
v
Market State
|
v
Quote Strategy
|
v
Order Intent
|
v
Portfolio Risk Manager
|
+-----+-----+
| |
v v
Approved Rejected
|
v
Execution Engine
|
v
Exchange
|
v
Order Updates
|
v
Portfolio State
|
+---------> Risk Manager
Let's understand the responsibilities.
Market Data
Receives real-time market information through WebSockets.
Market State
Maintains current prices, order book information, and market status.
Quote Strategy
Calculates possible bid and ask prices.
Order Intent
Describes what the strategy wants to do without immediately submitting an exchange order.
Portfolio Risk Manager
Checks whether the proposed action is acceptable.
Execution Engine
Submits, cancels, and tracks approved orders.
Portfolio State
Tracks balances, positions, cost basis, fills, and open orders.
Whenever an order fills, gets canceled, or changes state, the portfolio state should be updated.
The risk manager should receive the latest portfolio information before approving additional orders.
The system should also reconcile its internal state with the exchange to recover from missed WebSocket messages, reconnects, and unexpected execution results.
This separation makes the system easier to test, maintain, and extend.
12. Testing Inventory and Portfolio Risk
Before enabling live trading, we need to test the risk manager with controlled scenarios.
Here are some important test cases:
| Test | Expected behavior |
|---|---|
| Inventory below target | Allow buying within limits |
| Inventory at target | Reduce aggressive buying |
| Inventory above target | Skew quotes against further accumulation |
| Maximum inventory reached | Reject additional risk-increasing buys |
| Open orders consume capacity | Include potential fills |
| Partial fill received | Update inventory correctly |
| Cancellation uncertain | Reconcile before assuming cancellation |
| Market data becomes stale | Stop unsafe new quoting |
| Multiple markets exceed total limit | Reject additional exposure |
| Bot restarts | Restore and reconcile portfolio state |
You should also test situations where several events happen almost simultaneously.
For example:
- A YES buy order partially fills.
- Another order fills.
- The bot sends a cancellation request.
- The exchange reports another fill.
- The bot restarts.
The portfolio manager must correctly reconstruct the final position.
This is why inventory management and order management must work together.
For reliable testing, use recorded exchange events and deterministic simulations before connecting the risk system to live execution.
13. Common Inventory Management Mistakes
Mistake 1: Tracking only token quantities
Holding 100 YES tokens is not enough information.
You also need cost basis, current valuation, potential payouts, and open-order exposure.
Mistake 2: Assuming opposite positions automatically create profit
Equal YES and NO positions can create a matched resolution payout.
However, the combined purchase cost and trading fees still determine whether the position is profitable.
Mistake 3: Ignoring unfilled orders
Open buy orders can become additional inventory.
Always include potential fills when calculating projected exposure.
Mistake 4: Using identical order sizes everywhere
An order size that is reasonable in one market might be too large in another.
Consider liquidity, inventory, capital, and portfolio limits.
Mistake 5: Managing markets independently
Several positions can be exposed to the same underlying event.
A portfolio-level risk manager should evaluate concentration and correlation.
Mistake 6: Treating mark-to-market value as guaranteed cash
The displayed price is not necessarily the price at which the entire position can be sold.
Consider order book depth, liquidity, and slippage.
14. Final Implementation Checklist
Before running your Polymarket market maker, make sure it can:
- [ ] Track YES and NO inventory separately.
- [ ] Calculate cost basis.
- [ ] Calculate mark-to-market value.
- [ ] Estimate resolution scenario payouts.
- [ ] Track open orders and potential future fills.
- [ ] Apply inventory-aware quote adjustments.
- [ ] Enforce maximum position limits.
- [ ] Control order sizes.
- [ ] Reserve capital for outstanding orders.
- [ ] Evaluate potential hedges before execution.
- [ ] Track exposure across multiple markets.
- [ ] Detect stale or inconsistent portfolio data.
- [ ] Recover positions after a restart.
- [ ] Reconcile orders and balances with the exchange.
- [ ] Stop new trading when risk limits are exceeded.
A good market maker does not simply ask:
"Can I place this order?"
It also asks:
"What will my entire portfolio look like if this order fills?"
That is the difference between basic order automation and portfolio-aware trading infrastructure.
Conclusion
In Day 12, we moved beyond individual orders and focused on managing the entire portfolio.
We explored how to:
- Track YES and NO positions.
- Calculate acquisition cost and current position value.
- Understand possible resolution outcomes.
- Adjust quotes based on inventory.
- Control order sizes and available capital.
- Evaluate different hedging strategies.
- Manage exposure across multiple markets.
- Implement a basic Python inventory risk manager.
- Test different portfolio risk scenarios.
The most important lesson is that a market maker should not optimize every order independently.
It should make decisions based on the combined effect of all existing positions and outstanding orders.
Inventory management is not just an additional feature. It is a fundamental part of building a reliable market-making system.
What's Next?
In Day 13, we will move toward a larger trading system:
Day 13: Polymarket Market Maker Bot — Multi-Market Trading, Capital Allocation, and Portfolio Optimization
We will explore how to manage several markets simultaneously, decide where capital should be allocated, and prevent one market from consuming too much of the bot's available resources.
Follow the Project
I'm documenting the development of this Python-based Polymarket trading bot step by step.
-
GitHub:
Benj4minCup / Polymarket-Trading-Bot-python
polymarket Twap Bot polymarket Arbitrage Bot polymarket bot
Polymarket Trading bot system
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.
This repository is primarily intended for educational and research purposes. It includes strategy concepts, implementation approaches, and selected performance screenshots to help developers understand how different automated trading strategies can be designed and tested.
The repository does not provide a complete production-ready trading bot source code. Instead, it provides strategy descriptions and research materials that you can use as a foundation for developing your own system.
If you are interested in building a Polymarket Trading Bot, you can follow my tutorials and use the concepts in this repository to develop your own implementation.
For users who prefer a ready-to-deploy solution or require custom strategy development, commercial bot development and customization are also available.
Features
… YouTube:
Telegram: https://telegram.me/BenjaminCup
Follow the project to explore the implementation and learn how different components of a market-making system work together.


Top comments (0)