Skip to content

GuidesTrading & accounts

Paper trading gives every account a simulated USD balance and a set of order routes that look like the live ones. Orders are filled by the paper engine against the real order books Predictefy already streams, so a strategy can be exercised end to end before any money is involved.

Nothing here touches a venue. No order is sent, no balance moves, and no venue account is required — the fills, positions, and PnL are Predictefy’s simulation of what those books would have given you.

Paper trading is included on every plan, with these per-account limits (live since 2026-09-11):

PlanOpen paper orders at once
FreeUp to 10
BuilderUp to 50
ProUp to 200
EnterpriseNo cap

Only orders currently open or partially_filled count towards the cap. Fills, cancels, and account resets are unlimited. Reaching the cap returns 409 PAPER_WORKING_ORDER_CAP, with a message naming the cap and your plan. Cancel an open paper order or upgrade before placing another.

Credit metering is unchanged. See the plan table.

A paper order carries the parameters you would use to trade for real: the venue, the market and outcome, a side, a limit price on the market’s tick grid, a size, and a time in force. Prices are probabilities in (0, 1]; the tick is the market’s published tickSize, or one cent when it publishes none.

Every route needs an API key with the trade scope, which new keys do not carry by default. Select “Allow this key to place trades” when creating a key in the console.

RouteWhat it does
GET /v1/paper/accountThe simulated cash account, created on first touch
POST /v1/paper/account/resetRestart the simulation, keeping fills as history
POST /v1/paper/ordersPlace an order (Idempotency-Key required)
GET /v1/paper/ordersYour orders, newest first, cursor-paged
GET /v1/paper/orders/{id}One order together with its fills
POST /v1/paper/orders/{id}/cancelCancel a working order and release its hold
GET /v1/paper/fillsEvery simulated fill, cursor-paged
GET /v1/paper/positionsPer-outcome size, cost, and a catalog mark
GET /v1/paper/portfolioThe account, its positions, and their totals

Idempotency-Key is required on POST /v1/paper/orders; without it the request is refused with 400 IDEMPOTENCY_KEY_REQUIRED and nothing is stored. Reuse the same key when you retry: a first-seen key returns 201 with the new order, and a repeat returns 200 with the stored one plus the header Idempotency-Replay: true, whatever body the retry carried.

Terminal window
curl -s -X POST "$PREDICTEFY_API_URL/v1/paper/orders" \
-H "Authorization: Bearer pk_live_YOUR_KEY" \
-H "Idempotency-Key: 9d2f0f1c-order-1" \
-H "content-type: application/json" \
-d '{
"venue": "kalshi",
"marketId": "PRES-2028-DEM",
"outcomeId": "kalshi:PRES-2028-DEM:yes",
"side": "buy",
"price": 0.62,
"size": 100,
"tif": "GTC"
}'
import { Predictefy } from '@predictefy/sdk';
const client = new Predictefy({ apiKey: process.env.PREDICTEFY_API_KEY });
const order = await client.placePaperOrder({
venue: 'kalshi',
marketId: 'PRES-2028-DEM',
outcomeId: 'kalshi:PRES-2028-DEM:yes',
side: 'buy',
price: 0.62,
size: 100,
tif: 'GTC',
idempotencyKey: '9d2f0f1c-order-1', // reuse this exact key on a retry
});
const working = await client.listPaperOrders({ status: 'open' });
const portfolio = await client.paperPortfolio();
from predictefy import Predictefy
client = Predictefy(api_key="pk_live_YOUR_KEY")
order = client.place_paper_order(
"kalshi",
"PRES-2028-DEM",
"kalshi:PRES-2028-DEM:yes",
"buy",
0.62,
100,
tif="GTC",
idempotency_key="9d2f0f1c-order-1", # reuse this exact key on a retry
)
working = client.list_paper_orders(status="open")
portfolio = client.paper_portfolio()

A buy reserves price × size plus the modelled fee out of your cash and is refused with 409 PAPER_INSUFFICIENT_CASH when free cash cannot cover it. A sell may never exceed the size you hold minus what your working sells already offer — there is no shorting, and a sell past that line is refused with 409 PAPER_POSITION_TOO_SMALL. reserved is a hold inside cash, so your free cash is cash - reserved.

Cancelling releases the unspent part of the hold, recomputed from the order’s own fills, so a partially filled cancel never hands back cash the engine already spent. Only open and partially_filled orders can be cancelled; anything terminal returns 409 PAPER_ORDER_NOT_OPEN.

Fills come from Fill Model v1, one pure rule set shared by paper trading and the backtester. It is deliberately pessimistic: wherever the honest answer is unknown, it declines to fill rather than inventing an edge you could not have had.

  • Marketable orders walk the displayed depth. A buy at or above the best ask, or a sell at or below the best bid, walks the ladder from the touch and pays each level its own price, stopping at your limit. A top-of-book-only book is one level, so the walk caps at the displayed touch size.
  • Resting orders fill only when the market trades through them. Once an order has rested, the opposite quote must cross strictly through your limit, not merely touch it — a touch would require knowing your queue position. The fill takes only the crossing level and prints at your own limit price.
  • At most a quarter of a displayed lot per book update. One order may take no more than 25% of the size shown at a price level on any single book update, and orders evaluated against the same book share what is left of that level’s lot.
  • Exact venue fees where they are verified. Fees are charged per fill, at that fill’s own price and size, from the verified per-venue taker schedules. A venue with no proven fee model gets a fee of 0 labelled unverified on the fill — a labelled zero, never a silent one. A venue’s published per-order minimum or flat charge (Opinion’s $0.25 minimum, Myriad’s $0.0085 per transaction) is held with a buy order and paid over its fills. There are no maker rebates in v1; the fill records which side supplied liquidity.
  • No fills on a stale book. A book older than 30 seconds is not evidence about the present, so nothing fills against it and the order simply waits. This is the one case where time in force does not apply: even an IOC waits for a fresher book rather than giving up a chance it never actually got.

Time in force then decides the remainder: GTC rests, IOC gives up what is left, and FOK is all or nothing — a short book leaves the order rejected with no fills and no consumption, so the lot stays whole for the next order.

Every item below is a real cost or advantage of live trading that Fill Model v1 does not represent. Read simulated PnL with them in mind.

  • Queue position. Your place in a venue’s order queue is not modelled, which is why a resting order needs a strict cross rather than a touch.
  • Market impact. Your simulated order never moves the book it trades against.
  • Latency slippage. The delay between deciding and arriving at the venue is not modelled; there is no re-quote and no missed touch.
  • Price improvement. A resting order earns its own limit price, never the better price of the quote that crossed it.
  • Hidden liquidity. Only displayed size is available. Iceberg and other undisplayed size never fills you.
  • Continuity across an engine restart. A working order’s evaluation history does not survive a restart of the simulation engine, so it is re-evaluated from scratch against the next book it sees.

GET /v1/paper/positions returns one row per venue and outcome you have traded. A flattened position stays at size 0 so its realized PnL survives as history.

Marks come from Predictefy’s catalog snapshot, reported as markSource: "catalog". A position that cannot be marked reports mark: null, markSource: null, and unrealizedPnl: null rather than a fabricated number, and one unmarkable open position makes totals.equity and totals.unrealizedPnl null too. totals.realizedPnl is always a number and survives a reset.

Settlement coverage is partial and says so:

  • Gemini and Predict.fun resolutions are read from the venue’s published outcome, and positions in a resolved market settle automatically.
  • Every other venue leaves the position at settlement: "pending", untouched. An unparseable resolution payload does the same. Predictefy does not guess a winner it cannot read.

POST /v1/paper/account/reset restarts the simulation: working orders are cancelled, their holds released, position sizes flattened, and cash returned to initialCash — or to a new initialCash you supply, which becomes the new baseline. Fills, ledger rows, and realized PnL are kept as history. A reset restarts the simulation; it does not erase what happened.

Paper trading is metered at 0 credits. Calls are recorded as usage, but simulated orders, fills, positions, and portfolio reads cost nothing — there is no venue behind them. See Pricing, credits & billing for everything that is metered.

StatusCodeWhen
400IDEMPOTENCY_KEY_REQUIREDPOST /v1/paper/orders arrived without an Idempotency-Key
400VALIDATION_ERRORBad shape or range, an off-tick price, an unknown status, a stale cursor
403PLAN_REQUIREDThe account’s plan does not include paper trading
404MARKET_NOT_FOUNDUnknown market, or an outcome the market does not carry
404PAPER_ORDER_NOT_FOUNDUnknown order id, or one that is not yours
409PAPER_MARKET_CLOSEDThe market is not open, or already resolved
409PAPER_INSUFFICIENT_CASHFree cash cannot cover the buy’s reservation
409PAPER_POSITION_TOO_SMALLThe sell exceeds the size you hold less your working sells
409PAPER_WORKING_ORDER_CAPYour plan’s open paper orders are all in use
409PAPER_NO_LIVE_BOOKNo live order book is captured for that venue and outcome

PAPER_NO_LIVE_BOOK means Predictefy holds no book for that outcome, or none with a fresh frame, so the order is refused at intake rather than accepted against stale prices it could never honestly have filled against. The message names the venue, the outcome, and which of the two it was. Retry once that market’s book is live again, or place the order on a more liquid market. This is the intake check; the 30-second staleness rule above is the separate one the engine applies to orders that are already resting.

PAPER_WORKING_ORDER_CAP names both the cap and your plan — cancel an open paper order or upgrade. PLAN_REQUIRED is only reachable if a plan ever loses paper access; every plan carries it today.

Unknown, malformed, and other accounts’ order ids all return the same PAPER_ORDER_NOT_FOUND, so the route is never an oracle for another account’s orders.