BANGER
Product status: Banger’s public beta is paper-only. References to live execution are future-looking. Venue rules and legal claims can change; verify current primary sources for your jurisdiction.
2026-07-03 · 6 min read · facts as of 2026-07-03

Position Tracking and PnL for Binary Event Contracts

Binary event contracts look like equities with a price between 0 and 1, so it is tempting to reuse the same PnL code you would write for a stock. That reuse is where a lot of position trackers quietly go wrong. A share of stock has no forced terminal value. A binary contract does: it collapses to exactly 1 or 0 at resolution, and your accounting has to model that jump explicitly or your reported PnL will lie to you at the worst possible moment.

The instrument: a contract worth 1 or 0

On both major venues the payoff is the same. On Kalshi, each contract settles at $1 if your side is correct and $0 if it is not, and prices trade between 1 cent and 99 cents, where the price reads as the market-implied probability. On Polymarket, resolution assigns winning outcome tokens $1 each and losing tokens become worthless, with Yes and No prices always summing to about $1.

That last identity matters for tracking. On Polymarket, buying Yes at $0.60 is economically equivalent to selling No at $0.40, because every Yes/No pair in existence is backed by exactly $1.00 of collateral. If your position model treats Yes and No as unrelated instruments, you will double-count risk on a market where you hold both legs.

Track position as signed contracts plus cost basis

Keep it minimal. For each market outcome, store net contract quantity and the total cash you paid to acquire it, fees included. Everything else derives from those two numbers plus the current mark. Store the mark separately from cost. Do not overwrite your entry price with the live price, which is the single most common bug in homegrown trackers.

Realized vs unrealized PnL

Realized PnL is cash that has actually changed hands from closed contracts: proceeds from sells minus the cost basis of the contracts you sold, minus fees. Unrealized PnL is a mark-to-market estimate on contracts you still hold: (mark minus average cost per contract) times quantity. The two should never be summed into a single number without a label, because they have completely different reliability.

The naive equity approach marks open contracts at the last trade or the mid and calls it a day. For binary contracts that mid is often a lie, for two reasons. First, spreads on thin event markets can be wide, so the mid overstates what you can actually exit at. Mark to the exit side of the book. Second, and more importantly, the mark ignores the terminal jump.

The resolution jump is where equity-style PnL breaks

A contract trading at 0.92 the second before resolution does not glide to 1.00. It steps. If your side wins it jumps to 1.00; if it loses it drops to 0.00. There is no in-between except in specific edge cases, and your unrealized PnL swings discontinuously across that boundary.

Concretely: you bought 100 Yes at 0.40, cost $40 plus fees. The market now trades 0.92, so a mark-based tracker shows about $52 unrealized profit. If it resolves Yes, you receive $100 and realize roughly $60 before fees. If it resolves No, you receive $0 and realize a $40 loss. The last-traded mark of 0.92 described neither outcome. It described a probability, and probabilities do not get paid out. Only the resolution does.

So a correct tracker carries three PnL views for an open position: the mark-to-market value now, the payoff if it resolves in your favor, and the payoff if it resolves against you. That win/lose spread is the honest risk on the position, and it is exactly what a naive single-number PnL hides.

Settlement is not instantaneous, and marks can drift there

Do not assume the mark converges to the payout the instant the event happens. On Kalshi, most markets settle within a few hours after the outcome is known, and sometimes longer when waiting on official source-agency data, with the displayed close time not necessarily equal to the determination time. On Polymarket, resolution runs through the UMA optimistic oracle: a proposer posts an outcome, and if undisputed within a two-hour challenge window the market resolves, while disputes can escalate to a token-holder vote that adds days to the timeline.

During that window your position is effectively pending. A robust tracker should have a pending-settlement state rather than either marking at the last live price or prematurely booking the payout. Booking $1.00 the moment an event looks decided will overstate realized PnL if a dispute or a data delay is still in play.

Two edge cases to model rather than assume away. Polymarket can resolve a genuinely ambiguous or cancelled market at $0.50 per share, paying both sides equally rather than picking a winner. And on Polymarket, depending on the market version, winning tokens may require a separate redemption step before the USDC lands in your wallet. 'Resolved' and 'cash in hand' are two different states your accounting should distinguish.

A minimal PnL model

def position_pnl(qty, cost_basis, mark, resolution):
    # qty: net contracts held (>0 long the outcome)
    # cost_basis: total cash paid incl. entry fees
    # mark: exit-side price in [0, 1], for open positions
    # resolution: None | 'win' | 'loss' | 'split'
    if resolution == 'win':
        return qty * 1.00 - cost_basis      # realized
    if resolution == 'loss':
        return -cost_basis                  # realized
    if resolution == 'split':
        return qty * 0.50 - cost_basis      # realized

    # open position: report all three, never just the mark
    avg = cost_basis / qty if qty else 0.0
    return {
        'mark_to_market': (mark - avg) * qty,
        'if_win':  qty * 1.00 - cost_basis,
        'if_lose': -cost_basis,
    }

The point of returning a dict for open positions is discipline: it forces every consumer of your PnL to acknowledge that an open binary position has a range, not a point. Sum the mark-to-market across positions for a portfolio snapshot, but size risk off the if_lose column.

Why this matters for automation

If you are running a strategy unattended, the risk envelope reads these numbers. A daily loss stop that watches only mark-to-market can sail through the day looking fine and then get hit by a cluster of resolutions that were always going to be losses. Size and stop against realized-plus-worst-case, not against the friendly mark.

This is the accounting Banger uses under the hood. You paper-trade a banger.Strategy against the live order book first, marking open positions to the exit side of the book, and the declarative risk envelope (per-trade cap, daily loss stop, max open positions, kill switch) evaluates the worst-case resolution exposure rather than a single optimistic mark. Banger never custodies funds; you bring your own venue keys and it runs the same PnL model in paper and live so the numbers do not change when real money is on the line. Install with pip install bangertrades, then banger run strategy.py --paper.

The through-line: a binary contract is not a stock with a low price. It is a claim that will be worth exactly a dollar or exactly nothing, and any position tracker that forgets the second half of that sentence will hand you a comfortable number right up until the market resolves and takes it back.

Sources

Keep reading

Run your first strategy free

Paper-trade on Polymarket and Kalshi market data without venue keys.

Start free