javascript

Инвестиционные боты (почти) с нуля. Часть 2: свечи и индикаторы

  • четверг, 29 января 2026 г. в 00:00:08
https://habr.com/ru/articles/989596/

Всем привет.

В этой статье мы продолжим дорабатывать базовые инструменты для работы с инвестициями и начнем изучать индикаторы.

Сгенерировано с помощью https://nanabanana.ai/
Сгенерировано с помощью https://nanabanana.ai/

Коротко о том, чем мы тут занимаемся

Если вы пропустили предыдущие части, вот ссылки:

Инвестиционные боты (почти) с нуля. Часть 0: введение и постановка целей (vc.ru)

Инвестиционные боты (почти) с нуля. Часть 1: теория и первые шаги реализации (habr.com)

Для лиги лени добавляю краткое описание.

Цель проста как мир: получать деньги ничего не делая. Это, конечно, упрощение, но именно этого я добиваюсь. Для этого я использую свои знания в программировании чтобы создать систему, которая будет торговать на бирже и приносить стабильный доход.

Параллельно в своем цикле статей я делюсь с вами, что я уже изучил и что получилось реализовать. Для обучения я активно использую https://grok.com/, он же помогает мне редактировать статью перед публикацией.

Ранее мы уже настроили API T-Банка и научились работать с портфелем и ордерами, вывели базовые данные о инструментах. Теперь добавим визуализацию — свечи и индикаторы.


График цены

После того как я опубликовал предыдущую часть и взялся за работу над этой, я ощутил нехватку функционала на своем сайте и решил сначала заняться недоработками.

Конечно, изменение цены можно отобразить с помощью простого графика в виде линии с координатами времени и цены, но в инвестировании такой график редко где используется, я бы даже сказал, “почти никогда”.

Вместо него используются японские свечи - это простой и понятный способ отображения, что происходило с ценой за выбранный промежуток времени.

В таком графике вместо точек и линии используются, так называемые свечи, которые представляют собой прямоугольники с полосками сверху и снизу. Для графика задается интервал времени для которого он считается (минуты, часы, дни) и для каждого такого интервала времени на графике будет соответствовать одна свеча.

Прямоугольник - это тело свечи, где верхняя и нижняя грани показывают цены открытия и закрытия в указанный интервал. В зависимости от того, что выше (цена открытия или закрытия) меняется цвет свечи. Обычно используют зеленый (или белый), если цена закрытия выше цены открытия и красный (или черный), если наоборот. Всё как обычно просто: зеленый - это рост, красный - падение.

Также, сверху и снизу прямоугольника есть полоски - тень свечи. Они отвечают за отображение разброса цен в выбранный интервал времени. То есть цены открытия и закрытия могут не сильно отличаться друг от друга, но торги могли проходить в гораздо большем диапазоне.

Иллюстрация https://traderblog.net/japonskie-svechi/

Для отображения у себя я не стал изобретать велосипед и взял первую попавшуюся под руку готовую библиотеку для построения подобных графиков https://www.npmjs.com/package/lightweight-charts . Но пришлось ее немного допилить, так как изначально она не могла показывать детали по каждой свече. Куда ж без костылей? 😅

Отображение свечей в деталях инструмента.
Отображение свечей в деталях инструмента.

Работать с этой библиотекой довольно просто.
Для начала инициализируем библиотеку и добавляем серию свечей на график.

this.chart = LightweightCharts.createChart(document.getElementById('chart'), {
  width: document.getElementById('chart').clientWidth,
  height: 400,
  layout: { backgroundColor: '#ffffff', textColor: '#333' },
  grid: { vertLines: { color: '#f0f0f0' }, horzLines: { color: '#f0f0f0' } },
  crosshair: { mode: LightweightCharts.CrosshairMode.Normal},
  rightPriceScale: { borderColor: '#cccccc' },
  timeScale: {
	borderColor: '#cccccc',
	timeVisible: true,
	secondsVisible: true,
  },
});

// create chart series
this.candlestickSeries = this.chart.addSeries(LightweightCharts.CandlestickSeries);        
this.candlestickSeries.setData(this.candles);
this.chart.timeScale().fitContent();

Для добавления дополнительных графиков, используется следующая функция

const additional = this.chart.addSeries(LightweightCharts.LineSeries, {
  lineWidth: 1,
  color: color,
  title: c.title,
  lastValueVisible: false,
  priceLineVisible: false,
});
additional.setData(chartData);

Но, как я сказал ранее, библиотека не дает возможности в тултипе посмотреть детали конкретной точки на графике, поэтому пришлось немного покостылить.

Создаем ивент наведения на точку

this.chart.subscribeCrosshairMove(this.handleTooltip);

и добавляем обработчик к нему

handleTooltip(param) {
  if (!param.time || !param.point) {
    this.tooltip.style.display = 'none';
    return;
  }
  const candleData = param.seriesData.get(this.candlestickSeries);
  const data = this.candles.find(c => c.time == candleData.time);

  if (!data) {
    this.tooltip.style.display = 'none';
    return;
  }
  let text = `
    <strong>${data.timeStr}</strong><br/>
    Open: ${data.open}<br/>
    Close: ${data.close}<br/>
    High: ${data.high}<br/>
    Low: ${data.low}<br/>
    Volume: ${data.volume}<br/>
    Volume buy: ${data.volumeBuy}<br/>
    Volume sell: ${data.volumeSell}<br/>
  `;
  this.tooltip.innerHTML = text;
  this.tooltip.style.display = 'block';

  const coordinate = this.candlestickSeries.priceToCoordinate(data.close);
  if (coordinate === null) {
    this.tooltip.style.display = 'none';
    return;
  }

  const shiftedX = param.point.x + 15;
  const shiftedY = param.point.y - this.tooltip.offsetHeight - 10;
  this.tooltip.style.left = shiftedX + 'px';
  this.tooltip.style.top = shiftedY + 'px';
}

Получилось неплохо для начала.

Я добавил отображение основных параметров свечи при наведении на нее - дата, цена открытия и закрытия, минимум и максимум. Но не остановился на этом и добавил объёмы торгов общие и отдельно на покупку и продажу.

Кстати, иногда объёмы продаж делают дополнительным графиком под свечами для большей информативности, но мне это пока что не нужно, поэтому достаточно данных внутри тултипа.

На этом завершаются наши подготовительные работы и, наконец, пора взяться за …

Индикаторы📈

Ожидали увидеть тут главу про сигналы? Я даже начал именно так писать, но до сигналов нужно рассмотреть сначала индикаторы и, так вышло, что сигналы вообще не вместились 😅.

Индикаторы - формула (или набор формул), которая преобразует цену, объём или другие рыночные данные в линию, гистограмму, точки или область на графике.

В этой статье я обещал рассказать про скользящие средние, с них и начнем.

Простая скользящая средняя (Simple Moving Average, SMA)

Волей случая я начал знакомство именно с этой версией. И даже думал, что именно эта реализация является единственным вариантом скользящих средних. Но на деле оказалось, что скользящие средние это большой класс индикаторов, которые можно считать по разным формулам.

Простой скользящей средней называют просто среднее арифметическое цен закрытия за последние N свечей. Берём, например, 20 последних дней(минут, часов и т.д.), складываем цены закрытия и делим на 20 — получаем значение SMA-20 на сегодня. Завтра добавляем новую цену, убираем самую старую — и линия «скользит» дальше. Отсюда и название — скользящая.

Также стоит уточнить, что интервалы для свечей могут быть разные: более короткий интервал - более короткий горизонт планирования. Можно брать дни, недели, а можно минуты, соответственно после покупки нужно будет держать инструмент недели-месяцы или продать его в течение дня.

// Simple Moving Average calculator
function calculate(candles, period) {
  const result = new Array(candles.length - period + 1);
  let sum = 0;

  // first value
  for (let i = 0; i < period; i++) sum += candles[i].close;

  result[0] = {
    value: sum / period,
    time: candles[period - 1].time
  };

  // Next values
  for (let i = period, idx = 1; i < candles.length; i++, idx++) {
    sum += candles[i].close - candles[i - period].close;
    result[idx] = {
      value: sum / period,
      time: candles[i].time
    };
  }

  return result;
}

Моя реализация SMA на JavaScript.

Какие преимущества и недостатки индикаторов обсудим в конце главы.

Экспоненциальная скользящая средняя (Exponential Moving Average, EMA)

Если простая скользящая средняя (SMA) одинаково смотрит на все последние N дней, то в экспоненциальной скользящей средней EMA последние цены имеют гораздо больший вес, а чем старше цена — тем меньше она влияет на текущее значение.

Получается, что EMA реагирует на изменения цены быстрее и резче, чем SMA с тем же периодом.

Расчет EMA сложнее SMA.

Первое значение берется как SMA за тот же период, дальнейшие считаются по формуле

EMA = (close*k)+(EMA_пред * (1-k))

где k = 2/(period + 1)

// Exponential Moving Average calculator
function calculate(candles, period) {
  const k = 2 / (period + 1);
  const emaValues = [];

  // first value — SMA
  let ema = 0;
  for (let i = 0; i < period; i++) {
    ema += candles[i].close;
  }
  ema /= period;

  emaValues.push({
    value: ema,
    time: candles[period - 1].time
  });

  for (let i = period; i < candles.length; i++) {
    ema = candles[i].close * k + ema * (1 - k);
    emaValues.push({
      value: ema,
      time: candles[i].time
    });
  }

  return emaValues;
}

Моя реализация EMA на JavaScript.

И вот тут мы дошли до состояния, что можно получить просто используя Т-Инвестиции. В прошлом, когда я пробовал вручную торговать на бирже (лет 5 назад), этого не было. Сейчас графики SMA и EMA можно посмотреть для любого инструмента в деталях. Поэтому, чтобы не грустить из-за этого, добавим еще несколько индикаторов.

Взвешенная скользящая средняя (Weighted Moving Average, WMA)

Изначально не хотел делать этот индикатор, но он нужен для расчета следующего индикатора, поэтому “а почему бы и нет?”, лишним не будет.

По сути, это альтернатива EMA, где вес последних цен выше, чем у старых, но расчет немного иной. WMA медленнее реагирует, чем EMA, но быстрее SMA.

WMA = (P₁·n + P₂·(n-1) + ... + Pₙ·1) / (1 + 2 + ... + n)

Где:

  • P₁ — самая последняя (текущая) цена закрытия

  • Pₙ — самая старая цена в окне (n дней назад)

  • n — период WMA (например, 10, 20, 50)

Знаменатель — это сумма весов. Также есть формула для расчета первых n натуральных чисел, которой мы воспользуемся для упрощения (1 + 2 + ... + n) = n(n+1)/2

function calculate(candles, period) {
  const results = [];
  const weightSum = period * (period + 1) / 2;  // calc once

  // first window
  let weightedSum = 0;
  let simpleSum = 0;  // prices without weight
  for (let i = 0; i < period; i++) {
    const price = candles[i].close;
    const weight = i + 1;
    weightedSum += price * weight;
    simpleSum += price;
  }
  results.push({
    value: weightedSum / weightSum,
    time: candles[period - 1].time
  });

  // moving window
  for (let i = period; i < candles.length; i++) {
    const oldPrice = candles[i - period].close;  // deleted price
    const newPrice = candles[i].close;           // added price

    // Update sums:
    weightedSum = weightedSum - simpleSum + newPrice * period;
    simpleSum = simpleSum - oldPrice + newPrice;

    results.push({
      value: weightedSum / weightSum,
      time: candles[i].time
    });
  }

  return results;
}

Для оптимизации использовалось скользящее окно, с помощью которого удалось избавиться от внутреннего цикла.

Если говорить о плюсах и минусах, то можно сказать, что WMA - это что-то среднее между SMA и EMA, но, всё-таки, ближе к EMA.

(Экспоненциальная) Скользящая средняя Халла ((Exponential) Hull Moving Average, HMA/EHMA)

Этот индикатор один из самых быстрых и одновременно самых гладких скользящих средних.

Её придумал Алан Халл в 2005 году специально для того, чтобы максимально уменьшить запаздывание и при этом оставить линию достаточно гладкой (меньше ложных разворотов).

Изначальная логика строится на данных WMA и называется скользящей средней Халла (HMA), но есть экспоненциальная модификация (EHMA), когда в качестве основы используется EMA, сохранив всю остальную часть логики.

Формула очень сложная для расчета вручную, но компьютер пусть трудится.

HMA = WMA( 2 × WMA(n/2) − WMA(n) , √n )

Шаг 1.  WMA(n/2) - считаем WMA с половинным периодом

Шаг 2.  WMA(n) - считаем WMA с полным периодом

Шаг 3.  RawHMA   =  2 × WMA(n/2) − WMA(n)

Шаг 4.  HMA - считаем WMA с периодом √n, где на входе вместо свечей даем RawHMA. 

Соответственно, для EHMA меняем WMA на EMA.

При этом для округления периода n/2 берется округление вниз, а для √n - обычное математическое.

Писать повторно WMA внутри HMA, конечно же, мы не будем и вызовем готовую функцию, созданную чуть ранее. Также, подстроим формат rawHma к формату свечей, чтобы можно было вызвать функцию WMA.

function calculate(candles, period) {
  const half = Math.floor(period / 2);
  const sqrtP = Math.round(Math.sqrt(period));

  const wmaHalf = Mars.services.WMA({ candles, period: half });
  const wmaFull = Mars.services.WMA({ candles, period });

  const rawHma = [];
  const wmaFullMap = new Map(wmaFull.map(p => [p.time, p.value]));

  for (const p of wmaHalf) {
    const fullVal = wmaFullMap.get(p.time);
    if (fullVal !== undefined) {
      rawHma.push({
        time: p.time,
        close: 2 * p.value - fullVal
      }); // mimic to candles
    }
  }
  return Mars.services.WMA({ candles: rawHma, period: sqrtP });
}

Код для EHMA не добавляю, так как он полностью копирует HMA, за исключением замены WMA на EMA.

Сравнение скользящих средних🔍

Все рассмотренные скользящие средние смотрят за направлением тренда и реагируют на изменение этого тренда. Но результат отличается по скорости реакции и наличию шумов.

Теперь сравнительная таблица. Оценки по 5-балльной шкале: для сглаживания — 5 означает высокое сглаживание (меньше шума), для запаздывания — 5 означает низкий лаг (быстрая реакция).

Тип MA

Сглаживание

Запаздывание 

Плюсы

Минусы

SMA

5

1

Простота расчета; хорошо сглаживает шум; надежен для долгосрочных трендов.

Высокий лаг, медленная реакция на изменения; может пропустить начало тренда.

EMA

3

3

Быстрее реагирует на недавние цены.

Меньше сглаживания, может давать ложные сигналы в шуме.

WMA

4

3

Умеренный лаг; лучше сглаживает, чем EMA; акцент на недавние данные.

Линейное взвешивание может быть менее эффективным в экспоненциальных изменениях; сложнее SMA.

HMA

4

4

Значительно снижает лаг при хорошем сглаживании; сочетает скорость и гладкость; лучше SMA/EMA в трендах.

Может быть слишком чувствительным в боковике; сложный расчет.

EHMA

4

5

Минимальный лаг; комбинирует EMA и HMA для максимальной отзывчивости; отличное сглаживание без переоценки шума.

Может генерировать больше ложных сигналов в боковике; сложный расчет.

Промежуточный результат✅

Графики в деталях инструмента
Графики в деталях инструмента

На детали инструмента был добавлен важнейший график - японские свечи, с возможностью выбора временного интервала, и возможность отобразить графики SMA, EMA, WMA, HMA, EHMA с периодами 9, 20, 50, 200.

Эти графики уже могут быть полезными для опытного инвестора и они уже дают больше информации по скользящим средним, чем то что доступно в Т-Инвестициях.

Краткосрочные планы

Январь выдался на удивление продуктивным, надеюсь сохранить результативность и за месяц написать и выпустить следующую статью.

В следующей статье я хочу начать работу над сигналами, наконец-то, и рассмотреть пересе��ение скользящих средних, возврат к среднему, RSI - то, что должно быть в этой статье. Если есть более интересная тема - предлагайте в комментариях, возможно, планы будут скорректированы. Может стоит еще добавить индикаторов?