Documentation

Take Profit & Stop Loss

A take-profit (TP) and stop-loss (SL) are price levels you attach to an open position. They are not separate orders — just two numbers stored on the position itself. Once the verified oracle mark crosses a level, a keeper closes the whole position at market, and the trigger is re-checked on-chain so it can never fire early.

What take-profit and stop-loss are

A take-profit and a stop-loss are two optional trigger prices attached to a single open position. Each leg is per-position and independent: you can set a TP, an SL, both, or neither. A take-profit is the price at which you want the position closed in profit; a stop-loss is the price at which you want a losing trade cut before it runs further. Neither does anything while the position is open — they are standing instructions that only act once the mark reaches them.

The important thing to understand is that TP and SL are price levels, not orders. On Levant they are stored as two fields directly on your position (takeProfit and stopLoss). Setting them does not create a second position, does not escrow any extra collateral, and does not place a resting order anywhere. This is different from a limit or stop order used to *open* a trade — those escrow collateral on-chain as a pending intent. A TP/SL on a live position is just two numbers the keeper watches.

Note
Think of TP and SL as a note pinned to your position, not as separate trades. They lock no additional funds and add no pending order. A keeper reads the levels off your position and acts only when the oracle mark crosses one.

Setting them with setTpSl

You attach or change TP and SL by calling setTpSl(positionId, takeProfit, stopLoss) on an open position. Only the position owner can call it. Because the levels live on the position itself, they can only be set once the position exists — the open intent has no TP/SL field. In practice that means the moment your open fills: you can fire setTpSl right away, or add and adjust the levels any time the trade is live. A level of 0 disables that leg.

setTpSl(positionId, takeProfit, stopLoss)
  // owner-only; levels are oracle prices (1e18 scale on-chain)
  takeProfit = 0  ->  take-profit OFF
  stopLoss   = 0  ->  stop-loss  OFF
  // every call writes BOTH legs at once
Warning
setTpSl overwrites both legs on every call. To change only your take-profit while keeping your stop-loss, you must re-pass the current stop-loss value — otherwise it is set to whatever you send, and sending 0 clears it. Updating one leg means supplying both.

Which side each level goes

A trigger only makes sense on the correct side of your entry, because a TP closes in profit and an SL closes in loss. For a long, price rising is profit — the TP sits above entry, the SL below. For a short, price falling is profit, so the sides invert: the TP sits below entry, the SL above.

PositionTake-profit (closes in profit)Stop-loss (closes in loss)
LongAbove entry priceBelow entry price
ShortBelow entry priceAbove entry price

On-chain, the keeper fills the position the instant the verified oracle mark satisfies either condition. The comparison is inclusive, and a leg set to 0 never fires:

Long   TP: mark >= takeProfit      SL: mark <= stopLoss
Short  TP: mark <= takeProfit      SL: mark >= stopLoss

mark = verified oracle mid, re-checked on-chain (inclusive)
a leg set to 0 never fires; the fill then adds the close spread
Warning
The contract does not check your levels against your entry or the current price. Set a long's take-profit *below* the current mark by mistake and the condition is already true — the keeper will close you on its next pass. Always confirm each level is on the correct side.

How a trigger fires

The keeper is an off-chain service that watches the verified oracle mark for every position carrying a TP or SL. When the mark crosses one of your levels, it submits a signed price report to executeCloseTrigger, which closes the position. Only the keeper can call executeCloseTrigger — unlike liquidation, which anyone can trigger.

  1. 1You set a TP and/or SL with setTpSl. The levels are stored on your position; no funds move.
  2. 2The keeper watches the verified oracle mark for every position that has a trigger set.
  3. 3The mark crosses your level. The keeper submits a signed oracle price report to executeCloseTrigger.
  4. 4The contract re-checks the trigger against the verified mark. If the mark has genuinely crossed, the whole position closes and settles; if not, the call reverts with TriggerNotHit.

The on-chain re-check is what makes this trust-minimized. The keeper decides *when* to submit, but it cannot close your position early through this path: executeCloseTrigger independently recomputes the condition against the verified mark and reverts TriggerNotHit if it is not met. The price is a signed oracle report the keeper cannot forge, and a position with no TP or SL set at all cannot be trigger-closed. The keeper controls timing only — never the price, and never a close before your level is reached.

The close executes at market

A triggered close settles exactly like a market close of the full position. The trigger is tested on the raw oracle mid, but the fill then applies the deterministic close spread on top — closing a long is sell-side (fills below mid), closing a short is buy-side (fills above mid) — so your realized exit is always a touch past the level itself. The 0.08% close fee (on adjusted size = notional +/- realized PnL) and any accrued funding are settled, and profit is still subject to the 900%-of-margin max-profit cap. There is no slippage cap on a close, unlike on an open.

Warning
A trigger is not a guaranteed price — it decides *when* to close, not the exact price you get. In a fast market the mark can gap straight past your level, and the market close then fills at that gapped mark plus spread and fee, with no slippage guard. Execution also depends on the keeper being online; today it is a single off-chain keeper on testnet. Your backstops are a manual close (closePosition) and permissionless liquidation.

Full close vs scaling out

A TP/SL trigger always closes the entire position — executeCloseTrigger runs the full-close path, and there is no partial take-profit or partial stop on-chain. To scale out at a target instead, use a partial close: call closePartial for a slice (1 to 9,999 bps, i.e. 0.01%–99.99%), which the keeper settles through the same close path. A partial close realizes proportional PnL and fees and releases proportional reserved liquidity, while the remainder keeps its entry price, funding snapshot, leverage, and — importantly — its TP and SL settings.

MechanismClosesTriggered byPrice basis
Take-profit / stop-lossWhole positionKeeper, once the mark crosses your level (re-checked on-chain)Market: mid + close spread, close fee, funding
Manual closeWhole positionYou (closePosition), then keeper fillsMarket: mid + close spread, close fee, funding
Partial closeA slice (0.01%–99.99%)You (closePartial), then keeper fillsMarket on the slice; remainder keeps entry & TP/SL
LiquidationWhole positionAnyone (permissionless), at the 10% maintenance marginRaw mark, no spread; you forfeit all collateral

TP/SL vs liquidation

A stop-loss and a liquidation are not the same thing. Even with no SL set, a position is liquidated automatically once its equity falls to the 10% maintenance margin — a loss of 90% of collateral — at which point anyone can force-close it at the raw mark, you forfeit all collateral, and the liquidator earns a 5% fee. A stop-loss is a softer, trader-chosen exit that sits *above* that liquidation price: a well-placed SL closes you on your own terms, returning your remaining margin, before the position ever reaches the heavier liquidation outcome.

Tip
Place your stop-loss inside the liquidation threshold but outside normal market noise — far enough from entry that routine volatility does not stop you out, yet close enough that you exit voluntarily rather than being force-liquidated at a 90% loss.

Editing and clearing

  • Set — attach a TP and/or SL to any open position you own with setTpSl.
  • Update — move a level at any time; remember every call writes both legs, so re-pass the leg you want to keep.
  • Clear — pass 0 for a leg to disable it and let the position run with no automatic exit on that side.
  • Preserved on partial close — closing part of a position leaves the remainder's TP and SL exactly as they were.
  • No protocol fee to set — attaching or changing TP/SL only costs the gas of the setTpSl transaction; nothing is charged until a close actually settles.