I'm trying to figure out a good way to add percentage price change boxes inside a custom Japanese Candlestick chart that I have made using the MatPlotLibFinance
library on Python3
, these percentage price change boxes will help to visually appreciate how much the price increased or decreased from the open price of a particular candlestick.
The following information is stored in a variable called df
, it will be used to plot the candlestick chart
Index | Start Date | Open Price | High Price | Low Price | Close Price | Volume | End Date | Abs((CP-OP)/CP)*100 | Low SMA 9 | Close SMA 25 | High SMA 99 |
---|---|---|---|---|---|---|---|---|---|---|---|
12 | 2022-10-23 12:24:00 | 27.87 | 27.88 | 27.72 | 27.83 | 40623.0 | 2022-10-23 12:26:59.999 | 0.14 | 27.89888888888889 | 28.007600000000004 | 28.294343434343432 |
13 | 2022-10-23 12:27:00 | 27.83 | 27.91 | 27.83 | 27.91 | 17337.0 | 2022-10-23 12:29:59.999 | 0.29 | 27.887777777777778 | 27.997600000000002 | 28.289898989898994 |
14 | 2022-10-23 12:30:00 | 27.91 | 27.98 | 27.91 | 27.94 | 8235.0 | 2022-10-23 12:32:59.999 | 0.11 | 27.88222222222222 | 27.9908 | 28.286262626262626 |
15 | 2022-10-23 12:33:00 | 27.94 | 27.94 | 27.89 | 27.89 | 6809.0 | 2022-10-23 12:35:59.999 | 0.18 | 27.87333333333333 | 27.983599999999996 | 28.282121212121215 |
16 | 2022-10-23 12:36:00 | 27.89 | 27.9 | 27.85 | 27.88 | 4209.0 | 2022-10-23 12:38:59.999 | 0.04 | 27.863333333333333 | 27.973999999999997 | 28.277373737373736 |
17 | 2022-10-23 12:39:00 | 27.89 | 27.89 | 27.86 | 27.88 | 10082.0 | 2022-10-23 12:41:59.999 | 0.04 | 27.85666666666667 | 27.966400000000004 | 28.272121212121213 |
18 | 2022-10-23 12:42:00 | 27.88 | 27.89 | 27.83 | 27.88 | 13257.0 | 2022-10-23 12:44:59.999 | 0.0 | 27.846666666666668 | 27.957600000000003 | 28.26666666666667 |
19 | 2022-10-23 12:45:00 | 27.88 | 27.94 | 27.88 | 27.94 | 5462.0 | 2022-10-23 12:47:59.999 | 0.22 | 27.85 | 27.951999999999998 | 28.26131313131313 |
20 | 2022-10-23 12:48:00 | 27.93 | 28.03 | 27.93 | 28.03 | 10597.0 | 2022-10-23 12:50:59.999 | 0.36 | 27.855555555555554 | 27.9512 | 28.257070707070707 |
21 | 2022-10-23 12:51:00 | 28.03 | 28.06 | 27.98 | 28.05 | 10238.0 | 2022-10-23 12:53:59.999 | 0.07 | 27.884444444444444 | 27.951200000000004 | 28.253333333333334 |
22 | 2022-10-23 12:54:00 | 28.05 | 28.05 | 27.99 | 28.03 | 6352.0 | 2022-10-23 12:56:59.999 | 0.07 | 27.90222222222222 | 27.952800000000003 | 28.24959595959596 |
23 | 2022-10-23 12:57:00 | 28.02 | 28.04 | 28.0 | 28.04 | 3905.0 | 2022-10-23 12:59:59.999 | 0.07 | 27.91222222222222 | 27.9556 | 28.245656565656564 |
24 | 2022-10-23 13:00:00 | 28.03 | 28.05 | 28.02 | 28.03 | 4607.0 | 2022-10-23 13:02:59.999 | 0.0 | 27.926666666666666 | 27.9548 | 28.24222222222222 |
25 | 2022-10-23 13:03:00 | 28.04 | 28.04 | 28.0 | 28.03 | 4291.0 | 2022-10-23 13:05:59.999 | 0.04 | 27.94333333333333 | 27.956 | 28.23868686868687 |
26 | 2022-10-23 13:06:00 | 28.02 | 28.02 | 27.99 | 28.0 | 4856.0 | 2022-10-23 13:08:59.999 | 0.07 | 27.95777777777778 | 27.9568 | 28.234747474747476 |
27 | 2022-10-23 13:09:00 | 28.01 | 28.03 | 28.01 | 28.02 | 1343.0 | 2022-10-23 13:11:59.999 | 0.04 | 27.977777777777774 | 27.9584 | 28.230505050505048 |
28 | 2022-10-23 13:12:00 | 28.02 | 28.06 | 28.01 | 28.06 | 5932.0 | 2022-10-23 13:14:59.999 | 0.14 | 27.992222222222225 | 27.9624 | 28.226565656565658 |
29 | 2022-10-23 13:15:00 | 28.06 | 28.1 | 28.04 | 28.06 | 8292.0 | 2022-10-23 13:17:59.999 | 0.0 | 28.004444444444445 | 27.9656 | 28.223030303030303 |
When running df.dtypes
, the following output is thrown:
Start Date datetime64[ns]
Open Price float64
High Price float64
Low Price float64
Close Price float64
Volume float64
End Date datetime64[ns]
Abs((CP-OP)/CP)*100 float64
Low SMA 9 float64
Close SMA 25 float64
High SMA 99 float64
dtype: object
Also, another variable called df_trading_pair_date_time_index
contains the same information as the previous variable with slight modifications, since it can only be used in this way in the script below:
import pandas as pd
import mplfinance as mpf
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
def set_DateTimeIndex(df_trading_pair):
df_trading_pair = df_trading_pair.set_index('Start Date', inplace=False)
# Rename the column names for best practices
df_trading_pair.rename(columns = { "Open Price" : 'Open',
"High Price" : 'High',
"Low Price" : 'Low',
"Close Price" :'Close',
}, inplace = True)
return df_trading_pair
# Create another df just to properly plot the data
df_trading_pair_date_time_index = set_DateTimeIndex(df)
The following script will execute a function called mpl_plotting
which takes as input the variables df
, df_trading_pair_date_time_index
will be used to plot Japanese Candlestick chart, while the last parameter of int
type will be used to plot the price change boxes which will then be added to the Japanese candlestick Chart:
def mplf_plotting(df_trading_pair, df_trading_pair_date_time_index, entry_candlestick_index):
entry_price = df_trading_pair['Open Price'].iat[entry_candlestick_index]
maximum_price_reached = df_trading_pair['High Price'][entry_candlestick_index+1:].max()
maximum_price_index = df_trading_pair['Low Price'][entry_candlestick_index+1:].idxmax()
where_values_up = [entry_candlestick_index, maximum_price_index]
minimum_price_reached = df_trading_pair['Low Price'][entry_candlestick_index+1:].min()
minimum_price_index = df_trading_pair['Low Price'][entry_candlestick_index+1:].idxmin()
where_values_down = [entry_candlestick_index, df_trading_pair['Start Date'][minimum_price_index]]
# Plotting
# Create my own `marketcolors` style:
mc = mpf.make_marketcolors(up='#2fc71e',down='#ed2f1a',inherit=True)
# Create my own `MatPlotFinance` style:
s = mpf.make_mpf_style(base_mpl_style=['bmh', 'dark_background'],marketcolors=mc, y_on_right=True)
# Plot it
# First create a dictionary to store the plots to add
subplots = {'Low SMA 9': mpf.make_addplot(df_trading_pair['Low SMA 9'], width=1, color='#F0FF42'),
'Close SMA 25': mpf.make_addplot(df_trading_pair['Close SMA 25'], width=1.5, color='#EA047E'),
'High SMA 99': mpf.make_addplot(df_trading_pair['High SMA 99'], width=2, color='#00FFD1')}
pct_change_boxes ={'Percentage Change Up': mpf.make_addplot(df_trading_pair, fill_between=dict(y1=entry_price,y2=maximum_price_reached,where=where_values_up),alpha=0.5,color='g'),
'Percentage Change Down': mpf.make_addplot(df_trading_pair, fill_between=dict(y1=entry_price,y2=minimum_price_reached,where=where_values_down),alpha=0.5,color='g')}
list_of_plots = list(subplots.values())
#for i in list(pct_change_boxes.values()):
#list_of_plots.append(i)
trading_plot, axlist = mpf.plot(df_trading_pair_date_time_index,
figratio=(10, 6),
type="candle",
style=s,
tight_layout=True,
datetime_format = '%H:%M',
ylabel = "Precio ($)",
returnfig=True,
show_nontrading=True,
addplot=list_of_plots
)
# Plotting
# Add Title
trading_pair = "SOLBUSD"
symbol = trading_pair.replace("BUSD","")+"/"+"BUSD"
axlist[0].set_title(f"{symbol} - 3m", fontsize=25, style='italic', fontfamily='fantasy')
# Find which times should be shown every 6 minutes starting at the last row of the df
x_axis_minutes = []
for i in range (1,len(df_trading_pair_date_time_index),2):
x_axis_minutes.append(df_trading_pair_date_time_index.index[-i].minute)
# Set the main "ticks" to show at the x axis
axlist[0].xaxis.set_major_locator(mdates.MinuteLocator(byminute=x_axis_minutes))
# Set the x axis label
axlist[0].set_xlabel('Zona Horaria UTC')
# Set the y axis range
ymin_value = df_trading_pair[['Low Price','Low SMA 9','Close SMA 25', 'High SMA 99']].min(axis=1).min()
ymax_value = df_trading_pair[['High Price','Low SMA 9','Close SMA 25', 'High SMA 99']].max(axis=1).max()
axlist[0].set_ylim([ymin_value,ymax_value])
# Set the SMA legends
# First set the amount of legends to add to the legend box
axlist[0].legend([None]*(len(subplots)+2))
# Then Store the legend objects in a variable called "handles", based on this script, your objects to legend will appear from the third element in this list
handles = axlist[0].get_legend().legendHandles
# Finally set the corresponding names for the plotted SMA trends and place the legend box to the upper left corner in the bigger plot
axlist[0].legend(handles=handles[2:],labels=list(subplots.keys()), loc = 'upper left', fontsize = 15)
# Execute the function to plot
mplf_plotting(df, df_trading_pair_date_time_index, 14)
After running the script above, the following output is thrown:
Traceback (most recent call last):
File "C:\Users\ResetStoreX\AppData\Local\Programs\Python\Python39\lib\site-packages\spyder_kernels\py3compat.py", line 356, in compat_exec
exec(code, globals, locals)
File "c:\users\resetstorex\downloads\binance futures data\binance api key + binance wrapper\bollinger bands\timeframe - 30 minutes\binance_futures_busd-backtesting-of-moving-averages.py", line 224, in <module>
mplf_plotting(df_trading_pair[dict_index[i]:dict_index[i]+20], df_trading_pair_date_time_index, dict_index[i]+2)
File "c:\users\resetstorex\downloads\binance futures data\binance api key + binance wrapper\bollinger bands\timeframe - 30 minutes\binance_futures_busd-backtesting-of-moving-averages.py", line 136, in mplf_plotting
trading_plot, axlist = mpf.plot(df_trading_pair_date_time_index,
File "C:\Users\ResetStoreX\AppData\Local\Programs\Python\Python39\lib\site-packages\mplfinance\plotting.py", line 720, in plot
ax = _addplot_columns(panid,panels,ydata,apdict,xdates,config)
File "C:\Users\ResetStoreX\AppData\Local\Programs\Python\Python39\lib\site-packages\mplfinance\plotting.py", line 1014, in _addplot_columns
yd = [y for y in ydata if not math.isnan(y)]
File "C:\Users\ResetStoreX\AppData\Local\Programs\Python\Python39\lib\site-packages\mplfinance\plotting.py", line 1014, in <listcomp>
yd = [y for y in ydata if not math.isnan(y)]
TypeError: must be real number, not Timestamp
If I decided to remove the following lines from the function:
for i in list(pct_change_boxes.values()):
list_of_plots.append(i)
The following output is thrown:
I was expecting my script to print a image like the one down below, it essentially shows how much the price increased or decreased in percentage values based on the 3rd parameter passed to the mplf_plotting
function:
How could I fix my function to throw an output like the desired one?
The closest thing I have come up with was to make use of the following original MatPlotLib
functions: matplotlib.pyplot.text
, matplotlib.axes.Axes.vlines
, matplotlib.axes.Axes.hlines
and also fixed some minor issues in the mplf_plotting
function in order to avoid SettingWithCopyWarning
from Pandas
as well as other errors I can't remember atm.
The following improvements were implemented:
The mplf_plotting
will now take just a deepcopy
of the df
variable, and the regular df_trading_pair_date_time_index
The first thing this function will do is to reset the index of the information passed to the temporal df_trading_pair
variable for then deleting the old index
column left.
The value assigned to the entry_candlestick_index
will now be the the first valid index
of the df_trading_pair
plus the necessary amount of candlesticks to get the actual location of the desired candlestick (as it was previously set as 14
, the amount to add must be 2
).
The values stored in the lists of where_values_up
and where_values_down
will now be actual Timestamp
dates rather than its corresponding index values.
The percentage change values to show in the final output will be calculated in the pct_change_up
and pct_change_down
variables
Note: The following solution doesn't end up adding an actual percentage change box to the japanese candlestick chart, but hey, it works for me atm ¯\(ツ)/¯.
from datetime import timedelta
def mplf_plotting(df_trading_pair, df_trading_pair_date_time_index):
# Reset the index
df_trading_pair.reset_index(inplace=True)
df_trading_pair.drop('index', inplace=True, axis=1)
entry_candlestick_index = df_trading_pair.first_valid_index()+2
entry_price = df_trading_pair['Open Price'].iat[entry_candlestick_index]
maximum_price_reached = df_trading_pair['High Price'][entry_candlestick_index+1:].max()
maximum_price_index = df_trading_pair['Low Price'][entry_candlestick_index+1:].idxmax()
where_values_up = [df_trading_pair['Start Date'].iat[entry_candlestick_index], df_trading_pair['Start Date'].iat[maximum_price_index]]
pct_change_up = round((maximum_price_reached-entry_price)/entry_price*100,2)
minimum_price_reached = df_trading_pair['Low Price'][entry_candlestick_index+1:].min()
minimum_price_index = df_trading_pair['Low Price'][entry_candlestick_index+1:].idxmin()
where_values_down = [df_trading_pair['Start Date'].iat[entry_candlestick_index], df_trading_pair['Start Date'][minimum_price_index]]
pct_change_down = round((minimum_price_reached-entry_price)/entry_price*100,2)
# Plotting
# Create my own `marketcolors` style:
mc = mpf.make_marketcolors(up='#2fc71e',down='#ed2f1a',inherit=True)
# Create my own `MatPlotFinance` style:
s = mpf.make_mpf_style(base_mpl_style=['bmh', 'dark_background'],marketcolors=mc, y_on_right=True)
# Plot it
# First create a dictionary to store the plots to add
subplots = {'Low SMA 9': mpf.make_addplot(df_trading_pair['Low SMA 9'], width=1, color='#F0FF42'),
'Close SMA 25': mpf.make_addplot(df_trading_pair['Close SMA 25'], width=1.5, color='#EA047E'),
'High SMA 99': mpf.make_addplot(df_trading_pair['High SMA 99'], width=2, color='#00FFD1')}
list_of_plots = list(subplots.values())
trading_plot, axlist = mpf.plot(df_trading_pair_date_time_index,
figratio=(10, 6),
type="candle",
style=s,
tight_layout=True,
datetime_format = '%H:%M',
ylabel = "Precio ($)",
returnfig=True,
show_nontrading=True,
addplot=list_of_plots
)
# Add Title
symbol = trading_pair.replace("BUSD","")+"/"+"BUSD"
axlist[0].set_title(f"{symbol} - 3m", fontsize=25, style='italic', fontfamily='fantasy')
# Find which times should be shown every 6 minutes starting at the last row of the df
x_axis_minutes = []
for i in range (1,len(df_trading_pair_date_time_index),2):
x_axis_minutes.append(df_trading_pair_date_time_index.index[-i].minute)
# Plot and label the Maximum Positive ROI
axlist[0].hlines(maximum_price_reached, xmin=where_values_up[0], xmax=where_values_up[1],
color="#06FF44", linestyle="solid", linewidth=1.5, alpha=0.7)
axlist[0].vlines(where_values_up[0], ymin=entry_price, ymax=maximum_price_reached,
color="#06FF44", linestyle="solid", linewidth=1.5, alpha=0.7)
axlist[0].text(x=where_values_up[0]+timedelta(minutes=1), y=maximum_price_reached-0.009, s=f"ROI: +{pct_change_up}%",
ha="left", va="top", fontsize="14", color="#06FF44", backgroundcolor='#000000')
# Plot and label the Maximum Negative ROI
axlist[0].hlines(minimum_price_reached, xmin=where_values_down[0], xmax=where_values_down[1],
color="#F51912", linestyle="solid", linewidth=1.5, alpha=0.7)
axlist[0].vlines(where_values_down[0], ymin=entry_price, ymax=minimum_price_reached,
color="#F51912", linestyle="solid", linewidth=1.5, alpha=0.7)
axlist[0].text(x=where_values_up[0]+timedelta(minutes=1), y=minimum_price_reached+0.009, s=f"ROI: {pct_change_down}%",
ha="left", va="bottom", fontsize="14", color="#F51912", backgroundcolor='#000000')
# Set the main "ticks" to show at the x axis
axlist[0].xaxis.set_major_locator(mdates.MinuteLocator(byminute=x_axis_minutes))
# Set the x axis label
axlist[0].set_xlabel('Zona Horaria UTC')
# Set the y axis range
ymin_value = df_trading_pair[['Low Price','Low SMA 9','Close SMA 25', 'High SMA 99']].min(axis=1).min()
ymax_value = df_trading_pair[['High Price','Low SMA 9','Close SMA 25', 'High SMA 99']].max(axis=1).max()
axlist[0].set_ylim([ymin_value,ymax_value])
# Set the SMA legends
# First set the amount of legends to add to the legend box
axlist[0].legend([None]*(len(subplots)+2))
# Then Store the legend objects in a variable called "handles", based on this script, your objects to legend will appear from the third element in this list
handles = axlist[0].get_legend().legendHandles
# Finally set the corresponding names for the plotted SMA trends and place the legend box to the upper left corner in the bigger plot
axlist[0].legend(handles=handles[2:],labels=list(subplots.keys()), loc = 'upper left', fontsize = 15)
# Execute the function to plot
mplf_plotting(df.copy(deep=True), df_trading_pair_date_time_index)