Algopack Мосбиржи — получаем справочную информация о доступных акциях

fc69ad59b5f001fe9d6b860202956c1f.jpg

Совсем недавно, буквально 2 месяца назад, Мосбиржа запустила Algopack и выложила на Гитхаб долгожданную многими библиотеку на python — moexAlgo, которая должна упростить работу с AlgoPack API.

Что такое Алгопак?

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

Данные в ALGOPACK включают:

  • Super Candles — 5-минутные свечи с 50+ параметрами, история с 2020 года.

  • Mega Alerts — уведомления о рыночных аномалиях.

  • Market Signals — сигналы о рыночных аномалиях.

  • Market Data — стандартные онлайн данные: стаканы и свечи.

В настоящее время хорошего пособия с «разжеванными» примерами от Мосбиржи еще не существует. Поэтому я решил делиться своими экспериментами и постарался их подробно объяснять, чтобы трейдерам, даже только начинающим осваивать python (как и я), все было максимально понятно.

Возможно, кому то будет интересна и полезна документация для библиотеки moexalgo, которую можно сделать из докстрингов буквально за 2 минуты.

На данный момент я поставил задачу — вытащить исторические данные по российским акциям и в дальнейшем их регулярно обновлять. Это позволит мне при продолжении изучения Backtrader (торговый фреймворк для создания торгового робота) использовать данные Мосбиржи для компонента DataFeeds, а также разрабатывать и тестировать на исторических данных собственные торговые стратегии.

Приступим. Отправная точка — раздел moexalgo на Гитхабе.  Файл samples/quick_start.ipynb начинается с примера:

b2dba3f7f0cd785e74621c25ea7e80ff.png

И сразу возникает вопрос: что такое »stocks»?

На самом деле это алиас. Механизм алиасов в коде библиотеки на Python позволяет создавать псевдонимы для модулей или их функций, классов и переменных. Это полезно, когда нужно сократить длинные имена или импортировать функции или классы из модуля с более удобными именами. В библиотеке moexalgo в модуле market.py определены алиасы:

_ALIASES= { 
"index”: ("index”, "SNDX”),
"shares”: ("shares”, "TQBR”),
"stocks”: ("shares”, "TQBR”),
"EQ”: ("shares”, "TQBR”), 
}

Это означает, что объекты Market («stocks») и Market («shares/TQBR») будут эквивалентны и создают объект для работы с данными по акциям в режиме основных торгов TQBR. Первый вариант с использование «stocks» по замыслу разработчиков видимо является более удобным и коротким способом для написания кода.

Выражение Market («shares/TQBR») создает объект класса Market из библиотеки moexalgo, который представляет раздел рынка акций Московской биржи (TQBR).

TQBR позволяет осуществлять сделки с акциями.

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

Каждой торговой площадке на Мосбирже присваивается свой BOARDID:

BOARDID=TQBR — основная площадка для акций и облигаций (Торгово-Quotation Board)
BOARDID=TQTF — площадка для биржевых ETF и паев ПИФов
BOARDID=EQBR — площадка для торговли акциями малой капитализации
BOARDID=FQBR — площадка для корпоративных и муниципальных облигаций и т.д.
Этот идентификатор широко используется в отчетности и API Московской биржи.

Вернемся к нашему коду Market («shares/TQBR»).

При создании экземпляра класса Market нужно указать название раздела и режим торгов. В нашем случае shares — название раздела акций и TQBR — режим основных торгов акциями на Московской бирже.

А например другой вариант Market («index/SNDX») создаст объект класса Market для работы с индексами фондового рынка Московской биржи. Здесь index — раздел фондовых индексов, SNDX — режим основных торгов индексами Мосбиржи.

Сосредоточимся на акциях

Далее мы уже можем получить справочную информацию о всех инструментах рынка.

from moexalgo import Market

stocks = Market("shares/TQBR")
all_stocks = stocks.tickers()  # Вызоваем метод tickers() на экземпляре класса Market
print(type(all_stocks)) 
#  или  в зависимости от того, где вызвали метод (в интерактивной среде или нет) 

Здесь мы создаём объект stocks, который будет представлять раздел акций Московской биржи в режиме основных торгов TQBR. И далее вызываем метод stocks.tickers (), который возвращает информацию по всем инструментам (тикерам акций) с их метаданными в этом разделе рынка. Результат сохраняем в переменной all_stocks. Можем вывести на печать тип и увидим, что это будет список словарей с данными по каждой акции.

❗️ Тут есть одно важное замечание: метод tickers возвращает нам тип данных в зависимости от того, где выполняется код программы. Если программа выполняется в интерактивной среде (например, в Jupyter Notebook), то функция tickers возвращает результат в виде pandas DataFrame, потому что в интерактивных средах удобно работать с DataFrame. Если код выполняется не в интерактивной среде, то функция tickers возвращает результат в виде итератора объектов данных, потому что вне интерактивных сред это видимо более эффективный формат.

Так как в конечном итоге нам нужен код для обычной программы, и мне все же хочется оперировать с данными именно через pandas, то давайте полученную информацию в виде списка list отправим в Dataframe pandas. (Замечу, что Pandas для знакомства и начала работы с ним очень простой. Главное понять, что DataFrame в pandas  — это по сути плоская таблица данных, практически как Excell. Все остальное можно узнавать и изучать по мере необходимости.)

немного изменим код:

import pandas as pd
from moexalgo import Market

stocks = Market("shares/TQBR")
all_stocks = pd.DataFrame(stocks.tickers())
print(all_stocks)

запускаем и получаем таблицу Dataframe, включающую 248 акций

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

27 колонок со следующими названиями [‘SECID’, ‘BOARDID’, ‘SHORTNAME’, ‘PREVPRICE’, ‘LOTSIZE’, ‘FACEVALUE’, ‘STATUS’, ‘BOARDNAME’, ‘DECIMALS’, ‘SECNAME’, ‘REMARKS’, ‘MARKETCODE’, ‘INSTRID’, ‘SECTORID’, ‘MINSTEP’, ‘PREVWAPRICE’, ‘FACEUNIT’, ‘PREVDATE’, ‘ISSUESIZE’, ‘ISIN’, ‘LATNAME’, ‘REGNUMBER’, ‘PREVLEGALCLOSEPRICE’, ‘CURRENCYID’, ‘SECTYPE’, ‘LISTLEVEL’, ‘SETTLEDATE’]

27 колонок со следующими названиями [«SECID», «BOARDID», «SHORTNAME», «PREVPRICE», «LOTSIZE», «FACEVALUE», «STATUS», «BOARDNAME», «DECIMALS», «SECNAME», «REMARKS», «MARKETCODE», «INSTRID», «SECTORID», «MINSTEP», «PREVWAPRICE», «FACEUNIT», «PREVDATE», «ISSUESIZE», «ISIN», «LATNAME», «REGNUMBER», «PREVLEGALCLOSEPRICE», «CURRENCYID», «SECTYPE», «LISTLEVEL», «SETTLEDATE»]

и вот так выглядит таблица, если программу запустить в интерактивной среде в Jupyter Notebook:

9 колонок со следующими названиями [‘ticker’, ‘shortname’, ‘lotsize’, ‘decimals’, ‘minstep’, ‘issuesize’, ‘isin’, ‘regnumber’, ‘listlevel’] Источник: https://xn--80agadetfnxfwx.xn--p1ai/moex/algopack-moexalgo-spravochnaya-informatsiya-o-vseh-instrumentah-rynka

9 колонок со следующими названиями [«ticker», «shortname», «lotsize», «decimals», «minstep», «issuesize», «isin», «regnumber», «listlevel»] Источник: https://xn--80agadetfnxfwx.xn--p1ai/moex/algopack-moexalgo-spravochnaya-informatsiya-o-vseh-instrumentah-rynka

Полное содержание таблицы с акциями в сохраненном pdf файле.

❗️ Вы заметили, что получаемые dataframe на скриншотах выше отличаются друг от друга по своей структуре. Обращайте внимание на названия колонок.  Например символьное обозначение акции в первом варианте трактуется как SECID, а во втором как ticker. Это очень важно при написании программы. И если вы пишите программу модулями в Jupyter Notebook и обращаетесь в ней к названиям колонок, то скорее всего этот код в скрипте при запуске в обычном режиме выдаст ошибку.

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

Пройдемся по заголовкам полученного в Jupyter Notebook Dataframe:

ticker — торговый код (тикер) инструмента, его уникальный идентификатор. Например, SBER.
shortname — краткое название инструмента, может содержать аббревиатуру эмитента. К примеру, Сбербанк.
lotsize — минимальный объем одной заявки на покупку/продажу в лотах.
decimals — количество знаков после запятой при отображении цены инструмента.
minstep — минимальный шаг изменения цены при торговле данным инструментом.
issuesize — объем выпуска данного инструмента согласно проспекту эмиссии.
isin — международный идентификационный код ценной бумаги (ISIN).
regnumber — регистрационный номер выпуска ценной бумаги в ЦБ РФ.
listlevel — уровень листинга инструмента, соответствует котировальному списку на Мосбирже.
И остановимся на последнем параметре. На Московской бирже параметр listlevel обозначает уровень листинга, то есть котировальный список, в который включены ценные бумаги (акции, облигации).

Выделяют 3 уровня листинга:

Первый уровень (listlevel=1) — самые ликвидные и надежные ценные бумаги крупнейших и инвестиционно-привлекательных эмитентов. Для включения есть жесткие требования.
Второй уровень (listlevel=2) — бумаги компаний поменьше по капитализации и ликвидности. Требования мягче.
Третий уровень (listlevel=3) — как правило, акции компаний малой капитализации и более высокого риска. Требования слабее.
Чем выше уровень листинга, тем выше требования к эмитенту и качество его ценных бумаг с точки зрения надежности, информационной прозрачности и ликвидности. Все голубые фишки относятся к первому уровню листинга.

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

import pandas as pd
from moexalgo import Market

stocks = Market("stocks")
all_stocks = pd.DataFrame(stocks.tickers())
print(all_stocks.columns.tolist())
listlevels = sorted(all_stocks["LISTLEVEL"].unique())
with open("stock_listing.txt", "w", encoding="utf-8") as file:
    print(f"Всего в Алгопаке доступны данные по {all_stocks.shape[0]} акциям Мосбиржи")
    print(
        f"Всего в Алгопаке доступны данные по {all_stocks.shape[0]} акциям Мосбиржи",
        file=file,
    )
    for level in listlevels:
        stocks_level = all_stocks[all_stocks["LISTLEVEL"] == level]
        print(f"Для {level} уровня листинга отобрано {stocks_level.shape[0]} акций:")
        print(
            f"Для {level} уровня листинга отобрано {stocks_level.shape[0]} акций:",
            file=file,
        )
        list_tickers = stocks_level["SECID"].tolist()
        list_shortnames = stocks_level["SHORTNAME"].tolist()
        for ticker, shortname in zip(list_tickers, list_shortnames):
            print(f"{ticker} - {shortname}")
            print(f"{ticker} - {shortname}", file=file)
        print("_" * 70)
        print("_" * 70, file=file)

Давайте детально разберем код, чтобы даже любой начинающий «питонист» мог в нем разобраться.

Мы имеем all_stocks — это dataframe с информацией по всем акциям.

Выражение listlevels = sorted (all_stocks[«LISTLEVEL»].unique ())

Здесь метод .unique () используется для возврата уникальных значений в колонке «LISTLEVEL» нашего датафрейма all_stocks. В результате будет создан массив listlevels из уникальных значений в колонке LISTLEVEL. И сразу же методом sort () отсортируем значения в массиве listlevels. Получим [1, 2, 3]. (уже после написания статьи осознал, что можно было просто сделать так listlevels=[1, 2, 3] , но в момент освоения python все время хочется максимум автоматизировать))

with open («stock_listing.txt», «w», encoding=«utf-8″) as file:

в этом коде используется оператора with в сочетании с функцией open (). Оператор with используется для автоматического управления ресурсами, такими как файлы или соединения с базами данных. Он гарантирует, что после выполнения блока кода ресурсы будут корректно закрыты или освобождены.

В этом коде функция open () используется для открытия файла с именем «stock_listing.txt» в режиме записи («w»). Оператор with используется для гарантии правильного закрытия файла после того, как он больше не нужен. Если файла stock_listing.txt нет, то он будет создан, если есть — перезаписан. Благодаря with, нам не нужно вызывать метод file.close (), так как он автоматически будет выполнен после выхода из блока кода.

print (f«Всего в Алгопаке доступны данные по {all_stocks.shape[0]} акциям Мосбиржи», file=file)

print выводит сообщение в терминал, а print с параметром file — записывается в файл. В фигурных скобках выполняется выражение {all_stocks.shape[0]}, в котором применен атрибут shape у объекта all_stocks, чтобы узнать количество строк в нем. Атрибут shape возвращает кортеж с двумя элементами: количество строк и количество столбцов в датафрейме, соответственно элемент [0] содержит количество строк. Фактически мы узнали сколько строк в нашем DataFrame с информацией по акциям, т.е. узнали количество акций.

Далее запускаем цикл for level in listlevels: , т.е. выполняется итерация по элементам переменной listlevels. На каждой итерации значение level будет содержать очередной элемент из listlevels, т.е. на каждой итерации в переменную level будет попадать очередное значение из listlevels, которая содержит уникальные уровни листинга.

В цикле происходит следующее:

stocks_level = all_stocks[all_stocks[«LISTLEVEL»] == level]
Здесь происходит фильтрация данных. Мы берем весь датафрейм all_stocks и отбираем из него только те строки, в которых значение в столбце «LISTLEVEL» равно текущему значению переменной level, например 1,2 или 3. В результате в stocks_level будут записаны данные только по тем акциям, которые имеют текущее значение LISTLEVEL.

print (f«Для {level} уровня листинга отобрано {stocks_level.shape[0]} акций:»)

Выводим сообщение в терминал (в файл с параметром file) о том, какое количество акций отобрано для каждого уровня листинга.

list_tickers = stocks_level[«SECID»].tolist ()
Здесь из отфильтрованных данных по акциям stocks_level (датафрейм) извлекаем столбец с тикерами акций (»SECID») и преобразуем его в список с помощью tolist ().

.tolist () — это метод, который используется для преобразования массива в список (list).Таким образом в list_tickers попадают тикеры всех акций с текущим уровнем листинга.

Аналогично создаем список list_shortnames с перечнем SHORTNAME всех акций текущего уровня листинга.

Далее выводим список тикеров и названий акций из двух списков. Для этого используем функцию zip для объединения двух списков list_tickers и list_shortnames в один итератор, который возвращает кортежи из элементов с одинаковыми индексами. В каждом кортеже будут содержаться элементы из соответствующих позиций исходных списков. Это удобно, когда нужно проходить по нескольким спискам одновременно и выполнять операции с элементами из одной и той же позиции в каждом списке.

Выводим с помощью цикла for в терминал и файл список из пар элементов (ticker и shortname) из созданного функцией zip списка кортежей.

И вот таким образом на каждой итерации цикла (for level in listlevels: ) мы будем получать данные для очередного уникального LISTLEVEL.

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

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

И это в ближайшее время тема для следующей публикации, следите здесь или на Алготрейдинг на Python. Продолжение следует…

Видео по теме:

© Habrahabr.ru