Привет, Хабр! Продолжаю рассказывать, как я создаю библиотеку на Python. В этой статье я расскажу о том, как мне удалось структурировать проект, как был реализован функционал получения динамики по инструменту за указанный период - dynamics(), а также о том, как была добавлена возможность генерации простых графиков.

Предыдущие статьи на эту тему:

  1. MoexBuilder: как я создаю библиотеку на Python. Часть 1

  2. MoexBuilder: как я создаю библиотеку на Python. Часть 2

О проекте

Вот сам проект. На текущий момент было внесено несколько правок, исходя из комментариев к предыдущим статьям и личных размышлений. Наиболее существенная из них - это исправление способа получения информации о не торговых днях. Подход был пересмотрен и на текущий момент я отказался от использования константных кортежей в пользу получения информации от самого ISS MOEX. Теперь полученные значения weekends и workdays можно использовать , как свойства объекта MOEX(), что выглядит и звучит логично.

Кроме этого, я планирую (и уже даже начал) использовать TypedDict() и/или namedtuple() в тех местах, где это возможно и имеет смысл.

Честь и хвала комментаторам!

Проблема #4: Структура проекта

На текущий момент проект имеет следующую структуру. Существует класс MOEX(),который описывает реальный объект - Московскую биржу. Экземпляр класса MOEX() содержит в себе экземпляр класса IndexIMOEX(). Стоит отметить, что в качестве принципа взаимосвязи между этими объектами умышленно выбрана композиция, а не наследование. Это связано с тем, что реальный объект индекс Московской биржи содержится внутри самой Московской биржи. Иными словами, между ними нет связи по принципу наследования (родитель и потомок). Кроме этого, композиция почти всегда лучше наследования, так как обеспечивает более гибкий подход и позволяет избегать дополнительных зависимостей, которые возникают при использовании наследования.

И тут важно отметить, что несмотря на вышесказанное, не стоит впадать в крайности. Каждый из подходов имеет право на существование в тех случаях, когда условия для его реализации являются оптимальными. И тут есть хороший пример из проекта.

В проекте реализован функционал для работы с различными инструментами Московской биржи. На текущий момент это индексы (пока только один - IMOEX) и акции (пока только те, что входят в состав индекса IMOEX).

Учитывая, что для данных инструментов одинаково полезен функционал расчета динамики (расскажу далее в этой статье) и получения значений за указанный интервал, мы можем обозначить, что классы IndexIMOEX() и SharesIMOEX() наследуются от BaseInstrumentMOEX() (в ближайшее время планирую сделать его абстрактным классом). Это позволяет (среди прочего) использовать общую реализацию функций interval() и dynamics() для всех инструментов Московской биржи, если, конечно, они наследуются от BaseInstrumentMOEX(). Такая структура проекта позволит без лишних трудозатрат расширять проект новыми инструментами. В ближайшие планы входит добавление индекса RGBI.

Сомнение вызывает лишь один ярко выделяющийся момент. Дело в том, что данные о актуальном составе акций, входящих в индекс Московской биржи поступают от ISS MOEX - свойство actual_composition_index_tickers .Я умышленно не хотел поддерживать это вручную, что кажется верным. Акции хоть и не так часто попадают/исключаются из индекса, но все же хотелось максимально уйти от ручного контроля и ввода каких-либо значений. Соответственно, сами объекты класса SharesIMOEX() создаются с учетом этого полученного списка, следующим образом:

for ticker_name in self.actual_composition_index_tickers:
    self.__setattr__(ticker_name, SharesIMOEX(ticker_name, last_trade_day, weekends, workdays))

С точки зрения работоспособности кода вопросов нет. Но из-за динамического определения атрибутов класса IndexIMOEX() отсутствуют подсказки точечной нотации (dot notation). Мне это не нравится, но (насколько я знаю), это ожидаемый результат с учетом, что список имен атрибутов формируется только по факту получения данных от ISS MOEX в уже работающем приложении (то есть список не известен заранее).

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

Буду признателен, если вы поделитесь своими идеями по реализации данного фрагмента в комментариях.

Проблема #5: Добавление функционала dynamics

На самом деле данный функционал было достаточно легко реализовать. Дело в том, что для расчета динамики нам требуется информация лишь за два дня - правая и левая границы интервала. В классе Dynamics() реализованы свойства value (значение динамики в единице расчета инструмента), percent (значение динамики в процентах) и full_info (список словарей со значениями динамики - "было-стало"). Вероятно, для большей ясности и детализации потребуется добавить свойство, возвращающее единицу расчета инструмента (пока не уверен, что оно должно обитать в экземпляре класса Dynamics(), так как кажется, что это все уже свойство инструмента).

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

from moex import MOEX

imoex = MOEX().imoex

interval_imoex = imoex.interval('2024-08-08', soft_search='back')  # Создать объект Interval для индекса IMOEX
dynamics_imoex = imoex.dynamics('2024-08-08', soft_search='back')  # Создать объект Dynamics для индекса IMOEX
print(interval_imoex.max_value)  # {'from': '2024-08-14 11:50:00', 'to': '2024-08-14 11:59:59', 'value': 2905.99}
print(dynamics_imoex.value)  # -95.75

interval_sber = imoex.SBER.interval('2024-08-08', soft_search='back')  # Создать объект Interval для акций Сбера
dynamics_sber = imoex.SBER.dynamics('2024-08-08', soft_search='back')  # Создать объект Dynamics для акций Сбера
print(interval_sber.max_value)  # {'from': '2024-08-08 12:10:00', 'to': '2024-08-08 12:19:59', 'value': 285.1}
print(dynamics_sber.value)  # -25.11

Проблема #7: Добавление простых графиков

Если мы говорим о библиотеки для работы с инструментами Московской биржи, то нам, конечно же, нужны графики. Я решил начать с чего-то простого, но полезного. Учитывая, что у нас уже есть функционал, позволяющий получать значения инструмента за указанный интервал - функция interval(), мы с легкостью можем написать дополнительную функцию для построения графика на основании полученных данных - get_plot().

В таком случае подобный код:

from moex import MOEX

imoex = MOEX().imoex

interval_imoex = imoex.interval('2024-08-08', soft_search='back')  # Создать объект Interval для индекса IMOEX
interval_imoex.get_plot(save_format='.jpg')  # Сохранить график в формаnе jpg за указанный интервал в папке plots

interval_sber = imoex.SBER.interval('2024-08-08', soft_search='back')  # Создать объект Interval для акций Сбера
interval_sber.get_plot(save_format='.jpg')  # Сохранить график в формаnе jpg за указанный интервал в папке plots

Создает графики в папке plots:

График для индекса IMOX за указанный интервал
График для индекса IMOX за указанный интервал
График для акций Сбера за указанный интервал
График для акций Сбера за указанный интервал

Кроме указания формата сохраняемого файла - save_format, функция get_plot() также принимает параметры w_size и h_size, отвечающие за размеры графика соответственно. Для наглядности вывел линии максимума, минимума и среднего значения.

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

Спасибо за внимание!

Комментарии (2)


  1. Andrey_Solomatin
    13.11.2024 13:17

    С точки зрения работоспособности кода вопросов нет. Но из-за динамического определения атрибутов класса IndexIMOEX() отсутствуют подсказки точечной нотации (dot notation).

    Сегодня нет, а завтра код отвалится с ошибкой. Не делайте так. Сделайте get(name) который будет выкидывать специальное исключение. Пусть пользователь сам решает как его обрабатывать.


  1. Andrey_Solomatin
    13.11.2024 13:17

    Проблема #7: Добавление простых графиков

    У вас получается божественный класс. Сделайте рисовалку отдельно.