Trading Strategystockscryptofutures
Volume Profile Trading Strategy
Rolling volume profile (POC, 70% value area). Long when close crosses above VAL; short when close crosses below VAH.
What is Volume Profile Trading?
Rolling volume profile: POC (Point of Control), Value Area (70%). Long when price crosses above VAL (value area low – rotation bullish); short when crosses below VAH (value area high). Exits on opposite cross or value area flip. HVN/LVN concepts: trade bounces from value area and breakouts from low-volume zones.
Strategy Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| lookback | number | 50 | Bars for volume profile |
| valueAreaPct | number | 70 | Value area percentage |
| numRows | number | 100 | Price buckets for profile |
Use Cases
- ✓POC and value area (VAH/VAL)
- ✓Rotation at value area
- ✓Breakouts from low volume
- ✓Stocks, crypto, futures
Strategy Script (JavaScript)
This strategy runs in VaultCharts using the built-in strategy engine. Below is the full script in a readable format. You can copy it or run it directly in VaultCharts.
strategy.jsVaultCharts built-in
module.exports = {
meta: {
name: "Volume Profile Trading",
params: {
lookback: { type: "number", default: 50 },
valueAreaPct: { type: "number", default: 70 },
numRows: { type: "number", default: 100 }
}
},
compute: (data, params, utils) => {
const cleanData = data.filter(d =>
d && Number.isFinite(d.high) && Number.isFinite(d.low) && Number.isFinite(d.close) &&
Number.isFinite(d.time) && (d.volume == null || Number.isFinite(d.volume)) &&
d.high >= d.low && d.close > 0
);
const lookback = params?.lookback ?? 50;
const valueAreaPct = (params?.valueAreaPct ?? 70) / 100;
const numRows = params?.numRows ?? 100;
if (!cleanData || cleanData.length < lookback + 5) return { signals: [] };
function getPOCVAHVAL(slice) {
if (slice.length === 0) return { poc: null, vah: null, val: null };
let minP = slice[0].low, maxP = slice[0].high;
for (let k = 0; k < slice.length; k++) {
minP = Math.min(minP, slice[k].low);
maxP = Math.max(maxP, slice[k].high);
}
if (minP === maxP) return { poc: minP, vah: minP, val: minP };
const step = (maxP - minP) / numRows || 1e-9;
const dist = new Array(numRows).fill(0);
for (let k = 0; k < slice.length; k++) {
const c = slice[k];
const vol = c.volume || 1;
const startB = Math.max(0, Math.floor((c.low - minP) / step));
const endB = Math.min(numRows - 1, Math.floor((c.high - minP) / step));
const buckets = endB - startB + 1;
const vpb = vol / buckets;
for (let b = startB; b <= endB; b++) dist[b] += vpb;
}
let pocIdx = 0;
for (let b = 1; b < numRows; b++) if (dist[b] > dist[pocIdx]) pocIdx = b;
const poc = (pocIdx + 0.5) * step + minP;
const total = dist.reduce((a, b) => a + b, 0);
const target = total * valueAreaPct;
const sorted = dist.map((v, idx) => ({ v, idx })).sort((a, b) => b.v - a.v);
let cum = 0;
const indices = new Set();
for (let s = 0; s < sorted.length && cum < target; s++) {
cum += sorted[s].v;
indices.add(sorted[s].idx);
}
const indArr = Array.from(indices);
if (indArr.length === 0) return { poc, vah: poc, val: poc };
const vah = (Math.max(...indArr) + 0.5) * step + minP;
const val = (Math.min(...indArr) + 0.5) * step + minP;
return { poc, vah, val };
}
const signals = [];
let position = null;
for (let i = lookback; i < cleanData.length; i++) {
const candle = cleanData[i];
const prev = cleanData[i - 1];
const slice = cleanData.slice(i - lookback, i);
const { poc, vah, val } = getPOCVAHVAL(slice);
if (poc == null || vah == null || val == null) continue;
const closeAboveVAL = candle.close > val && (prev.close <= val || prev.close === undefined);
const closeBelowVAH = candle.close < vah && (prev.close >= vah || prev.close === undefined);
const closeBelowVAL = candle.close < val;
const closeAboveVAH = candle.close > vah;
if (position === 'long') {
if (closeBelowVAL) {
signals.push({ type: "exit", direction: "long", time: candle.time, price: candle.close, index: i });
position = null;
}
continue;
}
if (position === 'short') {
if (closeAboveVAH) {
signals.push({ type: "exit", direction: "short", time: candle.time, price: candle.close, index: i });
position = null;
}
continue;
}
if (closeAboveVAL && candle.close > candle.open) {
signals.push({ type: "entry", direction: "long", time: candle.time, price: candle.close, index: i });
position = 'long';
}
if (closeBelowVAH && candle.close < candle.open) {
signals.push({ type: "entry", direction: "short", time: candle.time, price: candle.close, index: i });
position = 'short';
}
}
return { signals };
}
};Run Volume Profile Trading in VaultCharts
VaultCharts includes this strategy as a built-in option. Backtest it, adjust parameters, and use it on your own data—all stored locally on your device.