Эксперимент VonmoTrade. Часть 4: Торговые графики

от автора

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

Прежде чем начать, хочу сделать небольшое отступление. Для внутренних проектов Vonmo используется обычная схема именования V+слово, наиболее ёмко характеризующее функции проекта. Сегодня я обнаружил, что VTrade – уже существующая компания. Дабы не вносить путаницу, я переименовал эксперимент в VonmoTrade.

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

  1. Линейные;
  2. Интервальные.

Линейные графики

Самый простой и понятный без подготовки график. Отображает зависимость цены финансового инструмента от времени.

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

Разрешение графика

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

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

Бары

Относятся к интервальным графикам. Для повышения информативности, необходимо для каждого интервала времени отобразить информацию о цене в начале и конце интервала, а также максимальную и минимальную цену. Графическое отображение этого набора называется баром. Рассмотрим схему одного бара:

Последовательность баров формирует график:

Японские свечи

Как и бары, относятся к интервальным графикам. Являются самым популярным типом графика при техническом анализе. Свеча состоит из чёрного либо белого тела и теней: верхней и нижней. Иногда тень называют фитилем. Верхняя и нижняя граница тени отображает максимум и минимум цены за соответствующий период. Границы тела отображают цену открытия и закрытия. Изобразим свечу:

Последовательность свеч образуют график:

OHLCV нотация

В прошлой статье мы разобрались со схемой хранения данных для графика в postgresql и создали таблицу для источника данных, которая будет хранить агрегированные данные:

CREATE TABLE df (     t timestamp without time zone NOT NULL,     r df_resolution NOT NULL DEFAULT '1m'::df_resolution,     o numeric(64,32),     h numeric(64,32),     l numeric(64,32),     c numeric(64,32),     v numeric(64,32),     CONSTRAINT df_pk PRIMARY KEY (t, r) )

Поля не требуют объяснения, кроме поля r – разрешение ряда. В postgresql есть перечисления, ими удобно пользоваться, когда заранее известен набор значений для какого-то поля. Через перечисления определим новый тип для допустимых разрешений графиков. Пусть это будет ряд от одной минуты до одного месяца:

CREATE TYPE df_resolution AS ENUM     ('1m', '3m', '5m', '15m', '30m', '45m',       '1h', '2h', '4h', '6h', '8h', '12h',       '1d', '3d', '1w', '1M');

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

  • Мы можем рассчитывать и хранить все резолюции в базе. Вариант удобен тем, что при выборке мы не тратим мощности на агрегацию интервалов, все данные сразу готовы к выдаче. В месяц для одного инструмента будет создано чуть более 72 тыс. записей. Выглядит просто и удобно, однако такая таблица будет слишком часто изменяться, так как на каждое обновление цен необходимо создать или обновить 16 записей в таблице и перестроить индекс. В postgresql дополнительно может возникнуть проблема со сборкой мусора.
  • Другим вариантом является хранение единственной базовой резолюции. При выборке из базовой резолюции необходимо построить требуемые резолюции. Например, при хранении минутной резолюции в качестве базовой в месяц для каждого инструмента будет создано 43 тыс записей. Таким образом, по сравнению с прошлым вариантом, объем записи и накладных расходов уменьшается на 40%. Нагрузка на процессор, однако, возрастает.

Как говорилось выше, важно найти баланс. Поэтому компромиссным вариантом будет хранение не одной базовой резолюции, а нескольких: 1 минута, 1 час, 1 день. При такой схеме для каждого инструмента в месяц будет создано 44,6 тыс записей. Оптимизация объема записи составит 36%, но при этом нагрузка на процессор будет приемлемой. Например, для построения недельных интервалов вместо считывания и агрегации 10080 записей в случае минутной базовой резолюции, нам потребуется считать с диска и агрегировать данные всего 7-ми дневных резолюций.

Хранение OHLCV

По природе OHLCV – временной ряд. Как известно, реляционная база данных не очень хорошо подходит для хранения и обработки подобных данных. Для решения этих проблем в проекте используется расширение Timescale.

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

Для создания и обновления баров нам потребуются только стандартные функции:

  • date_trunc(‘minute’ | ’hour’ | ’day’, transaction_ts) – для нахождения начала интервала минутной, часовой и дневной резолюции соответственно.
  • greatest и least для определения максимальной и минимальной цены.

Благодаря upsert api на каждую транзакцию выполняется только один запрос обновления.
У меня получился вот такой SQL для фиксации изменений рынка в базовых резолюциях:

FOR i IN 1 .. array_upper(storage_resolutions, 1) LOOP     resolution = storage_resolutions[i];     IF resolution = '1m' THEN         SELECT DATE_TRUNC('minute', ts) INTO bar_start;     ELSIF resolution = '1h' THEN         SELECT DATE_TRUNC('hour', ts) INTO bar_start;     ELSIF resolution = '1d' THEN         SELECT DATE_TRUNC('day', ts) INTO bar_start;     END IF;   EXECUTE format(     'INSERT INTO %I (t,r,o,h,l,c,v)     VALUES (%L,%L,%L::numeric,%L::numeric,%L::numeric,%L::numeric,%L::numeric)     ON CONFLICT (t,r) DO UPDATE     SET h = GREATEST(%I.h, %L::numeric), l = LEAST(%I.l, %L::numeric), c = %L::numeric, v = %I.v + %L::numeric;',     df_table, bar_start, resolution, price, price, price, price, volume,     df_table, price, df_table, price, price, df_table, volume  ); END LOOP;

При выборке, для агрегации интервалов нам понадобятся следующие функции:

  • time_bucket – для разбиения на интервалы
  • first – для нахождения цены открытия – O
  • max – наибольшая цена за интервал – H
  • min – наименьшая цена за интервал – L
  • last – для нахождения цены закрытия – C
  • sum – для нахождения объема торгов – V

Единственная найденная проблема при использовании Timescale – ограничения функции time_bucket. Она позволяет оперировать только интервалами меньшими чем месяц. Для построения месячной резолюции необходимо использовать стандартную функцию date_trunc.

API

Для отображения графиков на клиенте будем использовать lightweight-charts от Tradingview. Библиотека позволяет полностью настроить внешний вид графиков и удобна в работе. У меня получились вот такие графики:

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

Источник данных

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

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

Рассмотрим пример запроса 50 последних минут для USDGBP, с автоматической подпиской на обновления графика:

{    "m":"market",    "c":"get_chart",    "v":{       "charts":[          {             "ticker":"USDGBP",             "resolution":"1h",             "from":0,             "cnt":50,             "send_updates":true          }       ]    } }

Можно, конечно же, запрашивать диапазон дат (from, to), но так как интервал каждого бара известен, то декларативное API с указанием момента и количества бар мне кажется более удобным.
Датафид на этот запрос ответит подобным образом:

{    "m":"market",    "c":"chart",    "v":{       "bar_fields":[          "t","uts","o","h","l","c","v"       ],       "items":[          {             "ticker":"USDGBP",             "resolution":"1h",             "bars":[                [                   "2019-12-13 13:00:00",1576242000,"0.75236800",                   "0.76926400","0.75236800","0.76926400","138.10000000"                ],                ....             ]          }       ]    } }

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

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

{    "m":"market",    "c":"chart_tick",    "v":{       "ticker":"USDGBP",       "resolution":"1h",       "items":{          "v":"140.600",          "ut":1576242000,          "t":"2019-12-13T13:00:00",          "o":"0.752368",          "l":"0.752368",          "h":"0.770531",          "c":"0.770531"       }    } }

Предварительный итог

На протяжении цикла статей мы с вами разбирали теорию и практику построения биржи. Пришло время собирать систему воедино.

В следующей статье мы затронем вопросы разработки графических интерфейсов пользователя: служебный UI для управления платформой и UI для конечных потребителей. Также будет представлена демонстрационная версия Vonmo Trade.


ссылка на оригинал статьи https://habr.com/ru/post/483308/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *