A Profitable Dynamic Renko Trading Strategy with Python — A Step-by-Step Approach. (2024)

A Profitable Dynamic Renko Trading Strategy with Python — A Step-by-Step Approach. (2)

Renko charts have been in existence for a considerable time. The chart is believed to have acquired its name long ago, deriving from the Japanese term “renga,” which translates to “brick.” Traders use “Renko charts” to focus on price movement, providing a clearer visual representation of the trends and reversals.

Why would you want to consider the Renko Charts for your Trading Strategy?

A Profitable Dynamic Renko Trading Strategy with Python — A Step-by-Step Approach. (3)

To break it down, simply imagine that you are building a house with some special bricks called Renko, or better still “Renga” — as they are called in Japenese. Each brick represents a fixed price movement rather than a fixed timeframe it takes to lay. With Renko bricks what matters is the stacking size rather than the time it would take to stake them. So in 2 hours, you can stake one brick or 5 bricks or any number of bricks — what matters is the size, not the duration. This is what makes Renko so interesting, you don’t have to watch every tick of the clock as most charting tools do.

So now you might be wondering, what is the staking criteria if the duration it takes to lay doesn't matter?

To put it simply, whenever the price say; moves up by $20, you lay a brick upwards and when the price goes down by -$20 you lay the brick downwards. Pretty much a very straightforward concept right? Refer to the illustration in the figure above.

If we compare this to the traditional candlesticks, a new candle would be formed only when a specified duration has elapsed.

A Profitable Dynamic Renko Trading Strategy with Python — A Step-by-Step Approach. (4)

This means that you have candles chronologically stacked every say, 2 hours. In the candlestick charting style, both the price and time play an equal role in forming the chart. This results in a lot more market noise representation than the Renko chart.

Now let's get right into the code;

Step 1. Import the dependencies.

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

The first thing you want to do is to prepare your coding environment and then import the dependencies. As you can see we are using the “ccxt” package for collecting the data. It provides data from various crypto exchanges. In this example, we use it to get the the data from binance. Other important libraries for executing the strategy include the good old pandas, numpy, and Scipy for statistical computations. The “pandas_ta library is for accessing the technical indicators — it's built on top of “TA-Lib” a popular Python technical analysis library. The “stocktrends ” library is used to calculate the Renko bricks and generate a Renko dataframe. And finally, we will use the “mplfinance” and the “matplotlib ”libraries for data visualization.

Step 2: Fetch the asset data from ccxt

def 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

The function “fetch_asset_data” returns the data as pandas dataframe by passing in the symbol, start_date, interval, and exchange arguments. The symbol is the asset pair (such as ETH/USDT), the start date is the beginning data collection date, the interval is the time it takes to generate a single candle (which forms a row in the dataframe), and the exchange is the broker (e.g., Binance).

Step 3: Generate the Renko bricks as a dataframe

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

The function renko_data calculates the Renko data for the asset pair “ohlc ” dataframe as an input. The Average True Range is calculated and used to determine the optimal brick size for the Renko chart. The brick size is rounded off for consistency. The function returns the Renko data as a pandas dataframe with columns Date, Open, High, Low, and Close where each value is converted into a float data type.

It’s important to ensure solid and dependable backtested results. Thus, we drop the dataframe’s last row, which contains live data. Refreshing the strategy system with live data introduces inconsistencies. As a result, it is critical to only rely on previous data represented by completed candles. This is achieved with this line “data.drop(data.index[-1], inplace=True)”.

This function keeps the Renko brick size dynamically stable. Some of the drawbacks of some renko systems include the use of a relatively small brick size, which results in a large number of bricks forming in the same time period, especially when the price changes rapidly. You need a consistent brick size that adapts automatically based on market volatility. This is what the function accomplishes.

Step 4: Generate the Signals and Positions

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

The function “generate_positions” processes the renko dataframe (renko_df) to generate signals followed by buy and sell trading positions. For reliable backtested results, we remove look ahead bias using this “renko_df[‘signals’] = renko_df[“signals”].shift(1)”; otherwise, the result will be ambiguously inflated and inaccurate.

Step 5: Calculate the performance

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'].c*msum()

# 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

You want to know how your strategy is performing by checking on metrics like the profit and loss(p/l), the account growth(balance), and the maximum drawdown. The function “calculate_strategy_performance” takes the arguments “strategy_df” — which is returned from the “generate_positions” function, the “capital ” which represents the initial investment amount, and then the “leverage” which is the borrowing multiplier from the broker. The default value for ‘leverage’ is “x1” indicating no leverage is taken by default. However, feel free to experiment with your desired leverage amount.

It’s important to exercise caution when using leverage, especially in live trading, as over-leveraging can swiftly deplete your account if the trade position goes unfavorably. Please be cautious about this — and of course, nothing in this article is investment advice.

The function returns a dataframe “strategy_df” that contains the performance metrics and prints out some other metrics like the overall profit/loss, the balance, and the maximum drawdown.

Maximizing returns by compounding

This function adopts a compounding strategy, where the investment for each new position is based on the available balance, including past gains from closed positions. A key code snippet from the function is “cumulative_balance += pl”.

Step 6: Calculate the performance

# 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()

Now let's make some plots to visualize the data. For this three functions are created:

  1. The “plot_candlestick” function for plotting the original collected ohlc data from binance
  2. The “plot_renko” function for plotting the Renko bricks and the positions with buy and sell markers.
  3. And finally the “plot_performance_curve” for visualizing the growth curve of the balance.

Step 7: Run the strategy

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)

We have come quite a long way; now we need to run the strategy. We setup the variables and call the functions under the ‘if __name__ == “__main__”:’ constructor that will run the script when called in the Python interpreter (terminal).

For this tutorial, we illustrate with the ETH/USDT pair. Feel free to experiment with other assets. If you need to experiment with non-crypto assets like forex or stocks you may need to use another data provider such as Yahoo Finance (yfinance python library)

Step 8: Let’s take a look at the results

If you are working with a Python interpreter like I did in vscode, open up the terminal, and point it to the file directory. Of course, you need to have already installed a python environment with all the dependencies installed. In this case, my file is called “dynamicRenko.py”.

Run the script:

A Profitable Dynamic Renko Trading Strategy with Python — A Step-by-Step Approach. (5)

At the time of this tutorial, this is the resulting dataframe for ETH/USDT

A Profitable Dynamic Renko Trading Strategy with Python — A Step-by-Step Approach. (6)

And here is the candlestick plot for the asset data:

A Profitable Dynamic Renko Trading Strategy with Python — A Step-by-Step Approach. (7)

The following was the resulting dataframe (renko_df) after converting the candlestick data to renko bricks. In addition to that, the signals and positions columns were added.

A Profitable Dynamic Renko Trading Strategy with Python — A Step-by-Step Approach. (8)

And this was the resulting plot for “plot_renko(renko_df)”. As you can see the chart shows clear trends and clear buy and sell signals.

A Profitable Dynamic Renko Trading Strategy with Python — A Step-by-Step Approach. (9)

The calculated brick size was 25. This is also displayed on the terminal:

A Profitable Dynamic Renko Trading Strategy with Python — A Step-by-Step Approach. (10)

The last part is to determine the performance of the strategy. This was the resulting dataframe for the strategy(strategy_df):

A Profitable Dynamic Renko Trading Strategy with Python — A Step-by-Step Approach. (11)

And these were the performance metrics. This performance was limited to profit/loss, the ultimate balance, and the maximum drawdown. Feel free to determine other metrics like Sortino ratio, alpha etc.

As you can see for roughly a year, the strategy made a 309.7% return on investment. This is quite decent, right? — but remember past performance doesn't guarantee future performance.

A Profitable Dynamic Renko Trading Strategy with Python — A Step-by-Step Approach. (12)

And the this is the performance curve:

A Profitable Dynamic Renko Trading Strategy with Python — A Step-by-Step Approach. (13)

Now the Dynamic Renko Strategy must surely be interesting. Renko charts offer a unique perspective on price movements, emphasizing significant price changes rather than time intervals. Thus, traders identify trends and reversals more clearly. And this may lead to more profitability.

Here is the full code on my GitHub.

A Profitable Dynamic Renko Trading Strategy with Python — A Step-by-Step Approach. (2024)
Top Articles
Discord Review for Teachers | Common Sense Education
Is port 443 suppose to be open by default in windows 8.1 pro?
Scheelzien, volwassenen - Alrijne Ziekenhuis
Menards Thermal Fuse
Duralast Gold Cv Axle
Unit 30 Quiz: Idioms And Pronunciation
Restored Republic January 20 2023
Part time Jobs in El Paso; Texas that pay $15, $25, $30, $40, $50, $60 an hour online
Chatiw.ib
Amtrust Bank Cd Rates
How to Watch Braves vs. Dodgers: TV Channel & Live Stream - September 15
Southland Goldendoodles
Otr Cross Reference
Ukraine-Russia war: Latest updates
De Leerling Watch Online
What Is A Good Estimate For 380 Of 60
Alejos Hut Henderson Tx
Hell's Kitchen Valley Center Photos Menu
Munich residents spend the most online for food
Csi Tv Series Wiki
Is The Yankees Game Postponed Tonight
What Is Vioc On Credit Card Statement
Gayla Glenn Harris County Texas Update
Team C Lakewood
Mj Nails Derby Ct
Garnish For Shrimp Taco Nyt
Foolproof Module 6 Test Answers
Arrest Gif
UCLA Study Abroad | International Education Office
Effingham Daily News Police Report
Yayo - RimWorld Wiki
Stubhub Elton John Dodger Stadium
Craigslist/Phx
Opsahl Kostel Funeral Home & Crematory Yankton
Craigslist Gigs Norfolk
Truis Bank Near Me
Philadelphia Inquirer Obituaries This Week
Appraisalport Com Dashboard Orders
Achieving and Maintaining 10% Body Fat
Craigslist Odessa Midland Texas
LoL Lore: Die Story von Caitlyn, dem Sheriff von Piltover
Smite Builds Season 9
Powerspec G512
Advance Auto.parts Near Me
Mynord
Value Village Silver Spring Photos
Makes A Successful Catch Maybe Crossword Clue
Elvis Costello announces King Of America & Other Realms
Sam's Club Fountain Valley Gas Prices
Home | General Store and Gas Station | Cressman's General Store | California
Vcuapi
Uncle Pete's Wheeling Wv Menu
Latest Posts
Article information

Author: Virgilio Hermann JD

Last Updated:

Views: 5899

Rating: 4 / 5 (61 voted)

Reviews: 84% of readers found this page helpful

Author information

Name: Virgilio Hermann JD

Birthday: 1997-12-21

Address: 6946 Schoen Cove, Sipesshire, MO 55944

Phone: +3763365785260

Job: Accounting Engineer

Hobby: Web surfing, Rafting, Dowsing, Stand-up comedy, Ghost hunting, Swimming, Amateur radio

Introduction: My name is Virgilio Hermann JD, I am a fine, gifted, beautiful, encouraging, kind, talented, zealous person who loves writing and wants to share my knowledge and understanding with you.