EMA and ATR strategy runner
Implement the reusable runner as a risk-sized inverted EMA crossover strategy with RSI confirmation, ATR stops, targets, and trailing protection.
This implementation turns the strategy runner template into a complete contrarian EMA and ATR strategy. It fades confirmed EMA crosses: a bullish cross with strong RSI opens a short, while a bearish cross with weak RSI opens a long. It supports both sides, sizes each position by risk, caps exposure by available margin and leverage, executes protective conditions against one-minute candles, applies configurable slippage, and reports the result through chart plots, performance curves, a positions table, and a realtime HUD.
The sections below explain the entry rules, position size, exits, and displayed results.
Entry algorithm
A fast/slow EMA cross supplies the setup and RSI confirms its momentum. The strategy then trades in the opposite direction:
let price = chart.close ?? 0.0
let stamp = chart.time ?? time.fromUnixSeconds(0)
let fast = ta.ema(chart.close, fastLen)
let slow = ta.ema(chart.close, slowLen)
let rsi = ta.rsi(chart.close, rsiLen) ?? 50.0
let atr = chart.atr(atrLen) ?? 0.0
let crossUp = ta.crossover(fast, slow)
let crossDown = ta.crossunder(fast, slow)
if !position.isOpen() && atr > 0.0 {
if allowShorts && crossUp && rsi >= longRsi {
openPosition(-1, price, atr, stamp)
} else if allowLongs && crossDown && rsi <= shortRsi {
openPosition(1, price, atr, stamp)
}
}The entry timestamp is not decoration: openPosition stores it as position.openedAt, and the one-minute execution clock skips any minute with stamp <= position.openedAt so a position cannot be filled and stopped out inside the same boundary.
All signal inputs use confirmed series. The decision cannot disappear because the forming candle changes after a fill.
Risk-sized quantity
The position size is the smaller of risk-based quantity and leverage-capped quantity:
let stopDistance = math.max(atr * stopAtr, price * 0.0001)
let riskBudget = equity * riskPct / 100.0
let riskQty = riskBudget / stopDistance
let marginQty = equity * maxMarginPct / 100.0 * leverage / price
let quantity = math.min(riskQty, marginQty)This makes the risk input meaningful without allowing a very tight ATR stop to create more exposure than the configured account and leverage permit.
Exit priority
Each confirmed one-minute candle checks liquidation, the previously known stop, then target. If one minute spans multiple levels, that order is deliberately conservative: liquidation or stop wins over a favorable target because OHLC still cannot reveal the path inside that minute. Opening beyond a crossed level produces a gap fill from the minute open. Configured adverse slippage is then applied to every entry and exit.
The strategy-timeframe close exits an inverted long on a bullish cross and an inverted short on a bearish cross, then tightens the trailing stop for future minutes. This avoids using a closing price to invent a stop that could not have existed earlier in the same strategy candle. Because the one-minute subscription is declared first, its protective checks run before a same-boundary strategy close handler.
The model does not simulate spread separately, partial fills, queue position, available volume, or funding. For strategies where one-minute ordering is still too coarse, use raw trades as an optional execution clock and define how those trades translate into achievable fills.
Read the output
- Thin, muted EMA lines show the signal regime without overpowering price.
- Entry, stop, target, and liquidation levels are short rails near the right edge and exist only while a position is active. They are keyed entities rather than historical series, so unrelated trades are never joined by vertical lines.
- Small up/down markers are fills; yellow crosses are exits.
- The compact
Position Regimestrip is green while long, red while short, and blank while flat. - The main-chart
EMA Spread (bps)pane shows trend direction and strength without mixing it into the price scale. - The dockable performance window charts marked equity, cumulative PnL, and current drawdown. Marked equity includes unrealized PnL while a position is open, so its curve can move before the trade closes.
- The table's
LIVErow is the current marked position. It retains up to 999 closed trades in chronological key order, including reason, side, prices, quantity, net PnL, and post-trade equity. - The anchored HUD shows whether the chart is replaying history or receiving realtime data, plus equity, open PnL, trade count, win rate, profit factor, maximum drawdown, and RSI.
This is a paper strategy and educational backtest model, not exchange execution or a guarantee of tradable performance.
Strategy runner template
Build a reusable long-and-short paper strategy with deterministic backtesting, live marking, fees, leverage, liquidation, a trade ledger, and a fixed HUD.
Viewport-anchored market HUD
Build a fixed market-information card that remains in the same pane corner as the chart pans and zooms.