---
title: "Examples"
description: "Complete strategy files to start from: the five perps scenarios behind the engine's acceptance battery and a Hyperliquid perp walkthrough, each with a line on…"
order: 128
section: "strategies"
---

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

# Examples

Complete strategy files to start from: the five perps scenarios behind the engine's acceptance battery and a Hyperliquid perp walkthrough, each with a line on what it shows. The three spot examples live on the pages that teach them, listed first.

To run one, replace the starter in a **New indicator** draft with the file and press **Backtest**; the [overview](overview.md#what-a-strategy-file-is) has the steps. The strategy trades the chart's own market, and the Strategy Tester docks under the chart with the trades and the stats ([Reading the Strategy Tester](reading-the-tester.md)).

## Spot examples

| File | What it shows | Page |
| --- | --- | --- |
| Moving average cross | The smallest useful strategy: one entry rule, one exit rule. `percentOfEquity` sizing keeps the position proportional as equity compounds, and `commissionPercent` plus `slippageBps` make every fill pay realistic costs. | [Strategies overview](overview.md#what-a-strategy-file-is) |
| RSI reversion with a protective stop | Buys oversold dips and arms a stop under every entry, so a dip that keeps dipping is cut instead of riding to the bottom. `strategy.exit(...).from("Dip")` scopes the stop to the named entry, and the stop follows the close at placement time. Built up line by line on its page. | [Build your first strategy](first-strategy.md) |
| Trend entries with bracket exits | A crossover entry bracketed by a stop and a take-profit limit on one exit id, a one-cancels-all pair: whichever the market touches first closes the trade and cancels the other. `fillModel: "pathHeuristic"` decides a bar that touches both ([fill simulation](fill-simulation.md#intrabar-ordering-and-fill-models)). | [Writing strategies](writing-strategies.md#the-order-api) |

## The five perps scenarios

The five canonical perps scenarios behind the engine's acceptance battery: liquidation on both sides, fee classification, funding erosion and bankruptcy accounting. They are reproduction scripts: each lands on the number the engine's battery pins for bars priced near 100, which is what makes it verifiable. On a chart they run against that chart's own prices, where a scenario that waits for a price near 100 may never trade, so read them for the mechanics rather than to match the numbers. [Perps](perps.md#liquidation) has the formulas. A first-bar gate is a row counter the file keeps itself.

### 1. Long liquidation

What it shows: a 10x long entered at 100 with 0.5% maintenance margin must liquidate at exactly `(100 - 10) / 0.995 = 90.45226130653266`.

```typescript sample=strat-perps-long-liquidation
// Scenario 1, long liquidation: a 10x long at 100 with 0.5% maintenance margin must liquidate at (100 - 10) / 0.995.
strategy({ initialCapital: 10000, instrument: "perps", leverage: 10, maintenanceMarginPercent: 0.5, qtyType: "fixed", qtyValue: 1, slippageBps: 0, makerFeePercent: 0, takerFeePercent: 0, funding: "off" });
output("close", line, overlay, { description: "Close price" });

let bars: i32 = 0;

function onBar(): void {
  bars += 1;
  if (bars == 1) strategy.long("L").send();
  out_close(bar.close());
}
```

### 2. Short liquidation with taker fees

What it shows: the short side of the same formula, `(100 + 10) / 1.005 = 109.45273631840797`, with a market entry so both the entry fill and the liquidation fill pay `takerFeePercent`.

```typescript sample=strat-perps-short-liquidation-taker
// Scenario 2, short liquidation with taker fees: the short formula (100 + 10) / 1.005, both fills paying the taker rate.
strategy({ initialCapital: 10000, qtyType: "fixed", qtyValue: 1, instrument: "perps", leverage: 10, maintenanceMarginPercent: 0.5, takerFeePercent: 0.05, funding: "off" });
output("close", line, overlay, { description: "Close price" });

function onBar(): void {
  const close = bar.close();
  if (strategy.positionSize() == 0 && close < 100.5) strategy.short("S").send();
  out_close(close);
}
```

### 3. Maker and taker split

What it shows: two trades, four fills, four fee classifications. A limit entry and its take-profit limit pay maker; a market entry and its protective stop pay taker. Also a guard-design lesson: the file runs at the bar's close, after fills, so entry zones stay disjoint from exit prices or the flat-position guard re-arms on the very bar an exit filled.

```typescript sample=strat-perps-maker-taker-split
// Scenario 3, maker and taker split: a limit entry and its take-profit pay maker, a market entry and its stop pay taker.
strategy({ initialCapital: 10000, qtyType: "fixed", qtyValue: 1, instrument: "perps", leverage: 5, makerFeePercent: 0.01, takerFeePercent: 0.05, funding: "off" });
output("close", line, overlay, { description: "Close price" });

function onBar(): void {
  const close = bar.close();
  const held = strategy.positionSize();
  if (held == 0 && close > 100.5 && close < 103.0) strategy.long("LimitIn").limit(95.0).send();
  if (held == 0 && close > 110.5) strategy.long("MarketIn").send();
  if (held > 0) {
    strategy.exit("TP").from("LimitIn").limit(105.0).send();
    strategy.exit("SL").from("MarketIn").stop(92.0).send();
  }
  out_close(close);
}
```

### 4. Funding erosion

What it shows: funding settlements debit cash and committed margin together, so the liquidation price tightens as margin erodes, and a settlement that depletes the margin liquidates the position at that bar's open with zero price PnL. The chart attaches no funding data to a strategy run, so on the chart this file charges nothing and counts every unsettled open bar in `fundingUnavailableCount`, and the Strategy Tester's **Run details** say "Funding data did not cover N bars" ([Funding](perps.md#funding)).

```typescript sample=strat-perps-funding-erosion
// Scenario 4, funding erosion: settlements debit cash and committed margin together, and a depleting settlement liquidates at that bar's open.
strategy({ initialCapital: 10000, qtyType: "fixed", qtyValue: 1, instrument: "perps", leverage: 10, maintenanceMarginPercent: 0.5, funding: "data" });
output("close", line, overlay, { description: "Close price" });

function onBar(): void {
  const close = bar.close();
  if (strategy.positionSize() == 0 && close > 99.5) strategy.long("L").send();
  out_close(close);
}
```

### 5. Bankruptcy gap

What it shows: price gaps straight through the liquidation level. The fill is the bar price because it is worse, the loss beyond committed margin is recorded as `bankruptcyDeficit`, and equity floors at exactly zero.

```typescript sample=strat-perps-bankruptcy-gap
// Scenario 5, bankruptcy gap: a gap through the liquidation level fills at the worse bar price, the loss past the margin is the bankruptcy deficit, and equity floors at zero.
strategy({ initialCapital: 10, instrument: "perps", leverage: 10, maintenanceMarginPercent: 0.5, qtyType: "fixed", qtyValue: 1, slippageBps: 0, makerFeePercent: 0, takerFeePercent: 0, funding: "off" });
output("equity", line, lower, { description: "Strategy equity" });

let bars: i32 = 0;

function onBar(): void {
  bars += 1;
  if (bars == 1) strategy.long("L").send();
  out_equity(strategy.equity());
}
```

## Backtest a Hyperliquid perp strategy

What it shows: a trend strategy under perps accounting on a venue that is leveraged, funded and liquidatable, with isolated margin at 10x, maker and taker fees, recorded funding and a protective stop off the average entry. A backtest that ignores those three venue facts will happily approve a strategy the venue would have destroyed.

```typescript sample=strat-hl-funding-trend
// A funding-aware trend strategy at 10x: isolated margin, maker and taker rates, recorded funding, and a stop 3% under the average entry.
strategy({ initialCapital: 10000, instrument: "perps", leverage: 10, qtyType: "percentOfEquity", qtyValue: 10, makerFeePercent: 0.015, takerFeePercent: 0.045, funding: "data", slippageBps: 2 });
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();

function onBar(): void {
  const fast = fastEma.update(bar.close());
  const slow = slowEma.update(bar.close());
  if (isNaN(fast) || isNaN(slow)) return;
  const crossed = cross.update(fast, slow);
  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);
}
```

Put the chart on a Hyperliquid perpetual (BTC, for example) before you press **Backtest**: the file reads the chart's own close through `bar.close()` and pins no market, so the chart you run it on is the market it tests. 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 travel inside the indicator when you publish it.
- `funding: "data"`: the broker settles recorded funding against the open position when funding data is attached. The chart attaches none to a strategy run, so the run counts every unsettled open bar in `fundingUnavailableCount` and charges nothing; the count is the disclosure, and `funding: "off"` declares a strategy that should not depend on funding.
- `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. **Funding.** "Funding data did not cover N bars" in **Run details** (`fundingUnavailableCount`) is how many held bars went unsettled, and the Overview's **Funding** chart says no funding was applied. A long that looks fine gross can bleed through settlements while it holds, and this run has not charged them.
2. **Liquidations.** Trades that read **Liquidated** under **Exit via** (`liquidationCount` counts them), and the Overview's line saying when the first one happened. 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. **Model-settled fills.** The Performance tab's count (`ambiguousFillCount`) and the **± fill** badge in the Trades tab: fills the declared fill model settled where one bar touched the stop and a level; **Run details** read "Fill precision: bar resolution" once one has.

### Tune it like it is real

- **Leverage down first.** At 5x the liquidation level sits twice as far; watch the **Liquidated** exits disappear 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](fill-simulation.md#choosing-an-interval-to-trust) covers the trade-off.
- **Run the spot twin.** Copy the file into a second **New indicator** draft, drop the perps settings, and press **Backtest** on both: with two strategies on the chart, the Strategy Tester's **Compare** shows what leverage and fees cost you. That difference is the part most backtests never model.
