I have created a Python dashboard for a school project. The code generates two graphs (the second of which is unimportant for the purpose of this question). The first one plots a line that represents Wealth ($). This fluctuates above and below that horizontal line that represent’s the individual starting wealth. I would like to shade in green and red the area between the Wealth ($) like and the horizontal dashed line based on whether the former is above or below the latter. The image below shows what I have managed to achieve thus far.
To do so, I have followed the steps suggested in this post:
Unfortunately, you can see that the colour does not switch at the moment the horizontal axis is crossed, and there are some regions where the shading overlaps. Could anyone help me solve this problem? I have really tried my best and I can’t tackle it.
This is the code I am using:
import dash
from dash import dcc, html, Input, Output
import pandas as pd
import numpy as np
import plotly.express as px
import plotly.graph_objs as go
# Load the data
file_path = 'poker.xlsx'
df = pd.read_excel(file_path, sheet_name='Online')
# Filter out unnecessary columns if there are any
columns_to_keep = ['Day', 'Wealth ($)', 'Daily P&L ($)', 'Cumul P&L ($)', 'Cumul P&L (%)']
df = df[columns_to_keep]
# Initialize the Dash app
app = dash.Dash(__name__)
# Define the color for each bar based on 'Daily P&L ($)' value
df['color'] = df['Daily P&L ($)'].apply(lambda x: 'green' if x >= 0 else 'red')
# Get the starting value of Wealth ($)
starting_wealth = df['Wealth ($)'].iloc[0]
# Create the initial figures for the graphs (they will be updated by callbacks)
wealth_over_time_fig = go.Figure()
daily_pl_fig = go.Figure()
# Define the layout of the app
app.layout = html.Div([
html.H1('Online Poker Performance Dashboard', style={'textAlign': 'center'}),
dcc.Graph(id='wealth-over-time', figure=wealth_over_time_fig),
html.Div([
dcc.RangeSlider(
id='wealth-time-range-slider',
min=df['Day'].min(),
max=df['Day'].max(),
value=[df['Day'].min(), df['Day'].max()],
marks={str(day): str(day) for day in df['Day'].unique()},
step=None
)], style={'padding': '0px 60px 0px 60px'}),
dcc.Graph(id='daily-pl', figure=daily_pl_fig),
html.Div([
dcc.RangeSlider(
id='daily-pl-range-slider',
min=df['Day'].min(),
max=df['Day'].max(),
value=[df['Day'].min(), df['Day'].max()],
marks={str(day): str(day) for day in df['Day'].unique()},
step=None
)
], style={'padding': '0px 60px 0px 60px'}),
html.Div([
html.H3(f"Sharpe Ratio: {df['Cumul P&L (%)'].mean() / df['Cumul P&L (%)'].std():.4f}")
], style={'textAlign': 'left', 'padding-left': '75px'}),
])
# Callback for updating the Wealth Over Time graph based on the selected range from the RangeSlider
@app.callback(
Output('wealth-over-time', 'figure'),
Input('wealth-time-range-slider', 'value'))
def update_wealth_over_time(selected_range):
filtered_df = df[df['Day'].between(selected_range[0], selected_range[1])]
# Calculate the 'boundary' line
filtered_df['boundary'] = np.where(filtered_df['Wealth ($)'] < starting_wealth,
filtered_df['Wealth ($)'],
starting_wealth)
print(filtered_df)
# Create the figure
fig = go.Figure()
# Add the traces for 'Wealth ($)' line and 'boundary' line
fig.add_trace(go.Scatter(
x=filtered_df['Day'],
y=filtered_df['boundary'],
line=dict(color='rgba(255,255,255,0)'),
showlegend=False
))
# Fill between 'boundary' and starting wealth (red area)
fig.add_trace(go.Scatter(
x=filtered_df['Day'],
y=[starting_wealth] * len(filtered_df),
fill='tonexty',
fillcolor='rgba(255, 0, 0, 0.3)',
line=dict(color='rgba(255,0,0,0.3)'),
showlegend=False
))
# Fill between 'Wealth ($)' and 'boundary' (green area)
fig.add_trace(go.Scatter(
x=filtered_df['Day'],
y=filtered_df['boundary'],
line=dict(color='rgba(255,255,255,0)'),
showlegend=False
))
# Add the Wealth ($) line on top
fig.add_trace(go.Scatter(
x=filtered_df['Day'],
y=filtered_df['Wealth ($)'],
fill='tonexty',
fillcolor = 'rgba(0, 255, 0, 0.3)',
name='Wealth ($)',
line=dict(color='black')
))
# Add the starting wealth line (dashed)
fig.add_trace(go.Scatter(
x=[filtered_df['Day'].min(), filtered_df['Day'].max()],
y=[starting_wealth, starting_wealth],
mode='lines',
name='Starting Wealth',
line=dict(color='black', width=2, dash='dash')
))
# Update layout
fig.update_layout(
title='Wealth Over Time',
xaxis=dict(title='Day'),
yaxis=dict(title='Wealth ($)'),
plot_bgcolor='white'
)
return fig
# Callback for updating the Daily P&L graph based on the selected range from the RangeSlider
@app.callback(
Output('daily-pl', 'figure'),
Input('daily-pl-range-slider', 'value'))
def update_daily_pl(selected_range):
filtered_df = df[df['Day'].between(selected_range[0], selected_range[1])]
fig = go.Figure(
data=[
go.Bar(
x=filtered_df['Day'],
y=filtered_df['Daily P&L ($)'],
name='Daily P&L ($)',
marker=dict(
color=filtered_df['color'],
line=dict(
color='black',
width=1,
),
)
)
]
)
fig.update_layout(
title='Daily P&L',
plot_bgcolor='white',
xaxis=dict(
title='Day',
showgrid=True,
gridcolor='lightgrey',
gridwidth=1,
showline=True,
linewidth=2,
linecolor='black',
mirror=True,
),
yaxis=dict(
title='Daily P&L ($)',
showgrid=True,
gridcolor='lightgrey',
gridwidth=1,
showline=True,
linewidth=2,
linecolor='black',
mirror=True,
)
)
return fig
# Run the app
if __name__ == '__main__':
app.run_server(debug=True)
The data is not very important, as I have set it randomly, but here is the one I am using:
Day | Date | Wealth ($) | Daily P&L ($) | Daily Wealth P&L (%) | Daily Buy-In P&L (%) | Cumul P&L ($) | Cumul P&L (%) |
---|---|---|---|---|---|---|---|
1 | 44966 | 11.69 | 0 | 0 | 0 | 0 | 0 |
2 | 44967 | 11.91 | 0.22 | 0.018819504 | 0.11 | 0.22 | 0.018819504 |
3 | 44968 | 12.12 | 0.21 | 0.017632242 | 0.105 | 0.43 | 0.036783576 |
4 | 44980 | 13.74 | 1.62 | 0.133663366 | 0.81 | 2.05 | 0.175363559 |
5 | 45254 | 11.74 | -2 | -0.145560408 | -1 | 0.05 | 0.00427716 |
6 | 45255 | 10.44 | -1.3 | -0.110732538 | -0.65 | -1.25 | -0.106928999 |
7 | 45256 | 13.84 | 3.4 | 0.325670498 | 1.7 | 2.15 | 0.183917879 |
8 | 45257 | 14.01 | 0.17 | 0.012283237 | 0.085 | 2.32 | 0.198460222 |
9 | 45258 | 14.74 | 0.73 | 0.052105639 | 0.365 | 3.05 | 0.260906758 |
10 | 45262 | 14.88 | 0.14 | 0.009497965 | 0.07 | 3.19 | 0.272882806 |
11 | 45263 | 15.03 | 0.15 | 0.010080645 | 0.075 | 3.34 | 0.285714286 |
12 | 45264 | 15.44 | 0.41 | 0.027278776 | 0.205 | 3.75 | 0.320786997 |
13 | 45265 | 15.65 | 0.21 | 0.013601036 | 0.105 | 3.96 | 0.338751069 |
14 | 45266 | 13.46 | -2.19 | -0.139936102 | -1.095 | 1.77 | 0.151411463 |
15 | 45267 | 13.81 | 0.35 | 0.026002972 | 0.175 | 2.12 | 0.181351583 |
16 | 45269 | 13.92 | 0.11 | 0.007965243 | 0.055 | 2.23 | 0.190761334 |
17 | 45270 | 14.77 | 0.85 | 0.061063218 | 0.425 | 3.08 | 0.263473054 |
18 | 45271 | 14.87 | 0.1 | 0.006770481 | 0.05 | 3.18 | 0.272027374 |
19 | 45272 | 15.27 | 0.4 | 0.026899798 | 0.2 | 3.58 | 0.306244654 |
20 | 45273 | 16.35 | 1.08 | 0.070726916 | 0.54 | 4.66 | 0.398631309 |
21 | 45293 | 17.31 | 0.96 | 0.058715596 | 0.48 | 5.62 | 0.48075278 |
22 | 45294 | 8.55 | -8.76 | -0.506065858 | -4.38 | -3.14 | -0.268605646 |
23 | 45299 | 8.75 | 0.2 | 0.023391813 | 0.1 | -2.94 | -0.251497006 |
24 | 45300 | 8.81 | 0.06 | 0.006857143 | 0.03 | -2.88 | -0.246364414 |
25 | 45301 | 9.26 | 0.45 | 0.05107832 | 0.225 | -2.43 | -0.207869974 |
26 | 45304 | 10.42 | 1.16 | 0.125269978 | 0.58 | -1.27 | -0.108639863 |
27 | 45305 | 11.07 | 0.65 | 0.062380038 | 0.325 | -0.62 | -0.053036784 |
28 | 45306 | 11.9 | 0.83 | 0.074977416 | 0.415 | 0.21 | 0.017964072 |
29 | 45309 | 11.96 | 0.06 | 0.005042017 | 0.03 | 0.27 | 0.023096664 |
30 | 45310 | 8.78 | -3.18 | -0.265886288 | -1.59 | -2.91 | -0.24893071 |
31 | 45311 | 7.33 | -1.45 | -0.165148064 | -0.725 | -4.36 | -0.372968349 |
32 | 45328 | 7.58 | 0.25 | 0.034106412 | 0.125 | -4.11 | -0.351582549 |
33 | 45329 | 8.41 | 0.83 | 0.109498681 | 0.415 | -3.28 | -0.280581694 |
34 | 45330 | 10.12 | 1.71 | 0.20332937 | 0.855 | -1.57 | -0.134302823 |