Português
preview
Эволюционный торговый алгоритм обучения с подкреплением и вымиранием убыточных особей (ETARE)

Эволюционный торговый алгоритм обучения с подкреплением и вымиранием убыточных особей (ETARE)

MetaTrader 5Интеграция |
621 0
Yevgeniy Koshtenko
Yevgeniy Koshtenko

Введение

Знаете, что общего между эволюцией, нейронными сетями и трейдерами? Правильно — все они учатся на своих ошибках. Именно эта мысль пришла ко мне после очередной бессонной ночи за терминалом, когда мой "идеальный" торговый алгоритм в очередной раз слил депозит на неожиданном движении рынка.

Помню тот день как сейчас: 23 июня 2016 года, референдум по Brexit. Мой алгоритм, основанный на классических паттернах технического анализа, уверенно держал длинную позицию по фунту. "Все опросы показывают, что Британия останется в ЕС", — думал я тогда. И вот, в 4 утра по Москве, когда первые результаты показали победу сторонников выхода, фунт рухнул на 1800 пунктов за считанные минуты. Депозит испарился на 40%.

В марте 2023 года я начал разработку ETARE — Эволюционного торгового алгоритма с подкреплением и вымиранием (элиминация). Почему элиминация? Потому что в природе выживают сильнейшие. Так почему бы не применить этот принцип к торговым стратегиям?

Готовы погрузиться в мир, где классический технический анализ встречается с последними достижениями искусственного интеллекта? Где каждая торговая стратегия борется за выживание в дарвиновском естественном отборе? Тогда пристегните ремни — будет интересно. Потому что то, что вы сейчас увидите, — это не просто очередной торговый робот. Это результат 15 лет проб и ошибок, тысяч часов программирования и, честно говоря, нескольких сожженных депозитов. Но главное — это работающая система, которая уже приносит реальную прибыль своим пользователям.


Архитектура системы

В сердце ETARE лежит гибридная архитектура, напоминающая современный квантовый компьютер. Помните те времена, когда мы писали простые скрипты для MetaTrader4, основанные на пересечении двух скользящих средних? Тогда это казалось прорывом. Сейчас, оглядываясь назад, я понимаю: мы были как древние мореплаватели, пытающиеся пересечь океан с помощью только компаса и звезд.

После краха 2022 года стало очевидно: рынок слишком сложен для простых решений. Именно тогда началось мое путешествие в мир машинного обучения.

class HybridTrader:
    def __init__(self, symbols, population_size=50):
        self.population = []  # Популяция стратегий
        self.extinction_rate = 0.3  # Процент вымирания
        self.elite_size = 5  # Элитные особи
        self.inefficient_extinction_interval = 5  # Интервал чистки

Представьте колонию муравьев, где каждый муравей — это торговая стратегия. Сильные особи выживают и передают свои гены потомству, слабые — исчезают. В моей системе роль генов играют весовые коэффициенты нейронной сети.

Почему population_size=50? Потому что меньшее количество стратегий не дает достаточной диверсификации, а большее — затрудняет быструю адаптацию к изменениям рынка.

В природе муравьи постоянно исследуют новые территории, находят пищу и передают информацию сородичам. В ETARE каждая стратегия так же исследует рынок, а успешные паттерны торговли передаются следующим поколениям через механизм скрещивания:

def _crossover(self, parent1, parent2):
    child = TradingIndividual(self.input_size)
    # Скрещивание весов через маску
    for attr in ['input_weights', 'hidden_weights', 'output_weights']:
        parent1_weights = getattr(parent1.weights, attr)
        parent2_weights = getattr(parent2.weights, attr)
        mask = np.random.random(parent1_weights.shape) < 0.5
        child_weights = np.where(mask, parent1_weights, parent2_weights)
        setattr(child.weights, attr, child_weights)
    return child

В 2024 году (в декабре), анализируя логи торговли, я заметил, что наиболее успешные коды часто являются "гибридами" других успешных подходов. Как в природе сильные гены дают здоровое потомство, так и в алгоритмической торговле успешные паттерны могут комбинироваться, создавая еще более эффективные стратегии.

Сердцем системы стала LSTM-сеть — особый вид нейронных сетей с "памятью". После нескольких месяцев экспериментов с различными архитектурами, от простых многослойных персептронов до сложных трансформеров, мы остановились на этой конфигурации:

class LSTMModel(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(LSTMModel, self).__init__()
        self.lstm = nn.LSTM(input_size, hidden_size, batch_first=True)
        self.dropout = nn.Dropout(0.4)  # Защита от переобучения
        self.fc = nn.Linear(hidden_size, output_size)
    
    def forward(self, x):
        out, _ = self.lstm(x)
        out = self.dropout(out[:, -1, :])  # Используем последний выход LSTM
        out = self.fc(out)
        return out

Каждые 100 сделок система проводит "чистку", безжалостно удаляя убыточные стратегии. Это один из ключевых механизмов ETARE, и его создание — это отдельная история. Помню ночь в декабре 2023 года, когда анализировал логи торговли и заметил удивительную закономерность: большинство стратегий, показавших убыток в первых 100-150 сделках, продолжали быть убыточными и дальше. Это наблюдение полностью изменило архитектуру системы:

def _inefficient_extinction_event(self):
    """Периодическое вымирание неэффективных особей"""
    initial_size = len(self.population)
    
    # Анализ эффективности каждой стратегии
    performance_metrics = []
    for individual in self.population:
        metrics = {
            'profit_factor': individual.total_profit / abs(individual.max_drawdown) if individual.max_drawdown != 0 else 0,
            'win_rate': len([t for t in individual.trade_history if t.profit > 0]) / len(individual.trade_history) if individual.trade_history else 0,
            'risk_adjusted_return': individual.total_profit / individual.volatility if individual.volatility != 0 else 0
        }
        performance_metrics.append(metrics)
    
    # Удаляем убыточные стратегии с учетом комплексной оценки
    self.population = [ind for ind, metrics in zip(self.population, performance_metrics)
                      if metrics['profit_factor'] > 1.5 or metrics['win_rate'] > 0.6]
    
    # Создаем новых особей с улучшенной инициализацией
    while len(self.population) < initial_size:
        new_individual = TradingIndividual(self.input_size)
        new_individual.mutate()  # Случайные мутации
        
        # Наследование успешных паттернов
        if len(self.population) > 0:
            parent = random.choice(self.population)
            new_individual.inherit_patterns(parent)
            
        self.population.append(new_individual)

База данных торговых решений работает как память системы. Каждое решение, каждый результат — всё записывается для последующего анализа:

def _save_to_db(self):
    with self.conn:
        self.conn.execute('DELETE FROM population')
        for individual in self.population:
            data = {
                'weights': individual.weights.to_dict(),
                'fitness': individual.fitness,
                'profit': individual.total_profit
            }
            self.conn.execute(
                'INSERT INTO population (data) VALUES (?)',
                (json.dumps(data),)
            )

Весь этот сложный механизм работает как единый организм, постоянно эволюционируя и адаптируясь к изменениям рынка. В моменты высокой волатильности, например, когда VIX превышает 25, система автоматически увеличивает требования к надежности стратегий. А в спокойные периоды она становится более агрессивной, позволяя экспериментировать с новыми торговыми паттернами.



Механизм обучения с подкреплением

В разработке торговых роботов есть один парадокс: чем сложнее алгоритм, тем хуже он работает на реальном рынке. 

Поэтому в механизме обучения ETARE мы сделали ставку на простоту и прозрачность. После двух лет экспериментов с различными архитектурами, мы пришли к системе приоритизированной памяти:

class RLMemory:
    def __init__(self, capacity=10000):
        self.memory = deque(maxlen=capacity)
        self.priorities = deque(maxlen=capacity)
        
    def add(self, state, action, reward, next_state):
        priority = max(self.priorities) if self.priorities else 1.0
        self.memory.append((state, action, reward, next_state))
        self.priorities.append(priority)

Каждое торговое решение — это не просто вход в рынок, а сложный баланс между риском и потенциальной прибылью. Посмотрите, как система учится на своих решениях:

def update(self, state, action, reward, next_state):
    self.memory.add(state, action, reward, next_state)
    self.total_profit += reward

    if len(self.memory.memory) >= 32:
        batch = self.memory.sample(32)
        self._train_on_batch(batch)

Я много раз терял из-за того, что модели не могли перестроиться на иное состояние рынка. Их выносило. Именно тогда родилась идея адаптивного обучения. Теперь система анализирует каждую сделку и корректирует свое поведение:

def _calculate_confidence(self, prediction, patterns):
    # Базовая уверенность от ML-модели
    base_confidence = abs(prediction - 0.5) * 2
    
    # Учитываем исторический опыт
    pattern_confidence = self._get_pattern_confidence(patterns)
    
    # Динамическая адаптация к рынку
    market_volatility = self._get_current_volatility()
    return (base_confidence * 0.7 + pattern_confidence * 0.3) / market_volatility

Ключевой момент — система не просто запоминает успешные сделки, она учится понимать, почему они были успешными. Это стало возможным, благодаря многоуровневой архитектуре обратного распространения ошибки , реализованной в PyTorch:

def _train_on_batch(self, batch):
    states = torch.FloatTensor(np.array([x[0] for x in batch]))
    actions = torch.LongTensor(np.array([x[1].value for x in batch]))
    rewards = torch.FloatTensor(np.array([x[2] for x in batch]))
    next_states = torch.FloatTensor(np.array([x[3] for x in batch]))
    
    current_q = self.forward(states).gather(1, actions.unsqueeze(1))
    next_q = self.forward(next_states).max(1)[0].detach()
    target = rewards + self.gamma * next_q
    
    loss = self.criterion(current_q.squeeze(), target)
    self.optimizer.zero_grad()
    loss.backward()
    self.optimizer.step()

В итоге мы получили систему, которая учится не на идеальных бэктестах, а на реальном опыте торговли. За последний год тестирования на живом рынке ETARE показала способность адаптироваться к различным рыночным условиям: от спокойных трендов до высоковолатильных периодов.

Но самое важное — система продолжает эволюционировать. С каждой сделкой, с каждым рыночным циклом она становится немного умнее, немного эффективнее. Как сказал один из наших бета-тестеров: "Впервые вижу алгоритм, который действительно учится на своих ошибках, а не просто подгоняет параметры под исторические данные".


Механизм вымирания убыточных особей

Чарльз Дарвин никогда не торговал на финансовых рынках, но его теория эволюции удивительным образом описывает динамику успешных торговых стратегий. В природе выживают не самые сильные или быстрые особи, а те, кто лучше всего приспосабливается к изменениям окружающей среды. На рынке происходит то же самое.

История знает немало случаев, когда "идеальный" торговый алгоритм превращался в тыкву после первого же черного лебедя. В 2015 году я потерял значительную часть депозита, когда Швейцарский национальный банк отвязал франк от евро. Мой тогдашний алгоритм оказался совершенно не готов к такому событию. Это заставило меня задуматься: почему природа миллионы лет успешно справляется с "черными лебедями", а наши алгоритмы нет?

Ответ пришел неожиданно, во время чтения книги "Происхождение видов". Дарвин описывал, как в период резких климатических изменений выживали не самые специализированные виды, а те, кто сохранил способность к адаптации. Именно этот принцип лег в основу механизма вымирания в ETARE:

def _inefficient_extinction_event(self):
    """Периодическое вымирание неэффективных особей"""
    initial_population = len(self.population)
    market_conditions = self._analyze_market_state()
    
    # Оценка адаптивности каждой стратегии
    adaptability_scores = []
    for individual in self.population:
        score = self._calculate_adaptability(
            individual, 
            market_conditions
        )
        adaptability_scores.append(score)
    
    # Динамический порог выживания
    survival_threshold = np.percentile(
        adaptability_scores, 
        30  # Нижние 30% популяции вымирают
    )
    
    # Безжалостное вымирание
    survivors = []
    for ind, score in zip(self.population, adaptability_scores):
        if score > survival_threshold:
            survivors.append(ind)
    
    self.population = survivors
    
    # Восстановление популяции через мутации и скрещивание
    while len(self.population) < initial_population:
        if len(self.population) >= 2:
            # Скрещивание выживших
            parent1 = self._tournament_selection()
            parent2 = self._tournament_selection()
            child = self._crossover(parent1, parent2)
        else:
            # Создание новой особи
            child = TradingIndividual(self.input_size)
        
        # Мутации для адаптации
        child.mutate(market_conditions.volatility)
        self.population.append(child)

Как в природе периоды массового вымирания приводят к появлению новых, более совершенных видов, так и в нашей системе периоды высокой волатильности становятся катализатором эволюции стратегий. Взгляните на механизм естественного отбора:

def _extinction_event(self):
    # Анализ рыночных условий
    market_phase = self._identify_market_phase()
    volatility = self._calculate_market_volatility()
    trend_strength = self._measure_trend_strength()
    
    # Адаптивная сортировка по выживаемости
    def fitness_score(individual):
        return (
            individual.profit_factor * 0.4 +
            individual.sharp_ratio * 0.3 +
            individual.adaptability_score * 0.3
        ) * (1 + individual.correlation_with_market)
    
    self.population.sort(
        key=fitness_score, 
        reverse=True
    )
    
    # Сохранение элиты с учетом разнообразия
    elite_size = max(
        5, 
        int(len(self.population) * 0.1)
    )
    survivors = self.population[:elite_size]
    
    # Создание нового поколения
    while len(survivors) < self.population_size:
        if random.random() < 0.8:  # 80% кроссовер
            # Турнирная селекция родителей
            parent1 = self._tournament_selection()
            parent2 = self._tournament_selection()
            
            # Скрещивание с учетом рыночных условий
            child = self._adaptive_crossover(
                parent1, 
                parent2, 
                market_phase
            )
        else:  # 20% мутация элиты
            # Клонирование с мутациями
            template = random.choice(survivors[:3])
            child = self._clone_with_mutations(
                template,
                volatility,
                trend_strength
            )
        survivors.append(child)

Особое внимание мы уделили механизму оценки приспособленности. В природе это способность особи производить жизнеспособное потомство, в нашем случае — способность стратегии генерировать прибыль в различных рыночных условиях:

def evaluate_fitness(self, individual):
    # Базовые метрики
    profit_factor = individual.total_profit / max(
        abs(individual.total_loss), 
        1e-6
    )
    
    # Устойчивость к просадкам
    max_dd = max(individual.drawdown_history) if individual.drawdown_history else 0
    drawdown_resistance = 1 / (1 + max_dd)
    
    # Анализ последовательности прибылей
    profit_sequence = [t.profit for t in individual.trade_history[-50:]]
    consistency = self._analyze_profit_sequence(profit_sequence)
    
    # Корреляция с рынком
    market_correlation = self._calculate_market_correlation(
        individual.trade_history
    )
    
    # Адаптивность к изменениям
    adaptability = self._measure_adaptability(
        individual.performance_history
    )
    
    # Комплексная оценка
    fitness = (
        profit_factor * 0.3 +
        drawdown_resistance * 0.2 +
        consistency * 0.2 +
        (1 - abs(market_correlation)) * 0.1 +
        adaptability * 0.2
    )
    
    return fitness

А вот как происходит мутация выживших стратегий. Этот процесс напоминает генетические мутации в природе, где случайные изменения ДНК иногда приводят к появлению более жизнеспособных организмов:

def mutate(self, market_conditions):
    """Адаптивная мутация с учетом рыночного состояния"""
    # Динамическая настройка силы мутации
    self.mutation_strength = self._calculate_mutation_strength(
        market_conditions.volatility,
        market_conditions.trend_strength
    )
    
    if np.random.random() < self.mutation_rate:
        # Мутация весов нейросети
        for weight_matrix in [
            self.weights.input_weights,
            self.weights.hidden_weights,
            self.weights.output_weights
        ]:
            # Маска мутации с адаптивным порогом
            mutation_threshold = 0.1 * (
                1 + market_conditions.uncertainty
            )
            mask = np.random.random(weight_matrix.shape) < mutation_threshold
            
            # Генерация мутаций с учетом волатильности
            mutations = np.random.normal(
                0,
                self.mutation_strength * market_conditions.volatility,
                size=mask.sum()
            )
            
            # Применение мутаций
            weight_matrix[mask] += mutations
            
        # Мутация гиперпараметров
        if random.random() < 0.3:  # 30% шанс
            self._mutate_hyperparameters(market_conditions)

Интересно, что в некоторых версиях системы — в периоды высокой рыночной волатильности система автоматически увеличивает интенсивность мутаций. Это напоминает то, как некоторые бактерии ускоряют мутации в стрессовых условиях. В нашем случае:

def _calculate_mutation_strength(self, volatility, trend_strength):
    """Расчет силы мутации на основе рыночных условий"""
    base_strength = self.base_mutation_strength
    
    # Усиление мутаций при высокой волатильности
    volatility_factor = 1 + (volatility / self.average_volatility - 1)
    
    # Ослабление мутаций в сильном тренде
    trend_factor = 1 / (1 + trend_strength)
    
    # Итоговая сила мутации
    mutation_strength = (
        base_strength * 
        volatility_factor * 
        trend_factor
    )
    
    return np.clip(
        mutation_strength,
        self.min_mutation_strength,
        self.max_mutation_strength
    )

Особенно важен механизм диверсификации популяции. В природе генетическое разнообразие — ключ к выживанию вида. В ETARE мы реализовали аналогичный принцип:

def _maintain_population_diversity(self):
    """Поддержание разнообразия в популяции"""
    # Расчет матрицы сходства стратегий
    similarity_matrix = np.zeros(
        (len(self.population), len(self.population))
    )
    
    for i, ind1 in enumerate(self.population):
        for j, ind2 in enumerate(self.population[i+1:], i+1):
            similarity = self._calculate_strategy_similarity(ind1, ind2)
            similarity_matrix[i,j] = similarity_matrix[j,i] = similarity
    
    # Выявление кластеров похожих стратегий
    clusters = self._identify_strategy_clusters(similarity_matrix)
    
    # Принудительная диверсификация при необходимости
    for cluster in clusters:
        if len(cluster) > self.max_cluster_size:
            # Оставляем только лучшие стратегии в кластере
            survivors = sorted(
                cluster,
                key=lambda x: x.fitness,
                reverse=True
            )[:self.max_cluster_size]
            
            # Заменяем остальные новыми стратегиями
            for idx in cluster[self.max_cluster_size:]:
                self.population[idx] = TradingIndividual(
                    self.input_size,
                    mutation_rate=self.high_mutation_rate
                )

Результат? Система, которая не просто торгует, а эволюционирует вместе с рынком. Как говорил Дарвин, выживает не самый сильный, а самый адаптивный. В мире алгоритмического трейдинга это актуально как никогда.


База данных торговых решений

Сохранять опыт торговли так же важно, как и получать его. За годы работы с алгоритмическими системами я не раз убеждался: без надежной базы данных любая торговая система рано или поздно "забудет" свои лучшие стратегии. В ETARE мы реализовали многоуровневое хранение торговых решений:

def _create_tables(self):
    """Создание структуры базы данных"""
    with self.conn:
        self.conn.execute('''
            CREATE TABLE IF NOT EXISTS population (
                id INTEGER PRIMARY KEY,
                individual TEXT,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                last_update TIMESTAMP
            )
        ''')
        
        self.conn.execute('''
            CREATE TABLE IF NOT EXISTS history (
                id INTEGER PRIMARY KEY,
                generation INTEGER,
                individual_id INTEGER,
                trade_history TEXT,
                market_conditions TEXT,
                FOREIGN KEY(individual_id) REFERENCES population(id)
            )
        ''')

Каждая сделка, каждое решение, даже те, что кажутся незначительными, становятся частью коллективного опыта системы. Вот как мы сохраняем данные после каждого торгового цикла:

def _save_to_db(self):
    try:
        with self.conn:
            self.conn.execute('DELETE FROM population')
            for individual in self.population:
                individual_data = {
                    'weights': {
                        'input_weights': individual.weights.input_weights.tolist(),
                        'hidden_weights': individual.weights.hidden_weights.tolist(),
                        'output_weights': individual.weights.output_weights.tolist(),
                        'hidden_bias': individual.weights.hidden_bias.tolist(),
                        'output_bias': individual.weights.output_bias.tolist()
                    },
                    'fitness': individual.fitness,
                    'total_profit': individual.total_profit,
                    'trade_history': list(individual.trade_history),
                    'market_metadata': self._get_market_conditions()
                }
                self.conn.execute(
                    'INSERT INTO population (individual) VALUES (?)', 
                    (json.dumps(individual_data),)
                )
    except Exception as e:
        logging.error(f"Error saving population: {str(e)}")

Даже после критического сбоя сервера, вся система восстановится буквально за минуты, благодаря детальным логам и резервным копиям. Вот как работает механизм восстановления:

def _load_from_db(self):
    """Загрузка популяции из базы данных"""
    try:
        cursor = self.conn.execute('SELECT individual FROM population')
        rows = cursor.fetchall()
        for row in rows:
            individual_data = json.loads(row[0])
            individual = TradingIndividual(self.input_size)
            individual.weights = GeneticWeights(**individual_data['weights'])
            individual.fitness = individual_data['fitness']
            individual.total_profit = individual_data['total_profit']
            individual.trade_history = deque(
                individual_data['trade_history'], 
                maxlen=1000
            )
            self.population.append(individual)
    except Exception as e:
        logging.error(f"Error loading population: {str(e)}")

Особое внимание мы уделим анализу исторических данных. Каждая успешная стратегия оставляет след, который может быть использован для улучшения будущих решений:

def analyze_historical_performance(self):
    """Анализ исторической эффективности"""
    query = '''
        SELECT h.*, p.individual 
        FROM history h 
        JOIN population p ON h.individual_id = p.id 
        WHERE h.generation > ? 
        ORDER BY h.generation DESC
    '''
    
    cursor = self.conn.execute(query, (self.generation - 100,))
    performance_data = cursor.fetchall()
    
    # Анализ паттернов успешных стратегий
    success_patterns = defaultdict(list)
    for record in performance_data:
        trade_data = json.loads(record[3])
        if trade_data['profit'] > 0:
            market_conditions = json.loads(record[4])
            key_pattern = self._extract_key_pattern(market_conditions)
            success_patterns[key_pattern].append(trade_data)
    
    return success_patterns

База данных ETARE — это не просто хранилище информации, а настоящий "мозг" системы, способный анализировать прошлое и прогнозировать будущее. Как говорил мой старый наставник: "Торговая система без памяти — как трейдер без опыта: каждый день начинает с нуля".


Данные и признаки

За годы работы с алгоритмической торговлей я перепробовал сотни комбинаций индикаторов. В какой-то момент моя торговая система использовала больше 50 различных показателей — от классического RSI до экзотических индикаторов собственной разработки. Но знаете, что я понял после очередного слитого депозита? Дело не в количестве, а в правильной обработке данных.

Помню случай во время Brexit: система с десятками индикаторов просто "зависла", не в силах принять решение из-за противоречивых сигналов. Именно тогда родилась идея ETARE — системы, которая использует минимально необходимый набор индикаторов, но обрабатывает их по-умному.

def prepare_features(data: pd.DataFrame) -> pd.DataFrame:
    """Подготовка признаков для анализа"""
    df = data.copy()

    # RSI - как детектор перекупленности/перепроданности
    delta = df['close'].diff()
    gain = delta.where(delta > 0, 0).rolling(14).mean()
    loss = -delta.where(delta < 0, 0).rolling(14).mean()
    rs = gain / loss
    df['rsi'] = 100 - (100 / (1 + rs))
RSI в нашей системе — это не просто индикатор перекупленности/перепроданности. Мы используем его как часть комплексного анализа рыночного настроения. Особенно эффективно он работает в комбинации с MACD:
# MACD - для определения тренда
    exp1 = df['close'].ewm(span=12, adjust=False).mean()
    exp2 = df['close'].ewm(span=26, adjust=False).mean()
    df['macd'] = exp1 - exp2
    df['macd_signal'] = df['macd'].ewm(span=9, adjust=False).mean()
    df['macd_hist'] = df['macd'] - df['macd_signal']

Полосы Боллинджера — это наш "радар" волатильности.  

# Bollinger Bands с адаптивным периодом
    volatility = df['close'].rolling(50).std()
    adaptive_period = int(20 * (1 + volatility.mean()))
    
    df['bb_middle'] = df['close'].rolling(adaptive_period).mean()
    df['bb_std'] = df['close'].rolling(adaptive_period).std()
    df['bb_upper'] = df['bb_middle'] + 2 * df['bb_std']
    df['bb_lower'] = df['bb_middle'] - 2 * df['bb_std']

Отдельная история — анализ волатильности и импульса. 

# Momentum - "температура" рынка
    df['momentum'] = df['close'] / df['close'].shift(10)
    df['momentum_ma'] = df['momentum'].rolling(20).mean()
    df['momentum_std'] = df['momentum'].rolling(20).std()
    
    # Volatility - наш "сейсмограф"
    df['atr'] = df['high'].rolling(14).max() - df['low'].rolling(14).min()
    df['price_change'] = df['close'].pct_change()
    df['price_change_abs'] = df['price_change'].abs()
    
    # Волатильность объемов
    df['volume_volatility'] = df['tick_volume'].rolling(20).std() / df['tick_volume'].rolling(20).mean()

Объемный анализ в ETARE — это не просто подсчет тиков. Мы разработали специальный алгоритм выявления аномальных объемов, который помогает предсказывать сильные движения:

# Volume analysis - "пульс" рынка
    df['volume_ma'] = df['tick_volume'].rolling(20).mean()
    df['volume_std'] = df['tick_volume'].rolling(20).std()
    df['volume_ratio'] = df['tick_volume'] / df['volume_ma']
    
    # Выявление аномальных объемов
    df['volume_spike'] = (
        df['tick_volume'] > df['volume_ma'] + 2 * df['volume_std']
    ).astype(int)
    
    # Кластерный анализ объемов
    df['volume_cluster'] = (
        df['tick_volume'].rolling(3).sum() / 
        df['tick_volume'].rolling(20).sum()
    )

Финальный штрих — нормализация данных. Это критически важный этап, который многие недооценивают.

# Нормализация с учетом рыночных фаз
    numeric_cols = df.select_dtypes(include=[np.number]).columns
    for col in numeric_cols:
        # Адаптивная нормализация
        rolling_mean = df[col].rolling(100).mean()
        rolling_std = df[col].rolling(100).std()
        df[col] = (df[col] - rolling_mean) / (rolling_std + 1e-8)
    
    # Удаление выбросов
    df = df.clip(-4, 4)  # Ограничиваем значения в диапазоне [-4, 4]
    
    return df

Каждый признак в ETARE — это не просто число, а часть сложной мозаики рыночного анализа. Система постоянно адаптируется к изменениям рынка, корректируя вес каждого индикатора в зависимости от текущей ситуации. В следующих разделах мы увидим, как эти данные превращаются в конкретные торговые решения.


Торговая логика

Представляю вам описание инновационной торговой системы, которая воплощает в себе передовые технологии алгоритмической торговли. В основе системы лежит гибридный подход, объединяющий генетическую оптимизацию, машинное обучение и продвинутое управление рисками.

Сердцем системы является непрерывно работающий торговый цикл, который постоянно анализирует рыночные условия и адаптируется к ним. Подобно природной эволюции, система периодически проводит "чистку" неэффективных торговых стратегий, давая дорогу новым, более перспективным подходам. Это происходит каждые 50 сделок, что обеспечивает постоянное совершенствование торговых алгоритмов.

Каждый торговый инструмент обрабатывается индивидуально, с учетом его уникальных характеристик. Система анализирует исторические данные за последние 100 свечей, что позволяет ей формировать точное представление о текущем состоянии рынка. На основе этого анализа принимаются взвешенные решения об открытии и закрытии позиций.

Особое внимание уделяется стратегии усреднения позиций (DCA). При открытии новых позиций, система автоматически уменьшает их объем, начиная с 0.1 лота и постепенно снижая до минимального значения в 0.01 лот. Это позволяет эффективно управлять рисками и максимизировать потенциальную прибыль.

Процесс закрытия позиций также тщательно продуман. Система отслеживает прибыльность каждой позиции и закрывает их при достижении заданного уровня прибыли. При этом позиции Buy и Sell обрабатываются раздельно, что позволяет более гибко управлять портфелем. А полученные в результате торговли награды или штрафы — являются ключом к дальнейшему успешному обучению. 

Вся информация о торговых операциях и состоянии системы сохраняется в базе данных, обеспечивая возможность детального анализа и оптимизации стратегий. Это создает прочную основу для дальнейшего совершенствования торговых алгоритмов.

    def _process_individual(self, symbol: str, individual: TradingIndividual, current_state: np.ndarray):
        """Обработка торговой логики для отдельной особи с использованием DCA и раздельного закрытия по прибыли"""
        try:
            positions = individual.open_positions.get(symbol, [])

            if not positions:  # Открытие новой позиции
                action, _ = individual.predict(current_state)
                if action in [Action.OPEN_BUY, Action.OPEN_SELL]:
                    self._open_position(symbol, individual, action)
            else:  # Управление существующими позициями
                current_price = mt5.symbol_info_tick(symbol).bid

                # Закрытие позиций по прибыли
                self._close_positions_by_profit(symbol, individual, current_price)

                # Проверка на необходимость открытия новой позиции по DCA
                if len(positions) < self.max_positions_per_pair:
                    action, _ = individual.predict(current_state)
                    if action in [Action.OPEN_BUY, Action.OPEN_SELL]:
                        self._open_dca_position(symbol, individual, action, len(positions))

        except Exception as e:
            logging.error(f"Error processing individual: {str(e)}")

    def _open_position(self, symbol: str, individual: TradingIndividual, action: Action):
        """Открытие позиции"""
        try:
            volume = 0.1
            price = mt5.symbol_info_tick(symbol).ask if action == Action.OPEN_BUY else mt5.symbol_info_tick(symbol).bid

            request = {
                "action": mt5.TRADE_ACTION_DEAL,
                "symbol": symbol,
                "volume": volume,
                "type": mt5.ORDER_TYPE_BUY if action == Action.OPEN_BUY else mt5.ORDER_TYPE_SELL,
                "price": price,
                "deviation": 20,
                "magic": 123456,
                "comment": f"Gen{self.generation}",
                "type_time": mt5.ORDER_TIME_GTC,
                "type_filling": mt5.ORDER_FILLING_FOK,
            }

            result = mt5.order_send(request)
            if result and result.retcode == mt5.TRADE_RETCODE_DONE:
                trade = Trade(symbol=symbol, action=action, volume=volume,
                              entry_price=result.price, entry_time=time.time())
                if symbol not in individual.open_positions:
                    individual.open_positions[symbol] = []
                individual.open_positions[symbol].append(trade)

        except Exception as e:
            logging.error(f"Error opening position: {str(e)}")

    def _open_dca_position(self, symbol: str, individual: TradingIndividual, action: Action, position_count: int):
        """Открытие позиции с использованием стратегии DCA"""
        try:
            # Базовый объем
            base_volume = 0.1  # Начальный объем в лотах
            # Уменьшаем объем на 0.01 лота для каждой следующей позиции
            volume = max(0.01, base_volume - (position_count * 0.01))  # Минимальный объем 0.01 лота
            price = mt5.symbol_info_tick(symbol).ask if action == Action.OPEN_BUY else mt5.symbol_info_tick(symbol).bid

            request = {
                "action": mt5.TRADE_ACTION_DEAL,
                "symbol": symbol,
                "volume": volume,
                "type": mt5.ORDER_TYPE_BUY if action == Action.OPEN_BUY else mt5.ORDER_TYPE_SELL,
                "price": price,
                "deviation": 20,
                "magic": 123456,
                "comment": f"Gen{self.generation} DCA",
                "type_time": mt5.ORDER_TIME_GTC,
                "type_filling": mt5.ORDER_FILLING_FOK,
            }

            result = mt5.order_send(request)
            if result and result.retcode == mt5.TRADE_RETCODE_DONE:
                trade = Trade(symbol=symbol, action=action, volume=volume,
                              entry_price=result.price, entry_time=time.time())
                if symbol not in individual.open_positions:
                    individual.open_positions[symbol] = []
                individual.open_positions[symbol].append(trade)

        except Exception as e:
            logging.error(f"Error opening DCA position: {str(e)}")

    def _close_positions_by_profit(self, symbol: str, individual: TradingIndividual, current_price: float):
        """Закрытие позиций по прибыли раздельно для Buy и Sell"""
        try:
            positions = individual.open_positions.get(symbol, [])
            buy_positions = [pos for pos in positions if pos.action == Action.OPEN_BUY]
            sell_positions = [pos for pos in positions if pos.action == Action.OPEN_SELL]

            # Закрытие Buy позиций
            for position in buy_positions:
                profit = calculate_profit(position, current_price)
                if profit >= self.min_profit_pips:
                    self._close_position(symbol, individual, position)

            # Закрытие Sell позиций
            for position in sell_positions:
                profit = calculate_profit(position, current_price)
                if profit >= self.min_profit_pips:
                    self._close_position(symbol, individual, position)

        except Exception as e:
            logging.error(f"Error closing positions by profit: {str(e)}")

    def _close_position(self, symbol: str, individual: TradingIndividual, position: Trade):
        """Закрытие позиции с обновлением модели"""
        try:
            close_type = mt5.ORDER_TYPE_SELL if position.action == Action.OPEN_BUY else mt5.ORDER_TYPE_BUY
            price = mt5.symbol_info_tick(symbol).bid if close_type == mt5.ORDER_TYPE_SELL else mt5.symbol_info_tick(symbol).ask

            request = {
                "action": mt5.TRADE_ACTION_DEAL,
                "symbol": symbol,
                "volume": position.volume,
                "type": close_type,
                "price": price,
                "deviation": 20,
                "magic": 123456,
                "comment": "Close",
                "type_time": mt5.ORDER_TIME_GTC,
                "type_filling": mt5.ORDER_FILLING_FOK,
            }

            result = mt5.order_send(request)
            if result and result.retcode == mt5.TRADE_RETCODE_DONE:
                position.is_open = False
                position.exit_price = result.price
                position.exit_time = time.time()
                position.profit = calculate_profit(position, result.price)
                
                # Формируем данные для обучения
                trade_data = {
                    'symbol': symbol,
                    'action': position.action,
                    'entry_price': position.entry_price,
                    'exit_price': position.exit_price,
                    'volume': position.volume,
                    'profit': position.profit,
                    'holding_time': position.exit_time - position.entry_time
                }
                
                # Обновляем модель новыми данными
                individual.model.update(trade_data)
                
                # Сохраняем историю и обновляем открытые позиции
                individual.trade_history.append(position)
                individual.open_positions[symbol].remove(position)
                
                # Логируем результаты обучения
                logging.info(f"Model updated with trade data: {trade_data}")

        except Exception as e:
            logging.error(f"Error closing position: {str(e)}")

def main():
    symbols = ['EURUSD.ecn', 'GBPUSD.ecn', 'USDJPY.ecn', 'AUDUSD.ecn']
    trader = HybridTrader(symbols)
    trader.run_trading_cycle()

if __name__ == "__main__":
    main()

В результате мы получаем надежную, самообучающуюся торговую систему, способную эффективно работать в различных рыночных условиях. Сочетание эволюционных алгоритмов, машинного обучения и проверенных торговых стратегий делает её мощным инструментом для современного трейдинга.


Заключение

В заключение хочу подчеркнуть, что ETARE представляет собой не просто очередной торговый алгоритм, а результат многолетней эволюции алгоритмической торговли. Система объединяет в себе лучшие практики из различных областей: генетические алгоритмы для адаптации к меняющимся условиям рынка, глубокое обучение для принятия решений, и классические методы управления рисками.

Уникальность ETARE заключается в её способности непрерывно учиться на собственном опыте. Каждая сделка, независимо от результата, становится частью коллективной памяти системы, помогая совершенствовать будущие торговые решения. Механизм естественного отбора торговых стратегий, вдохновленный теорией эволюции Дарвина, обеспечивает выживание только самых эффективных подходов.

За время разработки и тестирования система доказала свою устойчивость в различных рыночных условиях — от спокойных трендовых движений до высоковолатильных периодов. Особенно важно отметить эффективность стратегии DCA и механизма раздельного закрытия позиций, которые позволяют максимизировать прибыль при контролируемом уровне риска.

Теперь о том, что касается эффективности. Скажу сразу и прямо: сам основной модуль ETARE у меня не торгует. Он вшит, как модуль, в более широкую торговую экосистему "Мидас".

В данный момент в Мидасе 24 модуля, включая этот. Сложность будет расти по нарастающей, и очень многое я буду описывать в будущих статьях. 


Будущее алгоритмической торговли лежит именно в таких адаптивных системах, способных эволюционировать вместе с рынком. ETARE – это шаг в этом направлении, демонстрирующий, как современные технологии могут быть применены для создания надежных и прибыльных торговых решений.

Прикрепленные файлы |
ETARE_module.py (34.24 KB)
От начального до среднего уровня: Операторы WHILE и DO WHILE От начального до среднего уровня: Операторы WHILE и DO WHILE
В этой статье мы практически и весьма наглядно рассмотрим первый оператор цикла. Несмотря на то, что многие новички испытывают страх, сталкиваясь с необходимостью создания циклов, знание того, как это делать правильно и безопасно, может прийти только с опытом и практикой. Но кто знает, возможно, я смогу уменьшить ваши трудности и страдания, показав основные проблемы и меры предосторожности, которые следует соблюдать при использовании циклов в коде.
Разработка системы репликации (Часть 62): Нажатие кнопки воспроизведения в сервисе (III) Разработка системы репликации (Часть 62): Нажатие кнопки воспроизведения в сервисе (III)
В данной статье мы начнем решать проблему переизбытка тиков, которые могут влиять на работу приложения при использовании реальных данных. Данный переизбыток часто мешает правильному отсчету времени, необходимому для построения минутного бара в соответствующем окне.
Добавляем пользовательскую LLM в торгового робота (Часть 5): Разработка и тестирование торговой стратегии с помощью LLM (I) - Тонкая настройка Добавляем пользовательскую LLM в торгового робота (Часть 5): Разработка и тестирование торговой стратегии с помощью LLM (I) - Тонкая настройка
Языковые модели (LLM) являются важной частью быстро развивающегося искусственного интеллекта, поэтому нам следует подумать о том, как интегрировать мощные LLM в нашу алгоритмическую торговлю. Большинству людей сложно настроить эти модели в соответствии со своими потребностями, развернуть их локально, а затем применить к алгоритмической торговле. В этой серии статей будет рассмотрен пошаговый подход к достижению этой цели.
Нейросети в трейдинге: Иерархический двухбашенный трансформер (Hidformer) Нейросети в трейдинге: Иерархический двухбашенный трансформер (Hidformer)
Предлагаем познакомиться с фреймворком иерархического двухбашенного трансформера (Hidformer), который был разработан для прогнозирования временных рядов и анализа данных. Авторы фреймворка предложили несколько улучшений к архитектуре Transformer, что позволило повысить точность прогнозов и снизить потребление вычислительных ресурсов.