Skip to content

Commit a82d2c4

Browse files
fix: Annotation now points to correct bar in both charts
The "Expected Return" annotation was misaligned because Plotly can't resolve categorical x-positions when each bar is a separate trace. Switched both charts to single-trace bar plots with per-bar colors. Also removed % from x-labels (using "WR 40" format) since Plotly interprets % as a special character in annotation coordinates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3fd90e9 commit a82d2c4

10 files changed

+31
-30
lines changed

docs/images/correlation_full.png

4.78 KB
Loading
-1.15 KB
Loading
-163 KB
Binary file not shown.
-11.5 KB
Loading

docs/images/correlation_prices.png

-22.8 KB
Loading

docs/images/correlation_sweep.png

-2.27 KB
Loading
-267 Bytes
Loading

docs/images/risk_return_rpur.png

-2.21 KB
Loading
-77 Bytes
Loading

pages/1_🎯_risk_return_analysis.py

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -53,27 +53,27 @@ def create_bar_chart_figure(
5353
) -> go.Figure:
5454
"""Bar chart of average % return over different RPUR levels."""
5555
sorted_results = sorted(results.items(), key=lambda x: np.mean(x[1]))
56-
averages = [np.mean(data) for _, data in sorted_results]
56+
averages = [float(np.mean(data)) for _, data in sorted_results]
5757
colors = _viridis_colors(averages)
58+
x_labels = [f"RPR {rpur:.2f}" for rpur, _ in sorted_results]
5859

5960
min_result, max_result = min(averages), max(averages)
6061
y_range = max_result - min_result
6162
y_tick_interval = y_range / 10 if y_range != 0 else 1
6263
y_ticks = np.arange(min_result, max_result + y_tick_interval, y_tick_interval)
6364

64-
fig = go.Figure()
65-
for i, (rpur, data) in enumerate(sorted_results):
66-
avg = averages[i]
67-
fig.add_trace(go.Bar(
68-
x=[f"RPR {rpur:.2f}"],
69-
y=[avg],
70-
marker_color=colors[i],
71-
hoverinfo="text",
72-
hovertext=f"RPUR: {rpur:.2f}<br>Avg Return: {avg:.2f}%",
73-
text=[f"{avg:.2f}%"],
74-
textposition="outside",
75-
textfont=dict(size=12, color="black"),
76-
))
65+
# Single trace with per-bar colors so categorical axis works correctly
66+
fig = go.Figure(go.Bar(
67+
x=x_labels,
68+
y=averages,
69+
marker_color=colors,
70+
hoverinfo="text",
71+
hovertext=[f"RPUR: {rpur:.2f}<br>Avg Return: {avg:.2f}%"
72+
for (rpur, _), avg in zip(sorted_results, averages)],
73+
text=[f"{avg:.2f}%" for avg in averages],
74+
textposition="outside",
75+
textfont=dict(size=12, color="black"),
76+
))
7777

7878
fig.update_layout(
7979
title="Average Percentage Return Over Different Levels of Return Per Unit Risk",
@@ -90,7 +90,7 @@ def create_bar_chart_figure(
9090
)
9191
fig.add_hline(y=0, line_dash="dash", line_color="gray")
9292

93-
avg_return_for_annotation = np.mean(results[return_per_unit_risk_value])
93+
avg_return_for_annotation = float(np.mean(results[return_per_unit_risk_value]))
9494
_add_expected_return_annotation(
9595
fig,
9696
x_label=f"RPR {return_per_unit_risk_value:.2f}",
@@ -121,6 +121,7 @@ def create_win_rate_vs_return_chart(
121121
avg_returns.append(float(np.mean(pct_returns)))
122122

123123
colors = _viridis_colors(avg_returns)
124+
x_labels = [f"WR {r:.0f}" for r in win_rates]
124125
min_return, max_return = min(avg_returns), max(avg_returns)
125126
y_range = max_return - min_return
126127
y_ticks = np.arange(
@@ -129,25 +130,25 @@ def create_win_rate_vs_return_chart(
129130
10,
130131
)
131132

132-
fig = go.Figure()
133-
for i, (rate, ret) in enumerate(zip(win_rates, avg_returns)):
134-
fig.add_trace(go.Bar(
135-
x=[f"{rate}%"],
136-
y=[ret],
137-
text=[f"{ret:.2f}%"],
138-
textposition="inside" if ret < 0 else "outside",
139-
marker_color=colors[i],
140-
textfont=dict(color="white" if ret < 0 else "black", size=12),
141-
hoverinfo="text",
142-
hovertext=f"Win Rate: {rate}%<br>Return: {ret:.2f}%",
143-
))
133+
# Single trace with per-bar colors so categorical axis works correctly
134+
fig = go.Figure(go.Bar(
135+
x=x_labels,
136+
y=avg_returns,
137+
text=[f"{r:.2f}%" for r in avg_returns],
138+
textposition=["inside" if r < 0 else "outside" for r in avg_returns],
139+
marker_color=colors,
140+
textfont=dict(size=12),
141+
hoverinfo="text",
142+
hovertext=[f"Win Rate: {wr:.0f}%<br>Return: {r:.2f}%"
143+
for wr, r in zip(win_rates, avg_returns)],
144+
))
144145

145146
fig.update_layout(
146147
title="Average Percentage Return vs. Win Rate",
147148
xaxis=dict(
148149
title="Win Rate (%)",
149-
tickvals=win_rates,
150-
ticktext=[f"{t}%" for t in win_rates],
150+
tickangle=-45,
151+
automargin=True,
151152
),
152153
yaxis=dict(
153154
title="Average Percentage Return (%)",
@@ -166,7 +167,7 @@ def create_win_rate_vs_return_chart(
166167
avg_for_annotation = float(np.interp(win_rate_value, win_rates, avg_returns))
167168
_add_expected_return_annotation(
168169
fig,
169-
x_label=f"{win_rate_value}%",
170+
x_label=f"WR {win_rate_value:.0f}",
170171
y_value=avg_for_annotation,
171172
y_range=y_range,
172173
)

0 commit comments

Comments
 (0)