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

Отказ от ответственности: библиотека графиков TradingView - это бесплатный, но частный проект на Github, для доступа к которому вы должны подать заявку. Лицензионное соглашение, как я полагаю, запрещает мне распространять его вам, поэтому, чтобы полностью изучить это руководство, вам нужно будет подать заявку на получение доступа для загрузки библиотеки диаграмм.

Чтобы запустить эту часть руководства локально (при условии, что у вас есть доступ к библиотеке диаграмм), клонируйте репозиторий, доступный ниже, а затем скопируйте папку библиотеки диаграмм в каталог /public/ в папке part1. Запустите npm install, а затем npm start, чтобы запустить сервер разработки.

Репозиторий учебника: https://github.com/jonchurch/tradingview-js-api-tutorial.git

Развернутая предварительная версия: https://tv-tut-part1.glitch.me/

TradingView позволяет вам использовать их Библиотеку графиков на вашем собственном сайте с вашим собственным источником данных.

Есть два способа получить ваши данные в TradingView: UDF API и JS API. JS API дает вам максимальный контроль над вашими данными и, на мой взгляд, гораздо более гибок. Плюс это Javascript!

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

ПРИМЕЧАНИЕ. TradingView НЕ предоставляет вам этот источник данных и предполагает, что вы реализовали свой собственный источник. В качестве источника данных для удобства в этом руководстве используется API исторических цен CryptoCompare.

Это руководство основано на примере React Javascript TradingView, доступном здесь.

Обзор

При первой загрузке виджета диаграммы он вызывает метод JS API resolveSymbol с именем символа для пары по умолчанию. В нашем примере Coinbase:BTC/USD - пара по умолчанию. Ожидается, что вы передадите объект symbolInfo в обратный вызов onSymbolResolved, переданный в функцию resolveSymbol библиотекой построения графиков.

Вся интеграция состоит из нескольких частей:

  • Конструктор ТВ-виджетов - принимает объект параметров виджета, передает канал данных, отображаемую пару символов по умолчанию, параметры пользователя, параметры загрузки / сохранения диаграммы
  • Datafeed - интерфейс между JS API и серверной частью
  • JS API - сигнатуры функций, необходимые ТВ для отображения ваших данных
  • Провайдер истории - API панели OHLCV
  • Поставщик в реальном времени - обновляйте последнюю свечу в реальном времени, запускайте новые свечи
  • Магазин символов - список доступных символов

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

Конструктор виджетов

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

Здесь мы устанавливаем такие параметры, как идентификатор пользователя, настройки стиля, язык, пару символов для загрузки, общедоступный путь к библиотеке диаграмм и передаем нашу реализацию JS API Datafeed.

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

Вот параметры конструктора, с которых мы начинаем:

const widgetOptions = {
   debug: false,
   symbol: 'Coinbase:BTC/USD',
   datafeed: Datafeed, // our datafeed object
   interval: '15',
   container_id: 'tv_chart_container',
   library_path: '/charting_library/',
   locale: getLanguageFromURL() || 'en',
   disabled_features: ['use_localstorage_for_settings'],
   enabled_features: [],
   client_id: 'test',
   user_id: 'public_user_id',
   fullscreen: false,
   autosize: true,
   overrides: {
    "paneProperties.background": "#131722",
    "paneProperties.vertGridProperties.color": "#363c4e",
    "paneProperties.horzGridProperties.color": "#363c4e",
    "symbolWatermarkProperties.transparency": 90,
    "scalesProperties.textColor" : "#AAA",
    "mainSeriesProperties.candleStyle.wickUpColor": '#336854',
    "mainSeriesProperties.candleStyle.wickDownColor": '#7f323f',
   }
  };

Это настраивает виджет так, чтобы он отображал Coinbase:BTC/USD с интервалом между свечами в 15 минут, и устанавливает некоторые другие настройки (отключенные функции, параметры по умолчанию, которые необходимо изменить, какой язык использовать и т. Д.).

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

Я установил для своих графиков по умолчанию темный режим, установив overrides."painProperties.background": “#131722”.

Интеграция с JS API Datafeed

Теперь, когда у нас есть виджет, настроенный и оформленный так, как нам нравится, давайте посмотрим, как мы подключаем данные нашего графика к JS API библиотеки графиков TradingView.

JS API - это действительно объект, который вы предоставляете для виджета TradingView, который предоставляет функции, которые будет вызывать TradingView, и в большинстве случаев ожидается, что вы будете передавать данные в обратные вызовы внутри этих функций, чтобы ваши данные работали с tradingview.

Например, мы используем исторические данные диаграммы * компании CryptoCompare, а во второй части - их веб-интерфейс API для получения обновлений цен в реальном времени.

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

Ниже представлена ​​полная сигнатура объекта JS API, которую Tradingview ожидает от вас, чтобы передать виджету. Некоторые методы необязательны, дополнительную информацию см. в документации

{
/* mandatory methods for realtime chart */
onReady: cb => {},
// only need searchSymbols when search is enabled
searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {},
resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {},
getBars: (symbolInfo, resolution, from, to, onHistoryCallback, onErrorCallback, firstDataRequest) => {},
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscribeUID, onResetCacheNeededCallback) => {},
unsubscribeBars: subscriberUID => {},

/* optional methods */
getServerTime: cb => {},
calculateHistoryDepth: (resolution, resolutionBack, intervalBack) => {},
getMarks: (symbolInfo, startDate, endDate, onDataCallback, resolution) => {},
getTimeScaleMarks: (symbolInfo, startDate, endDate, onDataCallback, resolution) => {}
}

При первой загрузке диаграммы поток JS API выглядит следующим образом:

1. onReady получает вызов, передайте параметры конфигурации канала данных cb

2. resolveSymbol получает вызов, передайте объект symbolInfo onSymbolResolvedCallback

3. getBars, передайте массив объектов ohlcv со временем UTC в миллисекундах в onHistoryCallback

Давайте посмотрим на нашу реализацию для каждого из этих

onReady

const config = {
  supported_resolutions: ["1", "3", "5", "15", "30", "60", "120",   "240", "D"]
}
onReady: cb => {
 console.log('=====onReady running') 
  setTimeout(() => cb(config), 0)
 }

onReady вызывается сразу после инициализации виджета диаграммы, и мы должны передать параметры конфигурации канала данных в функцию onReady cb. Библиотека диаграмм хочет, чтобы это выполнялось асинхронно, и предлагает обернуть в setTimeout с задержкой 0, чтобы заставить это поведение.

Сейчас мы указываем только один из возможных вариантов supported_resolutions, который сообщает библиотеке графиков, какой интервал поддерживает наш канал данных для баров. Они будут показаны пользователю и могут быть переопределены для каждой пары символов позже в resolveSymbol. Список, который мы предоставили, означает:

1 minute, 3 minute, 15 minute, 30 minute, 1 hour, 2 hour, 4 hour, 1 day

Позже в этом руководстве мы добавим параметры в конфигурацию Datafeed, поскольку реализуем поиск и диаграммы в реальном времени.

resolveSymbol

После настройки канала данных библиотека диаграмм вызовет resolveSymbol со свойством symbol из объекта конфигурации виджета. Нам дано только строковое значение, и мы должны вернуть объект symbolInfo, представляющий соответствующий символ.

resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {
  var split_data = symbolName.split(/[:/]/)
  
  var symbol_stub = {
   name: symbolName,
   description: '',
   type: 'crypto',
   session: '24x7',
   timezone: 'America/New_York',
   ticker: symbolName,
   minmov: 1,
   pricescale: 100000000,
   has_intraday: true,
   intraday_multipliers: ['1', '60'],
   supported_resolution:  ["1", "3", "5", "15", "30", "60", "120",   "240", "D"],
   volume_precision: 8,
   data_status: 'streaming',
  }
if (split_data[2].match(/USD|EUR|JPY|AUD|GBP|KRW|CNY/)) {
   symbol_stub.pricescale = 100
  }
  
  setTimeout(function() {
   onSymbolResolvedCallback(symbol_stub)
  }, 0)
}

Здесь вы настраиваете индивидуальную пару, устанавливаете количество отображаемых десятичных знаков, сколько она перемещается за тик (для криптовалюты это почти всегда будет 1), и очень важно и легко облажаться , intraday_multipliers! Поскольку торговля криптовалютой ведется без перерыва, мы установили сеанс ‘24x7'. Предполагается, что часовой пояс является часовым поясом биржи, на которой торгуется этот символ, что не очень важно для 24-часовых сессий.

Вся документация по symbolInfo здесь, обязательно ознакомьтесь с ней.

intraday_multipliers и has_intraday, отображающие интервалы бара меньше 1 дня. Вот где я сделал много ошибок: Tradingview может построить для вас несколько баров. Например, представим, что наш API исторических данных может предоставлять нам данные только для 1-минутных интервалов. Это означает, что если мы запрашиваем данные за последние 24 часа, мы получим 1440 точек данных, то есть количество минут за 24 часа. Но что, если мы хотим отображать 15-минутные столбцы? Вы можете сказать tradingview, что наш intraday_multiplier равен ‘1’, и пропускать только 1-минутные бары. Библиотека графиков построит для вас 15-минутные бары и отобразит их на графике.

Мы делаем то же самое для часовых баров, сообщая tradingview, что мы можем предоставить 60-минутные бары, и что он должен строить наши 2-часовые и 4-часовые бары из наших 60-минутных баров.

Тикер также очень важен. Если установлено, тогда библиотека диаграмм будет использовать тикер внутри для ссылки на эту уникальную пару (значение тикера будет отправлено в resolveSymbol вместо поля имени). Поле имени - это то, что будет отображаться пользователям. Я установил для name и ticker одно и то же значение, чтобы облегчить себе жизнь, и поскольку имена, которые я использую, включают всю информацию, которая мне нужна для идентификации символа: обмен, символ и символ (например, Coinbase: BTC / USD)

Шкала цен немного интересна, потому что разные пары могут использовать разную десятичную точность. Например, BTC / USD измеряется с точностью до двух десятичных знаков, поэтому pricescale = 100, но, скажем, TRX / BTC (0,00000771 BTC на момент написания), мы измеряем его до сатоши, 8 десятичных знаков. Итак, для TRX / BTC pricescale = 100000000, но для TRX / USD (0,059432 доллара на момент написания) мы переходим к 6 десятичным знакам и pricescale = 1000000.

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

getBars

А теперь самое интересное! Получение данных графика из нашего источника API и передача их в TradingView.

getBars: function(symbolInfo, resolution, from, to, onHistoryCallback, onErrorCallback, firstDataRequest) {

  historyProvider.getBars(symbolInfo, resolution, from, to, firstDataRequest)
  .then(bars => {
   if (bars.length) {
    onHistoryCallback(bars, {noData: false})
   } else {
    onHistoryCallback(bars, {noData: true})
   }
  }).catch(err => {
   console.log({err})
   onErrorCallback(err)
  })
}
...
/* historyProvider.js */
var rp = require('request-promise').defaults({json: true})
    getBars: function(symbolInfo, resolution, from, to, first, limit) {
  var split_symbol = symbolInfo.name.split(/[:/]/)
   
  const url = resolution === 'D' ? '/data/histoday' : resolution >= 60 ? '/data/histohour' : '/data/histominute'
   const qs = {
     e: split_symbol[0], // Coinbase
     fsym: split_symbol[1], // BTC
     tsym: split_symbol[2], // USD
     toTs:  to ? to : '',
     limit: 2000, 
    }
return rp({
                url: `${api_root}${url}`,
                qs,
            })
            .then(data => {
    if (data.Response && data.Response === 'Error') {
     console.log('CryptoCompare API error:',data.Message)
     return []
    }
    if (data.Data.length) {
     var bars = data.Data.map(el => {
      return {
       time: el.time * 1000, //TradingView requires bar time in ms
       low: el.low,
       high: el.high,
       open: el.open,
       close: el.close,
       volume: el.volumefrom 
      }
     })
     return bars
    } else {
     return []
    }
   })
}

Хорошо, давайте разберем весь этот код!

Tradingview вызывает getBars и передает в объект symbolInfo, который мы передали в обратный вызов resolveSymbol resolve, разрешение (нужны ли нам 1-минутные столбцы? 60-минутные столбцы? 1 день?), В метки времени и обратно, а также логическую маркировку, если это первые данные. запрос на этот символ.

Оттуда мы вызываем historyProvider.getBars, который является кодом, который мы написали для извлечения исторических данных ohlcv из API исторических цен Cryptocompare. Мы должны передать массив данных бара в getBar's onHistoryCallback, этот массив может выглядеть следующим образом для данных бара за 1 минуту:

[
...{
time: 1528322340000, //bar time must be in milliseconds
open: 7678.85,
high: 7702.55,
low: 7656.98,
close: 7658.25,
volume: 0.9834
},
...
]

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

Поскольку мы решили поместить всю необходимую информацию в название символа (Coinbase: BTC / USD), мы можем извлечь эти параметры из строки symbolInfo.name.

TradingView также передает resolution в getBars, который сообщает, какую конечную точку API мы запрашиваем у CryptoCompare, конечную точку исторических данных за минуту, час или день.

Из-за ограничений API CryptoCompare (мы можем получать только 2000 записей за раз) мы можем передавать неполный набор данных, запрошенных TradingView. Не стоит беспокоиться! getBars будет вызываться снова с временными метками new to и from, пока не будут получены все данные, необходимые для заполнения видимой части диаграммы.

Ура, статические диаграммы!

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

Вы, вероятно, думаете: Хорошо, Джон, но статические диаграммы мне не очень помогают. В части 2 этой серии руководств мы реализуем обновления графика в реальном времени. Важно сначала усвоить изложенные здесь концепции и ознакомиться с документацией TradingView.

Вот развернутая предварительная версия части 1: https://tv-tut-part1.glitch.me/

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

Код

Ознакомьтесь с кодом для всех частей этой серии руководств здесь.



* Использование сторонних сервисов для предоставления данных диаграммы в производственной среде не является оптимальным. Например, Cryptocompare ограничивает минутные данные только 7 днями в прошлом. Прекрасно для нашего руководства, но это не позволяет нам отображать минутные данные за пределами 7 дней в прошлом. Кроме того, вы ограничены лимитами API CryptoCompare и всеми возможными простоями, с которыми они могут столкнуться.