
Это третий пост-инструкция по созданию собственных индикаторов с отрисовкой их на графике. В первых материалах мы уже разобрали:
В формате данного материала мы с вами научимся, как правильно создавать осцилляторы, открываемые в нижнем дополнительном окне под графиком в Spectra Charts. Такие индикаторы особенно полезны при торговле бинарными опционами, когда важно видеть не только саму цену, но и её «настроение» — ускорение, замедление, зоны перекупленности и перепроданности.
Осциллятор — это отдельный индикатор под графиком цены, который:
Примеры осцилляторов:
Как и в предыдущих постах, главное — обучение нейросети. Делается это очень легко: достаточно скопировать всего один промпт, и вы получите полностью обученный чат, в котором сможете создавать любые индикаторы-осцилляторы под графиком.
Главное условие — аккуратно отправить обучающий промпт целиком, без обрезаний и пропусков. Тогда GPT будет понимать структуру кода и сможет дальше генерировать корректные осцилляторы под ваши задачи.
Этот промпт — основа. Вы даёте его GPT один раз, и после этого чат становится «обученным» на создание осцилляторов для Spectra Charts.
Текст промпта, который вы даёте GPT перед вставкой первого кода:
- Я тебе даю код, тебе нужно изучить его и понять, как правильно рисовать осцилляторы (индикаторы) в дополнительном окне под графиком.
- Так же изучи структуру кода для правильного написания индикаторов.
- Когда будешь писать мне исправления кода, обновления, вносить мелкие правки, всегда отдавай готовый код полностью.
- В ответ скажи мне, что всё понял, и жди от меня дальнейших команд.
- Настройки в параметрах всегда прописывай на моём языке понятными словами.
- Код для обучения:
// =========================== Phantom Wave Oscillator ===========================
// Осциллятор в отдельной панели. Основа — волновая логика (в стиле WaveTrend):
// AP = источник (по умолчанию HLC3)
// ESA = EMA(AP, channelLength)
// DEV = EMA(|AP - ESA|, channelLength)
// CI = (AP - ESA) / (0.015 * DEV)
// PWO = EMA(CI, averageLength) — основная линия
// SIG = EMA(PWO, signalLength) — сигнальная линия
// HIST = PWO - SIG — гистограмма
//
// Показ значений на ховере: серии всегда отдают значение (0 до "прогрева").
// Отрисовка: линии + гистограмма + нулевая линия + уровни +L / -L.
export const meta = {
name: "Phantom Wave Oscillator",
pane: "separate",
defaultParams: {
// === Значения (вверху) ===
source: "HLC3", // Close | HL2 | HLC3
channelLength: 10, // период ESA/DEV
averageLength: 21, // период EMA для сглаживания CI
signalLength: 4, // период сигнальной EMA
levelUp: 60, // верхний уровень
levelDown: -60, // нижний уровень
// === Вид (внизу, сгруппировано) ===
// Основная линия
showMain: true,
mainColor: "#06b6d4",
mainWidth: 2,
// Сигнальная линия
showSignal: true,
signalColor: "#f59e0b",
signalWidth: 2,
// Гистограмма
showHist: true,
histUpColor: "#22c55e",
histDownColor: "#ef4444",
histWidth: 2,
// Нулевая линия
showZero: true,
zeroColor: "#6b7280",
zeroWidth: 1,
zeroStyle: "dashed", // dashed | solid
// Уровни
showLevelUp: true,
levelUpColor: "#a855f7",
levelUpWidth: 1,
showLevelDown: true,
levelDownColor: "#a855f7",
levelDownWidth: 1
},
paramMeta: {
// === Значения ===
source: {
label: "Источник",
type: "select",
options: [
{ label: "Close", value: "Close" },
{ label: "HL2 (H+L)/2", value: "HL2" },
{ label: "HLC3 (H+L+C)/3", value: "HLC3" }
]
},
channelLength: { label: "Период канала (ESA/DEV)", type: "number", min: 2, max: 300 },
averageLength: { label: "Период сглаживания (EMA)", type: "number", min: 2, max: 300 },
signalLength: { label: "Период сигнала (EMA)", type: "number", min: 1, max: 300 },
levelUp: { label: "Уровень +L", type: "number", min: -2000, max: 2000, step: 1 },
levelDown: { label: "Уровень −L", type: "number", min: -2000, max: 2000, step: 1 },
// === Вид ===
showMain: { label: "Основная линия", type: "boolean" },
mainColor: { label: "Цвет основной", type: "color" },
mainWidth: { label: "Толщина основной (px)", type: "number", min: 1, max: 5 },
showSignal: { label: "Сигнальная линия", type: "boolean" },
signalColor: { label: "Цвет сигнальной", type: "color" },
signalWidth: { label: "Толщина сигнальной (px)", type: "number", min: 1, max: 5 },
showHist: { label: "Гистограмма", type: "boolean" },
histUpColor: { label: "Цвет гисто >0", type: "color" },
histDownColor:{ label: "Цвет гисто <0", type: "color" },
histWidth: { label: "Толщина гистограммы (px)", type: "number", min: 1, max: 5 },
showZero: { label: "Линия 0", type: "boolean" },
zeroColor: { label: "Цвет линии 0", type: "color" },
zeroWidth: { label: "Толщина линии 0 (px)", type: "number", min: 1, max: 5 },
zeroStyle: { label: "Стиль линии 0", type: "select", options: [
{ label: "Пунктир", value: "dashed" },
{ label: "Сплошная", value: "solid" }
]},
showLevelUp: { label: "Показывать +L", type: "boolean" },
levelUpColor: { label: "Цвет +L", type: "color" },
levelUpWidth: { label: "Толщина +L (px)", type: "number", min: 1, max: 5 },
showLevelDown: { label: "Показывать −L", type: "boolean" },
levelDownColor:{ label: "Цвет −L", type: "color" },
levelDownWidth:{ label: "Толщина −L (px)", type: "number", min: 1, max: 5 }
}
};
export function init(ctx) {
const { chart, addSeries, LineSeries, HistogramSeries, paneIndex, params } = ctx;
// ==== Серии (общая шкала) ====
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;
}
// Основной расчёт
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);
// Заполняем нулями до «прогрева», чтобы были значения на ховере
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 [];
}
// === Параметры ===
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);
// Формат шкалы
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 } });
}
// Данные
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
}));
// Применяем опции/видимость
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 // 0=Solid, 1=Dashed
});
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 []; // маркеров нет
}
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 };
}
Что происходит после отправки этого блока:
После того как вы отправили в чат обучающий промпт и получили от модели подтверждение (что всё понято и она ждёт команд), можно начинать формулировать собственные задачи по созданию визуальных инструментов под ваши нужды.
Развернём эти примеры чуть подробнее:
Чтобы было проще представить конечный результат, вот пример того, как могут выглядеть осцилляторы в Spectra Charts:

В одном окне под графиком можно:
Для трейдера это превращается в удобный прибор: вы видите не только голый график, но и понятные подсказки, когда стоит быть осторожнее, а когда наоборот — искать точки входа.
Нужно понимать одну важную вещь: Создать можно почти всё что угодно, но результат напрямую зависит от того, насколько правильно вы сформулируете задачу.
Чтобы увеличить шансы получить рабочий и понятный индикатор, придерживайтесь простых правил:
Напишите, где должен находиться индикатор и как он должен выглядеть:
В конце важно проговорить типичные ситуации, с которыми вы можете столкнуться, и сразу понимать, как их корректно решать через нейросеть.
Чтобы такого не происходило, об этом нужно прямо сказать нейросети и попросить доработать код очистки.
Рекомендуемая формулировка для GPT (только если такие ошибки проявились):
«Сделай так, чтобы при отключении индикатора все линии, объекты и данные полностью очищались. Ничего не должно оставаться на графике, чтобы не приходилось обновлять страницу».
Иногда индикатор может вести себя странно:
В этом случае:
«Разберись по этой ошибке и исправь код индикатора. Отдай полностью исправленную версию».
Так вы даёте нейросети конкретную точку опоры, и она сможет не просто переписать индикатор заново, а именно устранить причину сбоя.
Это был третий обучающий материал по созданию визуальных инструментов с помощью Спектра Чартс и если у вас возникают какие-либо вопросы или трудности с реализацией, вы всегда можете задать свой вопрос в нашу поддержку через бот: https://t.me/pocketservice111 или в нашем партнерском чате «Много Денег Trade»
Бинарные опционы, что это. Полный словарь терминов и их определений
Понятные определения для всех начинающих трейдеров: что такое бинарные опционы, как устроены сделки на Pocket Option, ключевые термины, индикаторы и стили торговли — всё в одном словаре.
Создаем визуальный инструмент для Spectra Charts с отрисовкой линий поверх графика
Пошаговое руководство, как обучить GPT рисовать линии и зоны в Spectra Charts и использовать это при торговле бинарными опционами на Pocket Option.