@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,194 @@
1
+ //! # LMSR (Logarithmic Market Scoring Rule)
2
+ //!
3
+ //! Calculations for LMSR-based prediction markets (used by Gnosis, Hanson, etc.)
4
+
5
+ use crate::prediction::Outcome;
6
+
7
+ /// State of an LMSR market
8
+ #[derive(Debug, Clone, Copy)]
9
+ pub struct LMSRState {
10
+ /// YES shares outstanding
11
+ pub yes_shares: f64,
12
+ /// NO shares outstanding
13
+ pub no_shares: f64,
14
+ /// Liquidity parameter (b)
15
+ pub b: f64,
16
+ }
17
+
18
+ impl LMSRState {
19
+ /// Create a new LMSR state
20
+ pub fn new(yes_shares: f64, no_shares: f64, b: f64) -> Self {
21
+ Self {
22
+ yes_shares,
23
+ no_shares,
24
+ b,
25
+ }
26
+ }
27
+ }
28
+
29
+ /// Calculate price in LMSR market.
30
+ ///
31
+ /// Price = e^(q_i / b) / sum(e^(q_j / b))
32
+ ///
33
+ /// # Arguments
34
+ /// * `state` - Current LMSR state
35
+ /// * `outcome` - Which outcome to price
36
+ ///
37
+ /// # Returns
38
+ /// Price as probability (0-1)
39
+ ///
40
+ /// # Example
41
+ /// ```
42
+ /// use quant_rust::prediction::lmsr::{LMSRState, lmsr_price};
43
+ /// use quant_rust::prediction::Outcome;
44
+ /// let state = LMSRState::new(0.0, 0.0, 100.0);
45
+ /// let price = lmsr_price(&state, Outcome::Yes);
46
+ /// assert!((price - 0.5).abs() < 1e-10); // Equal shares = 50/50
47
+ /// ```
48
+ pub fn lmsr_price(state: &LMSRState, outcome: Outcome) -> f64 {
49
+ let LMSRState { yes_shares, no_shares, b } = *state;
50
+
51
+ if b <= 0.0 {
52
+ return 0.0;
53
+ }
54
+
55
+ let exp_yes = (yes_shares / b).exp();
56
+ let exp_no = (no_shares / b).exp();
57
+ let sum = exp_yes + exp_no;
58
+
59
+ if sum == 0.0 {
60
+ return 0.5;
61
+ }
62
+
63
+ match outcome {
64
+ Outcome::Yes => exp_yes / sum,
65
+ Outcome::No => exp_no / sum,
66
+ }
67
+ }
68
+
69
+ /// Calculate cost to buy shares in LMSR.
70
+ ///
71
+ /// Cost = b * ln(sum(e^(q_i + delta_i) / b)) - b * ln(sum(e^(q_i) / b))
72
+ ///
73
+ /// # Arguments
74
+ /// * `state` - Current LMSR state
75
+ /// * `outcome` - Which outcome to buy
76
+ /// * `shares` - Number of shares to buy
77
+ ///
78
+ /// # Returns
79
+ /// Cost in quote currency
80
+ ///
81
+ /// # Example
82
+ /// ```
83
+ /// use quant_rust::prediction::lmsr::{LMSRState, lmsr_buy_cost};
84
+ /// use quant_rust::prediction::Outcome;
85
+ /// let state = LMSRState::new(0.0, 0.0, 100.0);
86
+ /// let cost = lmsr_buy_cost(&state, Outcome::Yes, 50.0);
87
+ /// assert!(cost > 0.0);
88
+ /// ```
89
+ pub fn lmsr_buy_cost(state: &LMSRState, outcome: Outcome, shares: f64) -> f64 {
90
+ let LMSRState { yes_shares, no_shares, b } = *state;
91
+
92
+ if b <= 0.0 {
93
+ return 0.0;
94
+ }
95
+
96
+ // Before cost: b * ln(e^(yes/b) + e^(no/b))
97
+ let before = b * ((yes_shares / b).exp() + (no_shares / b).exp()).ln();
98
+
99
+ // After cost depends on which outcome we're buying
100
+ let after = match outcome {
101
+ Outcome::Yes => {
102
+ b * (((yes_shares + shares) / b).exp() + (no_shares / b).exp()).ln()
103
+ }
104
+ Outcome::No => {
105
+ b * ((yes_shares / b).exp() + ((no_shares + shares) / b).exp()).ln()
106
+ }
107
+ };
108
+
109
+ after - before
110
+ }
111
+
112
+ #[cfg(test)]
113
+ mod tests {
114
+ use super::*;
115
+
116
+ #[test]
117
+ fn test_lmsr_price_equal() {
118
+ // Equal shares should give 50/50
119
+ let state = LMSRState::new(0.0, 0.0, 100.0);
120
+ let yes_price = lmsr_price(&state, Outcome::Yes);
121
+ let no_price = lmsr_price(&state, Outcome::No);
122
+ assert!((yes_price - 0.5).abs() < 1e-10);
123
+ assert!((no_price - 0.5).abs() < 1e-10);
124
+ assert!((yes_price + no_price - 1.0).abs() < 1e-10);
125
+ }
126
+
127
+ #[test]
128
+ fn test_lmsr_price_more_yes() {
129
+ // More YES shares should increase YES price
130
+ let state = LMSRState::new(100.0, 0.0, 100.0);
131
+ let yes_price = lmsr_price(&state, Outcome::Yes);
132
+ assert!(yes_price > 0.5);
133
+ }
134
+
135
+ #[test]
136
+ fn test_lmsr_price_more_no() {
137
+ // More NO shares should increase NO price
138
+ let state = LMSRState::new(0.0, 100.0, 100.0);
139
+ let yes_price = lmsr_price(&state, Outcome::Yes);
140
+ assert!(yes_price < 0.5);
141
+ }
142
+
143
+ #[test]
144
+ fn test_lmsr_buy_cost_positive() {
145
+ let state = LMSRState::new(0.0, 0.0, 100.0);
146
+ let cost = lmsr_buy_cost(&state, Outcome::Yes, 50.0);
147
+ assert!(cost > 0.0);
148
+ }
149
+
150
+ #[test]
151
+ fn test_lmsr_buy_cost_increases_with_shares() {
152
+ let state = LMSRState::new(0.0, 0.0, 100.0);
153
+ let cost_small = lmsr_buy_cost(&state, Outcome::Yes, 10.0);
154
+ let cost_large = lmsr_buy_cost(&state, Outcome::Yes, 50.0);
155
+ assert!(cost_large > cost_small);
156
+ }
157
+
158
+ #[test]
159
+ fn test_lmsr_buy_cost_symmetry() {
160
+ let state = LMSRState::new(0.0, 0.0, 100.0);
161
+ let cost_yes = lmsr_buy_cost(&state, Outcome::Yes, 50.0);
162
+ let cost_no = lmsr_buy_cost(&state, Outcome::No, 50.0);
163
+ // Should cost the same to buy either outcome from equal starting point
164
+ assert!((cost_yes - cost_no).abs() < 1e-10);
165
+ }
166
+
167
+ #[test]
168
+ fn test_lmsr_buy_cost_zero_shares() {
169
+ let state = LMSRState::new(0.0, 0.0, 100.0);
170
+ let cost = lmsr_buy_cost(&state, Outcome::Yes, 0.0);
171
+ assert!((cost - 0.0).abs() < 1e-10);
172
+ }
173
+
174
+ #[test]
175
+ fn test_lmsr_liquidity_parameter_effect() {
176
+ // Higher b = more liquidity = prices move less
177
+ let state_low = LMSRState::new(100.0, 0.0, 50.0);
178
+ let state_high = LMSRState::new(100.0, 0.0, 200.0);
179
+
180
+ let price_low = lmsr_price(&state_low, Outcome::Yes);
181
+ let price_high = lmsr_price(&state_high, Outcome::Yes);
182
+
183
+ // With same shares but higher b, price should be closer to 0.5
184
+ assert!(price_high < price_low);
185
+ }
186
+
187
+ #[test]
188
+ fn test_lmsr_prices_sum_to_one() {
189
+ let state = LMSRState::new(123.0, 456.0, 100.0);
190
+ let yes_price = lmsr_price(&state, Outcome::Yes);
191
+ let no_price = lmsr_price(&state, Outcome::No);
192
+ assert!((yes_price + no_price - 1.0).abs() < 1e-10);
193
+ }
194
+ }
@@ -0,0 +1,59 @@
1
+ //! # Prediction Market Tools
2
+ //!
3
+ //! Tools for binary outcome prediction markets (Polymarket, Manifold, Kalshi, etc.)
4
+ //!
5
+ //! ## Modules
6
+ //!
7
+ //! - [`odds`] - Probability and odds conversions
8
+ //! - [`kelly`] - Kelly criterion position sizing
9
+ //! - [`amm`] - Constant product AMM calculations
10
+ //! - [`lmsr`] - Logarithmic Market Scoring Rule
11
+ //! - [`arbitrage`] - Arbitrage detection
12
+ //! - [`calibration`] - Forecast calibration and scoring
13
+ //! - [`risk`] - Position risk metrics
14
+ //! - [`pnl`] - PnL and payout calculations
15
+
16
+ pub mod amm;
17
+ pub mod arbitrage;
18
+ pub mod calibration;
19
+ pub mod kelly;
20
+ pub mod lmsr;
21
+ pub mod odds;
22
+ pub mod pnl;
23
+ pub mod risk;
24
+
25
+ // Re-exports for convenience
26
+ pub use amm::{
27
+ amm_buy_cost, amm_impermanent_loss, amm_price_after_buy, amm_price_impact, amm_shares_received,
28
+ AMMState, PriceImpactResult,
29
+ };
30
+ pub use arbitrage::{
31
+ detect_arbitrage, find_cross_market_arbitrage, ArbitrageOpportunity, CrossMarketArbitrage,
32
+ MarketPrice,
33
+ };
34
+ pub use calibration::{
35
+ brier_score, calculate_calibration, log_loss, CalibrationBucket, CalibrationResult, Prediction,
36
+ };
37
+ pub use kelly::{fractional_kelly, kelly_criterion, kelly_multiple, BetOpportunity, KellyResult};
38
+ pub use lmsr::{lmsr_buy_cost, lmsr_price, LMSRState};
39
+ pub use odds::{
40
+ american_odds_to_prob, decimal_odds_to_prob, price_to_implied_prob, prob_to_american_odds,
41
+ prob_to_decimal_odds, prob_to_fair_price,
42
+ };
43
+ pub use pnl::{
44
+ calculate_payout, calculate_pnl, calculate_unrealized_pnl, time_weighted_return,
45
+ ClosedPosition,
46
+ };
47
+ pub use risk::{
48
+ break_even_prob, calculate_edge, calculate_prediction_risk, has_positive_ev,
49
+ PredictionRiskMetrics,
50
+ };
51
+
52
+ use serde::{Deserialize, Serialize};
53
+
54
+ /// Outcome type for prediction markets
55
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
56
+ pub enum Outcome {
57
+ Yes,
58
+ No,
59
+ }
@@ -0,0 +1,229 @@
1
+ //! # Odds and Probability Conversions
2
+ //!
3
+ //! Convert between probability, decimal odds, and American odds formats.
4
+
5
+ use std::f64::INFINITY;
6
+ use std::f64::NEG_INFINITY;
7
+
8
+ /// Convert probability (0-1) to decimal odds.
9
+ ///
10
+ /// # Arguments
11
+ /// * `prob` - Probability value (0-1)
12
+ ///
13
+ /// # Returns
14
+ /// Decimal odds (1 / prob), or infinity if prob is 0 or negative
15
+ ///
16
+ /// # Example
17
+ /// ```
18
+ /// use quant_rust::prediction::odds::prob_to_decimal_odds;
19
+ /// let odds = prob_to_decimal_odds(0.25);
20
+ /// assert_eq!(odds, 4.0);
21
+ /// ```
22
+ pub fn prob_to_decimal_odds(prob: f64) -> f64 {
23
+ if prob <= 0.0 {
24
+ return INFINITY;
25
+ }
26
+ 1.0 / prob
27
+ }
28
+
29
+ /// Convert probability (0-1) to American odds.
30
+ ///
31
+ /// # Arguments
32
+ /// * `prob` - Probability value (0-1)
33
+ ///
34
+ /// # Returns
35
+ /// American odds:
36
+ /// - Positive (>100): underdog, profit on $100 bet
37
+ /// - Negative (<-100): favorite, amount to bet to win $100
38
+ ///
39
+ /// # Example
40
+ /// ```
41
+ /// use quant_rust::prediction::odds::prob_to_american_odds;
42
+ /// let odds = prob_to_american_odds(0.25);
43
+ /// assert_eq!(odds, 300); // +300 underdog
44
+ ///
45
+ /// let odds = prob_to_american_odds(0.75);
46
+ /// assert_eq!(odds, -300); // -300 favorite
47
+ /// ```
48
+ pub fn prob_to_american_odds(prob: f64) -> f64 {
49
+ if prob <= 0.0 {
50
+ return INFINITY;
51
+ }
52
+ if prob >= 1.0 {
53
+ return NEG_INFINITY;
54
+ }
55
+
56
+ if prob > 0.5 {
57
+ // Favorite: negative odds
58
+ -((prob / (1.0 - prob)) * 100.0).round()
59
+ } else {
60
+ // Underdog: positive odds
61
+ (((1.0 - prob) / prob) * 100.0).round()
62
+ }
63
+ }
64
+
65
+ /// Convert decimal odds to probability.
66
+ ///
67
+ /// # Arguments
68
+ /// * `odds` - Decimal odds (must be >= 1)
69
+ ///
70
+ /// # Returns
71
+ /// Implied probability (1 / odds), or 1.0 if odds <= 1
72
+ ///
73
+ /// # Example
74
+ /// ```
75
+ /// use quant_rust::prediction::odds::decimal_odds_to_prob;
76
+ /// let prob = decimal_odds_to_prob(4.0);
77
+ /// assert_eq!(prob, 0.25);
78
+ /// ```
79
+ pub fn decimal_odds_to_prob(odds: f64) -> f64 {
80
+ if odds <= 1.0 {
81
+ return 1.0;
82
+ }
83
+ 1.0 / odds
84
+ }
85
+
86
+ /// Convert American odds to probability.
87
+ ///
88
+ /// # Arguments
89
+ /// * `odds` - American odds (positive or negative)
90
+ ///
91
+ /// # Returns
92
+ /// Implied probability
93
+ ///
94
+ /// # Example
95
+ /// ```
96
+ /// use quant_rust::prediction::odds::american_odds_to_prob;
97
+ /// let prob = american_odds_to_prob(300.0);
98
+ /// assert_eq!(prob, 0.25);
99
+ ///
100
+ /// let prob = american_odds_to_prob(-300.0);
101
+ /// assert_eq!(prob, 0.75);
102
+ /// ```
103
+ pub fn american_odds_to_prob(odds: f64) -> f64 {
104
+ if odds > 0.0 {
105
+ // Underdog: prob = 100 / (odds + 100)
106
+ 100.0 / (odds + 100.0)
107
+ } else {
108
+ // Favorite: prob = |odds| / (|odds| + 100)
109
+ odds.abs() / (odds.abs() + 100.0)
110
+ }
111
+ }
112
+
113
+ /// Convert price (0-1) to implied probability, accounting for fees.
114
+ ///
115
+ /// # Arguments
116
+ /// * `price` - Market price (0-1)
117
+ /// * `fee_bps` - Optional fee in basis points (e.g., 100 = 1%)
118
+ ///
119
+ /// # Returns
120
+ /// Implied probability including fee impact
121
+ ///
122
+ /// # Example
123
+ /// ```
124
+ /// use quant_rust::prediction::odds::price_to_implied_prob;
125
+ /// let prob = price_to_implied_prob(0.50, Some(100.0)); // 1% fee
126
+ /// assert!((prob - 0.505).abs() < 0.001);
127
+ /// ```
128
+ pub fn price_to_implied_prob(price: f64, fee_bps: Option<f64>) -> f64 {
129
+ if price <= 0.0 {
130
+ return 0.0;
131
+ }
132
+ if price >= 1.0 {
133
+ return 1.0;
134
+ }
135
+
136
+ let fee = fee_bps.unwrap_or(0.0);
137
+ price * (1.0 + fee / 10000.0)
138
+ }
139
+
140
+ /// Convert probability to fair price, accounting for fees.
141
+ ///
142
+ /// # Arguments
143
+ /// * `prob` - Probability value (0-1)
144
+ /// * `fee_bps` - Optional fee in basis points (e.g., 100 = 1%)
145
+ ///
146
+ /// # Returns
147
+ /// Fair price after accounting for fees
148
+ ///
149
+ /// # Example
150
+ /// ```
151
+ /// use quant_rust::prediction::odds::prob_to_fair_price;
152
+ /// let price = prob_to_fair_price(0.50, Some(100.0)); // 1% fee
153
+ /// assert!((price - 0.495).abs() < 0.001);
154
+ /// ```
155
+ pub fn prob_to_fair_price(prob: f64, fee_bps: Option<f64>) -> f64 {
156
+ if prob <= 0.0 {
157
+ return 0.0;
158
+ }
159
+ if prob >= 1.0 {
160
+ return 1.0;
161
+ }
162
+
163
+ let fee = fee_bps.unwrap_or(0.0);
164
+ prob / (1.0 + fee / 10000.0)
165
+ }
166
+
167
+ #[cfg(test)]
168
+ mod tests {
169
+ use super::*;
170
+
171
+ #[test]
172
+ fn test_prob_to_decimal_odds() {
173
+ assert_eq!(prob_to_decimal_odds(0.25), 4.0);
174
+ assert_eq!(prob_to_decimal_odds(0.5), 2.0);
175
+ assert_eq!(prob_to_decimal_odds(1.0), 1.0);
176
+ assert!(prob_to_decimal_odds(0.0).is_infinite());
177
+ assert!(prob_to_decimal_odds(-0.1).is_infinite());
178
+ }
179
+
180
+ #[test]
181
+ fn test_prob_to_american_odds() {
182
+ assert_eq!(prob_to_american_odds(0.25), 300.0); // +300 underdog
183
+ assert_eq!(prob_to_american_odds(0.75), -300.0); // -300 favorite
184
+ assert_eq!(prob_to_american_odds(0.5), 100.0); // Even money
185
+ assert!(prob_to_american_odds(0.0).is_infinite());
186
+ }
187
+
188
+ #[test]
189
+ fn test_decimal_odds_to_prob() {
190
+ assert_eq!(decimal_odds_to_prob(4.0), 0.25);
191
+ assert_eq!(decimal_odds_to_prob(2.0), 0.5);
192
+ assert_eq!(decimal_odds_to_prob(1.0), 1.0);
193
+ }
194
+
195
+ #[test]
196
+ fn test_american_odds_to_prob() {
197
+ assert_eq!(american_odds_to_prob(300.0), 0.25);
198
+ assert_eq!(american_odds_to_prob(-300.0), 0.75);
199
+ assert_eq!(american_odds_to_prob(100.0), 0.5);
200
+ }
201
+
202
+ #[test]
203
+ fn test_price_to_implied_prob() {
204
+ assert_eq!(price_to_implied_prob(0.5, None), 0.5);
205
+ assert_eq!(price_to_implied_prob(0.5, Some(0.0)), 0.5);
206
+ assert!((price_to_implied_prob(0.5, Some(100.0)) - 0.505).abs() < 0.001);
207
+ }
208
+
209
+ #[test]
210
+ fn test_prob_to_fair_price() {
211
+ assert_eq!(prob_to_fair_price(0.5, None), 0.5);
212
+ assert_eq!(prob_to_fair_price(0.5, Some(0.0)), 0.5);
213
+ assert!((prob_to_fair_price(0.5, Some(100.0)) - 0.49505).abs() < 0.001);
214
+ }
215
+
216
+ #[test]
217
+ fn test_round_trip_conversion() {
218
+ // Decimal odds round trip
219
+ let prob = 0.4;
220
+ let odds = prob_to_decimal_odds(prob);
221
+ let back = decimal_odds_to_prob(odds);
222
+ assert!((prob - back).abs() < 1e-10);
223
+
224
+ // American odds round trip
225
+ let odds = prob_to_american_odds(prob);
226
+ let back = american_odds_to_prob(odds);
227
+ assert!((prob - back).abs() < 0.01); // Allow rounding error
228
+ }
229
+ }