PearPear

Time-Weighted Average Price

Spread a basket entry into timed chunks.

A TWAP schedule splits the basket into evenly spaced slices over a window, which reduces market impact compared with one market order.

Create one with POST /schedules (SDK: sdk.core.schedules.create).

Open over time

{
  "strategy": "TWAP",
  "intent": "OPEN",
  "executionType": "MARKET",
  "startAt": "2026-01-01T00:00:00.000Z",
  "endAt": "2026-01-01T01:00:00.000Z",
  "sliceIntervalSec": 300,
  "targetNotional": "5000",
  "legs": [
    { "symbol": "0", "side": "BUY",  "weight": 0.5 },
    { "symbol": "1", "side": "SELL", "weight": 0.5 }
  ],
  "clientId": "your-client-id"
}

That works 5,000 USD into a long BTC / short ETH basket over one hour, in a slice every five minutes.

A TWAP sizes legs differently from a market order. You give one targetNotional for the whole basket, and each leg takes a weight of it. Leg weights must sum to exactly 1. A market order sizes each leg on its own, with mode and amount.

FieldRule
startAt, endAtISO-8601 timestamps. startAt must be before endAt, and the window must be at least 15 seconds.
sliceIntervalSecInteger, 15 seconds to 31 days (2,678,400 seconds).
targetNotionalUnsigned decimal string, greater than zero.
legs[].weightShare of targetNotional. All weights sum to exactly 1.

How many slices you get

sliceIntervalSec alone does not decide the slice count. The last 15 seconds of the window are reserved as delivery headroom for the final slice, and the first slice starts at startAt:

sliceCount = max(1, floor((windowSeconds - 15) / sliceIntervalSec) + 1)

The example above is a one-hour window at 300-second slices, so it runs 12 slices, not 13. An interval longer than the window collapses to a single slice.

The response echoes the computed sliceCount, so you do not have to derive it yourself.

Close over time

Set intent: "CLOSE" and give a positionId instead of legs and a notional. The schedule unwinds the whole position across the window:

{
  "strategy": "TWAP",
  "intent": "CLOSE",
  "executionType": "MARKET",
  "positionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "startAt": "2026-01-01T00:00:00.000Z",
  "endAt": "2026-01-01T01:00:00.000Z",
  "sliceIntervalSec": 300
}

Hide the pattern

Evenly spaced, equally sized slices are easy for others to read. randomization jitters each slice, in basis points of the interval and of the slice size:

{ "randomization": { "timingJitterBps": 500, "sizeJitterBps": 300 } }

Both fields accept integers from 0 to 5000 bps, and both default to 0. randomization works on CLOSE schedules as well as OPEN ones.

What gets rejected

Two rules reject a schedule at creation time, on both OPEN and CLOSE. Neither is visible from the field tables above.

RuleStatus
One active schedule per position key. A second POST /schedules for the same instruments on the same trade account is refused while the first is still active, whether it opens or closes. Cancel the first, or wait for it to finish.409
Slices must clear the venue minimum. Pear shrinks a slice by the largest downward sizeJitterBps swing and checks the result against every leg's minimum order notional. If a slice could fall below it, the whole plan is refused rather than stalling part-way through the window. Fix it with fewer slices, less size jitter, or a larger targetNotional.400

Response

200 OK, not 202. The body carries the created schedule:

{
  "schedule": {
    "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "connector": "hyperliquid",
    "strategy": "TWAP",
    "status": "ACTIVE",
    "intent": "OPEN",
    "positionKey": "9f2a6c1b4e07d38a5c91f0b2d47e63a8c05b9142ef7d3608ba1c4f52d9e07a63",
    "executionType": "MARKET",
    "startAt": "2026-01-01T00:00:00.000Z",
    "endAt": "2026-01-01T01:00:00.000Z",
    "sliceIntervalSec": 300,
    "sliceCount": 12,
    "randomization": { "timingJitterBps": 0, "sizeJitterBps": 0 },
    "legs": [
      { "symbol": "0", "side": "BUY",  "weight": 0.5 },
      { "symbol": "1", "side": "SELL", "weight": 0.5 }
    ],
    "targetNotional": "5000",
    "remainingNotional": "5000",
    "createdAt": "2026-01-01T00:00:00.000Z",
    "updatedAt": "2026-01-01T00:00:00.000Z"
  }
}
FieldMeaning
idSchedule UUID. Cancel the schedule with it.
statusACTIVE on creation, then COMPLETED, FAILED, or CANCELLED.
sliceCountHow many slices the window holds. Computed by Pear, never sent by you.
remainingNotionalStarts equal to targetNotional and falls as slices fill.
positionKeyThe identity of the instrument set this schedule builds. The one-active-schedule rule is keyed on it. See Position Key.

Manage schedules

Cancelling stops the pending slices. Slices that already filled stay as part of the position. Progress streams over the WebSocket as schedule.created, a per-slice execution, then schedule.completed.

On this page