API реального времени + ChartIO + CanvasJS = профессиональные диаграммы

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

Представляем CanvasJS

CanvasJS - это библиотека диаграмм для приложений JavaScript. Он использует элемент холста HTML5 и рисует диаграммы из объекта серии данных.

Вы можете установить CanvasJS с npm install canvasjs или yarn add canvasjs.

Библиотека CanvasJS предоставляет интуитивно понятный API для построения диаграмм и автоматического добавления их в DOM:

// in HTML: <body><div id="mychart-container"></div></body>
CanvasJS.Chart("mychart-container", {
  title: { text: "Stock Chart for BAC" },
  yAxis: {},
  data: [{
    type: "line",
    dataPoints: [
      { x: 10, y: 20 },
      { x: 20, y: 23 }
    ]
  }]
});

Используя CanvasJS.Chart API, приведенный выше код автоматически добавит HTMLCanvasElement в mychart-container. Затем он отобразит исходную диаграмму.

Конструктор Chart принимает идентификатор элемента DOM и объект параметров. Диаграмма холста добавляется в качестве дочернего элемента в предоставленный контейнерный элемент. Объект параметров должен иметь следующие свойства:

  • title: заголовок диаграммы. См. Ниже, название - «Чистая динамика».
  • data: Это массив объектов серии данных. Мы используем только ряд данных линейной диаграммы, которые содержат набор упорядоченных пар в качестве координат.

Модели, которые нельзя использовать для динамических диаграмм

Я встречал проблемы, подобные этой, когда люди предпочитали создавать новые CanvasJS.Chart объекты каждый раз, когда им нужно обновлять диаграммы. Однако это даже дырявое решение, поскольку вы создаете новый узел DOM каждый раз, когда поступают новые данные.

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

Наша модель в реальном времени

Мы собираемся создать ChartIO класс, который будет находиться между клиентским кодом и CanvasJS. ChartIO будет хранить буферы точек данных и обновлять их при необходимости, используя предопределенный API-интерфейс поставщика данных (или просто алгоритм случайных чисел).

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

const CanvasJS = require('canvas.js');
export class ChartIO {
  constructor(dpLen, chartContainerId, chartTitle="ChartIO Graph",
      channelSize=1) {
    const dataSeries = new Array(channelSize);
    const dataPointsBuffer = new Array(channelSize);
    for (let i = 0; i < channelSize; i++) {
      dataPointsBuffer[i] = new Array(dpLen);
      for (let j = 0; j < dpLen; j++)
        dataPointsBuffer[i][j] = { x: j, y: 0 };
      
      dataPointsBuffer[i].cur = dpLen+1;
      dataSeries[i] = {
        type: "line",
        dataPoints: dataPointsBuffer[i]
      };
    }
    this.dataPointsBuffer = dataPointsBuffer;
    this.dpLen = dpLen;
    this.chart = new CanvasJS.Chart(chartContainerId, {
       title: { text: chartTitle },
       axisY: { includeZero: true },
       data: dataSeries
    });
  }
}
export default ChartIO;

В конструкторе для ChartIO мы определили эти свойства:

  • dpLen (или длина точек данных): это количество точек данных, которые мы хотим отобразить пользователю. Все серии данных будут хранить только dpLen количество точек данных.
  • channelSize (или количество серий данных): это количество серий данных или «строк», которые мы хотим отобразить пользователю. Позже мы увидим, как мы можем использовать это для добавления статистических инструментов, таких как индикатор SMA.
  • dataPointsBuffer: Это массив, который будет хранить массивы точек данных. Каждый массив точек данных изначально заполняется нулевыми значениями y. Каждый массив точек данных имеет свойство cur, которое устанавливается равным количеству уже добавленных точек данных (не сохраненных, поскольку предыдущие данные удаляются через некоторое время). Свойство cur также сообщает следующее значение x для новых точек данных.
  • dataSeries: Это массив объектов серии данных. Вы можете видеть, что каждый объект серии данных ссылается на соответствующий элемент dataPointsBuffer как на свое свойство dataPoints. Это сделано для упрощения доступа к нашим данным. API CanvasJS требует, чтобы мы передавали массив объектов серии данных, а не точки данных напрямую.

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

addData(dataPoint, channelIndex=0) {// channel-index=data-series
    const dpBuf = this.dataPointsBuffer[channelIndex];
    const dpLen = this.dpLen;
    const rcyc = dpBuf[0];// recycle the first data-point object
    for (let i = 1; i < dpLen; i++) {
      dpBuf[i-1] = dpBuf[i];// shift left everything
    }
    rcyc.x = dpBuf.cur++;
    rcyc.y = dataPoint;
    dpBuf[dpLen - 1] = rcyc;// placed the recycled data-point obj
    this.chart.render();// render now!
}

Мы сделали кое-что творческое, переработав первый объект точки данных! Повторно используя первый объект точки данных, а затем помещая его в конец с обновленными данными, мы сохраняем выделение для каждого периода обновления!

Добавление статистических показателей и сглаживателей

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

Примеры статистических показателей

Если вы торговали акциями, то, возможно, слышали о скользящих средних и сигнале MACD. Хотя это не так сложно, мы можем использовать некоторые функции усреднения на нашем графике:

Простое среднее: вы можете рассчитать простое среднее n точек данных, если знаете три вещи: простое среднее последних n - 1 точки данных, следующая n-я точка данных.

S(n) = (S(n-1) * (n-1) + d(n)) / n

где S (n) - простое среднее значение для n точек данных, а d (n) - n-я точка данных.

Простая скользящая средняя: также сокращенно SMA. SMA учитывает только последние k точек данных вместо всех предыдущих точек данных. Отбрасывание точек данных из самого прошлого полезно при измерении средней тенденции в текущих точках данных. Это особенно полезно при измерении доходности акции, потому что историческая доходность (более 10 лет назад) не должна учитываться при расчете доходности текущего дня.

Чтобы вычислить SMA (без перебора последних k элементов):

SMA (n: n≥k + 1) = (SMA (n-1) * k - d (n-k) + d (n)) / k

ПРИМЕЧАНИЕ. Эта формула применяется только в том случае, если n больше k; для значений, меньших или равных k, SMA равно простому среднему.

Экспоненциальное среднее: SMA решает проблему учета исторических, но не связанных сейчас точек данных, не учитывая точки данных из далекого прошлого. Однако он по-прежнему считает все точки данных с равными весами. Советник решает эту проблему, присваивая прошлым точкам данных уменьшение веса. Например, (n-1) -ая точка данных имеет вес 0,5, (n-2) -й имеет вес 0,25, (n-3 ) th имеет .125 и так далее.

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

EA (n) можно рассчитать с помощью EA (n-1), w (n-1) и d (n ), где w - весовая функция.

w (n) = w (n-1) * r + 1, где r = 0,9.

EA(n) = (EA(n-1)*w(n-1) + d(n)) / w(n)

В приведенной выше формуле мы использовали r = 0,9, что означает, что каждый элемент теряет 10% своей «важности» с каждым новым обновлением. Для нормальной работы 0 ‹r≤1. Чем больше значение r, тем больше «сглаживающий» эффект советника; чем меньше значение r, тем актуальнее советник.

SmoothChartIO

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

import { ChartIO } from './ChartIO'
export class SmoothChartIO extends ChartIO {
  constructor(dpLen, chartContainerId, chartTitle, ...providers) {
    super(dpLen, chartContainerId, chartTitle, providers.length + 1);
    this.providers = providers;
    this.providersLastValue = [];
    for (let i = 0; i < providers.length; i++)
      this.providersLastValue[i] = 0;
  }
  addData(dataPoint) {
    super.addData(dataPoint, 0);
    for (let i = 0; i < this.providers.length; i++) {
      var value = this.providers[i](
                this.dataPointsBuffer[0].cur - this.dpLen - 1,
                this.providersLastValue[i],
                dataPoint,
                this.dataPointsBuffer[0]
              );
      super.addData(value, i + 1);
      this.providersLastValue[i] = value;
    }
  }
}

Наш класс SmoothChartIO добавляет следующие функции:

  • Массив поставщиков: поставщик - это функция, которая принимает четыре значения: n, p (n-1), d ( n) и сам d (n-dLen + 1… n). Это может сбивать с толку, но позвольте мне объяснить. p (n-1) - последнее значение, вычисленное поставщиком, d (n) - новая точка данных, а d (n-dLen + 1 … N) - это сам весь буфер. Все прояснится с кодом провайдера ниже.
  • providersLastValue[length=1+providers.count]: в этом массиве хранятся значения, возвращенные поставщиками из последнего вызова.

Написание наших провайдеров

Я собираюсь показать вам, как применять усредненные значения, которые мы уже обсуждали. Они следуют приведенным формулам!

Простое среднее:

export const SIMPLE_MAF = function(x, lastAverage, y) {
  return ((lastAverage * (x - 1)) + y) / x;
}

Это довольно просто. Это соответствует формуле, которую мы предоставили.

ПРИМЕЧАНИЕ для новичков в Javascript: функции JS не нужно записывать все аргументы, которые она получает. Вам нужно только написать первые n аргументов, которые вы собираетесь использовать. Следовательно, аргумент полного буфера точек данных не включается.

Простая скользящая средняя.

export const SIMPLE_MOVING_MAF = function(historySize) {
  return function(x, lastAverage, y, dBuf) {
    if (x > historySize) {
      let i = dBuf.length - historySize - 1;
      let sum = lastAverage * historySize;
      sum -= dBuf[i].y;
      sum += y;
      return sum / historySize;
    } else {
      let sum = lastAverage * (x - 1);
      sum += y;
      return sum / x;
    }
  }
}

ПРИМЕЧАНИЕ. SIMPLE_MOVING_MAF не является поставщиком услуг. Он возвращает поставщика, который учитывает только последние historySize точек данных при простом усреднении. historySize должен быть меньше dpLen на диаграмме. Например, используйте SIMPLE_MOVING_MAF(10) для SMA с k = 10.

Экспоненциальное среднее:

export const EXP_MAF = function(exp) {
  if (exp < 0 || exp > 1 || !(typeof exp === 'number')) {
    throw "exp must be b/w 0 & 1";
  }
  return function(x, lastAverage, y, dBuf) {
    if (dBuf.tweight === undefined)
      dBuf.tweight = 0;
    const ow = dBuf.tweight;
    dBuf.tweight = (dBuf.tweight * exp) + 1;
    return (lastAverage*ow*exp + y) / dBuf.tweight;
  }
}

Это тоже фабрика, как в примере с SMA. Он прикрепляет свойство tweight к буферу точек данных. То есть хранить общий вес последнего советника.



Написание нашего приложения

// index.js
import { SmoothChartIO } from './SmoothChartIO'
const chart = new SmoothChartIO(128, 'div-id', 'My Chart', EXP_MAF(0.95));
// chart with EA average with r=0.95
setInterval(function() {
  chart.addData(Math.random() * 100),
100);

Это простое приложение для проверки концепции выдает случайные числа и позволяет SmoothChartIO усреднить случайную функцию. Вы можете заменить случайный ряд данных более значимым источником входных данных! Кроме того, попробуйте реализовать дельта-функцию, показанную в моем «net K.E.» Диаграмма.

Дальнейшее чтение:

Расскажите мне в Twitter, как работало ваше приложение с динамическими диаграммами! Пришлите мне несколько скриншотов!