# BinoView Scripts > Песочница скриптов суперграфиков BinoView: JavaScript-модуль для lightweight-charts v5 с индикаторами, сигналами buy/sell для автоторговли и HUD-карточкой над графиком. Оглавление для ИИ-ассистента, который пишет скрипт по просьбе трейдера. Как пользоваться: открой по ссылкам «Контракт модуля» и «Правила и ловушки» целиком, потом разделы под задачу и один ближайший пример. Ответ: одна строка переформулировки задачи, затем весь скрипт одним блоком кода. Не выдумывай методы: код прогоняется через smoke-тест и линтер («Проверка кода»). Если страницы не открылись, скажи трейдеру и соблюдай минимальный контракт ниже. Версия рантайма: 13. ## Минимальный контракт - `init(ctx)` обязан вернуть объект `{ update(candles) }`. Отдельный `export function update` не работает. - `update` получает весь массив свечей на каждый вызов: индикаторы создавай внутри `update` через `new ctx.TA.X(...)`, серии заполняй полным `setData`, состояние между вызовами не копи. Минимум истории `meta.minBars` (число или `(p) => число`): пока свечей меньше, `update` не зовется. - Сигнал это маркер с `name: "buy"` или `"sell"` и `time` последней свечи; без `name` сигнала нет. Маркеры верни из `update` и нарисуй сам: `createSeriesMarkers(candleSeries, []).setMarkers(markers)`. - Ключи `_hud*` в `meta` не объявляй, их добавляет рантайм. Свои параметры карточки только в `hud.defaultParams` и `hud.paramMeta`. - `hud.anchor` только `"TR"`, `"TL"`, `"BR"`, `"BL"`, по умолчанию `"TR"`. Кнопки: `pointerEvents: "auto"` в объекте `hud` и подписка `ctx.hud.on("click", '[data-action="x"]', handler)` в `init`, не в `render`. - Запрещены таймеры, `window`, `document`, `fetch`, `localStorage`. `destroy` не пиши, рантайм чистит сам. Цвета по умолчанию `#RRGGBB` или `rgba(...)`. - Время, таймфрейм, пара и доходность только из `ctx.time` и `ctx.market`, не из `Date` и не из разницы свечей. Свечи другого таймфрейма только через `meta.timeframes` и `ctx.bars(tf)`, не сверткой. ## Каркас, который проходит проверку ```javascript export const meta = { name: "Мой скрипт", pane: "main", // "main" | "separate" minBars: (p) => p.period, // update не зовется, пока свечей меньше defaultParams: { period: 20, color: "#22c55e" }, paramMeta: { period: { label: "Период", type: "number", min: 2, max: 200 }, color: { label: "Цвет", type: "color" } } }; export function init(ctx) { const { candleSeries, addSeries, LineSeries, params, TA, createSeriesMarkers } = ctx; const line = addSeries(LineSeries, { color: params.color, lineWidth: 2 }); const markersApi = createSeriesMarkers(candleSeries, []); return { update(candles) { const ema = new TA.EMA(params.period); const data = [], markers = []; for (const c of candles) { const v = ema.add(c.close); if (ema.isStable) data.push({ time: c.time, value: v }); // прогрев } line.setData(data); markersApi.setMarkers(markers); return markers; // [{ name: "buy", time, price, position: "belowBar", shape: "arrowUp", color, text }] } }; } ``` ## Начни отсюда - [Контракт модуля](https://binoview.io/docs/scripts/contract.md): экспорты meta, init(ctx), hud; update(candles); все поля ctx; свеча, time в секундах - [Правила и ловушки](https://binoview.io/docs/scripts/rules.md): запреты песочницы, парсер, ловушки «компилируется, но работает неверно», формат ответа - [Выбор панели](https://binoview.io/docs/scripts/pane.md): main или separate по типу индикатора, priceScaleId ## Справочник - [Параметры](https://binoview.io/docs/scripts/params.md): defaultParams и paramMeta, типы number, boolean, color, select, text - [Индикаторы TA](https://binoview.io/docs/scripts/ta.md): trading-signals через ctx.TA, new TA.RSI(14).add(close), прогрев через null и isStable - [Сигналы и экспирация](https://binoview.io/docs/scripts/markers.md): формат маркера buy/sell, что читает рантайм, cooldown, adaptiveExpiration для автоторговли - [HUD-карточка](https://binoview.io/docs/scripts/hud.md): export const hud, render(state, html, env), css, anchor, ctx.hud.set из update, параметры _hud* - [Кнопки в HUD](https://binoview.io/docs/scripts/hud-buttons.md): ctx.hud.on, pointerEvents "auto", data-action - [Несколько HUD-карточек](https://binoview.io/docs/scripts/hud-cards.md): до четырех карточек, ctx.hud.set({ card: state }), ключи _hud..* - [Серии и уровни](https://binoview.io/docs/scripts/series.md): addSeries, LineSeries и HistogramSeries, уровень через createPriceLine - [Pane Primitives](https://binoview.io/docs/scripts/primitives.md): три класса, координаты по своим точкам - [Рынок и время](https://binoview.io/docs/scripts/market.md): ctx.market (пара, OTC, доходность, digits), ctx.time (tf, часы брокера, час и день в поясе графика), ctx.fmt - [Другие таймфреймы](https://binoview.io/docs/scripts/timeframes.md): meta.timeframes до трех, ctx.bars(tf) родные свечи M15 или S5 той же пары, bars.indexAt без перерисовки - [Проверка кода](https://binoview.io/docs/scripts/lint.md): smoke-тест и живой прогон, коды code-*, live-*, валидаторов, как читать ошибку ## Примеры - [EMA на свечах](https://binoview.io/docs/scripts/examples/ema.md): одна линия, прогрев - [Пересечение SMA с сигналами](https://binoview.io/docs/scripts/examples/sma-cross.md): маркеры buy/sell, cooldown - [RSI Signal Bot](https://binoview.io/docs/scripts/examples/rsi-signal.md): сигналы по выходу из зон 30/70 - [Streak Reversal](https://binoview.io/docs/scripts/examples/streak-buy.md): контртренд после N свечей - [RSI на отдельной панели](https://binoview.io/docs/scripts/examples/rsi.md): pane separate - [RSI с HUD-карточкой](https://binoview.io/docs/scripts/examples/rsi-hud.md): состояние индикатора в карточке - [Счетчик с кнопками в HUD](https://binoview.io/docs/scripts/examples/hud-buttons.md): ctx.hud.on, «Пауза» и «Сброс» - [Две карточки HUD](https://binoview.io/docs/scripts/examples/hud-cards.md): две карточки друг под другом, свои параметры - [Бот с адаптивной экспирацией](https://binoview.io/docs/scripts/examples/adaptive-bot.md): экспирация из скрипта, догоны - [MACD](https://binoview.io/docs/scripts/examples/macd.md): гистограмма и линии на отдельной панели - [Bollinger Bands](https://binoview.io/docs/scripts/examples/bollinger.md): три линии на графике - [Bollinger Bands с заливкой](https://binoview.io/docs/scripts/examples/bollinger-fill.md): Pane Primitive, заливка между линиями - [Уровень открытия дня](https://binoview.io/docs/scripts/examples/price-line.md): уровень через createPriceLine - [EMA Cross по сессии](https://binoview.io/docs/scripts/examples/session-filter.md): фильтр по часам, доходности и OTC - [EMA Cross по трем таймфреймам](https://binoview.io/docs/scripts/examples/mtf-trend.md): три ТФ как select, голоса трендов по закрытым барам, три линии EMA, доска в HUD - [Младший ТФ внутри свечи](https://binoview.io/docs/scripts/examples/mtf-inside.md): бары S5 внутри формирующейся свечи, HUD ## Optional - [Полная справка одним файлом](https://binoview.io/llms-full.txt): все страницы - [Что нового в API](https://binoview.io/docs/scripts/changelog.md): изменения по датам --- # Контракт модуля > Источник правды: app/utils/tw-scripts/runtime.js (compileUserScript, initCompiledScript), app/utils/tw-scripts/minBars.js, app/utils/tw-scripts/cooldown.js, app/composables/useChartScripts.js. Версия рантайма: 13. Что скрипт экспортирует, что лежит в ctx, когда и с чем рантайм зовет update. ## Три экспорта - `meta` обязателен: описание скрипта и параметров. - `init(ctx)` обязателен: создает серии, маркеры, примитивы и возвращает `{ update(candles) }`. - `hud` опционален: карточка над графиком, https://binoview.io/docs/scripts/hud.md. ```javascript export const meta = { name: "Название", pane: "main", // "main" | "separate" minBars: (p) => p.period, // update не зовется, пока свечей меньше cooldown: 2, // баров без сигналов после каждого сигнала defaultParams: { period: 14, color: "#22c55e" }, paramMeta: { period: { label: "Период", type: "number", min: 2, max: 200 }, color: { label: "Цвет", type: "color" } } }; export function init(ctx) { const { addSeries, LineSeries, params, TA } = ctx; const line = addSeries(LineSeries, { color: params.color, lineWidth: 2 }); return { update(candles) { // пересчет от всего массива, полный line.setData(...) return []; } }; } ``` ## Формы экспорта, которые понимает парсер Парсер переписывает ESM регулярками, допустимы только `export const meta = {...}` (также let/var), `export const hud = {...}`, `export function init(ctx) {...}`, `export const init = (ctx) => ({...})`, `export default function init(ctx) {...}`, `export { meta, init }` и `export default { meta, init }` для имен, объявленных выше. Имена фиксированы: `meta` (legacy `scriptMeta`, `botMeta`), `init` (или `initFn`), `hud`. Другие имена: «В модуле не найдены botMeta/scriptMeta/meta и init/initFn»; другие формы (`export default meta`): «Unexpected token 'export'». `import` не поддерживается. ## Поля meta - `name`: название в списке скриптов; им же рантайм подписывает отдельную панель. Пустое: замечание meta-name-missing. - `pane`: `"main"` или `"separate"`. Не задан: main. Другое значение: замечание meta-pane-invalid и main. Выбор: https://binoview.io/docs/scripts/pane.md. - `defaultParams`: значения по умолчанию всех параметров. - `paramMeta`: описание полей настроек, см. https://binoview.io/docs/scripts/params.md. - `minBars`: с какого числа свечей звать `update`: число или функция `(params) => число`, например `(p) => p.slow + 1`, не больше 1050 (история графика). Пока свечей меньше, `update` не вызывается: серии и маркеры пустые, сигналов нет. Готовность TA (`null`, `isStable`) это не заменяет. Не число или больше истории: ошибка в фазе init. - `cooldown`: пауза между сигналами в барах, число или `(params) => число`, 0..1050: сигналы в паузе рантайм убирает из `setMarkers` и возврата `update`. См. https://binoview.io/docs/scripts/markers.md. - `adaptiveExpiration: true`: скрипт задает время сделки полем `expiration` маркера; без флага источник экспирации «Из скрипта» в окне автоторговли недоступен. См. https://binoview.io/docs/scripts/markers.md. - `timeframes`: свечи других таймфреймов пары через `ctx.bars(tf)`: массив имен или секунд (до 3, `["M15"]`, `[900]`) либо `(params) => [...]`. См. https://binoview.io/docs/scripts/timeframes.md. ## Объект ctx В `init(ctx)` приходят ключи: - `chart`: IChartApi lightweight-charts v5 (прокси: `addSeries`, `panes()`, `addPane` отслеживаются). - `candleSeries`: серия свечей основной панели: `createSeriesMarkers`, `createPriceLine`, `attachPrimitive`. - `paneIndex`: 0 для main, номер новой панели для separate; третий аргумент `addSeries`. - `params`: `{ ...defaultParams, ...настройки пользователя }`, приведенные к типам из paramMeta. Здесь же параметры HUD. - `isDark`: `{ value: boolean }`, реактивный: читай `isDark.value`, голый `isDark` всегда truthy. - `TA`: trading-signals, см. https://binoview.io/docs/scripts/ta.md. - `createSeriesMarkers(series, [])`: api с `setMarkers`. - `addSeries(Type, options, paneIndex)`: ISeriesApi, см. https://binoview.io/docs/scripts/series.md. - `attachPrimitive(series, primitive)`: то же, что `series.attachPrimitive(primitive)`. См. https://binoview.io/docs/scripts/primitives.md. - `hud`: `{ set(state), on(event, selector, handler), off(selector) }`. Без `export const hud` это пустые заглушки. Несколько карточек: `set({ card: state })`, см. https://binoview.io/docs/scripts/hud-cards.md. - `LineSeries`, `HistogramSeries`: типы серий. - `market`, `time`, `fmt`: пара, OTC, доходность и digits; таймфрейм и его имя, часы брокера, час и день в поясе шкалы графика; форматирование цены и времени. См. https://binoview.io/docs/scripts/market.md. - `bars(tf)`: свечи таймфрейма из `meta.timeframes`, `bars.indexAt(tf, t)` индекс последнего бара, закрытого к `t`. См. https://binoview.io/docs/scripts/timeframes.md. ## Свеча `{ time, open, high, low, close, volume }`. `time` это Unix-время начала бара в секундах, не в миллисекундах; таймфрейм в секундах это `ctx.time.tf`. `volume` от брокера как есть (0, если не прислан); на OTC (`ctx.market.otc`) он синтетический, индикаторы по объему там не показательны. ## Когда зовется update(candles) 1. Сразу после `init`. Ошибка снимает скрипт с графика и показывает тост. 2. На каждом тике котировки, не чаще раза в ~2 с. 3. Тихий прогон через 50-350 мс после закрытия бара, уже с новой свечой, без сигналов. 4. Тихий перепрогон после показа запаркованного графика. 5. Синхронный прогон на закрытом баре для скрипта, за которым следит автоторговля в режиме «следующая свеча». 6. При смене пары или таймфрейма инстанс уничтожается и создается заново. Каждый раз приходит весь массив свечей от начала истории; последняя свеча формирующаяся: между вызовами меняются ее close/high/low при том же `time`. Контракт: каждый `update` пересчитывает все от `candles` и делает полный `series.setData(...)` и `markersApi.setMarkers(...)`: на запаркованном графике рантайм глотает рисование, а при показе перепрогоняет `update` и ждет полной картины; инкрементальный `series.update(bar)` оставил бы дыру. Статика из `init` (createPriceLine, легенда HUD) парковку переживает. Состояние между вызовами не копи: https://binoview.io/docs/scripts/rules.md. ## Что возвращает update Массив маркеров или `[]`. Рантайм читает только `name` (`"buy"` | `"sell"`), `time`, `price`, `expiration`; сигнал засчитывается, когда `time` равен времени последней свечи, не чаще раза на бар. Рисует маркеры сам скрипт: `markersApi.setMarkers(markers)`. Формат: https://binoview.io/docs/scripts/markers.md. ## Авто-очистка: destroy не пиши Все, что создано через ctx (серии, панели, маркеры, примитивы, HUD, `createPriceLine`, `autoscaleInfoProvider`), рантайм снимает сам при удалении скрипта. Возвращай из `init` только `update`: `destroy` дает code-destroy-in-init-return. --- # Правила и ловушки > Источник правды: app/utils/tw-scripts/runtime.js (SANDBOX_BLOCKED_NAMES, compileUserScript), shared/scriptValidationPatterns.js, shared/scriptCodeLinter.js, app/utils/tw-scripts/smokeTest.js. Версия рантайма: 13. Запреты и доступное в песочнице, ограничения парсера, ловушки, формат ответа. ## Запрещено в песочнице Любое обращение к этим именам (вызов или чтение свойства) бросает `[Sandbox] Доступ запрещен: <имя>`: в проверке фаза init или update, на графике тост. ``` Function setTimeout setInterval setImmediate clearTimeout clearInterval requestAnimationFrame cancelAnimationFrame requestIdleCallback cancelIdleCallback WebSocket Worker SharedWorker ServiceWorker importScripts document window globalThis self top parent frames navigator location history indexedDB caches crypto performance Notification AudioContext webkitAudioContext RTCPeerConnection EventSource BroadcastChannel MessageChannel Proxy Reflect Symbol queueMicrotask AbortController AbortSignal structuredClone TextEncoder TextDecoder atob btoa Blob File FileReader URL URLSearchParams FormData Headers Request Response ReadableStream WritableStream TransformStream CompressionStream DecompressionStream MutationObserver ResizeObserver IntersectionObserver Image Option postMessage Intl ``` Также заблокированы `eval`, `fetch`, `XMLHttpRequest`, `localStorage`, `sessionStorage` и свойство `constructor` у функций (`fn.constructor`, обходной путь к `Function`). Следствия: таймеров нет, `update` синхронный; сети и хранилищ нет; `Intl` нет, форматируй через `ctx.fmt`. ## Статические запреты на компиляции Регулярки до запуска, сервер повторяет их при сохранении; текст начинается с `[Sandbox]`. Запрещены: динамический `import()`, `import.meta`, `__proto__`, `__defineGetter__`/`__defineSetter__`/`__lookupGetter__`/`__lookupSetter__`, `.constructor.constructor`, `.constructor[...]`. ## Доступно `Math`, `console` (log, info, warn, error), `Date`, `JSON`, `Map`, `Set`, `WeakMap`, `Promise`, `Object`, `Array`, `Number`, `String`, `Boolean`, `BigInt`, `RegExp`, `Error`, типизированные массивы, `parseInt`/`parseFloat`, async/await. `alert()` подменен на `console.warn`. Промисы работают, но результат `update` нужен сразу: считай синхронно. Индикаторы только через `ctx.TA`, графика только через ctx. ## Ограничения парсера - Только формы export из https://binoview.io/docs/scripts/contract.md; имена экспортов фиксированы (meta, init, hud), остальные идентификаторы любые. - Любой `import ... from`: «Cannot use import statement outside a module». Внешних библиотек нет. ## Ловушки: компилируется, но работает неверно 1. **Состояние в замыкании init, мутируемое из update.** `update` каждые ~2 с получает весь массив свечей, поэтому `let wins = 0` в init и `wins++` в update считают неверно. Линтер: code-init-mutable-state. Считай статистику внутри update от `candles`. В замыкании держи только серии, примитивы и состояние обработчиков `ctx.hud.on` (https://binoview.io/docs/scripts/examples/hud-buttons.md). 2. **Неиспользуемые параметры.** Каждый ключ paramMeta должен читаться в коде, иначе ручка без эффекта. Линтер: code-paramMeta-unused. 3. **Горизонтальный уровень** это `candleSeries.createPriceLine({ price, color, lineWidth, lineStyle, axisLabelVisible, title })` в init и `line.applyOptions({ price })` в update, не LineSeries и не примитив. Пример https://binoview.io/docs/scripts/examples/price-line.md. 4. **HUD: state и render должны совпадать.** `undefined`, `null`, `false` рендерятся пустой строкой без ошибки: карточка молча пустеет. `params` в render нет (code-p-in-render), прокидывай нужное через set. 5. **Не выдумывай API.** Код прогоняется на синтетических свечах (https://binoview.io/docs/scripts/lint.md). Нет `rsi.last()`, `.value`, `series.lastBar()`, `chart.subscribeTick()`, `subscribeCrosshairMove`, `subscribeClick`, `timeScale().subscribe*`: логика только в `update`. TA: `new TA.X(...)`, `.add()`, `.getResult()`, `.isStable` (https://binoview.io/docs/scripts/ta.md); серии: https://binoview.io/docs/scripts/series.md. 6. **Время и рынок.** `time` свечи это Unix-секунды. Часы брокера, таймфрейм и пояс шкалы графика лежат в `ctx.time` (`now()`, `tf`, `hour(t)`, `barLeft()`), пара, OTC и доходность в `ctx.market`: https://binoview.io/docs/scripts/market.md. `new Date()`, `Date.now()`, `getHours()` это часы устройства, они не совпадают с брокером, линтер code-device-clock. «Сегодня» это `c.time >= ctx.time.dayStart()`. 7. **Объем на OTC.** На OTC объем синтетический: OBV, VWAP и всплески объема не работают, предупреждай трейдера. 8. **Тяжелый код.** `update` идет по всем графикам сетки, фаза проверки ограничена 1.5 с. В `View.update()` примитива не собирай Map по всем свечам и не читай `series.data()`; координаты только по своим точкам через `timeScale().timeToCoordinate(time)` и `series.priceToCoordinate(price)`, `null` это вне экрана. Пример https://binoview.io/docs/scripts/examples/bollinger-fill.md. 9. **isDark как boolean.** Это `{ value }`, голый `isDark` truthy: code-isDark-as-boolean. 10. **separate без paneIndex.** Серия ляжет на свечи: code-addSeries-no-paneIndex, см. https://binoview.io/docs/scripts/pane.md. 11. **Короткая история.** На новой паре свечей меньше периода: `candles[i - n]` дает undefined. Объяви `meta.minBars` (число или `(p) => p.slow + 1`): пока свечей меньше, `update` не зовется, серии и маркеры пустые. 12. **Свой cooldown.** `let lastSignalIdx` в init это ловушка 1. Объяви `meta.cooldown` (число или `(p) => p.cooldownBars`): сигналы в паузе рантайм уберет и со стрелок, и из возврата. См. https://binoview.io/docs/scripts/markers.md. 13. **Сигнал только на закрытой свече.** Цикл до предпоследней свечи или `time` предыдущей у маркера: на живую свечу сигнал не попадает, звука и сделок нет (live-signals-only-closed). Считай по предпоследней, маркер на последнюю. 14. **Свой старший таймфрейм из свечей.** Свертка по N баров ломается на секундных графиках и перерисовывает сигналы (code-manual-resample). Объяви `meta.timeframes` и бери родные бары через `ctx.bars(tf)`: https://binoview.io/docs/scripts/timeframes.md. ## Формат ответа - Первая строка: переформулировка задачи (панель, слои, сигналы). - Затем весь скрипт одним блоком ```javascript: meta, при необходимости hud, init с update. Полный код без многоточий и прозы внутри: трейдер вставит его в редактор, проверка пойдет сама. - Не дели код на блоки, не пиши destroy, после блока код не объясняй. Ошибку проверки трейдер пришлет текстом: чини по нему (https://binoview.io/docs/scripts/lint.md). ## Частые ошибки проверки - `init() не вернул объект с update()`: забыл `return { update }`. - `this.short.update is not a function`: `new TA.MACD(12, 26, 9)` с числами; MACD берет три экземпляра EMA или DEMA. - `_hud*` в meta.paramMeta: code-hud-prefix-in-meta; параметры HUD кладутся в hud.paramMeta без префикса. - Цвет `"red"` при type color: code-color-default-not-hex, нужен `#RRGGBB` или `rgba(r,g,b,a)`. - Результаты TA это обычные числа, `Number()` не нужен. --- # Выбор панели > Источник правды: app/composables/useChartScripts.js (paneIndex, applyPaneLayout), app/utils/tw-scripts/runtime.js (_decorateIndicatorPane), shared/scriptCodeLinter.js (code-addSeries-no-paneIndex). Версия рантайма: 13. Как выбрать `meta.pane` по типу индикатора, что обязательно для отдельной панели и когда переспрашивать трейдера. ## Два значения - `"main"`: поверх свечей, `ctx.paneIndex` равен 0. - `"separate"`: отдельная панель под графиком. Рантайм заранее кладет в `ctx.paneIndex` номер новой панели; панель появляется при первом `addSeries(..., paneIndex)`. Высота: основная панель 7 частей, каждая отдельная 1. - `pane` не задан: main. Другое значение (`"overlay"`, `"bottom"`): замечание meta-pane-invalid и main. ## Таблица выбора Явные слова трейдера побеждают таблицу. | Запрос | pane | |---|---| | «на свечах», «поверх свечей», «на основном графике», `main` | main | | «отдельной панелью», «под графиком», «под свечами», «снизу», `separate` | separate | | Скользящие и все, что измеряется в ценах: SMA, EMA, WMA, RMA, DEMA, WSMA, SMA15, DMA, Bollinger Bands, VWAP, ZigZag, PSAR, Acceleration Bands, Linear Regression, Ichimoku, уровни поддержки и сопротивления, любые «MA» | main | | Осцилляторы и индикаторы в своих единицах: RSI, MACD, Stochastic, Stochastic RSI, ATR, TR, ADX, DX, CCI, Williams %R, AO, AC, CG, REI, MOM, ROC, OBV, Bollinger Bands Width, IQR, MAD, TDS, Period | separate | | Сигнальный бот, маркеры buy/sell, стрелки на свечах (сам индикатор без панели) | main | | Своя графика на свечах: зоны, заливки, подсветки баров, метки | main | Индикатор есть в таблице: панель определена, дополнительные фичи (HUD, маркеры, пороги) ее не меняют. Маркеры всегда ложатся на свечи через `createSeriesMarkers(candleSeries, [])`, даже у скрипта с `pane: "separate"`. Не переспрашивай и не оправдывай выбор в ответе. ## Осциллятор на main это не ошибка Трейдер просит RSI, MACD или Stochastic «на свечах»: это просьба про overlay, а не путаница. Оставь `pane: "main"` и положи серии на свою шкалу, иначе шкала свечей сплющит индикатор в линию у нуля: ```javascript const line = addSeries(LineSeries, { color: params.color, priceScaleId: "osc-overlay", // любое уникальное имя, одно для всех серий индикатора priceLineVisible: false, lastValueVisible: false }); chart.priceScale("osc-overlay").applyOptions({ scaleMargins: { top: 0.7, bottom: 0.05 } // прижать к низу основной панели }); ``` Все серии одного индикатора на одной `priceScaleId`, иначе их шкалы разъедутся и пересечения линий визуально не совпадут. ## Что обязательно для separate - `paneIndex` третьим аргументом в каждом `addSeries`. Без него серия ляжет на свечи, линтер выдаст code-addSeries-no-paneIndex. - Все серии индикатора на одном `paneIndex`: одна панель на скрипт. - `priceFormat` для малых значений (форекс, MACD): `{ type: "price", precision: 5, minMove: 0.00001 }`, иначе значения вроде 0.00012 округлятся до 0.00. Для RSI и других шкал 0..100 хватает `precision: 2`. - Отступы шкалы: `line.priceScale().applyOptions({ scaleMargins: { top: 0.1, bottom: 0.1 } })`. - Рантайм сам подписывает панель именем `meta.name` в углу и прячет ее правую шкалу. Цифр на шкале нет: если трейдеру нужны значения, выводи их в HUD (https://binoview.io/docs/scripts/hud.md). - Панель уходит вместе со скриптом: создавать и удалять ее руками не нужно. Примеры: https://binoview.io/docs/scripts/examples/rsi.md, https://binoview.io/docs/scripts/examples/macd.md. ## Когда уточнять у трейдера Только если панель действительно неясна: - запрос пустой или абстрактный: «сделай индикатор», «нарисуй что-нибудь»; - индикатора нет в таблице, и в запросе нет слов про панель. Один короткий вопрос: «На свечах или отдельной панелью?». Во всех остальных случаях выбирай сам и сразу пиши код. --- # Параметры > Источник правды: app/utils/tw-scripts/runtime.js (normalizeParamType, _coerceParams, initCompiledScript), app/utils/tw-scripts/ui.js, app/utils/tw-scripts/sections.js, shared/scriptMetaValidator.js, shared/scriptCodeLinter.js, app/components/tw/EditScriptModal.vue. Версия рантайма: 13. Как объявлять настройки скрипта в `meta.defaultParams` и `meta.paramMeta`, что из них рисует окно настроек и в каком виде значения приходят в `ctx.params`. ## Два объекта - `defaultParams`: плоский объект «ключ: значение по умолчанию». - `paramMeta`: описание ручки для ключа: `{ label, type, ...доп. поля }`. Окно настроек рисует только ключи из `paramMeta`. - Каждый ключ `paramMeta` должен быть в `defaultParams` (валидатор: info `paramMeta-key-not-in-defaultParams`). Обратное направление не проверяется. - `ctx.params` = `{ ...defaultParams, ...значения трейдера }` после коэрсии. Читай `params.period` напрямую. ```javascript export const meta = { name: "RSI", pane: "separate", defaultParams: { period: 14, source: "close", showLine: true, color: "#a855f7" }, paramMeta: { period: { label: "Период", type: "number", min: 2, max: 100 }, source: { label: "Источник", type: "select", options: ["close", "open"] }, showLine: { label: "Линия", type: "boolean" }, color: { label: "Цвет", type: "color" } } }; ``` ## Типы | type | UI | Доп. поля | Значение в params | |---|---|---|---| | `number` | числовое поле | `min`, `max`, `step` (дробный шаг: `step: 0.1`) | number | | `boolean` | переключатель | нет | boolean | | `color` | палитра + слайдер прозрачности | нет | `#rrggbb` при alpha = 1, иначе `rgba(r,g,b,a)` | | `select` | выпадающий список | `options` (обязателен) | string | | `text` | строка | нет | string | - Алиасы: `int`, `float` = `number`; `switch`, `checkbox` = `boolean`; `string` = `text`. Незнакомый type: warn `paramMeta-type-unknown`, коэрсии нет. - `options` у select: массив строк или объектов `{ label, value }`; в params приходит `value`. Без `options`: warn `paramMeta-select-no-options`. - `label`: без него info `paramMeta-label-missing`. - `text`: поля `placeholder` и `maxLength` UI не рисует, не пиши их. ## Цвет - Дефолт только `#rgb`, `#rrggbb` или `rgba(r,g,b,a)`. Именованные цвета и `hsl()` ломают пикер: warn `code-color-default-not-hex`. - Полупрозрачный дефолт пиши сразу строкой `rgba(...)`, собирать ее из hex руками не нужно. Оба формата понимают lightweight-charts и Canvas. - Ориентиры alpha: линии и маркеры 1 (hex); заливки, зоны, облака 0.08..0.15; подсветки баров 0.2..0.35; фон HUD-карточки 0.85..0.95. - N перекрывающихся слоев с alpha a дают непрозрачность 1 - (1 - a)^N. При 20 и более зонах держи a не выше 0.08. ## Коэрсия Рантайм приводит значения к типу из `paramMeta` до вызова `init`: - number: `Number(v)`; NaN остается как было; - boolean: `"false"` и `"0"` становятся false, остальное `Boolean(v)`; - color, select, text: `String(v)`. Не пиши защиту вида `+params.x || 14` или `params.flag === "true"`. Ключи без `paramMeta` и значения null не трогаются. Отключить коэрсию для ключа: `coerce: false` в его `paramMeta`. ## Секции настроек (group) Окно настроек раскладывает ручки по вкладкам в фиксированном порядке: - `inputs`: по умолчанию для всех типов, кроме color; - `style`: автоматически для `type: "color"`; - `hud`: параметры HUD, метку ставит рантайм; - любая другая строка в `group`: своя вкладка, имя как есть, после канонических. Явный `group: "inputs"` или `group: "style"` переопределяет автодетект. ## Параметры HUD Настройки карточки объявляй в `export const hud`: `hud.defaultParams` + `hud.paramMeta` (та же структура). Рантайм вливает их в `meta` с `group: "hud"` и добавляет системные `_hudVisible`, `_hudAnchor`, `_hudTop`, `_hudRight`, `_hudBottom`, `_hudLeft`, `_hudFontPx`. В коде читай их из того же `ctx.params` без префикса: `params.overbought`. Ключи с префиксом `_hud` сам не объявляй: warn `code-hud-prefix-in-meta` и `hud-paramMeta-reserved-prefix`. Пример: https://binoview.io/docs/scripts/examples/rsi-hud.md, подробности: https://binoview.io/docs/scripts/hud.md. ## Неиспользуемые параметры Каждый ключ `paramMeta` (в meta и в hud) должен читаться в коде хотя бы раз: `params.x`, `ctx.params.x` или после деструктуризации. Иначе warn `code-paramMeta-unused`: у трейдера появится ручка, которая ничего не меняет. Лишний ключ удаляй из обоих объектов. ## Что проверяется `validateMeta`: `defaultParams` и `paramMeta` обязаны быть объектами (error), у записи `type` строка из списка выше (warn), у select есть `options` (warn), есть `label` (info), ключ есть в `defaultParams` (info). Замечания валидатора и линтера сохранение не блокируют, блокируют только ошибка компиляции и smoke-тест. Полный список кодов: https://binoview.io/docs/scripts/lint.md. --- # Индикаторы TA > Источник правды: node_modules/trading-signals/dist/index.d.mts (версия 7.4.3), shared/scriptCodeLinter.js (TA_CONSTRUCTORS, правило code-ta-arity-mismatch), app/utils/tw-scripts/runtime.js (ctx.TA). Версия рантайма: 13. Библиотека `trading-signals` доступна в скрипте как `ctx.TA`. Здесь: потоковая модель, прогрев, объекты результата и таблица всех классов с арностью конструктора. ## Потоковая модель `update(candles)` получает весь массив свечей, поэтому индикатор создается заново на каждый вызов и прогоняется по всем свечам: ```javascript update(candles) { const rsi = new TA.RSI(params.period); const data = []; for (const c of candles) { const v = rsi.add(c.close); // number | null if (v !== null) data.push({ time: c.time, value: v }); } line.setData(data); return []; } ``` - Методы экземпляра: `add(input)`, `replace(input)` (пересчет последнего значения), `getResult()` (последний результат или null), `getResultOrThrow()`, `isStable` (свойство), `getRequiredInputs()`. Методов `.last()`, `.value`, `.current`, `.getValue()` нет. - Значения обычные `number`, тип Decimal в 7.4.3 не используется, `Number(...)` не нужен. - Не пиши свой SMA, EMA, RSI, MACD или Bollinger через ручные формулы, когда есть класс. MACD собирается из трех скользящих: `new TA.MACD(new TA.EMA(12), new TA.EMA(26), new TA.EMA(9))`, вместо EMA допустим DEMA. - Вход «свеча» это объект с полями `high`, `low`, `close` (для OBV и VWAP еще `volume`, для OBV также `open`). Передавай свечу целиком: `atr.add(c)`, не `atr.add(c.close)`. - `volume` в свече фида может быть 0 (брокер объем не прислал), тогда OBV и VWAP сигналов не дадут. Предупреждай трейдера. ## Прогрев - Большинство классов возвращают `null`, пока не набрали период: проверяй `v !== null`. - `EMA`, `RMA`, `DEMA`, `TR` отдают число уже с первого входа. Прогрев у них виден только через `isStable`: пропускай значения, пока `ind.isStable === false`, иначе первые точки линии будут кривыми. - Для сигналов на пересечении храни предыдущие значения в локальных переменных цикла, а не в замыкании `init` (см. https://binoview.io/docs/scripts/rules.md). ## Объекты результата | Класс | Поля | |---|---| | `MACD` | `{ macd, signal, histogram }` | | `BollingerBands`, `AccelerationBands` | `{ upper, middle, lower }` | | `StochasticOscillator` | `{ stochK, stochD }` | | `DMA` | `{ short, long }` | | `LinearRegression` | `{ prediction, slope, intercept }` | | `Period` | `{ highest, lowest }` | Остальные классы возвращают число. У `ADX` после `add` доступны `adx.pdi` и `adx.mdi`. ## Все классы `MA?` = необязательный класс скользящей для сглаживания: `TA.SMA`, `TA.EMA`, `TA.WMA`, `TA.RMA`, `TA.WSMA` (передается класс, не экземпляр). Линтер сверяет число аргументов конструктора с этой таблицей: warn `code-ta-arity-mismatch`. | Класс | Конструктор | add принимает | Возвращает | |---|---|---|---| | `SMA`, `WMA`, `WSMA`, `SMA15` | `(interval)` | число | number или null | | `EMA`, `RMA`, `DEMA` | `(interval)` | число | number, прогрев по `isStable` | | `MOM`, `ROC` | `(interval)` | число | number или null | | `RSI` | `(interval, MA?)` | число | number или null | | `CG` | `(interval, signalInterval)` | число | number или null | | `StochasticRSI` | `(interval, MA?, { k, d }?)` | число | number или null | | `MACD` | `(fastMA, slowMA, signalMA)`, экземпляры EMA или DEMA | число | объект или null | | `DMA` | `(short, long, MA?)` | число | объект или null | | `BollingerBands` | `(interval, deviationMultiplier?)` | число | объект или null | | `BollingerBandsWidth` | `(bollingerBands)`, экземпляр | число | number или null | | `LinearRegression`, `Period`, `IQR`, `MAD` | `(interval)` | число | объект (LinearRegression, Period) или number, или null | | `ATR`, `ADX`, `DX` | `(interval, MA?)`, без MA сглаживание WSMA (метод Уайлдера, как в TradingView) | свеча | number или null | | `CCI`, `REI`, `WilliamsR` | `(interval)` | свеча | number или null | | `StochasticOscillator` | `(n, m, p)`: n период %K, m сглаживание %K (`stochK` уже сглаженный), p период %D | свеча | объект или null | | `AccelerationBands` | `(interval, width, MA?)` | свеча | объект или null | | `AO` | `(shortInterval, longInterval, MA?)` | свеча (high, low) | number или null | | `AC` | `(shortAO, longAO, signalInterval)` | свеча (high, low) | number или null | | `PSAR` | `({ accelerationStep, accelerationMax })` | свеча (high, low) | number или null | | `ZigZag` | `({ deviation })` | свеча (high, low) | number или null | | `OBV` | `(interval)` | свеча с open и volume | number или null | | `VWAP` | `()` | свеча с volume | number или null | | `TR` | `()` | свеча | number с первой свечи | | `TDS` | `()` | число (close) | number или null | `TR` и `TDS` в карте линтера нет: сигнатуру смотри в типах библиотеки, линтер арность у них не проверяет. Утилиты по массиву чисел (функции, не классы, `new` не нужен): `TA.getAverage`, `TA.getStandardDeviation`, `TA.getMedian`, `TA.getMaximum`, `TA.getMinimum`, `TA.getQuartile(values, 0.25 | 0.5 | 0.75)`, `TA.getStreaks`, `TA.getGrid`. ## Чего в библиотеке нет SuperTrend, Ichimoku, Keltner, Pivot Points, Heikin Ashi, свечные паттерны, фракталы, дивергенции. Пиши формулу руками, честно и с прогревом: пока данных меньше периода, точку не добавляй. Собирай из готовых блоков: SuperTrend и Keltner из `ATR` плюс `EMA`, Donchian из `Period` (`highest` и `lowest`), Heikin Ashi из open/high/low/close предыдущего бара. Не выдавай самодельную формулу за библиотечный класс: `TA.SuperTrend` не существует и уронит smoke-тест. Примеры: https://binoview.io/docs/scripts/examples/rsi.md, https://binoview.io/docs/scripts/examples/macd.md, https://binoview.io/docs/scripts/examples/bollinger.md, https://binoview.io/docs/scripts/examples/sma-cross.md. --- # Сигналы и экспирация > Источник правды: app/composables/useChartScripts.js (processMarkers, confirmBarClose), app/utils/tw-scripts/cooldown.js, app/stores/tw/signalBus.js, app/composables/useAutoTrade.js (effectiveExpiration, scriptOverlapCap, tryOpen), app/utils/tw-scripts/runtime.js (createSeriesMarkers), node_modules/lightweight-charts/dist/typings.d.ts (SeriesMarker). Версия рантайма: 13. Как скрипт сообщает платформе о сигнале buy или sell: формат маркера, что читает рантайм, дедуп, звук, автоторговля и адаптивная экспирация. ## Формат маркера ```javascript { name: "buy", // "buy" | "sell": читает рантайм time: c.time, // время свечи в секундах: читает рантайм price: c.low, // цена сигнала: читает рантайм и LWC position: "belowBar", // дальше только отрисовка LWC shape: "arrowUp", color: params.buyColor, text: "BUY" } ``` Рантайм читает только `name`, `time`, `price` и необязательный `expiration`. Остальные поля уходят в lightweight-charts: - `position`: `belowBar`, `aboveBar`, `inBar`, `atPriceTop`, `atPriceBottom`, `atPriceMiddle` (для `atPrice*` поле `price` обязательно); - `shape`: `arrowUp`, `arrowDown`, `circle`, `square`; - `color`: строка цвета; `text`: подпись; `size`: множитель размера, по умолчанию 1; `id`: строка. Поля `silent` нет: маркер без `name` buy или sell рисуется и сигналом не считается. ## Два действия в update Рантайм маркеры не рисует. Скрипт делает две вещи: ```javascript const markersApi = createSeriesMarkers(candleSeries, []); // в init // в update: markersApi.setMarkers(markers); // стрелки на графике return markers; // сигналы рантайму: звук, вкладка, бот ``` Считай маркеры по всем свечам и отдавай весь массив каждый вызов (как у серий: https://binoview.io/docs/scripts/series.md). Индикатор без сигналов возвращает `[]`. Проверка кода ловит на живых свечах стрелки без `name` из `update` (`live-markers-no-name`) и buy или sell только в `setMarkers` без возврата (`live-markers-not-returned`): стрелки есть, звука и сделок нет. Список: https://binoview.io/docs/scripts/lint.md. ## Когда срабатывает сигнал - Сигналом считается маркер с `name` buy или sell, у которого `time` равен времени последней свечи массива (формирующийся бар). Маркеры на прошлых барах только рисуются. - Дедуп: один сигнал на бар на скрипт, пару и таймфрейм. Повторные прогоны, вернувшийся маркер и смена раскладки сигнал не повторяют. - Эффекты: звук, подсветка вкладки на 30 секунд, автоторговля (по `scriptKey`, с любого графика). - Звук глушится при включенном глушении на низкой выплате, если выплата пары ниже порога. - Тихие прогоны (после парковки графика, форс на закрытии бара) сигналов не дают. - Режим бота «следующая свеча»: на закрытии бара платформа заново зовет `update` на закрытой свече и ищет маркер с ее `time`. Считай сигналы по закрытым данным, а не по случайному тику. Пример: https://binoview.io/docs/scripts/examples/sma-cross.md. ## Пауза между сигналами Условие входа держится несколько баров подряд, и скрипт ставит стрелку на каждом. Вместо своего счетчика баров объяви паузу: ```javascript export const meta = { cooldown: (p) => p.cooldownBars, // или число: cooldown: 2 defaultParams: { cooldownBars: 2 }, paramMeta: { cooldownBars: { label: "Пауза, баров", type: "number", min: 0, max: 50 } } }; ``` - N баров без сигналов после прошедшего: сигнал на баре j проходит, если j минус индекс предыдущего прошедшего больше N. При `cooldown: 2` бары 10 и 12 дают один сигнал, 10 и 13 оба. - По индексам `candles`, не по часам. Обе стороны в одной очереди: sell через бар после buy не пройдет. - Маркеры buy и sell в паузе убираются и из `setMarkers`, и из возврата `update` (якоря общие на вызов): стрелка в паузе не рисуется, не звучит, боту не уходит. Проверка кода считает сигналы после паузы. - Прошедший сигнал остается якорем, пока его свеча в истории, даже если скрипт его перерисовал: после сигнала N баров тишины гарантированы. - Маркеры без `name` и с `time` вне свечей не трогаются; два сигнала на одном баре остаются, хост берет первый. - Главный сценарий: пауза на время сделки. Свой cooldown в замыкании убери (code-init-mutable-state). ## Сигнал по закрытой свече Формирующаяся свеча меняется до закрытия, и сигнал по ней может исчезнуть. Чтобы считать по закрытым данным, условие бери с `candles[candles.length - 2]`, а маркер ставь с `time` последней свечи; в режиме бота «Следующая свеча» это лишнее, он сам подтверждает на закрытии, вместе выйдет двойная задержка. Маркер с `time` предпоследней или цикл до `candles.length - 1` без отдельной обработки последней свечи сигнала не дадут никогда: на истории стрелки есть, на живой нет, бот молчит. Проверка кода: `live-signals-only-closed`. ## Адаптивная экспирация По умолчанию время сделки задает трейдер в окне бота. Скрипт может задавать его сам, тогда: ```javascript export const meta = { name: "...", adaptiveExpiration: true, /* ... */ }; // в маркере: { name: "buy", time: c.time, price: c.low, expiration: 60 } { name: "sell", time: c.time, price: c.high, expiration: [60, 120, 180] } ``` Правила (движок useAutoTrade): - `meta.adaptiveExpiration: true` включает в окне бота источник «Из скрипта»; без него `expiration` бот не читает. - Значения в секундах. Число или массив `[вход, догон1, догон2, ...]`. Длина массива минус один = число догонов (перекрытий на той же паре), которое разрешает скрипт; число или массив из одного элемента = догонов нет, бот не перекрывается даже с тумблером. - Лестница работает только с «Перекрывать на той же паре»; шаг k берет элемент `k`, «Макс. перекрытий» режет число догонов только вниз. - Кламп 5..14400 секунд, затем лимиты экспирации пары: вне лимитов сигнал пропускается, счетчик «Пропущено». - В режиме «Из скрипта» сигнал без `expiration` (нечисло, ноль, отрицательное) пропускается, запасного значения нет. - В режиме «следующая свеча» берется `expiration` маркера, подтвержденного на закрытии. - Скрипт не знает исход сделки и глубину серии: считай экспирацию по рынку (ATR, таймфрейм), не «по номеру минуса». - Без явной просьбы трейдера поле `expiration` и флаг `adaptiveExpiration` не добавляй. Пример: https://binoview.io/docs/scripts/examples/adaptive-bot.md. --- # HUD-карточка > Источник правды: app/utils/tw-scripts/hud.js, app/utils/tw-scripts/runtime.js, shared/scriptMetaValidator.js. Версия рантайма: 13. DOM-карточка в углу графика: статистика, состояние бота, тревоги. Скрипт декларирует ее через `export const hud`, обновляет из `update` вызовом `ctx.hud.set(state)`. ## HUD или примитив | Задача | Инструмент | |---|---| | Числа, статусы, тревога в углу графика | HUD | | Кнопки «пауза», «сброс» | HUD, см. hud-buttons | | Заливка между линиями, зоны, фигуры на свечах | Pane Primitive, см. primitives | | Горизонтальный уровень | `series.createPriceLine`, см. series | ## Контракт ```javascript export const hud = { anchor: "TR", // "TR" | "TL" | "BR" | "BL", регистр не важен, невалидный = "TR" offset: { top: 12, right: 12, bottom: 12, left: 12 }, // px, дефолт 12 на каждую сторону fontPx: 12, // базовый font-size карточки visible: true, // дефолт видимости pointerEvents: "none", // только "auto" включает клики, любое другое значение = "none" css: `...`, // строка или функция (p, env) => string defaultParams: {}, // свои параметры карточки, необязательно paramMeta: {}, render: (s, html, env) => html`...` // обязателен }; ``` Обязателен только `render`: не функция, ошибка `hud-render-not-function`, HUD не монтируется. Невалидное значение любого другого поля дает warn (`hud-anchor-invalid`, `hud-offset-not-object`, `hud-css-invalid` и т.п.), рантайм берет дефолт. Рантайм монтирует host над графиком в закрытом shadow root, ставит `:host { all: initial }` и `.hud-root { font: px system-ui }`, добавляет твой css, при удалении скрипта снимает узел сам. `destroy` не пиши. ## Обновление из update ```javascript update(candles) { // расчет ctx.hud.set({ wr, wins, losses, alert: wr < params.alertWr }); return []; } ``` `ctx.hud.set(state)`: - зовет `render(state, html, env)`, санитайзит результат, пишет в `innerHTML`; - одинаковый HTML подряд не перерисовывает; - `null`, `undefined`, пустая строка из render: обновление пропущено, прошлый контент остается; - ошибка в render или css глотается с `console.warn`: скрипт живет, карточка молча не обновляется; - на скрытом (запаркованном) графике вызов игнорируется, после показа рантайм перепрогонит `update`. Всегда передавай полный state, не дельту. Без `export const hud` и при `_hudVisible: false` `ctx.hud.set/on/off` это пустые заглушки, проверки не нужны. ## render(state, html, env) - `state`: ровно то, что передали в `set`. `params`/`p` внутри render нет (линтер: warn `code-p-in-render`). Нужен параметр в шаблоне: положи его в state. - `html`: tagged template. Все `${...}` эскейпятся; вложенный `html\`...\`` и массивы фрагментов вставляются как есть; `null`, `undefined`, `false` дают пустую строку, число `0` выводится как `0`. - `env`: `{ isDark }`, plain-объект, снят в момент монтажа. - Возврат: результат `html\`...\``, строка (тоже санитайзится) или `null` (пропуск). - Чистая функция: без побочных эффектов и без `ctx.hud.on` внутри (линтер: error `code-hud-on-in-render`). Санитайзер вырезает `