PriceGenerator — платформа для генерации биржевых цен
Введение
Иногда для целей тестирования автоматизированных торговых алгоритмов бывает необходимо получить набор данных с биржевыми ценами. Обычно трейдеры и аналитики используют модель цен вида OHLCV-candlesticks (open, high, low, close, volume), так называемые японские свечи. Одна строка таких данных представляет собой набор цен для построения одной японской свечи: дата открытия, цена открытия, наибольшая цена, наименьшая цена, цена закрытия на данном временном интервале и значение объёма торгов.
📈PriceGenerator — это платформа для генерации цен, которую можно использовать как Python-модуль или запускать из командной строки и генерировать случайные ценовые данные, максимально похожие на «настоящие цены», но с заранее заданными статистическими характеристиками. Можно задать интервал цен, таймфрейм, максимальное и минимальное значения для диапазона цен, максимальный размер для свечей, вероятность направления для очередной свечи, вероятность ценовых выбросов, количество генерируемых свечей и некоторые другие параметры.
Для дальнейшего статистического анализа цен в Python очень часто используют библиотеку Pandas. Цены, сохранённые в виде Pandas DataFrame, могут выглядеть примерно так:
Библиотека PriceGenerator позволяет:
- сохранить сгенерированные цены в .csv-формате (пример: ./media/test.csv);
- сохранить сгенерированные цены в переменную формата Pandas DataFrame для дальнейшего использования в скриптах автоматизации;
- автоматически рассчитать некоторые статистические и вероятностные характеристики сгенерированных цен и сохранить их в Markdown-формате (пример: ./media/index.html.md);
- загрузить цены реальных инструментов по модели OHLCV-candlesticks из .csv-файла и провести их статистический анализ;
- нарисовать график сгенерированных или загруженных реальных цен и сохранить его в html-формате (пример: ./media/index.html);
- сгенерированные цены, график и некоторые данные о поведении цен можно сохранить в виде обычной png-картинки (пример: ./media/index.html.png). Дополнительно на графике можно включить отображение некоторых популярных индикаторов (скользящие средние, полосы Боллинджера, ZigZag и другие).
Метод генерации цен
Генерация набора свечей заданной длины horizon
происходит по следующему (упрощённому) алгоритму:
- Определяется или случайным образом генерируется в заданном диапазоне
[minClose, maxClose]
цена открытия open первой свечи. - Случайным образом, в зависимости от значения вероятности направления очередной свечи
upCandlesProb
(по умолчанию 50%), определяется направление свечи. Еслиrandom() ≤ upCandlesProb
, то будет генерироваться up-свеча (у которойopen ≤ close
), иначе будет генерироваться down-свеча (у которойopen > close
). - После определения направления значение
close
свечи случайным образом генерируется так, чтобы для получившегося «тела» свечи было справедливо|open − close| ≤ maxCandleBody
. - Случайным образом, в зависимости от значения вероятности ценовых выбросов свечи
maxOutlier
(по умолчанию 3%), генерируются значенияhigh
иlow
свечи. Еслиrandom() ≤ maxOutlier
, то будет генерироваться свеча с ценовыми выбросами: «хвосты» свечи могут получиться достаточно большими, что будет имитировать реальные ценовые «выбросы» на рынке. Если свеча без аномалий, то «хвосты» будут генерироваться в диапазоне не более половины тела свечи. - Значение
close
сгенерированной свечи становится значением цены открытияopen
следующей свечи. - Далее повторяются шаги 2–5 пока не будет сгенерирована вся цепочка цен заданной длины
horizon
.
Все параметры можно задать после инициализации экземпляра класса PriceGenerator()
. Результат генерации цен сохраняется в переменной класса prices
в формате Pandas DataFrame и может использоваться для дальнейшего анализа.
Также, для наилучшей реалистичности, значения объёма генерируются в зависимости от предыдущего значения и вероятности выбросов. Алгоритм генератора может создавать серию свечей, образующих сложные тренды, включая различные выбросы для верхних и нижних теней свечей, их тел и объёмов (пример: ./media/long-series-example-with-realistic-volumes.png).
Примеры использования из командной строки
Как установить
Проще всего использовать установку через PyPI:
После этого можно проверить установку командой:
Генерация цен с параметрами по умолчанию
Давайте попробуем сгенерировать случайные ценовые данные (ключ --generate
) со всеми параметрам и по умолчанию и сохранить их в файл test.csv
(ключ --save-to имя_csv_файла
). Команда может выглядеть так:
pricegenerator --debug-level 10 --generate --save-to test.csv
Должен получиться вывод логов примерно следующего содержания:
Также рядом будет сохранён файл test.csv
, пример которого можно посмотреть здесь: ./media/test.csv.
Генерация цен, получение статистики и отрисовка графика
В следующем примере давайте не только сгенерируем файл с данными по ценам, но и получим некоторые статистические параметры цен, а также отрисуем цены на графике (ключ --render-bokeh имя_html_файла
). Команда может быть примерно такой:
pricegenerator --debug-level 20 --generate --save-to test.csv --render-bokeh index.html
В случае успеха вы получите вывод в лог, похожий на этот:
После выполнения команды выше вы получите три файла:
test.csv
— файл в .csv-формате, который содержит случайный набор цен, похожих на настоящие (пример: ./media/test.csv);index.html
— график цен и статистику, отрисованные при помощи библиотеки Bokeh и сохранённые в .html-файл (пример: ./media/index.html);index.html.md
— статистика в текстовом виде, сохранённая в маркдаун-формате (пример: ./media/index.html.md).
Статистика и график из сохранённых цен
Если вам нужно получить статистику по уже сгенерированным или реальным ценам, вы можете просто загрузить файл (ключ --load-from имя_csv_файла
) и отрисовать график (ключ --render-bokeh имя_html_файла
):
pricegenerator --debug-level 20 --load-from test.csv --render-bokeh index.html
В результате выполнения команды вы получите аналогичный график в index.html
и статистику в index.html.md
.
Статистика и график на упрощённом шаблоне
В примерах выше вы можете использовать отображение цен на простом, не интерактивном графике цен. Для этого используется библиотека Google Candlestick chart и простейший jinja2 шаблон. Давайте опять загрузим цены (ключ --load-from имя_csv_файла
), но отрисуем график через Google библиотеку (ключ --render-google имя_html_файла
):
pricegenerator --debug-level 20 --load-from test.csv --render-google index_google.html
В результате выполнения команды вы получите график (пример: ./media/index_google.html) и статистику в Markdown файле. Выглядеть он будет примерно так:
Переопределение параметров
Давайте изменим некоторые параметры по умолчанию, которые влияют на генерацию цен и нарисуем свой уникальный график:
pricegenerator --debug-level 10 --ticker "MY_PRICES" --precision 2 --timeframe 240 --start "2020-01-01 00:00" --horizon 150 --max-close 18000 --min-close 14000 --init-close 15000 --max-outlier 1000 --max-body 500 --max-volume 400000 --up-candles-prob 0.48 --outliers-prob 0.05 --trend-deviation 0.03 --zigzag 0.03 --generate --render-bokeh index_custom.html
--ticker "MY_PRICES"
— установить название графика для ценовой последовательности как MY_PRICES;--precision 2
— установить точность, то есть количество знаков после запятой;--timeframe 240
— одна свеча должна отражать изменение цен за 4 часа (240 минут);--start "2020-01-01 00:00"
— дата и время первой свечи 2020-01-01 00:00;--horizon 150
— сгенерировать 150 свечей;--max-close 18000
— максимальная цена закрытия у любой свечи должна быть не больше 18000;--min-close 14000
— минимальная цена закрытия у любой свечи должна быть не больше 14000;--init-close 15000
— цена закрытия «предыдущей» и, соответственно, цена открытия первой генерируемой свечи должна быть равной 15000;--max-outlier 1000
— если у свечи есть «выбросы» и «хвосты» то они должны быть не больше чем 1000;--max-body 500
— максимальный размер «тела» свечи должен быть не более 500;--max-volume 400000
— максимальный объём торгов для каждой свечи должен быть не более 400000;--up-candles-prob 0.48
— установить вероятность того, что очередная свеча будет вверх, равной 0.48 (48%);--outliers-prob 0.05
— установить вероятность появления выбросов равной 0.05 (5%);--trend-deviation 0.03
— для определения тренда относительное изменение цен закрытия первой и последней свечей должно отличаться на ±0.03 (3%);--zigzag 0.03
— относительная разница между двумя точками ZigZag индикатора;--generate
— запустить генерацию цен;--render-bokeh index_custom.html
— сохранить сгенерированные цены в файлindex_custom.html
и открыть его в браузере.
По умолчанию используется светлая тема для графиков. Но если вы используете ключ --render-bokeh
, вы также можете добавить к команде ключ --dark
. В этом случае график будет отрисован в тёмном стиле:
В результате выполнения команды у вас получится свой уникальный график случайных цен с переопределёнными базовыми параметрами генератора. У нас получились вот такие артефакты:
- картинка с изображением цен ./media/index_custom.html.png;
- график цен и статистика ./media/index_custom.html;
- статистика в текстовом виде ./media/index_custom.html.md.
Разделение данных по трендам
Для указания трендов существуют два консольных ключа: --split-trend
и --split-count
. Они указывают на внешний вид цепочки мини-трендов и количество свечей в каждом тренде.
Ключ --split-trend
показывает вид движения, например --split-trend=/\-
означает, что будет сгенерирована такая цепочка свечей, что сначала тренд будет возрастающий в её первой части, затем тренд сменится на нисходящий, и в последней цепочке свечей тренда не будет.
Также есть возможность указания направлений генерируемых трендов с помощью слов или букв. Слова могут быть следующими: up
, down
или no
, а буквы могут быть такими: u
, d
или n
. Эти слова и знаки могут использоваться вместе с ключом --split-trend
, в дополнение к уже имеющейся возможности указывать тренд символами /\-
. Для разделения слов и букв используется символ дефиса, например, --split-trend=up-down-no-up
или --split-trend=u-d-n-u
.
Ключ --split-count
устанавливает количество свечей в каждом мини-тренде, например --split-count 5 10 15
означает, что в сгенерированной цепочке свечей будет три мини-тренда с 5, 10 и 15 свечами в каждом из них.
Чтобы понять, как это работает, попробуйте один из следующих примеров:
pricegenerator --horizon 300 --render-bokeh index.html --split-trend="/\-" --split-count 50 100 150 --generate
pricegenerator --horizon 300 --render-bokeh index.html --split-trend="\/\" --split-count 50 100 150 --generate
pricegenerator --horizon 300 --render-bokeh index.html --split-trend="\-/" --split-count 50 100 150 --generate
pricegenerator --horizon 100 --render-bokeh index.html --split-trend="/\/\" --split-count 20 30 30 20 --generate
Для последнего примера вы можете получить картинку движения цены, похожую на следующую (пример: ./media/index_with_trends.html.png).
Работа с PriceGenerator через импорт модуля
Давайте рассмотрим пример генерации цен с некоторыми изменёнными параметрами, сохраним их в Pandas DataFrame и нарисуем график. Просто сохраните и запустите следующий скрипт:
from pricegenerator.PriceGenerator import PriceGenerator, uLogger from datetime import datetime, timedelta
# Отключаем логирование, чтобы не мешалось: uLogger.setLevel(0)
# --- Инициализируем экземпляр класса генератора и настраиваем некоторые параметры: priceModel = PriceGenerator() priceModel.precision = 1 # сколько знаков после запятой должно быть в сгенерированных ценах priceModel.ticker = "MY_GENERATED_PRICES" # произвольное имя (тикер) генерируемых цен priceModel.timeframe = timedelta(days=1) # временной интервал между генерируемыми свечами, 1 час по умолчанию priceModel.timeStart = datetime.today() # с какой даты начинать генерацию свечей, по умолчанию с текущего времени priceModel.horizon = 60 # сколько сгенерировать свечей, их должно быть не меньше 5-ти, по умолчанию 100 priceModel.maxClose = 16000 # максимальная цена закрытия свечи во всей цепочке цен, по умолчанию генерируется случайно в интервале (70, 90), это немного похоже на цены USDRUB priceModel.minClose = 13800 # минимальная цена закрытия свечи во всей цепочке цен, по умолчанию генерируется случайно в интервале (60, 70), это немного похоже на цены USDRUB priceModel.initClose = 14400 # если цена указана, то она будет ценой close (как бы "предыдущей" свечи) и одновременно ценой open самой первой свечи в генерируемой цепочке. None по умолчанию означает, что цена open первой свечи будет сгенерирована случайно в интервале (minClose, maxClose) priceModel.maxOutlier = 500 # Максимальное значение для ценовых выбросов "хвостов" у свечей. None по умолчанию означает, что выбросы будут не более чем на (maxClose - minClose) / 10 priceModel.maxCandleBody = 300 # Максимальное значение для размера тел свечей abs(open - close). None по умолчанию означает, что тело свечи может быть не более чем 90% от размера максимального выброса: maxOutlier * 90% priceModel.maxVolume = 400000 # максимальное значение объёма торгов для одной свечи, по умолчанию берётся случайным образом из интервала (0, 100000) priceModel.upCandlesProb = 0.46 # вероятность того, что очередная генерируемая свеча будет вверх, 50% по умолчанию priceModel.outliersProb = 0.11 # вероятность того, что очередная генерируемая свеча будет иметь ценовой "выброс", 3% по умолчанию priceModel.trendDeviation = 0.005 # колебание цены close первой и последней свечей для определения тренда. "NO trend" если разница меньше этого значения, по умолчанию ±0.005 или ±0.5%. priceModel.zigzag = 0.05 # относительная разница между двумя точками ZigZag индикатора, 0.03 по умолчанию priceModel._chartTitle = "" # произвольный заголовок графика, обычно генерируется автоматически
# Цены пока не сгенерированы и не загружены, проверим это: print("Current prices:\n{}".format(priceModel.prices))
# Запускаем генератор цен, при этом они будут сохранены # в переменной priceModel.prices в формате Pandas DataFrame: priceModel.Generate()
# Убеждаемся, что цены сгенерированы: print("Generated prices as Pandas DataFrame:\n{}".format(priceModel.prices))
# Словарь с посчитанной статистикой сохраняется в переменную self.stat: print("Dict with statistics:\n{}".format(priceModel.stat))
# Сохраняем OHLCV-цены в .csv-файл priceModel.SaveToFile(fileName="test.csv")
# Сохраняем график цен в html-файл и сразу открываем его в браузере. # Статистика в текстовом виде будет автоматически сохранена в Markdown-файле с именем fileName + ".md". priceModel.RenderBokeh(fileName="index.html", viewInBrowser=True)
# Вместо библиотеки Bokeh вы можете отрисовать цены на простом, не интерактивном графике, # через библиотеку Google Candlestick chart. Просто раскомментируйте строчки ниже. # Перед вызовом priceModel.RenderGoogle(), вы можете задать свой шаблон в переменной self.j2template # priceModel.j2template = "google_template_example.j2" # полный путь до шаблона или мультистроковая переменная с jinja2-шаблоном # priceModel.RenderGoogle(fileName="index.html", viewInBrowser=True)
При запуске скрипта вы получите аналогичный вывод в логи, три файла: test.csv
, index.html
и index.html.md
. Вы можете самостоятельно поэкспериментировать с параметрами класса PriceGenerator()
для генерации цен подходящих под ваши условия.
Также вы можете манипулировать графиком и добавлять новые линии или маркеры на основной график. Используйте для этого параметры markers
и lines
.
Pandas DataFrame markers содержит ряды, которые показывают, какой маркер нанести для той или иной свечи. Маркер представляет собой некоторый символ, например, ×
, ↓
или ↑
или какой-либо другой. Датафрейм с маркерами должен содержать, как минимум, два ряда данных. Это столбец datetime
, с датой и временем, и один из столбцов или все сразу: markersUpper
, markersCenter
или markersLower
, с маркерами, которые нужно поставить сверху, по центру или снизу свечи соответственно. Длины рядов с маркерами должны быть равны длине основного ряда со свечами.
Лист lines
содержит ряды Pandas DataFrame с точками новых линий, которые нужно разместить на основном свечном графике. Каждый датафрейм с линиями должен содержать, как минимум, два столбца. Это datetime
(первый столбец), с датой и временем, и произвольно названный второй столбец custom_line_name
, с y-координатами точек линии. Длины рядов с точками линий должны быть равны длине основного ряда со свечами.
from pricegenerator.PriceGenerator import PriceGenerator, uLogger from datetime import datetime, timedelta import pandas as pd
uLogger.setLevel(0) # Отключаем излишнее логирование для этого примера.
# Инициализируем PriceGenerator: priceModel = PriceGenerator() priceModel.ticker = "TEST_PRICES" priceModel.precision = 0 priceModel.timeframe = timedelta(days=1) priceModel.timeStart = datetime.today() priceModel.horizon = 75 priceModel.maxClose = 140 priceModel.minClose = 40 priceModel.initClose = 50 priceModel.maxOutlier = 35 priceModel.maxCandleBody = 15 priceModel.maxVolume = 400000 priceModel.upCandlesProb = 0.51 priceModel.outliersProb = 0.1 priceModel.trendDeviation = 0.005 priceModel.trendSplit = "/\/" priceModel.splitCount = [40, 10, 25]
priceModel.Generate() # Генерируем основную серию свечей.
# Давайте построим новую среднюю линию на основном графике и установим маркеры сверху, по центру и снизу свечей: priceModel.prices["avg"] = priceModel.prices.low + (priceModel.prices.high - priceModel.prices.low) / 2 priceModel.prices["markersUpper"] = pd.Series(["↓"] * len(priceModel.prices.high)) priceModel.prices["markersCenter"] = pd.Series(["×"] * len(priceModel.prices.avg)) priceModel.prices["markersLower"] = pd.Series(["↑"] * len(priceModel.prices.low)) priceModel.RenderBokeh( fileName="index1.html", viewInBrowser=True, darkTheme=True, # Установите `False` для переключения светлой темы. markers=priceModel.prices[["datetime", "markersUpper", "markersCenter", "markersLower"]], lines=[priceModel.prices[["datetime", "avg"]]], showStatOnChart=True, showControlsOnChart=True, inline=False, # Раскомментируйте, если скрипт запускается в Jupyter Notebook. )
На выходе получаются промаркированными все свечи (пример: ./media/marked_dark.png). При желании в коде можно пропустить некоторые маркеры в соответствующих сериях.
На этом всё. Успехов вам в автоматизации и тестировании биржевых торговых стратегий! 🚀 😉✌️