feature
VWAP
Public library indicator VWAP (vwap). Same source as the Chartnaut-owned row in the live indicator table.
What it plots
Session (or period) volume-weighted average price on the overlay, plus optional ±σ bands from input.array / output.bandTemplate.
Key decisions
Piece | Why it matters |
|---|---|
| Resets cumulative TPV/volume at daily, session, or custom boundaries. |
| Trader-editable band list drives both UI and rendered series. |
Canonical | Stable ids for definitions/studies that read VWAP bands as deps. |
Full script
meta({
shortName: "VWAP*",
kind: "overlay",
dataSource: "candles",
category: "custom",
});
dialog({
title: "VWAP Configuration",
description: "Configure Volume Weighted Average Price (VWAP) settings",
width: 600,
height: 500,
resizable: false,
showEnabledCheckbox: true,
resetToDefaults: {
enabled: true,
confirmationMessage: "Are you sure you want to reset all VWAP settings to their default values?",
},
});
const calculationMode = input.select({
id: "calculationMode",
label: "Reset Time",
layout: "radio",
radioOrientation: "vertical",
default: "daily",
options: [
{ value: "consistent", label: "Remain Consistent", description: "VWAP calculation uses all available data points without resetting" },
{ value: "daily", label: "Reset Daily", description: "00:00 UTC each day" },
{ value: "weekly", label: "Reset Weekly", description: "00:00 UTC each Monday" },
{ value: "monthly", label: "Reset Monthly", description: "00:00 UTC on the 1st of each month" },
{ value: "sydney", label: "Reset at Sydney Session Open", description: "22:00 UTC" },
{ value: "tokyo", label: "Reset at Tokyo Session Open", description: "00:00 UTC" },
{ value: "london", label: "Reset at London Session Open", description: "08:00 UTC" },
{ value: "newyork", label: "Reset at New York Session Open", description: "13:30 UTC" },
],
});
const labelsEnabled = input.boolean({
id: "labelsEnabled",
label: "Show Labels",
description: "Display VWAP values and bands on chart",
default: true,
});
const vwapColor = input.color({
id: "vwapColor",
label: "Line Color",
default: "#2962FF",
allowAlpha: true,
});
const mainLineStyle = input.lineStyle({
id: "mainLineStyle",
label: "Line Style",
default: LineStyle.Solid,
});
const vwapBands = input.array({
id: "vwapBands",
label: "VWAP Bands",
description: "Configure standard deviation bands around the VWAP line",
maxItems: 10,
addButtonText: "Add Band",
collapsible: true,
sortable: false,
toggleEnableButton: { show: true, size: "sm" },
deleteButton: { show: true, size: "sm" },
cloneButton: { show: true, size: "sm" },
layout: {
direction: "horizontal",
gap: "sm",
rows: [
{ type: "row", fields: ["deviation", "color", "lineStyle"], className: "items-center", gap: "sm",
distribution: "custom", fieldWidths: { deviation: "20%", color: "20%", lineStyle: "40%" } },
{ type: "row", fields: ["fillEnabled", "fillColor"], className: "items-center", gap: "sm",
distribution: "custom", fieldWidths: { fillEnabled: "40%", fillColor: "40%" } },
],
itemWrapper: { padding: "sm", border: true },
},
defaultItems: [
{ id: "band1", deviation: 1, color: "#ff9900", lineStyle: LineStyle.Dashed, fillEnabled: false, fillColor: "rgba(255, 153, 0, 0.1)", enabled: true },
{ id: "band2", deviation: 2, color: "#ff6600", lineStyle: LineStyle.Dashed, fillEnabled: false, fillColor: "rgba(255, 102, 0, 0.1)", enabled: true },
],
itemTemplate: (input) => {
input.boolean({ id: "enabled", label: "Enabled", default: true, isEnableField: true });
input.number({ id: "deviation", label: "Deviation", default: 1, min: 0.1, step: 0.1, precision: 1, unit: "σ" });
input.color({ id: "color", label: "Color", default: "#ff9900", allowAlpha: true });
input.lineStyle({ id: "lineStyle", label: "Line Style", default: LineStyle.Dashed });
input.boolean({ id: "fillEnabled", label: "Fill Between Bands", default: false });
input.color({ id: "fillColor", label: "Fill Color", default: "rgba(255, 153, 0, 0.1)", allowAlpha: true,
visibleWhen: when("fillEnabled").equals(true) });
},
});
layout(
tab({ id: "general", title: "General" },
section({ id: "calculation", title: "Calculation Settings" }, calculationMode),
section({ id: "display", title: "Display Options" }, labelsEnabled),
),
tab({ id: "appearance", title: "Appearance" },
section({ id: "main_line", title: "Main VWAP Line", columns: 2 }, vwapColor, mainLineStyle),
),
tab({ id: "bands", title: "Bands" },
section({ id: "deviation_bands", title: "Standard Deviation Bands" }, vwapBands),
),
);
output.line({
id: "vwap",
lineWidth: 2,
colorFrom: vwapColor,
lineStyleFrom: mainLineStyle,
labelFrom: labelsEnabled,
titleWhenLabeled: "VWAP",
});
output.bandTemplate({
from: vwapBands,
itemIdField: "id",
itemEnabledField: "enabled",
upperIdPattern: "vwap_band_pos_{id}",
lowerIdPattern: "vwap_band_neg_{id}",
fillIdPattern: "vwap_band_fill_{id}",
itemStyle: {
colorField: "color",
lineStyleField: "lineStyle",
fillEnabledField: "fillEnabled",
fillColorField: "fillColor",
},
titleTemplate: "VWAP {deviation}σ",
});
/** @param {ScriptedCtx} ctx */
export function onBar(ctx) {
const mode = ctx.params.calculationMode || "daily";
const isStart = ctx.session.isStart(mode);
const tp = (ctx.high + ctx.low + ctx.close) / 3;
const cumTPV = ctx.accum("tpv", 0, (prev) => isStart ? tp * ctx.volume : prev + tp * ctx.volume);
const cumVol = ctx.accum("vol", 0, (prev) => isStart ? ctx.volume : prev + ctx.volume);
if (cumVol <= 0) return;
const vwap = cumTPV / cumVol;
ctx.plot("vwap", vwap);
const dev = tp - vwap;
const sumSq = ctx.accum("sumSq", 0, (prev) => isStart ? dev * dev : prev + dev * dev);
const count = ctx.accum("count", 0, (prev) => isStart ? 1 : prev + 1);
const stdDev = count > 0 ? Math.sqrt(sumSq / count) : 0;
// Emit canonical, deviation-keyed band ids (upper_<devKey> /
// lower_<devKey>) so flow consumers see the exact same accessor
// surface on both engines: ctx.indicators.<as>.upper_2.value etc.
// The output.bandTemplate above keeps {id}-style ids — that's the
// RENDERER contract (per-band styling lookup); the script-side
// ctx.plot ids are the canonical, settings-derived ids that the
// backend's NativeIndicatorOutputs + NativeDynamicOutputs schema
// promise. Keep the two in lockstep with the TS formatDeviationKey
// helper in nativeOutputSchemas.ts and Go FormatDeviationKey in
// authoring_shared/native_indicator_outputs.go.
const bands = ctx.params.vwapBands || [];
const seenDev = {};
for (let i = 0; i < bands.length; i++) {
const band = bands[i];
if (!band || band.enabled === false) continue;
const d = Number(band.deviation) || 0;
const absD = Math.abs(d);
const rounded = Math.round(absD * 100) / 100;
const devKey = rounded === Math.trunc(rounded)
? String(rounded)
: String(rounded).replace(/\./g, "_");
if (seenDev[devKey]) continue;
seenDev[devKey] = true;
ctx.plot("upper_" + devKey, vwap + absD * stdDev);
ctx.plot("lower_" + devKey, vwap - absD * stdDev);
}
}