Skip to content

Commit 4b3a62f

Browse files
fix: Adapt all pages and components for dark and light mode support
Remove hardcoded colors from Plotly charts, HTML elements, and metric boxes so Streamlit can auto-theme correctly in both dark and light modes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 920afb9 commit 4b3a62f

File tree

3 files changed

+55
-122
lines changed

3 files changed

+55
-122
lines changed

Welcome.py

Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,42 @@
11
import streamlit as st
22

3-
# Set the page configuration with a custom title, icon, and layout
43
st.set_page_config(
54
page_title="RiskSim: Risk Management Simulator",
65
page_icon="🎲",
76
layout="wide",
87
)
98

10-
# Main title and subtitle with enhanced styling
119
st.markdown("""
12-
<style>
13-
.main-title {
14-
font-size: 3rem;
15-
color: #2c3e50;
16-
font-weight: bold;
17-
text-align: center;
18-
}
19-
.sub-title {
20-
font-size: 1.25rem;
21-
color: #7f8c8d;
22-
text-align: center;
23-
margin-bottom: 25px;
24-
}
25-
</style>
26-
<h1 class="main-title">Welcome to the Risk Management Simulator 🎲</h1>
27-
<h3 class="sub-title">Empowering Your Trading Decisions Through Risk Analysis</h3>
10+
<h1 style="font-size: 3rem; font-weight: bold; text-align: center;">
11+
Welcome to the Risk Management Simulator 🎲
12+
</h1>
13+
<h3 style="font-size: 1.25rem; opacity: 0.6; text-align: center; margin-bottom: 25px;">
14+
Empowering Your Trading Decisions Through Risk Analysis
15+
</h3>
2816
""", unsafe_allow_html=True)
2917

30-
# Place an image in the app
31-
# Replace 'header_image.jpg' with the path to your image file
32-
# st.image('docs/images/header_image.jpg', use_column_width=True, caption="Explore Risk and Portfolio Dynamics")
33-
# Display the image with a reduced size (width set to 700px)
34-
# st.image('docs/images/header_image.jpg', use_column_width=False, width=700, caption="Explore Risk and Portfolio Dynamics")
35-
# Use st.image to display the image with a specified width and centered alignment
3618
col1, col2, col3 = st.columns([2, 4, 2])
3719
with col2:
38-
st.image('docs/images/header_image.jpg', width=700)
20+
st.image("docs/images/header_image.jpg", width=700)
3921

40-
41-
# Explanations about the various pages
4222
st.markdown("""
4323
---
44-
#### 🔍 Explore the Features of RiskSim:
24+
#### Explore the Features of RiskSim:
4525
**Navigate Through the App Using the Sidebar:**
4626
47-
- **📊 Risk-Return Analysis**:
27+
- **Risk-Return Analysis**:
4828
- Simulate how various trading parameters, such as win rate, number of trades, and risk per trade, affect your overall performance.
4929
- Use Monte Carlo simulations to explore potential outcomes based on your trading strategy.
5030
- Visualize the trade-off between risk and return to optimize your approach.
5131
52-
- **🔗 Asset Correlation Simulation**:
32+
- **Asset Correlation Simulation**:
5333
- Generate portfolios of assets with different correlation structures, and simulate their price evolution over time.
5434
- Adjust the number of assets, correlation range, and key return parameters to test portfolio performance.
5535
- Analyze performance metrics such as Sharpe Ratio, Maximum Drawdown, and Annualized Return to assess the strength of your portfolio under various correlation regimes.
5636
5737
5838
---
59-
#### 📘 How to Get Started:
39+
#### How to Get Started:
6040
6141
1. **Select a Page**: Use the sidebar on the left to navigate between different analyses.
6242
2. **Adjust Parameters**: Each page offers interactive controls to modify the simulation parameters.

pages/1_🎯_risk_return_analysis.py

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,10 @@ def _add_expected_return_annotation(
3636
arrowsize=1.5,
3737
ax=0,
3838
ay=-30,
39-
font=dict(family="Arial, sans-serif", size=14, color="black"),
39+
font=dict(family="Arial, sans-serif", size=14),
4040
align="center",
41-
arrowcolor="black",
42-
bordercolor="black",
4341
borderwidth=1,
4442
borderpad=4,
45-
bgcolor="rgba(255, 255, 255, 0.9)",
4643
opacity=0.9,
4744
)
4845

@@ -62,7 +59,6 @@ def create_bar_chart_figure(
6259
y_tick_interval = y_range / 10 if y_range != 0 else 1
6360
y_ticks = np.arange(min_result, max_result + y_tick_interval, y_tick_interval)
6461

65-
# Single trace with per-bar colors so categorical axis works correctly
6662
fig = go.Figure(go.Bar(
6763
x=x_labels,
6864
y=averages,
@@ -72,7 +68,7 @@ def create_bar_chart_figure(
7268
for (rpur, _), avg in zip(sorted_results, averages)],
7369
text=[f"{avg:.2f}%" for avg in averages],
7470
textposition="outside",
75-
textfont=dict(size=12, color="black"),
71+
textfont=dict(size=12),
7672
))
7773

7874
fig.update_layout(
@@ -81,12 +77,11 @@ def create_bar_chart_figure(
8177
title="Average Percentage Return (%)",
8278
tickvals=y_ticks,
8379
ticktext=[f"{t:.2f}%" for t in y_ticks],
84-
showgrid=True, gridwidth=1, gridcolor="LightGrey",
80+
showgrid=True, gridwidth=1,
8581
),
8682
xaxis=dict(tickangle=-45, automargin=True),
8783
showlegend=False,
8884
height=600,
89-
font=dict(family="Helvetica, sans-serif", size=12, color="#333"),
9085
)
9186
fig.add_hline(y=0, line_dash="dash", line_color="gray")
9287

@@ -130,7 +125,6 @@ def create_win_rate_vs_return_chart(
130125
10,
131126
)
132127

133-
# Single trace with per-bar colors so categorical axis works correctly
134128
fig = go.Figure(go.Bar(
135129
x=x_labels,
136130
y=avg_returns,
@@ -154,11 +148,10 @@ def create_win_rate_vs_return_chart(
154148
title="Average Percentage Return (%)",
155149
tickvals=y_ticks,
156150
ticktext=[f"{t}%" for t in y_ticks],
157-
showgrid=True, gridwidth=1, gridcolor="LightGrey",
151+
showgrid=True, gridwidth=1,
158152
range=[np.floor(min_return / 10) * 10, np.ceil(max_return / 10) * 10],
159153
),
160154
showlegend=False,
161-
font=dict(family="Helvetica, sans-serif", size=12, color="#333"),
162155
height=600,
163156
)
164157
fig.add_hline(y=0, line_dash="dash", line_color="gray")
@@ -177,13 +170,10 @@ def create_win_rate_vs_return_chart(
177170

178171
def app() -> None:
179172
"""Main function for the Risk-Return Analysis page."""
180-
st.markdown(
181-
'<h1 style="color: #1c2e4a;">Risk-Return Analysis</h1>',
182-
unsafe_allow_html=True,
183-
)
173+
st.header("Risk-Return Analysis")
184174
st.markdown("""
185-
<div style="text-align: center; padding: 22px;">
186-
<h4 style="color: #5D6D7E;">Stay Alive Long Enough to Get Lucky</h4>
175+
<div style="text-align: center; padding: 12px 0 22px 0;">
176+
<h4 style="opacity: 0.6;">Stay Alive Long Enough to Get Lucky</h4>
187177
</div>
188178
""", unsafe_allow_html=True)
189179

utils/style.py

Lines changed: 37 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,12 @@
55

66

77
def link(link, text, **style):
8-
"""
9-
Create an HTML link element with given href and text.
10-
11-
Args:
12-
link (str): The URL the link points to.
13-
text (str): The text to display for the link.
14-
**style: Additional CSS styles to apply to the link.
15-
16-
Returns:
17-
HtmlElement: An HTML 'a' element.
18-
"""
8+
"""Create an HTML link element with given href and text."""
199
return a(_href=link, _target="_blank", style=styles(**style))(text)
2010

2111

2212
def layout(*args):
23-
"""
24-
Layout the footer at the bottom of the Streamlit app and inject custom JavaScript.
25-
26-
Args:
27-
*args: Variable length argument list to include strings or HtmlElement objects in the footer.
28-
"""
13+
"""Layout the footer at the bottom of the Streamlit app."""
2914
style = """
3015
<style>
3116
#MainMenu {visibility: hidden;}
@@ -34,137 +19,115 @@ def layout(*args):
3419
</style>
3520
"""
3621

37-
# Define styles for the footer container and horizontal line
3822
style_div = styles(
3923
position="fixed",
4024
left=0,
4125
bottom=0,
4226
margin=px(0, 0, 0, 0),
4327
width=percent(100),
44-
color="black",
4528
text_align="center",
4629
height="auto",
47-
opacity=1
30+
opacity=1,
4831
)
4932

5033
style_hr = styles(
5134
display="block",
5235
margin=px(0, 0, 0, 0),
5336
border_style="inset",
54-
border_width=px(2)
37+
border_width=px(2),
5538
)
5639

57-
# Create the footer body
5840
body = p(
59-
id='myFooter',
41+
id="myFooter",
6042
style=styles(
6143
margin=px(0, 0, 0, 0),
6244
padding=px(5),
6345
font_size="0.8rem",
64-
color="rgb(51,51,51)"
65-
)
66-
)
67-
68-
# Combine the elements into the footer div
69-
foot = div(
70-
style=style_div
71-
)(
72-
hr(
73-
style=style_hr
46+
color="inherit",
7447
),
75-
body
7648
)
7749

78-
# Inject the custom style into the Streamlit app
50+
foot = div(style=style_div)(hr(style=style_hr), body)
51+
7952
st.markdown(style, unsafe_allow_html=True)
8053

81-
# Add the provided arguments to the body of the footer
8254
for arg in args:
83-
if isinstance(arg, str):
84-
body(arg)
85-
elif isinstance(arg, HtmlElement):
55+
if isinstance(arg, (str, HtmlElement)):
8656
body(arg)
8757

88-
# Render the footer in the Streamlit app
8958
st.markdown(str(foot), unsafe_allow_html=True)
9059

91-
# JavaScript code to dynamically change the footer text color based on the background color
92-
js_code = '''
60+
# JS to adapt footer color to Streamlit theme
61+
js_code = """
9362
<script>
9463
function rgbReverse(rgb){
9564
var r = rgb[0]*0.299;
9665
var g = rgb[1]*0.587;
9766
var b = rgb[2]*0.114;
98-
9967
if ((r + g + b)/255 > 0.5){
10068
return "rgb(49, 51, 63)"
101-
}else{
69+
} else {
10270
return "rgb(250, 250, 250)"
10371
}
104-
10572
};
10673
var stApp_css = window.parent.document.querySelector("#root > div:nth-child(1) > div > div > div");
10774
window.onload = function () {
10875
var mutationObserver = new MutationObserver(function(mutations) {
109-
mutations.forEach(function(mutation) {
110-
var bgColor = window.getComputedStyle(stApp_css).backgroundColor.replace("rgb(", "").replace(")", "").split(", ");
111-
var fontColor = rgbReverse(bgColor);
112-
var pTag = window.parent.document.getElementById("myFooter");
113-
pTag.style.color = fontColor;
114-
});
115-
});
116-
117-
/**Element**/
118-
mutationObserver.observe(stApp_css, {
119-
attributes: true,
120-
characterData: true,
121-
childList: true,
122-
subtree: true,
123-
attributeOldValue: true,
124-
characterDataOldValue: true
76+
mutations.forEach(function(mutation) {
77+
var bgColor = window.getComputedStyle(stApp_css).backgroundColor.replace("rgb(", "").replace(")", "").split(", ");
78+
var fontColor = rgbReverse(bgColor);
79+
var pTag = window.parent.document.getElementById("myFooter");
80+
if (pTag) { pTag.style.color = fontColor; }
12581
});
82+
});
83+
mutationObserver.observe(stApp_css, {
84+
attributes: true,
85+
characterData: true,
86+
childList: true,
87+
subtree: true,
88+
attributeOldValue: true,
89+
characterDataOldValue: true
90+
});
12691
}
12792
</script>
128-
'''
129-
# Inject the JavaScript code into the Streamlit app
93+
"""
13094
components.html(js_code)
13195

13296

13397
def footer():
134-
"""
135-
Display a footer with a custom message and link.
136-
"""
98+
"""Display a footer with a custom message and link."""
13799
myargs = [
138100
"Made with ❤️ by ",
139101
link("https://github.com/chrisduvillard", "Chris"),
140102
]
141103
layout(*myargs)
142104

143105

144-
def metric_box(title, value):
106+
def metric_box(title: str, value: str) -> str:
107+
"""Return an HTML metric card that adapts to light/dark Streamlit themes."""
145108
return f"""
146109
<div style="
147110
padding: 20px;
148111
border-radius: 15px;
149-
background: linear-gradient(135deg, #f9f9f9 0%, #ffffff 100%);
150-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
112+
background: color-mix(in srgb, currentColor 6%, transparent);
113+
box-shadow: 0 2px 8px rgba(128, 128, 128, 0.15);
151114
text-align: center;
152115
margin: 10px;
153116
width: 100%;
154117
font-family: 'Arial', sans-serif;
155-
transition: transform 0.2s ease-in-out;
156118
">
157119
<h4 style="
158-
color: #333333;
120+
color: inherit;
121+
opacity: 0.7;
159122
margin-bottom: 12px;
160-
font-size: 16px;
123+
font-size: 14px;
161124
text-transform: uppercase;
162125
letter-spacing: 1px;
163126
">{title}</h4>
164127
<p style="
165-
font-size: 18px;
128+
font-size: 20px;
166129
font-weight: bold;
167-
color: #355C7D;
130+
color: inherit;
168131
margin: 0;
169132
">{value}</p>
170133
</div>

0 commit comments

Comments
 (0)