Guide · updated September 2026

Why your Kalshi P&L is wrong

A trader told us FillBook showed him down fifty dollars when he was up about two hundred. He was right, and finding out why took three separate bug fixes. Every one of them will hit any spreadsheet or dashboard built on the Kalshi API, so here they are in full, plus the two more we found while checking our work against Kalshi's own numbers.

1. Half your fills are behind a different endpoint

Kalshi splits portfolio data at a moving cutoff, a few months back.GET /portfolio/fills only returns fills after it; everything older lives at GET /historical/fills with the same shape and cursor. Ask GET /historical/cutoff for the boundary (trades_created_ts is the one for fills).

Read only the live endpoint and a position opened in May and closed in September is computed from its closing fills alone: no cost basis, so the loss or gain is nonsense. Settlements, orders and positions are partitioned the same way, each with its own cutoff.

2. A sell is reported on the opposite side

This is the expensive one. Kalshi reports a fill by the side of the contract that changed hands, not the side of the position you hold. Sell the YES contracts you are long and the fill comes back as:

{ "action": "sell", "side": "no",
  "yes_price_dollars": "0.1200", "no_price_dollars": "0.8800" }

Take it literally and you record a new short NO position while your YES position stays open. Books never close. Markets that settled months ago sit there carrying their entire cost as an unrealized loss, which is exactly how a profitable account reads as a losing one.

The rule that reconciles with Kalshi's own numbers: on a sell, apply the fill to the oppositeside of the one reported, at that side's price. Closing a long NO arrives as sell / yes and uses no_price_dollars. We verified this across every settled market of a real 596-fill account; the literal reading matched none of them, this one matched all of them.

3. Contracts are fractional

count_fp is a fixed-point string with two decimals. Real fills look like 17.03, 0.41, 372.32. In the account we audited, a quarter of all fills were fractional.

Round them to whole contracts and each fill is off by up to half a contract of cost, which across six hundred fills came to about ten dollars of drift. Store quantities scaled (hundredths of a contract as an integer works well) rather than as floats or rounded counts.

4. Prices go below a cent

Longshots fill at sub-cent prices: "yes_price_dollars": "0.0050" is half a cent. The dollar fields carry four decimals for this reason.

Storing prices as whole cents rounds half a cent up to one, which doublesthe cost basis of that position. One golf market in our audit showed a $3.85 loss against Kalshi's $1.99 for exactly this reason. Keep prices in hundredths of a cent.

5. Settlement rows net offsetting pairs

If you hold YES and NO in the same market, the pairs are guaranteed to pay a dollar each, and Kalshi settles them separately. The settlement record then shows revenue for the remaining one-sided position while yes_total_cost_dollars and no_total_cost_dollars still list the cost of both sides.

Reconstruct P&L from that row alone and a winning market can look like a small loss. We hit this while building a reconciliation check: a market where our number said +$4.26 and the settlement row implied −$0.25. The journal was right; the row was incomplete.

How to check your own numbers

Kalshi publishes realized_pnl_dollars and fees_paid_dollars per market on GET /portfolio/positions and GET /historical/positions. That figure is gross of fees, so compare it against your realized number with fees folded in, market by market. Settled markets should agree to the cent. Anything else is a bug, and the five above cover most of them.

Two caveats from doing this in anger: Kalshi archives settled positions with a lag, so a recently active account may have nothing to compare yet (settlements fill that gap), and skip markets where you held both sides, for the reason in point five.

Or let FillBook do it

FillBook imports your Kalshi fills with a read-only key, handles all five of these, marks your open positions at the current bid, and has a button that reconciles your P&L against Kalshi's own per-market figures so you do not have to take our word for any of it.

See the live demo → or import your own fills. Free, no card. The export guide covers the endpoints if you would rather build it yourself.

FAQ

Why does my Kalshi P&L spreadsheet not match my account?
The usual causes are mechanical, not arithmetic: fills older than Kalshi's historical cutoff are served by a different endpoint and get missed, sells are reported under the opposite side so exits look like new positions, and fractional contracts and sub-cent prices get rounded away. Each one moves the total in a different direction.
Why does selling my YES contracts show up as a NO fill?
Kalshi reports a fill by the side of the contract that changed hands, not the side of the position you held. Closing a long YES comes back as action sell with side no. If you treat that literally you open a phantom opposite position and the original trade never closes.
Does Kalshi trade fractional contracts?
Yes. Counts come back as fixed-point strings with two decimals (count_fp of 372.32 is normal), and prices on longshots go below a cent (yes_price_dollars of 0.0050 is half a cent). Storing either as a whole number distorts the cost basis of exactly the trades where it matters most.
Why is my settlement payout smaller than the contracts I held?
If you hold both YES and NO in the same market, Kalshi nets the offsetting pairs and pays them out separately, so the settlement record shows the remaining one-sided position's revenue while still listing the cost of both sides. Reconstructing P&L from that row alone understates it.
How can I verify a Kalshi P&L number?
Kalshi publishes realized_pnl_dollars per market on its positions endpoints. Recompute your own number from raw fills and compare market by market; anything beyond a cent of rounding is a bug in the computation.

Behaviour observed on Kalshi's public API in September 2026 against real accounts. Not affiliated with Kalshi; confirm against docs.kalshi.com.