@ebowwa/quant-rust 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +161 -0
- package/bun-ffi.d.ts +54 -0
- package/dist/index.js +576 -0
- package/dist/src/index.d.ts +324 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/types/index.d.ts +403 -0
- package/dist/types/index.d.ts.map +1 -0
- package/native/README.md +62 -0
- package/native/darwin-arm64/libquant_rust.dylib +0 -0
- package/package.json +70 -0
- package/scripts/postinstall.cjs +85 -0
- package/src/ffi.rs +496 -0
- package/src/index.ts +1073 -0
- package/src/indicators/ma.rs +222 -0
- package/src/indicators/mod.rs +18 -0
- package/src/indicators/momentum.rs +353 -0
- package/src/indicators/sr.rs +195 -0
- package/src/indicators/trend.rs +351 -0
- package/src/indicators/volatility.rs +270 -0
- package/src/indicators/volume.rs +213 -0
- package/src/lib.rs +130 -0
- package/src/patterns/breakout.rs +431 -0
- package/src/patterns/chart.rs +772 -0
- package/src/patterns/mod.rs +394 -0
- package/src/patterns/sr.rs +423 -0
- package/src/prediction/amm.rs +338 -0
- package/src/prediction/arbitrage.rs +230 -0
- package/src/prediction/calibration.rs +317 -0
- package/src/prediction/kelly.rs +232 -0
- package/src/prediction/lmsr.rs +194 -0
- package/src/prediction/mod.rs +59 -0
- package/src/prediction/odds.rs +229 -0
- package/src/prediction/pnl.rs +254 -0
- package/src/prediction/risk.rs +228 -0
- package/src/risk/beta.rs +257 -0
- package/src/risk/drawdown.rs +256 -0
- package/src/risk/leverage.rs +201 -0
- package/src/risk/mod.rs +388 -0
- package/src/risk/portfolio.rs +287 -0
- package/src/risk/ratios.rs +290 -0
- package/src/risk/sizing.rs +194 -0
- package/src/risk/var.rs +222 -0
- package/src/stats/cdf.rs +257 -0
- package/src/stats/correlation.rs +225 -0
- package/src/stats/distribution.rs +194 -0
- package/src/stats/hypothesis.rs +177 -0
- package/src/stats/matrix.rs +346 -0
- package/src/stats/mod.rs +257 -0
- package/src/stats/regression.rs +239 -0
- package/src/stats/rolling.rs +193 -0
- package/src/stats/timeseries.rs +263 -0
- package/src/types.rs +224 -0
- package/src/utils/mod.rs +215 -0
- package/src/utils/normalize.rs +192 -0
- package/src/utils/price.rs +167 -0
- package/src/utils/quantiles.rs +177 -0
- package/src/utils/returns.rs +158 -0
- package/src/utils/rolling.rs +97 -0
- package/src/utils/stats.rs +154 -0
- package/types/index.ts +513 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
//! # PnL and Payout Calculations
|
|
2
|
+
//!
|
|
3
|
+
//! Calculate payouts, realized and unrealized PnL for prediction positions.
|
|
4
|
+
|
|
5
|
+
use crate::prediction::Outcome;
|
|
6
|
+
|
|
7
|
+
/// Calculate payout for a resolved prediction market.
|
|
8
|
+
///
|
|
9
|
+
/// # Arguments
|
|
10
|
+
/// * `shares` - Number of shares held
|
|
11
|
+
/// * `outcome` - Outcome you bet on
|
|
12
|
+
/// * `resolved_outcome` - Actual resolved outcome
|
|
13
|
+
///
|
|
14
|
+
/// # Returns
|
|
15
|
+
/// Payout amount ($1 per winning share, $0 for losing shares)
|
|
16
|
+
///
|
|
17
|
+
/// # Example
|
|
18
|
+
/// ```
|
|
19
|
+
/// use quant_rust::prediction::pnl::calculate_payout;
|
|
20
|
+
/// use quant_rust::prediction::Outcome;
|
|
21
|
+
/// let payout = calculate_payout(100.0, Outcome::Yes, Outcome::Yes);
|
|
22
|
+
/// assert_eq!(payout, 100.0);
|
|
23
|
+
///
|
|
24
|
+
/// let payout = calculate_payout(100.0, Outcome::Yes, Outcome::No);
|
|
25
|
+
/// assert_eq!(payout, 0.0);
|
|
26
|
+
/// ```
|
|
27
|
+
pub fn calculate_payout(shares: f64, outcome: Outcome, resolved_outcome: Outcome) -> f64 {
|
|
28
|
+
if outcome == resolved_outcome {
|
|
29
|
+
shares
|
|
30
|
+
} else {
|
|
31
|
+
0.0
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Calculate PnL for a closed position.
|
|
36
|
+
///
|
|
37
|
+
/// # Arguments
|
|
38
|
+
/// * `shares` - Number of shares held
|
|
39
|
+
/// * `avg_price` - Average purchase price per share
|
|
40
|
+
/// * `outcome` - Outcome you bet on
|
|
41
|
+
/// * `resolved_outcome` - Actual resolved outcome
|
|
42
|
+
///
|
|
43
|
+
/// # Returns
|
|
44
|
+
/// Profit/Loss (payout - cost)
|
|
45
|
+
///
|
|
46
|
+
/// # Example
|
|
47
|
+
/// ```
|
|
48
|
+
/// use quant_rust::prediction::pnl::calculate_pnl;
|
|
49
|
+
/// use quant_rust::prediction::Outcome;
|
|
50
|
+
/// // Won: bought 100 shares at $0.50, resolved YES
|
|
51
|
+
/// let pnl = calculate_pnl(100.0, 0.5, Outcome::Yes, Outcome::Yes);
|
|
52
|
+
/// assert_eq!(pnl, 50.0); // $100 payout - $50 cost
|
|
53
|
+
///
|
|
54
|
+
/// // Lost: bought 100 shares at $0.50, resolved NO
|
|
55
|
+
/// let pnl = calculate_pnl(100.0, 0.5, Outcome::Yes, Outcome::No);
|
|
56
|
+
/// assert_eq!(pnl, -50.0); // $0 payout - $50 cost
|
|
57
|
+
/// ```
|
|
58
|
+
pub fn calculate_pnl(
|
|
59
|
+
shares: f64,
|
|
60
|
+
avg_price: f64,
|
|
61
|
+
outcome: Outcome,
|
|
62
|
+
resolved_outcome: Outcome,
|
|
63
|
+
) -> f64 {
|
|
64
|
+
let payout = calculate_payout(shares, outcome, resolved_outcome);
|
|
65
|
+
let cost = shares * avg_price;
|
|
66
|
+
payout - cost
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Calculate unrealized PnL for an open position.
|
|
70
|
+
///
|
|
71
|
+
/// # Arguments
|
|
72
|
+
/// * `shares` - Number of shares held
|
|
73
|
+
/// * `avg_price` - Average purchase price per share
|
|
74
|
+
/// * `current_price` - Current market price
|
|
75
|
+
///
|
|
76
|
+
/// # Returns
|
|
77
|
+
/// Unrealized PnL (current value - cost)
|
|
78
|
+
///
|
|
79
|
+
/// # Example
|
|
80
|
+
/// ```
|
|
81
|
+
/// use quant_rust::prediction::pnl::calculate_unrealized_pnl;
|
|
82
|
+
/// // Bought at $0.50, now at $0.60
|
|
83
|
+
/// let pnl = calculate_unrealized_pnl(100.0, 0.5, 0.6);
|
|
84
|
+
/// assert_eq!(pnl, 10.0); // $60 value - $50 cost
|
|
85
|
+
/// ```
|
|
86
|
+
pub fn calculate_unrealized_pnl(shares: f64, avg_price: f64, current_price: f64) -> f64 {
|
|
87
|
+
let current_value = shares * current_price;
|
|
88
|
+
let cost = shares * avg_price;
|
|
89
|
+
current_value - cost
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// A closed position for TWR calculation
|
|
93
|
+
#[derive(Debug, Clone, Copy)]
|
|
94
|
+
pub struct ClosedPosition {
|
|
95
|
+
/// Entry timestamp (milliseconds)
|
|
96
|
+
pub entry_time: i64,
|
|
97
|
+
/// Exit timestamp (milliseconds)
|
|
98
|
+
pub exit_time: i64,
|
|
99
|
+
/// Value at entry
|
|
100
|
+
pub entry_value: f64,
|
|
101
|
+
/// Value at exit
|
|
102
|
+
pub exit_value: f64,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// Calculate time-weighted return for a prediction portfolio.
|
|
106
|
+
///
|
|
107
|
+
/// TWR = product(exit_value / entry_value) - 1
|
|
108
|
+
///
|
|
109
|
+
/// This eliminates the effect of cash flows on returns.
|
|
110
|
+
///
|
|
111
|
+
/// # Arguments
|
|
112
|
+
/// * `positions` - Slice of closed positions
|
|
113
|
+
///
|
|
114
|
+
/// # Returns
|
|
115
|
+
/// Time-weighted return as decimal (e.g., 0.10 = 10%)
|
|
116
|
+
///
|
|
117
|
+
/// # Example
|
|
118
|
+
/// ```
|
|
119
|
+
/// use quant_rust::prediction::pnl::{time_weighted_return, ClosedPosition};
|
|
120
|
+
/// let positions = vec![
|
|
121
|
+
/// ClosedPosition { entry_time: 0, exit_time: 100, entry_value: 100.0, exit_value: 110.0 },
|
|
122
|
+
/// ClosedPosition { entry_time: 100, exit_time: 200, entry_value: 110.0, exit_value: 121.0 },
|
|
123
|
+
/// ];
|
|
124
|
+
/// let twr = time_weighted_return(&positions);
|
|
125
|
+
/// // 10% + 10% = 21% total
|
|
126
|
+
/// assert!((twr - 0.21).abs() < 1e-10);
|
|
127
|
+
/// ```
|
|
128
|
+
pub fn time_weighted_return(positions: &[ClosedPosition]) -> f64 {
|
|
129
|
+
if positions.is_empty() {
|
|
130
|
+
return 0.0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let mut twr = 1.0;
|
|
134
|
+
for pos in positions {
|
|
135
|
+
if pos.entry_value > 0.0 {
|
|
136
|
+
twr *= pos.exit_value / pos.entry_value;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
twr - 1.0
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
#[cfg(test)]
|
|
144
|
+
mod tests {
|
|
145
|
+
use super::*;
|
|
146
|
+
|
|
147
|
+
#[test]
|
|
148
|
+
fn test_calculate_payout_winner() {
|
|
149
|
+
let payout = calculate_payout(100.0, Outcome::Yes, Outcome::Yes);
|
|
150
|
+
assert_eq!(payout, 100.0);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#[test]
|
|
154
|
+
fn test_calculate_payout_loser() {
|
|
155
|
+
let payout = calculate_payout(100.0, Outcome::Yes, Outcome::No);
|
|
156
|
+
assert_eq!(payout, 0.0);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
#[test]
|
|
160
|
+
fn test_calculate_pnl_winner() {
|
|
161
|
+
let pnl = calculate_pnl(100.0, 0.5, Outcome::Yes, Outcome::Yes);
|
|
162
|
+
assert_eq!(pnl, 50.0); // $100 - $50
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
#[test]
|
|
166
|
+
fn test_calculate_pnl_loser() {
|
|
167
|
+
let pnl = calculate_pnl(100.0, 0.5, Outcome::Yes, Outcome::No);
|
|
168
|
+
assert_eq!(pnl, -50.0); // $0 - $50
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
#[test]
|
|
172
|
+
fn test_calculate_pnl_zero_cost() {
|
|
173
|
+
let pnl = calculate_pnl(100.0, 0.0, Outcome::Yes, Outcome::Yes);
|
|
174
|
+
assert_eq!(pnl, 100.0);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
#[test]
|
|
178
|
+
fn test_calculate_unrealized_pnl_positive() {
|
|
179
|
+
let pnl = calculate_unrealized_pnl(100.0, 0.5, 0.6);
|
|
180
|
+
assert_eq!(pnl, 10.0); // $60 - $50
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
#[test]
|
|
184
|
+
fn test_calculate_unrealized_pnl_negative() {
|
|
185
|
+
let pnl = calculate_unrealized_pnl(100.0, 0.6, 0.5);
|
|
186
|
+
assert_eq!(pnl, -10.0); // $50 - $60
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
#[test]
|
|
190
|
+
fn test_calculate_unrealized_pnl_zero() {
|
|
191
|
+
let pnl = calculate_unrealized_pnl(100.0, 0.5, 0.5);
|
|
192
|
+
assert_eq!(pnl, 0.0);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
#[test]
|
|
196
|
+
fn test_time_weighted_return_empty() {
|
|
197
|
+
let twr = time_weighted_return(&[]);
|
|
198
|
+
assert_eq!(twr, 0.0);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#[test]
|
|
202
|
+
fn test_time_weighted_return_single() {
|
|
203
|
+
let positions = vec![ClosedPosition {
|
|
204
|
+
entry_time: 0,
|
|
205
|
+
exit_time: 100,
|
|
206
|
+
entry_value: 100.0,
|
|
207
|
+
exit_value: 110.0,
|
|
208
|
+
}];
|
|
209
|
+
let twr = time_weighted_return(&positions);
|
|
210
|
+
assert!((twr - 0.10).abs() < 1e-10);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
#[test]
|
|
214
|
+
fn test_time_weighted_return_multiple() {
|
|
215
|
+
let positions = vec![
|
|
216
|
+
ClosedPosition {
|
|
217
|
+
entry_time: 0,
|
|
218
|
+
exit_time: 100,
|
|
219
|
+
entry_value: 100.0,
|
|
220
|
+
exit_value: 110.0,
|
|
221
|
+
},
|
|
222
|
+
ClosedPosition {
|
|
223
|
+
entry_time: 100,
|
|
224
|
+
exit_time: 200,
|
|
225
|
+
entry_value: 110.0,
|
|
226
|
+
exit_value: 121.0,
|
|
227
|
+
},
|
|
228
|
+
];
|
|
229
|
+
let twr = time_weighted_return(&positions);
|
|
230
|
+
// 1.1 * 1.1 - 1 = 0.21
|
|
231
|
+
assert!((twr - 0.21).abs() < 1e-10);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
#[test]
|
|
235
|
+
fn test_time_weighted_return_with_loss() {
|
|
236
|
+
let positions = vec![
|
|
237
|
+
ClosedPosition {
|
|
238
|
+
entry_time: 0,
|
|
239
|
+
exit_time: 100,
|
|
240
|
+
entry_value: 100.0,
|
|
241
|
+
exit_value: 90.0,
|
|
242
|
+
},
|
|
243
|
+
ClosedPosition {
|
|
244
|
+
entry_time: 100,
|
|
245
|
+
exit_time: 200,
|
|
246
|
+
entry_value: 90.0,
|
|
247
|
+
exit_value: 99.0,
|
|
248
|
+
},
|
|
249
|
+
];
|
|
250
|
+
let twr = time_weighted_return(&positions);
|
|
251
|
+
// 0.9 * 1.1 - 1 = -0.01
|
|
252
|
+
assert!((twr - (-0.01)).abs() < 1e-10);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
//! # Prediction Market Risk Metrics
|
|
2
|
+
//!
|
|
3
|
+
//! Risk calculations for binary outcome positions.
|
|
4
|
+
|
|
5
|
+
/// Risk metrics for a prediction market position
|
|
6
|
+
#[derive(Debug, Clone, Copy)]
|
|
7
|
+
pub struct PredictionRiskMetrics {
|
|
8
|
+
/// Expected value of the position
|
|
9
|
+
pub expected_value: f64,
|
|
10
|
+
/// Variance of returns
|
|
11
|
+
pub variance: f64,
|
|
12
|
+
/// Standard deviation of returns
|
|
13
|
+
pub std_dev: f64,
|
|
14
|
+
/// Sharpe-like ratio (EV / StdDev)
|
|
15
|
+
pub sharpe: f64,
|
|
16
|
+
/// Maximum loss (cost of position)
|
|
17
|
+
pub max_loss: f64,
|
|
18
|
+
/// Maximum gain (payout - cost)
|
|
19
|
+
pub max_gain: f64,
|
|
20
|
+
/// Payoff ratio (max_gain / max_loss)
|
|
21
|
+
pub payoff_ratio: f64,
|
|
22
|
+
/// Edge per dollar invested
|
|
23
|
+
pub edge_per_dollar: f64,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// Calculate risk metrics for a prediction position.
|
|
27
|
+
///
|
|
28
|
+
/// # Arguments
|
|
29
|
+
/// * `your_prob` - Your estimated probability of winning
|
|
30
|
+
/// * `_market_price` - Price paid per share (unused, kept for API compatibility)
|
|
31
|
+
/// * `shares` - Number of shares
|
|
32
|
+
/// * `cost` - Total cost of position
|
|
33
|
+
///
|
|
34
|
+
/// # Returns
|
|
35
|
+
/// PredictionRiskMetrics with detailed risk analysis
|
|
36
|
+
///
|
|
37
|
+
/// # Example
|
|
38
|
+
/// ```
|
|
39
|
+
/// use quant_rust::prediction::risk::calculate_prediction_risk;
|
|
40
|
+
/// let metrics = calculate_prediction_risk(0.6, 0.5, 100.0, 50.0);
|
|
41
|
+
/// assert!(metrics.expected_value > 0.0);
|
|
42
|
+
/// assert!(metrics.max_loss == 50.0);
|
|
43
|
+
/// ```
|
|
44
|
+
pub fn calculate_prediction_risk(
|
|
45
|
+
your_prob: f64,
|
|
46
|
+
_market_price: f64,
|
|
47
|
+
shares: f64,
|
|
48
|
+
cost: f64,
|
|
49
|
+
) -> PredictionRiskMetrics {
|
|
50
|
+
// Binary outcome: Win = shares * $1, Lose = $0
|
|
51
|
+
let max_gain = shares - cost; // If wins, get $1 per share
|
|
52
|
+
let max_loss = cost; // If loses, lose entire cost
|
|
53
|
+
|
|
54
|
+
// Expected value = p * maxGain - (1-p) * maxLoss
|
|
55
|
+
let expected_value = your_prob * max_gain - (1.0 - your_prob) * max_loss;
|
|
56
|
+
|
|
57
|
+
// Variance of binary = p(1-p) * (maxGain + maxLoss)^2
|
|
58
|
+
let variance = your_prob * (1.0 - your_prob) * (max_gain + max_loss).powi(2);
|
|
59
|
+
let std_dev = variance.sqrt();
|
|
60
|
+
|
|
61
|
+
// Sharpe-like ratio for single bet
|
|
62
|
+
let sharpe = if std_dev > 0.0 {
|
|
63
|
+
expected_value / std_dev
|
|
64
|
+
} else {
|
|
65
|
+
0.0
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Edge per dollar invested
|
|
69
|
+
let edge_per_dollar = if cost > 0.0 {
|
|
70
|
+
expected_value / cost
|
|
71
|
+
} else {
|
|
72
|
+
0.0
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Payoff ratio
|
|
76
|
+
let payoff_ratio = if max_loss > 0.0 {
|
|
77
|
+
max_gain / max_loss
|
|
78
|
+
} else {
|
|
79
|
+
0.0
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
PredictionRiskMetrics {
|
|
83
|
+
expected_value,
|
|
84
|
+
variance,
|
|
85
|
+
std_dev,
|
|
86
|
+
sharpe,
|
|
87
|
+
max_loss,
|
|
88
|
+
max_gain,
|
|
89
|
+
payoff_ratio,
|
|
90
|
+
edge_per_dollar,
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// Check if a bet has positive expected value.
|
|
95
|
+
///
|
|
96
|
+
/// # Arguments
|
|
97
|
+
/// * `your_prob` - Your estimated probability
|
|
98
|
+
/// * `market_price` - Current market price
|
|
99
|
+
///
|
|
100
|
+
/// # Returns
|
|
101
|
+
/// true if your_prob > market_price
|
|
102
|
+
///
|
|
103
|
+
/// # Example
|
|
104
|
+
/// ```
|
|
105
|
+
/// use quant_rust::prediction::risk::has_positive_ev;
|
|
106
|
+
/// assert!(has_positive_ev(0.6, 0.5));
|
|
107
|
+
/// assert!(!has_positive_ev(0.4, 0.5));
|
|
108
|
+
/// ```
|
|
109
|
+
pub fn has_positive_ev(your_prob: f64, market_price: f64) -> bool {
|
|
110
|
+
your_prob > market_price
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// Calculate edge (your probability - market implied probability).
|
|
114
|
+
///
|
|
115
|
+
/// # Arguments
|
|
116
|
+
/// * `your_prob` - Your estimated probability
|
|
117
|
+
/// * `market_price` - Current market price
|
|
118
|
+
///
|
|
119
|
+
/// # Returns
|
|
120
|
+
/// Edge (positive = favorable)
|
|
121
|
+
///
|
|
122
|
+
/// # Example
|
|
123
|
+
/// ```
|
|
124
|
+
/// use quant_rust::prediction::risk::calculate_edge;
|
|
125
|
+
/// let edge = calculate_edge(0.6, 0.5);
|
|
126
|
+
/// assert!((edge - 0.1).abs() < 1e-10);
|
|
127
|
+
/// ```
|
|
128
|
+
pub fn calculate_edge(your_prob: f64, market_price: f64) -> f64 {
|
|
129
|
+
your_prob - market_price
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/// Calculate break-even probability.
|
|
133
|
+
///
|
|
134
|
+
/// For a binary bet at price p, you need probability > p to have positive EV.
|
|
135
|
+
///
|
|
136
|
+
/// # Arguments
|
|
137
|
+
/// * `market_price` - Current market price
|
|
138
|
+
///
|
|
139
|
+
/// # Returns
|
|
140
|
+
/// Break-even probability
|
|
141
|
+
///
|
|
142
|
+
/// # Example
|
|
143
|
+
/// ```
|
|
144
|
+
/// use quant_rust::prediction::risk::break_even_prob;
|
|
145
|
+
/// let prob = break_even_prob(0.5);
|
|
146
|
+
/// assert!((prob - 0.5).abs() < 1e-10);
|
|
147
|
+
/// ```
|
|
148
|
+
pub fn break_even_prob(market_price: f64) -> f64 {
|
|
149
|
+
market_price
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#[cfg(test)]
|
|
153
|
+
mod tests {
|
|
154
|
+
use super::*;
|
|
155
|
+
|
|
156
|
+
#[test]
|
|
157
|
+
fn test_calculate_prediction_risk_positive_ev() {
|
|
158
|
+
let metrics = calculate_prediction_risk(0.6, 0.5, 100.0, 50.0);
|
|
159
|
+
assert!(metrics.expected_value > 0.0);
|
|
160
|
+
assert_eq!(metrics.max_loss, 50.0);
|
|
161
|
+
assert_eq!(metrics.max_gain, 50.0); // 100 shares - 50 cost
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
#[test]
|
|
165
|
+
fn test_calculate_prediction_risk_negative_ev() {
|
|
166
|
+
let metrics = calculate_prediction_risk(0.4, 0.5, 100.0, 50.0);
|
|
167
|
+
assert!(metrics.expected_value < 0.0);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
#[test]
|
|
171
|
+
fn test_calculate_prediction_risk_max_gain_loss() {
|
|
172
|
+
let metrics = calculate_prediction_risk(0.5, 0.5, 100.0, 50.0);
|
|
173
|
+
assert_eq!(metrics.max_loss, 50.0);
|
|
174
|
+
assert_eq!(metrics.max_gain, 50.0);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
#[test]
|
|
178
|
+
fn test_calculate_prediction_risk_payoff_ratio() {
|
|
179
|
+
let metrics = calculate_prediction_risk(0.6, 0.4, 100.0, 40.0);
|
|
180
|
+
// Max gain = 100 - 40 = 60, Max loss = 40
|
|
181
|
+
assert!((metrics.payoff_ratio - 1.5).abs() < 1e-10);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
#[test]
|
|
185
|
+
fn test_has_positive_ev_true() {
|
|
186
|
+
assert!(has_positive_ev(0.6, 0.5));
|
|
187
|
+
assert!(has_positive_ev(0.51, 0.5));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
#[test]
|
|
191
|
+
fn test_has_positive_ev_false() {
|
|
192
|
+
assert!(!has_positive_ev(0.4, 0.5));
|
|
193
|
+
assert!(!has_positive_ev(0.5, 0.5));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
#[test]
|
|
197
|
+
fn test_calculate_edge_positive() {
|
|
198
|
+
let edge = calculate_edge(0.6, 0.5);
|
|
199
|
+
assert!((edge - 0.1).abs() < 1e-10);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
#[test]
|
|
203
|
+
fn test_calculate_edge_negative() {
|
|
204
|
+
let edge = calculate_edge(0.4, 0.5);
|
|
205
|
+
assert!((edge - (-0.1)).abs() < 1e-10);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
#[test]
|
|
209
|
+
fn test_break_even_prob() {
|
|
210
|
+
assert!((break_even_prob(0.5) - 0.5).abs() < 1e-10);
|
|
211
|
+
assert!((break_even_prob(0.75) - 0.75).abs() < 1e-10);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
#[test]
|
|
215
|
+
fn test_risk_metrics_sharpe() {
|
|
216
|
+
// High edge should give positive sharpe
|
|
217
|
+
let metrics = calculate_prediction_risk(0.8, 0.5, 100.0, 50.0);
|
|
218
|
+
assert!(metrics.sharpe > 0.0);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
#[test]
|
|
222
|
+
fn test_edge_per_dollar() {
|
|
223
|
+
let metrics = calculate_prediction_risk(0.6, 0.5, 100.0, 50.0);
|
|
224
|
+
// EV = 0.6 * 50 - 0.4 * 50 = 10
|
|
225
|
+
// Edge per dollar = 10 / 50 = 0.2
|
|
226
|
+
assert!((metrics.edge_per_dollar - 0.2).abs() < 1e-10);
|
|
227
|
+
}
|
|
228
|
+
}
|