AT industries: Course (Developers)
Основатели AT industries : alexmak, tim.eth
Введение
Основной целью, которую мы ставили перед собой при создании данного курса, было обучение заинтересованных людей языку программирования Python с более практической точки зрения его применения.
Все ссылки, приведенные в курсе, являются выжимкой информации, которую мы считаем достаточной для прикладного применения Python.
Данный курс не имеет строгого хронологического порядка, однако мы советуем следовать программе в том порядке, в котором она представлена ниже.
Удачи в прохождении!
Программа курса:
- Объектно-ориентированное программирование
- Анализ данных (numpy, pandas)
- API-запросы (requests)
- Графики (plotly)
- Асинхронное программирование (asyncio)
- Вебсокеты (threading, websocket)
- Логирование (logger)
- Телеграм (aiogram)
- Базы данных (sqlite3)
- Web3 (web3)
1. Объектно-ориентированное программирование
В математике любой объект можно рассмотреть в виде множества. Например:
- Число — это множество, но из одного элемента | a := {a}.
- Матрица — это множество множеств | A := {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}
- Поле — это множество элементов | R := {..., -0.5, ..., 0.0, ..., 0.5, ...}
Так вот, если в математике каждый объект есть множество, то в программировании каждый объект есть класс! А точнее объект некоторого класса!
В данном разделе мы изучим объектно-ориентированное программирование, основная идея которого заключается в представлении программы в виде совокупности объектов, каждый из которых является экземпляром определённого класса.
Зачем это нужно?
Простыми словами, ООП делает написание кода быстрее и его структуру более понятной. В вычислительном плане ООП обычно не вносит существенных изменений (иногда даже замедляет выполнение кода). Однако, ООП сильно помогает экономить время написании кода, именно поэтому его следует изучать!
ТЕОРИЯ
плейлист: ссылка
ПРАКТИКА
задание_01:
- Написать класс Asset с полями: type(str), name(str), price(float), status(bool), где type - это тип нашего актива (equity/option/cryptocurrency), name - имя нашего актива (SPX/USOIL/BTCUSDT), price - цена нашего актива (36,123/123,0/-12.11), status - статус торговли (True/False).
- Написать конструктор класса, который при создании объекта выводит сообщение в терминал о создании данного объекта.
- Перегрузить оператор печати "__str__", сделать вывод некоторой информации об объекте данного класса.
- Перегрузить оператор сравнения "__lt__". Объекты класса должны сравниваться по значению поля pricе.
- Создать массив объектов данного класса и отсортировать по цене.
задание_02:
- Написать класс Trade, который является наследником класса Asset и состоит из полей: date(str), quantity(float), side(str), где date - это дата совершения сделки (1707580035.000/1706379357.000), quantity - количество купленного актива (0.0042, 1235.12), side - сторона сделка (buy/sell).
- Для класса Trade реализовать метод get_size(), который считает объем позиции совершенной сделки в долларах.
- Создать несколько объектов класса Trade с рандомными значениями полей. Для всех объетов класса Trade вызвать метод get_size().
2. Анализ данных (numpy, pandas)
Есть две основные библиотеки для анализа данных в Python: NumPy и Pandas. Мы не будем сильно углубляться в каждую из них, так как не считаем, что стоит тратить много времени на изучение данных библиотек. Однако, поверхностное ознакомление с numpy и pandas явно не будет лишним для Python-разработчика.
Что такое NumPy?
NumPy — это библиотека Python, которую применяют для математических вычислений: начиная с базовых функций и заканчивая линейной алгеброй.
По сути, NumPy — это удобная библиотека, которая включает в себя множество реализаций математических методов. Ее удобно использовать, когда не хочется заморачиваться с написанием своих методов. Однако важно понимать, что NumPy не является универсальным решением для всех задач, и иногда все же стоит прибегать к написанию собственных методов, например, для оптимизации времени или памяти, требуемых для выполнения программы.
Что такое Pandas?
Pandas — это библиотека Python для обработки и анализа структурированных панельных данных. Панельными данными называют информацию, полученную в результате исследований в виде таблиц.
То есть, Pandas — это удобная библиотека, для работы с таблицами (иногда очень большого размера).
ТЕОРИЯ
видео_01: ссылка (nupmy)
видео_02: ссылка (pandas)
ПРАКТИКА
задание_01: (nupmy)
Задания: 1, 2, 5, 7, 10, 11, 14, 15
https://colab.research.google.com/drive/1xG9SKOsXwq0wFW5NBtnSw1B8odFcAU0y?usp=sharing
задание_02: (pandas)
Задания: 0, 1, 2, 3, 4, 5, 6, 7
https://colab.research.google.com/drive/1CWFPDleJQdv_eG_yRYXBOs9Aj7-osVAG?usp=sharing
3. API-запросы (requests)
В этом разделе мы научимся создавать и отправлять API-запросы с вашего компьютера на другие сервера. Но для начала, давайте определим, что такое API и API-запросы.
Что такое API?
API (Application Programming Interface) — это набор правил и способов, с помощью которых различные компьютерные программы взаимодействуют друг с другом. Другими словами, API — это язык, на котором программы обмениваются информацией и выполняют различные операции.
Что такое API-запросы?
API-запрос — это само сообщение, которое одна программа отправляет другой программе. Обычно API-запросы представлены в формате JSON и содержат определенную информацию, которую мы можем понять с помощью правил, описанных в документации API.
Что такое GET и POST?
Все API-запросы делятся на два типа: GET и POST.
GET — это тип запроса, который не вносит изменений в базу данных на сервере-получателе. Например, чтобы узнать ваш баланс USD на бирже, сервер-получатель просто прочитает значение вашего баланса и отправит ответный JSON с полученной информацией, не изменяя данные в базе данных своих пользователей.
POST — это тип запроса, который привносит изменения в базу данных на сервере-получателе. Например, чтобы отправить заявку на покупку BTC на бирже, сервер-получатель должен взаимодействовать с базой данных как минимум для уменьшения количества USD и увеличения количества BTC на вашем балансе.
Обычно GET-запросы открыты для всех пользователей, если запрашиваемая информация не содержит конфиденциальных данных о пользователе. В то время POST-запросы зачастую являются закрытыми и требуют аутентификации пользователя. Чтобы отправить POST-запрос и взаимодействовать с базой данных сервера, пользователь должен иметь пароль или какой-то другой метод аутентификации. Этот пароль или ключ используется для шифрования запроса, отправляемого на сервер. Затем сервер, имея доступ к паролю пользователя, расшифровывает запрос и выполняет действия, описанные в нем. Это обеспечивает безопасность и контроль доступа к данным на сервере.
Например, на биржах криптовалют (и не только) такими паролями (ключами) являются ваши API-ключи, а в блокчейне — seed фразы или приватные ключи от кошелька.
Также хотелось бы добавить, что в блокчейне, особенно в сети Ethereum, GET-запросы являются бесплатными функциями, так как НЕ изменяют состояние блокчейна, их называют "read". С другой стороны, POST-запросы, также называемые "write", платные, так как требуют выполнения транзакций и оплаты комиссий майнерам за изменение состояния блокчейна.
Взаимодействовать с биржами (сайтами) можно с помощью специальных библиотек, написанных самой биржей (сайтом) на Python (например, у ByBit это библиотека pybit), однако мы будем учиться взаимодействовать с биржами без сторонних библиотек, отправляя API-запросы биржам напрямую, по нескольким причинам. Во-первых, мы считаем, что каждому разработчику нужно уметь работать с чистыми API-запросами без сторонних библиотек, так как это полезный навык для разработчика. Во-вторых, не все биржи (сайты) имеют свои библиотеки для взаимодействия (например, многие децентрализованные и непопулярные биржи не имеют своих библиотек, но имеют API документацию).
документация_01: ссылка (OKX)
документация_02: ссылка (ByBit)
документация_03: ссылка (Binance)
ТЕОРИЯ
видео_01: ссылка
видео_02: ссылка (GET)
видео_03: ссылка (POST)
ПРАКТИКА
Выберите одну из бирж с которой вам удобнее всего работать.
задание_01:
- Написать функцию get_current_price(ticker: str), которая будет возвращать текущую цену актива на выбранной вами бирже.
- Написать get_decimals(ticker: str), которая будет возвращать количество знаков после запятой у данного актива на выбранной вами бирже.
задание_02:
- Написать функцию get_trading_balance(ticker: str), которая будет возвращать ваш баланс актива на выбранной вами бирже.
- Написать функцию post_limit_order(ticker: str, amount: float, price: float), которая будет выставлять лимитную заявку на выбранной вами бирже.
задание_03:
- Обернуть все вышеперечисленные функции в класс, конструктор которого принимает API ключи пользователя.
- Реализовать метод check_connection() для класса, который проверяет подключение к бирже и возвращает булево значение (True/False). Обязательна проверка введенных пользователем API ключей!
- Вне класса написать функцию run_mainloop(ticker: str, timeframe: str), которая запускает вечный цикл. Вечный цикл должен присылать информацию по закрытой свече того таймфрейма и того актива, которые подаются на вход функции. Например run_mainloop('BTCUSDT', '1m') будет присылать информацию (price_open, price_close, price_high, price_low, volume, timestamp) для каждой новой закрытой минутной свечи.
4. Графики (plotly)
В анализе информации очень важна визуализация происходящего. Одной из самых крупных библиотек для построения графиков (и не только) на Python является библиотека Plotly. В Plotly есть практически все, что может понадобиться для визуализации данных.
Хотя в рамках одного раздела мы не сможем охватить все возможности данной библиотеки, вы можете столкнуться с необходимостью использования различных типов графиков по мере своего развития как разработчика.
Поэтому основная задача данного раздела — научиться ориентироваться в документации библиотеки Plotly и находить нужные виды графиков для решения поставленных вами задач.
документация_01: ссылка (Plotly)
ТЕОРИЯ
видео_01: ссылка
ПРАКТИКА
Выберите одну из бирж с которой вам удобнее всего работать.
задание_01:
- Написать функцию get_klines_df(ticker: str, timeframe: str, n: int), которая получает информацию о свечах введенного актива в виде датафрейма за последне n закрытых свечей введенного таймфрейма на выбранной вами бирже.
- По полученному из первого пункта датафрейму построить свечной график цены актива (смотреть: Candlestick Charts).
- На свечной график цены нанести индикатор SMA-10 в виде обычного графика линии. Подписать график индикатора как sma_10.
- На свечной график цены нанести горизонтальную линию красного цвета, значение которой равно: (df['high'].max() + df['low'].min()) / 2
- Под свечным графиком цены построить график объемов (по желанию в виде столбчатой диаграммы). Важно, чтобы ось времени у обоих графиков была одна и та же, чтобы при увеличении одного графика увеличивался и другой!
- Настроить конфиг: убрать ползунки диапазонов, добавить zoom, сделать шкалу значений цены актива логарифмической.
- Сохранить полученный график в виде HTML и PNG файлов.
5. Асинхронное программирование (asyncio)
Python является однопоточным языком программирования. Это означает, что без использования сторонних модулей в один и тот же момент времени Python не может выполнять более одной задачи одновременно. То есть, если в Python запущен один процесс, то ситуация того, что параллельно с этим процессом происходит какой-либо другой процесс, невозможна!
Данная концепция не совсем удобна для огромного количества проектов по нескольким причинам:
Первая причина — потеря времени. Однопоточность предполагает, что если во время выполнения программы код останавливается на каком-то этапе (например, в ожидании ответа от сервера), мы теряем время, которое могли бы использовать для выполнения других задач во время ожидания кода.
Вторая причина — неэффективное использование мощностей компьютера. В случае однопоточного выполнения программы процессор может оставаться бездействующим во время ожидания завершения определенных операций, таких как чтение данных с диска или ожидание ответа от внешнего сервера. Это приводит к неоптимальному использованию ресурсов компьютера, поскольку доступные процессорные ядра могли бы быть задействованы в других задачах во время простоя.
Третья причина — отсутствие возможности параллельного запуска процессов. В некоторых проектах параллельный запуск процессов необходим для обработки задач в режиме реального времени или для обработки запросов от нескольких клиентов одновременно. Например, в веб-серверах параллельное выполнение процессов позволяет обрабатывать запросы клиентов параллельно, а не последовательно, что повышает производительность и отзывчивость системы.
Существует несколько решений вышеперечисленных проблем. Однако, в данном разделе мы сосредоточимся только на одной из них — асинхронном программировании.
Асинхронное программирование — концепция программирования, при которой результат выполнения функции доступен спустя некоторое время в виде асинхронного (нарушающего стандартный порядок выполнения) вызова. Запуск длительных операций происходит без ожидания их завершения и не блокирует дальнейшее выполнение программы.
Важно понимать, что при асинхронном программировании программа всё равно продолжает работать в однопоточном режиме. Однако, передавая обязанности между процессами, мы можем достигать более оптимального использования времени и мощностей компьютера, а также запускать программу в "псевдопараллельном" режиме.
ТЕОРИЯ
видео_01: ссылка
видео_02: ссылка
плейлист_01: ссылка
ПРАКТИКА
задание_01:
Реализовать первый пример применения асинхронности из указанного выше видео_02: "Python с нуля для криптанов...await, брутфорс кошельков"
задание_02:
Реализовать первый пример применения асинхронности из видео #7 из указанного выше плейлиста: "Асинхронность в Python"
задание_03:
Реализовать второй пример применения асинхронности из видео #7 из указанного выше плейлиста: "Асинхронность в Python"
6. Вебсокеты (threading, websocket)
Помимо асинхронного программирования существует еще одно решение проблем, затронутых в разделе [5] этого курса. Этим решением является внешний модуль Threading.
Threading — внешний модуль, который позволяет создавать многопоточные процессы в Python. Это означает, что с его помощью вы можете выполнять несколько операций одновременно, используя разные потоки.
Также, в этом разделе мы разберем работу с вебсокетами в Python.
Вебсокет — это протокол обмена данными между клиентом и сервером в режиме реального времени. Вебсокеты используются для создания, интерактивных веб-приложений (чаты, биржы, игры), где требуется быстрый обмен информацией.
ТЕОРИЯ
статья_01: ссылка (threading)
статья_02: ссылка (websocket)
ПРАКТИКА
Выберите одну из бирж с которой вам удобнее всего работать.
задание_01:
- Реализовать примеры из статьи: "Как работать с модулем threading в Python"
- Реализовать примеры из статьи: "Как работать с WebSocket в Python"
задание_02:
- Написать свой класс Socket, конструктор которого принимает на вход следующие параметры: url и ticker, где url — это ссылка на подключение к вебсокету, ticker — это тикер актива, по которому мы хотим получать информацию. Конструктор дожен сохранять значения url и ticker, а также создавать вебсокет подключение, используя бибоиотеку websocket.
- В классе Socket реализовать следующие методы: on_message(), on_open(), on_close(), on_error(). Каждый метод должен выводить сообщение в терминал при вызове, пример: 'Websocket: opened BTCUSDT'
- Параллельно запустить вебсокеты для нескольких активов активов, используя библиотеку threading.
задание_03:
В данном задании мы реализуем небольшую торговую стратегию на вебсокетах, которая будет присылать сигналы на покупку и продажу актива по индикатору BollingerBands при отклонении цены на несколько стандартых отклонений от ее среднего значения за некоторый промежуток времени.
- Дополнить класс Socket, который мы создали во втором задании этого раздела, следующими полями: n и data, где n — размер окна индикатора, который мы будем использовать в стратегии, data — словарь, в котором мы будем сохранять информацию о монете за последние n тиков.
- Написать создание словаря data в конструкторе таким образом, чтобы при вызове конструктора нашего класса создавался словарь, ключами которого были: 'datetime' и 'price', а их значениями были массивы из None (кол-ва n).
- В методе on_message необходимо распаковать полученную информацию об активе и записать datetime и price в наш словарь data таким образом, чтобы новые элементы массивов вставали на первое место, а самый старые элементы (n+1)ые стирались их массивов. Иными словами, реализовать динамическую очередь.
- В методе on_message только после полного заполнения словаря data последней информацией об активе, необходимо рассчитать стандартное отклонение цены актива за последние n тиков.
- В методе on_message написть алгоритм стратегии: если цена актива, больше или меньше вычисленного стандартного отклонения — отправить сообщение в терминал о покупке или продаже актива.
7. Логирование (logger)
Ранее мы использовали функцию print() для вывода всех комментариев о работе программы в терминале, и это было довольно эффективно.
Однако, в крупных проектах необходимо не только выводить информацию о работе программы в терминал, но и сохранять её на жестком диске. Это позволяет в дальнейшем проанализировать выполнение нашего кода, например, чтобы выявить причины возникновения ошибок.
Для решения данной задачи почти во всех языках программирования существует отдельный модуль отвечающий за логирование информации о работе программы во время ее выполнения и сохранения данной информации в отдельный файл.
Лог (log) — это текстовый файл, куда автоматически записывается важная информация о работе системы или программы.
Важно осознавать значимость логирования, особенно в крупных проектах, где становится сложно отслеживать логику действий пользователя только по его комментариям и жалобам на работу программы.
Мы настоятельно рекомендуем всегда записывать логи!
ТЕОРИЯ
видео_01: ссылка
ПРАКТИКА
задание_01:
- Создать отдельный файл logger.py в котором будет храниться глобальная переменная нашего логгера и вся информация о нем.
- Создать отдельную папку, в которой будут сохраняться логи нашего запусков программы.
- Поменять конфигурацию логера таким образом, чтобы файлы с логами сохранялись в папку, созданную в пункте 2, с названиями даты и временем старта программы ('%Y-%m-%d_%H-%M-%S.log').
- Поменять конфигурацию логера таким образом, чтобы его сообщения отображались вот так:
2024-02-21 19:53:26 | DEB | ...
2024-02-21 19:53:26 | ERR | ... - Поменять конфигурацию логера таким образом, чтобы во время выполнения программы логи не только записывались в файл, но и выводились в окне терминала.
8. Телеграм (aiogram)
Для взаимодействия с Telegram можно использовать прямые API-запросы. Однако, при работе над крупными проектами удобнее обращаться к сторонним библиотекам, таким как telebot или aiogram.
В чем различие telebot и aiogram?
Основное различие между библиотеками telebot и aiogram в том, что aiogram использует асинхронность, то есть при ожидании ответа от пользователя выполнение кода останавливается.
В нашем курсе мы сосредоточимся на использовании библиотеки aiogram.
ТЕОРИЯ
плейлист_01: ссылка (смотреть выборочно)
ПРАКТИКА
Выберите одну из бирж с которой вам удобнее всего работать.
задание_01:
Написать телеграм бота, который принимает ticker актива и присылает в ответ текущую цену данного актива.
задание_02:
Используя машину состояний (смотреть плейлист), написать телеграм бота, который по команде /saveorder принимает подряд следующие значения: type, ticker, price, amount и сохраняет информацию о сделке. По команде /showorders выводит все сделки, занесенные в телеграм бота.
задание_03:
Написать телеграм бота, который с некоторой регулярностью присылает информацию об активах добавленных пользователем по команде /addcoin. Информация должна содержать в себе: тикер актива, цену и объем.
9. Базы данных (sqlite3)
До этого момента все данные, с которыми взаимодействовали наши программы, находились только в оперативной памяти (за исключением записей в логах). Однако во многих проектах требуется сохранять информацию на жесткий диск компьютера для повторного использования при запуске программы. Наиболее удобным решением данной проблемы является применение баз данных.
База данных — это набор информации, которая хранится упорядоченно в электронном виде. Простыми словами, база данных это файл, который может содержать в себе много разных таблиц с информацией. Основные преимущества использования базы данных: удобство хранения информации и скорость взаимодействия с данными.
В нашем курсе мы рассмотрим библиотеку sqlite3 для работы с базами данных используя SQL в Python.
SQL — это отдельный язык программирования, который мы будем использовать для взаимодействия с самой базой данных. То есть, в проектах, написанных на Python, мы будем отправлять SQL-запросы в базу данных для получения или изменения хранимой информации.
ТЕОРИЯ
плейлист_01: ссылка
ПРАКТИКА
Написать API базы данных, которая состоит из таблиц: users и orders, где таблица users — это таблица с данными о пользователе (id, name, balance), а orders — это таблица со сделками пользователей (id, side, ticker, price, amount). В функционал API должны входить следующие методы:
- add_user(id: str, name: str, balance: float) — добавление нового пользователя в таблицу users базы данных.
- delete_user(id: str) — удаление пользователя из базы данных по id. Удаление пользователя подразумевает удаление данных о пользователе как из таблицы users, так и из таблицы orders!
- get_users() — получение словаря, содержащего информацию о всех пользователях из таблицы users базы данных, где ключом словаря является id пользователя, а значением словарь с информацией о пользователе.
- add_order(id: str, side: str, ticker: str, price: float, amount: float) — добавление сделки в таблицу orders базы данных. Если пользователя с таким id нет в таблице users, то функция должна вернуть ошибку.
- delete_orders(id: str) — удаление всех сделок пользователя с данным id. Удаление сделок пользователя из таблицы orders не подразумевает удаление данных о пользователе из таблицы users!
10. Web3 (web3)
В данном разделе мы научимся взаимодействовать с блокчейном и смарт-контрактами, используя библиотеку web3 на Python.
Настоятельно рекомендуем прочитать предисловие к разделу [3] данного курса, перед тем как браться за этот раздел. Также, для начального понимания работы блокчейна посмотрите видео_01, приведенное ниже в теории данного раздела.
Как уже упоминалось в разделе [3], у смарт-контракта существует 2 типа функций для взаимодействия с ним: read и write. Запросы read являются бесплатными для использования, write — платными. Поэтому, для выполнения практической части данного раздела, необходимо пополнить баланс вашего EVM-кошелька нативными монетами сетей, которые мы будем использовать.
ТЕОРИЯ
видео_01: ссылка (блокчейн)
видео_02: ссылка (практика)
ПРАКТИКА
В практической части данного раздела мы научимся бриджить USDC между EVM сетями по мостам проекта Wormhole.
задание_01:
Написать собственный класс Client (видео_02) для взаимодействия с блокчейном. Класс должен содержать в себе следующие методы:
- is_connected() — проверка подключения к ноде c используемой RPC.
- get_balance_native() — получение баланса нативной монеты кошелька.
- get_balance_foreign(address_token: str) — получение баланса стороннего токена кошелька, где address_token — адрес смарт-контракта токена.
- approve(address_token: str, address_spender: str, amount: int) — одобрение на списывания токено , где address_token — адрес смарт-контракта токена, address_spender — адрес транжира, amount — количесвто токенов.
- send_transaction(address_to: str, address_from: str, data: dict, value: float) — отправление транзакции в блокчейн, где address_to — адрес отправителя, address_from — адрес получателя, data — допонительна информация о транзакции, value — количество пересылаемых нативных токенов.
задание_02:
Написать собственный класс Wormhole(client) для работы с мостами проекта Wormhole. В данном классе реализовать один метод:
transfer(
address_token: str, address_bridge: str,
address_from: str, address_to: str,
chain_id_to: int, amount: int,
),
где address_token — адрес смарт-контракта токена, address_bridge — адрес бриджа, address_from — адрес отправителя, address_to — адрес получателя, chain_id_to — айди сети получателя, amount — количество USDC для бриджа.
Данный метод должен бриджить USDC из одной сети в другую используя мосты проекта Wormhole. Пример такой транзакции можно посмотреть тут:
https://snowtrace.io/tx/0x1b655f9641aaf0a3e5da3b2f3672b426fc0aeb73a1e2eb7735013ea5bd904f11
задание_03:
Проект Wormhole написан таким образом, что для выполнения полноценного бриджа токенов помимо подписании транзакции на трансфер со стороны отправителя (то чем мы занимались в задание_02), также необходимо подписывать транзакцию о получении токенов на стороне получателя.
Если посмотреть в ABI смарт-контракта бриджа, который мы использовали в предыдущем задании для трансфера тоекнов, мы найдем нужную нам функцию клейма токенов со стороны получателя под названием 'completeTransfer'. Однако, входные данные этой функции могут смутить начинающего разработчика в области web3.
Поэтому, данное задание состоит в том, чтобы самостоятельно подумать о реализации функци 'completeTransfer' и написать нам о своих результатах и мыслях по этому поводу: tim.eth, alexmak.
Мы дадим некоторые подсказки о том, как все же реализовать данный метод!)