Как проверить параметрическую торговую систему, откалиброванную с помощью генетического алгоритма с помощью Python

В этом документе я представляю один подход к оценке надежности параметрической модели для систематической торговли фьючерсами.

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

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

Итак, начнем:

Сбор данных

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

Например, это фрейм исторических данных для GOLD:

Модель и калибровка

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

Эти n_parameters будут формировать определенное пространство параметров определенной размерности, в частности, здесь все параметры будут целыми числами, ограниченными сверху и снизу.

Par1 [LB1, HB1]

Par2 [LB2, HB2]

.

.

ParN [LBN, HBN]

Чтобы откалибровать модель по пространству параметров, нам нужно указать функцию пригодности, которая будет максимизирована во время калибровки.

Я буду использовать коэффициент Кальмара в качестве функции выбора фитнеса (максимальная прибыль / максимальная просадка).

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

Я использую библиотеку с открытым исходным кодом PyGAD:

 

PyGAD
PyGAD — это библиотека Python с открытым исходным кодом для создания генетического алгоритма и оптимизации алгоритмов машинного обучения… pygad.readthedocs.io

 

Схематично это код, который будет выполнять калибровку модели с учетом вашего тренировочного набора df исторических данных, вашей фитнес-функции и набора диапазонов параметров:

def fitness_func(solution,solution_idx)
        fitness = “your function f(df,parameters)”
        return fitness  

 
fitness_function = fitness_func
ga_instance = pygad.GA(num_generations=15,#15,#10
                       num_parents_mating=10,#10
                       fitness_func=fitness_function,
                       sol_per_pop=100,#100
                       num_genes=8,
                       gene_type=int,
                       gene_space = [range(LB1,HB1+1),…,range(LBN,HBN+1)]
                       crossover_probability = 0.95,
                       mutation_probability = 0.05,
                       save_solutions=True,
                       save_best_solutions=True)
    ga_instance.run()
    solution, solution_fitness, solution_idx = ga_instance.best_solution()

Проверка

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

См. также:  PHP 7.x - P41: стрелочные функции

Я использую вариант стандартного пошагового анализа:

Набор данных делится на набор поездов и набор тестов.

Наборы тестов — это неперекрывающиеся интервалы времени, и объединение всех наборов тестов соответствует совокупности доступных данных.

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

На жаргоне машинного обучения эта методология называется k-кратной перекрестной проверкой.

Чтобы сгенерировать пространство для наборов поездов и тестов, вы можете использовать следующий фрагмент:

def create_IS_OS(df,len_OOS)
    n = len_OOS
    df = df.copy()
    df_OS ={}
    df_IS_1 = {}
    df_IS_2 = {}
    df_IS = {}
    for (i,j) in enumerate(range(0, len(df), n)):
        df_OS[i] = df.iloc[j:j+n]
        df_IS_1[i] = df.iloc[:j]
        df_IS_2[i] = df.iloc[j+n:]
        if (i>0 and j+n<len(df)):
            df_IS_2[i] = df_IS_2[i].copy()
            delta = (df_IS_1[i].iloc[-1]-df_IS_2[i].iloc[0])
            df_IS_2[i]['OPEN'] = df_IS_2[i].OPEN+delta.OPEN
            df_IS_2[i]['CLOSE'] = df_IS_2[i].CLOSE+delta.CLOSE
            df_IS_2[i]['HIGH'] = df_IS_2[i].HIGH+delta.HIGH
            df_IS_2[i]['LOW'] = df_IS_2[i].LOW+delta.LOW
        df_IS[i] = pd.concat([df_IS_1[i],df_IS_2[i]])
    return df_IS,df_OS

Учитывая желаемую длину каждого из наборов тестов, функция вернет словарь, в котором ключ i_th — это набор поездов, а набор тестов i_th раунда.

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

Для каждого раунда нам нужно будет откалибровать модель и проверить производительность модели с откалиброванным параметром в тестовом наборе.

Этот код выполнит работу:

Обратите внимание, что я предполагаю, что функция фитнеса возвращает 3 значения: Calmar, P&L; как функцию времени, количество сделок модели, связанной с набором данных, и некоторые конкретные параметры.

os_pl = []
is_pl = []
summary = []
for i in range(len(df_IS.keys())):
    OS_start = df_OS[i].index[0]
    OS_end = df_OS[i].index[-1]
    def fitness_func(solution,solution_idx):
        fitness fitness = “your function f(df,parameters)”
        return fitness   
    fitness_function = fitness_func
    ga_instance = pygad.GA(num_generations=15,#15,#10
                       num_parents_mating=10,#10
                       fitness_func=fitness_function,
                       sol_per_pop=100,#100
                       num_genes=8,
                       gene_type=int,
                       gene_space = [range(LB1,HB1+1),…,range(LBN,HBN+1)]
                       crossover_probability = 0.95,
                       mutation_probability = 0.05,
                       save_solutions=True,
                       save_best_solutions=True
                     
                      )
    ga_instance.run()
    solution, solution_fitness, solution_idx = ga_instance.best_solution()
    print("Round {n}".format(n = i))
    print("Parameters of the best solution : {solution}".format(solution=solution))
    print("Fitness value of the best solution = {solution_fitness}".format(solution_fitness=solution_fitness))
    print("OS starts: {OS_start}".format(OS_start = OS_start))
    print("OS starts: {OS_end}".format(OS_end = OS_end))


    calmar_os,pl_os,n_trades_os = your fitness function(df_OS[i],*solution)
    calmar_is,pl_is,n_trades_is = your fitness function(df_IS[i],*solution)
    os_pl.append(pl_os)
    is_pl.append(pl_is)
    summary.append([OS_start,OS_end,calmar_os,n_trades_os])
SUMMARY = pd.DataFrame(summary)
SUMMARY.columns = ['OS_start','OS_end','Calmar_OS','n_trades_OS']]

Когда вы запустите его, вы увидите результаты калибровки каждого раунда:

См. также:  MLOps @ re: Invent 2020

И вы можете увидеть сводку статистики модели в каждом наборе тестов, используя параметры, откалиброванные в наборе поездов.

Теперь мы можем построить график результатов, чтобы иметь представление о том, как модель работает в различных интервалах набора тестов:

pl = {i:(pd.concat([pd.DataFrame(is_pl[i]),pd.DataFrame(os_pl[i])]).sort_index()).cumsum() for i in range(len(df_IS.keys()))

import matplotlib.pyplot as plt
fig = plt.figure(figsize=(30,20))
for i in range(len(df_IS.keys())):
    fig.add_subplot(5,3, i+1)
    plt.plot(pl[i], color='green',label='train_set')
    plt.plot((pl[i][(pl[i].index>=os_pl[i].index[i]) & (pl[i].index<=os_pl[i].index[-1])]['OPEN']), color='blue',label='test_set')
    plt.title('Walk forward')
    plt.legend()
plt.show()}

Если мы соединим все синие кривые, мы получим временные ряды чисто выходных характеристик, которые охватывают всю историю имеющихся данных, то есть примерно 13 лет выходных характеристик.

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

Пожалуйста, свяжитесь с нами, если вы хотите узнать больше.

[email protected]

Спасибо!

Больше контента на plainenglish.io

Понравилась статья? Поделиться с друзьями:
IT Шеф
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: