Description
HTF PO3 indicator. free for community use. full source code is available and in the sdk docs, to help users have reference for how to code using our sdk. confirmed close is separate from current as to prevent repainting if using for signals or automation.
upgrades, you can choose to have up to 5 candles of that specific HTF display at right side of chart. disable price lables. etc. made it better.
Categories & Tags
Comments (0)
0/2000
Loading comments…
Source code
// HTF Power of 3 — SXVNT SDK
// Projects the last N HTF candles to the right of price action.
// Most recent candle gets dashed connectors showing where O/H/L/C
// occurred on the LTF chart. Each candle shows its hour label.
function calculate(bars, ctx) {
// ═══ Inputs ═══
const htfTimeframe = ctx.input('HTF', '1H');
const numCandles = ctx.input('Candles', 5, { min: 1, max: 5, step: 1 });
const rightOffset = ctx.input('Right Offset', 8, { min: 5, max: 50, step: 1 });
const candleWidth = ctx.input('Candle Width', 6, { min: 3, max: 12, step: 1 });
const candleGap = ctx.input('Candle Gap', 3, { min: 1, max: 8, step: 1 });
const bullColor = ctx.input('Bull Color', '#089981');
const bearColor = ctx.input('Bear Color', '#f23645');
const showLabels = ctx.input('Price Labels', true);
const showDelta = ctx.input('Show Delta', true);
// ═══ Get HTF data (non-repainting) ═══
const htf = ctx.request(htfTimeframe);
const last = bars.length - 1;
if (last < 0 || htf.bars.length === 0) return;
// ═══ Collect candles to draw (confirmed + forming) ═══
const f = htf.forming;
const formingCount = f ? (f.endBar - f.startBar + 1) : 0;
const hasForming = f && formingCount >= 3;
const displayCandles = [];
const confirmedToShow = hasForming ? numCandles - 1 : numCandles;
const startFrom = Math.max(0, htf.bars.length - confirmedToShow);
for (let i = startFrom; i < htf.bars.length; i++) {
const b = htf.bars[i];
displayCandles.push({
o: b.open, h: b.high, l: b.low, c: b.close, vol: b.volume,
sBar: b.startIdx, eBar: b.endIdx, ts: b.timestamp, isForming: false,
});
}
if (hasForming) {
displayCandles.push({
o: f.open, h: f.high, l: f.low, c: f.close, vol: f.volume,
sBar: f.startBar, eBar: f.endBar, ts: f.timestamp, isForming: true,
});
}
if (displayCandles.length === 0) return;
// ═══ Draw each candle ═══
for (let ci = 0; ci < displayCandles.length; ci++) {
const d = displayCandles[ci];
const isLast = ci === displayCandles.length - 1;
// ── Position ──
const startX = last + rightOffset + ci * (candleWidth + candleGap);
const endX = startX + candleWidth;
const midX = Math.round((startX + endX) / 2);
const isBull = d.c >= d.o;
const color = isBull ? bullColor : bearColor;
// ── Wick ──
ctx.line(midX, d.h, midX, d.l, { color, lineWidth: 2 });
// ── Body ──
ctx.box(startX, Math.max(d.o, d.c), endX, Math.min(d.o, d.c), {
fillColor: color, borderColor: color,
});
// ── Hour label above candle ──
const dt = new Date(d.ts);
const hourStr = String(dt.getHours());
ctx.label(midX, d.h, hourStr, {
color: 'transparent', textColor: '#787b86', size: 10,
position: 'above', style: 'none',
});
// ── Delta volume below candle ──
if (showDelta && d.sBar >= 0 && d.eBar < bars.length) {
let delta = 0;
for (let i = d.sBar; i <= d.eBar; i++) {
if (bars[i].close > bars[i].open) delta += bars[i].volume;
else if (bars[i].close < bars[i].open) delta -= bars[i].volume;
}
const sign = delta > 0 ? '+' : '';
const abs = Math.abs(delta);
let deltaStr;
if (abs >= 1e6) deltaStr = sign + (delta / 1e6).toFixed(1) + 'M';
else if (abs >= 1000) deltaStr = sign + (delta / 1000).toFixed(1) + 'K';
else deltaStr = sign + Math.round(delta).toString();
ctx.label(midX, d.l, deltaStr, {
textColor: delta >= 0 ? bullColor : bearColor,
size: 9, position: 'below', style: 'none',
});
}
// ── Connectors + price labels on rightmost candle only ──
if (isLast) {
let hIdx = d.sBar, lIdx = d.sBar;
if (d.sBar >= 0 && d.eBar < bars.length) {
for (let i = d.sBar; i <= d.eBar; i++) {
if (bars[i].high >= d.h) hIdx = i;
if (bars[i].low <= d.l) lIdx = i;
}
}
ctx.line(d.sBar, d.o, startX, d.o, {
color: '#787b86', lineWidth: 1, lineStyle: 'dashed',
});
ctx.line(hIdx, d.h, midX, d.h, {
color: bullColor, lineWidth: 1, lineStyle: 'dashed',
});
ctx.line(lIdx, d.l, midX, d.l, {
color: bearColor, lineWidth: 1, lineStyle: 'dashed',
});
ctx.line(last, d.c, endX, d.c, {
color, lineWidth: 1, lineStyle: 'dashed',
});
if (showLabels) {
const labelX = endX + 1;
ctx.label(labelX, d.h, 'High ' + d.h.toFixed(2), {
textColor: bullColor, size: 10, position: 'at',
style: 'none', textAlign: 'left',
});
ctx.label(labelX, d.c, 'Close ' + d.c.toFixed(2), {
textColor: color, size: 10, position: 'at',
style: 'none', textAlign: 'left',
});
ctx.label(labelX, d.o, 'Open ' + d.o.toFixed(2), {
textColor: '#787b86', size: 10, position: 'at',
style: 'none', textAlign: 'left',
});
ctx.label(labelX, d.l, 'Low ' + d.l.toFixed(2), {
textColor: bearColor, size: 10, position: 'at',
style: 'none', textAlign: 'left',
});
}
ctx.label(midX, d.l, htfTimeframe, {
color: 'transparent', textColor: '#787b86', size: 11,
position: 'below', style: 'none',
});
}
}
}