Trading

Paper Trading

Simulated trading against live market data, for user-facing paper portfolios, testing, and QA.

Two paths. They are not interchangeable.

PathWho calls itAuth
mode: "paper" on the managed quote and the fillYour app, with the user's sessionx-app-id and Authorization: Bearer. No API key.
Paper account adminYour backendx-app-api-key only

Only managed quote and fill accept mode: "paper". Direct orders reject it, and an app in paper trading mode gets 400 with code: "paper_mode_unsupported" from POST /execution/orders. Reads listed below take the same mode so paper rows are not mixed with live ones.

Paper trading prices quotes against the current book and writes simulated balances, orders, and positions. It never submits to a venue and never runs a bridge, custody transfer, deposit, withdrawal, or redemption. Do not show a paper balance as a funded wallet.

The signed-in user's paper account is the one whose externalId is user:<aggUserId>. It is created the first time they request paper balances, positions, a quote, or a fill. Keep paper and live state visually separate.

User quote and fill

A paper quote must be filled with mode: "paper". A live fill cannot execute a paper quote. Switching modes needs a fresh quote.

SDK
// After the user signs in. This client has no apiKey.
const quote = await client.getSmartRoute({
  venueMarketOutcomeId, // id from venueMarketOutcomes[].id
  tradeSide: "buy",
  maxSpend: 25,
  mode: "paper",
});

await client.executeManaged({ quoteId: quote.quoteId, mode: "paper" });

Get a quote and Execute quote. Poll Get execution status with the same mode until terminal is true.

Pass mode: "paper" on these reads as well:

SDK methodAPI reference
getManagedBalances({ mode: "paper" })Get balances
getExecutionStatus({ quoteId, mode: "paper" })Get execution status
getExecutionPositions({ mode: "paper" })Get user positions
getExecutionOrders({ mode: "paper" })Get user orders

POST /execution/orders is live only. It has no mode field and rejects one with 400.

UI components

Set paper mode once on AggProvider. HomePage, EventMarketPage, PlaceOrder, and UserProfilePage read it when no executionMode prop is passed.

import { AggProvider } from "@agg-build/ui";
import { QueryClient, QueryClientProvider } from "@agg-build/hooks";
import { HomePage } from "@agg-build/ui/pages";

const queryClient = new QueryClient();

export function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <AggProvider client={client} config={{ trading: { executionMode: "paper" } }}>
        <HomePage />
      </AggProvider>
    </QueryClientProvider>
  );
}

Pass executionMode on one component when it must differ from the provider. Hide deposit, withdrawal, and redemption controls in a paper-only experience.

Admin accounts

These methods are on AggClient, not a separate admin client. Configure apiKey on a server-side client. Never call them from the browser. The route authorizes x-app-api-key only. Do not send a user bearer token.

The signed-in user sees the account whose externalId is user:<aggUserId>. externalId is unique per app. Creating one that already exists returns a validation error. Look it up with List paper trading accounts instead of creating it again.

SDK
// Server-side only. { appId } selects the account path.
const agg = createAggClient({
  baseUrl: process.env.AGG_API_BASE_URL!,
  appId: "your-app-id",
  apiKey: process.env.AGG_APP_API_KEY!,
});

await agg.createPaperTradingAccount(
  {
    externalId: `user:${aggUserId}`,
    initialBalanceRaw: "100000000", // 6-decimal USD. 1000000 = $1
  },
  { appId: "your-app-id" },
);

Create a paper trading account.

Additional paper-account operations:

SDK methodAPI reference
listPaperTradingAccountsList paper trading accounts
getPaperTradingAccountGet a paper trading account
setPaperTradingBalanceSet a paper trading account cash balance
resetPaperTradingAccountReset a paper trading account
placePaperTradingOrderPlace a paper trading order
listPaperTradingOrdersList paper trading orders
listPaperTradingPositionsList paper trading positions
getPaperTradingPortfolioGet a paper trading portfolio

resetPaperTradingAccount clears open positions and sets cash. Order history survives a reset. setPaperTradingBalance changes cash only. Amounts are 6-decimal integer strings. initialBalance, balance, and spend also accept numbers. Prefer the raw string for a repeatable fixture.

On placePaperTradingOrder, clientOrderId is stored for traceability only. It is not an idempotency key. The order prices against the current book and comes back filled or rejected.

Testing

Sign in a test user, read paper balances and check PAPER_USD is present, quote an open market with mode: "paper", fill it, and poll to a terminal state. Then read paper orders and positions.

Negative paths: fill a paper quote without mode: "paper" and expect a rejection. Switch from paper to live only with a fresh quote. Paper pages show no deposit, withdrawal, bridge, or redemption.

For a repeatable run, reset the user:<aggUserId> account first, pick outcomes from current discovery, and use a scenario-owned clientOrderId on backend orders so logs map fixtures to orders.

On this page