Hyperliquid trades as perpetuals: leveraged, funded and liquidatable. A backtest that ignores those three facts will happily approve a strategy the venue would have destroyed. This is the kScript (legacy) walkthrough as one Indicator file: a trend strategy under perps accounting, isolated margin at 10x, maker and taker fees, and a protective stop off the average entry.
The strategy
// A funding-aware trend strategy at 10x: isolated margin, maker and taker rates, recorded funding, and a stop 3% under the average entry.
import { input, line, ohlcv, output, overlay } from "./sdk/declare";
import { in_close } from "./gen/inputs";
import { emitRow, out_fast, out_slow } from "./gen/outputs";
import { strategy } from "./gen/strategy";
import { Cross, Ema } from "./sdk/ta";
strategy({ initialCapital: 10000, instrument: "perps", leverage: 10, qtyType: "percentOfEquity", qtyValue: 10, makerFeePercent: 0.015, takerFeePercent: 0.045, funding: "data", slippageBps: 2 });
input("close", ohlcv.close);
output("fast", line, overlay, { description: "21-period EMA of close" });
output("slow", line, overlay, { description: "55-period EMA of close" });
const fastEma = new Ema(21);
const slowEma = new Ema(55);
const cross = new Cross();
let fast: f64 = NaN;
let slow: f64 = NaN;
let crossed: i32 = 0;
export function init(): void {}
export function state(): i32 {
fast = fastEma.update(in_close());
slow = slowEma.update(in_close());
if (isNaN(fast) || isNaN(slow)) return 0;
crossed = cross.update(fast, slow);
return 1;
}
export function finalize(): void {
if (crossed == 1) strategy.long("Trend").send();
if (crossed == -1) strategy.closeAll();
if (strategy.positionSize() > 0) strategy.exit("Protect").from("Trend").stop(strategy.positionAvgPrice() * 0.97).send();
out_fast(fast);
out_slow(slow);
emitRow();
}
export function reset(): void {
fastEma.reset();
slowEma.reset();
cross.reset();
fast = NaN;
slow = NaN;
crossed = 0;
}om wrun install ./hl-funding-trend
om backtest @you/hl-funding-trend --asset HYPERLIQUID_FUTURES:BTC --window 90dThe price input carries no market pin: a strategy trades the market it is run on, and --asset names it. Reading the declaration, which does most of the perps work:
instrument: "perps"withleverage: 10: sizing commits margin, not notional.qtyValue: 10means each entry commits 10% of equity as isolated margin; the notional is that margin times leverage. The broker tracks the liquidation price from your leverage and maintenance margin and closes you there if a bar proves or assumes the level traded.makerFeePercent: 0.015, takerFeePercent: 0.045: fees route by fill type, so market entries, stops andcloseAllpay taker while limit-bound fills pay maker. Set your own tier's numbers; they ship with the package.funding: "data": the broker settles recorded funding against the open position when a funding provider is attached. This release attaches none on any host, so the run counts every unsettled open bar infundingUnavailableCountand charges nothing; the number is the disclosure until funding data lands, andfunding: "off"declares a strategy that should not depend on it.strategy.positionAvgPrice() * 0.97: the stop tracks the average entry, re-armed every held bar.
Reading the result
Three lines to check before believing the equity curve:
fundingUnavailableCountin the stats and the card's note: how many held bars went unsettled. A long that looks fine gross can bleed through settlements while it holds, and this run has not charged them.liquidationCountand trades whose exit saysliquidation: at 10x, a 3% protective stop and the liquidation level are uncomfortably close neighbors. If liquidations show up, the venue closed you before your stop did.ambiguousFillCount: fills the declared fill model settled where one bar touched the stop and a level; the run details readbar resolutionin this release.
Tune it like it is real
- Leverage down first. At 5x the liquidation level sits twice as far; watch
liquidationCountgo to zero before you tune anything else. - Widen the stop or drop the interval. Brackets arm on the bar after entry, so a tight stop on a coarse chart is exposed for one full bar; fill simulation covers the trade-off.
- Run the spot twin. Copy the file, drop the perps settings, and put both on the chart: compare mode shows what leverage and fees cost you. That difference is the part most backtests never model.