# Executing trades

This guide covers all order types available on Hyperliquid through the `@nktkas/hyperliquid` SDK with Openfort backend wallets.

## Set up the client

```ts
import Openfort from '@openfort/openfort-node'
import * as hl from '@nktkas/hyperliquid'

const openfort = new Openfort(process.env.OPENFORT_SECRET_KEY!, {
  walletSecret: process.env.OPENFORT_WALLET_SECRET!,
})
const account = await openfort.accounts.evm.backend.create()

const transport = new hl.HttpTransport({ isTestnet: true })
const exchange = new hl.ExchangeClient({ wallet: account, transport })
const info = new hl.InfoClient({ transport })
```

## Market data

Before placing orders, fetch metadata to get correct tick sizes and asset indices:

```ts
const meta = await info.meta()
const ethAsset = meta.universe.find((a) => a.name === 'ETH')

console.log('Asset index:', ethAsset.assetId) // 4
console.log('Tick size:', ethAsset.szDecimals) // price decimal places

// Current mid prices
const allMids = await info.allMids()
console.log('ETH mid:', allMids['ETH'])
```

### Tick size and lot size rules

Hyperliquid enforces strict precision on prices and sizes:

| Rule | Constraint |
|------|-----------|
| **Price precision** | Up to 5 significant figures |
| **Max price decimals** | 6 for perpetuals, 8 for spot |
| **Size precision** | Rounded to `szDecimals` per asset |

Use asset metadata to validate before submitting orders:

```ts
const meta = await info.meta()
const ethAsset = meta.universe.find((a) => a.name === 'ETH')!

function formatPrice(price: number): string {
  // Round to 5 significant figures
  const rounded = Number.parseFloat(price.toPrecision(5))
  // Ensure no more than MAX_DECIMALS decimal places
  const maxDecimals = 6 // perpetuals
  return rounded.toFixed(Math.min(maxDecimals, 5))
}

function formatSize(size: number, szDecimals: number): string {
  return size.toFixed(szDecimals)
}
```

:::warning
Always use tick-size-compliant prices. The exchange rejects orders with prices that don't match the tick size.
:::

## Market orders (IOC)

A market order uses **Immediate-or-Cancel (IOC)** time-in-force. Set the price with enough slippage to fill immediately:

```ts
const allMids = await info.allMids()
const ethMid = Number.parseFloat(allMids['ETH'])

// Market buy with 1% slippage
await exchange.order({
  orders: [
    {
      a: 4,
      b: true,
      p: (ethMid * 1.01).toFixed(1),
      s: '0.01',
      r: false,
      t: { limit: { tif: 'Ioc' } },
    },
  ],
  grouping: 'na',
})
```

## Limit orders (GTC)

Good-til-Canceled orders rest on the book until filled or canceled:

```ts
await exchange.order({
  orders: [
    {
      a: 4,
      b: true,
      p: '2800.0',
      s: '0.1',
      r: false,
      t: { limit: { tif: 'Gtc' } },
    },
  ],
  grouping: 'na',
})
```

### Post-only (ALO)

Add-Liquidity-Only orders are rejected if they would immediately match:

```ts
await exchange.order({
  orders: [
    {
      a: 4,
      b: true,
      p: '2800.0',
      s: '0.1',
      r: false,
      t: { limit: { tif: 'Alo' } },
    },
  ],
  grouping: 'na',
})
```

## Stop-loss and take-profit

Trigger orders execute when the oracle price crosses a threshold:

```ts
// Stop-loss: sell if price drops to 2500
await exchange.order({
  orders: [
    {
      a: 4,
      b: false, // sell
      p: '2490.0', // limit price after trigger
      s: '0.1',
      r: true, // reduce-only
      t: {
        trigger: {
          isMarket: true,
          triggerPx: '2500.0',
          tpsl: 'sl',
        },
      },
    },
  ],
  grouping: 'na',
})

// Take-profit: sell if price rises to 3500
await exchange.order({
  orders: [
    {
      a: 4,
      b: false,
      p: '3510.0',
      s: '0.1',
      r: true,
      t: {
        trigger: {
          isMarket: true,
          triggerPx: '3500.0',
          tpsl: 'tp',
        },
      },
    },
  ],
  grouping: 'na',
})
```

## TWAP orders

Time-Weighted Average Price orders split large trades across a duration:

```ts
await exchange.twapOrder({
  a: 4,
  b: true,
  s: '1.0', // total size
  r: false,
  m: 5, // 5 minutes
  t: true, // randomize sub-order timing
})
```

## Leverage and margin

Set leverage before placing orders:

```ts
// Set 10x cross leverage on ETH
await exchange.updateLeverage({
  asset: 4,
  isCross: true,
  leverage: 10,
})

// Switch to isolated margin
await exchange.updateIsolatedMargin({
  asset: 4,
  isBuy: true,
  ntli: 500, // add 500 USD isolated margin
})
```

## Cancel orders

Cancel individual orders by ID or all orders for an asset:

```ts
// Cancel a specific order
await exchange.cancel({
  cancels: [{ a: 4, o: orderId }],
})

// Cancel all orders for an asset
await exchange.cancelByCloid({
  cancels: [{ asset: 4, cloid: 'my-order-id' }],
})
```

## Monitor positions

Query the clearinghouse state to check positions and open orders:

```ts
const state = await info.clearinghouseState({ user: account.address })

for (const position of state.assetPositions) {
  const pos = position.position
  console.log(
    pos.coin,
    'Size:',
    pos.szi,
    'Entry:',
    pos.entryPx,
    'PnL:',
    pos.unrealizedPnl,
  )
}

// Open orders
const openOrders = await info.openOrders({ user: account.address })
console.log('Open orders:', openOrders.length)
```

## Batch orders

Send multiple orders in a single request:

```ts
await exchange.order({
  orders: [
    {
      a: 4,
      b: true,
      p: '2800.0',
      s: '0.05',
      r: false,
      t: { limit: { tif: 'Gtc' } },
    },
    {
      a: 4,
      b: true,
      p: '2750.0',
      s: '0.05',
      r: false,
      t: { limit: { tif: 'Gtc' } },
    },
    {
      a: 4,
      b: true,
      p: '2700.0',
      s: '0.1',
      r: false,
      t: { limit: { tif: 'Gtc' } },
    },
  ],
  grouping: 'na',
})
```

## Best practices

* **Validate tick and lot sizes** before submitting — the exchange rejects non-compliant orders silently
* **Use reduce-only** (`r: true`) for stop-loss and take-profit orders to avoid accidentally increasing position size
* **Monitor funding rates** — high funding can erode profits on leveraged positions held overnight
* **Implement circuit breakers** — automatically cancel open orders and reduce positions when losses exceed a threshold
* **Batch related orders** — send entry + stop-loss + take-profit in a single `order` call with `grouping: 'normalTpsl'` to ensure they're linked
