В этом документе я представляю один подход к оценке надежности параметрической модели для систематической торговли фьючерсами.
Я покажу вам, как откалибровать модель с помощью генетического алгоритма и как реализовать валидационный анализ с помощью Python.
Хотя я не буду раскрывать фактическую модель, которую использую, пожалуйста, свяжитесь с нами по адресу [email protected], если вы хотите получить более конкретную информацию.
Итак, начнем:
Сбор данных
Первым делом загрузим наш фрейм данных. Я буду использовать почасовые данные OHLC за 13 лет для фьючерсного рынка, скорректированные с учетом разницы.
Например, это фрейм исторических данных для GOLD:
Модель и калибровка
Обсуждаемая торговая модель является параметрической моделью, что означает, что стратегия является функцией наборов данных и n параметров модели.
Эти n_parameters будут формировать определенное пространство параметров определенной размерности, в частности, здесь все параметры будут целыми числами, ограниченными сверху и снизу.
Par1 [LB1, HB1]
Par2 [LB2, HB2]
.
.
ParN [LBN, HBN]
Чтобы откалибровать модель по пространству параметров, нам нужно указать функцию пригодности, которая будет максимизирована во время калибровки.
Я буду использовать коэффициент Кальмара в качестве функции выбора фитнеса (максимальная прибыль / максимальная просадка).
Пространство параметров чрезвычайно велико, количество всех возможных комбинаций составляет около 10⁶, чтобы сделать задачу выполнимой и уменьшить риск переобучения, необходима генетическая оптимизация.
Я использую библиотеку с открытым исходным кодом PyGAD:
Схематично это код, который будет выполнять калибровку модели с учетом вашего тренировочного набора 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()
Проверка
Теперь, когда мы знаем, как откалибровать модель, осталось разработать процедуру проверки, которая даст нам некоторые подсказки о том, как модель может реально работать с невидимыми данными.
Я использую вариант стандартного пошагового анализа:
Набор данных делится на набор поездов и набор тестов.
Наборы тестов — это неперекрывающиеся интервалы времени, и объединение всех наборов тестов соответствует совокупности доступных данных.
Хотя эта структура отличается от наиболее часто используемых настроек перехода вперед с привязкой и без привязки, я считаю, что она наиболее соответствует реальности, потому что на каждом этапе мы используем объем обучающих данных, соответствующий количеству данных, которые он будет использовать в собственно процесс калибровки.
На жаргоне машинного обучения эта методология называется 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']]
Когда вы запустите его, вы увидите результаты калибровки каждого раунда:
И вы можете увидеть сводку статистики модели в каждом наборе тестов, используя параметры, откалиброванные в наборе поездов.
Теперь мы можем построить график результатов, чтобы иметь представление о том, как модель работает в различных интервалах набора тестов:
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 лет выходных характеристик.
То, что я показал здесь, является всего лишь примером проверки модели для конкретного инструмента, в данном случае для золота, но тот же подход можно повторить для большого набора фьючерсов, что даст аналогичные утешительные результаты.
Пожалуйста, свяжитесь с нами, если вы хотите узнать больше.
Спасибо!
Больше контента на plainenglish.io