Backtest a Hyperliquid Perp Strategy

Hyperliquid trades as perpetuals: leveraged, funded and liquidatable. A backtest that ignores those three facts will happily approve a strategy the venue would…

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 90d

The 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" with leverage: 10: sizing commits margin, not notional. qtyValue: 10 means 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 and closeAll pay 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 in fundingUnavailableCount and charges nothing; the number is the disclosure until funding data lands, and funding: "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:

  1. fundingUnavailableCount in 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.
  2. liquidationCount and trades whose exit says liquidation: 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.
  3. ambiguousFillCount: fills the declared fill model settled where one bar touched the stop and a level; the run details read bar resolution in this release.

Tune it like it is real

  • Leverage down first. At 5x the liquidation level sits twice as far; watch liquidationCount go 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.