What perps mode changes
Set instrument="perps" on strategy() when the backtest should use isolated leveraged perpetual accounting instead of spot accounting. 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.
//@version=2
strategy(title="Perps Margin Declaration", initialCapital=10000, instrument="perps", leverage=10, maintenanceMarginPercent=0.5, qtyType="percentOfEquity", qtyValue=10, makerFeePercent=0.02, takerFeePercent=0.05, slippageBps=0, funding="off")
timeseries bars = ohlcv(symbol=currentSymbol, exchange=currentExchange)
if (barIndex == 0) {
strategy.entry("MarginLong", "long", limit=100)
}
plotLine(value=bars.close, width=1, colors=["#4f8cff"], label=["Close"], desc=["Close price"])That probe enters at 100 with qtyType="percentOfEquity", qtyValue=10, and leverage=10. The output open trade has qty=100, committedMargin=1000, fees=2, stats.makerFeesPaid=2, and stats.takerFeesPaid=0. In other words, the 10% sizing setting committed 1000 margin, not 1000 notional; notional was margin times leverage.
Margin sizing and admission
For perps, qtyType="percentOfEquity" and qtyType="cash" size the margin commitment. The engine then computes notional = margin * leverage and qty = notional / fillPrice. qtyType="fixed" and an explicit strategy.entry(..., qty=...) are direct quantities instead: they are margin-checked as abs(qty) * fillPrice / leverage.
Entries are admitted against realized-basis equity. The check is committedMargin + entryFee <= availableMargin, where available margin excludes unrealized PnL from the open position. A winning open position does not become collateral for adding more exposure. Reversals remain atomic: the broker first simulates the closing leg, then checks whether the new opening leg fits. If it does not fit, the whole reversal is rejected and the old position remains open.
Liquidation price
The geometry in one picture: leverage decides how much price movement your margin can absorb before the exchange closes you.
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 awayPerps mode 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 that the liquidation level traded.
For a long, the isolated liquidation level is:
P_liq = (Q * E - M) / (Q * (1 - m))For a short, it is:
P_liq = (E + M / Q) / (1 + m)Q is absolute quantity, E is average entry, M is committed isolated margin, and m is maintenanceMarginPercent / 100. With a single 10x long entered at 100 and maintenanceMarginPercent=0.5, the liquidation probe exits at 90.45226130653266.
//@version=2
strategy(title="Perps Liquidation Anchor", initialCapital=10000, instrument="perps", leverage=10, maintenanceMarginPercent=0.5, qtyType="fixed", qtyValue=1, slippageBps=0, makerFeePercent=0, takerFeePercent=0, funding="off")
timeseries bars = ohlcv(symbol=currentSymbol, exchange=currentExchange)
if (barIndex == 0) {
strategy.entry("L", "long")
}
plotLine(value=bars.close, width=1, colors=["#dc2626"], label=["Close"], desc=["Close price"])The probe output records one trade with entryPrice=100, exitReason="liquidation", exitPrice=90.45226130653266, liquidationCount=1, and bankruptcyDeficit=0. Entry-bar liquidation is possible because the market entry fills at the bar open and the phase-end liquidation check sees the same bar's range.
The short-side fixture, perps-short-liquidation-bars.json, uses the same entry price, leverage, and maintenance rate. For E=100, M=10, Q=1, and m=0.005, the short formula gives 109.45273631840797; the probe trades through that level, exits there, and records takerFeesPaid=0.05472636815920399 with makerFeesPaid=0. Because the entry is a limit fill with maker fee set to zero, the nonzero taker total comes from the liquidation fill.
When fills on the same bar change the final position, the engine assumes liquidation if that final liquidation level is reached and marks the fill as ambiguous. The perps-ambiguous-liquidation.ks output records ambiguousFillCount=1; its final long P_liq is 122.625, outside the fixture bar's 90 to 120 traded range, so the liquidation fill is clamped to the bar high and recorded at 120.
//@version=2
strategy(title="Perps Short Liquidation Taker", initialCapital=10000, instrument="perps", leverage=10, maintenanceMarginPercent=0.5, qtyType="fixed", qtyValue=1, slippageBps=0, makerFeePercent=0, takerFeePercent=0.05, funding="off")
timeseries bars = ohlcv(symbol=currentSymbol, exchange=currentExchange)
if (barIndex == 0) {
strategy.entry("S", "short", limit=100)
}
plotLine(value=bars.close, width=1, colors=["#be123c"], label=["Close"], desc=["Close price"])Maker and taker fees
Perps ignores commissionPercent; use makerFeePercent and takerFeePercent instead. Limit-bound fills pay maker fees: limit entries, take-profit limit legs, and the flattening leg of a reversal triggered by a limit entry. Fills that cross the market pay taker fees: market entries, stop entries, protective stops, trailing stops, signal exits, closeAll, liquidation, and reversal flattening legs triggered by market or stop entries.
Spot scripts are unchanged. In spot mode, commissionPercent is active and maker/taker params are ignored. In perps mode, nonzero commissionPercent is ignored and the engine emits a warning so the result does not silently double-charge. The warning probes record STRATEGY_PERPS_COMMISSION_IGNORED for perps-commission-warning.ks and STRATEGY_SPOT_MAKER_TAKER_IGNORED for spot-maker-taker-warning.ks.
Funding
Funding is a drip against your isolated margin, and the liquidation line moves closer with every settlement you pay:
committed margin over a held long (positive rates)
10 |#########
9 | ######### each settlement debits
8 | ######### cash AND margin together
7 | #########
0 +--------|--------|--------|--------|---- bars
settle settle settle depleting settle
-> liquidates at
that bar's openWith funding="data" on a perps strategy, the engine consumes settlement events from recorded funding rates from your chart's venue. The settlement rate is decimal, so 1 bp is 0.0001. Positive rates mean longs pay and shorts receive; negative rates reverse that direction. stats.fundingPaid is signed in account currency, with positive meaning the strategy paid funding.
//@version=2
strategy(title="Perps Funding Data", initialCapital=10000, instrument="perps", funding="data", leverage=10, maintenanceMarginPercent=0.5, qtyType="fixed", qtyValue=1, slippageBps=0, makerFeePercent=0, takerFeePercent=0)
timeseries bars = ohlcv(symbol=currentSymbol, exchange=currentExchange)
if (barIndex == 0) {
strategy.entry("L", "long")
}
plotLine(value=strategy.equity(), width=1, colors=["#0f766e"], label=["Equity"], desc=["Strategy equity"])The probe attaches this serializable provider fixture:
{
"events": [
{
"time": 1700009000000,
"rate": 0.01
}
],
"coverage": {
"start": 1700000000000,
"end": 1700010800000
}
}The output records fundingPaid=1, fundingUnavailableCount=1, and an open trade whose committedMargin moved from 10 to 9. Funding settles against isolated margin, so it moves the liquidation price. The short-side direction probe, perps-short-funding.ks, uses the same positive-rate provider on a short position and records fundingPaid=-1, meaning the strategy received funding.
If funding erodes committed margin to zero or below, the bar still has one phase-end liquidation evaluation, but the bar-open settlement forces that outcome: the prior position liquidates at the settlement bar's open, and later same-bar fills cannot rescue it. The depletion probe uses perps-funding-depletion-bars.json and perps-funding-depletion-provider.json, where a 0.1 rate settles at 1700007200000; the recorded trade exits on exitBar=2 at that bar's 100 open with exitReason="liquidation", fundingPaid=10, and liquidationCount=1.
//@version=2
strategy(title="Perps Funding Depletion", initialCapital=10000, instrument="perps", funding="data", leverage=10, maintenanceMarginPercent=0.5, qtyType="fixed", qtyValue=1, slippageBps=0, makerFeePercent=0, takerFeePercent=0)
timeseries bars = ohlcv(symbol=currentSymbol, exchange=currentExchange)
if (barIndex == 0) {
strategy.entry("L", "long")
}
plotLine(value=strategy.equity(), width=1, colors=["#0e7490"], label=["Equity"], desc=["Strategy equity"])Set funding="off" to make funding an exact no-op. Missing or partial funding coverage is counted, not charged. A complete provider range with no events is known zero funding.
Perps stats
The Strategy Tester always emits the perps stats, even for spot runs where they are zero. makerFeesPaid and takerFeesPaid are account-currency fee totals by fill class. fundingPaid is signed account-currency funding, positive when the strategy paid and negative when it received. fundingUnavailableCount counts bars where a perps position was open and funding coverage could not prove the settlement window. liquidationCount counts system liquidation events. bankruptcyDeficit records how far equity would have gone below zero before the engine floored it at zero.
The bankruptcy gap probe uses perps-bankruptcy-gap-bars.json, starts with initialCapital=10, enters one 10x long at 100, then gaps the next bar to 80. It records exitReason="liquidation", exitPrice=80, netProfit=-20, and bankruptcyDeficit=10.
//@version=2
strategy(title="Perps Bankruptcy Gap", initialCapital=10, instrument="perps", leverage=10, maintenanceMarginPercent=0.5, qtyType="fixed", qtyValue=1, slippageBps=0, makerFeePercent=0, takerFeePercent=0, funding="off")
timeseries bars = ohlcv(symbol=currentSymbol, exchange=currentExchange)
if (barIndex == 0) {
strategy.entry("L", "long")
}
plotLine(value=strategy.equity(), width=1, colors=["#7c2d12"], label=["Equity"], desc=["Strategy equity"])Not bugs
These are intentional parts of the model. commissionPercent does nothing under instrument="perps" and the warning is expected. Missing funding rows are counted in fundingUnavailableCount and not charged. Liquidation uses the chart's last-traded OHLC, not mark price, because the strategy engine does not have mark-price data. Spot scripts and spot economics are unchanged unless you opt into instrument="perps".
The five canonical scenarios
Five scripts define what correct perps behavior means, and they exist in three places at once: as strategy templates in the editor (the Perps: entries in the template picker), as the executed examples behind the numbers on this page, and as an acceptance battery inside the engine's own test suite. Every expected value in that battery is derived by hand from the formulas on this page, never from engine output, and the engine cannot ship a release unless all five reproduce to the last digit.
| Scenario | Template | What it pins down | The number it must land on | Executed probe |
|---|---|---|---|---|
| 1. Long liquidation | Perps: Long Liquidation | The long liquidation price formula | Entry 100 at 10x, 0.5% maintenance liquidates at 90.45226130653266 | perps-liquidation.ks |
| 2. Short liquidation with fees | Perps: Short with Taker Fees | The short formula plus taker fees on the market entry and the liquidation fill | Entry 100 liquidates at 109.45273631840797, total fees 0.104726368159204 | perps-acceptance-short-taker.ks |
| 3. Maker and taker split | Perps: Maker and Taker Split | Fee class per fill: limit legs maker, market and stop legs taker | makerFeesPaid 0.02, takerFeesPaid 0.096, net 1.8840000000000003 | perps-maker-taker-split.ks |
| 4. Funding erosion | Perps: Funding Erosion | Funding debits cash and committed margin; depletion liquidates at the bar open | Three 1% settlements walk margin 10 to 7; a 7% settlement liquidates with fundingPaid 10 and zero price pnl | perps-acceptance-funding-erosion.ks |
| 5. Bankruptcy gap | Perps: Bankruptcy Gap | Gap-through fills at the worse bar price; deficit accounting | A gap to 80 books bankruptcyDeficit 10 and equity floors at exactly 0 | perps-bankruptcy-gap.ks |
Each scenario is an executed probe script with its bar fixtures, and its outputs are recorded in the observed artifacts beside it. Scenario 2 differs from the short-formula example earlier on this page on purpose: that example enters with a zero-fee limit fill to isolate the formula, while the canonical scenario uses a market entry so both legs pay taker. Scenario 4 likewise complements the single-settlement depletion example with a gradual erosion before the depleting settlement.
One design note the third scenario teaches: the script phase runs at bar close, after that bar's fills, so a flat-position entry guard sees the post-exit state on the exit bar itself. Keep entry zones disjoint from your exit prices or the strategy re-arms on the very bar it exited; a consumed entry order never refills, but a re-placed one is a new order.