When I was trying to compare 3 financial assets based exclusively on its price series, I realized that to do so all of those price series had to be converted to price change series, so that way, one could properly see which one of them was performing better during a period of time.
Later on, I set a custom plot on tradingview that allowed me to visualize exactly what I wanted to do on my end, here:
This plot basically shows:
The price change series of 3 financial assets, these price change series are calculated as follows: (Close Price - Very first Open Price)/(Very first Open Price)
. (Keep also in mind these series are updated automatically based on the the data that it's being considered so if you zoom-in or zoom-out the main plot the price change series wil change too).
The candlestick chart of one of the assets, in this case it's BNBUSDT, since my interest is to find a candlestick pattern in that asset.
The period of time at which these data were recorded, which start date was 14th of September 2022 01:00:00 UTC
and finish date was 14th of September 2022 23:00:00 UTC
, furthermore each candlestick represent 1 hour of data and all of these information was requested from Binance Exchange.
So, I put my brain to work, and managed to get this output:
Using this info:
df_chg_btc
Start Date | % Chg | |
---|---|---|
2410 | 2022-09-14 01:00:00 | 0.51 |
2411 | 2022-09-14 02:00:00 | 1.22 |
2412 | 2022-09-14 03:00:00 | 0.62 |
2413 | 2022-09-14 04:00:00 | 0.95 |
2414 | 2022-09-14 05:00:00 | 0.58 |
2415 | 2022-09-14 06:00:00 | 0.2 |
2416 | 2022-09-14 07:00:00 | 0.22 |
2417 | 2022-09-14 08:00:00 | 0.92 |
2418 | 2022-09-14 09:00:00 | 0.74 |
2419 | 2022-09-14 10:00:00 | 0.7 |
2420 | 2022-09-14 11:00:00 | 0.13 |
2421 | 2022-09-14 12:00:00 | 0.55 |
2422 | 2022-09-14 13:00:00 | 0.56 |
2423 | 2022-09-14 14:00:00 | 0.47 |
2424 | 2022-09-14 15:00:00 | -0.1 |
2425 | 2022-09-14 16:00:00 | -0.28 |
2426 | 2022-09-14 17:00:00 | -0.46 |
2427 | 2022-09-14 18:00:00 | -2.08 |
2428 | 2022-09-14 19:00:00 | -1.22 |
2429 | 2022-09-14 20:00:00 | -1.25 |
2430 | 2022-09-14 21:00:00 | 0.02 |
2431 | 2022-09-14 22:00:00 | 0.48 |
2432 | 2022-09-14 23:00:00 | 0.14 |
df_chg_eth
Start Date | % Chg | |
---|---|---|
2410 | 2022-09-14 01:00:00 | 0.59 |
2411 | 2022-09-14 02:00:00 | 1.75 |
2412 | 2022-09-14 03:00:00 | 1.87 |
2413 | 2022-09-14 04:00:00 | 3.04 |
2414 | 2022-09-14 05:00:00 | 2.53 |
2415 | 2022-09-14 06:00:00 | 2.18 |
2416 | 2022-09-14 07:00:00 | 2.45 |
2417 | 2022-09-14 08:00:00 | 1.88 |
2418 | 2022-09-14 09:00:00 | 2.0 |
2419 | 2022-09-14 10:00:00 | 1.79 |
2420 | 2022-09-14 11:00:00 | 1.38 |
2421 | 2022-09-14 12:00:00 | 2.02 |
2422 | 2022-09-14 13:00:00 | 2.01 |
2423 | 2022-09-14 14:00:00 | 1.82 |
2424 | 2022-09-14 15:00:00 | 1.36 |
2425 | 2022-09-14 16:00:00 | 0.75 |
2426 | 2022-09-14 17:00:00 | 1.0 |
2427 | 2022-09-14 18:00:00 | 0.35 |
2428 | 2022-09-14 19:00:00 | 1.67 |
2429 | 2022-09-14 20:00:00 | 1.84 |
2430 | 2022-09-14 21:00:00 | 2.98 |
2431 | 2022-09-14 22:00:00 | 4.15 |
2432 | 2022-09-14 23:00:00 | 4.12 |
df_chg
Start Date | % Chg | |
---|---|---|
2410 | 2022-09-14 01:00:00 | -0.14 |
2411 | 2022-09-14 02:00:00 | 0.54 |
2412 | 2022-09-14 03:00:00 | 0.72 |
2413 | 2022-09-14 04:00:00 | 1.26 |
2414 | 2022-09-14 05:00:00 | 0.83 |
2415 | 2022-09-14 06:00:00 | 0.22 |
2416 | 2022-09-14 07:00:00 | 0.4 |
2417 | 2022-09-14 08:00:00 | 0.76 |
2418 | 2022-09-14 09:00:00 | 0.68 |
2419 | 2022-09-14 10:00:00 | 0.58 |
2420 | 2022-09-14 11:00:00 | 0.58 |
2421 | 2022-09-14 12:00:00 | 0.5 |
2422 | 2022-09-14 13:00:00 | 1.22 |
2423 | 2022-09-14 14:00:00 | 0.79 |
2424 | 2022-09-14 15:00:00 | 0.54 |
2425 | 2022-09-14 16:00:00 | 0.07 |
2426 | 2022-09-14 17:00:00 | -0.07 |
2427 | 2022-09-14 18:00:00 | -0.83 |
2428 | 2022-09-14 19:00:00 | -0.18 |
2429 | 2022-09-14 20:00:00 | -0.04 |
2430 | 2022-09-14 21:00:00 | 0.5 |
2431 | 2022-09-14 22:00:00 | 0.61 |
2432 | 2022-09-14 23:00:00 | 0.47 |
df_ohlc
Start Date | Open Price | High Price | Low Price | Close Price | Volume | End Date | |
---|---|---|---|---|---|---|---|
2410 | 2022-09-14 01:00:00 | 278.0 | 278.1 | 276.7 | 277.6 | 16139.244 | 2022-09-14 01:59:59.999 |
2411 | 2022-09-14 02:00:00 | 277.5 | 280.1 | 277.4 | 279.5 | 13583.474 | 2022-09-14 02:59:59.999 |
2412 | 2022-09-14 03:00:00 | 279.6 | 280.7 | 278.9 | 280.0 | 13142.362 | 2022-09-14 03:59:59.999 |
2413 | 2022-09-14 04:00:00 | 280.1 | 282.0 | 279.9 | 281.5 | 13518.034 | 2022-09-14 04:59:59.999 |
2414 | 2022-09-14 05:00:00 | 281.4 | 281.8 | 280.0 | 280.3 | 9906.908 | 2022-09-14 05:59:59.999 |
2415 | 2022-09-14 06:00:00 | 280.3 | 281.2 | 278.4 | 278.6 | 14126.511 | 2022-09-14 06:59:59.999 |
2416 | 2022-09-14 07:00:00 | 278.6 | 279.7 | 277.1 | 279.1 | 23193.697 | 2022-09-14 07:59:59.999 |
2417 | 2022-09-14 08:00:00 | 279.1 | 280.2 | 277.2 | 280.1 | 25811.769 | 2022-09-14 08:59:59.999 |
2418 | 2022-09-14 09:00:00 | 280.1 | 280.7 | 279.1 | 279.9 | 11960.898 | 2022-09-14 09:59:59.999 |
2419 | 2022-09-14 10:00:00 | 279.8 | 280.3 | 279.1 | 279.6 | 11943.115 | 2022-09-14 10:59:59.999 |
2420 | 2022-09-14 11:00:00 | 279.6 | 280.5 | 278.0 | 279.6 | 17152.818 | 2022-09-14 11:59:59.999 |
2421 | 2022-09-14 12:00:00 | 279.6 | 280.7 | 277.5 | 279.4 | 28562.346 | 2022-09-14 12:59:59.999 |
2422 | 2022-09-14 13:00:00 | 279.4 | 282.8 | 278.6 | 281.4 | 32421.324 | 2022-09-14 13:59:59.999 |
2423 | 2022-09-14 14:00:00 | 281.3 | 281.4 | 278.6 | 280.2 | 25790.991 | 2022-09-14 14:59:59.999 |
2424 | 2022-09-14 15:00:00 | 280.3 | 281.1 | 279.4 | 279.5 | 12341.206 | 2022-09-14 15:59:59.999 |
2425 | 2022-09-14 16:00:00 | 279.5 | 279.7 | 278.0 | 278.2 | 18802.725 | 2022-09-14 16:59:59.999 |
2426 | 2022-09-14 17:00:00 | 278.1 | 279.5 | 277.7 | 277.8 | 13507.618 | 2022-09-14 17:59:59.999 |
2427 | 2022-09-14 18:00:00 | 277.7 | 278.2 | 273.3 | 275.7 | 35438.286 | 2022-09-14 18:59:59.999 |
2428 | 2022-09-14 19:00:00 | 275.8 | 277.7 | 275.3 | 277.5 | 13976.371 | 2022-09-14 19:59:59.999 |
2429 | 2022-09-14 20:00:00 | 277.5 | 278.4 | 277.3 | 277.9 | 9690.736 | 2022-09-14 20:59:59.999 |
2430 | 2022-09-14 21:00:00 | 277.9 | 279.4 | 276.8 | 279.4 | 11821.016 | 2022-09-14 21:59:59.999 |
2431 | 2022-09-14 22:00:00 | 279.4 | 280.4 | 278.8 | 279.7 | 17948.597 | 2022-09-14 22:59:59.999 |
2432 | 2022-09-14 23:00:00 | 279.8 | 279.9 | 278.7 | 279.3 | 13572.694 | 2022-09-14 23:59:59.999 |
And this python code:
import pandas as pd
from pandas import Timedelta
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
## Assume here were set the variables of df_chg_btc, df_chg_eth, df_chg
df_chg_btc = df_chg_btc.set_index('Start Date', inplace=False)
df_chg_eth = df_chg_eth.set_index('Start Date', inplace=False)
df_chg = df_chg.set_index('Start Date', inplace=False)
trading_pair = 'BNB/USDT'
start_index = '14 September 2022'
## Plotting using matplotlib and saving the plot in axlist
# Initialize the plot with #162125 as outer color and a figsize of (12, 6)
fig, ax = plt.subplots(figsize=(12, 6), facecolor='#162125')
# Place the indices and values of df_chg,df_chg_btc and df_chg_eth in the x and y axis correspondingly
symbol = trading_pair.replace("USDT", "")+"/"+"USDT"
ax.plot(df_chg.index, df_chg['% Chg'], marker='o', linestyle='-', color='#39c9bb', label=f'{symbol}')
ax.plot(df_chg_btc.index, df_chg_btc['% Chg'], marker='o', linestyle='-', color='#ff9900', label='BTC/USDT')
ax.plot(df_chg_eth.index, df_chg_eth['% Chg'], marker='o', linestyle='-', color='#8893B1', label='ETH/USDT')
# Set inner color of the plot
ax.set_facecolor("#162125")
# Show every 5th tick to avoid overcrowding
ax.set_xticks(ax.get_xticks()[::5])
# Set the color of x ticks, rotation and the font weight
ax.tick_params(axis='x', colors='white')
plt.xticks(rotation=45)
ax.set_xticklabels(ax.get_xticks(), weight='bold', size=10)
ax.set_xlabel('Time UTC', color='white', fontsize=14, fontweight='bold')
# Format the date on the x-axis
if df_chg.index[1]-df_chg.index[0] == Timedelta(hours=1):
time_format = mdates.DateFormatter('%H:%M:%S')
ax.xaxis.set_major_formatter(time_format)
ax.xaxis.set_major_locator(mdates.HourLocator(interval=4))
# Set the color of y ticks and the font weight
ax.set_yticks(ax.get_yticks(), size=10)
ax.set_yticklabels(['{:,.2f}%'.format(y) for y in ax.get_yticks()], weight='bold')
ax.tick_params(axis='y', colors='white')
ax.set_ylabel('% Change', color='white', fontsize=14, fontweight='bold')
# Set the title
if df_chg.index[1]-df_chg.index[0] == Timedelta(hours=1):
ax.set_title(f'Price Change Comparison - {start_index}', color='white', fontsize=16, fontweight='bold')
# Set grid linestyle, width and color
ax.grid(color='white', linestyle='--', linewidth=1)
# Create the legend with customizations
legend = ax.legend(prop={'size': 12, 'weight': 'bold'})
# Customize the legend text color
legend.get_texts()[0].set_color('#162125')
# Set the opacity (alpha) of the legend box
legend.get_frame().set_alpha(1)
plt.tight_layout()
# Update all the spines' outercolors to the same color as facecolor
for spine in ax.spines.values():
spine.set_edgecolor('#FFFFFF')
# Save the plot
filename = start_index+".png"
fig.savefig(+trading_pair+'//'+filename,dpi=300, bbox_inches = "tight")
#RELEASE THE MEMORY RAM
plt.close('all')
But I have no idea how to properly plot the df_ohlc
data since every time I try to come up with a solution it ends up messed up terribly (i.e. insanely big green and red rectangles that supposedly represent the data, wrong location of the candlesticks, an extra y axis representing the price of the asset...). I also tried to find for similar approaches to my case but couldn't find anything on StackOverflow nor the first 4 pages of Google using words like
"candlestick plot with price change values matplotlib"
It would be great if anybody here could help me out, any approach using matplotlib is welcomed.
Solved, I got illuminated:
import pandas as pd
from pandas import Timedelta
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
def convert_ohlc_prices_to_percentage_values(df):
# Create a new DataFrame to store the results
df_result = pd.DataFrame()
# Add the 'Start Date' column from the original DataFrame to the new DataFrame
df_result['Start Date'] = df['Start Date']
# Reset the index accordingly
df_result.set_index(df.index, inplace=True)
for col in df.columns:
if 'Price' in col:
# Calculate the result using the formula
result = round((df[col] - df['Open Price'].iat[0]) / df['Open Price'].iat[0] * 100, 2)
# Replace values equal to 'Open Price'.iat[0]' with 0
result = result.where(result != 0, 0)
# Add the result to the new DataFrame
df_result[col] = result
# Add the 'Volume' and 'End Date' column from the original DataFrame to the new DataFrame
df_result['Volume'] = df['Volume']
df_result['End Date'] = df['End Date']
return df_result
### Assume here were set the variables of df_ohlc, df_chg_btc, df_chg_eth, df_chg
df_chg_btc = df_chg_btc.set_index('Start Date', inplace=False)
df_chg_eth = df_chg_eth.set_index('Start Date', inplace=False)
df_chg = df_chg.set_index('Start Date', inplace=False)
df_ohlc_percentages = convert_ohlc_prices_to_percentage_values(df_ohlc)
trading_pair = 'BNB/USDT'
start_index = '14 September 2022'
## Plotting using matplotlib and saving the plot in axlist
# Initialize the plot with #162125 as outer color and a figsize of (12, 6)
fig, ax = plt.subplots(figsize=(12, 6), facecolor='#162125')
# Place the indices and values of df_chg,df_chg_btc and df_chg_eth in the x and y axis correspondingly
symbol = trading_pair.replace("USDT", "")+"/"+"USDT"
ax.plot(df_chg.index, df_chg['% Chg'], marker='o', linestyle='-', color='#39c9bb', label=f'{symbol}')
ax.plot(df_chg_btc.index, df_chg_btc['% Chg'], marker='o', linestyle='-', color='#ff9900', label='BTC/USDT')
ax.plot(df_chg_eth.index, df_chg_eth['% Chg'], marker='o', linestyle='-', color='#8893B1', label='ETH/USDT')
# Creación del gráfico de velas
# Calcula la anchura según el intervalo de tiempo (15 minutos en este caso)
time_diff = df_ohlc_percentages["Start Date"].diff().mean()
width = 0.6 * time_diff
for index, row in df_ohlc_percentages.iterrows():
open_price = row["Open Price"]
close_price = row["Close Price"]
high_price = row["High Price"]
low_price = row["Low Price"]
date = row["Start Date"]
color = '#027F7F' if close_price >= open_price else '#FB0000'
height = abs(close_price - open_price)
bottom = min(open_price, close_price)
ax.bar(date, height, width=width, bottom=bottom, align='center', color=color)
ax.vlines(x=date, ymin=low_price, ymax=high_price, color=color, linewidth=1)
# Set inner color of the plot
ax.set_facecolor("#162125")
# Show every 5th tick to avoid overcrowding
ax.set_xticks(ax.get_xticks()[::5])
# Set the color of x ticks, rotation and the font weight
ax.tick_params(axis='x', colors='white')
plt.xticks(rotation=45)
ax.set_xticklabels(ax.get_xticks(), weight='bold', size=10)
ax.set_xlabel('Time UTC', color='white', fontsize=14, fontweight='bold')
# Format the date on the x-axis
if df_chg.index[1]-df_chg.index[0] == Timedelta(hours=1):
time_format = mdates.DateFormatter('%H:%M:%S')
ax.xaxis.set_major_formatter(time_format)
ax.xaxis.set_major_locator(mdates.HourLocator(interval=4))
# Set the color of y ticks and the font weight
ax.set_yticks(ax.get_yticks(), size=10)
ax.set_yticklabels(['{:,.2f}%'.format(y) for y in ax.get_yticks()], weight='bold')
ax.tick_params(axis='y', colors='white')
ax.set_ylabel('% Change', color='white', fontsize=14, fontweight='bold')
# Set the title
if df_chg.index[1]-df_chg.index[0] == Timedelta(hours=1):
ax.set_title(f'Price Change Comparison - {start_index}', color='white', fontsize=16, fontweight='bold')
# Set grid linestyle, width and color
ax.grid(color='white', linestyle='--', linewidth=1)
# Create the legend with customizations
legend = ax.legend(prop={'size': 12, 'weight': 'bold'})
# Customize the legend text color
legend.get_texts()[0].set_color('#162125')
# Set the opacity (alpha) of the legend box
legend.get_frame().set_alpha(1)
plt.tight_layout()
# Update all the spines' outercolors to the same color as facecolor
for spine in ax.spines.values():
spine.set_edgecolor('#FFFFFF')
# Save the plot
filename = start_index+".png"
fig.savefig(+trading_pair+'//'+filename,dpi=300, bbox_inches = "tight")
#RELEASE THE MEMORY RAM
plt.close('all')
Báasically, what I did was:
df_ohlc
prices into percent values considering the change against the first value in the ["Open Price"]
column. That's what the convert_ohlc_prices_to_percentage_values(df)
function is for.["Start Date" ]
and multiply them by a constant (0.6
), then I use 2 for loops to make and place each candle, using the .bar()
and .vlines()
methods, and voila.Output:
If someone knows another approach, feel free to share it.