---
title: "Examples: Spot Strategies"
description: "Three complete spot strategies, the ports of the three kScript (legacy) examples: a two-line trend follower, a reversion with a protective stop, and bracketed…"
order: 86
section: "strategies"
---

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

# Examples: Spot Strategies

Three complete spot strategies, the ports of the three kScript (legacy) examples: a two-line trend follower, a reversion with a protective stop, and bracketed exits. Each is one Indicator file, small enough to read in a minute and a good skeleton for your own. Install one and run it:

```bash
om wrun install ./ma-cross
om backtest @you/ma-cross --asset BINANCE_FUTURES:BTCUSDT --window 90d
```

## Moving average cross

The smallest useful strategy: long when the fast EMA crosses above the slow one, flat when it crosses back under. One entry rule, one exit rule.

```typescript
// Moving average cross as a strategy: long when the fast EMA crosses above the slow one, flat when it crosses back under.
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, qtyType: "percentOfEquity", qtyValue: 50, commissionPercent: 0.05, slippageBps: 2 });
input("close", ohlcv.close);
output("fast", line, overlay, { description: "9-period EMA of close" });
output("slow", line, overlay, { description: "21-period EMA of close" });

const fastEma = new Ema(9);
const slowEma = new Ema(21);
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("L").send();
  if (crossed == -1) strategy.closeAll();
  out_fast(fast);
  out_slow(slow);
  emitRow();
}

export function reset(): void {
  fastEma.reset();
  slowEma.reset();
  cross.reset();
  fast = NaN;
  slow = NaN;
  crossed = 0;
}
```

What to notice: `percentOfEquity` sizing keeps the position size proportional as equity compounds, and `commissionPercent` plus `slippageBps` make the run pay realistic costs on every fill.

## RSI reversion with a protective stop

Buys oversold dips and adds a protective stop under every entry, so a dip that keeps dipping gets cut instead of riding to the bottom.

```typescript
// RSI reversion with a protective stop: buy the dip on the bar RSI crosses up through oversold, protect it 4% under the close, leave on recovery.
import { input, line, lower, ohlcv, output, param } from "./sdk/declare";
import { in_close } from "./gen/inputs";
import { emitRow, out_rsi } from "./gen/outputs";
import { p_period } from "./gen/params";
import { strategy } from "./gen/strategy";
import { Cross, Rsi } from "./sdk/ta";

strategy({ initialCapital: 10000, qtyType: "percentOfEquity", qtyValue: 25, commissionPercent: 0.05, slippageBps: 2 });
param("period", 14, { min: 2, max: 200, description: "RSI length in bars" });
input("close", ohlcv.close);
output("rsi", line, lower, { description: "Wilder RSI of close, 0 to 100" });

let rsi = new Rsi(14);
const dip = new Cross();
const recovery = new Cross();
let value: f64 = NaN;
let close: f64 = NaN;
let dipped: i32 = 0;
let recovered: i32 = 0;

export function init(): void {
  rsi = new Rsi(i32(p_period()));
}

export function state(): i32 {
  close = in_close();
  value = rsi.update(close);
  if (isNaN(value)) return 0;
  dipped = dip.update(value, 30.0);
  recovered = recovery.update(value, 55.0);
  return 1;
}

export function finalize(): void {
  if (dipped == 1) strategy.long("Dip").send();
  if (strategy.positionSize() > 0) strategy.exit("Protect").from("Dip").stop(close * 0.96).send();
  if (recovered == 1) strategy.closeAll();
  out_rsi(value);
  emitRow();
}

export function reset(): void {
  rsi.reset();
  dip.reset();
  recovery.reset();
  value = NaN;
  close = NaN;
  dipped = 0;
  recovered = 0;
}
```

What to notice: `strategy.exit(...).from("Dip")` scopes the stop to the named entry, and the stop follows the close at placement time. The [first strategy](first-strategy.md) page builds this file up line by line.

## Trend entries with bracket exits

A crossover entry bracketed by both a stop and a take-profit limit. Whichever side the market touches first closes the trade and cancels the other.

```typescript
// Trend entries with bracket exits: a stop and a take-profit limit on one exit id, whichever the market touches first closes the trade and cancels the other.
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, Sma } from "./sdk/ta";

strategy({ initialCapital: 10000, qtyType: "fixed", qtyValue: 1, pyramiding: 1, fillModel: "pathHeuristic" });
input("close", ohlcv.close);
output("fast", line, overlay, { description: "5-period SMA of close" });
output("slow", line, overlay, { description: "20-period SMA of close" });

const fastSma = new Sma(5);
const slowSma = new Sma(20);
const cross = new Cross();
let fast: f64 = NaN;
let slow: f64 = NaN;
let close: f64 = NaN;
let crossed: i32 = 0;

export function init(): void {}

export function state(): i32 {
  close = in_close();
  fast = fastSma.update(close);
  slow = slowSma.update(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 (strategy.positionSize() > 0) strategy.exit("Protect").from("Trend").stop(close * 0.97).limit(close * 1.05).send();
  out_fast(fast);
  out_slow(slow);
  emitRow();
}

export function reset(): void {
  fastSma.reset();
  slowSma.reset();
  cross.reset();
  fast = NaN;
  slow = NaN;
  close = NaN;
  crossed = 0;
}
```

What to notice: the stop and limit placed through one `strategy.exit` call form a one-cancels-all pair, and `fillModel: "pathHeuristic"` decides a bar that touches both. [Fill simulation](fill-simulation.md) explains exactly how.
