Оптимізація параметрів стратегії (генетичний алгоритм)

Зауважимо: коли ми беремося за оптимізацію торгової стратегії, перший біль — ручний перебір десятків комбінацій або Grid Search, який при 7 параметрах потребує ~100 тис. бектестів. На одному з проєктів клієнт витратив два тижні на перебір — і отримав локальний оптимум. Ми впровадили генетичний алгор

Напрямки блокчейн-розробки

Часті запитання

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1450
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1309
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1005
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1270
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    719
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    1011

Зауважимо: коли ми беремося за оптимізацію торгової стратегії, перший біль — ручний перебір десятків комбінацій або Grid Search, який при 7 параметрах потребує ~100 тис. бектестів. На одному з проєктів клієнт витратив два тижні на перебір — і отримав локальний оптимум. Ми впровадили генетичний алгоритм (GA) і скоротили час пошуку до 1 дня. GA вирішує проблему комбінаторного вибуху: замість повного перебору він вирощує популяцію рішень через відбір, схрещування та мутацію. Ефективність особливо помітна для просторів із 5+ параметрами, де Grid Search стає непрактичним. Наша реалізація на Python із бібліотекою DEAP дає приріст швидкості до 50× без втрати якості.

Як генетичний алгоритм вирішує проблему комбінаторного вибуху?

В основі GA лежить еволюційна модель. Кожна особина — набір параметрів (періоди ковзних середніх, коефіцієнти стоп-лосу, пороги RSI). Популяція еволюціонує через відбір найкращих (за Sharpe ratio), схрещування (blend crossover) і мутацію (гаусів шум). Ми використовуємо DEAP — зрілий фреймворк із підтримкою паралельних обчислень. Це дозволяє обробляти до 60 особин за покоління за секунди. Для 10 параметрів із 10 градаціями повний перебір дав би 10 млрд комбінацій, а GA знаходить хороше рішення за 2000–5000 ітерацій.

Які проблеми вирішуємо?

  • Комбінаторний вибух: 10 параметрів із 10 градаціями = 10 млрд комбінацій. GA знаходить хороше рішення за 2000–5000 ітерацій.
  • Перетренування: еволюція легко запам'ятовує шум. Ми вбудовуємо штрафи за малу кількість угод (<20) і перевіряємо на out-of-sample даних.
  • Несумісність із блекбоксами: наші оптимізатори працюють із будь-яким бектест-движком через callback-функцію.

Як уникнути перетренування при еволюційній оптимізації?

Перетренування — одна з головних пасток. Ми застосовуємо крос-валідацію за часовими періодами (walk-forward), штрафуємо за складність моделі та обов'язково перевіряємо найкращі рішення на незалежному out-of-sample наборі даних. Наприклад, якщо стратегія показує Sharpe 2.5 на тренувальних даних, але 0.3 на валідації — такий набір відкидається. Фінальний результат завжди підтверджується на свіжих ринкових даних.

Порівняння методів оптимізації

Метод Кількість ітерацій (7 параметрів) Ризик перетренування Час виконання
Grid Search 10 млн Високий Тижні
Random Search 10 тис. Середній Дні
Genetic Algorithm 2–5 тис. Низький (з валідацією) Години

Як ми це робимо?

На одному проєкті для крипто-арбітражної стратегії ми оптимізували 7 параметрів (періоди ковзних середніх, RSI, стоп-лос, тейк-профіт). Використовували DEAP із population_size=60, поколінь=40. Фітнес-функція — Sharpe ratio, зі штрафом за <20 угод. Результат: Sharpe 2.1 проти 0.8 у ручного підбору. Згідно з документацією DEAP, паралельна оцінка на 4 ядрах прискорює роботу у 2–3 рази.

from deap import base, creator, tools, algorithms import random import numpy as np from functools import partial # Визначаємо задачу максимізації Sharpe ratio creator.create("FitnessMax", base.Fitness, weights=(1.0,)) creator.create("Individual", list, fitness=creator.FitnessMax) class GeneticOptimizer: def __init__( self, param_bounds: dict[str, tuple], # {'param': (min, max)} backtest_fn: callable, population_size: int = 50, n_generations: int = 30, crossover_prob: float = 0.7, mutation_prob: float = 0.2, n_jobs: int = 4, ): self.param_names = list(param_bounds.keys()) self.param_bounds = list(param_bounds.values()) self.backtest_fn = backtest_fn self.pop_size = population_size self.n_gen = n_generations self.cx_prob = crossover_prob self.mut_prob = mutation_prob self.n_jobs = n_jobs def decode_individual(self, individual: list) -> dict: """Конвертуємо список значень [0,1] у реальні параметри""" params = {} for i, name in enumerate(self.param_names): low, high = self.param_bounds[i] if isinstance(low, int) and isinstance(high, int): # Цілочисельний параметр params[name] = int(round(low + individual[i] * (high - low))) else: # Дійсний параметр params[name] = low + individual[i] * (high - low) return params def evaluate(self, individual: list) -> tuple: """Функція fitness: запускаємо бектест, повертаємо Sharpe ratio""" params = self.decode_individual(individual) try: metrics = self.backtest_fn(params) sharpe = metrics.get('sharpe_ratio', 0) # Штраф за занадто мало угод trades = metrics.get('total_trades', 0) if trades < 20: sharpe *= trades / 20 return (sharpe,) except Exception: return (-999.0,) def run(self) -> tuple[dict, pd.DataFrame]: toolbox = base.Toolbox() # Генератор особин: кожен параметр = float у [0, 1] toolbox.register("attr_float", random.random) toolbox.register( "individual", tools.initRepeat, creator.Individual, toolbox.attr_float, n=len(self.param_names), ) toolbox.register("population", tools.initRepeat, list, toolbox.individual) toolbox.register("evaluate", self.evaluate) toolbox.register("mate", tools.cxBlend, alpha=0.3) # Blend crossover toolbox.register("mutate", tools.mutGaussian, mu=0, sigma=0.1, indpb=0.2) toolbox.register("select", tools.selTournament, tournsize=3) # Обмежуємо значення в [0, 1] після мутації def check_bounds(individual): for i in range(len(individual)): individual[i] = max(0.0, min(1.0, individual[i])) return individual, toolbox.decorate("mutate", check_bounds) toolbox.decorate("mate", check_bounds) # Паралельна оцінка if self.n_jobs > 1: from multiprocessing.pool import Pool pool = Pool(self.n_jobs) toolbox.register("map", pool.map) # Запуск еволюції population = toolbox.population(n=self.pop_size) stats = tools.Statistics(lambda ind: ind.fitness.values[0]) stats.register("max", np.max) stats.register("avg", np.mean) hof = tools.HallOfFame(10) # Топ-10 найкращих особин population, logbook = algorithms.eaSimple( population, toolbox, cxpb=self.cx_prob, mutpb=self.mut_prob, ngen=self.n_gen, stats=stats, halloffame=hof, verbose=True, ) if self.n_jobs > 1: pool.close() # Результати best_params = self.decode_individual(hof[0]) all_results = [] for ind in hof: params = self.decode_individual(ind) all_results.append({**params, 'sharpe': ind.fitness.values[0]}) return best_params, pd.DataFrame(all_results) 
Типові помилки при оптимізації GA
  • Занадто маленька популяція (<30) призводить до передчасної збіжності.
  • Занадто висока ймовірність мутації (>0.5) руйнує хороші рішення.
  • Відсутність out-of-sample валідації — гарантія перетренування.
  • Ігнорування обмежень (min/max параметрів) може дати нереалізовані комбінації.

Що входить у роботу?

  • Адаптований код оптимізатора під ваш стек
  • Документація з налаштування та запуску
  • Підтримка при інтеграції у вашу систему
  • Рекомендації щодо покращення стратегії на основі результатів

Орієнтовні терміни

Етап Час
Аналітика та налаштування фітнес-функції 1–3 дні
Розробка оптимізатора під ваш стек 3–5 днів
Тестування та перевірка на out-of-sample 2–4 дні
Документування та передача 1–2 дні

Терміни залежать від складності стратегії та кількості параметрів. Вартість розраховується індивідуально.

Як проходить процес?

  1. Аналітика: розбираємо вашу стратегію, визначаємо параметри для оптимізації та межі.
  2. Проектування: пишемо фітнес-функцію з урахуванням ваших метрик (Sharpe, Sortino, drawdown).
  3. Реалізація: налаштовуємо GA на DEAP або Foundry (для смарт-контрактів).
  4. Тест: запускаємо еволюцію, порівнюємо з baseline, перевіряємо на out-of-sample.
  5. Деплой: видаємо код оптимізатора та топ-10 рішень із документацією.

Впровадження GA окупається, якщо ви витрачаєте тижні на ручний підбір або Grid Search. Наша команда має багаторічний досвід в оптимізації торгових алгоритмів. Зв'яжіться з нами — ми оцінимо ваш проєкт і запропонуємо рішення. Отримайте консультацію, щоб обговорити деталі.

Чому обирають нас?

  • Більше 30 проєктів з оптимізації стратегій
  • Використовуємо лише open-source інструменти (DEAP, Pandas) — жодних вендор-локів
  • Повна прозорість: ви отримуєте вихідний код і документацію