|
166 | 166 | } |
167 | 167 | #sidebar-btn:hover, #sidebar-btn.open { color: var(--blue); border-color: var(--blue); } |
168 | 168 | 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%); } |
169 | 212 | </style> |
170 | 213 | {{ plotly_js | safe }} |
171 | 214 | </head> |
@@ -195,14 +238,85 @@ <h2 id="sec-kpi">Key Performance Indicators</h2> |
195 | 238 | <div class="kpi"><div class="label">Win Rate</div><div class="value">{{ "{:.1f}%".format(metrics.win_rate * 100) }}</div></div> |
196 | 239 | <div class="kpi"><div class="label">Avg Win</div><div class="value pos">${{ "{:,.2f}".format(metrics.avg_win) }}</div></div> |
197 | 240 | <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> |
201 | 298 | <div class="kpi"><div class="label">Max Drawdown</div><div class="value neg">-${{ "{:,.2f}".format(metrics.max_drawdown) }}</div></div> |
202 | 299 | <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> |
203 | 300 | <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> |
204 | 301 | <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> |
206 | 320 | <div class="kpi"><div class="label">Consec. Wins</div><div class="value">{{ metrics.max_consec_wins }}</div></div> |
207 | 321 | <div class="kpi"><div class="label">Consec. Losses</div><div class="value">{{ metrics.max_consec_losses }}</div></div> |
208 | 322 | </div> |
@@ -665,15 +779,45 @@ <h3>Monthly P&L</h3> |
665 | 779 | {% if benchmark_metrics %} |
666 | 780 | <h2 id="sec-benchmark">Benchmark Comparison (S&P 500 / {{ benchmark_metrics.ticker or "SPY" }})</h2> |
667 | 781 | <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> |
669 | 784 | <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> |
671 | 801 | <div class="kpi"><div class="label">S&P 500 Return ({{ benchmark_metrics.ticker or "SPY" }})</div> |
672 | 802 | <div class="value {{ 'pos' if benchmark_metrics.benchmark_total_return_pct >= 0 else 'neg' }}"> |
673 | 803 | {{ "{:.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> |
675 | 806 | <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> |
677 | 821 | {% if benchmark_metrics.beta is not none %} |
678 | 822 | <div class="kpi"><div class="label">Beta</div><div class="value">{{ "{:.2f}".format(benchmark_metrics.beta) }}</div></div> |
679 | 823 | <div class="kpi"><div class="label">Alpha (ann.)</div> |
|
0 commit comments