Skip to main content

sandbox_quant/ev/
price_model.rs

1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2pub enum PositionSide {
3    Long,
4    Short,
5}
6
7#[derive(Debug, Clone, Copy)]
8pub struct YNormal {
9    pub mu: f64,
10    pub sigma: f64,
11}
12
13#[derive(Debug, Clone, Copy)]
14pub struct SpotEvInputs {
15    pub p0: f64,
16    pub qty: f64,
17    pub side: PositionSide,
18    pub fee: f64,
19    pub slippage: f64,
20    pub borrow: f64,
21}
22
23#[derive(Debug, Clone, Copy)]
24pub struct FuturesEvInputs {
25    pub p0: f64,
26    pub qty: f64,
27    pub multiplier: f64,
28    pub side: PositionSide,
29    pub fee: f64,
30    pub slippage: f64,
31    pub funding: f64,
32    pub liq_risk: f64,
33}
34
35#[derive(Debug, Clone, Copy, Default)]
36pub struct EvStats {
37    pub ev: f64,
38    pub ev_std: f64,
39    pub p_win: f64,
40}
41
42pub fn spot_ev_from_y_normal(y: YNormal, i: SpotEvInputs) -> EvStats {
43    let p0 = i.p0.max(0.0);
44    let qty = i.qty.abs();
45    if p0 <= f64::EPSILON || qty <= f64::EPSILON {
46        return EvStats::default();
47    }
48    let sigma = y.sigma.max(0.0);
49    let cost = i.fee + i.slippage + i.borrow;
50    let signed = side_sign(i.side);
51    let (e_pt, var_pt) = lognormal_moments(p0, y.mu, sigma);
52    let pnl_mean = signed * qty * (e_pt - p0) - cost;
53    let pnl_std = (qty * var_pt.sqrt()).abs();
54    let p_win = p_win_lognormal(y.mu, sigma, p0, qty, signed, cost);
55    EvStats {
56        ev: pnl_mean,
57        ev_std: pnl_std,
58        p_win,
59    }
60}
61
62pub fn futures_ev_from_y_normal(y: YNormal, i: FuturesEvInputs) -> EvStats {
63    let p0 = i.p0.max(0.0);
64    let qty = i.qty.abs();
65    let m = i.multiplier.abs();
66    if p0 <= f64::EPSILON || qty <= f64::EPSILON || m <= f64::EPSILON {
67        return EvStats::default();
68    }
69    let sigma = y.sigma.max(0.0);
70    let cost = i.fee + i.slippage + i.funding + i.liq_risk;
71    let signed = side_sign(i.side);
72    let scale = m * qty;
73    let (e_pt, var_pt) = lognormal_moments(p0, y.mu, sigma);
74    let pnl_mean = signed * scale * (e_pt - p0) - cost;
75    let pnl_std = (scale * var_pt.sqrt()).abs();
76    let p_win = p_win_lognormal(y.mu, sigma, p0, scale, signed, cost);
77    EvStats {
78        ev: pnl_mean,
79        ev_std: pnl_std,
80        p_win,
81    }
82}
83
84fn side_sign(side: PositionSide) -> f64 {
85    match side {
86        PositionSide::Long => 1.0,
87        PositionSide::Short => -1.0,
88    }
89}
90
91fn lognormal_moments(p0: f64, mu: f64, sigma: f64) -> (f64, f64) {
92    let sigma2 = sigma * sigma;
93    let e_pt = p0 * (mu + 0.5 * sigma2).exp();
94    let var_pt = e_pt * e_pt * (sigma2.exp() - 1.0).max(0.0);
95    (e_pt, var_pt)
96}
97
98fn p_win_lognormal(mu: f64, sigma: f64, p0: f64, scale: f64, signed: f64, cost: f64) -> f64 {
99    if scale <= f64::EPSILON || p0 <= f64::EPSILON {
100        return 0.0;
101    }
102    let thresh = if signed > 0.0 {
103        p0 + cost / scale
104    } else {
105        p0 - cost / scale
106    };
107    if thresh <= 0.0 {
108        return if signed > 0.0 { 1.0 } else { 0.0 };
109    }
110    if sigma <= f64::EPSILON {
111        let pt = p0 * mu.exp();
112        return if signed > 0.0 {
113            (pt > thresh) as i32 as f64
114        } else {
115            (pt < thresh) as i32 as f64
116        };
117    }
118    let z = ((thresh / p0).ln() - mu) / sigma;
119    if signed > 0.0 {
120        1.0 - normal_cdf(z)
121    } else {
122        normal_cdf(z)
123    }
124}
125
126fn normal_cdf(x: f64) -> f64 {
127    0.5 * (1.0 + erf_approx(x / 2f64.sqrt()))
128}
129
130// Abramowitz-Stegun style approximation; sufficient for gating/probability display.
131fn erf_approx(x: f64) -> f64 {
132    let sign = if x < 0.0 { -1.0 } else { 1.0 };
133    let x = x.abs();
134    let t = 1.0 / (1.0 + 0.3275911 * x);
135    let a1 = 0.254829592;
136    let a2 = -0.284496736;
137    let a3 = 1.421413741;
138    let a4 = -1.453152027;
139    let a5 = 1.061405429;
140    let y = 1.0 - (((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * (-x * x).exp());
141    sign * y
142}