FI QuantumQuantum strategy API

Quantum Strategy API

Strategies in FI Quantum are short JavaScript classes. The same file backtests over your recorded sessions and runs on a chart during a replay — and it can read everything Quantum draws: bars and delta, the footprint, market structure, key levels, TPO and volume value areas, and 183 indicators.

Overview

A strategy is a JavaScript class that extends Strategy. It runs in a sandbox inside Quantum: modern JavaScript works (class, extends, arrow functions, destructuring, template strings), but there is no file, network or timer access and no require/import. What a strategy can use is Strategy, console, the standard built-ins, and four names Quantum provides: market, broker, log and params.

The same script runs in two places:

  • Backtests walk your stored ticks or stored minute bars and fill orders in a simulator against the prints that actually happened.
  • Charts: a strategy armed on a chart during a replay session trades the replay simulator. Arming re-reads the file, so an edit is picked up the next time you arm it.

A backtest can never place a real order

Arming a strategy on a chart affects orders, so it always needs your approval in Quantum. In the current version, armed strategies run on replay sessions.

Workflow

  1. Open Strategies in Quantum. The library groups the bundled strategies by setup — trend, order flow, order book, structure, market profile — and each one is a readable example of the house style.
  2. Write or edit in the Editor. A file is compiled before it is saved, and one that does not compile is refused with the error and its line number.
  3. Run it: choose tick tapes or stored bars, the market, the dates and the settings. Start with a few days, read the statistics and the log, then widen the sample.
  4. Review the run in the Analyzer, which keeps every run so you can compare them. Record what you learned — including what did not work — in the file's header comment.

A strategy's shape

A complete strategy. The first line files it in the library; the header comment says what the rule is and what is known about it; the class declares its settings and answers one question per closed bar.

Delta continuation.js
// @group trend
// Delta continuation -- go with a bar that closed strong on real buying.
//
// THE RULE
//   On a bar that closes in its top fifth with positive delta, go long, stop
//   under the bar's low. Mirror for shorts. Built for NQ on 5-minute bars.
//
// Research tool, not advice. Test it on Sim or Playback.

class DeltaContinuation extends Strategy {

  static get label() { return "Delta continuation" }
  static get barSeconds() { return 300 }

  static get params() {
    return {
      minCloseLocation: { def: 0.8, lo: 0.5, hi: 1.0, real: true, group: "Entry",
        desc: "How near the bar's extreme it must close. 0.8 = the top fifth." },
      stopTicks:   { def: 40, lo: 4, hi: 400, group: "Stops",
        desc: "Fallback stop when the bar gives no usable level." },
      targetTicks: { def: 80, lo: 0, hi: 800, group: "Targets",
        desc: "Profit target. 0 means no target." },
      qty: { def: 1, lo: 1, hi: 20, group: "Size", desc: "Contracts per entry." },
    }
  }

  /** Consulted only while flat. 1 long, -1 short, 0 nothing. */
  signal() {
    const closedBar = market.bar(0)
    if (closedBar.close === undefined) return 0      // no bar yet
    if (closedBar.deltaFromBar) return 0             // delta missing, not zero

    const closeLocation = market.closeLocation(0)     // NaN on a doji
    if (closeLocation >= params.minCloseLocation && closedBar.delta > 0) return 1
    if (closeLocation <= 1 - params.minCloseLocation && closedBar.delta < 0) return -1
    return 0
  }

  /** The protective stop, as a PRICE. */
  suggestedStop(isLong) {
    const closedBar = market.bar(0)
    return isLong ? closedBar.low - market.tickSize : closedBar.high + market.tickSize
  }
}

DeltaContinuation

The last line must be the class name, on its own

A class declaration evaluates to nothing, so without that line Quantum cannot tell which class in the file is the strategy, and compile fails with a message saying so. A file may declare helper classes; the last line names the one to run.

Statics Quantum reads

StaticMeaning
static get label()Display name. Defaults to the class name.
static get params()Parameter declarations — they become the settings panel.
static get barSeconds()The bar the rule is written for (60, 300, 900 …). Used when a backtest does not name one. 0 or absent = any.
static get group()Library category. Read from the source text, so it must be a literal string; a first line of // @group trend does the same.

Lifecycle

MethodWhen it is called
signal()Once per closed bar, only while flat. Return 1 long, −1 short, 0 nothing. The base turns it into a bracketed market order. The usual override.
suggestedStop(isLong)At entry: the stop as a price, or NaN to fall back to stopTicks.
targetTicks(isLong)At entry: ticks to the target. Defaults to the targetTicks parameter (0 = none).
onBar()Every closed bar, flat or not. Override it to manage a position or to track state; call super.onBar() to keep signal() consulted.
onTick()Every print — about 2.3 million a day — in a backtest on tick tapes only. Not called on stored bars or on a chart. Backtests get much slower.
init()Once per instance: once per day in a backtest (each day gets a new instance), and on a chart each time it is armed or its settings change. Reset per-session state here.
maintain()Before onBar() every bar: applies the inherited break-even and trailing stop. Override with maintain() {} to switch both off.

State you keep on this survives from bar to bar within a run. A backtest builds a fresh instance each day, so do not rely on this across days — the value areas and prior-day levels in market do carry over. The base class also gives you this.ticksTo(price), this.trailStop(price) (which only ever moves the stop in the trade's favor) and this.breakEven(points, offsetTicks).

Parameters

params
static get params() {
  return {
    stackLevels: { def: 3, lo: 2, hi: 20, group: "Entry", desc: "Why this matters." },
    riskReward:  { def: 2.0, lo: 0.5, hi: 10, real: true, group: "Targets", desc: "..." },
    tradeShorts: { def: 1, lo: 0, hi: 1, group: "Entry", desc: "0/1 toggles render as switches." },
  }
}
FieldMeaning
defDefault. Required, numeric.
lo, hiBounds; values are clamped into them. hi < lo is refused as a typo.
realtrue for a decimal field; otherwise whole numbers.
labelControl label; defaults to the key.
descShown under the control. Say why, not what.
groupPanel section: Entry, Stops, Targets, Size, Diagnostics, or your own.
hiddentrue hides it. With lo == hi == def it is pinned.

Parameters are all numbers — use 0/1 for switches. Read them as params.stopTicks. Every strategy also inherits the stop management parameters below; restating one in your own params only changes its default. The base reads the conventional keys stopTicks, targetTicks and qty.

The five names

NameWhat it is
marketEverything you READ: bars, price, the studies, the footprint, market structure, the profiles, the order book.
brokerEverything you DO: buy, sell, flatten, cancelAll, moveStop, and position.
loglog("…") itself, plus log.warn, .error, .debug and .plot.
paramsWhat static get params() declared, under the name it declared.
contextAll of it in one object, for when that is what you mean — the same object as this.c.
Using the names
signal() {
  const squeeze = market.indicators.ttmSqueeze({ length: params.squeezeLength })
  if (squeeze.squeezeOn > 0) return 0          // compressed: stand aside

  const ladder = market.footprint(0)
  if (!ladder.cells) return 0                  // this bar has no footprint

  log("delta " + ladder.delta + " at " + market.price)
  return ladder.delta > 0 ? 1 : -1
}

Older strategies use this.c (the same object as market) and this.p (the same as params), with short names such as c.fp(n) for market.footprint(n), c.ms for market.structure and c.tpo(n) for market.marketProfile(n). They still work; new code should use the spelled-out names.

There is no bare c

this.c works; c on its own is a ReferenceError. The four names above are the only ones available without this., and a helper written outside the class that takes c as a parameter is fine — one that reaches for it is not. An audit of 72 strategies found this exact mistake breaking three of them on the first bar of every run.

Reading the market

Bars are indexed by barsAgo, as in NinjaScript: 0 is the bar that just closed — never the forming one. Anything out of range returns an empty object, not zeros, so a missing value reads as undefined.

Guard every read

if (bar.close === undefined) return 0, if (!priorDay.high) return 0. A comparison against undefined is silently false, and a zero that “is a price” would pass everything — which is why nothing here returns 0 for “missing”.

Bars and state

ReadReturns
market.price, .bid, .askLast trade and the inside market at this print.
market.tickSizeMinimum price increment (0.25 on NQ and ES).
market.barIndexIndex of the bar being reported.
market.bar(n)open high low close time volume trades delta deltaMin deltaMax cvd deltaFromBar. time is the bar's open in ms. deltaFromBar true means delta is missing, not balanced.
market.bar1m(n)A closed 1-minute bar, for finer checks on a slower chart.
market.closeLocation(n)0 at the low, 1 at the high; NaN on a zero-range bar.
market.atr(period)Average true range over the last period bars (a simple mean, in price); 0 until there are enough bars.
market.toTicks(distance)A price distance in ticks.
broker.positionqty (signed), avg, and long / short / flat.

Footprint

market.footprint(n) is a bar's ladder: poc total delta deltaMin deltaMax, and cells[] ascending by price — {price, bid, ask, delta, buyImb, sellImb, stacked, thin, buyAbs, sellAbs}. stacks[] lists same-side imbalance runs. The chart's own per-bar signals come with it: unfinished highs and lows, exhaustion, an absorption score, big buy and sell levels, the bar's value area and where its POC sits.

Footprint
// one run of six or more buy imbalances -- not two runs of three
const deepBuyStack = market.footprint(0).stacks.some(run => run.isBuy && run.levels >= 6)

// the chart's own stacked-imbalance arrow for this bar: 1, -1 or 0
const arrow = market.stackedImbalance(0, { minLevels: 3, closePercent: 0.8, priorBars: 2 })

Selectivity matters more than the signal: a stacked run of three or more appears on a large share of ordinary bars. Check how often a gate fires before trusting what follows it.

Market structure

ReadReturns
dir1 bullish, −1 bearish, 0 undecided.
bosNow, chochNow1 / −1 / 0: a break of structure or change of character fired on THIS bar.
bos[]Every break so far, with the zone that caused it and whether price has since closed through it (a breaker).
lastBreak{isBullish, isChoch, price}.
pivots[]Every confirmed swing: price, bar, strength (8 major, 5 minor).
strongHigh, strongLowThe range, and the chart's names for it.
fib50, fib618, fibExtFib levels of the range; −1 when not valid.
zones[]Supply and demand zones off the swings, with whether each has broken.

market.structure2m, structure15m and structure1h give the same read on higher timeframes, as of their last closed bar.

Market structure
const structure = market.structure
if (structure.dir === undefined) return 0      // not enough bars yet

// Test the EVENT, not a level: bosNow / chochNow are non-zero only on the bar
// that broke. A level is present on that bar and a hundred bars later.
if (structure.bosNow === 1 && !structure.lastBreak.isChoch) {
  log("bullish break of structure at " + structure.lastBreak.price)
}

// The 15-minute read, as of its last CLOSED bar. Its bar numbers are 15-minute
// bars, so compare with structure15m.bar, never with market.barIndex.
const higherTimeframe = market.structure15m
if (higherTimeframe.dir === 1) { /* 15-minute structure is bullish */ }

Key levels

market.priorDay(n) is a completed session — 0 is yesterday: high low open close range, the initial balance (ibHigh ibLow ibMid), the opening range (orHigh orLow) and the premarket (pmHigh pmLow). market.today() is the session in progress, and market.priorWeek(n) the last completed weeks. All are resolved by time, so they never show a session that has not finished. In a backtest they are built from the days the run walks, so the first day of a run has no prior day — start a run a day before the first day you care about.

Key levels
signal() {
  const priorDay = market.priorDay(0)          // yesterday's completed session
  if (!priorDay.high) return 0                 // no completed session yet
  const closedBar = market.bar(0)

  // Fade the first close back inside after a probe above yesterday's high.
  if (closedBar.high > priorDay.high && closedBar.close < priorDay.high) return -1
  return 0
}

Value areas and market profile

ReadReturns
market.marketProfile(n)A completed RTH session's TPO profile (30-minute periods, 70% value area): poc vah val ibHigh ibLow high low. Write market-profile rules such as the 80% rule against this one.
market.marketProfile(n, desk)The same, for one auction: "asia", "london" or "newyork". n counts that desk's own sessions.
market.valueArea(n)A completed session's volume profile: poc vah val volume.
market.valueArea(n, desk)The volume profile of one desk. Can answer { pending: true } the first time — see below.
market.measuredMove(n, seconds)The impulse, the fib that triggered it, the −23.6% target, and whether targetHit or stopHit has happened. 0 seconds is this strategy's own series.
market.ssl(n)The SSL channel: line (a natural stop) and state (1 green, −1 red). ssl1m and ssl5m read the 1- and 5-minute channels.
market.momentum(n)Simple Momentum — the Heikin-Ashi SuperTrend drawn as arrows. signal is the flip itself (1, −1 or 0), trend the state on every bar. { raw: true } drops the EMA and SSL gates.
Value areas
// The 80% rule is written against the MARKET PROFILE (TPO), not volume.
const profile = market.marketProfile(0)        // yesterday's RTH TPO profile
if (!profile.vah) return 0                     // no such session

const volume = market.valueArea(0)             // yesterday's VOLUME profile
log("TPO value " + profile.val + "-" + profile.vah + ", POC " + profile.poc +
    " | volume POC " + volume.poc + " | IB " + profile.ibLow + "-" + profile.ibHigh)

The TPO profile also reports the shape of each end — buyingTail and sellingTail as { low, high } price bands, poorHigh / poorLow for an extreme left without excess, and singlePrints for the mid-profile gaps. The class reference has every field, the desk windows, and the measured-move fields.

A desk volume area can answer “not yet”

market.valueArea(n, "asia") is the one read that can return { pending: true }: the desk volume cut is a second walk of the tick log and asking is what starts it. Test for poc, not for emptiness — pending means “not yet” and an empty map means “no such session”. In a backtest neither is ever pending.

Timeframes are named

Anything that reads another timeframe takes one by name: "chart" (the series the strategy runs on), "1m", "2m", "3m", "5m", "10m", "15m", "30m", "1h", "2h", "4h", "1d" — market.timeframes lists them. A name this run cannot serve throws with the reason rather than reading as nothing.

The one exception is market.measuredMove(n, seconds), which takes a number of seconds — 0 for this strategy's own series, 300 for 5 minutes, 900 for 15 — because the named set could not answer “5 minute” at all. It returns an empty map for a series this run cannot serve, rather than throwing.

Timeframes
market.bar(0, "15m")            // or market.bar15m(0)
market.footprint(0, "5m")        // or market.footprint5m(0)
market.ssl(0, "5m")              // chart, 1m and 5m only
market.structureAt("15m")        // the same object as market.structure15m
market.fairValueGaps("15m", { unfilledOnly: true })
market.indicators.rsi({ period: 14 }, 0, "15m")   // settings, barsAgo, timeframe

// barsAgo counts bars of THAT timeframe, and every bar number a higher
// timeframe reports is in its own bars — compare with its .bar, not barIndex.
market.timeframes                // every name this run can serve

183 indicators

The full list — every parameter, default and plot, with an example for each — is in the searchable indicator catalog.

Every indicator in Quantum's library is a named function on market.indicators — trend, momentum, volatility, volume and structure studies. Plots come back in their own spelling and in camelCase, and parameter names ignore case.

Indicators
const kama    = market.indicators.kama({ period: 20 }).kama
const squeeze = market.indicators.ttmSqueeze({ length: 20 })   // every plot
const rsi15m  = market.indicators.rsi({ period: 14 }, 0, "15m") // another timeframe
const hammer  = market.indicators.candlestickPattern({ Pattern: "Hammer" }) // choices by name
const fast    = market.indicators.ema({ period: 9 }).ema

// Nothing fails quietly: a typo in a parameter is refused by name.
market.indicators.choppinessIndex({ Perod: 14 })
// TypeError: ChoppinessIndex has no parameter "Perod". It takes: Period

Each function also describes itself: market.indicators.ttmSqueeze.params and .plots. Warm-up values come back undefined, never 0.

Order book

market.book.available first, always. The book exposes the same stats strip the DOM shows — book ratio, liquidity, imbalance, trade rate, absorption and more — through market.book.stats(), .stat(key) and .series(key, n).

There is no order book in a backtest

Every book read is empty there. A DOM rule can only be evaluated live or on a replay.

Orders and stops

Usually you never place an order: return 1 or −1 from signal() and the base places a market order with a protective bracket. To manage a trade yourself, use broker.buy / broker.sell (always with stopTicks > 0 — an entry without a stop is refused), broker.flatten(), broker.cancelAll() and this.trailStop(price). There is no pyramiding: an entry is refused while a position is open.

Managing a position
// Usually you never call these -- return 1 or -1 from signal() and the base
// places a bracketed market order. When you manage a trade yourself:
onBar() {
  if (broker.position.long && market.bar(0).close < this.exitLevel) {
    broker.flatten()                           // close at market, drop the bracket
  }
  super.onBar()                                // keep consulting signal() while flat
}

// broker.buy({ stopTicks: 20, targetTicks: 40, qty: 1 })  -- ALWAYS give a stop

Stop management every strategy inherits

Parameter (default)Meaning
autoTrail (1)Walk the resting stop behind a trail source.
trailSource (0)0 this chart's SSL, 1 the 1-minute SSL, 2 the 5-minute SSL, 3 ATR behind price.
trailSlackTicks (12)How far behind the SSL line the stop sits.
atrTrailMult (2.0), atrTrailPeriod (14)For trail source 3.
breakEvenPoints (20)Move the stop to entry once this many points ahead; 0 disables.
breakEvenOffsetTicks (2)Ticks beyond entry for break-even.

For a rule that must hold its stop, pin them off — autoTrail: { def: 0, lo: 0, hi: 0, hidden: true } — or override maintain() {}.

Logging

log(text), log.warn and log.error write to the run's log, stamped with the bar time in New York (log.debug lines are kept only after log.level("debug"), which belongs in init()). log.plot(name, value) records up to eight named series against bar time. Log every entry and every veto with the numbers that decided it: a run with no trades says nothing about why, and the log is how you debug a rule you cannot watch. Never log from onTick().

Backtesting

OptionMeaning
Data: tick tapesEvery print with the bid and ask, so order flow is real. Only the days you downloaded for replay. Use it for order-flow rules.
Data: stored barsRecorded 1-minute bars walked as a price path — about 90 days per contract. Volume and delta are estimates, and there is no footprint or order book. Use it for bar and level rules.
Bar periodThe bar onBar is called for; defaults to the strategy's barSeconds.
RTH onlyOnly call the strategy 09:30–16:00 ET (series still build overnight).
SlippageOne tick per side by default — the honest floor.
SettingsOverride any parameter for a single run.

A run reports trades, win rate, profit factor, drawdown, average MAE and MFE, and a t-statistic with its standard error — read that before the net. Each trade lists entry, exit, ticks, MAE, MFE, bars held and why it closed (target, stop, signal or end of session).

Hypothetical results have limits

A backtest is simulated on historical data and is not a record of actual trading. Thirty trades prove little; fix your thresholds before looking at new days, because a rule fitted to the days it is judged on will look good and then fail.

Sample strategies

Two complete strategies in the house style. Both were compiled and run in Quantum before being published here. Paste one into a new strategy in the Strategies editor to run it or change it.

Goes with the fast EMA crossing the slow one. The cross is found in onBar(), not signal(), because signal() is skipped while a position is open and would miss every bar held in a trade.
EMA crossover.js
// @group trend
// EMA crossover -- go with the fast average crossing the slow one.
//
// THE RULE
//   When the fast EMA crosses above the slow EMA on a closed bar, go long.
//   When it crosses below, go short. The stop sits beyond the recent swing
//   and the target is a multiple of that risk. Built for NQ on 5-minute bars.
//
// WHY THE CROSS, NOT THE STATE
//   "fast > slow" is true on every bar of a trend. The cross -- the sign of
//   (fast - slow) changing -- is the event, so that is what is tested.
//
// WHAT IS KNOWN
//   A documentation sample. Untested; record results here as they come in.
//
// Research tool, not advice. Test it on Sim or Playback.

class EmaCrossover extends Strategy {

  /** @returns {string} what the library and the logs call it */
  static get label() { return "EMA crossover" }

  /** @returns {number} the bar this rule is written for, in seconds */
  static get barSeconds() { return 300 }

  /** @returns {Object} parameter declarations; these become the settings panel */
  static get params() {
    return {
      fastPeriod:  { def: 9, lo: 2, hi: 100, group: "Entry",
        desc: "The fast average. Shorter reacts sooner and whipsaws more." },
      slowPeriod:  { def: 21, lo: 3, hi: 400, group: "Entry",
        desc: "The slow average the fast one has to cross." },
      tradeShorts: { def: 1, lo: 0, hi: 1, group: "Entry",
        desc: "0 takes longs only." },
      swingBars:   { def: 5, lo: 1, hi: 50, group: "Stops",
        desc: "How many closed bars back to look for the swing the stop sits beyond." },
      stopTicks:   { def: 40, lo: 4, hi: 400, group: "Stops",
        desc: "Fallback stop when there are not enough bars to find a swing." },
      riskReward:  { def: 2.0, lo: 0.5, hi: 10, real: true, group: "Targets",
        desc: "Target as a multiple of the distance to the stop." },
      qty:         { def: 1, lo: 1, hi: 20, group: "Size", desc: "Contracts per entry." },
    }
  }

  /** Once per day, before the walk: forget yesterday's averages. */
  init() {
    this.previousSpread = undefined
    this.crossDirection = 0
  }

  /**
   * Every closed bar, flat or not. The cross is found here rather than in
   * signal(), because signal() is skipped while a position is open and would
   * miss every bar held in a trade.
   */
  onBar() {
    const fast = market.indicators.ema({ period: params.fastPeriod }).ema
    const slow = market.indicators.ema({ period: params.slowPeriod }).ema
    this.crossDirection = 0
    if (fast !== undefined && slow !== undefined) {   // undefined while warming up
      const spread = fast - slow
      if (this.previousSpread !== undefined) {
        if (this.previousSpread <= 0 && spread > 0) this.crossDirection = 1
        if (this.previousSpread >= 0 && spread < 0) this.crossDirection = -1
      }
      this.previousSpread = spread
    }
    super.onBar()   // still consult signal() while flat
  }

  /** Consulted only while flat: act on a cross that happened on this bar. */
  signal() {
    if (this.crossDirection === 1) {
      log("EMA " + params.fastPeriod + " crossed above " + params.slowPeriod + " at " + market.price)
      return 1
    }
    if (this.crossDirection === -1 && params.tradeShorts) {
      log("EMA " + params.fastPeriod + " crossed below " + params.slowPeriod + " at " + market.price)
      return -1
    }
    return 0
  }

  /**
   * The stop, as a PRICE: one tick beyond the lowest low (long) or highest
   * high (short) of the last few closed bars.
   * @param {boolean} isLong
   * @returns {number}
   */
  suggestedStop(isLong) {
    let swingLow = Infinity, swingHigh = -Infinity
    for (let barsAgo = 0; barsAgo < params.swingBars; ++barsAgo) {
      const closedBar = market.bar(barsAgo)
      if (closedBar.close === undefined) return NaN    // not enough bars: use stopTicks
      swingLow = Math.min(swingLow, closedBar.low)
      swingHigh = Math.max(swingHigh, closedBar.high)
    }
    return isLong ? swingLow - market.tickSize : swingHigh + market.tickSize
  }

  /**
   * Target in ticks: the stop distance times riskReward.
   * @param {boolean} isLong
   * @returns {number}
   */
  targetTicks(isLong) {
    const stopPrice = this.suggestedStop(isLong)
    const riskTicks = isNaN(stopPrice)
      ? params.stopTicks
      : market.toTicks(Math.abs(market.price - stopPrice))
    return Math.max(1, Math.round(riskTicks * params.riskReward))
  }
}

EmaCrossover

Quantum also ships with working strategies to read and adapt, including the 80 Percent Rule, Break of Structure, Failed Breakout to POC, LVN Pullback Continuation, Stacked Imbalance, SSL Flip and Structure Break CVD.

Writing them with the AI

Quantum's AI reads this same reference before it writes a strategy. Describe a setup — “backtest a BOS retest on my last 20 sessions”, “test an EMA 9/21 crossover and compare” — and it writes the file, compiles it, runs the backtest and walks you through the log. The file lands in your library like any other, so you can read and change every line.

An outside client — Claude Code, Claude Desktop or any MCP client — can drive Quantum the same way over a local connection. See the AI Control tool reference.

AI Control uses your own AI subscription or a local model. See AI Control.

Pitfalls

Forgetting the last line

The file must end with the class name on its own line.

Running on the timeframe you meant to read

A backtest replays one day at a time and the strategy's own bar series starts empty each morning, so a coarse series never prints enough bars to confirm a swing: on 1-hour bars market.structure finds nearly nothing and the rule takes zero trades while looking merely unprofitable. Write it as a 5-minute strategy reading structure1h instead — those higher series are carried across days and hold the whole history. Under about 50 bars a day, the backtest log now says so.

Reaching for a bare c

market, broker, params and log are the only names available without this. Anything else, c included, is a ReferenceError on the first bar.

market.bar1m() on a coarse run

A backtest folds its timeframes upward and cannot make a minute bar out of 5-minute ones, so this raises rather than returning an empty object. Guard it by the run's own period, not by testing the result.

Reading the future

market.bar(0) is the bar that just closed; there is no access to the forming bar, and higher-timeframe reads are cut by time. Do not work around it, for example by caching values in init().

Treating missing as a number

Empty objects and undefined mean no answer. Guard every level read: a missing gate fails closed in a plain test and OPEN in a negated one.

Keying on a state instead of an event

fast > slow is true on every bar of a trend; the cross is the event. The same goes for market.structure.bosNow versus a structure level.

Updating indicators inside signal()

signal() is skipped while a position is open, so a running value updated there misses every bar held in a trade. Update in onBar() and call super.onBar().

Relying on the trail in a backtest

A backtest cannot amend a resting order, so the stop stays where it was placed. Keep a bar-close exit as the fallback.

Order book rules in a backtest

There is no book in a backtest; test DOM rules on a replay.

Backfilled delta

deltaFromBar true means delta is missing, not zero. On stored bars, delta is an estimate and there is no footprint; order-flow rules need tick tapes.

Small samples and zero slippage

A handful of trades proves nothing, and a result that only works at zero slippage is not a result.

onTick cost

Defining onTick() makes every print a call. Only define it when a rule truly needs per-print timing.

Futures trading involves substantial risk of loss. The strategies on this page document Quantum's scripting API; they are not trading advice or a recommendation to trade any particular way. Backtest results are hypothetical and have inherent limitations. Test any strategy on a replay or simulation account first. See our full disclaimer.