@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.
Files changed (60) hide show
  1. package/README.md +161 -0
  2. package/bun-ffi.d.ts +54 -0
  3. package/dist/index.js +576 -0
  4. package/dist/src/index.d.ts +324 -0
  5. package/dist/src/index.d.ts.map +1 -0
  6. package/dist/types/index.d.ts +403 -0
  7. package/dist/types/index.d.ts.map +1 -0
  8. package/native/README.md +62 -0
  9. package/native/darwin-arm64/libquant_rust.dylib +0 -0
  10. package/package.json +70 -0
  11. package/scripts/postinstall.cjs +85 -0
  12. package/src/ffi.rs +496 -0
  13. package/src/index.ts +1073 -0
  14. package/src/indicators/ma.rs +222 -0
  15. package/src/indicators/mod.rs +18 -0
  16. package/src/indicators/momentum.rs +353 -0
  17. package/src/indicators/sr.rs +195 -0
  18. package/src/indicators/trend.rs +351 -0
  19. package/src/indicators/volatility.rs +270 -0
  20. package/src/indicators/volume.rs +213 -0
  21. package/src/lib.rs +130 -0
  22. package/src/patterns/breakout.rs +431 -0
  23. package/src/patterns/chart.rs +772 -0
  24. package/src/patterns/mod.rs +394 -0
  25. package/src/patterns/sr.rs +423 -0
  26. package/src/prediction/amm.rs +338 -0
  27. package/src/prediction/arbitrage.rs +230 -0
  28. package/src/prediction/calibration.rs +317 -0
  29. package/src/prediction/kelly.rs +232 -0
  30. package/src/prediction/lmsr.rs +194 -0
  31. package/src/prediction/mod.rs +59 -0
  32. package/src/prediction/odds.rs +229 -0
  33. package/src/prediction/pnl.rs +254 -0
  34. package/src/prediction/risk.rs +228 -0
  35. package/src/risk/beta.rs +257 -0
  36. package/src/risk/drawdown.rs +256 -0
  37. package/src/risk/leverage.rs +201 -0
  38. package/src/risk/mod.rs +388 -0
  39. package/src/risk/portfolio.rs +287 -0
  40. package/src/risk/ratios.rs +290 -0
  41. package/src/risk/sizing.rs +194 -0
  42. package/src/risk/var.rs +222 -0
  43. package/src/stats/cdf.rs +257 -0
  44. package/src/stats/correlation.rs +225 -0
  45. package/src/stats/distribution.rs +194 -0
  46. package/src/stats/hypothesis.rs +177 -0
  47. package/src/stats/matrix.rs +346 -0
  48. package/src/stats/mod.rs +257 -0
  49. package/src/stats/regression.rs +239 -0
  50. package/src/stats/rolling.rs +193 -0
  51. package/src/stats/timeseries.rs +263 -0
  52. package/src/types.rs +224 -0
  53. package/src/utils/mod.rs +215 -0
  54. package/src/utils/normalize.rs +192 -0
  55. package/src/utils/price.rs +167 -0
  56. package/src/utils/quantiles.rs +177 -0
  57. package/src/utils/returns.rs +158 -0
  58. package/src/utils/rolling.rs +97 -0
  59. package/src/utils/stats.rs +154 -0
  60. 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
+ }