# Trading

> Place and cancel orders, set expiration, and arm cancel-on-disconnect.

Source: https://docs.stxapp.io/sdks/typescript/trading/

Placing and cancelling needs a `read_write` key. The examples run on the US demo exchange with 1-cent buy orders that will not fill. How orders behave on the exchange is covered in [Order types](/concepts/order-types/) and [Risk controls](/risk-controls/); this page shows the SDK calls.

The examples use a market that is open, accepting orders (`trading` is `true`), and whose event has not started:

```ts
import { STX } from "@stxapp/stx-typescript";

const client = new STX();

let market;
for await (const m of client.iterMarkets({ trading: true, sortBy: "event_start", sortDirection: "desc" })) {
  if (m.event_status === "scheduled") {
    market = m;
    break;
  }
}
if (!market?.market_id) throw new Error("no tradeable market");
const marketId = market.market_id;
```

## Place an order

```ts
const order = await client.placeOrder(marketId, "buy", "limit", {
  price: "0.01",
  quantity: "1",
  clientOrderId: "my-order-1",
});
console.log(order.id, order.status);

await client.cancelOrder(order.id!);
```

`placeOrder(marketId, action, orderType, options)` takes `action` `"buy"` or `"sell"` and `orderType` `"limit"` or `"market"`. Options:

| Option | Type |
|---|---|
| `price` | Dollar string, e.g. `"0.56"`. Required for `"limit"`, omitted for `"market"` |
| `quantity` | Contract string, e.g. `"2"` |
| `clientOrderId` | Your own id, echoed back and filterable |
| `expiration`, `expirationTime` | See [Expiration](#expiration) |
| `cancelOnDisconnect` | See [Cancel-on-disconnect](#cancel-on-disconnect) |
| `deviceId` | Optional label |
| `geoLocation` | See [Geo-fencing](#geo-fencing) |

If the exchange refuses the order, the call throws with the exchange's message:

```ts
import { STXRejectedException, STXValidationException } from "@stxapp/stx-typescript";

try {
  await client.placeOrder(marketId, "buy", "limit", { price: "0.01", quantity: "1.5" });
} catch (err) {
  if (err instanceof STXRejectedException || err instanceof STXValidationException) {
    console.error(err.statusCode, err.message);
  } else {
    throw err;
  }
}
```

A market order leaves out `price`:

```ts
await client.placeOrder(marketId, "buy", "market", { quantity: "1" });
```

## Place several orders

```ts
const results = await client.placeOrders([
  { marketId, action: "buy", orderType: "limit", price: "0.01", quantity: "1" },
  { marketId, action: "buy", orderType: "limit", price: "0.02", quantity: "1" },
]);
for (const r of results) {
  console.log(r.ok ? r.order!.id : r.errors);
}
```

Each result holds either `order` or `errors`, in the order you sent them. One rejection does not stop the rest.

## Cancel

```ts
await client.cancelOrder(orderId);            // one
await client.cancelOrders([idA, idB]);        // several
await client.cancelAllOrders();               // everything on the account
```

Each returns cancellations with `order_id` and `status`. An order can still fill while a cancel is in flight, so reconcile against fills.

## Read orders

```ts
for await (const o of client.iterOrders({ status: ["open", "delayed"] })) {
  console.log(o.id, o.price, o.filled, "/", o.quantity);
}
const one = await client.order(orderId);
```

`orders()` filters on `orderIds`, `clientOrderIds`, `marketIds` and `status`.

## Avoid duplicate orders

The client never resends a `POST` after a server error or a dropped connection, because the order may already be on the book. Send a `clientOrderId` and look it up before trying again:

```ts
import { randomUUID } from "node:crypto";
import { STXServerException, STXTransportException } from "@stxapp/stx-typescript";

const clientOrderId = randomUUID();
let order;
try {
  order = await client.placeOrder(marketId, "buy", "limit", { price: "0.01", quantity: "1", clientOrderId });
} catch (err) {
  if (!(err instanceof STXServerException || err instanceof STXTransportException)) throw err;
  order = (await client.orders({ clientOrderIds: [clientOrderId] })).items[0];
}
```

## Expiration

The two expiration modes are described in [Risk controls](/risk-controls/#order-expiration). In the SDK:

```ts
await client.placeOrder(marketId, "buy", "limit", { price: "0.01", quantity: "1", expiration: "good_till_start" });

await client.placeOrder(marketId, "buy", "limit", {
  price: "0.01",
  quantity: "1",
  expiration: "good_till_time",
  expirationTime: (Date.now() + 10 * 60_000) * 1000, // microseconds
});
```

:::caution
`expirationTime` is Unix time in **microseconds**. `Date.now()` returns milliseconds, so multiply it by 1000.
:::

## Cancel-on-disconnect

How cancel-on-disconnect works is described in [Risk controls](/risk-controls/#cancel-orders-on-disconnect). In the SDK, arm it on the `orders` channel and opt each order in. The client then keeps pinging the channel for you while the socket is up.

```ts
const ws = await client.websocket().connect();
const orders = await ws.orders({ cancelOnDisconnect: true, pingTimeout: 5000 });
console.log("granted timeout (ms):", orders.reply.ping_timeout);

await client.placeOrder(marketId, "buy", "limit", { price: "0.01", quantity: "1", cancelOnDisconnect: true });
```

## Geo-fencing

STX Ontario checks where each order write comes from. A server at a fixed IP address needs that address whitelisted on your account. Otherwise, give the client a geolocation packet, or a function that returns a fresh one, and it is attached to every order write:

```ts
const client = new STX({ geoLocation: () => getLatestPacket() });
```

Check before trading, without placing anything (needs a `read_write` key; call it once at start-up):

```ts
const check = await client.geoCheck();
if (!check.allowed) console.error(check.reason, check.message);
```

A refused write throws `STXGeoLocationException` with a `reason`.
