> ## Documentation index
> Fetch the complete documentation index at: https://docs.predictefy.com/llms.txt
> Use it to discover every available page before exploring further.

# Paper trading

> Simulate orders against real venue books with Fill Model v1 — same parameters as live execution, no money at risk.

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.

:::note[Live since 2026-09-11]
The `/v1/paper/*` family is open on every plan, including Free. The only per-plan difference is
the cap on open orders below.
:::

## Plan availability

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

| Plan       | Open paper orders at once |
| ---------- | ------------------------- |
| Free       | Up to 10                  |
| Builder    | Up to 50                  |
| Pro        | Up to 200                 |
| Enterprise | No 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](/guides/credits/#feature-access).

## Same parameters as live

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.

| Route                               | What it does                                       |
| ----------------------------------- | -------------------------------------------------- |
| `GET /v1/paper/account`             | The simulated cash account, created on first touch |
| `POST /v1/paper/account/reset`      | Restart the simulation, keeping fills as history   |
| `POST /v1/paper/orders`             | Place an order (`Idempotency-Key` required)        |
| `GET /v1/paper/orders`              | Your orders, newest first, cursor-paged            |
| `GET /v1/paper/orders/{id}`         | One order together with its fills                  |
| `POST /v1/paper/orders/{id}/cancel` | Cancel a working order and release its hold        |
| `GET /v1/paper/fills`               | Every simulated fill, cursor-paged                 |
| `GET /v1/paper/positions`           | Per-outcome size, cost, and a catalog mark         |
| `GET /v1/paper/portfolio`           | The account, its positions, and their totals       |

## Place an order

`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.

```sh
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"
  }'
```

```ts
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();
```

```python
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`.

## How fills are decided

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.

## What the model does not simulate

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.

## Positions, portfolio, and settlement

`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.

## Credits

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](/guides/credits/) for everything that is metered.

## Errors

| Status | Code                       | When                                                                     |
| -----: | -------------------------- | ------------------------------------------------------------------------ |
|    400 | `IDEMPOTENCY_KEY_REQUIRED` | `POST /v1/paper/orders` arrived without an `Idempotency-Key`             |
|    400 | `VALIDATION_ERROR`         | Bad shape or range, an off-tick price, an unknown status, a stale cursor |
|    403 | `PLAN_REQUIRED`            | The account's plan does not include paper trading                        |
|    404 | `MARKET_NOT_FOUND`         | Unknown market, or an outcome the market does not carry                  |
|    404 | `PAPER_ORDER_NOT_FOUND`    | Unknown order id, or one that is not yours                               |
|    409 | `PAPER_MARKET_CLOSED`      | The market is not open, or already resolved                              |
|    409 | `PAPER_INSUFFICIENT_CASH`  | Free cash cannot cover the buy's reservation                             |
|    409 | `PAPER_POSITION_TOO_SMALL` | The sell exceeds the size you hold less your working sells               |
|    409 | `PAPER_WORKING_ORDER_CAP`  | Your plan's open paper orders are all in use                             |
|    409 | `PAPER_NO_LIVE_BOOK`       | No 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.
