Валидация параметрических краткосрочных стратегий с помощью Python: насколько быстро вы действительно можете двигаться?

Введение

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

Параметрические модели:

Параметрическая модель — это модель, которая зависит от набора данных и набора параметров.

Эти параметры имеют предопределенные значения, определяющие пространство комбинаций модели, которое представлено всеми возможными комбинациями n_plet [p1, p2….pn].

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

Калибровка модели включает в себя идентификацию n_plet, которая максимизирует функцию пригодности в пространстве параметров.

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

Если мы предположим, что инструменты торгуются 23 часа каждый день, существует 23*22 возможных пары или 2_плета.

Проверка модели всегда подразумевает создание матрицы, охватывающей все временные ряды доходности, связанные с параметрическим пространством. Количество столбцов в этой матрице эквивалентно общему количеству возможных комбинаций параметров (например, 23*22 в предыдущем простом примере). При этом количество строк определяется количеством временных меток в возвращаемом временном ряду.

Например, предположим, что у нас есть десять лет наблюдений за ценами, и в каждом году 250 торговых дней. В этом случае количество столбцов в матрице будет (250*10)-1 (исключая один, поскольку расчет переходит от цены к доходности).

Параметрические модели, зависящие от пути и не зависящие от пути:

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

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

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

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

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

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

Возможные подходы: векторизация Python и оптимизация Numba».

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

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

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

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

Сравнение скорости

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

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

Генерация данных

Цены, cond_entries и cond-выходы необходимой длины генерируются случайным образом с помощью методов generate_random:

def generate_random(array_length)
    # Generate random array of True and False values
    random_cond_entry = np.random.choice([True, False], size=array_length)
    random_cond_exit = np.random.choice([True, False], size=array_length)
    side = 0
    # Generate random prices between a specified range
    min_price = 10.0
    max_price = 100.0
    random_prices = np.random.uniform(min_price, max_price, size=array_length)
    return random_cond_entry,random_cond_exit,random_prices

Расчет сигнала

Сигналы и PL вычисляются функцией calc_signal.

Для лучшего понимания давайте определим некоторые основные обозначения и правила расчета:

t_i — это i_th отметка времени, и она обозначается как i.

Условия входа и выхода из сделок, обозначаемые как condentry и condexit, зависят от набора параметров: p_1, p_2, …, p_n, а также доступных данных до момента времени i.

Если есть сигнал, сгенерированный condentry, истинный в момент времени i, то позиция будет открыта в момент времени i+1. Положение останется прежним, пока не будет сигнала от condexit.

Доход, соответствующий сигналу, сгенерированному в момент времени i, будет известен в момент времени i+2.

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

Наконец, собственный капитал в момент времени i представляет собой кумулятивную сумму всех значений прибыли или убытка до момента времени i:

Переведя приведенные выше определения на python, мы можем написать две версии функции calc_signal.

#vectorized
def calc_signal(P, cond_entry, cond_exit, side)
    signal = bn.push(np.where(cond_exit, 0, np.where(cond_entry, side, np.nan)))
    rets = (P[2:]-P[1:-1])/P[1:-1]
    pl = np.multiply(rets,signal[:-2])
    return signal, pl
#numba
@nb.jit
def calc_signal_numba(P,cond_entry, cond_exit, side):
    signal = np.zeros(len(cond_entry))
    pl = np.zeros(len(cond_entry))
    for i in range(signal.shape[0]-2):
        signal[i] = side if cond_entry[i] else signal[i-1]
        ret = (P[i+2] - P[i+1]) / P[i+1]
        pl[i] = ret * signal[i] if not cond_exit[i] else 0
    return signal, pl:

Измерение времени выполнения

Чтобы сравнить производительность двух подходов, мы измеряем среднее время выполнения с помощью модуля timeit. Две функции, Measure_execution_time_numba и Measure_execution_time_no_numba, определены для переноса кода расчета сигнала для подходов Numba и векторизованного NumPy соответственно.

number_of_runs = 10  # Number of times to run the cod
# Define a function to wrap the code snippet
def measure_execution_time_numba():
    calc_signal_numba(random_prices, random_cond_entry, random_cond_exit, side)[1]
def measure_execution_time_vectorized():
    calc_signal(random_prices, random_cond_entry, random_cond_exit, side)[1]

Результаты и визуализация

Мы вычисляем время вычислений для различных случаев, соответствующих увеличению количества данных, от ¹⁰⁴ до ¹⁰⁸ временных меток:

combs = [num for exponent in range(4, 8) for num in [j*10**exponent for j in range(1,10)]]   
end_times = []   
for array_length in combs:
    #generate_data
    random_cond_entry, random_cond_exit, pyrandom_prices = generate_random(array_length)
    # Measure the execution time using numba approach
    total_execution_time_numba = timeit.timeit(measure_execution_time_numba, number=number_of_runs)
    average_execution_time_numba = total_execution_time_numba / number_of_runs
    # Measure the execution time using vectorized approach
    total_execution_time_no_numba = timeit.timeit(measure_execution_time_no_numba, number=number_of_runs)
    average_execution_time_no_numba = total_execution_time_no_numba / number_of_runs
    end_times.append([average_execution_time_numba,average_execution_time_no_numba])

Окончательные результаты показаны на диаграмме ниже:

Сравнение времени выполнения Numba и векторизованного NumPy’

Интересно, что мы наблюдаем, что Numba эффективно ускоряет расчет прибылей и убытков (PL) даже по сравнению с уже высоко оптимизированным векторизованным подходом NumPy. Это подчеркивает убедительные преимущества использования Numba для проверки краткосрочных параметрических моделей.

Большое спасибо всем читателям! Я искренне ценю ваше время и интерес к этой статье.

Не стесняйтесь обращаться ко мне по адресу [email protected] и подписывайтесь на мою среднюю страницу, чтобы быть в курсе новых материалов по темам, связанным с систематической торговлей.

Профиль Linkedin: https://www.linkedin.com/in/francesco-landolfi-2311953/

Канал на YouTube (если вас интересует не только наука о данных!) https://www.youtube.com/@FraLandolfi/

Повышение уровня кодирования

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

  • 👏 Хлопайте за историю и подписывайтесь на автора 👉
  • 📰 Смотрите больше контента в публикации Level Up Coding

🔔 Подписывайтесь на нас: Twitter | ЛинкедИн | "Новостная рассылка"

🧠 AI Tools ⇒ Стань шустрым инженером