Зауважимо: коли ми беремося за оптимізацію торгової стратегії, перший біль — ручний перебір десятків комбінацій або 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 дні |
Терміни залежать від складності стратегії та кількості параметрів. Вартість розраховується індивідуально.
Як проходить процес?
- Аналітика: розбираємо вашу стратегію, визначаємо параметри для оптимізації та межі.
- Проектування: пишемо фітнес-функцію з урахуванням ваших метрик (Sharpe, Sortino, drawdown).
- Реалізація: налаштовуємо GA на DEAP або Foundry (для смарт-контрактів).
- Тест: запускаємо еволюцію, порівнюємо з baseline, перевіряємо на out-of-sample.
- Деплой: видаємо код оптимізатора та топ-10 рішень із документацією.
Впровадження GA окупається, якщо ви витрачаєте тижні на ручний підбір або Grid Search. Наша команда має багаторічний досвід в оптимізації торгових алгоритмів. Зв'яжіться з нами — ми оцінимо ваш проєкт і запропонуємо рішення. Отримайте консультацію, щоб обговорити деталі.
Чому обирають нас?
- Більше 30 проєктів з оптимізації стратегій
- Використовуємо лише open-source інструменти (DEAP, Pandas) — жодних вендор-локів
- Повна прозорість: ви отримуєте вихідний код і документацію







