Торговая стратегия следования за трендом Renko

  • Адаптивная торговая стратегия следования за трендом на основе Renko
  • Прибыльная динамическая торговая стратегия Ренко на Python

Адаптивная торговая стратегия следования за трендом на основе Renko

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

  1. Понятие
  2. Описание алгоритма
  3. Разработка торговой стратегии
  4. Тестирование на истории и анализ результата
  5. Дальнейшее обсуждение проблем
  6. Выводы

Понятие

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

Оптимизация качества с течением времени в этом контексте называется «адаптивностью».

Описание алгоритма

Эта торговая стратегия является типичным следованием за трендом, но следование основано на последнем направлении Ренко, а не на цене скользящей средней. Основные шаги:

  1. Если диаграмма Ренко пуста, постройте диаграмму, используя оптимизацию размера кирпича. Адаптивность подразумевает, что уровень волатильности может быть важен для оптимизации процесса. В этом примере оптимальный размер кирпича находится внутри IQR абсолютных изменений цен (например, ежедневно) за последние N дней. Кроме того, вы можете выбрать любой диапазон для оптимизации (на основе индикатора ATR, фиксированный или в процентах от последней цены).

2. Если график Ренко не пустой, то получаем последнюю рыночную цену и прибавляем к Ренко. Если новый кирпич не построен, то переходите к следующей итерации. В противном случае, если новый кирпич следует текущему направлению, то часть текущего положения должна быть покрыта (0 — 100%). Если новый кирпич построен в противоположном направлении, то текущая позиция должна быть закрыта, это означает, что тренд изменился. График Ренко должен быть пустым.

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

Разработка торговой стратегии

Я буду использовать фреймворк Catalyst для разработки торговой стратегии. Как установить фреймворк и несколько примеров вы можете найти на сайте.

Catalyst — это библиотека алгоритмической торговли криптоактивами, написанная на Python. Это позволяет легко выражать торговые стратегии и тестировать их на исторических данных (с ежедневным и минутным разрешением), предоставляя аналитику и понимание эффективности конкретной стратегии.

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

Прежде всего, необходимо указать необходимые библиотеки, модуль pyrenko вы можете найти на github.

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import pyrenko
import scipy.optimize as opt
from scipy.stats import iqr
import talib

from catalyst import run_algorithm
from catalyst.api import (record, symbol, order_target, order_target_percent, get_datetime)

Немного информации из учебника:

Каждый алгоритм catalyst состоит как минимум из двух функций, которые вы должны определить:

initialize(context)

handle_data(context, data)

Перед запуском алгоритма catalyst вызывает функцию initialize() и передает context переменную. context — это постоянное пространство имен для хранения переменных, необходимых для доступа от одной итерации алгоритма к другой.

После инициализации алгоритма catalyst вызывает функцию handle_data() на каждой итерации, то есть по одной в день (ежедневно) или раз в минуту handle_data()минуту), в зависимости от частоты, которую мы выбираем для запуска нашего моделирования. На каждой итерации handle_data() передает одну и ту же contextпеременную и событийный кадр, называемый data, содержащими текущий торговый бар с ценами открытия, максимума, минимума и закрытия (OHLC), а также объем для каждого криптоактива в вашей вселенной.

Наша функция инициализации выглядит так:

def initialize(context):
    context.asset = symbol('eth_btc')

    context.leverage = 1.0              # 1.0 - no leverage
    context.n_history = 24 * 15         # Number of lookback bars for modelling
    context.tf = '60T'                  # How many minutes in a timeframe
    context.model = pyrenko.renko()     # Renko object
    context.part_cover_ratio = 0.166    # Partially cover position ratio
    context.last_brick_size = 0.0       # Last optimal brick size (just for storing)
    
    context.set_benchmark(context.asset)
    context.set_commission(maker = 0.001, taker = 0.002)
    context.set_slippage(slippage = 0.0005)

Мы работаем с криптопарой ETH/BTC. Базовый таймфрейм – часовой (60Т). На графике Ренко используются данные за 15 (15*24 часа) дней. Мы покрываем 16,6% от суммы позиции после каждого нового кирпича в текущем направлении. Комиссия аналогична комиссии на бирже Bitfinex. Кроме того, мы используем значение проскальзывания, оно больше похоже на то, как это будет происходить в реальном режиме.

Общая логика алгоритма заключается в handle_data функции:

# Function for optimization
def evaluate_renko(brick, history, column_name):
    renko_obj = pyrenko.renko()
    renko_obj.set_brick_size(brick_size = brick, auto = False)
    renko_obj.build_history(prices = history)
    return renko_obj.evaluate()[column_name]

def handle_data(context, data):
    current_time = get_datetime().time()
    if current_time.hour == 0 and current_time.minute == 0:
        print('Current date is ' + str(get_datetime().date()))

    # When model is empty
    if len(context.model.get_renko_prices()) == 0:
        context.model = pyrenko.renko()
        history = data.history(context.asset,
            'price',
            bar_count = context.n_history, 
            frequency = context.tf
            )

        # Get daily absolute returns
        diffs = history.diff(24).abs()
        diffs = diffs[~np.isnan(diffs)]
        # Calculate IQR of daily returns
        iqr_diffs = np.percentile(diffs, [25, 75])

        # Find the optimal brick size
        opt_bs = opt.fminbound(lambda x: -evaluate_renko(brick = x,
            history = history, column_name = 'score'),
        iqr_diffs[0], iqr_diffs[1], disp=0)

        # Build the model
        print('REBUILDING RENKO: ' + str(opt_bs))
        context.last_brick_size = opt_bs
        context.model.set_brick_size(brick_size = opt_bs, auto = False)
        context.model.build_history(prices = history)
        
        # Open a position
        order_target_percent(context.asset, context.leverage * context.model.get_renko_directions()[-1])

        # Store some information
        record(
            rebuilding_status = 1,
            brick_size = context.last_brick_size,
            price = history[-1],
            renko_price = context.model.get_renko_prices()[-1],
            num_created_bars = 0,
            amount = context.portfolio.positions[context.asset].amount
        )

    else:
        last_price = data.history(context.asset,
                              'price',
                              bar_count = 1,
                              frequency = '1440T',
                              )

        # Just for output and debug
        prev = context.model.get_renko_prices()[-1]
        prev_dir = context.model.get_renko_directions()[-1]
        num_created_bars = context.model.do_next(last_price)
        if num_created_bars != 0:
            print('New Renko bars created')
            print('last price: ' + str(last_price))
            print('previous Renko price: ' + str(prev))
            print('current Renko price: ' + str(context.model.get_renko_prices()[-1]))
            print('direction: ' + str(prev_dir))
            print('brick size: ' + str(context.model.brick_size))

        # Store some information
        record(
            rebuilding_status = 0,
            brick_size = context.last_brick_size,
            price = last_price,
            renko_price = context.model.get_renko_prices()[-1],
            num_created_bars = num_created_bars,
            amount = context.portfolio.positions[context.asset].amount
        )

        # If the last price moves in the backward direction we should rebuild the model
        if np.sign(context.portfolio.positions[context.asset].amount * context.model.get_renko_directions()[-1]) == -1:
            order_target_percent(context.asset, 0.0)
            context.model = pyrenko.renko()
        # or we cover the part of the position
        elif context.part_cover_ratio > 0.0 and num_created_bars != 0:
            order_target(context.asset, context.portfolio.positions[context.asset].amount * (1.0 - context.part_cover_ratio))

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

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

def analyze(context, perf):
    # Summary output
    print('Total return: ' + str(perf.algorithm_period_return[-1]))
    print('Sortino coef: ' + str(perf.sortino[-1]))
    print('Max drawdown: ' + str(np.min(perf.max_drawdown)))
    print('Alpha: ' + str(perf.alpha[-1]))
    print('Beta: ' + str(perf.beta[-1]))
    perf.to_csv('perf_' + str(context.asset) + '.csv')
    
    f = plt.figure(figsize = (7.2, 7.2))

    # Plot performance
    ax1 = f.add_subplot(611)
    ax1.plot(perf.algorithm_period_return, 'blue')
    ax1.plot(perf.benchmark_period_return, 'red')
    ax1.set_title('Performance')
    ax1.set_xlabel('Time')
    ax1.set_ylabel('Return')

    # Plot price and renko price
    ax2 = f.add_subplot(612, sharex = ax1)
    ax2.plot(perf.price, 'grey')
    ax2.plot(perf.renko_price, 'yellow')
    ax2.set_title(context.asset)
    ax2.set_xlabel('Time')
    ax2.set_ylabel('Price')

    # Plot brick size
    ax3 = f.add_subplot(613, sharex = ax1)
    ax3.plot(perf.brick_size, 'blue')
    xcoords = perf.index[perf.rebuilding_status == 1]
    for xc in xcoords:
        ax3.axvline(x = xc, color = 'red')
    ax3.set_title('Brick size and rebuilding status')
    ax3.set_xlabel('Time')
    ax3.set_ylabel('Size and Status')

    # Plot renko_price
    ax4 = f.add_subplot(614, sharex = ax1)
    ax4.plot(perf.num_created_bars, 'green')
    ax4.set_title('Number of created Renko bars')
    ax4.set_xlabel('Time')
    ax4.set_ylabel('Amount')

    # Plot amount of asset in portfolio
    ax5 = f.add_subplot(615, sharex = ax1)
    ax5.plot(perf.amount, 'black')
    ax5.set_title('Asset amount in portfolio')
    ax5.set_xlabel('Time')
    ax5.set_ylabel('Amount')

    # Plot drawdown
    ax6 = f.add_subplot(616, sharex = ax1)
    ax6.plot(perf.max_drawdown, 'yellow')
    ax6.set_title('Max drawdown')
    ax6.set_xlabel('Time')
    ax6.set_ylabel('Drawdown')

    plt.show()

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

run_algorithm(
    capital_base = 10,
    data_frequency = 'daily',
    initialize = initialize,
    handle_data = handle_data,
    analyze = analyze,
    exchange_name = 'bitfinex',
    quote_currency = 'btc',
    start = pd.to_datetime('2017-12-1', utc = True),
    end = pd.to_datetime('2018-11-12', utc = True))

В этом примере мы работаем с ежедневной подачей данных (параметр data_frequency).

Тестирование на истории и анализ результата

Запустим наш скрипт в среде Catalyst по команде:

python renko_trend_following.py

Получаем примерно так:

Основные метрики вы можете найти в выходных данных окна терминала, эти метрики мы выводим в функции анализа. Общая отдача алгоритма составляет 252,55% при максимальной просадке -18,74%. Это неплохо для почти 1 года. Вы можете использовать коэффициент Сортино для сравнения разных алгоритмов, эту метрику я рассматривал в этой статье. Бета очень близка к 0, а альфа положительная, это означает, что наш алгоритм нейтрален к рынку, и мы превзошли бенчмарк. Если вы не знакомы с этими показателями, я рекомендую вам эту статью.

Синяя линия на первом графике — это эквити алгоритма, красная линия — эквити бенчмарка (актив ETH/BTC). Второй график содержит цену ETH/BTC (серый цвет) и цену Renko (желтый цвет). Размер кирпича показан на третьем графике (синий цвет), вертикальные красные линии — это моменты времени, когда график Ренко был перестроен. Количество созданных кирпичей Ренко показано на четвертом графике, сумма позиции показана на пятом графике. Последний график содержит просадку.

Давайте получим дополнительную информацию на основе нашего результата. При дальнейшем анализе используется переменная perf в csv-формате. Для этого я использую библиотеку pyfolio.

Это библиотека Python для анализа производительности и рисков финансовых портфелей, разработанная Quantopian Inc. Он хорошо работает с библиотекой тестирования с открытым исходным кодом Zipline.

Прежде всего, нарисуйте доходность алгоритма и сравните эквити с бенчмарком:

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

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

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

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

Рассмотрим волатильность алгоритма как месячную скользящую среднюю:

Снижение коэффициента Шарпа (например, отрицательного) также может быть триггером для повторной оптимизации процесса в конвейере жизненного цикла:

Дальнейшее обсуждение проблем

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

  1. Попытайтесь использовать минутное разрешение данных, чтобы учесть данные, которые мы получаем в течение дня. Теперь алгоритм использует только дневное разрешение, это означает, что мы теряем данные и движение цены.
  2. Измените рыночные ордера на лимитные. Это позволит снизить комиссии, т.к. комиссия тейкера выше, чем комиссия мейкера . Некоторые биржи даже платят вам за лимитные ордера, это называется рибейтом, это своего рода вознаграждение.
  3. Проводите множество экспериментов с различными активами, чтобы создать надежный портфель активов и настроить управление капиталом между ними.
  4. Разработайте и следуйте правилу реоптимизации — пересылки, чтобы получить момент, когда мы должны изменить некоторые параметры модели (длину истории, коэффициент покрытия, таймфрейм и т.д.). Это правило включает в себя частоту оптимизации, временные периоды для оптимизации и пересылки (или пересылки) процессов, минимальные требования к метрикам для принятия алгоритма как рабочего.
  5. Разработайте или выберите фреймворк выполнения для запуска алгоритма в рабочем режиме. Даже если вы сможете получить надежную торговую стратегию, одобренную на тоннах бэктестов, вы можете потерпеть неудачу в реальном режиме, потому что вы получите много ошибок или несовершенств в инфраструктуре (внутри или за пределами вашей экосистемы). Например, вы можете использовать Catalyst в режиме тестирования на истории, но вы не можете использовать его в продакшене для этого алгоритма, потому что Catalyst сейчас не поддерживает торговлю на маржинальном счете.

Выводы

  1. Создал алгоритмическую торговую стратегию, основанную на теоретических исследованиях. Этот алгоритм пытается адаптироваться к уровню волатильности, уменьшить шум и следовать тренду.
  2. Алгоритм имеет положительный результат. Продемонстрированы различные метрики и графики производительности.
  3. Предложен совет о том, как улучшить это исследование.
  4. Исходный код вы можете получить на github (скрипт-катализатор и ipython-notebook для расширенной аналитики).

Пример оптимизации данной стратегии.

Прибыльная динамическая торговая стратегия Ренко на Python

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

Почему вы хотите использовать графики Ренко для своей торговой стратегии?

Иллюстрация стейкинга кирпича Ренко

Чтобы объяснить это, просто представьте, что вы строите дом из каких-то особых кирпичей, называемых ренко, или, еще лучше, «ренга» — как они называются по-японски. Каждый кирпич представляет собой фиксированное движение цены, а не фиксированный временной интервал, необходимый для укладки. В случае с кирпичами Ренко важен размер штабеля, а не время, которое потребовалось бы для их стейкинга. Таким образом, за 2 часа вы можете застолбить один кирпич из 5 кирпичей или любое количество кирпичей — важен размер, а не продолжительность. Это то, что делает Renko таким интересным, вам не нужно следить за каждым тиканьем часов, как это делает большинство инструментов для построения графиков.

Итак, теперь вы можете задаться вопросом, каковы критерии ставок, если продолжительность, необходимая для ставок, не имеет значения?

Проще говоря, всякий раз, когда цена говорит; движется вверх на 20 долларов, вы кладете кирпич вверх, а когда цена падает на -20 долларов, вы кладете кирпич вниз. Довольно простая концепция, не так ли? Обратитесь к иллюстрации на рисунке выше.

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

Традиционные подсвечники

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

Теперь давайте перейдем непосредственно к коду;

Шаг 1. Импортируйте зависимости.import ccxt
from datetime import datetime
import pandas as pd
import scipy.optimize as opt
import numpy as np
import pandas_ta as ta
from stocktrends import Renko
import mplfinance as mpf
import matplotlib.pyplot as plt

Первое, что вам нужно сделать, это подготовить среду кодирования, а затем импортировать зависимости. Как видите, для сбора данных мы используем пакет «ccxt«. Он предоставляет данные с различных криптобирж. В этом примере мы используем его для получения данных с binance. Другими важными библиотеками для выполнения стратегии являются старые добрые pandasnumpy и Scipy для статистических вычислений. Библиотека «pandas_ta« предназначена для доступа к техническим индикаторам — она построена на основе популярной библиотеки технического анализа Python «TA-Lib«. Библиотека «stocktrends» используется для вычисления кирпичей Ренко и генерации датафрейма Ренко. И, наконец, для визуализации данных воспользуемся библиотеками «mplfinance» и «matplotlib«.

Шаг 2: Получение данных об активе из ccxtdef fetch_asset_data(symbol, start_date, interval, exchange):
# Convert start_date to milliseconds timestamp
start_date_ms = exchange.parse8601(start_date)

ohlcv = exchange.fetch_ohlcv(symbol, interval, since=start_date_ms)

header = [«Date», «Open», «High», «Low», «Close», «Volume»]
df = pd.DataFrame(ohlcv, columns=header)
df[‘Date’] = pd.to_datetime(df[‘Date’], unit=’ms’)
df.set_index(«Date», inplace=True)
return df

Функция «fetch_asset_data» возвращает данные в виде кадра данных pandas, передавая аргументы symbol, start_date, interval и exchange. Символ — это пара активов (например, ETH/USDT), дата начала — дата начала сбора данных, интервал — это время, необходимое для генерации одной свечи (которая формирует строку в датафрейме), а биржа — это брокер (например, Binance).

Шаг 3: Сгенерируйте кирпичи Ренко в качестве фрейма данныхdef renko_data(data): # Get the Renko data
# For a stable backtest we need to drop tha last row of the dataframe
# This is because you want to backtest with any live candles
# In this case the CCXT’s last data points (last row) is live so that’s why we need to drop it
# In other words you want completed candles
data.drop(data.index[-1], inplace=True)

data[‘ATR’] = ta.atr(high=data[‘High’], low=data[‘Low’], close=data[‘Close’], length=14)
data.dropna(inplace=True)

def evaluate_brick_size_atr(brick_size, atr_values):
# Calculate number of bricks based on ATR and brick size
num_bricks = atr_values // brick_size
return np.sum(num_bricks)

# Get optimised brick size
brick = opt.fminbound(lambda x: -evaluate_brick_size_atr(x, data[‘ATR’]), np.min(
data[‘ATR’]), np.max(data[‘ATR’]), disp=0)

def custom_round(number):
# List of available rounding values
rounding_values = [0.001, 0.005, 0.01, 0.05,
0.1, 0.5, 1] + list(range(5, 100, 5))
rounding_values += list(range(100, 1000, 50)) + \
list(range(1000, 10000, 100))

# Finding the closest rounding value
closest_value = min(rounding_values, key=lambda x: abs(x — number))
return closest_value
brick_size = custom_round(brick)
print(f’brick size: {brick_size}’)
data.reset_index(inplace=True)
data.columns = [i.lower() for i in data.columns]
df = Renko(data)
df.brick_size = brick_size
renko_df = df.get_ohlc_data()

# Capitalize the Column names for ohlc
renko_df.rename(columns={‘date’: ‘Date’, ‘open’: ‘Open’, ‘high’: ‘High’, ‘low’: ‘Low’, ‘close’: ‘Close’}, inplace=True)

# Return the ohlc colums to floats
renko_df[‘Open’] = renko_df[‘Open’].astype(float)
renko_df[‘High’] = renko_df[‘High’].astype(float)
renko_df[‘Low’] = renko_df[‘Low’].astype(float)
renko_df[‘Close’] = renko_df[‘Close’].astype(float)
return renko_df

Функция renko_data вычисляет данные Ренко для пары активов «ohlc » в качестве входных данных. Average True Range вычисляется и используется для определения оптимального размера кирпича для графика Ренко. Размер кирпича округляется для однородности. Функция возвращает данные Ренко в виде фрейма данных pandas со столбцами Date, Open, High, Low и Close, где каждое значение преобразуется в тип данных с плавающей запятой.

Важно обеспечить надежные и надежные результаты тестирования на истории. Таким образом, мы отбрасываем последнюю строку датафрейма, содержащую оперативные данные. Обновление системы стратегий оперативными данными приводит к несоответствиям. Поэтому очень важно полагаться только на предыдущие данные, представленные завершенными свечами. Это достигается с помощью следующей строки «data.drop(data.index[-1], inplace=True)«.

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

Шаг 4: Сгенерируйте сигналы и позицииdef generate_positions(renko_df):
# Rename the index of the renko data to brick
renko_df.index.name = «brick»

# Initialize signals list with 0 (no signal) for the first brick
signals = []

for i in range(0, len(renko_df)):
# Get the current and previous brick colors
is_current_green = renko_df[‘Close’].iloc[i] > renko_df[‘Open’].iloc[i]
is_prev_green = renko_df[‘Close’].iloc[i —
1] > renko_df[‘Open’].iloc[i — 1]

if is_current_green and not is_prev_green:
signals.append(1) # Buy signal when the brick changes to green
elif is_current_green and is_prev_green:
signals.append(1) # Hold signal when the brick remains green
elif not is_current_green and is_prev_green:
signals.append(-1) # Sell signal when the brick changes to red
elif not is_current_green and not is_prev_green:
signals.append(-1) # Hold signal when the brick remains red

# Add the ‘signals’ column to the DataFrame
renko_df[‘signals’] = signals
renko_df[‘signals’] = renko_df[«signals»].shift(1) #Remove look ahead bias
renko_df.fillna(0.0, inplace=True)
renko_df.set_index(«Date», inplace=True)

# Create the Positions
# Initialize positions with nan
renko_df[‘buy_positions’] = np.nan
renko_df[‘sell_positions’] = np.nan

renko_df.index.freq = pd.infer_freq(renko_df.index)

# Update the buy_positions with the close price where the signal is 1 and the previous signal is not equal to the current signal
buy_signal_indices = renko_df[(renko_df[‘signals’] == 1) & (renko_df[‘signals’] != renko_df[‘signals’].shift(1))].index
renko_df.loc[buy_signal_indices, ‘buy_positions’] = renko_df.loc[buy_signal_indices, ‘Close’]

# Update the sell_positions with close price where the signal is -1 and the previous signal is not equal to the current signal
sell_signal_indices = renko_df[(renko_df[‘signals’] == -1) & (renko_df[‘signals’] != renko_df[‘signals’].shift(1))].index
renko_df.loc[sell_signal_indices, ‘sell_positions’] = renko_df.loc[sell_signal_indices, ‘Close’]

# Reset duplicate dates in the positions to nan, i.e where the previous date is equal to the current date
renko_df.loc[renko_df.index == pd.Series(renko_df.index).shift(1), [‘buy_positions’, ‘sell_positions’]] = np.nan

return renko_df

Функция «generate_positions» обрабатывает датафрейм ренко (renko_df) для генерации сигналов с последующими торговыми позициями на покупку и продажу. Для получения надежных результатов тестирования на истории мы удаляем смещение с помощью «renko_df[‘signals’] = renko_df[«signals»].shift(1)«; В противном случае результат будет неоднозначно завышенным и неточным.

Шаг 5: Рассчитайте производительностьdef calculate_strategy_performance(strategy_df, capital=100, leverage=1):
# Initialize the performance variables
cumulative_balance = capital
investment = capital
pl = 0
max_drawdown = 0
max_drawdown_percentage = 0

# Lists to store intermediate values for calculating metrics
balance_list = [capital]
pnl_list = [0]
investment_list = [capital]
peak_balance = capital

# Loop from the second row (index 1) of the DataFrame
for index in range(1, len(strategy_df)):
row = strategy_df.iloc[index]

# Calculate P/L for each trade signal
if row[‘signals’] == 1:
pl = ((row[‘Close’] — row[‘Open’]) / row[‘Open’]) * \
investment * leverage
elif row[‘signals’] == -1:
pl = ((row[‘Open’] — row[‘Close’]) / row[‘Close’]) * \
investment * leverage
else:
pl = 0

# Update the investment if there is a signal reversal
if row[‘signals’] != strategy_df.iloc[index — 1][‘signals’]:
investment = cumulative_balance

# Calculate the new balance based on P/L and leverage
cumulative_balance += pl

# Update the investment list
investment_list.append(investment)

# Calculate the cumulative balance and add it to the DataFrame
balance_list.append(cumulative_balance)

# Calculate the overall P/L and add it to the DataFrame
pnl_list.append(pl)

# Calculate max drawdown
drawdown = cumulative_balance — peak_balance
if drawdown < max_drawdown:
max_drawdown = drawdown
max_drawdown_percentage = (max_drawdown / peak_balance) * 100

# Update the peak balance
if cumulative_balance > peak_balance:
peak_balance = cumulative_balance

# Add new columns to the DataFrame
strategy_df[‘investment’] = investment_list
strategy_df[‘cumulative_balance’] = balance_list
strategy_df[‘pl’] = pnl_list
strategy_df[‘cumPL’] = strategy_df[‘pl’].cumsum()

# Calculate other performance metrics (replace with your calculations)
overall_pl_percentage = (
strategy_df[‘cumulative_balance’].iloc[-1] — capital) * 100 / capital
overall_pl = strategy_df[‘cumulative_balance’].iloc[-1] — capital
min_balance = min(strategy_df[‘cumulative_balance’])
max_balance = max(strategy_df[‘cumulative_balance’])

# Print the performance metrics
print(«Overall P/L: {:.2f}%».format(overall_pl_percentage))
print(«Overall P/L: {:.2f}».format(overall_pl))
print(«Min balance: {:.2f}».format(min_balance))
print(«Max balance: {:.2f}».format(max_balance))
print(«Maximum Drawdown: {:.2f}».format(max_drawdown))
print(«Maximum Drawdown %: {:.2f}%».format(max_drawdown_percentage))

# Return the Strategy DataFrame
return strategy_df

Вы хотите знать, как работает ваша стратегия, проверяя такие показатели, как прибыль и убыток (p/l), рост счета (баланс) и максимальная просадка. Функция «calculate_strategy_performance» принимает аргументы «strategy_df» — которые возвращаются от функции «generate_positions», «капитал», который представляет собой первоначальную сумму инвестиций, а затем «кредитное плечо», которое является множителем заимствования у брокера. Значение по умолчанию для «leverage» равно «x1», что означает, что кредитное плечо по умолчанию не принимается. Тем не менее, не стесняйтесь экспериментировать с желаемой суммой кредитного плеча.

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

Функция возвращает датафрейм «strategy_df«, который содержит метрики производительности, и выводит некоторые другие метрики, такие как общая прибыль/убыток, баланс и максимальная просадка.

Максимизация прибыли за счет сложного процента

Эта функция использует стратегию сложного процента, где инвестиции для каждой новой позиции основаны на доступном балансе, включая прошлые прибыли от закрытых позиций. Фрагмент кода клавиши из функции: «cumulative_balance += pl«.

Шаг 6: Рассчитайте производительность# Plot the Candlestick data, the buy and sell signal markers
def plot_candlestick(df):
# Plot the candlestick chart
mpf.plot(df, type=’candle’, style=’charles’, datetime_format=’%Y-%m-%d’, xrotation=20,
title=str(symbol + ‘ Candlestick Chart’), ylabel=’Price’, xlabel=’Date’, scale_width_adjustment=dict(candle=2))

# Plot the Renko Data.
def plot_renko(renko_df):
# Plot the Renko Chart
adp = [mpf.make_addplot(renko_df[‘buy_positions’], type=’scatter’, marker=’^’, label= «Buy», markersize=80, color=’#2cf651′),
mpf.make_addplot(renko_df[‘sell_positions’], type=’scatter’, marker=’v’, label= «Sell», markersize=80, color=’#f50100′)
]
mpf.plot(renko_df, addplot=adp, type=’candle’, style=’charles’, datetime_format=’%Y-%m-%d’, xrotation=20,
title=str(symbol + ‘ Renko Chart’), ylabel=’Price’, xlabel=’Date’, scale_width_adjustment=dict(candle=2))

# Plot the performance curve
def plot_performance_curve(strategy_df):
# Plot the performance curve
plt.plot(strategy_df[‘cumulative_balance’])
plt.title(‘Performance Curve’)
plt.xlabel(‘Date’)
plt.ylabel(‘Balance’)
plt.xticks(rotation=70)
plt.show()

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

  1. Функция «plot_candlestick» для построения графиков исходных собранных данных ohlc из финансов
  2. Функция «plot_renko» для построения кирпичей Ренко и позиций с маркерами покупки и продажи.
  3. И, наконец, «plot_performance_curve» для визуализации кривой роста баланса.

Шаг 7: Запустите стратегиюif __name__ == «__main__»:
# Define the symbol, start date, interval and exchange
symbol = «ETH/USDT»
start_date = «2022-12-1»
interval = ‘4h’
exchange = ccxt.binance()

# Fetch the historical data and Convert the data to a Pandas dataframe
data = fetch_asset_data(symbol=symbol, start_date=start_date, interval=interval, exchange=exchange)

# Print the asset dataframe
print(data)

# Plot the Symbol data candlestick chart
plot_candlestick(df=data)

# Get the Renko Bricks
renko_df = renko_data(data)
print(renko_df)

# Generate Strategy Signals
positions_df = generate_positions(renko_df)
print(positions_df)

# Plot the Renko Bricks and Positions
plot_renko(renko_df)

# Calculate Strategy Performance
strategy_df = calculate_strategy_performance(positions_df)
print(strategy_df)

# Plot the performance curve
plot_performance_curve(strategy_df)

Мы прошли довольно долгий путь; Теперь нам нужно запустить стратегию. Настраиваем переменные и вызываем функции под конструктором ‘if __name__ == «__main__»:‘, который будет запускать скрипт при вызове в интерпретаторе Python (терминале).

В этом руководстве мы проиллюстрируем это на примере пары ETH/USDT. Не стесняйтесь экспериментировать с другими активами. Если вам нужно поэкспериментировать с некриптоактивами, такими как форекс или акции, вам может потребоваться использовать другого поставщика данных, такого как Yahoo Finance (библиотека yfinance python)

Шаг 8: Давайте посмотрим на результаты

Если вы работаете с интерпретатором Python, как это делал я в vscode, откройте терминал и укажите ему каталог с файлами. Конечно, у вас уже должна быть установлена среда python со всеми установленными зависимостями. В данном случае мой файл называется «dynamicRenko.py«.

Запустите скрипт:

Иллюстрация того, как выполнить скрипт в вашем терминале

На момент написания этого руководства это результирующий фрейм данных для ETH/USDT

Фрейм данных ETH/USDT

А вот свечной график для данных актива:

Данные свечей OHLC для ETH/USDT

Ниже был получен датафрейм (renko_df) после преобразования данных свечей в кирпичи ренко. Кроме того, были добавлены колонки сигналов и позиций.

Фрейм данных Ренко

И это был получившийся сюжет для «plot_renko(renko_df)«. Как видите, график показывает четкие тренды и четкие сигналы на покупку и продажу.

График Ренко с позициями на покупку и продажу

Расчетный размер кирпича был 25. Это также отображается на терминале:

Последняя часть заключается в определении эффективности стратегии. Вот такой получившийся датафрейм для strategy(strategy_df):

Фрейм данных эффективности стратегии

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

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

Показатели производительности

А вот кривая производительности:

Кривая производительности

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

Вот полный код на моем GitHub.

Источник

Источник