Polymarket Balance/Allowance Error: A Fractional Shares Problem?

Hit a confusing error when placing orders on Polymarket:

PolyApiException[status_code=400, error_message={'error': 'not enough balance / allowance'}]

Checked balance and allowances—everything looked fine. USDC balance sufficient, allowances set correctly. Error only appeared on specific markets, not others. Spent time debugging the wrong thing before finding the likely cause.

Short version: Position sizes aren't round numbers. Tried to sell 23 YES, actually held 22.99695 YES. Order rejected.

Not 100% confirmed this is the issue, but timing and behavior match up.

The Misleading Debug Path

First instinct: check balance and allowances as suggested by folks online.

from py_clob_client.clob_types import AssetType, BalanceAllowanceParams

polymarket_client.clob_client.get_balance_allowance(
    params=BalanceAllowanceParams(asset_type=AssetType.COLLATERAL)
)

Output:

{
    "balance": "123456",  # plenty of USDC
    "allowances": {
        "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E": "115792...760179",  # Exchange
        "0xC5d563A36AE78145C45a50134d48A1215220f80a": "115792...639935",  # Operator
        "0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296": "115792...639935",  # NegRisk
    }
}

Those allowances are uint256.max (unlimited). Balance is fine. Allowances are fine. But order still fails.

Found others online with similar error, but they had zero balance or missing allowances. Different issue. That was a rabbit hole.

Buy vs Sell Asymmetry

Next observation: error only happened when SELLING (I think, didn't do extensive testing). Buying worked fine.

Tried this:

  • SELL 23 YES @ 0.94 → error
  • BUY 23 NO @ 0.06 → works

Economically equivalent (buying NO at 0.06 is selling YES at 0.94), mechanically different.

Initial hypothesis: Sell requires holding the token, buy only needs USDC. Maybe I don't have enough YES tokens?

Checked positions. Had YES tokens. Long position in that market. Should work.

Still failed.

The Likely Culprit: Fractional Position Sizes

Looked closer at actual position sizes. Positions API returns floats:

positions_df = client.positions_df
print(positions_df.filter(pl.col("outcome") == "Yes").select(["title", "size"]))

# Output:
# title: "Will Gemini 3.0 be released by October 15?"
# size: 22.99695

Not 23. 22.99695.

When I tried to SELL 23 YES, the order was rejected. Makes sense if I only held 22.99695—can't sell what you don't have.

Why aren't positions round numbers? Not entirely clear. Could be:

  • Fee calculations leaving fractional amounts
  • Rounding in the settlement process
  • Partial fills accumulating small differences
  • Something else in the exchange mechanics

Doesn't really matter why. Point is: positions aren't the round numbers you might expect from your order sizes.

The Fix

Query actual position sizes before selling:

positions = client.positions_df
yes_position = positions.filter(
    (pl.col("conditionId") == condition_id) &
    (pl.col("outcome") == "Yes")
)
actual_size = yes_position["size"][0]  # 22.99695, not 23

# Sell what you actually have
client.create_order(
    token_id=yes_token_id,
    price=0.94,
    size=actual_size,
    side="SELL"
)

After using actual position sizes, the error stopped appearing.

Why BUY Works But SELL Fails

When you BUY YES tokens:

  • Exchange checks your USDC balance
  • USDC balance is typically exact
  • As long as price * size ≤ usdc_balance, order succeeds

When you SELL YES tokens:

  • Exchange checks your YES token balance
  • Token balance has fractional parts
  • If size > token_balance (even by 0.00305), order fails

The asymmetry: buying checks collateral (usually exact), selling checks tokens (fractional).

Implications for Programmatic Trading

If you're placing orders programmatically, you might do:

# Buy 100 YES at 0.48
client.create_order(token_id=yes_id, price=0.48, size=100, side="BUY")

# Later, sell 100 YES at 0.52
client.create_order(token_id=yes_id, price=0.52, size=100, side="SELL")

Second order maybe fails. You might not have exactly 100 YES.

Better approach is to track actual positions:

# After buying, check what you actually received
positions = client.positions_df
actual_yes = positions.filter(
    (pl.col("asset") == yes_token_id)
)["size"][0]

# Sell your actual position
client.create_order(token_id=yes_id, price=0.52, size=actual_yes, side="SELL")

Or build in a buffer:

import math

actual_yes = 99.97  # what you actually have
safe_size = math.floor(actual_yes)  # 99

# Sell 99, leaves 0.97 behind
client.create_order(token_id=yes_id, price=0.52, size=safe_size, side="SELL")

First approach (sell everything) is cleaner. Second approach (floor to integer) leaves dust that accumulates over time.

What I Still Don't Know

Few open questions:

Why are positions fractional? Probably fees, but could be other rounding in the settlement process. Haven't traced through the full exchange mechanics to confirm.

Are there other precision issues? If positions can be off by 0.00305, what about orders? Do order sizes get rounded anywhere in the flow? Unknown.

Key Takeaways

  • Position sizes aren't round numbers: Even if you bought 23 shares, you might hold 22.99695.

  • Error message is misleading: "not enough balance" could mean "0.00305 tokens short." Check actual position size, not expected size.

  • Always query actual balances: Don't assume positions match your order history. Query positions_df before placing sell orders.

  • Buy vs sell asymmetry: Buying checks collateral (typically exact), selling checks tokens (fractional). Sells are more likely to hit precision issues?

  • Probably related to fees or settlement rounding: But haven't confirmed the exact mechanism.

Simple fix (use actual position sizes), but took time to figure out. The balance/allowance checks were red herrings—those were fine. The issue was trying to sell slightly more than I held.

If you hit this error and your balances/allowances look correct, check if your sell size matches your actual position size. Might save you some debugging time.

Related Posts

Merging Polymarket positions: A real hassle to get working

October 10, 2025

prediction_markets
polymarket
prediction markets
Read More

Stock Embeddings - Learning Distributed Representations for Financial Assets

April 08, 2025

research
machine-learning
finance
Read More