Отметим: когда мы беремся за оптимизацию торговой стратегии, первая боль — ручной перебор десятков комбинаций или 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) — никаких вендор-локов
- Полная прозрачность: вы получаете исходный код и документацию







