# Test orders

> Rehearse your bot against the live market and your real accounts without placing anything.

Add `"test": true` to an open, an exit, a close or a cancel. The order is checked and priced exactly like the real one, at that moment: same request checks, same market hours, same live price, same size limits, same buying power, same account and rules. Then it isn't placed.

You get back what a real order returns, with `"test": true` on it and `meta.test: true`. If the real order would've been refused, the test is refused with the same status and the same `code`. So a test order answers one question: **would this have worked right now, and at what price?**

`GET /api/nexus/v1/trading/capabilities` says `"testOrders": true` when test orders are on.

## What it never touches

A test order is never a trade and never an order on an account. It doesn't move your paper wallet, a futures balance or a position. It doesn't reach an evaluation's rules. It doesn't show up in your stats, your rank, the feed, your journal, notifications, Discord or copy trading. It's one line in your test log, and that's the only place it exists.

(One small exception: if you've never opened your futures practice account, a test order that names it sets up the empty account for you. No balance moves and nothing trades.)

## The test log

Your test log keeps your **10 newest test orders**. When the 11th comes in, the oldest drops off.

```bash
# Read the log, newest first
curl -s "https://app.skylit.ai/api/nexus/v1/trades?test=true" \
  -H "Authorization: Bearer $SKYLIT_API_KEY"
```

```json
{
  "data": [
    {
      "id": "9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
      "assetClass": "futures",
      "source": "api",
      "account": { "id": "0b6f3c1e-8a52-4d0e-9c1a-2f4f6f1f7a10", "kind": "practice", "name": "Paper" },
      "symbol": "NQZ26",
      "side": "buy",
      "quantity": 1,
      "role": "entry",
      "orderType": "market",
      "status": "filled",
      "fillPrice": 25311.5,
      "placedAt": "2026-10-09T14:33:02Z",
      "filledAt": "2026-10-09T14:33:02Z",
      "test": true
    }
  ],
  "meta": { "limit": 50, "maxRows": 10, "offset": 0, "test": true, "total": 1 }
}
```

`"status": "filled"` on a test order means "this would have filled at this price". Nothing was booked.

- `status`, `limit` and `offset` work as they do on the normal list. A futures test order is neither open nor closed, so it only shows under `status=all` (the default).
- `GET /api/nexus/v1/trades/{id}` reads one test order by its id.
- `?test=` must be `true` or `false`. Anything else answers `400 bad_request`.

## Rehearse open, trim, close (options and stocks)

An options or stock test trade stays open in the log until you exit it, so your bot can run its whole routine:

```bash
# 1. Test open
curl -s -X POST "https://app.skylit.ai/api/nexus/v1/trades" \
  -H "Authorization: Bearer $SKYLIT_API_KEY" -H "Content-Type: application/json" \
  -d '{"contract":"SPY 600C 10/16","quantity":2,"test":true,"clientOrderId":"t-open-1"}'
# Returns a trade with "test": true. Say its id is TEST_ID.

# 2. Test trim. The id is a test order, so this exit is a test. No "test" field needed.
curl -s -X POST "https://app.skylit.ai/api/nexus/v1/trades/TEST_ID/exits" \
  -H "Authorization: Bearer $SKYLIT_API_KEY" -H "Content-Type: application/json" \
  -d '{"quantity":1,"clientOrderId":"t-trim-1"}'

# 3. Test close
curl -s -X POST "https://app.skylit.ai/api/nexus/v1/trades/TEST_ID/exits" \
  -H "Authorization: Bearer $SKYLIT_API_KEY" -H "Content-Type: application/json" \
  -d '{"closeAll":true,"clientOrderId":"t-close-1"}'
```

> **Warning:** **On an exit, the trade decides, not the request.** An exit on a test order's id is always a test exit, whatever the body says. `"test": true` on an id that isn't one of your test orders answers `404 trade_not_found` and closes nothing, so a bot left in test mode can never sell a real position. `"test": false` on a test order's id answers `400 bad_request`.

## Rehearse futures

```bash
# A test order into the evaluation
curl -s -X POST "https://app.skylit.ai/api/nexus/v1/trades" \
  -H "Authorization: Bearer $SKYLIT_API_KEY" -H "Content-Type: application/json" \
  -d '{"assetClass":"futures","ticker":"NQ","side":"buy","quantity":1,"account":"evaluation","test":true}'

# A test close. It previews closing what the account REALLY holds right now.
curl -s -X POST "https://app.skylit.ai/api/nexus/v1/trading/accounts/evaluation/close" \
  -H "Authorization: Bearer $SKYLIT_API_KEY" -H "Content-Type: application/json" \
  -d '{"ticker":"NQ","test":true}'
```

A test close answers in the close's own shape, with `"test": true` on the response and on each order in `closed`. Nothing is closed and no resting order is cancelled.

## Test orders on the resting-order routes

`"test": true` works on a futures limit order, a stop order and a bracket. The order gets the same checks as a real one, then it's kept in your test log as `working`, with its bracket. Nothing ever fills a test order, so it stays that way.

- **Read it:** `GET .../orders/{orderId}` with a test order's id answers with the test order, marked `"test": true`.
- **Cancel it:** `POST .../orders/{orderId}/cancel` with a test order's id cancels it in the test log. You don't need to send `test`. The id says it's a test.
- **Cancel them all:** `POST .../orders/cancel` with `"test": true` cancels your test orders for that account and nothing else.
- **You can't move it.** `POST .../orders/{orderId}/modify` on a test order answers `400 test_modify_unavailable`. Cancel it and send a new test order at the new price.
- **`"test": true` never touches a real order.** Aim it at a real order's id and you get `404 order_not_found`.

## Its limits

Know these before you trust a green test run:

1. **Tests are checked against your real positions and buying power, not against each other.** Each one is a dry run on the account as it really is.
2. **Options and stocks: one open test position per contract.** While a test trade is open in the log, a second test open on the same contract answers `409 duplicate_position`, like a real one would. You also get it if you really hold that contract.
3. **Futures test orders don't net.** A test sell after a test buy is checked as if the buy never happened. A test close previews closing what you really hold. There's no futures test P/L.
4. **A test on an evaluation or funded account can't run the rule judgment.** A real order that opens exposure there first judges the account's loss rules at the latest price. A test sees the account as it was last judged, and the response says so: `meta.notChecked` contains `"account_rules_judgment"`. Everything else is checked: the contract, the session, the live price, the size cap, the account's status, your own risk rules.
5. **A test market order with a bracket, on an account that already holds that contract,** has `meta.notChecked: ["bracket_netting"]`. A real fill would fold the new stop and target into the ones the position already has. The test shows them as you sent them.
6. **The cap of 50 open API positions applies to test opens too.** It counts your real open API trades.
7. **A test `closeAll` uses one log row per position.** An account holding 4 contracts uses 4 of your 10 rows.
8. **Test ids don't last as long.** A test `clientOrderId` is remembered only while its test order is still in your 10, and 24 hours at most. Test ids never block or replay a real order with the same id.
9. **A test proves "right now".** The price and your account can change a second later. A test passing doesn't promise the real one will.
