# Fills and trade history

Read your executions, reconcile order totals and fees, and backfill a durable trading history.

A fill is one execution of an order. An order can fill several times, at different prices and with different liquidity roles. Keep individual fills for inventory and realised P\&L, and use order totals to reconcile them.

These reads are available on Testnet at `https://api.testnet.polyester.com`. Mainnet is coming soon.

## Read your fills

Call [`GetUserTrades`](https://testnet.polyester.com/docs/api-docs/connect/orders.v1.OrdersReadService/GetUserTrades) or [`GET /v1/spot/trades`](https://testnet.polyester.com/docs/api-docs/rest/GET/v1/spot/trades) for the selected account's fills. Keep the same Main Account or Subaccount scope throughout a backfill. These are your executions, not the public market's trade tape.

Filter by `orderId` for one physical order, or `lineageId` for the logical order across accepted replacements. The filters are mutually exclusive. `throughGeneration` limits a lineage query to an inclusive generation; it does not freeze a generation that is still filling. See [Order types and lifecycle](https://testnet.polyester.com/docs/developer-docs/shared-concepts/order-types-and-lifecycle).

For one order, [`GetOrder`](https://testnet.polyester.com/docs/api-docs/connect/orders.v1.OrdersReadService/GetOrder) ([`GET /v1/spot/orders/{order_id}`](https://testnet.polyester.com/docs/api-docs/rest/GET/v1/spot/orders/order_id)) includes a bounded page of lineage executions through that order's generation by default. Follow its execution cursor using the same physical `orderId`, rather than a client ID that could later be reused.

### Maker, taker, and fees

`isMaker` is `true` when that fill provided resting liquidity, and `false` when it took liquidity. Read the role per fill: a limit order can take liquidity on arrival and provide liquidity later. Use the recorded fee for accounting, rather than recalculating old fills from today's [fee rates and VIP tier](https://testnet.polyester.com/docs/developer-docs/account/fees-and-vip).

ConnectRPC's `fee_amount_e18` is an unsigned fee magnitude in fixed 18-decimal units of `fee_asset`. Reconstruct its U128 integer, then divide by `10^18`. The Pair's quantity scale does not change this fee scale. REST already returns `fee` as a decimal string in Asset units; do not divide it again.

`feeIsRebate` (`fee_is_rebate` in Protobuf) means the magnitude was credited. For arithmetic, treat a charged fee as positive and a rebate as negative. Resolve `BASE` or `QUOTE` against the Pair's Assets, and keep totals in their actual Assets. See [Fee Assets](https://testnet.polyester.com/docs/developer-docs/shared-concepts/fee-assets) and [Scaled integers](https://testnet.polyester.com/docs/developer-docs/connectrpc/scaled-integers).

## Reconcile order totals

[Order history](https://testnet.polyester.com/docs/api-docs/rest/GET/v1/orders/history) and single-order reads with execution history include the net fee total. Open-order reads and private order updates omit it.

| REST value | Use                                                                                   |
| ---------- | ------------------------------------------------------------------------------------- |
| `cumQuote` | Exact executed quote amount before fees, across the lineage through this generation.  |
| `avgPx`    | Average fill price, rounded half to even at 18 decimal places; `"0"` before any fill. |
| `feeTotal` | Net fee magnitude, rebate direction, and the filled quantity its total covers.        |

Use `cumQuote` for notional instead of multiplying the rounded `avgPx` by `cumQty`. ConnectRPC names these amounts `cum_notional` and `avg_price_e18`; their scales are explained in [Trading quantities](https://testnet.polyester.com/docs/developer-docs/connectrpc/scaled-integers#trading-quantities).

A fill settles its quote amount rounded down to 18 decimal places. When the Pair's base quantity scale exceeds 9, `cumQuote` can exceed the settled quote by less than `10^-18` quote units per fill. Use settlement transfers when reconciling exact balance movements. Request them with `includeTransfers` on user trades; deduplicate repeated legs by `txId` within the selected account.

### Wait for history to catch up

An order shows an execution before its trade history necessarily does. In `GetOrder`, `executionsComplete` is true when trades across all execution pages account for the order's `cumQty` through its generation. An empty `nextPageToken` alone means no more rows are visible now, not that settlement or execution recording has finished.

Fees can lag too. Wait until `feeTotal.qty` equals `cumQty` before treating the total as covering every fill. Until then, refetch later. Settlement transfers can appear after the trades they settle. A terminal order status does not remove these delays.

Replacement totals include earlier generations. Do not add predecessor and successor `cumQuote` or `feeTotal` values together. For fees per physical order, sum its distinct fills. For fees per logical order, sum distinct lineage fills or use the latest generation's complete total. See [Order sizing](https://testnet.polyester.com/docs/developer-docs/shared-concepts/order-sizing) for quote-budget reconciliation.

## Pagination and backfill

User trades are ordered by timestamp, symbol, match, and physical order, descending. Order history is also newest first. Both accept a page limit from 1 to 1000, default 100. The API reference owns the complete filter schema.

1. Subscribe before reading history

   Start buffering [user trade executions](https://testnet.polyester.com/docs/api-docs/streams/private-spot-trades), then request history. For reconnect handling and the separate private order socket, follow [Recover after reconnect](https://testnet.polyester.com/docs/developer-docs/shared-concepts/recover-after-reconnect).

2. Read an overlapping range

   REST uses `startTime` and `endTime`; ConnectRPC uses `start_ts_ns` and `end_ts_ns`. Both bounds are inclusive. Start at or before your last reconciled time and fix the range while paging. Replay overlapping ranges to catch late-recorded fills; do not checkpoint solely from the newest live fill.

   For a per-symbol catch-up, `afterMatchId` (`after_match_id`) returns durable fills with a strictly greater match ID and requires the symbol filter. Use a previously reconciled checkpoint, not an ID received only on the live stream. Match IDs are monotonic per symbol, not across the account.

3. Follow every cursor

   Pass `nextPageToken` as `pageToken` until empty, keeping the account, scope, filters, and time range unchanged. Cursors are opaque and exclusive. Continue even after a short or empty page when a token remains. Start a new query when changing the range.

4. Upsert and checkpoint together

   Key fills by account, symbol, `matchId`, and `orderId`; ConnectRPC uses the corresponding numeric IDs. An already stored identity replaces its earlier copy. Never count a replay as another fill. Match ID alone is insufficient, including when the same account has both sides of a match.

   Merge buffered publications in array order, then publication order. Persist the resulting data and recovery checkpoints together. Different symbols have no defined relative event order.

## Compute realised P\&L and fees

The following is a client accounting method for spot inventory. An opening BUY acquires inventory; its cash debit alone is not a realised loss. Choose a cost-basis method, such as FIFO or weighted average, and apply it consistently across orders.

For each distinct fill, let `B` be gross base quantity, `Q` its settled quote amount, and `F_base` and `F_quote` its signed fee in the corresponding Asset. The fee in the other Asset is zero.

| Side | Inventory change      | Quote cash flow  |
| ---- | --------------------- | ---------------- |
| BUY  | `B - F_base` received | `-(Q + F_quote)` |
| SELL | `B` delivered         | `Q - F_quote`    |

A BUY paying in base therefore has less inventory over which to allocate its quote cost. Rebates reverse the fee's effect. SELL fees are in quote. Allocate acquisition fees into inventory cost once, then calculate realised P\&L as net sale proceeds less the cost basis of the inventory sold. Do not subtract acquisition fees again on disposal.

Group these results by the executing `orderId`, or by lineage for a logical-order report. Sum signed fees separately per Asset. A multi-Pair P\&L report needs an explicit valuation currency and conversion policy; raw fees in different Assets cannot be added. If acquired inventory or its cost basis is missing, keep that P\&L unresolved rather than assigning a zero cost.

Use exact integer or decimal arithmetic with enough precision for intermediate products. Keep raw fill records and settlement legs alongside derived reports so you can recompute them after a backfill.

## Exports and history limits

Build your export from paginated fills and, when needed, order history and settlement transfers. Preserve exact decimal strings, identities, account scope, execution times, and fee Assets in CSV or another durable format. The public trading contracts do not specify a CSV export operation.

Page size is not a retention guarantee. The public contracts do not specify how far back fill and order history remains available, or a maximum query time span. Archive continuously rather than depending on an unlimited future backfill.

For immediate execution notifications, use the existing [private stream's execution events](https://testnet.polyester.com/docs/developer-docs/connectrpc/private-websocket#units). For settled fill details, use the [user trade execution stream](https://testnet.polyester.com/docs/api-docs/streams/private-spot-trades) and the history reads above.
