Создание эффективного программного обеспечения для криптотрейдинга с помощью Python

Создание эффективного программного обеспечения для криптотрейдинга с помощью Python

29 октября 2022 г.

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

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

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

Мы обсудим три основных понятия: асинхронное программирование, архитектуру, управляемую событиями и архитектуру микросервисов.

Поскольку наше ПО будет использовать разные источники данных, сервисы API и прочее, нам придется сделать его асинхронным, чтобы не ждать задержки данных от каких-либо удаленных сервисов. Асинхронное выполнение обязательно.

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

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

Асинхронное программирование

Было бы лучше, если бы вы имели хотя бы базовое представление об асинхронном программировании для создания быстродействующего программного обеспечения для торговли.

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

Асинхронное программирование позволяет нам пропустить ожидание ввода данных и перейти к потоку управления или потоку обработки данных. Такой подход радикально повышает скорость и производительность нашего программного обеспечения.

Мы будем использовать библиотеку Python asyncio для создания асинхронного приложения. Чтобы начать создавать асинхронное программное обеспечение, нам нужно изучить несколько концепций.

Во-первых, наши функции помечаются как асинхронные словом async, как в JavaScript. Пример:

async def my_function()

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

await asyncio.sleep(0)

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

В-третьих, при создании асинхронного ПО мы оперируем не только функциями, но и задачами. Внутри задачи может быть одна или несколько функций.

Пример:

async def input_func():
    pass


async def input_task():
    await asyncio.sleep(0)
    await self.input_func()

И, наконец, петли. Наши задачи выполняются внутри циклов, которые можно назвать «потоками управления».

Пример:

ioloop = asyncio.get_event_loop()
tasks = [ioloop.create_task(input_task()), ioloop.create_task(another_task())]
wait_tasks = asyncio.wait(tasks)
ioloop.run_until_complete(wait_tasks)

Это пример простого асинхронного приложения. Вы можете найти его в этом репозитории GitHub.

Событийный подход

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

Мы можем думать о программном обеспечении для трейдинга как о системе, которая реагирует на некоторые случайные события в реальном мире. Что-то происходит на рынке; цены меняются, что является событием. Мы можем обработать его внутри нашей системы и произвести другое событие, заказ, например, если условия выполнены. Мы также можем создать событие регистрации или уведомления в зависимости от наших потребностей.

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

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

Пример:

class Event(object):
    #base class
    pass
class AccountUpdateEvent(Event):
    def __init__(self, timestamp, username, total):
        self.type = 'ACCOUNT_UPDATE'
        self.timestamp = timestamp
        self.username = username
        self.total = float(total)

Теперь мы создадим простое приложение, которое может создавать и обрабатывать события менее чем в 100 строк кода Python!

Наш первый модуль будет выполнять вызовы API и создавать события.

Пример кода (весь класс доступен в моем репозитории GitHub):

class GetAccountData:
    def create_event(self, timestamp, username, total):
        self.events.put(AccountUpdateEvent(timestamp=timestamp, username=username, total=total))

Второй модуль обрабатывает события.

Пример кода (весь класс доступен в моем репозитории GitHub):

class ProcessAccountData:
    def update_account_data(self, event):
        print('Account update received at: ', event.timestamp, 'Username: ', event.username, 'Total balance: ', event.total)

И, наконец, наше приложение. Он будет содержать как GetAccountData, так и ProcessAccountData, а также очередь событий.

Пример кода (весь класс доступен в моем репозитории GitHub):

class App:
    def __init__(self):
        self.events = Queue()
        self.get_data_account = GetAccountData(self.events)
        self.process_data_account = ProcessAccountData()

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

Пример кода (весь класс доступен в моем репозитории GitHub):

def event_loop(self):
       while True:
            try:
                event = self.events.get(False)
            except Exception as e:
                time.sleep(1)
                break

            else:
                try:
                    if event is not None:
                        if event.type == 'ACCOUNT_UPDATE':
                            self.process_data_account.update_account_data(event)

                except Exception as e:
                    print('event_loop error:', e)

Это пример простого приложения, управляемого событиями. Исходный код можно найти в моем репозитории GitHub.

Микросервисы

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

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

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

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

Пример кода:

class Database:
    #this is a DB connector
    #we will use SQLite in this example for simplicity
    def init(self):
        #filename and path to the database are hardcoded for simplicity
        self.connect_to = 'test.db'
    def create_table_orders(self):
        #a func to create our database
        conn = sqlite3.connect(self.connect_to)
        conn.execute('''CREATE TABLE if not exists Orders
        (timestamp TEXT NOT NULL,
        username TEXT NOT NULL,
        market TEXT NOT NULL,
        side TEXT NOT NULL,
        size FLOAT NOT NULL,
        price FLOAT NOT NULL
        );''')
        conn.close()

В соединителе базы данных будет реализован только один метод для сохранения данных заказа в базе данных.

Пример кода:

def add_data_orders(self, timestamp, username, market, side, size, price):
    #a func to save orders data
    conn = sqlite3.connect(self.connect_to)
    conn.execute("INSERT INTO Orders (timestamp, username, market, side, size, price) VALUES (?, ?, ?, ?, ?, ?)", (timestamp,
    username, market, side, size, price));
    conn.commit()
    conn.close()

Во-вторых, нам нужен сервер API. Возможно создание простого API-сервера с модулем Flask менее чем из 30 строк кода. Он сможет получать HTTP-запросы POST с данными заказа и сохранять их в базе данных.

Пример кода:

@app.post("/API/orders")
def save_orders():
    if request.is_json:
        response = request.get_json()
        DB.add_data_orders(response['timestamp'], response['username'], response['market'], response['side'],  
        response['size'], response['price'])
        return response, 201
        return {"error": "Request must be JSON"}, 415

Полный код соединителя базы данных и код API SERVER можно найти на GitHub.

И, наконец, нам нужен коннектор API для нашего сервиса. Наш коннектор API будет использовать библиотеку запросов для выполнения HTTP-запросов POST к нашему серверу API.

Пример кода:

def generate_request(self, order):
    try:
        response = requests.post(self.api_url, json=order)
        print(response)
    except Exception as e:
        print('generate_request - Exception', e)

Полный код API CLIENT можно найти на GitHub.

В ~100 строках кода мы создали коннектор базы данных, API-сервер и API-клиент для сохранения данных заказа в базе данных на удаленном сервере.

Код доступен в этом репозитории Github.

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


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE