preview
Угловой анализ ценовых движений: гибридная модель прогнозирования финансовых рынков

Угловой анализ ценовых движений: гибридная модель прогнозирования финансовых рынков

MetaTrader 5Интеграция | 17 апреля 2025, 09:14
723 5
Yevgeniy Koshtenko
Yevgeniy Koshtenko

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

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

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



Анатомия ценового движения

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

Взгляните на обычный график EURUSD. Что вы видите? Линии и бары? А теперь представьте, что каждый сегмент между двумя последовательными точками образует определённый угол с горизонтальной осью. Этот угол имеет точное математическое значение. Положительный угол означает восходящее движение, отрицательный — нисходящее. Чем больше угол, тем круче движение цены.

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

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



Угловой анализ Ганна: от классики к инновациям

Идея использовать углы для анализа ценовых движений не нова. Её истоки уходят корнями в работы легендарного трейдера и аналитика Уильяма Делберта Ганна, который еще в начале XX века предложил свою систему углового анализа финансовых рынков, индикатор по ней мы создавали вот тут.

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

Однако, классический подход Ганна имел два существенных недостатка. Во-первых, он был слишком субъективным — разные аналитики могли интерпретировать одни и те же угловые построения совершенно по-разному. Во-вторых, его система была разработана для бумажных графиков с определенным масштабом, что делало ее применение в современном цифровом анализе проблематичным.

Меня не оставляла мысль: что если Ганн был прав в своей основной концепции, но просто не имел доступа к современным вычислительным инструментам? Что если его интуиция о значимости углов верна, но требует другого, более строгого математического подхода?

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

И тут меня осенило: ценовые движения на рынке — это тоже своего рода траектории, результат "столкновения" рыночных сил! Что если вместо субъективного проведения линий Ганна, мы будем точно измерять угол между каждыми двумя последовательными точками ценового графика? Что если мы превратим это в строгий математический анализ с применением машинного обучения?

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

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

В отличие от Ганна, который основывал свой анализ на геометрических построениях и интуиции, я решил довериться объективным математическим методам и алгоритмам машинного обучения. Вместо того, чтобы искать "магические" углы в 45° или 26.25° (любимые углы Ганна), мы позволили алгоритму самому определить, какие угловые паттерны наиболее значимы для предсказания будущих движений.

Что интересно, анализ результатов показал, что некоторые из выявленных алгоритмом закономерностей действительно перекликаются с наблюдениями Ганна, но при этом обретают строгую математическую форму и статистическое подтверждение. Например, Ганн придавал особое значение линии 1:1 (45°), и наша модель также выявила, что смена знака угла с околонулевых значений на положительные значения, близкие к 45°, часто предшествует сильному направленному движению.

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

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



Танец углов

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

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

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

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



От идеи к коду: создаем угловой анализатор

Теория — хорошо, но без практического воплощения она остается лишь красивыми словами. Для начала нам нужно было получить рыночные данные и научиться с ними работать. В качестве инструмента мы выбрали Python и библиотеку MetaTrader 5, которая позволяет напрямую получать данные из торгового терминала.

Вот код, который загружает историю котировок:

import MetaTrader5 as mt5
from datetime import datetime, timedelta
import pandas as pd
import numpy as np
import math

def get_mt5_data(symbol='EURUSD', timeframe=mt5.TIMEFRAME_M5, days=60):
    if not mt5.initialize():
        print(f"Ошибка инициализации MT5: {mt5.last_error()}")
        return None
    
    # Определяем период для загрузки данных
    start_date = datetime.now() - timedelta(days=days)
    rates = mt5.copy_rates_range(symbol, timeframe, start_date, datetime.now())
    mt5.shutdown()
    
    # Преобразуем данные в удобный формат
    df = pd.DataFrame(rates)
    df['time'] = pd.to_datetime(df['time'], unit='s')
    return df

Этот небольшой фрагмент кода — ваш билет в мир рыночных данных. Он подключается к терминалу MetaTrader 5, загружает историю котировок за указанное количество дней и преобразует её в удобный для анализа формат.

Теперь нам нужно рассчитать углы между последовательными точками. Но здесь возникает проблема: как корректно измерить угол на графике, где ось времени и ось цены имеют совершенно разные масштабы? Если просто использовать координаты точек как есть, углы будут бессмысленными.

Решение — нормализация осей. Мы должны привести временную и ценовую шкалы к сопоставимым масштабам:

def calculate_angle(p1, p2):
    # p1 и p2 - кортежи (время_нормализованное, цена)
    x1, y1 = p1
    x2, y2 = p2
    
    # Обработка вертикальных линий
    if x2 - x1 == 0:
        return 90 if y2 > y1 else -90
    
    # Расчет угла в радианах и преобразование в градусы
    angle_rad = math.atan2(y2 - y1, x2 - x1)
    angle_deg = math.degrees(angle_rad)
    
    return angle_deg

def create_angular_features(df):
    # Создаем копию DataFrame
    angular_df = df.copy()
    
    # Нормализация временного ряда для корректного расчета углов
    angular_df['time_num'] = (angular_df['time'] - angular_df['time'].min()).dt.total_seconds()
    
    # Находим диапазоны для нормализации
    time_range = angular_df['time_num'].max() - angular_df['time_num'].min()
    price_range = angular_df['close'].max() - angular_df['close'].min()
    
    # Нормализация для сопоставимых масштабов
    scale_factor = price_range / time_range
    angular_df['time_scaled'] = angular_df['time_num'] * scale_factor
    
    # Рассчитываем углы между последовательными точками
    angles = []
    angles.append(np.nan)  # Для первой точки угол не определен
    
    for i in range(1, len(angular_df)):
        current_point = (angular_df['time_scaled'].iloc[i], angular_df['close'].iloc[i])
        prev_point = (angular_df['time_scaled'].iloc[i-1], angular_df['close'].iloc[i-1])
        angle = calculate_angle(prev_point, current_point)
        angles.append(angle)
    
    angular_df['angle'] = angles
    
    return angular_df

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

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

def add_future_price_info(angular_df, prediction_period=24):
    # Добавляем будущее направление цены
    future_directions = []
    for i in range(len(angular_df)):
        if i + prediction_period < len(angular_df):
            # 1 = рост, 0 = падение
            future_dir = 1 if angular_df['close'].iloc[i + prediction_period] > angular_df['close'].iloc[i] else 0
            future_directions.append(future_dir)
        else:
            future_directions.append(np.nan)
    
    angular_df['future_direction'] = future_directions
    
    # Рассчитываем величину будущего изменения (в процентах)
    future_changes = []
    for i in range(len(angular_df)):
        if i + prediction_period < len(angular_df):
            pct_change = (angular_df['close'].iloc[i + prediction_period] - angular_df['close'].iloc[i]) / angular_df['close'].iloc[i] * 100
            future_changes.append(pct_change)
        else:
            future_changes.append(np.nan)
    
    angular_df['future_change_pct'] = future_changes
    
    return angular_df

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

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

def prepare_features(angular_df, lookback=15):
    features = []
    targets_class = []  # Для классификации (направление)
    targets_reg = []    # Для регрессии (процентное изменение)
    
    # Отбрасываем строки с NaN
    filtered_df = angular_df.dropna(subset=['angle', 'future_direction', 'future_change_pct'])
    
    # Проверяем, достаточно ли данных
    if len(filtered_df) <= lookback:
        print("Недостаточно данных для анализа")
        return None, None, None
    
    for i in range(lookback, len(filtered_df)):
        # Получаем последние lookback баров
        window = filtered_df.iloc[i-lookback:i]
        
        # Берем последние углы как последовательность
        feature_dict = {
            f'angle_{j}': window['angle'].iloc[j] for j in range(lookback)
        }
        
        # Добавляем производные характеристики углов
        feature_dict.update({
            'angle_mean': window['angle'].mean(),
            'angle_std': window['angle'].std(),
            'angle_min': window['angle'].min(),
            'angle_max': window['angle'].max(),
            'angle_last': window['angle'].iloc[-1],
            'angle_last_3_mean': window['angle'].iloc[-3:].mean(),
            'angle_last_5_mean': window['angle'].iloc[-5:].mean(),
            'angle_last_10_mean': window['angle'].iloc[-10:].mean(),
            'positive_angles_ratio': (window['angle'] > 0).mean(),
            'current_price': window['close'].iloc[-1],
            'price_std': window['close'].std(),
            'price_change_pct': (window['close'].iloc[-1] - window['close'].iloc[0]) / window['close'].iloc[0] * 100,
            'high_low_range': (window['high'].max() - window['low'].min()) / window['close'].iloc[-1] * 100,
            'last_tick_volume': window['tick_volume'].iloc[-1],
            'avg_tick_volume': window['tick_volume'].mean(),
            'tick_volume_ratio': window['tick_volume'].iloc[-1] / window['tick_volume'].mean() if window['tick_volume'].mean() > 0 else 1,
        })
        
        features.append(feature_dict)
        targets_class.append(filtered_df.iloc[i]['future_direction'])
        targets_reg.append(filtered_df.iloc[i]['future_change_pct'])
    
    return pd.DataFrame(features), np.array(targets_class), np.array(targets_reg)

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



Машинное обучение раскрывает секреты углов

Теперь, когда у нас есть данные и признаки, пришло время обучить модели, которые будут искать в них закономерности. Мы решили использовать библиотеку CatBoost — современный алгоритм градиентного бустинга, который особенно хорошо работает с временными рядами.

Особенность нашего подхода в том, что мы обучаем не одну, а две модели:

from catboost import CatBoostClassifier, CatBoostRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, mean_squared_error

def train_hybrid_model(X, y_class, y_reg, test_size=0.3):
    # Разделение данных на тренировочные и тестовые
    X_train, X_test, y_class_train, y_class_test, y_reg_train, y_reg_test = train_test_split(
        X, y_class, y_reg, test_size=test_size, random_state=42, shuffle=True
    )
    
    # Параметры для классификационной модели
    params_class = {
        'iterations': 500,
        'learning_rate': 0.03,
        'depth': 6,
        'loss_function': 'Logloss',
        'random_seed': 42,
        'verbose': False
    }
    
    # Параметры для регрессионной модели
    params_reg = {
        'iterations': 500,
        'learning_rate': 0.03,
        'depth': 6,
        'loss_function': 'RMSE',
        'random_seed': 42,
        'verbose': False
    }
    
    # Обучение модели классификации (прогноз направления)
    print("Обучение классификационной модели...")
    model_class = CatBoostClassifier(**params_class)
    model_class.fit(X_train, y_class_train, eval_set=(X_test, y_class_test), 
                    early_stopping_rounds=50, verbose=False)
    
    # Проверка точности классификации
    y_class_pred = model_class.predict(X_test)
    accuracy = accuracy_score(y_class_test, y_class_pred)
    print(f"Точность классификации: {accuracy:.4f} ({accuracy*100:.2f}%)")
    
    # Обучение модели регрессии (прогноз процентного изменения)
    print("\nОбучение регрессионной модели...")
    model_reg = CatBoostRegressor(**params_reg)
    model_reg.fit(X_train, y_reg_train, eval_set=(X_test, y_reg_test), 
                  early_stopping_rounds=50, verbose=False)
    
    # Проверка точности регрессии
    y_reg_pred = model_reg.predict(X_test)
    rmse = np.sqrt(mean_squared_error(y_reg_test, y_reg_pred))
    print(f"RMSE регрессии: {rmse:.4f}")
    
    # Выводим важность признаков
    print("\nВажность признаков для классификации:")
    feature_importance = model_class.get_feature_importance(prettified=True)
    print(feature_importance.head(5))
    
    return model_class, model_reg

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

После обучения, мы можем использовать эти модели для прогнозирования в реальном времени:

def predict_future_movement(model_class, model_reg, angular_df, lookback=15):
    # Получаем последние данные
    if len(angular_df) < lookback:
        print("Недостаточно данных для прогноза")
        return None
    
    # Получаем последние lookback баров
    last_window = angular_df.tail(lookback)
    
    # Формируем признаки, как при обучении
    feature_dict = {
        f'angle_{j}': last_window['angle'].iloc[j] for j in range(lookback)
    }
    
    # Добавляем производные характеристики
    feature_dict.update({
        'angle_mean': last_window['angle'].mean(),
        'angle_std': last_window['angle'].std(),
        'angle_min': last_window['angle'].min(),
        'angle_max': last_window['angle'].max(),
        'angle_last': last_window['angle'].iloc[-1],
        'angle_last_3_mean': last_window['angle'].iloc[-3:].mean(),
        'angle_last_5_mean': last_window['angle'].iloc[-5:].mean(),
        'angle_last_10_mean': last_window['angle'].iloc[-10:].mean(),
        'positive_angles_ratio': (last_window['angle'] > 0).mean(),
        'current_price': last_window['close'].iloc[-1],
        'price_std': last_window['close'].std(),
        'price_change_pct': (last_window['close'].iloc[-1] - last_window['close'].iloc[0]) / last_window['close'].iloc[0] * 100,
        'high_low_range': (last_window['high'].max() - last_window['low'].min()) / last_window['close'].iloc[-1] * 100,
        'last_tick_volume': last_window['tick_volume'].iloc[-1],
        'avg_tick_volume': last_window['tick_volume'].mean(),
        'tick_volume_ratio': last_window['tick_volume'].iloc[-1] / last_window['tick_volume'].mean() if last_window['tick_volume'].mean() > 0 else 1,
    })
    
    # Преобразуем в формат для модели
    X_pred = pd.DataFrame([feature_dict])
    
    # Прогнозы моделей
    direction_proba = model_class.predict_proba(X_pred)[0]
    direction = model_class.predict(X_pred)[0]
    change_pct = model_reg.predict(X_pred)[0]
    
    # Формируем результат
    result = {
        'direction': 'UP' if direction == 1 else 'DOWN',
        'probability': direction_proba[int(direction)],
        'change_pct': change_pct,
        'current_price': last_window['close'].iloc[-1],
        'predicted_price': last_window['close'].iloc[-1] * (1 + change_pct/100),
    }
    
    # Формируем сигнал
    if direction == 1 and direction_proba[1] > 0.7 and change_pct > 0.5:
        result['signal'] = 'STRONG_BUY'
    elif direction == 1 and direction_proba[1] > 0.6:
        result['signal'] = 'BUY'
    elif direction == 0 and direction_proba[0] > 0.7 and change_pct < -0.5:
        result['signal'] = 'STRONG_SELL'
    elif direction == 0 and direction_proba[0] > 0.6:
        result['signal'] = 'SELL'
    else:
        result['signal'] = 'NEUTRAL'
    
    return result

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



Проверка боем: тестирование стратегии

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

def backtest_strategy(angular_df, model_class, model_reg, lookback=15):
    # Фильтруем данные
    clean_df = angular_df.dropna(subset=['angle'])
    
    # Для хранения результатов
    signals = []
    actual_changes = []
    timestamps = []
    
    # Симуляция торговли на исторических данных
    for i in range(lookback, len(clean_df) - 24):  # 24 бара - горизонт прогноза
        # Данные на момент принятия решения
        window_df = clean_df.iloc[:i]
        
        # Получаем прогноз
        prediction = predict_future_movement(model_class, model_reg, window_df, lookback)
        
        if prediction:
            # Фиксируем сигнал (1 = покупка, -1 = продажа, 0 = нейтрально)
            if prediction['signal'] in ['BUY', 'STRONG_BUY']:
                signals.append(1)
            elif prediction['signal'] in ['SELL', 'STRONG_SELL']:
                signals.append(-1)
            else:
                signals.append(0)
            
            # Фиксируем фактическое изменение
            actual_change = (clean_df.iloc[i+24]['close'] - clean_df.iloc[i]['close']) / clean_df.iloc[i]['close'] * 100
            actual_changes.append(actual_change)
            
            # Фиксируем время
            timestamps.append(clean_df.iloc[i]['time'])
    
    # Анализ результатов
    signals = np.array(signals)
    actual_changes = np.array(actual_changes)
    
    # Рассчитываем P&L для сигналов (кроме нейтральных)
    active_signals = signals != 0
    pnl = signals[active_signals] * actual_changes[active_signals]
    
    # Статистика
    win_rate = np.sum(pnl > 0) / len(pnl)
    avg_win = np.mean(pnl[pnl > 0]) if np.any(pnl > 0) else 0
    avg_loss = np.mean(pnl[pnl < 0]) if np.any(pnl < 0) else 0
    profit_factor = abs(np.sum(pnl[pnl > 0]) / np.sum(pnl[pnl < 0])) if np.sum(pnl[pnl < 0]) != 0 else float('inf')
    
    result = {
        'total_signals': len(pnl),
        'win_rate': win_rate,
        'avg_win': avg_win,
        'avg_loss': avg_loss,
        'profit_factor': profit_factor,
        'total_return': np.sum(pnl)
    }
    
    return result


Результаты, которые говорят сами за себя


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

Особенно интересным оказался анализ важности признаков. Вот топ-5 факторов, которые больше всего влияли на прогноз:

  1. angle_last — последний угол перед прогнозируемой точкой
  2. angle_last_3_mean — среднее значение трёх последних углов
  3. positive_angles_ratio — соотношение положительных и отрицательных углов
  4. angle_std — стандартное отклонение углов
  5. angle_max — максимальный угол в последовательности

Это подтвердило нашу гипотезу: углы действительно содержат в себе предиктивную информацию о будущем движении цены. Особенно важны самые последние углы — они как последние ноты перед кульминацией музыкального произведения, по которым опытный слушатель может предугадать финал.

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

  1. В периоды направленного движения (трендов) точность прогнозов достигала 75%.
  2. Наиболее надежные сигналы возникали после серии однонаправленных углов, за которыми следовало резкое изменение угла в противоположную сторону.
  3. Система особенно хорошо предсказывала развороты после сильных импульсных движений.

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



Как это работает в реальности

Типичный угловой сигнал формируется не за один бар. Это последовательность углов, которая создает определенный паттерн. Например, перед сильным восходящим движением мы часто видим следующее: серия углов колеблется около нуля (горизонтальное движение), затем появляются 2-3 небольших отрицательных угла (небольшое снижение), а после — резкий положительный угол, за которым следуют еще несколько положительных с возрастающей амплитудой.

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

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



Обучение: сложный путь к пониманию

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

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

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

Затем мы сопоставляли эти характеристики с будущим движением цены через 24 бара. Это было похоже на составление огромного словаря, где каждой угловой комбинации соответствовало определенное движение рынка в будущем.

Обучение заняло месяцы. Модель переваривала гигабайты данных, учась распознавать тончайшие нюансы угловых последовательностей. Но результат стоил потраченного времени. Мы получили инструмент, способный "слышать" рынок так, как не способен ни один трейдер-человек.



Философия углового анализа

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

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

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

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



Заключение: новый взгляд на рынок

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

Мы не претендуем на создание безупречного индикатора. Такого не существует. Но мы предлагаем взглянуть на графики под новым углом — буквально и фигурально. Увидеть в них не просто линии и бары, а геометрический код, который можно расшифровать с помощью современных технологий.

Торговля всегда была и остается игрой вероятностей. Но чем больше у вас инструментов для анализа этих вероятностей, тем лучше ваши шансы. Угловой анализ — один из таких инструментов, возможно, самый недооцененный в современном техническом анализе.

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

Прикрепленные файлы |
AnglesModel.py (18.57 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (5)
Maxim Kuznetsov
Maxim Kuznetsov | 17 апр. 2025 в 12:32

3 публикации подряд, я поражаюсь, "у дураков мысли схожи" :-)

делаем примерно одно и то-же, одновременно и независимо 

схожие разборки с углами, но только лошадь впереди телеги (единственное что предсказывается - личный баланс, котировки не волнуют): 

про Гана скриншотить не буду, но по моему убеждению - там всё неплохо и там угол=типичная волатильность естественных циклов.
интуитивно,по личному опыту и обладая тем что под рукой, Ган вывел то что вывел. зато объективнее чем MACD :-)

Aliaksandr Kazunka
Aliaksandr Kazunka | 17 апр. 2025 в 18:07
а 24 бара это просто пример?
Aliaksandr Kazunka
Aliaksandr Kazunka | 22 апр. 2025 в 16:49

И еще вопрос, если сможете ответить.

При выгрузке результатов в ONNX и реализации советника возникла проблема. При передаче данных с размерностью {1,31} в первую классификационную модель проблем нет, получаю значения

2025.04.22 19:47:28.268 test_gann (ORDIUSDT,M5) directionUpDn = 1   directionStrength=0.44935011863708496


, а вот при передаче этих же данных во вторую модель получаю все время ошибку: ONNX: parameter is empty, inspect code '›°‚˜ :àh½5E' (705:10). Ни один из передаваемых параметров не равен 0. 

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 0, input_matrix[0][i] = -12.92599868774414

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 1, input_matrix[0][i] = -12.92599868774414

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 2, input_matrix[0][i] = -42.55295181274414

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 3, input_matrix[0][i] = 72.71257781982422

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 4, input_matrix[0][i] = 74.29901123046875

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 5, input_matrix[0][i] = -61.42539596557617

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 6, input_matrix[0][i] = 56.164878845214844

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 7, input_matrix[0][i] = -80.11347198486328

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 8, input_matrix[0][i] = 79.91580200195312

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 9, input_matrix[0][i] = -48.93017578125

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 10, input_matrix[0][i] = 80.48663330078125

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 11, input_matrix[0][i] = -79.71015930175781

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 12, input_matrix[0][i] = -45.92404556274414

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 13, input_matrix[0][i] = -82.36412048339844

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 14, input_matrix[0][i] = -56.164878845214844

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 15, input_matrix[0][i] = -10.630552291870117

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 16, input_matrix[0][i] = 62.323272705078125

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 17, input_matrix[0][i] = 13.0

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 18, input_matrix[0][i] = 10.0

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 19, input_matrix[0][i] = -12.92599868774414

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 20, input_matrix[0][i] = -61.48434829711914

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 21, input_matrix[0][i] = -36.735313415527344

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 22, input_matrix[0][i] = -23.80649185180664

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 23, input_matrix[0][i] = 0.3333333432674408

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 24, input_matrix[0][i] = 6.955999851226807

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 25, input_matrix[0][i] = 0.029581977054476738

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 26, input_matrix[0][i] = -0.5281187295913696

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 27, input_matrix[0][i] = 0.4025301933288574

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 28, input_matrix[0][i] = 420.0

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 29, input_matrix[0][i] = 641.6666870117188

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) i = 30, input_matrix[0][i] = 0.6545454263687134

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) ONNX: parameter is empty, inspect code '›°‚˜ :àh½5E' (705:10)

2025.04.22 19:39:38.482 test_gann (ORDIUSDT,M5) Ошибка выполнения: 5805

Может поможете с ошибкой (просторы интернета не помогли)




Aliaksandr Kazunka
Aliaksandr Kazunka | 22 апр. 2025 в 16:52

в нетроне сама модель отображается нормально

Aliaksandr Kazunka
Aliaksandr Kazunka | 16 мая 2025 в 19:16

Метрики для bar назад = 60, вперед = 30

 

Train Accuracy: 0.9200 | Test Accuracy: 0.8713 | GAP: 0.0486

Train F1-score: 0.9187 | Test F1-score: 0.8682 | GAP: 0.0505


На коротких дистанциях CatBoost ничего хорошего не дает, модель переобучается

Самооптимизирующийся советник на языках MQL5 и Python (Часть IV): Стекинг моделей Самооптимизирующийся советник на языках MQL5 и Python (Часть IV): Стекинг моделей
В статье мы продемонстрируем, как можно создавать торговые приложения на базе ИИ, способные учиться на собственных ошибках. Мы рассмотрим технику, известную как стекинг (stacking), при которой мы используем 2 модели для создания 1 прогноза. Первая модель, как правило, является более слабым обучающимся алгоритмом, а вторая - более мощной моделью, которая обучается на результатах более слабого алгоритма. Наша цель — создать ансамбль моделей, чтобы достичь более высокой точности.
Создание торговой панели администратора на MQL5 (Часть III): Улучшение графического интерфейса пользователя (GUI) с помощью визуального оформления (I) Создание торговой панели администратора на MQL5 (Часть III): Улучшение графического интерфейса пользователя (GUI) с помощью визуального оформления (I)
В настоящей статье мы сосредоточимся на визуальном оформлении графического интерфейса пользователя (GUI) нашей торговой панели администратора с использованием MQL5. Мы рассмотрим различные методы и функции, доступные в MQL5, которые позволяют настраивать и оптимизировать интерфейс, обеспечивая его соответствие потребностям трейдеров при сохранении привлекательной эстетики.
Нейросети в трейдинге: Актер—Режиссёр—Критик (Окончание) Нейросети в трейдинге: Актер—Режиссёр—Критик (Окончание)
Фреймворк Actor–Director–Critic — это эволюция классической архитектуры агентного обучения. В статье представлен практический опыт его реализации и адаптации к условиям финансовых рынков.
Как опередить любой рынок (Часть IV): Индексы волатильности евро и золота CBOE Как опередить любой рынок (Часть IV): Индексы волатильности евро и золота CBOE
Мы проанализируем альтернативные данные, собранные Чикагской опционной биржей (Chicago Board of Options Exchange, CBOE), чтобы повысить точность наших глубоких нейронных сетей при прогнозировании символа XAUEUR.