---
title: "Perps and Leverage"
description: "Declare a perps strategy, and understand leveraged margin sizing, liquidation, maker and taker fees, funding and the perps stats. The formulas are the kScript…"
order: 81
section: "strategies"
---

<!-- source: docs/indicators/strategies/perps-and-leverage.md; generated by packages/cli/scripts/gen-indicator-docs.ts, do not edit -->

# Perps and Leverage

Declare a perps strategy, and understand leveraged margin sizing, liquidation, maker and taker fees, funding and the perps stats. The formulas are the kScript (legacy) engine's own, reproduced by the same broker; the five canonical scenarios that pin them ship as ports on the [examples page](examples-perps.md).

## What perps mode changes

Set `instrument: "perps"` when the backtest should use isolated leveraged perpetual accounting instead of spot. Perps mode keeps the same order API and one-net-position model, but it changes how default sizing is interpreted, how entries are admitted, how fees are charged, and how open positions can be liquidated or debited by funding.

```typescript
// Perps margin sizing: percentOfEquity sizes the margin commitment, and notional is that margin times leverage.
import { input, line, ohlcv, output, overlay } from "./sdk/declare";
import { in_close } from "./gen/inputs";
import { emitRow, out_close } from "./gen/outputs";
import { strategy } from "./gen/strategy";

strategy({ initialCapital: 10000, instrument: "perps", leverage: 10, maintenanceMarginPercent: 0.5, qtyType: "percentOfEquity", qtyValue: 10, makerFeePercent: 0.02, takerFeePercent: 0.05, slippageBps: 0, funding: "off" });
input("close", ohlcv.close);
output("close", line, overlay, { description: "Close price" });

let bars: i32 = 0;
let close: f64 = NaN;

export function init(): void {}

export function state(): i32 {
  close = in_close();
  bars += 1;
  return 1;
}

export function finalize(): void {
  if (bars == 1) strategy.long("MarginLong").limit(100.0).send();
  out_close(close);
  emitRow();
}

export function reset(): void {
  bars = 0;
  close = NaN;
}
```

On bars priced near 100, that entry fills at 100 with `qtyType: "percentOfEquity"`, `qtyValue: 10` and `leverage: 10`: the open trade has `qty` 100, `committedMargin` 1000 and `fees` 2, with `makerFeesPaid` 2 and `takerFeesPaid` 0. The 10% sizing setting committed 1000 of margin, not 1000 of notional; the notional was margin times leverage. The `bars` counter is the port of kScript's `barIndex == 0` gate: the file keeps its own row count.

## Margin sizing and admission

For perps, `qtyType: "percentOfEquity"` and `qtyType: "cash"` size the margin commitment. The broker then computes `notional = margin * leverage` and `qty = notional / fillPrice`. `qtyType: "fixed"` and an explicit `.qty(...)` are direct quantities instead, margin-checked as `abs(qty) * fillPrice / leverage`.

Entries are admitted against realized-basis equity: `committedMargin + entryFee <= availableMargin`, where the available margin excludes unrealized PnL from the open position. A winning open position does not become collateral for adding exposure. Reversals stay atomic: the broker simulates the closing leg, then checks whether the new opening leg fits; if it does not, the whole reversal is rejected and the old position remains open.

## Liquidation price

```text
   long 1 unit at 100, leverage 10, maintenance 0.5%

   entry 100 -+------------------------------------
              |  committed margin = 100/10 = 10
              |  the price may fall ~9.55 before
              |  margin (less maintenance) is gone
   P_liq  ----+---- 90.4522...  <- liquidation
              |
              +-- lower leverage pushes this line further away
```

The broker maintains an implicit system liquidation stop for the final open net position. It is not a user order, cannot be canceled, and closes with `exitReason: "liquidation"` when the bar proves or assumes the level traded. For a long the isolated liquidation level is `P_liq = (Q * E - M) / (Q * (1 - m))`; for a short `P_liq = (E + M / Q) / (1 + m)`, where `Q` is the absolute quantity, `E` the average entry, `M` the committed isolated margin and `m` the maintenance margin as a fraction. A single 10x long entered at 100 with 0.5% maintenance liquidates at `90.45226130653266`; the short mirror at `109.45273631840797`.

Entry-bar liquidation is possible, because the market entry fills at the bar's open and the phase-end check sees the same bar's range. When fills on the same bar change the final position, the bar cannot prove whether the adverse extreme came before or after the change; the broker assumes liquidation if the final level is reached, clamps the fill into the bar's traded range, counts it in `ambiguousFillCount` and flags the trade. When price gaps through the level, the fill is the bar price, because that is where the market traded, and the loss beyond committed margin is `stats.bankruptcyDeficit` with equity floored at zero.

## Maker and taker fees

Perps ignores `commissionPercent`; use `makerFeePercent` and `takerFeePercent`. Limit-bound fills pay maker: limit entries, take-profit limit legs, and the flattening leg of a reversal a limit entry triggered. Fills that cross the market pay taker: market entries, stop entries, protective stops, trailing stops, signal exits, `closeAll`, liquidation, and reversal flattening legs a market or stop entry triggered. Declaring the other instrument's fees is not an error; the build warns and the broker ignores them.

## Funding

Funding is a drip against isolated margin, and the liquidation line moves closer with every settlement paid. With `funding: "data"` the broker consumes settlement events from recorded funding rates when a provider is attached; the rate is decimal (1 bp is `0.0001`), positive rates mean longs pay and shorts receive, and `stats.fundingPaid` is signed with positive meaning the strategy paid. A settlement that erodes committed margin to zero or below liquidates the prior position at that bar's open, and later same-bar fills cannot rescue it. This release attaches no provider on any host: `funding: "data"` counts every unsettled open bar in `fundingUnavailableCount` and charges nothing; `funding: "off"` is an exact no-op. Partial coverage is counted, never charged.

## Perps stats

The output always carries the perps stats, zero on spot: `makerFeesPaid` and `takerFeesPaid` by fill class; `fundingPaid` signed; `fundingEventsApplied`; `fundingUnavailableCount` (bars with an open position and no settlement data); `liquidationCount`; `liquidationHalted`; `bankruptcyDeficit` (how far equity would have gone below zero before the floor). The [stats reference](stats-reference.md) defines each.

## Not bugs

`commissionPercent` does nothing under perps and the warning is expected. Unsettled funding bars are counted in `fundingUnavailableCount`, not charged. Liquidation uses the bar's traded prices, not a mark price the broker does not have. Spot economics are unchanged unless you opt into `instrument: "perps"`.
