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







