Perps trading changes account exposure on an instrument. An order expresses what
the account wants to do, but exposure only changes when the order fills.An accepted order can rest on the book before it fills.Use order state to track accepted, resting, modified, canceled, or rejected
orders. Use fills and portfolio state to confirm any exposure change.
Start by identifying the Perps instrument you want to trade. Instruments define
the market and include the constraints used later when building an order.
TypeScript
Python
API
Fetch instruments and select the market you want to trade.
const instruments = await client.fetchPerpsInstruments();const instrument = instruments.find((item) => item.symbol === "BTC-PERP");if (!instrument) { throw new Error("Instrument not found");}
Keep the selected instrument available for the next steps.
Fetch instruments and select the market you want to trade.
instruments = await client.fetch_perps_instruments()instrument = next((item for item in instruments if item.symbol == "BTC-PERP"), None)if instrument is None: raise RuntimeError("Instrument not found")
Keep the selected instrument available for the next steps.
Fetch instruments and select the market you want to trade.
Before placing an order, decide whether it should open new exposure, increase
existing exposure, reduce exposure, or close the position.The effect of a buy or sell depends on the current position.
Current position
Buy order
Sell order
No position
Opens long
Opens short
Long
Increases long
Reduces or closes long
Short
Reduces or closes short
Increases short
To close a position, submit an order in the opposite direction for the current
open position size.
TypeScript
Python
API
Read the portfolio when the order decision depends on current exposure.
Positive position.size means the account is long. Negative position.size
means the account is short. No matching position means the account has no open
exposure for that instrument.
Read the portfolio when the order decision depends on current exposure.
portfolio = await session.fetch_portfolio()position = next( (item for item in portfolio.positions if item.instrument_id == instrument.id), None,)
Positive position.size means the account is long. Negative position.size
means the account is short. No matching position means the account has no open
exposure for that instrument.
Read the portfolio when the order decision depends on current exposure.
Positive positions[].size means the account is long. Negative
positions[].size means the account is short. No matching position means the
account has no open exposure for that instrument.
Place an order when the account is ready to express buy or sell intent. Use an
explicit limit price when you need price protection. Use immediate-or-cancel
execution when the order should fill immediately or cancel any unfilled quantity.
TypeScript
Python
API
1
Choose a Price
Fetch a ticker when you need a lightweight current-price reference. Fetch the
book when the order price depends on spread, depth, or top-of-book liquidity.
Set price as pUSD per quantity unit, rounded to instrument.priceDecimals.
2
Build the Order
Create the order request from the direction, price, quantity, and execution
behavior you chose. This example submits an immediate-or-cancel (IOC) buy order
for 0.01 BTC-PERP at 65000 pUSD per quantity unit.
For an IOC order, status can be one of these values.
Status
Description
PerpsOrderStatus.Filled
The order fully filled.
PerpsOrderStatus.IocNoFill
The order did not fill.
PerpsOrderStatus.IocExpired
The order filled partially and the remainder canceled.
PerpsOrderStatus.StpCancelled
The order would have matched your own resting order, so it canceled.
1
Choose a Price
Fetch a ticker when you need a lightweight current-price reference. Fetch the
book when the order price depends on spread, depth, or top-of-book liquidity.
Set price as pUSD per quantity unit, rounded to instrument.price_decimals.
2
Build the Order
Create the order request from the direction, price, quantity, and execution
behavior you chose. This example submits an immediate-or-cancel (IOC) buy order
for 0.01 BTC-PERP at 65000 pUSD per quantity unit.
Use tickers for a lightweight current-price reference. Use the book when the
order price depends on spread, depth, or top-of-book liquidity.Set price as pUSD per quantity unit, rounded to the instrument’s
price_decimals.
2
Build the Order Operation
Create a createOrders operation from the direction, price, quantity, and
execution behavior you chose. This example submits an immediate-or-cancel (ioc)
buy order for 0.01 BTC-PERP at 65000 pUSD per quantity unit.
Concept
Field
Notes
Instrument
iid
Instrument identifier.
Direction
buy
true buys, false sells.
Price
p
Price chosen in the first step.
Quantity
qty
Number of quantity units, with no more than quantity_decimals decimal places.
Time in force
tif
Use ioc for this basic order.
Client order ID
c
Optional identifier your integration can use to track or cancel the order. Must be a 32-character lowercase hex value.
Omit undefined array entries from the compact operation, then
MessagePack-encode it and hash the encoded bytes with keccak256.The example below uses @msgpack/msgpack and Viem.
import { encode } from "@msgpack/msgpack";import { keccak256 } from "viem";const signableOperation = [ "createOrders", [ [ 1, true, "65000", "0.01", "ioc", false, undefined, "7f9e4a2b6c8d0e1f1234567890abcdef", undefined, ].filter((value) => value !== undefined), ],] as const;const opHash = keccak256(encode(signableOperation));
Use take-profit and stop-loss (TP/SL) orders to place conditional exits for a
position. A take-profit order exits when price moves in your favor. A stop-loss
order exits when price moves against you.
TP/SL orders watch the mark price, not the last traded price. A trigger fires
when the mark price touches the trigger price, then submits a reduce-only order to
close exposure.
Position
Take-profit fires when
Stop-loss fires when
Long
Mark rises to trigger
Mark falls to trigger
Short
Mark falls to trigger
Mark rises to trigger
For a long position, take-profit triggers usually sit above the current mark and
stop-loss triggers usually sit below it. For a short position, the directions are
reversed.
A bracket order submits one entry order with up to one take-profit and one
stop-loss trigger. The triggers stay dormant until the entry fills in full. If
the entry is canceled, rejected, or only partially filled, the triggers are
canceled too. A bracket protects the completed entry, not a partial fill.
You cannot attach TP/SL triggers to an order that is already resting on the
book. To protect a position from an existing order, wait for the fill and then
protect the position.
TypeScript
Python
API
Place a GTC entry order with take-profit and stop-loss triggers.
Use the same signing flow as Place Orders. For hashing, sign the
compact operation, not the structured JSON body. The TP/SL group is the third
compact element:
Add TP/SL orders to a position that is already open. Position TP/SL closes the
full position at trigger time, so it does not use a fixed quantity.Position TP/SL has a few placement rules:
The account must already hold a position in the instrument.
The account can have at most one position take-profit and one position stop-loss per instrument.
When the trigger fires, the exit submits immediately. Position TP/SL does not support limit prices.
A position trigger is rejected if the current mark has already crossed it, because it would fire immediately.
place_position_tp_sl sizes the exit at trigger time, so you do not pass a
position side or quantity.
Submit position-scoped TP/SL orders with grp set to "position". Use
qty: "0" so the engine sizes the exit at trigger time.Set buy to the side that closes the position: false for a long position and
true for a short position.
After the order is accepted, read order state from GET /v1/account/orders or
the private orders WebSocket channel. For a gtc order, status can be one
of these values.
Status
Meaning
open
The order is resting on the book.
filled
The order fully filled before resting.
stp_cancelled
The order would have matched your own resting order, so it canceled.
If a gtc order partially fills and rests, inspect filled_quantity and
resting_quantity.
A post-only order is a special type of good-til-canceled order that only adds
liquidity. If it would cross the book and take liquidity, it is rejected instead.
Cancel resting orders when they are stale, conflict with updated strategy, or
should no longer remain on the book. Canceling a resting order does not close any
filled position.
TypeScript
Python
API
Cancel stale orders by order ID or by client order ID.
const result = await session.cancelOrder({ orderId: order.id,});
Use result.status to confirm whether the cancel request was accepted.
Status
Meaning
ok
The cancel command was accepted.
err
The cancel command was not accepted.
Canceling a working order removes it from the book.
Cancel stale orders by order ID or by client order ID.
result = await session.cancel_order(order_id=order.id)# result: PerpsCancelOrderResult
Use result.status to confirm whether the cancel request was accepted.
Status
Meaning
ok
The cancel command was accepted.
err
The cancel command was not accepted.
Canceling a working order removes it from the book.
Closing a position is a new order in the opposite direction of the open position.
Use the full open position size to close. Use a smaller quantity to reduce
instead. Mark close orders reduce-only so they cannot flip the account into new
exposure if position state changes before execution.Reduce-only is a safeguard, not a sizing shortcut. You still choose the quantity:
use the full position size to close or a smaller quantity to reduce. If the order
would increase exposure, flip the position, or exceed the remaining closeable
size, it is rejected.
Current Position
Reduce-Only Buy
Reduce-Only Sell
Long
Rejected
Reduces or closes
Short
Reduces or closes
Rejected
No position
Rejected
Rejected
The remaining closeable size accounts for other resting reduce-only orders on
the same instrument.
TypeScript
Python
API
Find the current position by market symbol and parse its decimal size.
import Big from "big.js";const portfolio = await session.fetchPortfolio();const position = portfolio.positions.find((item) => item.symbol === "BTC-PERP");if (!position) { throw new Error("No open BTC-PERP position");}const size = new Big(position.size);
Close a long position by selling the full size, or close a short position by
buying the absolute position size.
import { OrderSide, PerpsOrderStatus, PerpsTimeInForce,} from "@polymarket/client";if (size.gt(0)) { const order = await session.placeOrder({ instrumentId: instrument.id, side: OrderSide.SELL, quantity: position.size, timeInForce: PerpsTimeInForce.IOC, reduceOnly: true, }); if (order.status === PerpsOrderStatus.Filled) { // The position is fully closed. }}
These examples use an IOC order with no price and only treat the close as
complete when order.status is PerpsOrderStatus.Filled. Include a price when
you want to limit execution, or use FOK or GTC when the close should follow a
different order behavior.For reduce-only orders, also handle these statuses.
Status
Meaning
PerpsOrderStatus.ReduceOnlyInvalid
The order would not reduce the remaining closeable position.
PerpsOrderStatus.ReduceOnlyExpired
A resting reduce-only order was canceled after the position fully closed.
Find the current position by market symbol and inspect its decimal size.
portfolio = await session.fetch_portfolio()position = next((item for item in portfolio.positions if item.symbol == "BTC-PERP"), None)if position is None: raise RuntimeError("No open BTC-PERP position")# position: PerpsPosition# position.size: Decimal
Close a long position by selling the full size, or close a short position by
buying the absolute position size.
if position.size > 0: result = await session.place_order( instrument_id=instrument.id, side="SELL", quantity=position.size, time_in_force="ioc", reduce_only=True, ) if result.order.status == "filled": # The position is fully closed. pass
These examples use an IOC order with no price and only treat the close as
complete when result.order.status is filled. Include a price when you want to
limit execution, or use FOK or GTC when the close should follow a different order
behavior.For reduce-only orders, also handle these statuses.
Status
Meaning
reduce_only_invalid
The order would not reduce the remaining closeable position.
reduce_only_expired
A resting reduce-only order was canceled after the position fully closed.
Fetch the portfolio and inspect the position size for the instrument.
Leverage controls how much notional exposure a position can carry relative to its
margin. Cross margin uses available account collateral for the position;
non-cross margin keeps the position isolated. Validate leverage against
instrument.maxLeverage and the current instrument.riskTiers before submitting
the change.
TypeScript
Python
API
1
Read Current Configuration
Read the current account configuration before changing leverage.
Keep local trading state in sync by starting from a local snapshot, applying
live updates, and reconciling again whenever the session tells you state may have
been missed.TP/SL orders appear alongside regular orders in open-order and order-history
reads. Track their lifecycle in addition to the position and fills they protect.
Lifecycle State
Meaning
Dormant
Waiting for a bracket entry order to fill in full.
Armed
Watching the mark price for the trigger.
Triggered
Fired and submitted the closing order.
Canceled
Removed by you, by parent cancellation, or by auto-cancel.
Cleared
Removed because the protected position closed or changed sides.
Then use the authenticated WebSocket session for live updates. Private WebSocket
frames can include balances, orders, fills, portfolio, and TP/SL updates. See
Authenticated Sessions for the session
lifecycle.
Track the sq sequence number for each channel. If a sequence number is skipped
or the connection reconnects, repeat the account reads above before relying on
local state.
Each fee entry contains the market category and its maker and taker fee rates.
type PerpsFeeScheduleEntry = { category: PerpsInstrumentCategory; makerFeeRate: DecimalString; takerFeeRate: DecimalString;};
makerFeeRate applies when an order adds liquidity, such as a resting or
post-only order. takerFeeRate applies when an order removes liquidity, such as
an immediately filled IOC order.
Each fee entry contains the market category and its maker and taker fee rates.maker_fee_rate applies when an order adds liquidity, such as a resting or
post-only order. taker_fee_rate applies when an order removes liquidity, such
as an immediately filled IOC order.
maker_fee_rate applies when an order adds liquidity, such as a resting or
post-only order. taker_fee_rate applies when an order removes liquidity, such
as an immediately filled ioc order.