MMTMMT Docs

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 Regime strip 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 LIVE row 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.

On this page