FI QuantumQuantum class reference

Quantum Class Reference

Every object a Quantum strategy can use, member by member: its type, its units, and what it returns when there is no answer. For how a strategy is put together, start with the strategy guide; for the 183 indicators, see the indicator catalog.

Missing is never zero — except where it says so

Out-of-range reads return an empty object {}, so their fields read undefined. Some fields use a sentinel instead, and the tables below say which: -1 for a bar index or a structure level that does not exist, NaN for a ratio with no answer, and 0 for a session level that has not formed. Guard every read before you compare it.

Globals

In scope in every strategy
// Everything a strategy can reach, besides the standard JavaScript built-ins:
market     // everything you READ
broker     // everything you DO
log        // log("…"), log.warn, log.error, log.plot
params     // your resolved parameters
context    // all of it in one object (the same as this.c)
console    // console.log / .warn / .error route to the same log

There is no file, network or timer access, and no require or import. Older strategies use this.c (the same as context) and this.p (the same as params); both still work.

Strategy

The base class every strategy extends. The file's last line must be the class name, and the class must override at least one of signal, onBar or onTick.

Statics

StaticTypeMeaning
labelstringDisplay name. Defaults to the class name.
barSecondsnumberThe bar the rule is written for (60, 300, 900 …). 0 or absent = any.
paramsobjectParameter declarations — see the table below. They become the settings panel.
groupstringLibrary category, read from the source text — so it must be a literal. // @group trend as the first line does the same.

Parameter fields

FieldDefaultMeaning
defrequiredThe default value. A missing or NaN def is refused.
lo0Lower bound. Values are clamped into [lo, hi].
hi1000Upper bound. hi < lo is refused as a typo.
realfalsetrue for a decimal; otherwise the value is rounded to a whole number.
labelthe keyControl label.
desc—Shown under the control. Say why, not what.
group—Panel section.
hiddenfalseHides it from the panel; with lo == hi == def it is pinned.

Methods you override

MethodReturnsWhen it runs
signal()1, -1 or 0Once per closed bar, only while flat — consulted by the base onBar(). The base turns 1/-1 into a bracketed market order.
suggestedStop(isLong)price or NaNAt entry. A price for the stop, or NaN to use params.stopTicks.
targetTicks(isLong)ticksAt entry. Defaults to params.targetTicks (0 = no target).
onBar()—Every closed bar, flat or not. Call super.onBar() to keep signal() consulted.
onTick()—Every print, in a backtest on tick tapes only. market.bar(0) is still the last closed bar.
init()—Once per instance: once per day in a backtest, and on a chart each time it is armed or its settings change.
maintain()—Before onBar() every bar: applies break-even, then the trail. Override with maintain() {} to switch both off.

Helpers on this

MemberReturnsMeaning
this.ticksTo(price)whole ticks ≥ 0Distance from the last price to a level.
this.trailStop(price)true if it movedMoves the resting stop, but only in the trade's favor. Always false in a backtest, which cannot amend a resting order.
this.breakEven(points, offsetTicks)true if it movedMoves the stop to entry ± offset once the trade is `points` ahead (points, not ticks).
this.c / this.contextcontextThe same object as the global context (and market).
this.p / this.paramsobjectThe resolved parameters.
Opening drive.js
class OpeningDrive extends Strategy {
  static get label() { return "Opening drive" }
  static get barSeconds() { return 300 }
  static get params() {
    return {
      minRangeTicks: { def: 40, lo: 4, hi: 400, group: "Entry",
        desc: "The first bar must be at least this wide." },
      stopTicks: { def: 30, lo: 4, hi: 400, group: "Stops", desc: "Fallback stop." },
    }
  }

  init() { this.tookTrade = false }            // once per instance (per day in a backtest)

  signal() {
    const bar = market.bar(0)
    if (bar.close === undefined || this.tookTrade) return 0
    if (bar.nyMin !== 935) return 0              // only the 9:35 bar (the first RTH 5-minute bar)
    if (market.toTicks(bar.high - bar.low) < params.minRangeTicks) return 0
    this.tookTrade = true
    return market.closeLocation(0) > 0.5 ? 1 : -1
  }

  suggestedStop(isLong) {                       // a PRICE, or NaN for params.stopTicks
    const bar = market.bar(0)
    return isLong ? bar.low - market.tickSize : bar.high + market.tickSize
  }
}

OpeningDrive

A throw from any method stops the run and is reported with its method and line, e.g. signal, line 14: …. On a chart it also disarms the strategy.

market

Everything a strategy reads. Its properties are read live, so they are always current. Bar arguments are barsAgo: 0 is the bar that just closed, never the forming one.

Properties

PropertyTypeMeaning / when missing
pricepriceLast traded price when the bar is processed.
bid, askpriceThe inside market.
tickSizepriceMinimum increment (0.25 on NQ and ES). Older name: tick.
pointValuecurrencyPer point per contract; 0 means report ticks only.
timeepoch msThe time of the bar being processed.
barIndexintIndex of the bar being reported; -1 with no bars. Older name: i.
barsintSize of the bar series.
domSamplesintHow many order-book samples are held. 0 in a backtest.
structureStructureThis chart's market structure — see Structure. Older name: ms.
structure2m, structure15m, structure1hStructureHigher timeframes, as of their last closed bar.
bookobjectThe order book — see Order book.
indicatorsobject183 indicator functions — see the catalog. Alias: ta.

Methods

MethodReturnsWhen missing
bar(n = 0, timeframe = "chart")Bar{} out of range; throws on a timeframe this run cannot serve
bar1m(n = 0)Bar (subset) + ageSec{}
footprint(n = 0, timeframe = "chart")Footprint{} when the bar has no ladder
footprint1m … footprint1h(n)Footprint{}; throws on a timeframe this run cannot serve
structureAt(timeframe)StructureThe same object as structure15m and friends.
timeframesstring[]Every timeframe name this run can serve.
stackedImbalance(n, opts)1, -1 or 00
fairValueGaps(timeframe?, opts?)array, newest first[]; throws on an unknown option or timeframe. Named forms too — fairValueGaps1m, 5m, 15m, 30m, 1h — each taking the options object.
priorDay(n = 0)Session{}
today()Session{} outside a session
priorWeek(n = 0)Week{}
valueArea(n = 0, desk?)Volume profile{}; { pending: true } while a desk cut is still being built
marketProfile(n = 0, desk?)TPO profile{}; {} too for a desk name it does not know
measuredMove(n = 0, seconds = 0)Measured move{} when there is no such move, or no such series
measuredMoveCount(seconds = 0)int0
momentum(n = 0, opts?)Momentum{} when the host offers no momentum at all — which signal: 0 could not say. Older name: simpleMomentum is the long form of c.momentum.
ssl(n, timeframe = "chart")SSL{} while warming up. chart, 1m and 5m only; ssl1m(n) and ssl5m(n) are the named forms.
closeLocation(n = 0)0 … 1NaN out of range or on a zero-range bar
atr(period = 14)price0 until there are enough bars. A simple mean of true range.
toTicks(distance)ticks (fractional, signed)0 if tickSize is 0
Reading the market
const bar = market.bar(0)                        // the bar that just closed
const fifteen = market.bar(0, 900)               // the last closed 15-minute bar
const minute = market.bar1m(0)                   // the last closed 1-minute bar
const where = market.closeLocation(0)            // 0 at the low .. 1 at the high
const atr = market.atr(14)                       // average true range, in price
const ticks = market.toTicks(bar.high - bar.low) // price distance in ticks
log("last " + market.price + " on bar " + market.barIndex)

Timeframes

Anything that reads another timeframe takes one by name. market.timeframes lists what this run can serve: chart (the series the strategy runs on), own, and 1m, 2m, 3m, 5m, 10m, 15m, 30m, 1h, 2h, 4h, 1d. A number of seconds still works, for strategies written before the names existed.

Timeframes
market.timeframes            // every name this run can serve
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

market.bar(0, "7m")
// Error: market.bar(): no timeframe "7m". It takes: chart, own, 1m, 2m, 3m,
// 5m, 10m, 15m, 30m, 1h, 2h, 4h, 1d -- or a number of seconds

Bar numbers belong to their own timeframe

barsAgo counts bars of the timeframe you asked for, and every bar number a higher timeframe reports is in its own bars — compare with its .bar, never market.barIndex. A name this run cannot serve throws with the reason, rather than reading as nothing. Live, the timeframes are the ones the feed keeps; in a backtest they are folded from the walk.

Bar

Returned by market.bar(n) (and bar(n, 900) for a higher timeframe). market.bar1m(n) returns the fields marked “1m” plus ageSec.

FieldUnitsMeaning
timeepoch msThe bar's OPEN. (1m)
open, high, low, closeprice(1m)
volumecontracts(1m)
deltacontractsAggressive buys minus aggressive sells. (1m)
deltaMin, deltaMaxcontractsHow far running delta reached during the bar, relative to its open.
cvdcontractsSession cumulative delta at the bar's close. (1m)
tradesintPrints folded in; 0 on a backfilled bar.
deltaFromBarboolTrue when delta came from a backfilled bar — it is missing, not balanced. (1m)
nyMinminutesMinutes since midnight New York time (570 = 9:30).
dow1 … 7Day of week, 1 = Monday.
ageSecsecondsbar1m only: how old that minute bar is.
Bar
const bar = market.bar(0)
if (bar.close === undefined) return 0            // not enough bars yet
if (bar.deltaFromBar) return 0                   // delta is MISSING here, not zero

// A bar that ran to -400 and closed at -380 was one-sided; one that ran to
// -400 and closed at -100 changed its mind. Same net delta, different trade.
const oneSidedSelling = bar.delta < 0 && bar.delta <= 0.8 * bar.deltaMin

// Time filters without date arithmetic
const isFirstHour = bar.nyMin >= 570 && bar.nyMin < 630   // 9:30–10:30 New York
const isFriday = bar.dow === 5

Footprint

Returned by market.footprint(n): the bar's ladder and the chart's own per-bar signals.

Tick tapes only

A backtest on stored bars has no footprint — every read is {}. Order-flow rules need the tick-tape data source.
FieldTypeMeaning
pocpriceBusiest price in the bar.
totalcontractsVolume.
delta, deltaMin, deltaMaxcontractsNet delta and its running excursions.
open, high, low, closepriceThe ladder's own OHLC.
cells[]Cell[]Every price level, ascending — see below.
stacks[]Stack[]Same-side imbalance runs — see below.
unfinishedHigh, unfinishedLowboolOpposing volume at the extreme: an unfinished auction.
exhaustion, exhaustionBearishboolExhaustion print at the extreme, and its side.
absorptionScore0 … 5How strongly the bar shows absorption.
absBuyCount, absSellCountintLevels flagged as absorbing each side.
absorptionBullishboolWhich side the absorption favors.
maxLevelDelta, minLevelDeltacontractsThe most positive and most negative single-level delta.
bigBuyLevel, bigSellLevelpricePrice of an outsized print (3× the mean); 0 when none.
vaHigh, vaLowpriceThe bar's own 70% value area; 0 when none.
pocPct0 … 100Where the POC sits in the bar: 0 at the low, 100 at the high; -1 on a zero-range bar.

Cell (cells[])

FieldTypeMeaning
pricepriceThe level.
bidcontractsTraded at the bid — sellers hitting.
askcontractsTraded at the ask — buyers lifting.
deltacontractsask − bid.
buyImb, sellImbboolThe chart's diagonal imbalance flags.
stackedboolPart of a same-side imbalance run.
thinboolA low-volume gap level inside the bar.
buyAbs, sellAbsboolAbsorption flags.

Stack (stacks[])

FieldTypeMeaning
low, highpriceThe run's range.
levelsintHow many consecutive imbalanced levels.
isBuyboolBuy-side or sell-side run.

market.stackedImbalance(n, opts) returns the chart's stacked-imbalance arrow for a bar: 1, −1 or 0. Options (all optional): minLevels (3), closePercent (0.8), priorBars (2), clearHighs (true).

Footprint
const ladder = market.footprint(0)
if (!ladder.cells) return 0                      // no ladder (e.g. a stored-bar backtest)

// One run of six or more buy imbalances -- not two runs of three
const deepBuyStack = ladder.stacks.some(run => run.isBuy && run.levels >= 6)

// Where the heaviest selling sat inside the bar
let heaviestBid = ladder.cells[0]
for (const cell of ladder.cells) if (cell.bid > heaviestBid.bid) heaviestBid = cell

// The chart's own signals, computed for you
if (ladder.unfinishedHigh || ladder.exhaustion) log("auction looks done at " + ladder.high)
const arrow = market.stackedImbalance(0, { minLevels: 3, closePercent: 0.8, priorBars: 2 })

Structure

market.structure — FI Market Structure for this chart. The whole object is {} until there are enough bars, so test dir !== undefined first.

FieldTypeMeaning / when missing
dir1, -1, 0Bullish, bearish, undecided.
bosNow, chochNow1, -1, 0A break of structure / change of character fired ON THIS BAR. This chart only.
lastBreak{ isBullish, isChoch, price }The most recent break; absent when there are none.
breaksintHow many breaks so far — watch it go up.
bos[]Break[]Every break, oldest first — see below.
strongHigh, strongLowpriceThe range; 0 when unset.
highName, lowNamestringThe chart's names: Strong / Weak / Swing High, Strong / Weak / Swing Low.
strongHighBar, strongLowBarintBar index of each; -1 when none. …BarsAgo variants too.
internalHigh, internalLowprice-1 when there is none (zero is a price).
internalHighBar, internalLowBarint-1 when none. …BarsAgo variants too.
fibsValidboolWhether the fib levels below are meaningful.
fib50, fib618, fibExtprice50%, 61.8% and the −23.6% extension; -1 when not valid.
fibBearishboolDirection of the fib.
pivots[]Pivot[]Every confirmed swing, oldest first.
zones[]Zone[]Supply and demand zones off the swings.
barintHigher timeframes only: that timeframe's last closed bar.

Break (bos[])

FieldTypeMeaning
idintPosition in the log, which is never pruned or reordered.
pricepriceThe level that broke.
startBar, endBarintWhere the broken swing started and the bar that broke it.
isBullish, isChochboolDirection, and whether it was a change of character.
zoneTop, zoneBot, zoneBarprice / intThe break's own zone (the order block that caused it); 0 when it has none.
zoneWhystringWhy there is no zone, when there is none.
zoneBroken, zoneBrokenAtBarbool / intPrice has since closed through the zone — it is now a breaker. -1 while it holds.
revHigh, revLowpriceThe reversal swing's range.

Pivot (pivots[])

FieldTypeMeaning
pricepriceThe swing.
bar, barsAgointWhere it formed.
isHighboolSwing high or swing low.
strength8 or 58 = major, 5 = minor.

Zone (zones[])

FieldTypeMeaning
idintStable while the strategy runs (the list itself is trimmed and re-sorted).
top, botpriceThe zone.
isSupplyboolSupply or demand.
broken, brokenAtBarbool / intWhether price closed through it; -1 while it holds.
bar, createdBarintThe swing's bar and when the zone was created.

A break's zone is not in zones[]

zones[] are zones off the swings; the order block that caused a break lives on that break in bos[]. For breakers and order-block retests, read bos[]. On higher timeframes, every bar number is in that timeframe's bars — compare with .bar, never market.barIndex — and there is no bosNow.
Structure
const structure = market.structure
if (structure.dir === undefined) return 0        // not enough bars yet

// EVENTS fire on the bar that broke: test these, not a level
if (structure.bosNow === 1 && !structure.lastBreak.isChoch) {
  const lastBreak = structure.bos[structure.bos.length - 1]
  log("bullish BOS at " + lastBreak.price +
      (lastBreak.zoneTop ? ", order block " + lastBreak.zoneBot + "-" + lastBreak.zoneTop : ""))
}

// The range, with the chart's own names for it ("Strong Low", "Weak High" …)
log(structure.lowName + " " + structure.strongLow + " / " + structure.highName + " " + structure.strongHigh)

// Unbroken demand zones below price
const demand = structure.zones.filter(z => !z.isSupply && !z.broken && z.top < market.price)

// Higher timeframe, as of its last CLOSED bar -- compare bars with .bar, not barIndex
const htf = market.structure15m
const htfJustBroke = htf.bos && htf.bos.length && htf.bos[htf.bos.length - 1].endBar === htf.bar

Fair value gaps

market.fairValueGaps(opts) — the gaps on this chart, newest first, filtered by the options you pass. An option it does not have is refused by name.

Backtests and replays of the chart's own series

On a chart the gaps are available for higher timeframes (via periodSeconds); for the chart's own series they currently come back empty. In backtests both work.

Options

OptionDefaultMeaning
bullishbothtrue for bullish gaps only, false for bearish only.
unfilledOnlyfalseOnly gaps price has not filled.
includeBrokenfalseBroken gaps are left out unless this is true.
minTicks, maxTicks0 (off)Gap size filter, in ticks.
maxAgeBars0 (off)Only gaps from the last N bars.
limit0 (all)Most gaps to return.
timeframe"chart"Another timeframe by name — or pass it as the first argument. periodSeconds still works.
minBodyPct0The impulse bar's body as a share of its range.
minVolMult, minDeltaMult0The impulse bar's volume or |delta| as a multiple of the prior average.
displacementLookback20Bars in that average.
mitigatedOnly, unmitigatedOnlyfalseFilter by mitigation.
mitigationPct0.5How far into the gap counts as mitigated (0 … 1).

Gap

FieldTypeMeaning
top, bot, midpriceThe gap and its midpoint (ce is the same as mid).
sizeTicksticksWidth.
bullishboolSide.
bar, barsAgointThe confirming bar.
filled, filledBarbool / int-1 when not filled.
fillPct, fillMax0 … 1How far price is into it now, and the deepest it has been.
touchingboolPrice is in it but has not filled it.
broken, brokenBarbool / int-1 when not broken.
invertedboolFilled but not broken.
mitigated, mitigatedBar, mitigatedBarsAgobool / int-1 when not mitigated.
bodyPct, volMult, deltaMultnumberThe impulse bar's measurements; -1 when they cannot be computed.
Fair value gaps
// Unfilled gaps from the last 50 bars, newest first
const gaps = market.fairValueGaps({ unfilledOnly: true, maxAgeBars: 50, limit: 5 })
for (const gap of gaps) {
  if (gap.bullish && market.price <= gap.top && market.price >= gap.bot) {
    log("price is inside a bullish gap " + gap.bot + "-" + gap.top +
        " (" + gap.sizeTicks + " ticks, " + Math.round(gap.fillPct * 100) + "% filled)")
  }
}

// Only gaps made by a real impulse bar
const strong = market.fairValueGaps({ minBodyPct: 0.6, minVolMult: 1.5 })

// A misspelled option is refused by name, with the list of real ones
market.fairValueGaps({ unfiledOnly: true })
// Error: fairValueGaps(): has no option "unfiledOnly". It takes: bullish, unfilledOnly, …

Session levels

FieldMeaning
date"yyyy-MM-dd"
open, high, low, closeThe RTH session (09:30–16:00 New York).
ibHigh, ibLow, ibMidInitial balance (the first hour, to 10:30).
orHigh, orLowOpening range (to 10:00).
pmHigh, pmLowPremarket, 04:00–09:30.
rangehigh − low.

market.priorDay(n) returns a completed session (0 is the most recent) and market.today() the one in progress. A level that has not formed yet is 0. market.priorWeek(n) returns { open, high, low, close, range, from, to }.

The first day of a backtest has no prior day

Session levels, value areas and TPO profiles are built from the days a run walks. Start a run a day before the first day you want to test, and a week before for priorWeek. After the 16:00 close, priorDay(0) is already today's session.
Session levels
const priorDay = market.priorDay(0)            // yesterday's completed RTH session
if (!priorDay.high) return 0                    // none yet (e.g. the first day of a run)

const today = market.today()                    // the session in progress
const aboveYesterday = market.price > priorDay.high
const insideIB = market.price > today.ibLow && market.price < today.ibHigh
const week = market.priorWeek(0)                // { open, high, low, close, range, from, to }

Value area & market profile

Two different questions about the same session. valueArea(n) is where size traded — volume at price. marketProfile(n) is where time was spent — 30-minute TPO letters over the RTH, which is what Dalton's rules are written against. They routinely disagree, and the disagreement is itself worth reading. 0 is the most recent completed session.

ReadFieldsMeaning
valueArea(n, desk?)poc, vah, val, volumeA completed session's VOLUME profile. With a desk, also open, end and part.
marketProfile(n, desk?)poc, vah, val, ibHigh, ibLow, high, low, row, open, endA completed session's TPO profile: 30-minute periods, 70% value area. open and end are epoch ms; row is the TPO row height.

What the TPO says about the extremes

marketProfile(n) also reports the shape of each end. A tail is excess — the auction was rejected there and it tends to hold. A poor extreme is the complement: a flat end with no excess, so the business is unfinished and it tends to be revisited. Neither is set while price is still printing at that extreme.

FieldTypeMeaning
buyingTail, sellingTail{ low, high }The excess at each end, as a price band — the run spans whole TPO rows. Absent where there is none, so if (t.buyingTail) is the test.
poorHigh, poorLowboolDalton's poor high / poor low: an extreme the auction left without excess.
singlePrints{ low, high }[]The mid-profile gaps, in the same band form — the runs the chart outlines and extends. Empty array when there are none.
Value areas
const profile = market.marketProfile(0)         // yesterday's TPO profile
const volume = market.valueArea(0)              // yesterday's VOLUME profile
if (!profile.vah || !volume.poc) return 0       // no completed session yet

// The 80% rule is written against the TPO value area, not the volume one
const openedBelowValue = market.today().open < profile.val
log("TPO " + profile.val + "-" + profile.vah + " POC " + profile.poc +
    " | volume POC " + volume.poc)

One desk at a time

Both reads take an optional second argument naming which auction you mean: "asia" (18:00–03:00 New York), "london" (03:00–09:30) or "newyork" (09:30–17:00, also "ny"). Left out, marketProfile means RTH and valueArea means the store's own session cut — which is what they have always meant, so nothing written before this changes. A name it does not recognize returns {} rather than guessing.

The overnight desks
// The same two reads, one desk at a time: "asia", "london", "newyork" ("ny").
// n counts THAT desk's own sessions -- va(1, "asia") is the Asia before last,
// never yesterday's London.
const asiaLetters = market.marketProfile(0, "asia")   // where TIME was spent
const asiaVolume  = market.valueArea(0, "asia")       // where SIZE traded

// The desk volume cut is a second walk of the tick log, and asking is what
// starts it. Test for a level, never for emptiness: pending means "not yet",
// an empty map means "no such session", and a rule that cannot tell them
// apart will trade the warm-up.
if (asiaVolume.pending) return 0
if (!asiaVolume.poc || !asiaLetters.poc) return 0

// The pair disagreeing is the reading: a volume POC well away from the TPO POC
// says the night's size went somewhere the clock did not.
const apart = Math.abs(asiaVolume.poc - asiaLetters.poc)
log("Asia volume POC " + asiaVolume.poc + " vs letters " + asiaLetters.poc +
    " (" + market.toTicks(apart) + " ticks apart)")

A desk volume area can answer “not yet”

valueArea(n, desk) is the one read that can come back { pending: true }. The desk volume cut is a second walk of the tick log and asking is what starts it, so a live strategy's first calls can be pending for a few seconds. Test for poc, not for emptiness: pending means “not yet”, an empty map means “no such session”, and a rule that cannot tell them apart will trade the warm-up. In a backtest both are built from the walk and neither is ever pending.

Global New York is not the RTH profile

The desk cut runs to 17:00 and takes the post-close with it, where marketProfile(n) stops at the bell. Both are right for what they are; a study must not mix them without saying which it meant.

Measured moves

market.measuredMove(n, seconds) — c.mm — returns the impulse that set a range, the fib retracement that triggered it, the −23.6% extension target, and whether that target or the stop has since been reached. 0 is the newest. market.measuredMoveCount() says how many that series holds.

FieldTypeMeaning
longboolDirection of the move.
triggerpriceWhere the retracement triggered it.
fibnumberThe retracement level that did (0.5, 0.618 …).
targetpriceThe −23.6% extension target.
swingHigh, swingLowpriceThe impulse that set the range.
targetHit, stopHit, doneboolWhat has happened since.
openboolNone of those three — still live. This is the one to trade off.
qualitystring"perfect", "good" or "ok": how clean the touch was.
trendstring"with", "counter" or "neutral".
confluence, precisionnumberHow much else agreed at that level, and how close the touch was.
barintThe bar it triggered on, in that series' own bars.
agointBars back on that series — the only count that means anything when the map came from a different one.
Measured moves
// 0 is this strategy's own series; a number of SECONDS is another one.
const own = market.measuredMove(0)              // newest move on our own bars
const higher = market.measuredMove(0, 900)      // and the 15-minute one
if (!own.target) return 0                       // no move folded yet

// Only trade the open ones, with the trend, when the fib was a clean touch
const tradeable = own.open && own.trend === "with" && own.quality !== "ok"
log("MM " + (own.long ? "long" : "short") + " trigger " + own.trigger +
    " target " + own.target + " " + own.quality +
    " | " + market.measuredMoveCount(900) + " on 15m")

This one takes seconds, not a timeframe name

Everything else that reads another timeframe takes a name ("15m"). measuredMove takes a number of seconds, because the named set could not answer “5 minute” at all: 0 is the strategy's own series, 300 is 5 minutes, 900 is 15. Live, any whole-minute series the app is keeping can answer; in a backtest it is the run's own period plus 120, 300, 900 and 3600. Anything else returns {} rather than reading as nothing.

Each series is folded the first time it is asked for, then stepped forward, so a strategy that never calls this pays nothing for it.

Simple Momentum

market.momentum(n, opts) is the Heikin-Ashi SuperTrend the chart draws as arrows, ported from FIMarketStructure. It answers with both the event and the state, because a strategy trading flips needs the event: a level is equally true on the bar that made it and a hundred bars later, and a flip is not.

FieldTypeMeaning
signal1, -1 or 0The EVENT: 1 on the bar a buy fired, −1 on a sell, 0 on every other bar. Same shape as stackedImbalance.
trend1 or -1The STATE, on every bar.
lastSignal1 or -1Which way the most recent arrow pointed.
barsSinceSignalintHow long ago it fired — with lastSignal, this answers “are we still in the move it called”. Absent when nothing has fired yet.
raw, trendFilterboolWhich engine answered, and whether its trend filter is on.
Simple Momentum
const mom = market.momentum(0)
if (!mom.trend) return 0                        // no momentum on this host

// The EVENT, confirmed by the ribbon: trade the flip, not the state
if (mom.signal > 0 && market.ssl(0).state > 0) return 1

// Or the ungated engine, for the Pine's timing
if (market.momentum(0, { raw: true }).signal < 0) return -1

// Still in the move it called?
const riding = mom.lastSignal > 0 && mom.barsSinceSignal < 20

Two engines, and the gates are not the same kind

By default the 200 EMA rejects a flip on the wrong side outright, while SSL disagreement only defers one until the two agree. { raw: true } drops both — every flip signals, which is the Pine this was ported from, with no confirmation lag. The two are separate folds and asking is what builds one, so a strategy that never calls this pays nothing for it.

SSL

FieldTypeMeaning
linepriceThe trailing line — a natural stop.
channelpriceThe other edge of the channel (omitted when not available).
state1 or -1Green or red.
periodintThe SSL period (20 by default; a strategy parameter named sslPeriod sets it).
barClose, ageSecprice / sssl1m and ssl5m only: that timeframe's last closed bar.
SSL
const ribbon = market.ssl(0)                    // this chart's SSL channel
if (ribbon.line === undefined) return 0         // warming up: no answer, not neutral
if (ribbon.state > 0 && market.bar(0).close > ribbon.line) return 1

// The 5-minute channel as of the last 5-minute bar that had CLOSED -- cannot repaint
const fiveMinute = market.ssl5m(0)
const aligned = fiveMinute.state === ribbon.state

market.ind() swallows a typo; market.indicators does not

market.indicators.rsi({ period: 14 }) refuses a parameter it does not have, by name, with a TypeError naming the ones it takes. The older market.ind(key, params, plot, n) accepts any bag of parameters and ignores what it does not recognize — so a misspelled one is an indicator quietly running on its defaults, which looks exactly like one that works. A key or plot name that does not exist returns undefined in both forms, and so does warm-up, which is why a mistyped name gives you a strategy that compiles, runs, takes no trades and never says why.

Order book

market.book exposes the stats strip the DOM shows. stat(key) returns one cell, stats() all of them, and series("KEY.field", n) the last n samples (about one a second, oldest first). available and samples say whether there is a book and how much history is held.

KeyFieldsMeaning (default window)
BOOKratio, wbid, waskWeighted bid/ask depth over the top 10 levels.
LIQbid, askLiquidity added.
SUMbid, askTotal resting size.
TOPbid, askSize at the inside.
SPREADticksInside spread.
IMBratio, buy, sell, buySideAggressor imbalance (30 s).
TRDrate, printsTrade rate (5 s).
VOLDvol, deltaVolume and delta of the forming bar.
CVDcvdSession cumulative delta.
ABSdom, ticks, buy, sell, perTickAbsorption on the forming bar.
AGRpct, buy, sellAggression on the forming bar.
MAXqtyLargest print on the forming bar.
DPSrate, net, buy, sellDelta per second (30 s).
VACpct, depth, avgDepth against its 5-minute average.
MVperMin, net, lift, hitMarket volume (300 s).
LIFT, HITqtyContracts lifted / hit (300 s).
VSRnet, sr, lift, hitVolume strength ratio (300 s).

No book in a backtest

market.book.available is false and every read is empty. An order-book rule can only be evaluated on a chart during a replay.
Order book
if (!market.book.available) return 0          // always false in a backtest

const book = market.book.stat("BOOK")           // { ratio, wbid, wask }
const imbalance = market.book.stat("IMB")       // { ratio, buy, sell, buySide }
const lastMinute = market.book.series("BOOK.ratio", 60)   // about one sample a second
if (book.ratio > 1.5 && imbalance.buySide) log("book leaning bid, tape lifting")

broker

MemberReturnsRules
buy({ qty, stopTicks, targetTicks })boolMarket entry with a protective bracket. qty defaults to 1. Refused while a position is open (no pyramiding). On a chart an entry without stopTicks > 0 is refused — always give a stop.
sell({ qty, stopTicks, targetTicks })boolThe same, short.
flatten()—Closes the position at market and drops the protective orders.
cancelAll()—Cancels working orders.
moveStop(price)boolMoves the resting stop. Always false in a backtest. Prefer this.trailStop(price), which only moves in your favor.
position{ qty, avg, long, short, flat }qty is signed: positive long, negative short. avg is 0 when flat.
broker
onBar() {
  const position = broker.position              // { qty, avg, long, short, flat }
  if (position.long && market.bar(0).close < this.exitLevel) {
    broker.flatten()                            // close at market, drop the bracket
  }
  super.onBar()                                 // keep consulting signal() while flat
}

// Placing an order yourself -- ALWAYS with a stop
const placed = broker.buy({ qty: 1, stopTicks: 20, targetTicks: 40 })
if (!placed) log.warn("entry refused")

log

CallMeaning
log(text), log.info(text)An INFO line, stamped with the bar time in New York and the bar number.
log.warn(text), log.error(text)WARN and ERROR lines.
log.debug(text)Kept only after log.level("debug"), which belongs in init(). Levels: debug, info, warn, error.
log.plot(name, value)A named series against bar time — up to 8 series. Plot a gate's input to see whether a condition was ever close.
console.log / .warn / .errorRouted to the same log.

The log holds up to 5,000 lines (per day in a backtest), then says it was truncated. Never log from onTick().

log
log("entry: close " + market.bar(0).close + " above " + this.level)
log.warn("skipped: spread too wide")
log.error("unexpected state")
log.plot("closeLocation", market.closeLocation(0))   // up to 8 named series
console.log("same as log()")

Inherited stop management

Every strategy inherits these parameters (group “Stops”), applied by maintain() before onBar(). Declaring one in your own params only changes its default. The base also reads the conventional keys stopTicks, targetTicks and qty.

ParameterDefaultRangeMeaning
autoTrail10 – 1Walk the resting stop along the trail source.
trailSource00 – 30 this chart's SSL, 1 the 1-minute SSL, 2 the 5-minute SSL, 3 ATR behind price.
trailSlackTicks120 – 200Ticks behind the SSL line.
atrTrailMult2.00.25 – 10ATR multiples behind price (trail source 3).
atrTrailPeriod142 – 100ATR period (trail source 3).
breakEvenPoints200 – 500Move the stop to entry once this many POINTS ahead; 0 disables.
breakEvenOffsetTicks20 – 40Ticks beyond entry for break-even.

Futures trading involves substantial risk of loss. This reference documents Quantum's scripting API; the examples are not trading advice. Backtest results are hypothetical and have inherent limitations. See our full disclaimer.