24 Dec 23:47 avatar

Финансовый Telegram-бот за 30 минут с Market Data API

Наука и техника: Финансовый Telegram-бот за 30 минут с Market Data API

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

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

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

Доступ к данным

Начнем с простой ситуации: приложение будет использовать текущие и исторические данные о торгах, а не отправлять на биржу собственные заявки. Эти данные (т. н. market data, или биржевую информацию) можно получать у ряда компаний за относительно небольшую плату или вообще бесплатно. С отправкой заявок все заметно сложнее (как минимум — дороже), и мы рассмотрим этот процесс подробнее в следующих статьях.
Какие существуют технологии для получения биржевых данных? Список их не слишком велик: это FIX-протокол (реализации могут немного различаться от поставщика к поставщику), FAST, ITCH и несколько вариантов бинарных и HTTP API (к примеру, CQG, EXANTE или MOEX). Впрочем, универсализация здесь не так принципиальна: набор предоставляемых данных может сильно различаться, и в любом случае при интеграции придется разобраться с особенностями конкретного поставщика.
Наука и техника: Финансовый Telegram-бот за 30 минут с Market Data API

Мы будем использовать недавно появившийся EXANTE Market Data API: начать разработку с ним можно просто и быстро, регистрация в системе не требует дополнительных подтверждений, а доступ к данным бесплатен. Пока API работает в режиме Tech Preview, но доступ открыт для всех желающих.

Функциональность

Определив спектр возможностей, нужно решить, что именно будет делать наш чат-бот. Существует масса вариантов: от отображения курсов валют до аналитики торговых стратегий по запросу на конкретный биржевой инструмент. Пока что не станем углубляться в детали финансовых алгоритмов и попробуем сделать что-то полезное, но при этом достаточно простое.
Один из самых понятных финансовых инструментов — это акции компаний, торгующиеся на фондовых биржах. С ними и будем работать, выбрав для простоты фондовый рынок США, т. к. по нему легче всего получить фундаментальные данные, и торги там наиболее активны.
Что интересует начинающего инвестора? Конечно же, выбор портфеля акций, вложив средства в которые, он сможет получить прибыль. Существует много способов выбирать акции: можно читать обзоры, можно ориентироваться на портфели лучших инвесторов, вроде Уоррена Баффетта или Билла Экмана, а можно пользоваться аналитическими методами. Один из общепринятых и самых распространенных методов — это оценка компании по метрике P/E (коэффициент цена/прибыль). P/E рассчитывается как отношение нынешней цены акции компании к показателю Earning Per Share (EPS, прибыль на акцию).
Таким образом, наш чат-бот будет помогать инвестору решить, включать ли акции определенной компании фондового рынка США в свой портфель, исходя из текущей оценки коэффициента цена/прибыль. Высокий P/E относительно других компаний этой отрасли покажет, что у акций есть потенциал роста. Низкий же, напротив, даст понять, что в будущем компания может столкнуться с проблемами.

Архитектура

Итак, в качестве основного источника биржевой информации выберем EXANTE Market Data API (MD API). Для получения фундаментальной информации — информации об общем состоянии финансов компании — будем использовать открытый источник данных datatables.org, с которым можно работать через YQL (Yahoo! Query Language).
Для реализации самого бота возьмем Python 3, а чтобы запустить его максимально быстро, применим фреймворк, поддерживающий все необходимые методы Telegram: python-telegram-bot.
Для работы с Telegram будем использовать поллинг новых сообщений с сервера, т. к. в прототипе мы не рассчитываем на большой объем трафика. 
Заранее подумаем о том, чтобы приложение могло работать не только с одним клиентом. Для этого будем обрабатывать запросы в отдельных потоках. Для синхронизации и запуска потоков используем встроенные возможности фреймворка python-telegram-bot и примитивы синхронизации, доступные в Python.
Все выбранные внешние сервисы доступны по HTTP, так что для работы с ними будем использовать известный модуль Requests.
Наверняка многие инвесторы будут интересоваться одними и теми же акциями, которые на слуху, так что добавим слой кэширования, чтобы эффективнее использовать ресурсы.
MD API требует авторизации запросов с помощью JSON Web Token, для генерации токенов возьмем библиотеку PyJWT.

Подключение к API

Для начала работы с MD API нужно зарегистрироваться на сайте EXANTE для разработчиков
После регистрации на портале становится доступным дэшборд с данными для доступа и управлением приложениями. Создадим там приложение для нашего бота:
Наука и техника: Финансовый Telegram-бот за 30 минут с Market Data API
Самого бота заведем так, как описано в документации к Telegram, через переписку с роботом BotFather:

Наука и техника: Финансовый Telegram-бот за 30 минут с Market Data API


Реализация

Начнем с того, что научим бота обрабатывать полученные запросы. Из каждого сообщения будем пытаться выделить тикеры акций и выдавать по ним информацию, чтобы диалог мог выглядеть так:
— Привет, робот, сегодня в новостях слышал об AAPL, кажется, это какая-то фруктовая компания, думаю вложить туда деньги, что скажешь?
— Акции AAPL (Apple Inc, биржа NASDAQ) имеют текущую оценку P/E 14, цена акции $117,06
— Спасибо, а что насчет NVDA и GOOG?
— NVDA (Nvidia Corp., NASDAQ): P/E 69, цена $105.7
GOOG (Alphabet Inc., NASDAQ): P/E 29, цена $796.42

Инициализируем бота и создаем обработчики сообщений:

<code class="python hljs"><span class="hljs-comment"># -*- coding:utf-8 -*-</span> <span class="hljs-keyword">import</span> re <span class="hljs-keyword">from</span> sys <span class="hljs-keyword">import</span> path <span class="hljs-keyword">from</span> configparser <span class="hljs-keyword">import</span> ConfigParser <span class="hljs-keyword">from</span> telegram <span class="hljs-keyword">import</span> ParseMode, Emoji <span class="hljs-keyword">from</span> telegram.ext <span class="hljs-keyword">import</span> Updater, CommandHandler, MessageHandler, Filters config = ConfigParser() config.read_file(open(<span class="hljs-string">'config.ini'</span>)) <span class="hljs-comment"># Create telegram poller with token from settings</span> up = Updater(token=config[‘Api’][<span class="hljs-string">'token'</span>]) dispatcher = up.dispatcher <span class="hljs-comment"># Welcome message</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">start</span><span class="hljs-params">(bot, update)</span>:</span> msg = <span class="hljs-string">"Hello {user_name}! I'm {bot_name}. Ask me about stocks!"</span> <span class="hljs-comment"># Send the message</span> bot.send_message(chat_id=update.message.chat_id, text=msg.format( user_name=update.message.from_user.first_name, bot_name=bot.name)) <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process</span><span class="hljs-params">(bot, update)</span>:</span> msg = <span class="hljs-string">"I will try to show info on {tickers}"</span> tickers = re.findall(<span class="hljs-string">r'[A-Z]{1,4}'</span>, update.message.text) bot.send_message(chat_id=update.message.chat_id, text=msg.format(tickers=<span class="hljs-string">", "</span>.join(tickers))) <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span><span class="hljs-params">()</span>:</span> <span class="hljs-comment"># Add handlers to dispatcher</span> dispatcher.add_handler(CommandHandler(<span class="hljs-string">"start"</span>, start)) dispatcher.add_handler(MessageHandler(Filters.text, process)) <span class="hljs-comment"># Start the program</span> up.start_polling() up.idle() <span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>: main()</code>


Сейчас наш бот уже умеет выделять тикеры акций, но ничего больше с ними сделать не может. 

Напишем интерфейс для работы с Market Data API и генерации токенов. Используем документацию и руководство по авторизации.

<code class="python hljs"><span class="hljs-keyword">import</span> jwt <span class="hljs-comment"># token expiration time in seconds</span> EXPIRATION = <span class="hljs-number">3600</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MDApiConnector</span><span class="hljs-params">()</span>:</span> token = (<span class="hljs-keyword">None</span>, <span class="hljs-keyword">None</span>) algo = <span class="hljs-string">"HS256"</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self, client_id, app_id, key)</span>:</span> self.client_id = client_id self.app_id = app_id self.key = key <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__get_token</span><span class="hljs-params">(self)</span>:</span> now = datetime.now() <span class="hljs-comment"># if there is token and it's not expired yet</span> <span class="hljs-keyword">if</span> self.token[<span class="hljs-number">0</span>] <span class="hljs-keyword">and</span> (now - self.token[<span class="hljs-number">1</span>]).total_seconds() < EXPIRATION: <span class="hljs-keyword">return</span> self.token[<span class="hljs-number">0</span>] claims = { <span class="hljs-string">"iss"</span>: self.client_id, <span class="hljs-string">"sub"</span>: self.app_id, <span class="hljs-string">"aud"</span>: [<span class="hljs-string">"symbols"</span>, <span class="hljs-string">"ohlc"</span>], <span class="hljs-comment"># NB: only allowed scopes can be accessed</span> <span class="hljs-string">"iat"</span>: int(now.timestamp()), <span class="hljs-string">"exp"</span>: int(now.timestamp()) + EXPIRATION } new_token = str(jwt.encode(claims, self.key, self.algo), ‘utf<span class="hljs-number">-8</span>’) self.token = (new_token, now) <span class="hljs-keyword">return</span> new_token</code>


Полный код всех модулей доступен в репозитории: github.com/exante/telegram-bot-with-md-api

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

<code class="python hljs"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DataStorage</span><span class="hljs-params">(Thread)</span>:</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self, connector)</span>:</span> super().__init__() self.connector = connector self.stocks = {} <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">run</span><span class="hljs-params">(self)</span>:</span> <span class="hljs-keyword">while</span> <span class="hljs-keyword">True</span>: timeout = <span class="hljs-number">15</span> * <span class="hljs-number">60</span> <span class="hljs-comment"># 15 minutes</span> <span class="hljs-keyword">try</span>: self.stocks = connector.get_stocks() <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e: logger.error(e) timeout = <span class="hljs-number">30</span> <span class="hljs-comment"># re-read in case of exception</span> time.sleep(timeout)</code>


Метод работы с API для получения списка акций США может выглядеть так:

<code class="python hljs"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_stocks</span><span class="hljs-params">(self)</span>:</span> stocks = self.__request(<span class="hljs-string">"/types/STOCK"</span>) <span class="hljs-keyword">return</span> {x[<span class="hljs-string">'ticker'</span>]: {<span class="hljs-string">"id"</span>: x[<span class="hljs-string">"id"</span>], <span class="hljs-string">"exchange"</span>: x[<span class="hljs-string">"exchange"</span>], <span class="hljs-string">"description"</span>: x[<span class="hljs-string">"description"</span>]} <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> stocks <span class="hljs-keyword">if</span> x.get(<span class="hljs-string">"country"</span>) == <span class="hljs-string">"US"</span>} </code>


После запуска этого потока и обращения к нему из обработчика сообщения, бот сможет вывести больше полезных данных (P/E здесь пока еще заглушка):

Наука и техника: Финансовый Telegram-бот за 30 минут с Market Data API


Добавим запрос Earning Per Share, для этого сделаем небольшую обертку над YQL с кэшированием (в скором будущем мы сможем заменить этот вызов на аналогичный из MD API), которая запросит значение «EarningsShare» для выбранной акции.

Теперь мы можем вывести полученный показатель EPS:

Наука и техника: Финансовый Telegram-бот за 30 минут с Market Data API


Осталось последнее: получить текущую цену акции. Для большей производительности нам следовало бы подписаться на поток обновлений с ценами, но для прототипа можно выбрать более простой способ: запрашивать последнюю дневную «свечу» — так называют элемент графика цен, популярного среди трейдеров.
Наука и техника: Финансовый Telegram-бот за 30 минут с Market Data API

Пример свечного графика соотношения индекса DJI и цены на золото по годам

«Свеча» строится для определенного периода (например, дня или часа) и на одном рисунке объединяет четыре цифры: цену на начало периода, максимальную и минимальную цену за период и цену на момент окончания периода. Сокращение OHLC, обозначающее такую свечу, как раз и расшифровывается как Open-High-Low-Close. Цена Close самой последней свечи будет соответствовать текущей цене акции.
Метод получения последней свечи может выглядеть так:

<code class="python hljs"> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_last_ohlc_bar</span><span class="hljs-params">(self, symbolId)</span>:</span> <span class="hljs-comment"># NB: we use internal symbolId, not ticker</span> <span class="hljs-comment"># 86400 (sec) - day duration</span> ohlc = self.__request(<span class="hljs-string">"/ohlc/%s/86400"</span> % symbolId, {<span class="hljs-string">"size"</span>: <span class="hljs-number">1</span>}) <span class="hljs-keyword">return</span> ohlc[<span class="hljs-number">0</span>]</code>


Собрав вместе все вызовы, мы получим такой код обработки одного тикера:

<code class="python hljs"> stock = storage.stocks.get(ticker) eps = fundamendal_api.request(ticker).get(<span class="hljs-string">'EarningsShare'</span>) price = api.get_last_ohlc_bar(stock[<span class="hljs-string">'id'</span>]) ratio = Decimal(<span class="hljs-string">"%.4f"</span> % price[<span class="hljs-string">'close'</span>]) / Decimal(eps) msg = <span class="hljs-string">"{ticker} ({name}, {exchange}): EPS {eps}, P/E {ratio}, цена ${price} \n"</span>.format( ticker = ticker, name = stock[<span class="hljs-string">'description'</span>], exchange = stock[<span class="hljs-string">'exchange'</span>], ratio = <span class="hljs-string">"%.2f"</span> % ratio, price = price[<span class="hljs-string">'close'</span>], eps = eps )</code>


И теперь наш бот стал действительно полезен! Он может рассказать о текущем положении дел на рынке акций и даже кое-что посоветовать:
Наука и техника: Финансовый Telegram-бот за 30 минут с Market Data API

Развитие проекта

Текущий проект можно найти по адресу github.com/exante/telegram-bot-with-md-api
Дальнейшее развитие возможно по многим направлениям. К примеру, можно воспользоваться потоком данных о нынешней цене акции из MD API (

/md/1.0/feed
) и не запрашивать цену каждый раз из «свечек», а просто брать ее из внутреннего кэша, куда та будет попадать при обновлении потока.
Можно добавить боту мониторинг и аналитику (например через botan.io), а также развернуть его на каком-нибудь облачном хостинге, вроде Heroku или Google App Engine. 
Бота можно сделать более «живым», добавив больше вариантов ответов, а также научить его отображать графики изменения цен, чтобы дать инвестору еще больше информации для анализа. Можно добавить любые другие метрики для оценки акций, сохранять портфель в данных робота, чтобы держать инвестора в курсе всех изменений, и расширить функциональность — например, на российский рынок акций.

Заключение

Подключив EXANTE Market Data API и воспользовавшись открытой фундаментальной информацией, за короткий срок мы разработали функционального робота, который поможет пользователю быстро оценить ситуацию на рынке. В процессе работы мы узнали о некоторых способах оценки акций на рынке и о терминологии, используемой в биржевой торговле.
Кроме того, мы рассмотрели возможности развития, и даже у такого небольшого робота их немало. Есть еще много способов применения market data — и много пользователей, которые заинтересуются вашими финансовыми приложениями.
В феврале EXANTE проведет хакатон, посвященный работе с рыночными данными с помощью EXANTE Market Data API. Авторы лучших чат-ботов и приложений получат призы, и сейчас как раз есть время подготовиться :) 
А какие API используете вы? Что бы вы хотели делать с рыночными данными?

habrahabr.ru

0 комментариев

Оставить комментарий