Skip to content

Commit b6f4ee0

Browse files
committed
Adding color ranges
1 parent 6f7ed7d commit b6f4ee0

1 file changed

Lines changed: 152 additions & 8 deletions

File tree

‎tearsheet/report/template.html‎

Lines changed: 152 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,49 @@
166166
}
167167
#sidebar-btn:hover, #sidebar-btn.open { color: var(--blue); border-color: var(--blue); }
168168
body.sidebar-open #sidebar-btn { left: 228px; }
169+
/* KPI help icon & tooltip (shared) */
170+
.kpi-label-row { display: flex; align-items: center; gap: 0; }
171+
.kpi-help { position: relative; display: inline-flex; align-items: center; }
172+
.kpi-help-link {
173+
display: inline-flex; align-items: center; justify-content: center;
174+
width: 13px; height: 13px; border-radius: 50%;
175+
background: var(--muted); color: var(--bg);
176+
font-size: 9px; font-weight: 700; line-height: 1;
177+
text-decoration: none; margin-left: 5px; flex-shrink: 0;
178+
}
179+
.kpi-help-link:hover { background: var(--blue); }
180+
.kpi-help-popup {
181+
position: absolute; bottom: calc(100% + 7px); left: 50%;
182+
transform: translateX(-50%);
183+
background: #1c2129; border: 1px solid var(--border); border-radius: 6px;
184+
padding: 8px 10px; font-size: 11px; color: var(--text); width: 220px;
185+
display: none; z-index: 100; pointer-events: none;
186+
text-transform: none; letter-spacing: normal; font-weight: 400; line-height: 1.45;
187+
}
188+
.kpi-help:hover .kpi-help-popup { display: block; }
189+
/* KPI color scale (shared) */
190+
.kpi-scale-wrap { margin-top: 9px; }
191+
.kpi-marker-row { position: relative; height: 10px; }
192+
.kpi-scale-marker {
193+
position: absolute; bottom: 0; transform: translateX(-50%);
194+
width: 0; height: 0;
195+
border-left: 5px solid transparent; border-right: 5px solid transparent;
196+
border-top: 7px solid var(--text);
197+
}
198+
.kpi-scale-bar { height: 6px; border-radius: 3px; }
199+
.kpi-tier { font-size: 9px; color: var(--muted); text-align: center; margin-top: 4px; }
200+
/* SQN gradient: red → green (higher is better) */
201+
.sqn-scale-bar { background: linear-gradient(to right, #f85149 0%, #d4a020 30%, #3fb950 100%); }
202+
/* Ulcer Index gradient: green → red (lower is better) */
203+
.ulcer-scale-bar { background: linear-gradient(to right, #3fb950 0%, #d4a020 50%, #f85149 100%); }
204+
/* Profit Factor gradient: red → green (higher is better, 0–10 scale) */
205+
.pf-scale-bar { background: linear-gradient(to right, #f85149 0%, #d4a020 13%, #3fb950 30%, #56e07f 100%); }
206+
/* Expectancy gradient: red (neg) → yellow (0–5) → green (5–25) → bright green (25+); scale −10 to 50 */
207+
.expectancy-scale-bar { background: linear-gradient(to right, #f85149 0%, #f85149 16.7%, #d4a020 22%, #d4a020 25%, #3fb950 35%, #3fb950 58.3%, #56e07f 100%); }
208+
/* Alpha gradient: red (<0) → yellow (0–0.5) → yellow-green (0.5–2) → green (2–4) → bright green (4+); scale −2 to 6 */
209+
.alpha-scale-bar { background: linear-gradient(to right, #f85149 0%, #f85149 25%, #d4a020 31%, #8bc34a 50%, #3fb950 63%, #56e07f 75%, #56e07f 100%); }
210+
/* Strategy Return gradient: red (<0) → amber (0–10%) → yellow-green (10–20%) → green (20–35%) → bright green (35%+); scale −20 to 100 */
211+
.strategy-return-scale-bar { background: linear-gradient(to right, #f85149 0%, #f85149 16.7%, #d4a020 25%, #8bc34a 33%, #3fb950 46%, #56e07f 100%); }
169212
</style>
170213
{{ plotly_js | safe }}
171214
</head>
@@ -195,14 +238,85 @@ <h2 id="sec-kpi">Key Performance Indicators</h2>
195238
<div class="kpi"><div class="label">Win Rate</div><div class="value">{{ "{:.1f}%".format(metrics.win_rate * 100) }}</div></div>
196239
<div class="kpi"><div class="label">Avg Win</div><div class="value pos">${{ "{:,.2f}".format(metrics.avg_win) }}</div></div>
197240
<div class="kpi"><div class="label">Avg Loss</div><div class="value neg">${{ "{:,.2f}".format(metrics.avg_loss) }}</div></div>
198-
<div class="kpi"><div class="label">Profit Factor</div><div class="value">{{ "{:.2f}".format(metrics.profit_factor) if metrics.profit_factor is not none else "—" }}</div></div>
199-
<div class="kpi"><div class="label">Expectancy</div><div class="value {{ 'pos' if metrics.expectancy >= 0 else 'neg' }}">${{ "{:,.2f}".format(metrics.expectancy) }}</div></div>
200-
<div class="kpi"><div class="label">SQN</div><div class="value">{{ "{:.2f}".format(metrics.sqn) if metrics.sqn is not none else "—" }}</div></div>
241+
<div class="kpi">
242+
<div class="label kpi-label-row">Profit Factor<span class="kpi-help"><a href="https://www.tradezella.com/blog/profit-factor" target="_blank" rel="noopener" class="kpi-help-link">?</a><span class="kpi-help-popup">Profit Factor is the ratio of gross profit to gross loss. Above 1.0 means net-positive; 1.3–1.75 is a realistic sustainable edge; above 3.0 may suggest over-fitting.</span></span></div>
243+
<div class="value">{{ "{:.2f}".format(metrics.profit_factor) if metrics.profit_factor is not none else "—" }}</div>
244+
{% if metrics.profit_factor is not none %}
245+
{% set _pf_max = 10.0 %}
246+
{% if metrics.profit_factor < 0 %}{% set _pf_clamped = 0.0 %}{% elif metrics.profit_factor > _pf_max %}{% set _pf_clamped = _pf_max %}{% else %}{% set _pf_clamped = metrics.profit_factor %}{% endif %}
247+
{% set _pf_pct = (_pf_clamped / _pf_max * 100) | round(1) %}
248+
{% if metrics.profit_factor < 1.0 %}{% set _pf_tier = "Losing" %}
249+
{% elif metrics.profit_factor < 1.3 %}{% set _pf_tier = "Marginal" %}
250+
{% elif metrics.profit_factor < 1.75 %}{% set _pf_tier = "Respectable" %}
251+
{% elif metrics.profit_factor < 3.0 %}{% set _pf_tier = "Strong" %}
252+
{% else %}{% set _pf_tier = "Exceptional ⚠" %}{% endif %}
253+
<div class="kpi-scale-wrap">
254+
<div class="kpi-marker-row"><div class="kpi-scale-marker" style="left:{{ _pf_pct }}%"></div></div>
255+
<div class="kpi-scale-bar pf-scale-bar"></div>
256+
<div class="kpi-tier">{{ _pf_tier }}</div>
257+
</div>
258+
{% endif %}
259+
</div>
260+
<div class="kpi">
261+
<div class="label kpi-label-row">Expectancy<span class="kpi-help"><a href="https://www.heygotrade.com/en/blog/what-is-expectancy-in-trading/" target="_blank" rel="noopener" class="kpi-help-link">?</a><span class="kpi-help-popup">Expectancy is the average dollar gain or loss per trade. Positive means a profitable edge; above $5 is very good; above $25 is excellent.</span></span></div>
262+
<div class="value {{ 'pos' if metrics.expectancy >= 0 else 'neg' }}">${{ "{:,.2f}".format(metrics.expectancy) }}</div>
263+
{% set _exp_min = -10.0 %}
264+
{% set _exp_max = 50.0 %}
265+
{% if metrics.expectancy < _exp_min %}{% set _exp_clamped = _exp_min %}{% elif metrics.expectancy > _exp_max %}{% set _exp_clamped = _exp_max %}{% else %}{% set _exp_clamped = metrics.expectancy %}{% endif %}
266+
{% set _exp_pct = ((_exp_clamped - _exp_min) / 60.0 * 100) | round(1) %}
267+
{% if metrics.expectancy < 0 %}{% set _exp_tier = "Losing" %}
268+
{% elif metrics.expectancy < 5 %}{% set _exp_tier = "Solid" %}
269+
{% elif metrics.expectancy < 25 %}{% set _exp_tier = "Very Good" %}
270+
{% else %}{% set _exp_tier = "Excellent" %}{% endif %}
271+
<div class="kpi-scale-wrap">
272+
<div class="kpi-marker-row"><div class="kpi-scale-marker" style="left:{{ _exp_pct }}%"></div></div>
273+
<div class="kpi-scale-bar expectancy-scale-bar"></div>
274+
<div class="kpi-tier">{{ _exp_tier }}</div>
275+
</div>
276+
</div>
277+
<div class="kpi">
278+
<div class="label kpi-label-row">SQN<span class="kpi-help"><a href="https://tradersync.com/support/system-quality-number-sqn" target="_blank" rel="noopener" class="kpi-help-link">?</a><span class="kpi-help-popup">SQN (System Quality Number) scores a system's consistency by combining expectancy with the square root of trade count. Higher values indicate more statistically reliable performance.</span></span></div>
279+
<div class="value">{{ "{:.2f}".format(metrics.sqn) if metrics.sqn is not none else "—" }}</div>
280+
{% if metrics.sqn is not none %}
281+
{% set _sqn_max = 7.5 %}
282+
{% if metrics.sqn < 0 %}{% set _sqn_clamped = 0.0 %}{% elif metrics.sqn > _sqn_max %}{% set _sqn_clamped = _sqn_max %}{% else %}{% set _sqn_clamped = metrics.sqn %}{% endif %}
283+
{% set _sqn_pct = (_sqn_clamped / _sqn_max * 100) | round(1) %}
284+
{% if metrics.sqn < 1.6 %}{% set _sqn_tier = "Poor" %}
285+
{% elif metrics.sqn < 2.0 %}{% set _sqn_tier = "Below Average" %}
286+
{% elif metrics.sqn < 2.5 %}{% set _sqn_tier = "Average" %}
287+
{% elif metrics.sqn < 3.0 %}{% set _sqn_tier = "Good" %}
288+
{% elif metrics.sqn <= 5.0 %}{% set _sqn_tier = "Excellent" %}
289+
{% elif metrics.sqn <= 6.9 %}{% set _sqn_tier = "Superb" %}
290+
{% else %}{% set _sqn_tier = "Holy Grail ⚠" %}{% endif %}
291+
<div class="kpi-scale-wrap">
292+
<div class="kpi-marker-row"><div class="kpi-scale-marker" style="left:{{ _sqn_pct }}%"></div></div>
293+
<div class="kpi-scale-bar sqn-scale-bar"></div>
294+
<div class="kpi-tier">{{ _sqn_tier }}</div>
295+
</div>
296+
{% endif %}
297+
</div>
201298
<div class="kpi"><div class="label">Max Drawdown</div><div class="value neg">-${{ "{:,.2f}".format(metrics.max_drawdown) }}</div></div>
202299
<div class="kpi"><div class="label">Sharpe</div><div class="value">{{ "{:.2f}".format(metrics.sharpe_ratio) if metrics.sharpe_ratio is not none else "—" }}</div></div>
203300
<div class="kpi"><div class="label">Sortino</div><div class="value">{{ "{:.2f}".format(metrics.sortino_ratio) if metrics.sortino_ratio is not none else "—" }}</div></div>
204301
<div class="kpi"><div class="label">Calmar</div><div class="value">{{ "{:.2f}".format(metrics.calmar_ratio) if metrics.calmar_ratio is not none else "—" }}</div></div>
205-
<div class="kpi"><div class="label">Ulcer Index</div><div class="value">{{ "{:.4f}".format(metrics.ulcer_index) if metrics.ulcer_index is not none else "—" }}</div></div>
302+
<div class="kpi">
303+
<div class="label kpi-label-row">Ulcer Index<span class="kpi-help"><a href="https://www.investopedia.com/terms/u/ulcerindex.asp" target="_blank" rel="noopener" class="kpi-help-link">?</a><span class="kpi-help-popup">The Ulcer Index measures the depth and duration of drawdowns in equity. Lower is better—near zero means the account consistently trades at or near new highs.</span></span></div>
304+
<div class="value">{{ "{:.4f}".format(metrics.ulcer_index) if metrics.ulcer_index is not none else "—" }}</div>
305+
{% if metrics.ulcer_index is not none %}
306+
{% set _ui_max = 12.0 %}
307+
{% if metrics.ulcer_index < 0 %}{% set _ui_clamped = 0.0 %}{% elif metrics.ulcer_index > _ui_max %}{% set _ui_clamped = _ui_max %}{% else %}{% set _ui_clamped = metrics.ulcer_index %}{% endif %}
308+
{% set _ui_pct = (_ui_clamped / _ui_max * 100) | round(1) %}
309+
{% if metrics.ulcer_index < 1 %}{% set _ui_tier = "Near Zero" %}
310+
{% elif metrics.ulcer_index < 5 %}{% set _ui_tier = "Low Risk" %}
311+
{% elif metrics.ulcer_index < 10 %}{% set _ui_tier = "Moderate Risk" %}
312+
{% else %}{% set _ui_tier = "High Risk" %}{% endif %}
313+
<div class="kpi-scale-wrap">
314+
<div class="kpi-marker-row"><div class="kpi-scale-marker" style="left:{{ _ui_pct }}%"></div></div>
315+
<div class="kpi-scale-bar ulcer-scale-bar"></div>
316+
<div class="kpi-tier">{{ _ui_tier }}</div>
317+
</div>
318+
{% endif %}
319+
</div>
206320
<div class="kpi"><div class="label">Consec. Wins</div><div class="value">{{ metrics.max_consec_wins }}</div></div>
207321
<div class="kpi"><div class="label">Consec. Losses</div><div class="value">{{ metrics.max_consec_losses }}</div></div>
208322
</div>
@@ -665,15 +779,45 @@ <h3>Monthly P&amp;L</h3>
665779
{% if benchmark_metrics %}
666780
<h2 id="sec-benchmark">Benchmark Comparison (S&amp;P 500 / {{ benchmark_metrics.ticker or "SPY" }})</h2>
667781
<div class="kpi-grid" style="margin-bottom:12px">
668-
<div class="kpi"><div class="label">Strategy Return</div>
782+
<div class="kpi">
783+
<div class="label kpi-label-row">Strategy Return<span class="kpi-help"><a href="https://www.nerdwallet.com/investing/learn/average-stock-market-return" target="_blank" rel="noopener" class="kpi-help-link">?</a><span class="kpi-help-popup">Cumulative return of the strategy over the period. The market historical average is ~10% annually. Active traders typically target 15–35%; above 35% is exceptional.</span></span></div>
669784
<div class="value {{ 'pos' if benchmark_metrics.strategy_total_return_pct >= 0 else 'neg' }}">
670-
{{ "{:.2f}%".format(benchmark_metrics.strategy_total_return_pct) }}</div></div>
785+
{{ "{:.2f}%".format(benchmark_metrics.strategy_total_return_pct) }}</div>
786+
{% set _sr_min = -20.0 %}
787+
{% set _sr_max = 100.0 %}
788+
{% if benchmark_metrics.strategy_total_return_pct < _sr_min %}{% set _sr_clamped = _sr_min %}{% elif benchmark_metrics.strategy_total_return_pct > _sr_max %}{% set _sr_clamped = _sr_max %}{% else %}{% set _sr_clamped = benchmark_metrics.strategy_total_return_pct %}{% endif %}
789+
{% set _sr_pct = ((_sr_clamped - _sr_min) / 120.0 * 100) | round(1) %}
790+
{% if benchmark_metrics.strategy_total_return_pct < 0 %}{% set _sr_tier = "Losing" %}
791+
{% elif benchmark_metrics.strategy_total_return_pct < 10 %}{% set _sr_tier = "Below Market" %}
792+
{% elif benchmark_metrics.strategy_total_return_pct < 20 %}{% set _sr_tier = "Market Rate" %}
793+
{% elif benchmark_metrics.strategy_total_return_pct < 35 %}{% set _sr_tier = "Strong" %}
794+
{% else %}{% set _sr_tier = "Exceptional" %}{% endif %}
795+
<div class="kpi-scale-wrap">
796+
<div class="kpi-marker-row"><div class="kpi-scale-marker" style="left:{{ _sr_pct }}%"></div></div>
797+
<div class="kpi-scale-bar strategy-return-scale-bar"></div>
798+
<div class="kpi-tier">{{ _sr_tier }}</div>
799+
</div>
800+
</div>
671801
<div class="kpi"><div class="label">S&amp;P 500 Return ({{ benchmark_metrics.ticker or "SPY" }})</div>
672802
<div class="value {{ 'pos' if benchmark_metrics.benchmark_total_return_pct >= 0 else 'neg' }}">
673803
{{ "{:.2f}%".format(benchmark_metrics.benchmark_total_return_pct) }}</div></div>
674-
<div class="kpi"><div class="label">Alpha</div>
804+
<div class="kpi">
805+
<div class="label kpi-label-row">Alpha<span class="kpi-help"><a href="https://www.investopedia.com/terms/a/alpha.asp" target="_blank" rel="noopener" class="kpi-help-link">?</a><span class="kpi-help-popup">Alpha measures excess return vs. the benchmark. Zero means matching the benchmark; positive values indicate outperformance; above 4% is exceptional.</span></span></div>
675806
<div class="value {{ 'pos' if benchmark_metrics.alpha >= 0 else 'neg' }}">
676-
{{ "{:.2f}%".format(benchmark_metrics.alpha) }}</div></div>
807+
{{ "{:.2f}%".format(benchmark_metrics.alpha) }}</div>
808+
{% if benchmark_metrics.alpha < -2 %}{% set _alpha_clamped = -2.0 %}{% elif benchmark_metrics.alpha > 6 %}{% set _alpha_clamped = 6.0 %}{% else %}{% set _alpha_clamped = benchmark_metrics.alpha %}{% endif %}
809+
{% set _alpha_pct = ((_alpha_clamped + 2) / 8.0 * 100) | round(1) %}
810+
{% if benchmark_metrics.alpha < 0 %}{% set _alpha_tier = "Underperform" %}
811+
{% elif benchmark_metrics.alpha < 0.5 %}{% set _alpha_tier = "Average" %}
812+
{% elif benchmark_metrics.alpha < 3 %}{% set _alpha_tier = "Good" %}
813+
{% elif benchmark_metrics.alpha < 4 %}{% set _alpha_tier = "Very Good" %}
814+
{% else %}{% set _alpha_tier = "Exceptional" %}{% endif %}
815+
<div class="kpi-scale-wrap">
816+
<div class="kpi-marker-row"><div class="kpi-scale-marker" style="left:{{ _alpha_pct }}%"></div></div>
817+
<div class="kpi-scale-bar alpha-scale-bar"></div>
818+
<div class="kpi-tier">{{ _alpha_tier }}</div>
819+
</div>
820+
</div>
677821
{% if benchmark_metrics.beta is not none %}
678822
<div class="kpi"><div class="label">Beta</div><div class="value">{{ "{:.2f}".format(benchmark_metrics.beta) }}</div></div>
679823
<div class="kpi"><div class="label">Alpha (ann.)</div>

0 commit comments

Comments
 (0)