---
title: "Risk-sized reversion"
description: "A strategy: buy the dip as RSI climbs back out of oversold, protect it with a stop under the entry and a target above it (both sized in ATR), leave on recovery…"
order: 115
section: "cookbook"
---

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

# Risk-sized reversion

![RSI reversion strategy with entry, stop and target rails](/wrun/images/strategy-risk-reversion.png)

A strategy: buy the dip as RSI climbs back out of oversold, protect it with a stop under the entry and a target above it (both sized in ATR), leave on recovery; each entry is sized so a trade stopped out loses the risk setting's percent of the equity (1% by default), never buying more than the equity holds. RSI (violet) in its own pane under the chart, with the oversold level (30, dashed), the recovery level (55, dotted sky) and the overbought level (70, dashed). On price, the rails of each trade from the fill bar to the bar it closed: the entry (sky), the stop (orange, dashed) and the target (amber, dashed). The open trade's rails are bright; past trades' rails stay dim, the newest 20 kept. The Strategy Tester under the chart holds the fills, equity against buy and hold, the numbers and the trade list.

The parts are an entry sized from its stop with `qty(...)` and `strategy.equity()` ([Writing strategies](../strategies/writing-strategies.md)), a stop and a limit exit re-armed each bar the trade is held ([Your first strategy](../strategies/first-strategy.md)), the `Rsi`, `Atr` and `Cross` helpers ([TA library](../functions/ta-library.md)), and line handles in chart time and price for the rails ([Drawing objects](../presentation/drawing-objects.md)). This is also the `strategy-risk-reversion` template: the **Risk-Sized Reversion** card under **Strategies** in the editor's starter list, and it compiles as written.

## The wrun indicator

```typescript sample=strategy-risk-reversion
// Risk-Sized Reversion: buy the dip as RSI climbs back out of oversold, protect it with a stop under the entry and a
// target above it, both sized in ATR so they fit the market and the interval, and leave on recovery. Each entry is
// sized so a trade stopped out loses risk_pct of the equity, never buying more than the equity holds.
// The trader sees RSI in its own pane with the oversold, recovery and overbought levels, and on price the rails of
// each trade: the entry (sky), the stop (orange, dashed) and the target (amber, dashed), drawn from the fill bar to
// the bar the trade closed; the open trade's rails are bright, past trades' rails stay dim. The Strategy Tester under
// the chart holds the fills and the numbers.

// The settings sit on two pages of the dialog: page() opens one, and the rows declared after it land on it.
page("Signal");
param.int("rsi_len", 14, { min: 2, max: 200, label: "RSI length in bars", description: "RSI length in bars" });
param.range("rsi_band", [30, 70], { min: 5, max: 95, label: "Oversold / overbought", hint: "A dip must climb back through the low end to enter; the high end is drawn as the upper reference" }); // a low..high pair on one slider: p_rsi_band_lo() and p_rsi_band_hi()
param.number("recovery", 55, { min: 30, max: 90, label: "Recovery level", description: "RSI level that ends the trade" });
page("Risk");
param.number("risk_pct", 1, { min: 0.1, max: 10, step: 0.1, label: "Risk per trade, percent", description: "Percent of the equity a trade loses at its stop: each entry is sized to it, never past what the equity buys" });
// strategy({...}) makes the package a strategy; every entry passes its own size, so the default order size is the cap: all of the equity.
strategy({ initialCapital: 10000, qtyType: "percentOfEquity", qtyValue: 100, commissionPercent: 0.05, slippageBps: 2 });
param.int("atr_len", 14, { min: 2, max: 200, label: "ATR length in bars", description: "ATR length in bars, the unit of the stop and the target" });
param.number("stop_atr", 1.5, { min: 0.1, max: 10, step: 0.1, label: "Stop distance in ATR", description: "Stop distance under the entry price, in ATR" });
param.number("target_atr", 3, { min: 0.1, max: 20, step: 0.1, label: "Target distance in ATR", description: "Target distance above the entry price, in ATR" });
const rsiLine = output("rsi", line, lower, { color: "#a78bfa", width: 2, label: "RSI", format: "0.0", description: "Wilder RSI of the close, 0 to 100" }); // the handle names it for the hover card
output("oversold_line", line, lower, { color: "#94a3b8", width: 1, line_style: "dashed", label: "Oversold", format: "int", description: "The oversold level" });
output("recovery_line", line, lower, { color: "#38bdf8", width: 1, line_style: "dotted", label: "Recovery", format: "int", description: "The recovery level" });
output("overbought_line", line, lower, { color: "#94a3b8", width: 1, line_style: "dashed", label: "Overbought", format: "int", description: "The overbought level" });
handles.line({ color: "#38bdf8", width: 1 }); // the rails: line handles in chart time and price
hover(rsiLine, [block.value("RSI", "rsi", { format: "0.0" }), block.rows([["Oversold", "oversold_line", "int"], ["Recovery", "recovery_line", "int"], ["Overbought", "overbought_line", "int"]])]); // the card the RSI line opens under the cursor: its value and the three levels

const SKY = rgba(56, 189, 248, 255);
const ORANGE = rgba(248, 104, 0, 255);
const AMBER = rgba(248, 192, 0, 255);
const TRADES_KEPT = 20; // rails ride a ring: the newest 20 trades keep theirs, 3 line handles each, never past 60
const rails: LineHandle[] = [];
for (let i = 0; i < TRADES_KEPT * 3; i += 1) rails.push(draw.line(i)); // handle objects allocate once, at module load

let rsi = new Rsi(14); // onStart() rebuilds it at the chosen length
let atr = new Atr(14); // the unit of the stop and the target
const dip = new Cross(); // +1 on the bar RSI crosses above the oversold level
const recovery = new Cross(); // +1 on the bar RSI crosses above the recovery level
let oversoldLevel = 30.0;
let recoveryLevel = 55.0;
let overboughtLevel = 70.0;
let stopAtr = 1.5;
let targetAtr = 3.0;
let riskPct = 1.0;
let stopDistance: f64 = NaN; // the entry's stop and target distances, fixed when its order goes out
let targetDistance: f64 = NaN;
let atrValue: f64 = NaN; // this bar's ATR
let barTime: f64 = NaN; // this bar's open time in epoch seconds
let prevBarTime: f64 = NaN;
let intervalSec = 0.0; // the bar spacing, measured from consecutive bars, so a rail ends at the bar's close
let inTrade = false;
let tradeCount = 0;
let railBase = 0; // the first of the open trade's three handles
let entryTime: f64 = NaN;
let entryPrice: f64 = NaN;
let stopPrice: f64 = NaN;
let targetPrice: f64 = NaN;

// A fill landed on this bar: place the stop and the target the order was sized by around the entry price, start the rails.
function openRails(): void {
  entryPrice = strategy.positionAvgPrice();
  if (!isFinite(entryPrice) || !isFinite(barTime) || !isFinite(stopDistance)) return; // every draw coordinate must be finite
  inTrade = true;
  tradeCount += 1;
  entryTime = barTime;
  stopPrice = entryPrice - stopDistance;
  targetPrice = entryPrice + targetDistance;
  railBase = ((tradeCount - 1) % TRADES_KEPT) * 3;
  const right = barTime + intervalSec;
  rails[railBase].set(entryTime, entryPrice, right, entryPrice).color(SKY).style(LineStyle.Solid).width(1.5).opacity(1.0);
  rails[railBase + 1].set(entryTime, stopPrice, right, stopPrice).color(ORANGE).style(LineStyle.Dashed).width(1.0).opacity(1.0);
  rails[railBase + 2].set(entryTime, targetPrice, right, targetPrice).color(AMBER).style(LineStyle.Dashed).width(1.0).opacity(1.0);
}

// While the trade is open, every bar extends the three rails to the bar's close.
function extendRails(): void {
  const right = barTime + intervalSec;
  rails[railBase].setXy2(right, entryPrice);
  rails[railBase + 1].setXy2(right, stopPrice);
  rails[railBase + 2].setXy2(right, targetPrice);
}

// The trade closed on this bar: its rails stay where they ended, dimmed.
function closeRails(): void {
  inTrade = false;
  rails[railBase].opacity(0.5);
  rails[railBase + 1].opacity(0.5);
  rails[railBase + 2].opacity(0.5);
}

// onStart() runs once before the first bar: read each setting through its p_ reader.
function onStart(): void {
  rsi = new Rsi(i32(p_rsi_len()));
  oversoldLevel = p_rsi_band_lo();
  recoveryLevel = p_recovery();
  overboughtLevel = p_rsi_band_hi();
  atr = new Atr(i32(p_atr_len()));
  stopAtr = p_stop_atr();
  targetAtr = p_target_atr();
  riskPct = p_risk_pct();
}

// onBar() runs once per bar: fold the close into the RSI and leave while it warms up (nothing written, no orders);
// then keep the rails in step with the position (the getters read the position after this bar's fills), place the
// orders (they fill at the next bar's open) and write the outputs.
function onBar(): void {
  const close = bar.close();
  prevBarTime = barTime;
  barTime = bar.time();
  if (isFinite(prevBarTime) && barTime > prevBarTime) intervalSec = barTime - prevBarTime;
  atrValue = atr.update(bar.high(), bar.low(), close);
  const value = rsi.update(close); // this bar's RSI, NaN until the window is full
  if (isNaN(value) || isNaN(atrValue)) return;
  const dipped = dip.update(value, oversoldLevel);
  const recovered = recovery.update(value, recoveryLevel);
  const size = strategy.positionSize();
  if (size > 0.0 && !inTrade) openRails();
  if (inTrade) extendRails();
  if (size == 0.0 && inTrade) closeRails();
  if (dipped == 1 && size == 0.0 && atrValue > 0.0 && close > 0.0) { // a market entry under the id Dip
    stopDistance = stopAtr * atrValue;
    targetDistance = targetAtr * atrValue;
    const equity = strategy.equity();
    let qty = (equity * riskPct) / 100.0 / stopDistance; // stopped out at the stop, the trade loses risk_pct of the equity
    if (qty * close > equity) qty = equity / close; // never more than the equity buys
    strategy.long("Dip").qty(qty).send();
  }
  if (size > 0.0 && isFinite(stopPrice) && isFinite(targetPrice)) {
    strategy.exit("Protect").from("Dip").stop(stopPrice).limit(targetPrice).send(); // re-armed each bar held
  }
  if (recovered == 1) strategy.closeAll(); // the signal exit: every open entry closes at the next open
  out_rsi(value);
  out_oversold_line(oversoldLevel);
  out_recovery_line(recoveryLevel);
  out_overbought_line(overboughtLevel);
}
```

## How it works

**The risk is a setting.** `risk_pct` (**Risk per trade, percent**, 1, from 0.1 to 10) is the share of the equity a trade loses at its stop. On the signal bar the entry fixes its stop distance, `stop_atr` times this bar's ATR, and its quantity: the equity times `risk_pct` over 100, divided by the stop distance, so a fill stopped out loses `risk_pct` of the equity (before costs and any gap through the stop). A quantity whose notional would pass the equity is cut to what the equity buys, so with a tight stop on a short interval the entry buys the whole equity and risks less. `strategy.long("Dip").qty(qty).send()` passes the size on the order, so the `strategy({ ... })` declaration's own size, `qtyType: "percentOfEquity"` at `qtyValue: 100`, is the cap and never decides an entry. 10,000 starting equity, 0.05% commission and 2 bps slippage are the file's.

**Three rules.** Entry: RSI (`rsi_len` 14) crosses above the low end of `rsi_band` (30, the oversold level) while flat, and the sized `strategy.long("Dip")` fills at the next open. Protection: `strategy.exit("Protect").from("Dip").stop(stopPrice).limit(targetPrice).send()` re-armed every bar the trade is held, the stop `stop_atr` (1.5) ATR under the fill price and the target `target_atr` (3) ATR above it, the ATR (`atr_len` 14) of the signal bar, the distances the entry was sized by. Exit: RSI crosses above `recovery` (55) and `strategy.closeAll()` closes every open entry at the next open. The high end of `rsi_band` (70, the overbought level) is drawn as a reference line only.

**Rails follow the position.** `strategy.positionSize()` and `strategy.positionAvgPrice()` read the position after the bar's fills: the bar a fill lands fixes the entry, the stop and the target and opens three line handles at the bar's open time; every bar held extends them to the bar's close; the bar the position goes flat dims them. Twenty trades keep their rails on a ring of 60 handle ids.

## Where it runs

Every market with candles: perps, spot, prediction markets, FX. The Strategy Tester replays it in your browser over the chart's loaded window and recomputes as you pan; orders fill against the chart's own candles. The **Strategies** group of the starter list shows where the chart has the Strategy Tester.

## When data is missing

The first `rsi_len` and `atr_len` bars are warm-up: nothing written, no orders, no rails. A trade still open when the loaded history ends keeps its bright rails extended to the last bar.

## Customize it

- **Tighter protection.** `stop_atr` and `target_atr` are settings in ATR, so they fit the market and the interval; a tighter stop buys more at the same risk, up to the whole equity.
- **Risk more or less.** `risk_pct` moves every entry's size and reruns the strategy over the same candles.
- **A different dip.** Move the two ends of `rsi_band` (one slider in the dialog) and `recovery`; the reference lines follow.
- **Shorts too.** Mirror the rules with `strategy.short(...)` on RSI leaving overbought ([Writing strategies](../strategies/writing-strategies.md)).
- **Alert on the trades.** Once the indicator is published and on a chart, the alert dialog adds the strategy's own choices: "Strategy order placed", "Strategy trade opened or closed", "Strategy position" and "Strategy equity" ([Alerts](../functions/alerts.md)).

## Run it

1. In the editor's Explorer, press the **Templates** icon ("Browse starter templates") and pick **Risk-Sized Reversion** under **Strategies**.
2. Press **Run**: RSI and its three levels draw in a pane under the chart, each trade's rails draw on price, and the Strategy Tester under the chart shows the fills, the equity against buy and hold, and the trade list.

## Concepts used

- [Your first strategy](../strategies/first-strategy.md) for the protective exit
- [Writing strategies](../strategies/writing-strategies.md) for the order builders, `qty(...)` on an entry and the position and equity getters
- [Drawing objects](../presentation/drawing-objects.md) for line handles in chart time and price
