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

  • Разработка стратегий внутридневной торговли
  • Создание стратегии скальпинга на Python
  • Обнаружение торговых паттернов с помощью Python

Разработка стратегий внутридневной торговли

Знакомство:

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

Все данные, использованные в этом исследовании, получены из forex.com, авторитетного брокера, и легко доступны для скачивания на их официальном сайте.

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

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

Использованное время

Все указанные времена взяты от моего брокера, который находится на GMT (+2).

Сезонно

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

Python был использован для нахождения средних значений месячных цен с использованием еженедельных данных, которые затем использовались для поиска квартальной доходности с обзором на 5 лет. Ретроспектива за последние 5 лет была использована, так как во время ковида произошло значительное изменение цен, которое настолько резко изменило данные, что взгляд на 10–20 лет назад не даст большого значения.

Все графики показывают среднюю цену закрытия за определенный год на основе данных за последние 2,5 и 15 лет.

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

GBP Сезонность и квартальная доходность

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

Еженедельные профили:

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

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

Вероятность закрытия свечи вверх или вниз внутри бычьей или медвежьей недели

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

Для бычьих условий очерченные профили охватывают следующие сценарии:

  1. Неделя начинается с минимума в понедельник и достигает максимума в пятницу.
  2. Неделя характеризуется минимумом понедельника, за которым следует максимум в четверг.
  3. Неделя началась с минимума вторника, достигнув максимума в пятницу.
  4. Неделя была отмечена минимумом вторника, а затем завершилась максимумом в четверг.
  5. Неделя, которая начинается с минимума среды и достигает максимума в пятницу.
  6. Неделя, начинающаяся с минимума среды и завершающаяся максимумом в четверг.

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

Самая распространенная бычья неделя для GU
Самая распространенная медвежья неделя

Ежечасный

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

Ящичковая диаграмма доходности свечи H1

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

Вертикальные линии показывают начало Лондонской сессии, а также Нью-Йоркской. Алмазы являются выбросами, а тела — там, где находится большая часть доходов.

Волатильность сессии

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

Средний размер пипса каждой часовой свечи

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

  1. Оценка волатильности: помогает трейдерам оценить волатильность рынка. Сравнивая средние размеры пунктов в разное время дня, они могут точно определить, когда рынок имеет тенденцию быть более или менее волатильным. Эта информация имеет решающее значение для корректировки торговых стратегий в соответствии с текущими рыночными условиями.
  2. Тайминг сделок: Трейдеры могут использовать эти данные для более эффективного расчета времени своих сделок. Когда они ожидают более высокой волатильности, они могут входить в рынок в периоды, когда потенциал для более крупных ценовых движений больше, оптимизируя свои точки входа и выхода.
  3. Управление рисками: Понимание того, когда следует ожидать более крупных ценовых движений, позволяет трейдерам лучше управлять своими рисками. В периоды высокой волатильности они могут корректировать размеры своих позиций и устанавливать соответствующие стоп-лоссы для защиты своего капитала.
  4. Оптимизация стратегии: Трейдеры могут адаптировать свои торговые стратегии на основе этой информации. Например, они могут использовать различные методы, такие как скальпинг в периоды низкой волатильности и свинг-трейдинг в периоды высокой волатильности.
  5. Понимание рынка: Эти знания дают трейдерам более глубокое понимание настроений и динамики рынка. Это позволяет им оценить, когда важные новости или события могут повлиять на движение цен.

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

Диапазон сеансов

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

Более того, я использую хорошо зарекомендовавшую себя структурную схему, известную как профиль «Искать и уничтожать», представленную Inner Circle Trader (ICT). Этот паттерн включает в себя преднамеренный захват максимума (или минимума) лондонской сессии перед последующим движением в противоположном направлении во время нью-йоркской сессии. Выявляя и понимая эту закономерность, трейдеры могут еще больше усовершенствовать свои торговые стратегии и извлечь выгоду из этого предсказуемого поведения рынка.

Времена, когда формировались дневные максимумы и минимумы

Этот график эффективно очерчивает моменты, когда график GBP/USD устанавливает свои дневные максимумы и минимумы с помощью 15-минутных свечей. Даже без необходимости в вертикальных линиях, разграничивающих торговые сессии, график обеспечивает четкое визуальное представление отдельных торговых сессий. При этом Нью-Йорк обеспечивает самые высокие шансы на выбор минимума или максимума дня. Также наблюдается всплеск в 15:30 — это время, когда выходят важные новостные события.

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

Австралийский доллар (AUDUSD)

Азиатская сессия:

Время, когда максимум/минимум сессии формировался во время азиатской сессии

Глядя на график, становится ясно, что в течение сессии есть определенные моменты, когда максимум и минимум сессии имеют более высокую вероятность формирования и могут быть использованы для захвата всего диапазона.

Лондонская сессия:

Время, когда максимум/минимум сессии формировался во время лондонской сессии

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

Нью-Йоркская сессия:

Время, когда максимум/минимум сессии сформировался во время Нью-Йоркской сессии

Совпадение между Лондонской и Нью-Йоркской сессиями проявляется в частом появлении высоких или низких ценовых точек

Почасовые профили

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

Возврат 15-минутных свечей в 1-часовые окна

Два метода, которые я использовал:

  1. Доходность (средняя цена тел): Этот метод вычисляет среднюю цену, рассматривая тела свечей. Тело свечи представляет собой ценовой диапазон между ценами открытия и закрытия в течение заданного периода времени (например, M15 или H1). Расхождения в этом методе могут возникать, когда тела свечей значительно меняются в пределах выбранного таймфрейма, отражая изменения настроений рынка и ценового импульса.
  2. High-Low (средняя цена фитилей): Этот метод вычисляет среднюю цену, принимая во внимание максимальную и минимальную цены (фитили), достигнутые за один и тот же период времени. Колебания могут возникнуть, если фитили демонстрируют заметные колебания, на которые могут влиять такие факторы, как волатильность рынка, ликвидность или появление важных новостей.

Важно подчеркнуть, что оба этих метода представляют исключительно динамику цены в пределах таймфрейма М15 часовой свечи. В этом контексте вырисовываются определенные закономерности:

  • В первые 15 минут часто наблюдаются значительные колебания цен, характеризующиеся заметными движениями.
  • Между 15-30-минутным окном цена имеет тенденцию демонстрировать склонность к стабильности, что часто приводит к минимальным изменениям.
  • В течение последних 15 минут более вероятны существенные ценовые сдвиги, особенно когда часовая свеча закрывается в медвежьей манере.

Заключение

Использование этой методологии, основанной на данных, в сфере торговли на рынке Форекс оказывается очень выгодным при построении систематической торговой стратегии. Благодаря этому подходу трейдеры могут точно определить моменты в течение дня, когда они ожидают определенного поведения рынка. Более того, они могут работать с уверенностью, опираясь на статистическое преимущество, предоставляемое системой, созданной на основе анализа и разработки на основе Python.

Ссылки:

Репозиторий Github:
https://github.com/MrPotatoHead-Dev/Forex-trading-strategy-development

Создание стратегии скальпинга на Python

Знакомство

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

Я был очарован и попробовал свои силы в стратегии. Я придумал свою собственную стратегию скальпинга и протестировал ее на Python, и результаты были довольно интересными.

В этой статье я расскажу о стратегии, которую я реализовал с помощью Python, и о том, как вы можете ее разработать. Итак, сначала мы получим некоторую информацию о нашей торговой стратегии, а затем перейдем к части кодирования, где мы получим исторические и внутридневные данные с помощью API FinancialModelingPrep (FMP) и протестируем нашу стратегию на истории в Python.

Без лишних слов, давайте углубимся в статью!

Наша торговая стратегия скальпинга

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

Скальпинг Трейдинг

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

Например, трейдер, который следит за скальпинговой торговлей, покупает огромное количество, скажем, акций Apple (AAPL) по цене 170 долларов и стремится продать акции при очень небольшом росте цены, например, 170,5 или 171 доллар.

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

Торговая стратегия

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

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

Ниже приведена механика нашей торговой стратегии:

Мы входим в рынок, если: Рынок открывается с повышением цены на 1% от закрытия предыдущего дня.

Мы выходим из рынка, когда: Цена акции увеличивается на 1% по сравнению с ценой покупки, которая является ценой открытия дня. Если акция не достигает роста цены на 1%, мы выходим к концу торгового дня по цене закрытия.

Цель здесь не в том, чтобы создать сложную стратегию с различными условиями входа и выхода, а в том, чтобы создать простую стратегию, чтобы понять природу скальпинговой торговли. С учетом сказанного, давайте перейдем к части кодирования!

Импорт пакетов

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

  • Pandas — для форматирования, очистки, манипулирования данными и других связанных с ними целей
  • Matplotlib — для создания графиков и различного рода визуализаций
  • Запросы — для выполнения вызовов API с целью извлечения данных
  • Termcolor — для настройки стандартного вывода, отображаемого в записной книжке Jupyter.
  • Math — для различных математических функций и операций
  • NumPy — для численных и высокоуровневых математических функций

Следующий код импортирует все вышеупомянутые пакеты в нашу среду Python:

# IMPORTING PACKAGES

import requests
import pandas as pd
import matplotlib.pyplot as plt
from termcolor import colored as cl
import numpy as np
import math

Если вы не установили ни один из импортированных пакетов, обязательно сделайте это с помощью команды pip в вашем терминале.

Извлечение исторических данных

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

# EXTRACTING HISTORICAL DATA

api_key = 'YOUR API KEY'
tsla_json = requests.get(f'https://financialmodelingprep.com/api/v3/historical-price-full/TSLA?from=2014-01-01&apikey={api_key}').json()

tsla_df = pd.DataFrame(tsla_json['historical']).drop('label', axis = 1)
tsla_df = tsla_df.set_index('date')
tsla_df = tsla_df.iloc[::-1]
tsla_df.index = pd.to_datetime(tsla_df.index)

tsla_df

Код очень простой. Сначала мы сохраняем ключ API в переменной api_key. Обязательно замените YOUR API KEY секретным ключом API, который вы можете получить после создания учетной записи разработчика FMP.

Затем, используя функцию get, предоставляемую пакетом Requests, мы выполняем вызов API для получения исторических данных TSLA. Наконец, мы преобразуем извлеченный ответ JSON в работоспособный кадр данных Pandas вместе с некоторыми манипуляциями с данными, и вот результат:

Исторические данные TSLA, извлеченные с помощью API FMP (изображение автора)

Одна вещь, которая мне действительно нравится в ответе API, выдаваемом конечной точкой исторических данных FMP, — это огромное количество дополнительных данных, которые с ней связаны. Помимо данных OHLC, вы получаете VWAP, Change % и многое другое, что может быть действительно полезно в различных сценариях.

Вычисление процентного изменения и фильтрация

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

# CALCULATING % CHANGE

tsla_df['pclose_open_pc'] = np.nan

for i in range(1, len(tsla_df)):
diff, avg = (tsla_df.close[i-1] - tsla_df.open[i]) , (tsla_df.close[i-1] + tsla_df.open[i])/2
pct_change = (diff / avg)*100
tsla_df['pclose_open_pc'][i] = pct_change

tsla_df = tsla_df.dropna().drop(['change', 'changePercent', 'changeOverTime'], axis = 1)
tsla_df = tsla_df[tsla_df.pclose_open_pc > 1]

tsla_df

Код может показаться немного нечетким из-за цикла for и прочего, но на самом деле он довольно прост. Позвольте мне разложить все по полочкам.

Во-первых, мы создадим новый столбец с именем pclose_open_pc для хранения значений процентного изменения. Затем идет цикл for. Основная идея, лежащая в основе этого цикла for, заключается в том, чтобы воспроизвести функционирование функции pct_change() предоставляемой Pandas.

Единственное изменение, которое мы вносим здесь с помощью цикла for, заключается в вычислении процентного изменения между двумя различными переменными. т.е. открытие текущего дня и закрытие предыдущего дня, что невозможно с pct_change() который позволяет вычислить процентное изменение между текущим значением и старым значением для одной переменной.

После этого мы отбрасываем некоторые столбцы и срезаем фрейм данных с помощью условия, которое выбирает строки, где процентное изменение больше 1, т.е. цена открытия текущего дня выше цены закрытия предыдущего дня на 1%.

Это окончательный датафрейм после всей обработки:

Отфильтрованные исторические данные TSLA (изображение автора)

Теперь, когда мы подготовили данные, пришло время создать и протестировать нашу торговую стратегию скальпинга.

Тестирование стратегии на истории

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

investment = 100000
equity = investment
earning = 0

earnings_record = []

for i in range(len(tsla_df)):

# EXTRACTING INTRADAY DATA
date = str(tsla_df.index[i])[:10]
intra_json = requests.get(f'https://financialmodelingprep.com/api/v3/historical-chart/1min/TSLA?from={date}&to={date}&apikey={api_key}').json()
intra_df = pd.DataFrame(intra_json)
intra_df = intra_df.set_index(pd.to_datetime(intra_df.date)).iloc[::-1]

# ENTERING POSITION
open_p = tsla_df.iloc[i].open
no_of_shares = math.floor(equity/open_p)
equity -= (no_of_shares * open_p)

# EXITING POSITION
intra_df['p_change'] = np.nan

for i in range(len(intra_df)):
diff, avg = (intra_df.close[i] - open_p), (intra_df.close[i] + open_p)/2
pct_change = (diff / avg)*100
intra_df['p_change'][i] = pct_change
intra_df = intra_df.dropna()
greater_1 = intra_df[intra_df.p_change > 1]

if len(greater_1) > 0:
sell_price = greater_1.iloc[0].close
equity += (no_of_shares * sell_price)
else:
sell_price = intra_df.iloc[-1].close
equity += (no_of_shares * sell_price)

# CALCULATING TRADE EARNINGS
investment += earning
earning = round(equity-investment, 2)
earnings_record.append(earning)

if earning > 0:
print(cl('PROFIT:', color = 'green', attrs = ['bold']), f'Earning on {date}: ${earning}; Bought ', cl(f'{no_of_shares}', attrs = ['bold']), 'stocks at ', cl(f'${open_p}', attrs = ['bold']), 'and Sold at ', cl(f'${sell_price}', attrs = ['bold']))
else:
print(cl('LOSS:', color = 'red', attrs = ['bold']), f'Loss on {date}: ${earning}; Bought ', cl(f'{no_of_shares}', attrs = ['bold']), 'stocks at ', cl(f'${open_p}', attrs = ['bold']), 'and Sold at ', cl(f'${sell_price}', attrs = ['bold']))

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

В принципе, код можно разделить на четыре раздела (как видно из комментариев к коду):

  • Первый — это извлечение внутридневных данных с использованием внутридневной конечной точки FMP для дней, когда мы входим в рынок.
  • Во втором разделе мы входим в нашу позицию, покупая акции по цене открытия дня.
  • Третий раздел — это код для выхода из нашей позиции, где мы сначала вычисляем процентное изменение цены, а затем выравниваем позицию, как только происходит рост цены на 1%.
  • Четвертый раздел посвящен расчету заработка по каждой сделке и выводу результатов в выходной терминал.

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

Сделки, сгенерированные программой (Изображение автора)

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

Анализ и оценка доходности стратегии

В этом разделе мы углубимся в эффективность стратегии. Начнем с основных метрик доходности стратегии и ROI. Следующий код рассчитывает общий заработок и ROI стратегии:

# STRATEGY RETURNS

print(cl(f'TSLA BACKTESTING RESULTS:', attrs = ['bold']))
print(' ')

strategy_earning = round(equity - 100000, 2)
roi = round(strategy_earning / 100000 * 100, 2)

print(cl(f'EARNING: ${strategy_earning} ; ROI: {roi}%', attrs = ['bold']))

Код очень простой. Мы просто реализуем математические формулы для расчета общей прибыли и рентабельности инвестиций (ROI), чтобы получить окончательные цифры, и вот окончательный результат:

Результаты тестирования на истории (изображение автора)

Таким образом, наша стратегия скальпинга принесла общий заработок в размере 110 тысяч долларов США с рентабельностью инвестиций 110% в течение десяти лет (2014–2024 гг.). Это неплохо. Несмотря на то, что результаты не являются чем-то ошеломляющим, они все равно достаточно хороши.

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

earnings_df = pd.DataFrame(columns = ['date', 'earning'])
earnings_df.date = tsla_df.index
earnings_df.earning = earnings_record

earnings_df.tail()

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

Данные о доходах (Изображение автора)

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

  • Максимальный убыток: Заработок на худшей сделке
  • Max profit: Заработок на лучшей сделке
  • Total trades: Сумма сделок, сгенерированных стратегией
  • Винрейт: Вероятность успеха стратегии
  • Среднее количество сделок в месяц и средний заработок в месяц

Следующий код вычисляет все вышеописанные метрики:

max_loss = earnings_df.earning.min()
max_profit = earnings_df.earning.max()

no_of_wins = len(earnings_df.iloc[np.where(earnings_df.earning > 0)[0]])
no_of_losses = len(earnings_df.iloc[np.where(earnings_df.earning < 0)[0]])
no_of_trades = no_of_wins+no_of_losses
win_rate = (no_of_wins/(no_of_wins + no_of_losses))*100

print(cl('MAX LOSS:', color = 'red', attrs = ['bold']), f'${max_loss};',
cl('MAX PROFIT:', color = 'green', attrs = ['bold']), f'{max_profit};',
cl('TOTAL TRADES:', attrs = ['bold']), f'{no_of_trades};',
cl('WIN RATE:', attrs = ['bold']), f'{round(win_rate)}%;',
cl('AVG. TRADES/MONTH:', attrs = ['bold']), f'{round(no_of_trades/120)};',
cl('AVG. EARNING/MONTH:', attrs = ['bold']), f'${round(strategy_earning/120)}'
)

plt.style.use('ggplot')

earnings_df.earning.hist()
plt.title('Earnings Distribution')
plt.show()

earnings_df = earnings_df.set_index('date')

earnings_df.earning.cumsum().plot()
plt.title('Strategy Cumulative Returns')
plt.show()

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

Эффективность стратегии (изображения автора)

Давайте сначала поговорим о метриках. Максимальный убыток составляет $15,1 тыс., а максимальная прибыль — $5 тыс. Дело в том, что всегда лучше, чтобы абсолютное значение максимального убытка было меньше, чем максимальная прибыль, так как это снижает риск стратегии.

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

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

Что касается графиков, то их два, которые можно увидеть на представленном выше выходе. Первый — это гистограмма, которая показывает распределение прибыли по нашей стратегии. Видно, что большая часть сделок принесла около $1500-$2500.

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

Сравнение доходности «купить/держать»

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

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

Следующий код реализует стратегию «купить/держать» и рассчитывает доходность:

api_key = 'YOUR API KEY'
json = requests.get(f'https://financialmodelingprep.com/api/v3/historical-price-full/TSLA?from=2014-01-01&apikey={api_key}').json()

df = pd.DataFrame(json['historical']).drop('label', axis = 1)
df = df.set_index('date')
df = df.iloc[::-1]
df.index = pd.to_datetime(df.index)

tsla_roi = round(list(df['close'].pct_change().cumsum())[-1],4)*100
print(cl(f'BUY/HOLD STRATEGY ROI: {round(tsla_roi,2)}%', attrs = ['bold']))

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

Окупаемость инвестиций в стратегию «купить/держать» (изображение автора)

После сравнения результатов стратегии «купи/держи» и нашей стратегии скальпинговой торговли, стратегия «купи/держи» превзошла нашу с разницей в 333% по ROI. Что это значит? Это означает, что мы должны много работать над нашей стратегией скальпинга, прежде чем выводить ее на рынок.

Заключение

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

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

Обнаружение торговых паттернов с помощью Python

Для того, чтобы обсудить тему Практического руководства по автоматическому обнаружению торговых паттернов с помощью Python, давайте начнем с основ. Большинство знакомо с внешним видом торгового графика, обычно иллюстрируемого зелеными и красными столбиками, которые вместе образуют линейный график. Этот простой визуальный элемент инкапсулирует огромное количество данных и информации. Одна свеча представляет собой интервал данных, например, один час на графике, и инкапсулирует четыре критически важных фрагмента информации: открытие, максимум, минимум и закрытие для этого интервала, обычно сокращенно называемые данными OHLC. Если цена закрытия выше цены открытия, свеча будет выглядеть зеленой; И наоборот, он будет красным, если цена закрытия ниже. Эта концепция более наглядно проиллюстрирована на изображении ниже, которое служит введением в наше Практическое руководство по автоматическому обнаружению торговых паттернов с помощью Python.

Изображение с сайта ig.com

Пример этого изображен следующим образом:

500 долларов США в час от ig.com

Вы можете заметить появление определенных паттернов в последовательности свечей, называемых свечными моделями. На основе этих паттернов часто разрабатываются торговые стратегии.

Использование S&P 500 из API EODHD для демонстрации этого

Начальный шаг включает в себя получение набора данных, необходимого для нашего анализа, для чего мы будем использовать официальную библиотеку EODHD API Python. Приведенный ниже фрагмент кода извлекает 720 часов данных, что эквивалентно 30 дням.

import config as cfg
from eodhd import APIClient

api = APIClient(cfg.API_KEY)


def get_ohlc_data():
df = api.get_historical_data("AAPL.US", "1h", results=(24*30))
return df


if __name__ == "__main__":
df = get_ohlc_data()
print(df)

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

Модифицированный вариант изображения свечи для иллюстрации этого паттерна представлен следующим образом:

Изображение с сайта ig.com

Таким образом, паттерн «молот» сигнализирует о бычьем развороте и рассматривается как умеренный разворотный паттерн, указывающий на потенциальный сдвиг направления рынка с нисходящего на восходящее.

Мы разработали код для точного определения этих свечей в нашем наборе данных.

import pandas as pd
import config as cfg
from eodhd import APIClient

api = APIClient(cfg.API_KEY)


def candle_hammer(df: pd.DataFrame = None) -> pd.Series:
"""* Candlestick Detected: Hammer ("Weak - Reversal - Bullish Signal - Up"""

# Fill NaN values with 0
df = df.fillna(0)

return (
((df["high"] - df["low"]) > 3 * (df["open"] - df["close"]))
& (((df["close"] - df["low"]) / (0.001 + df["high"] - df["low"])) > 0.6)
& (((df["open"] - df["low"]) / (0.001 + df["high"] - df["low"])) > 0.6)
)


def get_ohlc_data():
df = api.get_historical_data("AAPL.US", "1h", results=(24*30))
return df


if __name__ == "__main__":
df = get_ohlc_data()
df["hammer"] = candle_hammer(df)
print(df)
print(df[df["hammer"] == True])

Для демонстрации мы показываем набор данных дважды. Исходный датасет раскрывает идентификацию свечи-молота. В последующем наборе данных представлены исключительно экземпляры, в которых был обнаружен паттерн молота. За последние 720 часов паттерн «молот» всплыл 67 раз.

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

def candle_inverted_hammer(df: pd.DataFrame = None) -> pd.Series:
"""* Candlestick Detected: Inverted Hammer ("Weak - Continuation - Bullish Pattern - Up")"""

# Fill NaN values with 0
df = df.fillna(0)

return (
((df["high"] - df["low"]) > 3 * (df["open"] - df["close"]))
& ((df["high"] - df["close"]) / (0.001 + df["high"] - df["low"]) > 0.6)
& ((df["high"] - df["open"]) / (0.001 + df["high"] - df["low"]) > 0.6)
)

Свечные модели

Свечные модели можно условно разделить на две категории:

  1. Бычий или медвежий
  2. Нерешительность / Нейтральный, Слабый, Надежный или Сильный

Вот краткий обзор распространенных свечных моделей:

Нерешительность / Нейтральный

  • Доджи

Слабый

  • Молот (бычий)
  • Перевернутый молот (бычий)
  • Падающая звезда (медвежий)

Достоверный

  • Повешенный (медвежий)
  • Three Line Strike (бычий)
  • Два черных зияния (медвежьи)
  • Брошенный ребенок (бычий)
  • Утренняя звезда Доджи (бычий)
  • Вечерняя Звезда Доджи (медвежья)

Сильный

  • Три белых солдата (бычий)
  • Три черные вороны (медвежьи)
  • Утренняя звезда (бычий)
  • Вечерняя звезда (медвежья)

Мы закодировали эти свечные паттерны в функции Python, используя Numpy для некоторых из наиболее сложных паттернов.

import numpy as np

def candle_hammer(df: pd.DataFrame = None) -> pd.Series:
"""* Candlestick Detected: Hammer ("Weak - Reversal - Bullish Signal - Up"""

# Fill NaN values with 0
df = df.fillna(0)

return (
((df["high"] - df["low"]) > 3 * (df["open"] - df["close"]))
& (((df["close"] - df["low"]) / (0.001 + df["high"] - df["low"])) > 0.6)
& (((df["open"] - df["low"]) / (0.001 + df["high"] - df["low"])) > 0.6)
)


def candle_inverted_hammer(df: pd.DataFrame = None) -> pd.Series:
"""* Candlestick Detected: Inverted Hammer ("Weak - Reversal - Bullish Pattern - Up")"""

# Fill NaN values with 0
df = df.fillna(0)

return (
((df["high"] - df["low"]) > 3 * (df["open"] - df["close"]))
& ((df["high"] - df["close"]) / (0.001 + df["high"] - df["low"]) > 0.6)
& ((df["high"] - df["open"]) / (0.001 + df["high"] - df["low"]) > 0.6)
)


def candle_shooting_star(df: pd.DataFrame = None) -> pd.Series:
"""* Candlestick Detected: Shooting Star ("Weak - Reversal - Bearish Pattern - Down")"""

# Fill NaN values with 0
df = df.fillna(0)

return (
((df["open"].shift(1) < df["close"].shift(1)) & (df["close"].shift(1) < df["open"]))
& (df["high"] - np.maximum(df["open"], df["close"]) >= (abs(df["open"] - df["close"]) * 3))
& ((np.minimum(df["close"], df["open"]) - df["low"]) <= abs(df["open"] - df["close"]))
)


def candle_hanging_man(df: pd.DataFrame = None) -> pd.Series:
"""* Candlestick Detected: Hanging Man ("Weak - Reliable - Bearish Pattern - Down")"""

# Fill NaN values with 0
df = df.fillna(0)

return (
((df["high"] - df["low"]) > (4 * (df["open"] - df["close"])))
& (((df["close"] - df["low"]) / (0.001 + df["high"] - df["low"])) >= 0.75)
& (((df["open"] - df["low"]) / (0.001 + df["high"] - df["low"])) >= 0.75)
& (df["high"].shift(1) < df["open"])
& (df["high"].shift(2) < df["open"])
)


def candle_three_white_soldiers(df: pd.DataFrame = None) -> pd.Series:
"""*** Candlestick Detected: Three White Soldiers ("Strong - Reversal - Bullish Pattern - Up")"""

# Fill NaN values with 0
df = df.fillna(0)

return (
((df["open"] > df["open"].shift(1)) & (df["open"] < df["close"].shift(1)))
& (df["close"] > df["high"].shift(1))
& (df["high"] - np.maximum(df["open"], df["close"]) < (abs(df["open"] - df["close"])))
& ((df["open"].shift(1) > df["open"].shift(2)) & (df["open"].shift(1) < df["close"].shift(2)))
& (df["close"].shift(1) > df["high"].shift(2))
& (
df["high"].shift(1) - np.maximum(df["open"].shift(1), df["close"].shift(1))
< (abs(df["open"].shift(1) - df["close"].shift(1)))
)
)


def candle_three_black_crows(df: pd.DataFrame = None) -> pd.Series:
"""* Candlestick Detected: Three Black Crows ("Strong - Reversal - Bearish Pattern - Down")"""

# Fill NaN values with 0
df = df.fillna(0)

return (
((df["open"] < df["open"].shift(1)) & (df["open"] > df["close"].shift(1)))
& (df["close"] < df["low"].shift(1))
& (df["low"] - np.maximum(df["open"], df["close"]) < (abs(df["open"] - df["close"])))
& ((df["open"].shift(1) < df["open"].shift(2)) & (df["open"].shift(1) > df["close"].shift(2)))
& (df["close"].shift(1) < df["low"].shift(2))
& (
df["low"].shift(1) - np.maximum(df["open"].shift(1), df["close"].shift(1))
< (abs(df["open"].shift(1) - df["close"].shift(1)))
)
)


def candle_doji(df: pd.DataFrame = None) -> pd.Series:
"""! Candlestick Detected: Doji ("Indecision / Neutral")"""

# Fill NaN values with 0
df = df.fillna(0)

return (
((abs(df["close"] - df["open"]) / (df["high"] - df["low"])) < 0.1)
& ((df["high"] - np.maximum(df["close"], df["open"])) > (3 * abs(df["close"] - df["open"])))
& ((np.minimum(df["close"], df["open"]) - df["low"]) > (3 * abs(df["close"] - df["open"])))
)


def candle_three_line_strike(df: pd.DataFrame = None) -> pd.Series:
"""** Candlestick Detected: Three Line Strike ("Reliable - Reversal - Bullish Pattern - Up")"""

# Fill NaN values with 0
df = df.fillna(0)

return (
((df["open"].shift(1) < df["open"].shift(2)) & (df["open"].shift(1) > df["close"].shift(2)))
& (df["close"].shift(1) < df["low"].shift(2))
& (
df["low"].shift(1) - np.maximum(df["open"].shift(1), df["close"].shift(1))
< (abs(df["open"].shift(1) - df["close"].shift(1)))
)
& ((df["open"].shift(2) < df["open"].shift(3)) & (df["open"].shift(2) > df["close"].shift(3)))
& (df["close"].shift(2) < df["low"].shift(3))
& (
df["low"].shift(2) - np.maximum(df["open"].shift(2), df["close"].shift(2))
< (abs(df["open"].shift(2) - df["close"].shift(2)))
)
& ((df["open"] < df["low"].shift(1)) & (df["close"] > df["high"].shift(3)))
)


def candle_two_black_gapping(df: pd.DataFrame = None) -> pd.Series:
"""*** Candlestick Detected: Two Black Gapping ("Reliable - Reversal - Bearish Pattern - Down")"""

# Fill NaN values with 0
df = df.fillna(0)

return (
((df["open"] < df["open"].shift(1)) & (df["open"] > df["close"].shift(1)))
& (df["close"] < df["low"].shift(1))
& (df["low"] - np.maximum(df["open"], df["close"]) < (abs(df["open"] - df["close"])))
& (df["high"].shift(1) < df["low"].shift(2))
)


def candle_morning_star(df: pd.DataFrame = None) -> pd.Series:
"""*** Candlestick Detected: Morning Star ("Strong - Reversal - Bullish Pattern - Up")"""

# Fill NaN values with 0
df = df.fillna(0)

return (
(np.maximum(df["open"].shift(1), df["close"].shift(1)) < df["close"].shift(2)) & (df["close"].shift(2) < df["open"].shift(2))
) & ((df["close"] > df["open"]) & (df["open"] > np.maximum(df["open"].shift(1), df["close"].shift(1))))


def candle_evening_star(df: pd.DataFrame = None) -> np.ndarray:
"""*** Candlestick Detected: Evening Star ("Strong - Reversal - Bearish Pattern - Down")"""

# Fill NaN values with 0
df = df.fillna(0)

return (
(np.minimum(df["open"].shift(1), df["close"].shift(1)) > df["close"].shift(2)) & (df["close"].shift(2) > df["open"].shift(2))
) & ((df["close"] < df["open"]) & (df["open"] < np.minimum(df["open"].shift(1), df["close"].shift(1))))


def candle_abandoned_baby(df: pd.DataFrame = None) -> pd.Series:
"""** Candlestick Detected: Abandoned Baby ("Reliable - Reversal - Bullish Pattern - Up")"""

# Fill NaN values with 0
df = df.fillna(0)

return (
(df["open"] < df["close"])
& (df["high"].shift(1) < df["low"])
& (df["open"].shift(2) > df["close"].shift(2))
& (df["high"].shift(1) < df["low"].shift(2))
)


def candle_morning_doji_star(df: pd.DataFrame = None) -> pd.Series:
"""** Candlestick Detected: Morning Doji Star ("Reliable - Reversal - Bullish Pattern - Up")"""

# Fill NaN values with 0
df = df.fillna(0)

return (df["close"].shift(2) < df["open"].shift(2)) & (
abs(df["close"].shift(2) - df["open"].shift(2)) / (df["high"].shift(2) - df["low"].shift(2)) >= 0.7
) & (abs(df["close"].shift(1) - df["open"].shift(1)) / (df["high"].shift(1) - df["low"].shift(1)) < 0.1) & (
df["close"] > df["open"]
) & (
abs(df["close"] - df["open"]) / (df["high"] - df["low"]) >= 0.7
) & (
df["close"].shift(2) > df["close"].shift(1)
) & (
df["close"].shift(2) > df["open"].shift(1)
) & (
df["close"].shift(1) < df["open"]
) & (
df["open"].shift(1) < df["open"]
) & (
df["close"] > df["close"].shift(2)
) & (
(df["high"].shift(1) - np.maximum(df["close"].shift(1), df["open"].shift(1)))
> (3 * abs(df["close"].shift(1) - df["open"].shift(1)))
) & (
np.minimum(df["close"].shift(1), df["open"].shift(1)) - df["low"].shift(1)
) > (
3 * abs(df["close"].shift(1) - df["open"].shift(1))
)


def candle_evening_doji_star(df: pd.DataFrame = None) -> pd.Series:
"""** Candlestick Detected: Evening Doji Star ("Reliable - Reversal - Bearish Pattern - Down")"""

# Fill NaN values with 0
df = df.fillna(0)

return (df["close"].shift(2) > df["open"].shift(2)) & (
abs(df["close"].shift(2) - df["open"].shift(2)) / (df["high"].shift(2) - df["low"].shift(2)) >= 0.7
) & (abs(df["close"].shift(1) - df["open"].shift(1)) / (df["high"].shift(1) - df["low"].shift(1)) < 0.1) & (
df["close"] < df["open"]
) & (
abs(df["close"] - df["open"]) / (df["high"] - df["low"]) >= 0.7
) & (
df["close"].shift(2) < df["close"].shift(1)
) & (
df["close"].shift(2) < df["open"].shift(1)
) & (
df["close"].shift(1) > df["open"]
) & (
df["open"].shift(1) > df["open"]
) & (
df["close"] < df["close"].shift(2)
) & (
(df["high"].shift(1) - np.maximum(df["close"].shift(1), df["open"].shift(1)))
> (3 * abs(df["close"].shift(1) - df["open"].shift(1)))
) & (
np.minimum(df["close"].shift(1), df["open"].shift(1)) - df["low"].shift(1)
) > (
3 * abs(df["close"].shift(1) - df["open"].shift(1))
)

Давайте изучим наш набор данных, чтобы увидеть, сможем ли мы определить какой-либо из свечных паттернов Strong…

if __name__ == "__main__":
df = get_ohlc_data()

df["three_white_soldiers"] = candle_three_white_soldiers(df)
df["three_black_crows"] = candle_three_black_crows(df)
df["morning_star"] = candle_morning_star(df)
df["evening_star"] = candle_evening_star(df)

print(df[(df["three_white_soldiers"] == True) | (df["three_black_crows"] == True) | (df["morning_star"] == True) | (df["evening_star"] == True)])

Действительно, за последние 30 дней паттерны Стронга появлялись несколько раз. По предоставленным данным можно определить, когда была идентифицирована свечная модель и ее тип. Увлекательным упражнением было бы изучить часовой график S&P 500 для этих конкретных интервалов и попытаться точно определить закономерности.

Заключение

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

Источник

Источник

Источник