Metadata-Version: 2.5
Name: prediction-fees
Version: 0.2.0
Summary: Exact Kalshi and Polymarket trading fees from a dated schedule: taker, maker, multi-level executions, cent round-up.
Project-URL: Homepage, https://langbot.ai
Author-email: Langbot <hello@langbot.ai>
Maintainer-email: Langbot <hello@langbot.ai>
License-Expression: MIT
License-File: LICENSE
Keywords: backtesting,fees,kalshi,polymarket,prediction-markets,trading
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial :: Investment
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# prediction-fees

Exact trading fees for Kalshi and Polymarket, from a dated schedule. No dependencies, MIT.

```bash
pip install prediction-fees
```

```python
from prediction_fees import kalshi_fee, kalshi_fee_levels, polymarket_fee, polymarket_rebate

kalshi_fee(0.50, 100)  # Decimal('1.75')  taker
kalshi_fee(0.50, 1)  # Decimal('0.02')  0.0175 rounds up to the cent
kalshi_fee(0.50, 100, maker=True)  # Decimal('0.44')
kalshi_fee(0.50, 100, as_of="2026-11-03")

# one order that walked three levels: summed first, rounded once
kalshi_fee_levels([(0.45, 10), (0.46, 20), (0.48, 5)])  # Decimal('0.61')

# a series without maker fees (Kalshi GET /series/{ticker} fee_type "quadratic")
kalshi_fee(0.50, 100, maker=True, fee_type="quadratic")  # Decimal('0.00')

polymarket_fee(0.50, 100, category="crypto")  # Decimal('1.75000') USDC
polymarket_fee(0.50, 100, rate=0.03)  # Decimal('0.75000')  the market's own feeSchedule.rate
polymarket_fee(0.50, 100, maker=True)  # Decimal('0.00000')
polymarket_rebate(0.60, 100, category="politics")  # Decimal('0.240000')  maker rebate
```

Every function returns a `Decimal`. Prices are in dollars (0 to 1). Inputs can be `int`,
`float`, `str` or `Decimal`; floats are read through their shortest repr, so `0.07` is exactly
`0.07` and there is no float error in the round-up (a naive `math.ceil` charges a cent too many
on, for example, 100 contracts at 0.10).

## The rules

**Kalshi.** Per order execution (one order matched at one time, across however many price
levels it took):

```
fee = round_up_to_cent( rate × multiplier × Σ C × P × (1 − P) )
```

`rate` is 0.07 for takers and 0.0175 for makers. `multiplier` is the series'
`fee_multiplier` and applies to takers and makers (1 for most series; some 0.5, a few 0).
**Makers pay only on series whose `fee_type` charges makers** (`quadratic_with_maker_fees`,
`quadratic_with_combo_maker_fees`); a `quadratic` series charges makers 0. On 2 Nov 2026 that
was 163 of Kalshi's 15,600 series. Read both from Kalshi's public
`GET /trade-api/v2/series/{series_ticker}` (the series is the market ticker up to the first
`-`) and pass them: `fee_type="quadratic"` makes a maker fill cost 0, and
`kalshi_multipliers(fee_type, fee_multiplier)` gives the `(taker, maker)` multipliers. Without `fee_type` makers are charged (the
conservative side).

**Polymarket.** Takers, per execution:

```
fee = round_down_to_0.00001( rate × Σ C × P × (1 − P) )
```

`rate` is the market's own coefficient: Gamma `GET /markets?id=` returns it as
`feeSchedule.rate` (0 when `feesEnabled` is false), and some older sports markets still carry
0.03. Without it, the category table:

| category | taker coefficient | maker rebate share |
|---|---|---|
| crypto | 0.07 | 0.20 |
| sports | 0.05 | 0.15 |
| economics, culture, weather, other | 0.05 | 0.25 |
| combos | 0.05 | 0 |
| finance, politics, mentions, tech | 0.04 | 0.25 |
| geopolitics | 0 | 0 |

Pass the market's own coefficient as `rate` or `category` (case-insensitive);
`polymarket_market_terms(market)` reads a Gamma row (its `feeSchedule`, else the category its `feeType` names,
e.g. `politics_fees`). Fees are rounded to 5 decimals, down: Polymarket's docs say anything
below 0.00001 USDC rounds to zero.

Makers pay 0 and earn a rebate, `polymarket_rebate`:

```
rebate = rebate_share × rate × Σ C × P × (1 − P)
```

Each market's daily rebate pool is its rebate share of the taker fees paid there, split pro rata
by each maker's fee-equivalent, so a maker's rebate is exactly the share of its own
fee-equivalent. Pass the market's `feeSchedule.rebateRate` as `rebate_share` (or a `category`).
The rebate is not rounded (it is paid daily in aggregate) and the $1 minimum payout is not
modelled.

**Edge cases.** A price of exactly 0 or 1 costs nothing (`P × (1 − P)` is 0). A price outside
[0, 1], a negative size or rate, NaN or infinity raises `FeeError` (a `ValueError`). Zero
contracts cost nothing.

## Dates

The table (`fees.json`, shipped inside the package) lists each venue's rates with the date they
took effect. `as_of=` takes a `date`, a `datetime` (read in UTC) or an ISO string and picks the
entry in force that day; without it you get the latest entry. A date before the first entry
raises `FeeError` rather than guessing; pass `rate=` to price an older trade.

| venue | effective | rates |
|---|---|---|
| Kalshi | 2026-07-07 | taker 0.07, maker 0.0175 |
| Polymarket | 2026-07-10 | the category table above, makers 0 plus the rebate share |

`prediction_fees.table()` returns the whole table, sources and notes included.

## What is not certain

Checked on 2026-11-01 (Polymarket and the Kalshi series rule on 2026-11-02):

- Kalshi's taker formula and 0.07 are well established. The maker coefficient 0.0175 and the
  2026-07-07 effective date come from secondary sources quoting Kalshi's fee schedule PDF
  (kalshi.com/docs/kalshi-fee-schedule.pdf), which could not be fetched to confirm.
- Kalshi's API docs describe fees accrued to $0.000001 with a per-order rounding accumulator
  that refunds over-rounding. This library follows the published schedule (round up to the
  cent per execution), so a fill split across several executions may show a cent or two more
  than Kalshi's final charge.
- Polymarket's coefficients and rebate shares are as read from docs.polymarket.com on
  2026-11-02. 2026-07-10 is the date of the last change in the published timeline (sports 0.03
  to 0.05); earlier periods are not tabulated, and a market's own `feeSchedule` can still differ
  from the table (0.03 on some older sports markets), so prefer it. The docs say fees are
  rounded to 5 decimals and that anything below 0.00001 USDC rounds to zero; this library reads
  that as rounding down, which the docs do not state in so many words.
- Not covered: the $1 minimum rebate payout, taker volume rebates, Polymarket US (a separate
  exchange with its own schedule), Kalshi incentive programs and perpetuals.

Corrections with a source are welcome: hello@langbot.ai.

The same table and functions ship for JavaScript/TypeScript as
[`prediction-fees` on npm](https://www.npmjs.com/package/prediction-fees).

## License

MIT. Maintained by [Langbot](https://langbot.ai).
