Привет, Хабр! Продолжаю рассказывать, как я создаю библиотеку на Python. В этой статье я расскажу о том, как мне удалось структурировать проект, как был реализован функционал получения динамики по инструменту за указанный период - dynamics()
, а также о том, как была добавлена возможность генерации простых графиков.
Предыдущие статьи на эту тему:
О проекте
Вот сам проект. На текущий момент было внесено несколько правок, исходя из комментариев к предыдущим статьям и личных размышлений. Наиболее существенная из них - это исправление способа получения информации о не торговых днях. Подход был пересмотрен и на текущий момент я отказался от использования константных кортежей в пользу получения информации от самого 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:
Кроме указания формата сохраняемого файла - save_format
, функция get_plot()
также принимает параметры w_size
и h_size
, отвечающие за размеры графика соответственно. Для наглядности вывел линии максимума, минимума и среднего значения.
В планах расширить функционал использования графиков, например, добавить возможность отображения нескольких инструментов на одном графике (конечно, не в их абсолютных значениях), для большей наглядности и т.д.
Спасибо за внимание!
Комментарии (2)
Andrey_Solomatin
13.11.2024 13:17Проблема #7: Добавление простых графиков
У вас получается божественный класс. Сделайте рисовалку отдельно.
Andrey_Solomatin
Сегодня нет, а завтра код отвалится с ошибкой. Не делайте так. Сделайте get(name) который будет выкидывать специальное исключение. Пусть пользователь сам решает как его обрабатывать.