
This is the third instructional post in our series on creating custom indicators and visual tools in Spectra Charts. In the previous materials, we covered:
In this material, you’ll learn how to correctly create oscillators displayed in a separate panel under the main price chart in Spectra Charts. These indicators are especially useful when trading binary options, where it’s important to see not only the price but also its “momentum” — acceleration, slowdown, overbought and oversold conditions.
An oscillator is a standalone indicator located below the main price chart, which:
Examples of oscillators:
As with the previous posts, the key is training the neural network. This is extremely easy: you only need to copy one prompt, and you’ll get a fully trained chat capable of creating any oscillator-indicators for the lower panel.
The main requirement is to send the training prompt in full, without trimming or missing pieces. This ensures GPT understands the code structure and can generate correct oscillators for your tasks.
This prompt is the foundation. You give it to GPT once, and after that, the chat becomes “trained” to build oscillators for Spectra Charts.
Here is the prompt you send to GPT before inserting your first code:
- I’m giving you code; you must study it and understand how to correctly draw oscillators (indicators) in a separate panel below the price chart.
- Also study the code structure to understand the proper format of Spectra Charts indicators.
- When you write code fixes, updates, or minor adjustments, always return the **full** code, not fragments.
- In your response, confirm that you understood everything and wait for my next commands.
- In the parameters section, always use **natural language labels** (clear and human-readable).
- Training code:
// =========================== Phantom Wave Oscillator ===========================
// Oscillator in a separate pane. Based on a WaveTrend-style algorithm:
// AP = source (default HLC3)
// ESA = EMA(AP, channelLength)
// DEV = EMA(|AP - ESA|, channelLength)
// CI = (AP - ESA) / (0.015 * DEV)
// PWO = EMA(CI, averageLength) — main line
// SIG = EMA(PWO, signalLength) — signal line
// HIST = PWO - SIG — histogram
//
// Hover values: all series must always return a value (0 before “warm-up”).
// Rendering: lines + histogram + zero line + +L / –L levels.
export const meta = {
name: "Phantom Wave Oscillator",
pane: "separate",
defaultParams: {
// === Values (top) ===
source: "HLC3", // Close | HL2 | HLC3
channelLength: 10, // ESA/DEV period
averageLength: 21, // EMA smoothing period for CI
signalLength: 4, // signal EMA period
levelUp: 60, // upper level
levelDown: -60, // lower level
// === Appearance (bottom, grouped) ===
// Main line
showMain: true,
mainColor: "#06b6d4",
mainWidth: 2,
// Signal line
showSignal: true,
signalColor: "#f59e0b",
signalWidth: 2,
// Histogram
showHist: true,
histUpColor: "#22c55e",
histDownColor: "#ef4444",
histWidth: 2,
// Zero line
showZero: true,
zeroColor: "#6b7280",
zeroWidth: 1,
zeroStyle: "dashed", // dashed | solid
// Levels
showLevelUp: true,
levelUpColor: "#a855f7",
levelUpWidth: 1,
showLevelDown: true,
levelDownColor: "#a855f7",
levelDownWidth: 1
},
paramMeta: {
// === Values ===
source: {
label: "Source",
type: "select",
options: [
{ label: "Close", value: "Close" },
{ label: "HL2 (H+L)/2", value: "HL2" },
{ label: "HLC3 (H+L+C)/3", value: "HLC3" }
]
},
channelLength: { label: "Channel length (ESA/DEV)", type: "number", min: 2, max: 300 },
averageLength: { label: "Smoothing period (EMA)", type: "number", min: 2, max: 300 },
signalLength: { label: "Signal period (EMA)", type: "number", min: 1, max: 300 },
levelUp: { label: "Level +L", type: "number", min: -2000, max: 2000, step: 1 },
levelDown: { label: "Level −L", type: "number", min: -2000, max: 2000, step: 1 },
// === Appearance ===
showMain: { label: "Main line", type: "boolean" },
mainColor: { label: "Main color", type: "color" },
mainWidth: { label: "Main width (px)", type: "number", min: 1, max: 5 },
showSignal: { label: "Signal line", type: "boolean" },
signalColor: { label: "Signal color", type: "color" },
signalWidth: { label: "Signal width (px)", type: "number", min: 1, max: 5 },
showHist: { label: "Histogram", type: "boolean" },
histUpColor: { label: "Histogram >0 color", type: "color" },
histDownColor: { label: "Histogram <0 color", type: "color" },
histWidth: { label: "Histogram width (px)", type: "number", min: 1, max: 5 },
showZero: { label: "Zero line", type: "boolean" },
zeroColor: { label: "Zero line color", type: "color" },
zeroWidth: { label: "Zero width (px)", type: "number", min: 1, max: 5 },
zeroStyle: { label: "Zero line style", type: "select", options: [
{ label: "Dashed", value: "dashed" },
{ label: "Solid", value: "solid" }
]},
showLevelUp: { label: "Show +L", type: "boolean" },
levelUpColor: { label: "Color +L", type: "color" },
levelUpWidth: { label: "Width +L (px)", type: "number", min: 1, max: 5 },
showLevelDown: { label: "Show −L", type: "boolean" },
levelDownColor:{ label: "Color −L", type: "color" },
levelDownWidth:{ label: "Width −L (px)", type: "number", min: 1, max: 5 }
}
};
export function init(ctx) {
const { chart, addSeries, LineSeries, HistogramSeries, paneIndex, params } = ctx;
// ==== Series (shared scale) ====
const scaleId = "right";
const main = addSeries(LineSeries, { priceScaleId: scaleId, lineWidth: 2, priceLineVisible: false, lastValueVisible: false }, paneIndex);
const signal = addSeries(LineSeries, { priceScaleId: scaleId, lineWidth: 2, priceLineVisible: false, lastValueVisible: false }, paneIndex);
const hist = addSeries(HistogramSeries, { priceScaleId: scaleId, base: 0, priceLineVisible: false }, paneIndex);
const zero = addSeries(LineSeries, { priceScaleId: scaleId, lineWidth: 1, priceLineVisible: false, lastValueVisible: false }, paneIndex);
const lvlUp = addSeries(LineSeries, { priceScaleId: scaleId, lineWidth: 1, priceLineVisible: false, lastValueVisible: false }, paneIndex);
const lvlDn = addSeries(LineSeries, { priceScaleId: scaleId, lineWidth: 1, priceLineVisible: false, lastValueVisible: false }, paneIndex);
// ==== Utils ====
const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
const clampW = (v, d = 1) => { const n = Number(v); return Math.max(1, Math.min(5, Number.isFinite(n) ? Math.round(n) : d)); };
function pickPrecision(sample) {
if (!Number.isFinite(sample) || sample === 0) return 5;
const v = Math.abs(sample);
if (v >= 100) return 2;
if (v >= 10) return 3;
if (v >= 1) return 4;
if (v >= 0.1) return 5;
return 6;
}
function EMA(src, len) {
const n = src.length, out = new Array(n).fill(undefined);
if (!n || len <= 0) return out;
const a = 2 / (len + 1);
let v = src[0];
out[0] = v;
for (let i = 1; i < n; i++) { v = a * src[i] + (1 - a) * v; out[i] = v; }
return out;
}
// Main calculation
function calcPWO(candles, srcType, chLen, avgLen, sigLen) {
const n = candles.length; if (!n) return { t: [], pwo: [], sig: [], hist: [] };
const t = candles.map(c => c.time);
let src;
switch (srcType) {
case "Close": src = candles.map(c => c.close); break;
case "HL2": src = candles.map(c => (c.high + c.low) / 2); break;
default: src = candles.map(c => (c.high + c.low + c.close) / 3); // HLC3
}
// ESA & DEV
const esa = EMA(src, chLen);
const absDiff = src.map((v, i) => Math.abs(v - (esa[i] ?? v)));
const dev = EMA(absDiff, chLen).map(v => (v && v !== 0) ? v : 1e-12);
// CI → PWO → SIG
const ci = src.map((v, i) => (v - (esa[i] ?? v)) / (0.015 * dev[i]));
const pwo = EMA(ci, avgLen);
const sig = EMA(pwo.map(v => Number.isFinite(v) ? v : 0), sigLen);
// HIST
const hist = pwo.map((v, i) => (Number.isFinite(v) && Number.isFinite(sig[i])) ? (v - sig[i]) : 0);
// Pre-fill with zeros for hover values
const safe = arr => arr.map(v => Number.isFinite(v) ? v : 0);
return { t, pwo: safe(pwo), sig: safe(sig), hist: safe(hist) };
}
function update(candles) {
if (!candles || candles.length === 0) {
main.setData([]); signal.setData([]); hist.setData([]); zero.setData([]); lvlUp.setData([]); lvlDn.setData([]);
return [];
}
// === Params ===
const srcType = (params.source === "Close" || params.source === "HL2" || params.source === "HLC3") ? params.source : "HLC3";
const chLen = Math.max(2, +params.channelLength || 10);
const avgLen = Math.max(2, +params.averageLength || 21);
const sigLen = Math.max(1, +params.signalLength || 4);
const Lup = Number(params.levelUp ?? 60);
const Ldn = Number(params.levelDown ?? -60);
const { t, pwo, sig, hist: H } = calcPWO(candles, srcType, chLen, avgLen, sigLen);
// Scale format
const last = pwo.length ? pwo[pwo.length - 1] : 0;
const prec = pickPrecision(last);
const minMove = Math.pow(10, -prec);
for (const s of [main, signal, hist, zero, lvlUp, lvlDn]) {
s.applyOptions?.({ priceFormat: { type: "price", precision: prec, minMove } });
}
// Data
const mainData = t.map((time, i) => ({ time, value: pwo[i] }));
const signalData = t.map((time, i) => ({ time, value: sig[i] }));
const zeroData = t.map(time => ({ time, value: 0 }));
const upData = t.map(time => ({ time, value: Lup }));
const dnData = t.map(time => ({ time, value: Ldn }));
const histUpColor = params.histUpColor || "#22c55e";
const histDownColor = params.histDownColor || "#ef4444";
const histData = t.map((time, i) => ({
time,
value: H[i],
color: (H[i] >= 0) ? histUpColor : histDownColor
}));
// Apply options / visibility
main.applyOptions({
visible: params.showMain !== false,
color: params.mainColor || "#06b6d4",
lineWidth: clampW(params.mainWidth, 2)
});
main.setData(params.showMain !== false ? mainData : []);
signal.applyOptions({
visible: params.showSignal !== false,
color: params.signalColor || "#f59e0b",
lineWidth: clampW(params.signalWidth, 2)
});
signal.setData(params.showSignal !== false ? signalData : []);
hist.applyOptions({
visible: params.showHist !== false,
color: histUpColor,
lineWidth: clampW(params.histWidth, 2)
});
hist.setData(params.showHist !== false ? histData : []);
zero.applyOptions({
visible: params.showZero !== false,
color: params.zeroColor || "#6b7280",
lineWidth: clampW(params.zeroWidth, 1),
lineStyle: (params.zeroStyle === "solid") ? 0 : 1
});
zero.setData(params.showZero !== false ? zeroData : []);
lvlUp.applyOptions({
visible: params.showLevelUp !== false,
color: params.levelUpColor || "#a855f7",
lineWidth: clampW(params.levelUpWidth, 1)
});
lvlUp.setData(params.showLevelUp !== false ? upData : []);
lvlDn.applyOptions({
visible: params.showLevelDown !== false,
color: params.levelDownColor || "#a855f7",
lineWidth: clampW(params.levelDownWidth, 1)
});
lvlDn.setData(params.showLevelDown !== false ? dnData : []);
return []; // no markers
}
function destroy() {
try { chart.removeSeries(main); } catch (_) {}
try { chart.removeSeries(signal); } catch (_) {}
try { chart.removeSeries(hist); } catch (_) {}
try { chart.removeSeries(zero); } catch (_) {}
try { chart.removeSeries(lvlUp); } catch (_) {}
try { chart.removeSeries(lvlDn); } catch (_) {}
}
return { update, destroy };
}
What happens after sending this block:
Once you’ve sent the training prompt and GPT confirmed that it understands everything and is waiting for commands, you can begin formulating your own tasks for creating visual tools under the chart.
Let’s expand these with more detail:
To help you visualize the final result, here’s an example of what oscillators in Spectra Charts may look like:

In a single lower pane, you can:
For a trader, this becomes a powerful instrument: you don’t just see the raw chart — you also see actionable clues about when to be cautious or when to search for entries.
There is one important idea you must understand: You can create nearly any tool you want — but the result fully depends on how correctly you describe the task.
To increase your chances of getting a functional, understandable indicator, follow these simple rules:
Explain where the indicator should be placed and what it should look like:
Finally, let’s discuss common situations you may face — and how to solve them quickly through GPT.
To avoid this, you must explicitly tell the neural network to fix the cleanup logic.
Recommended phrasing (only if the issue has appeared):
“Ensure that when the indicator is disabled, all lines, objects, and data are fully removed. Nothing should stay on the chart, so I don’t have to refresh the page.”
An indicator may behave strangely:
What to do:
“Analyze this error and fix the indicator. Return the fully corrected version.”
This gives the neural network a solid reference point, helping it fix the real cause rather than rewriting the indicator from scratch.
This was the third educational guide on creating visual tools using Spectra Charts.
If you have any questions or difficulties implementing your indicators, feel free to contact our support bot: https://t.me/pocketservice111
or our partner chat “Mnogo Deneg Trade”.
How to register on the Pocket Option exchange
Step-by-step guide to creating a new account: Pocket Option account registration, identity verification, and getting started with binary options trading.
Creating a Visual Tool for Spectra Charts with Line Drawing on the Chart
Step-by-step guide on how to teach GPT to draw lines and zones in Spectra Charts and use it when trading binary options on Pocket Option.