File size: 1,968 Bytes
27c0524
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
"""Value formatting shared by every part of the UI.

One rule runs through all of it: a number that does not exist must never render
as a number that does. Empty segments become an em dash, not `0.00`.
"""

from __future__ import annotations

import pandas as pd

EM = "—"


def pct(v, digits: int = 1, signed: bool = True) -> str:
    if v is None or pd.isna(v):
        return EM
    return f"{v * 100:+.{digits}f}%" if signed else f"{v * 100:.{digits}f}%"


def num(v, digits: int = 2) -> str:
    if v is None or pd.isna(v):
        return EM
    return f"{v:.{digits}f}"


def money(v) -> str:
    if v is None or pd.isna(v):
        return EM
    return f"${v:,.0f}"


def count(v) -> str:
    if v is None or pd.isna(v):
        return EM
    return f"{int(v):,}"


def seg(metrics, fmt, *args, **kwargs) -> str:
    """Format a segment metric, or an em dash when that segment has no bars.

    "The out-of-sample Sharpe is zero" and "there is no out-of-sample period"
    are different claims. Only one of them is ever true here.
    """
    if metrics is None or getattr(metrics, "bars", 0) == 0:
        return EM
    return fmt(*args, **kwargs)


def tone(v) -> str:
    """CSS class for a signed value."""
    if v is None or pd.isna(v) or v == 0:
        return ""
    return "bit-up" if v > 0 else "bit-down"


def arrow(v) -> str:
    """Direction as a glyph, so colour is never the only encoding."""
    if v is None or pd.isna(v) or v == 0:
        return ""
    return " ▲" if v > 0 else " ▼"


def sharpe_tone(v) -> str:
    if v is None or pd.isna(v):
        return "var(--text-tertiary)"
    if v >= 1.0:
        return "var(--accent-moss-strong)"
    if v < 0:
        return "var(--fin-down)"
    return "var(--text-secondary)"


def esc(s) -> str:
    """Minimal HTML escaping for values interpolated into markup."""
    return (str(s).replace("&", "&amp;").replace("<", "&lt;")
            .replace(">", "&gt;").replace('"', "&quot;"))