Создание и тестирование стратегии на основе психологических уровней.

Нам часто говорят, что определенный уровень поддержки или сопротивления является психологическим. Обычно это круглое число или номинальное число, такое как 1,0000 для USDCHF или 100 долларов США для Apple. Вопрос в том, если мы подойдем к психологическим уровням, стоит ли открывать сделки?

Я только что опубликовал новую книгу после успеха Новые технические индикаторы в Python. Он содержит более полное описание и добавление сложных торговых стратегий со страницей Github, посвященной постоянно обновляемому коду. Если вы считаете, что это вас заинтересует, не стесняйтесь перейти по приведенной ниже ссылке или, если вы предпочитаете купить версию в формате PDF, вы можете связаться со мной в Linkedin.



Психологические уровни

Психологические уровни играют важную роль в любом анализе. Причина этого в том, что мысленно им уделяется больше внимания, чем другим уровням. Например, какую цену вы бы больше запомнили, если бы столкнулись с ней? 1.1500 по EURUSD или 1.3279 по GBPUSD?

Ясно, что круглые числа - первая часть психологической цены. Другая часть - просто значение. Например, номинальная стоимость или уровень номинала, такой как 1.000 по USDCHF или 100.00 по USDJPY, считается номинальным уровнем. Основная идея состоит в том, что вокруг этих уровней участники рынка могут разместить свои ордера, следовательно, может произойти определенная реакция.

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

Создание и тестирование стратегии

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

Когда мы берем значения H3 на EURUSD и применяем алгоритм, показанный ниже, мы можем получить следующие сигналы, как показано на графике:

Каждый раз, когда рыночная цена, округленная до 4 знаков после запятой, закрывается на психологическом уровне, алгоритм генерирует сигнал. Чтобы узнать, является ли сигнал бычьим или медвежьим, алгоритм сравнивает значение последнего n-периода (по умолчанию 20) и принимает решение. Если текущая цена меньше, чем цена 20 периодов назад, то сигнал является бычьим, поскольку предполагается, что рыночная цена находится на уровне поддержки. Если текущая цена выше, чем цена 20 периодов назад, то сигнал является медвежьим, поскольку предполагается, что рыночная цена находится на уровне сопротивления.

def psychological_levels_scanner(Data, trend, signal, buy, sell):
    
    # Adding buy and sell columns
    Data = adder(Data, 15)
    
    # Rounding for ease of use
    Data = rounding(Data, 4)
    
    # Scanning for Psychological Levels
    for i in range(len(Data)):
        
        if  Data[i, 3] == 0.6000 or Data[i, 3] == 0.6100 or Data[i, 3] == 0.6200 or Data[i, 3] == 0.6300  or \
            Data[i, 3] == 0.6400 or Data[i, 3] == 0.6500 or Data[i, 3] == 0.6600 or Data[i, 3] == 0.6700 or \
            Data[i, 3] == 0.6800 or Data[i, 3] == 0.6900 or Data[i, 3] == 0.7000 or Data[i, 3] == 0.7100 or \
            Data[i, 3] == 0.7200 or Data[i, 3] == 0.7300 or Data[i, 3] == 0.7400 or Data[i, 3] == 0.7500 or \
            Data[i, 3] == 0.7600 or Data[i, 3] == 0.7700 or Data[i, 3] == 0.7800 or Data[i, 3] == 0.7900 or \
            Data[i, 3] == 0.8000 or Data[i, 3] == 0.8100 or Data[i, 3] == 0.8200 or Data[i, 3] == 0.8300 or \
            Data[i, 3] == 0.8400 or Data[i, 3] == 0.8500 or Data[i, 3] == 0.8600 or Data[i, 3] == 0.8700 or \
            Data[i, 3] == 0.8800 or Data[i, 3] == 0.8900 or Data[i, 3] == 0.9000 or Data[i, 3] == 0.9100 or \
            Data[i, 3] == 0.9200 or Data[i, 3] == 0.9300 or Data[i, 3] == 0.9400 or Data[i, 3] == 0.9500 or \
            Data[i, 3] == 0.9600 or Data[i, 3] == 0.9700 or Data[i, 3] == 0.9800 or Data[i, 3] == 0.9900 or \
            Data[i, 3] == 1.0000 or Data[i, 3] == 1.0100 or Data[i, 3] == 1.0200 or Data[i, 3] == 1.0300 or \
            Data[i, 3] == 1.0400 or Data[i, 3] == 1.0500 or Data[i, 3] == 1.0600 or Data[i, 3] == 1.0700 or \
            Data[i, 3] == 1.0800 or Data[i, 3] == 1.0900 or Data[i, 3] == 1.1000 or Data[i, 3] == 1.1100 or \
            Data[i, 3] == 1.1200 or Data[i, 3] == 1.1300 or Data[i, 3] == 1.1400 or Data[i, 3] == 1.1500 or \
            Data[i, 3] == 1.1600 or Data[i, 3] == 1.1700 or Data[i, 3] == 1.1800 or Data[i, 3] == 1.1900 or \
            Data[i, 3] == 1.2000 or Data[i, 3] == 1.2100 or Data[i, 3] == 1.2300 or Data[i, 3] == 1.2400 or \
            Data[i, 3] == 1.2500 or Data[i, 3] == 1.2600 or Data[i, 3] == 1.2700 or Data[i, 3] == 1.2800 or \
            Data[i, 3] == 1.2900 or Data[i, 3] == 1.3000 or Data[i, 3] == 1.3100 or Data[i, 3] == 1.3200 or \
            Data[i, 3] == 1.3300 or Data[i, 3] == 1.3400 or Data[i, 3] == 1.3500 or Data[i, 3] == 1.3600 or \
            Data[i, 3] == 1.3700 or Data[i, 3] == 1.3800 or Data[i, 3] == 1.3900 or Data[i, 3] == 1.4000 or \
            Data[i, 3] == 1.4100 or Data[i, 3] == 1.4200 or Data[i, 3] == 1.4300 or Data[i, 3] == 1.4400 or \
            Data[i, 3] == 1.4500 or Data[i, 3] == 1.4600 or Data[i, 3] == 1.4700 or Data[i, 3] == 1.4800:
                
                Data[i, signal] = 1
                
    # Signal function
    for i in range(len(Data)):
        
        if Data[i, 5] == 1 and Data[i, 3] < Data[i - trend, 3]:
            
            Data[i, buy] = 1
        
        elif Data[i, 5] == 1 and Data[i, 3] > Data[i - trend, 3]:
            
            Data[i, sell] = -1
return Data

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

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

Давайте протестируем алгоритм на истории EURUSD, используя соотношение риска и прибыли 3,0, обусловленное 20-периодным средним истинным диапазоном. Используемый спред составляет 0,2 пункта за раунд сделки с использованием значений H3 с 2005 года.

Результаты показали, что реализованное соотношение риска и прибыли составило 3,03, а коэффициент успешности 27,33%. Ожидаемое значение размера позиции 1 стандартным лотом с использованием кредитного плеча 1: 100 составляет 37,17 доллара США за сделку. Коэффициент прибыли равен 1,14, который представляет собой валовую прибыль, деленную на валовой абсолютный убыток. Рентабельность инвестиций составляет 11,97%, таким образом, в годовом исчислении чистая прибыль составляет 0,74%. Количество заключенных сделок: 322. Учитывая, что это с 2005 года, у нас примерно 20 сделок в год.

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



Несколько слов об управлении рисками

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

Длинная позиция (покупка):

  • Алгоритм инициирует ордер на покупку после того, как был сгенерирован сигнал по определенной стратегии.
  • Затем алгоритм будет отслеживать тики, и всякий раз, когда максимум равен определенной константе, умноженной на значение ATR во время открытия сделки, инициируется ордер выхода (с прибылью). Одновременно, если виден минимум, равный определенной константе, умноженной на значение ATR на момент открытия сделки, инициируется выход (с убытком). Первым встреченный выход является, естественно, принятым событием.

Короткая (продажа) позиция:

  • Алгоритм инициирует короткий ордер на продажу после того, как был сгенерирован сигнал в соответствии с определенной стратегией.
  • Затем алгоритм будет отслеживать тики, и всякий раз, когда минимум равен определенной константе, умноженной на значение ATR во время открытия сделки, инициируется ордер выхода (с прибылью). Одновременно, если виден максимум, равный определенной константе, умноженной на значение ATR на момент открытия сделки, инициируется выход (с убытком). Первым встреченный выход является, естественно, принятым событием.

Взгляните на последнее значение ATR. Это около 0,0014 (14 пунктов). Если мы инициируем ордер на покупку, следуя простому соотношению риска и прибыли 2,00 (рискуя половиной того, что мы ожидаем получить), мы можем разместить ордер следующим образом:

  • Купить по текущей рыночной цене.
  • Тейк-профит по текущей рыночной цене + (2 x 14 пунктов).
  • Остановите позицию по текущей рыночной цене - (1 x 14 пунктов).

Код, который я использую для индикатора среднего истинного диапазона, выглядит следующим образом:

def ema(Data, alpha, lookback, what, where):
    
    # alpha is the smoothing factor
    # window is the lookback period
    # what is the column that needs to have its average calculated
    # where is where to put the exponential moving average
    
    alpha = alpha / (lookback + 1.0)
    beta  = 1 - alpha
    
    # First value is a simple SMA
    Data = ma(Data, lookback, what, where)
    
    # Calculating first EMA
    Data[lookback + 1, where] = (Data[lookback + 1, what] * alpha) + (Data[lookback, where] * beta)                        
    # Calculating the rest of EMA
    for i in range(lookback + 2, len(Data)):
            try:
                Data[i, where] = (Data[i, what] * alpha) + (Data[i - 1, where] * beta)
        
            except IndexError:
                pass
    return Datadef atr(Data, lookback, high, low, close, where):
    
    # Adding the required columns
    Data = adder(Data, 2)
    
    # True Range Calculation
    for i in range(len(Data)):
        try:
            
          Data[i, where] = max(Data[i, high] - Data[i, low],
                           abs(Data[i, high] - Data[i - 1, close]),
                           abs(Data[i, low] - Data[i - 1, close]))
            
        except ValueError:
            pass
        
    Data[0, where] = 0   
    
    # Average True Range Calculation
    Data = ema(Data, 2, lookback, where, where + 1)
    
    # Cleaning
    Data = deleter(Data, where, 1)
    Data = jump(Data, lookback)        
    return Data

Заключение и важный отказ от ответственности

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

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

Я должен отметить несколько моментов в моих тестах на истории и статьях:

  • Я использую спред, основанный на институциональных котировках небольшой доли пунктов. Как правило, розничным трейдерам предоставляется колоссальный спред в размере 0,80–3,00 пункта за сделку. Это огромно и несправедливо по отношению к ним. Тестирую на истории спред 0,2–0,5. Однако большинство «прибыльных» стратегий, использующих часовой таймфрейм, по-прежнему работают со спредом в 1 пункт. Для тех, кто использует таймфреймы M15 или M5, они не могут быть прибыльными со спредом в 1 пункт.
  • Я использую расчет периода удержания, близкий к близкому, если нет процесса управления рисками.
  • Некоторые из представленных мною тестов на истории неудачны, и они публикуются либо для того, чтобы развенчать миф о торговле, либо для того, чтобы представить интересные функции, которые читатели могут запрограммировать.
  • Наконец, я твердо убежден, что нельзя кормить с ложечки. Я научился на практике, а не копируя. Вы должны понять идею, функцию, интуицию, условия стратегии, а затем разработать (даже лучше) одну из них самостоятельно, чтобы вы протестировали ее на исторических данных и улучшили, прежде чем принимать решение о том, чтобы реализовать ее или устранить. Так вы сможете поделиться со мной своей лучшей стратегией, и мы вместе разбогатеем.

Подводя итог, можно ли сказать, что стратегии, которые я предлагаю, реалистичны? Да, но только путем оптимизации среды (надежный алгоритм, низкие затраты, честный брокер, надлежащее управление рисками и управление заказами). Предусмотрены ли стратегии исключительно для торговли? Нет, это нужно для стимулирования мозгового штурма и получения новых торговых идей, поскольку мы все устали слышать о перепроданности RSI как о причине для открытия короткой позиции или о преодолении сопротивления как о причине идти долго. Я пытаюсь представить новую область под названием «Объективный технический анализ», в которой мы используем достоверные данные для оценки наших методов, а не полагаемся на устаревшие классические методы.