Обзор

В этом руководстве будет показано, как создать гибридный пакет Python / C ++ с использованием setuptools, системы сборки Python и CMake, системы сборки, используемой во многих проектах C / C ++.

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

Прежде чем двигаться дальше, вам может быть интересно: какова цель создания гибридного пакета Python / C ++? Какие преимущества он предлагает по сравнению с обычным пакетом Python?

Во-первых, Python - это потрясающий язык программирования. Python де-факто является языком компьютерного зрения, машинного обучения и анализа данных. Кроме того, он отлично подходит для веб-разработки и быстрого прототипирования, а также для других задач. Однако для задач, требующих тяжелых вычислений, лучше подходят скомпилированные языки программирования, такие как C ++.

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

Однако то, что мы решили реализовать функцию на C ++, не означает, что мы должны реализовать всю платформу для торговли акциями на C ++. Мы можем создать библиотеку C ++ с нашей функцией технического индикатора, создать привязки Python для библиотеки, а затем вызвать функцию, как если бы это была любая другая функция Python. Фактически, именно так работают такие пакеты, как NumPy и TensorFlow: основная логика реализована на C ++, и вы взаимодействуете с этой логикой через привязки Python.

Сборка пакета

Продолжая, давайте рассмотрим структуру нашего репозитория:

|DolphinTradiningIndicators
|- docs/
|- lib/
|- - catch2/
|- - - catch.hh
|- - indicators/
|- - - include/
|- - - - indicators_core.h
|- - - tests/
|- - - - test_first_derivative.cpp
|- - - CMakeLists.txt
|- - - indicators_core.cpp
|- - pybind11/ (Git Submodule)
|- src/
|- - DolphinTrading/
|- - - __init__.py
|- - - indicator_bindings.cpp
|- - - python_indicators.py
|- tests/
|- - __init__.py
|- - test_first_derivative.py
|- - test_moving_average.py
|- - utilities.py
|- CMakeLists.txt
|- LICENSE
|- README.md
|- MANIFEST.in
|- pytest.ini
|- setup.py

Примечание. Термин «пакет» несколько неоднозначен. В этом репозитории фактический пакет Python находится по адресу src/DolphinTrading; пакет Python - это набор модулей Python в каталоге с модулем __init__.py. Однако обычно весь репозиторий также называют пакетом, поскольку он содержит все файлы, необходимые для сборки и установки пакета Python.

В пакете есть две основные функции, каждая из которых вычисляет технический индикатор: moving_average и first_derivative.

Функция moving_average написана на Python. Он принимает список целых чисел или чисел с плавающей запятой и возвращает список, содержащий простую скользящую среднюю за 3 периода. Исходный код выглядит следующим образом:

Функция first_derivative написана на C ++. Он принимает вектор с плавающей запятой и возвращает вектор, содержащий соседние различия. Исходный код выглядит следующим образом:

Функция first_derivative находится в файле с именем indicators_core.cpp, который мы будем использовать для создания статической библиотеки C ++ с именем indicators_core.

Для сборки библиотеки мы используем систему сборки CMake; в частности, мы создаем файл CMakeLists.txt в каталоге lib/indicators со следующим содержимым:

# CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(indicators)
SET(CMAKE_CXX_FLAGS "-std=c++0x")
add_library(indicators_core STATIC indicators_core.cpp)
add_executable(test_first_derivative tests/test_first_derivative.cpp)
target_include_directories(indicators_core PUBLIC "${CMAKE_CURRENT_LIST_DIR}/include")
target_include_directories(test_first_derivative PUBLIC ../catch2)
target_link_libraries(test_first_derivative indicators_core)

Кроме того: если вы ищете отличный ресурс для изучения CMake, посмотрите эту электронную книгу.

Теперь, когда мы создали статическую библиотеку с одной из наших функций технических индикаторов, мы можем создать привязки Python для доступа к ней. Для этого мы создадим файл с именем indicator_bindings.cpp в src/DolphinTrading. В этот файл мы включаем библиотеку с именем pybind11 в дополнение к нашей настраиваемой статической библиотеке. Pybind11 - это облегченная библиотека только для заголовков, которая предоставляет типы C ++ в Python и наоборот, в основном для создания привязок Python для существующего кода C ++ [1].

Завершим настройку пакета Python, добавив __init__.py модуль. Чтобы упростить импорт функций технических индикаторов, добавим в __init__.py следующее.

# src/DolphinTrading/__init__.py
from .indicator_bindings import *
from .python_indicators import *

В каталоге верхнего уровня нашего репозитория мы создадим еще один файл CMakeLists.txt. Это создаст общую библиотеку из indicator_bindings.cpp, которую затем можно будет импортировать в Python, как и любой другой модуль Python. Этот тип модуля называется модулем расширения.

# CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(DolphinTrading)
SET(CMAKE_CXX_FLAGS "-std=c++0x")
add_subdirectory(lib/indicators)
add_subdirectory(lib/pybind11)
set(SOURCE_DIR "src/DolphinTrading")
set(SOURCES "${SOURCE_DIR}/indicator_bindings.cpp")
pybind11_add_module(indicator_bindings ${SOURCES})
target_link_libraries(indicator_bindings PRIVATE indicators_core)

Теперь давайте создадим setup.pymodule в каталоге верхнего уровня репозитория. Этот модуль действует как драйвер для создания и установки пакетов Python. Мы будем использовать setuptools, полнофункциональную, активно поддерживаемую и стабильную библиотеку, разработанную для облегчения упаковки проектов Python для настройки нашего пакета.

Чтобы вызвать CMake для создания нашего модуля расширения C ++, мы переопределим классы Extension и build_ext из setuptools, как показано ниже:

Мы закончили сборку пакета! В терминале в каталоге верхнего уровня этого репозитория выполните команду: python setup.py install. Теперь вы можете начать использовать пакет.

Python 3.7.6 (default, Dec 30 2019, 19:38:26) 
[Clang 11.0.0 (clang-1100.0.33.16)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import DolphinTrading
>>> DolphinTrading.first_derivative([2,4,6,9,10])
[2.0, 2.0, 2.0, 3.0, 1.0]
>>> DolphinTrading.moving_average([2,3,4,5,6])
[3.0, 4.0, 5.0]
>>>

Примечание. Если вы активно разрабатываете пакет, часто рекомендуется установить его через python setup.py develop. Это позволяет редактировать пакет без необходимости переустанавливать его после каждого изменения. Подробнее см. [2].

Тестирование пакета

Тестирование - неотъемлемая часть процесса разработки программного обеспечения; это особенно верно при разработке пакетов, которые другие пользователи будут интегрировать в свои собственные проекты. Например, можете ли вы представить, что функция квадратного корня Numpy, numpy.sqrt, содержит ошибку, которая вычисляет кубический корень вместо квадратного корня? Миллионы пользователей полагаются на NumPy и верят, что его функции делают то, что им положено. В этом духе мы создадим простой набор тестов для проверки каждой из функций наших технических индикаторов. Мы будем использовать среду pytest, чтобы писать модульные тесты. Pytest - это простой в использовании, но надежный фреймворк для тестирования. Чтобы установить pytest, выполните команду pip install pytest.

Примечание. pytest будет рассматривать любую функцию с префиксом test в модуле test_*.py или *_test.py как тестовый пример. Каждый тестовый пример должен иметь оператор assert, который проверяет ожидания тестового примера. См. [3] для получения дополнительной информации.

Наши тесты будут помещены в каталог tests/. Прежде чем приступить к реализации тестовых примеров, давайте создадим модуль с именем utilities.py; это будет содержать функции, которые мы будем использовать во всех тестовых примерах. В частности, мы реализуем функцию, которая возвращает True, если два списка равны, и False в противном случае.

Теперь давайте проведем тест индикатора скользящей средней:

И, наконец, что не менее важно, тест на первый производный индикатор:

После завершения тестовых примеров давайте создадим файл с именем pytest.ini в каталоге верхнего уровня репозитория. Это позволяет нам настроить, как двоичный файл pytest будет запускать наши тесты. В нем мы говорим pytest искать тесты в каталоге tests/.

[pytest]
testpaths = tests

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

Чтобы сделать еще один шаг вперед, мы будем использовать среду тестирования под названием tox в сочетании с pytest; tox создаст изолированные виртуальные среды Python (используя virtualenv), а затем запустит pytest (или другую среду модульного тестирования) в этих средах. Это дает нам возможность моделировать, как наш пакет будет работать в разных средах Python.

Чтобы начать работу с tox, установите его через pip: pip install tox. Затем добавьте файл tox.ini на верхний уровень репозитория. В него добавьте следующее:

[tox]
envlist = py37
[testenv]
deps = pytest
commands = pytest

Указанные нами параметры tox.ini дадут указание tox создать виртуальную среду Python с Python 3.7, установить pytest и запустить наш набор тестов с помощью pytest.

Следующие шаги

В следующем руководстве мы узнаем, как создать пакет Python с поддержкой CUDA для вычислительной мощности с ускорением на GPU. Спасибо за прочтение!

Источники

  1. Https://github.com/pybind/pybind11
  2. Https://stackoverflow.com/questions/19048732/python-setup-py-develop-vs-install
  3. Https://docs.pytest.org/en/latest/goodpractices.html#test-discovery
  4. Https://www.benjack.io/2017/06/12/python-cpp-tests.html