Sessions and units

View as MarkdownOpen the editor

param.session declares a window on the 24-hour clock in its zone, and unit on a number puts a unit picker in the field.

What it is

param.session(name, "HH:MM-HH:MM", { tz }) declares a window on the 24-hour clock and the zone it is written in. A window may wrap midnight ("22:00-04:00"). Per bar, inSession(...) says whether the bar's open sits inside the window, with daylight saving applied per bar from the zone's own rule.

unit: ["price", "ticks", "%", "atr"] on a number puts a unit picker in the field, listing the units in your order; unit_default names the one picked at first (the first listed when absent). unitToPrice(...) then turns the number into a price distance per bar.

Declare it

wrun
// session: a window in its zone.
param.session("rth", "09:30-16:00", { tz: "America/New_York", label: "Active hours" });
// number with a unit picker: the value and the unit's code.
param.number("offset", 0.0, { min: -100, max: 100, unit: ["price", "ticks", "%", "atr"], unit_default: "%" });
// A bounded int drawn as a slider: the ATR an atr-unit offset scales by.
param.int("atr_length", 14, { min: 2, max: 50, slider: true, label: "ATR length" });
// The tick size, written by the chart into a hidden setting.
market.tick_size();

tz is param.session only: the zone the window is written in, one of these, each with its own daylight-saving rule:

IndextzStandard offsetDaylight saving (one hour ahead)
0UTC (the default)UTC+0none
1America/New_YorkUTC-5second Sunday of March to first Sunday of November
2America/ChicagoUTC-6second Sunday of March to first Sunday of November
3Europe/LondonUTC+0last Sunday of March to last Sunday of October
4Europe/BerlinUTC+1last Sunday of March to last Sunday of October
5Asia/TokyoUTC+9none
6Asia/Hong_KongUTC+8none
7Asia/SingaporeUTC+8none
8Australia/SydneyUTC+10first Sunday of October to first Sunday of April
9Asia/KolkataUTC+5:30none
10America/Los_AngelesUTC-8second Sunday of March to first Sunday of November
11America/TorontoUTC-5second Sunday of March to first Sunday of November
12America/Mexico_CityUTC-6none
13America/Sao_PauloUTC-3none
14America/Argentina/Buenos_AiresUTC-3none
15Europe/ParisUTC+1last Sunday of March to last Sunday of October
16Europe/AmsterdamUTC+1last Sunday of March to last Sunday of October
17Europe/ZurichUTC+1last Sunday of March to last Sunday of October
18Europe/MadridUTC+1last Sunday of March to last Sunday of October
19Europe/RomeUTC+1last Sunday of March to last Sunday of October
20Europe/StockholmUTC+1last Sunday of March to last Sunday of October
21Europe/OsloUTC+1last Sunday of March to last Sunday of October
22Europe/CopenhagenUTC+1last Sunday of March to last Sunday of October
23Europe/WarsawUTC+1last Sunday of March to last Sunday of October
24Europe/HelsinkiUTC+2last Sunday of March to last Sunday of October
25Europe/AthensUTC+2last Sunday of March to last Sunday of October
26Europe/IstanbulUTC+3none
27Europe/MoscowUTC+3none
28Asia/JerusalemUTC+2Friday before the last Sunday of March to last Sunday of October
29Asia/RiyadhUTC+3none
30Asia/DubaiUTC+4none
31Africa/JohannesburgUTC+2none
32Asia/KarachiUTC+5none
33Asia/BangkokUTC+7none
34Asia/JakartaUTC+7none
35Asia/Ho_Chi_MinhUTC+7none
36Asia/Kuala_LumpurUTC+8none
37Asia/ShanghaiUTC+8none
38Asia/TaipeiUTC+8none
39Asia/ManilaUTC+8none
40Asia/SeoulUTC+9none
41Pacific/AucklandUTC+12last Sunday of September to first Sunday of April

The dialog shows each zone by its city (SESSION_TZ_LABELS: New York, Kolkata, Sao Paulo, ...). The list only grows at its end: a saved setting stores the index p_<name>_tz() reads, so the first nine never move and a new zone is appended. A tz outside the list refuses the build and names every id.

unit and unit_default belong to one number field (int, number, price): a range's two ends have no unit picker. market.tick_size() declares the tick size as a hidden setting the host writes before onStart(); market.price_precision() does the same for the price decimals, and market.zone() for the market's own time zone as an index into this same list (Chart context says who fills what).

What the dialog draws

a day strip with the window shaded, a start and an end on the 24-hour clock, and a zone menu
a number field with a unit picker beside it

Read it in onStart()

A session is three readers: p_<name>_start() and p_<name>_end() (minutes from midnight) and p_<name>_tz() (the zone's index). The index is what p_<name>_tz() reads and what inSession takes; SESSION_TZ_IDS and SESSION_TZ_LABELS hold the ids and the dialog's words in the same order. The rules are compiled into the module, so a session reads the same in your browser, on OpenMarket's servers and in an alert. The bar's open is bar.time() (Time and sessions).

A unit pick reaches the module as a code that never changes with the order you list: price 0, ticks 1, % 2, atr 3, read through p_<name>_unit() beside p_<name>(). The tick size is read through p_market_tick_size(), the price decimals through p_market_price_precision().

wrun
function onStart(): void {
  offset = p_offset();
  offsetUnit = i32(p_offset_unit());
  atr = new Atr(i32(p_atr_length()));
  tick = p_market_tick_size();
  sessionStart = p_rth_start();
  sessionEnd = p_rth_end();
  zone = i32(p_rth_tz());
}

Per bar, inSession(barOpenSec, startMin, endMin, zone) answers whether the bar's open sits inside the window, and unitToPrice(value, unit, close, tick, atr) turns the number into a price distance: ticks multiplies by the tick size, % takes that percent of close, atr multiplies by the ATR you pass, and price is the value as it is.

wrun
const atrValue = atr.update(bar.high(), bar.low(), close);
// The offset in the unit the dialog picked, as a price distance.
if (isFinite(basis)) basis += unitToPrice(offset, offsetUnit, close, tick, atrValue);
active = inSession(t, sessionStart, sessionEnd, zone) && multiHas(dayMask, weekday);

Chart context

Numbers about the chart and its market reach your file before the first bar: the bar interval, the price decimals, the tick size, the chart's colours, and the market's kind, point value, time zone and quote currency (The market's facts). Declare the ones you need at the top level and read them in onStart(). Wherever the indicator runs, it gets the numbers that place can know, and 0 for the rest.

// A close line with a corner readout of the three facts the host writes before the first bar.
chart.interval_sec();
market.price_precision();
market.tick_size();

output("close_line", line, overlay, { color: "#94a3b8" });
string("facts", { max_bytes: 96 });
render.table("corner", { rows: 1, cols: 1, cells: ["facts"], position: "top_left" });

let intervalSec: f64 = 0;
let decimals: i32 = 0;
let tick: f64 = 0;

function onStart(): void {
  intervalSec = p_chart_interval_sec();
  decimals = i32(p_market_price_precision());
  tick = p_market_tick_size();
}

function onBar(): void {
  const close = bar.close();
  if (isNaN(close)) return;
  out_close_line(close);
  sb_clear();
  sb_text("bar ");
  // 0 means the host did not fill the fact.
  if (intervalSec > 0) sb_duration(intervalSec); else sb_text("unknown");
  sb_text(", close ");
  sb_f64(close, decimals);
  sb_text(tick > 0 ? ", tick known" : ", tick unknown");
  str_facts_sb();
}

On a 15-minute BTC chart the readout says bar 15m, the close at the chart's own decimals, and tick unknown.

Who fills what

DeclareRead withOn the chartOn OpenMarket's servers and in alertsWhere there is no chart
chart.interval_sec()p_chart_interval_sec()the bar interval in seconds (a 15-minute chart reads 900)the chart's number, carried with the indicatorthe interval of the bars it runs on
market.price_precision()p_market_price_precision()the chart's price decimalsthe chart's numberthe most decimals the market's candle prices carry, at most 10; 0 when it reads no candles of that market
market.tick_size()p_market_tick_size()the market's tick where the market publishes one (CME markets), else 0the chart's number0, until market data carries tick sizes
chart.bg_color()i32(p_chart_bg_color())the background colour, packedthe chart's number0
chart.fg_color()i32(p_chart_fg_color())the text colour, packedthe chart's number0
market.kind()i32(p_market_kind())what the chart's market tradesthe chart's numberwhat the market directory says the market trades
market.point_value()p_market_point_value()the money one point of price is worth on one contractthe chart's number1 for a market priced per unit (a coin, a perpetual, a stock), 0 on a CME futures market, whose multiplier is not known there
market.zone()i32(p_market_zone())the market's exchange time zonethe chart's numberNew York for US stocks and indices, Chicago for CME, London for the London metals, else 0 (UTC)
market.quote_is_usd()i32(p_market_quote_is_usd())whether prices are in US dollarsthe chart's numberfrom the market's quote currency
  • Read 0 as "unknown". unitToPrice leaves a ticks value as it is when the tick is 0, and a Bucket refuses its span when the interval is 0. The zone is the one exception: its 0 is UTC, also where the zone is unknown.
  • These are hidden settings: the settings dialog never shows them, and the number the host writes always wins over anything passed under the same name.
  • Each one counts toward the 128 settings.

The interval and the colours

wrun
// The bar interval and the chart's two colours, written by the chart into hidden settings.
chart.interval_sec();
chart.bg_color();
chart.fg_color();
  • chart.interval_sec() is the chart's bar interval in seconds, read through p_chart_interval_sec() and available from the first bar (a 15-minute chart reads 900); 0 only when the interval is unknown.
  • chart.bg_color() is the chart's background, a custom background included, and chart.fg_color() is the chart's text colour, each as the packed colour the colours kit uses ((r << 24) | (g << 16) | (b << 8) | a as a signed 32-bit integer, so white reads -1). Read them with i32(p_chart_bg_color()) and i32(p_chart_fg_color()); 0 means unavailable.
wrun
function onStart(): void {
  intervalSec = p_chart_interval_sec();
  bg = i32(p_chart_bg_color());
  fg = i32(p_chart_fg_color());
}

The text colour is the theme's, not the background's: on a custom background it may not contrast with the background. Choose ink from the background's brightness instead: red(bg), green(bg) and blue(bg) read its channels, and Read a color back draws dark words on a light chart and light words on a dark one. Treat fg_color as the theme's hint.

The market's facts

wrun
// What the market trades, its point value, its time zone and its quote, written by the host into hidden settings.
market.kind();
market.point_value();
market.zone();
market.quote_is_usd();

Each is a number, and 0 means the host does not know:

DeclareReadsValues
market.kind()i32(p_market_kind())1 crypto, 2 a stock or an ETF, 3 forex, 4 a metal, 5 an index, 6 an economic series; 0 unknown, or none of these (a prediction market, an oil contract)
market.point_value()p_market_point_value()the money one whole point of price is worth on one contract: a futures multiplier (50 on the E-mini S&P), 1 on a market priced per unit (a coin, a perpetual, a stock); 0 unknown
market.zone()i32(p_market_zone())the market's exchange time zone as its index in the zone list above (1 New York, 2 Chicago, 3 London); 0 is UTC, also where the zone is unknown
market.quote_is_usd()i32(p_market_quote_is_usd())1 when prices are in US dollars, 2 in another currency or coin (USDT and USDC included: BTCUSDT is priced in USDT), 0 unknown
  • A futures market reports what it trades: an index future reads 5, a gold future 4.
  • A stock that trades around the clock on a crypto venue reads 2 and its venue's zone, UTC.
  • The codes never change: a new kind is added at the end of the list.
  • The zone is an index, not an offset: pass it to inSession(...) or new Clock(SESSION_TZ_IDS[zone]) and each bar gets its own daylight-saving offset.
// A listed stock's regular hours on its own exchange clock: the close drawn only between 09:30 and 16:00 there, every bar on a market that trades around the clock.
market.kind();
market.zone();
market.point_value();
market.quote_is_usd();

output("session_close", line, overlay, { color: "#2962ff" });
output("point_value_usd", line, lower, { color: "#94a3b8" });

let kind: i32 = 0;
let zone: i32 = 0;
let pointValue: f64 = 0;
let inDollars = false;

function onStart(): void {
  kind = i32(p_market_kind());
  // 0 is UTC, also where the host does not know the zone.
  zone = i32(p_market_zone());
  pointValue = p_market_point_value();
  inDollars = i32(p_market_quote_is_usd()) == 1;
}

function onBar(): void {
  const close = bar.close();
  // A stock (kind 2) on an exchange clock keeps its hours, 09:30 (minute 570) to 16:00 (minute 960);
  // zone 0 is a venue that trades around the clock (a stock perpetual), or an unknown zone.
  const open = kind != 2 || zone == 0 || inSession(bar.time(), 570, 960, zone);
  out_session_close(open ? close : NaN);
  // A one-point move on one contract, in dollars where the market is priced in them.
  out_point_value_usd(inDollars && pointValue > 0 ? pointValue : NaN);
}
BTCUSDT perpetual on Binance, 1 hour bars, Aug 10 to Aug 18, 2026Real output from OpenMarket's engine

On a US stock the line breaks outside New York's regular hours, daylight saving included, and the lower pane reads 1. On BTCUSDT, and on a stock perpetual that trades around the clock, the line runs through every bar; on BTCUSDT the lower pane stays empty, since the price is in USDT.

Gotchas

  • A tick size or an ATR of 0 means unavailable, and unitToPrice then leaves the value as it is.
  • A unit picker on a range is refused: param.range 'r' takes no unit option (a unit picker sits beside one number field, not a range).
  • A session counts as three settings and a unit list as one more toward the 128-setting cap (Picks, lanes, the cap).
  • A preset sets a session or a unit through its parts (rth_start, offset_unit, by the dialog's words for a session zone or a unit) (Presets).
  • A window fixed in the file needs no setting: it is a Session (Clock and sessions kit).