@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,257 @@
1
+ //! Beta and Alpha Functions
2
+ //!
3
+ //! Market sensitivity (beta) and excess return (alpha) calculations.
4
+
5
+ use crate::utils::{mean, variance};
6
+
7
+ /// Calculate beta (market sensitivity)
8
+ ///
9
+ /// Beta measures how much an asset moves relative to the market.
10
+ /// - Beta > 1: More volatile than market
11
+ /// - Beta < 1: Less volatile than market
12
+ /// - Beta = 1: Same volatility as market
13
+ ///
14
+ /// # Arguments
15
+ /// * `asset_returns` - Array of asset periodic returns
16
+ /// * `market_returns` - Array of market/benchmark periodic returns
17
+ ///
18
+ /// # Returns
19
+ /// Beta coefficient
20
+ ///
21
+ /// # Example
22
+ /// ```
23
+ /// use quant_rust::risk::beta::beta;
24
+ /// let asset = vec![0.02, 0.03, -0.01, 0.04, -0.02];
25
+ /// let market = vec![0.01, 0.02, -0.005, 0.02, -0.01];
26
+ /// let b = beta(&asset, &market);
27
+ /// assert!(b > 0.0);
28
+ /// ```
29
+ pub fn beta(asset_returns: &[f64], market_returns: &[f64]) -> f64 {
30
+ if asset_returns.len() != market_returns.len() || asset_returns.is_empty() {
31
+ return 0.0;
32
+ }
33
+
34
+ let n = asset_returns.len() as f64;
35
+ let avg_asset = mean(asset_returns);
36
+ let avg_market = mean(market_returns);
37
+
38
+ // Covariance
39
+ let cov: f64 = asset_returns
40
+ .iter()
41
+ .zip(market_returns.iter())
42
+ .map(|(a, m)| (a - avg_asset) * (m - avg_market))
43
+ .sum::<f64>()
44
+ / n;
45
+
46
+ // Market variance (population variance since we're using n in covariance)
47
+ let market_var = variance(market_returns, true);
48
+
49
+ if market_var == 0.0 {
50
+ return 0.0;
51
+ }
52
+
53
+ cov / market_var
54
+ }
55
+
56
+ /// Calculate alpha (excess return)
57
+ ///
58
+ /// Alpha measures the excess return of an asset relative to its beta-adjusted
59
+ /// expected return. Positive alpha indicates outperformance.
60
+ ///
61
+ /// Uses the Capital Asset Pricing Model (CAPM):
62
+ /// Alpha = Asset Return - (Risk-Free Rate + Beta * (Market Return - Risk-Free Rate))
63
+ ///
64
+ /// # Arguments
65
+ /// * `asset_returns` - Array of asset periodic returns
66
+ /// * `market_returns` - Array of market/benchmark periodic returns
67
+ /// * `risk_free_rate` - Annual risk-free rate
68
+ /// * `periods_per_year` - Number of periods in a year
69
+ ///
70
+ /// # Returns
71
+ /// Annualized alpha
72
+ ///
73
+ /// # Example
74
+ /// ```
75
+ /// use quant_rust::risk::beta::alpha;
76
+ /// let asset = vec![0.02, 0.03, -0.01, 0.04, -0.02];
77
+ /// let market = vec![0.01, 0.02, -0.005, 0.02, -0.01];
78
+ /// let a = alpha(&asset, &market, 0.04, 252);
79
+ /// assert!(a.is_finite());
80
+ /// ```
81
+ pub fn alpha(
82
+ asset_returns: &[f64],
83
+ market_returns: &[f64],
84
+ risk_free_rate: f64,
85
+ periods_per_year: usize,
86
+ ) -> f64 {
87
+ if asset_returns.len() != market_returns.len() || asset_returns.is_empty() {
88
+ return 0.0;
89
+ }
90
+
91
+ let b = beta(asset_returns, market_returns);
92
+ let asset_return = mean(asset_returns) * periods_per_year as f64;
93
+ let market_return = mean(market_returns) * periods_per_year as f64;
94
+
95
+ // Jensen's alpha
96
+ asset_return - (risk_free_rate + b * (market_return - risk_free_rate))
97
+ }
98
+
99
+ /// Result of beta/alpha calculation
100
+ #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
101
+ pub struct BetaAlphaResult {
102
+ /// Beta coefficient
103
+ pub beta: f64,
104
+ /// Alpha (annualized excess return)
105
+ pub alpha: f64,
106
+ /// Correlation between asset and market
107
+ pub correlation: f64,
108
+ /// R-squared (coefficient of determination)
109
+ pub r_squared: f64,
110
+ }
111
+
112
+ /// Calculate comprehensive beta and alpha metrics
113
+ ///
114
+ /// # Arguments
115
+ /// * `asset_returns` - Array of asset periodic returns
116
+ /// * `market_returns` - Array of market/benchmark periodic returns
117
+ /// * `risk_free_rate` - Annual risk-free rate
118
+ /// * `periods_per_year` - Number of periods in a year
119
+ ///
120
+ /// # Returns
121
+ /// BetaAlphaResult with beta, alpha, correlation, and R-squared
122
+ ///
123
+ /// # Example
124
+ /// ```
125
+ /// use quant_rust::risk::beta::calculate_beta_alpha;
126
+ /// let asset = vec![0.02, 0.03, -0.01, 0.04, -0.02];
127
+ /// let market = vec![0.01, 0.02, -0.005, 0.02, -0.01];
128
+ /// let result = calculate_beta_alpha(&asset, &market, 0.04, 252);
129
+ /// assert!(result.beta > 0.0);
130
+ /// assert!(result.correlation >= -1.0 && result.correlation <= 1.0);
131
+ /// ```
132
+ pub fn calculate_beta_alpha(
133
+ asset_returns: &[f64],
134
+ market_returns: &[f64],
135
+ risk_free_rate: f64,
136
+ periods_per_year: usize,
137
+ ) -> BetaAlphaResult {
138
+ if asset_returns.len() != market_returns.len() || asset_returns.is_empty() {
139
+ return BetaAlphaResult {
140
+ beta: 0.0,
141
+ alpha: 0.0,
142
+ correlation: 0.0,
143
+ r_squared: 0.0,
144
+ };
145
+ }
146
+
147
+ let n = asset_returns.len() as f64;
148
+ let avg_asset = mean(asset_returns);
149
+ let avg_market = mean(market_returns);
150
+
151
+ // Calculate variances and covariance
152
+ let mut cov = 0.0;
153
+ let mut var_asset = 0.0;
154
+ let mut var_market = 0.0;
155
+
156
+ for i in 0..asset_returns.len() {
157
+ let d_asset = asset_returns[i] - avg_asset;
158
+ let d_market = market_returns[i] - avg_market;
159
+ cov += d_asset * d_market;
160
+ var_asset += d_asset * d_asset;
161
+ var_market += d_market * d_market;
162
+ }
163
+
164
+ cov /= n;
165
+ var_market /= n;
166
+
167
+ let b = if var_market > 0.0 { cov / var_market } else { 0.0 };
168
+
169
+ // Correlation
170
+ let std_asset = (var_asset / n).sqrt();
171
+ let std_market = var_market.sqrt();
172
+ let corr = if std_asset > 0.0 && std_market > 0.0 {
173
+ cov / (std_asset * std_market)
174
+ } else {
175
+ 0.0
176
+ };
177
+
178
+ // R-squared = correlation squared
179
+ let r_squared = corr * corr;
180
+
181
+ // Alpha
182
+ let asset_return = avg_asset * periods_per_year as f64;
183
+ let market_return = avg_market * periods_per_year as f64;
184
+ let a = asset_return - (risk_free_rate + b * (market_return - risk_free_rate));
185
+
186
+ BetaAlphaResult {
187
+ beta: b,
188
+ alpha: a,
189
+ correlation: corr,
190
+ r_squared,
191
+ }
192
+ }
193
+
194
+ #[cfg(test)]
195
+ mod tests {
196
+ use super::*;
197
+
198
+ #[test]
199
+ fn test_beta() {
200
+ let asset = vec![0.02, 0.03, -0.01, 0.04, -0.02];
201
+ let market = vec![0.01, 0.02, -0.005, 0.02, -0.01];
202
+ let b = beta(&asset, &market);
203
+ assert!(b > 0.0);
204
+ }
205
+
206
+ #[test]
207
+ fn test_beta_equal_length() {
208
+ // Asset moves 2x market
209
+ let asset = vec![0.02, 0.04, -0.01, 0.02];
210
+ let market = vec![0.01, 0.02, -0.005, 0.01];
211
+ let b = beta(&asset, &market);
212
+ // Should be approximately 2
213
+ assert!((b - 2.0).abs() < 0.5);
214
+ }
215
+
216
+ #[test]
217
+ fn test_beta_empty() {
218
+ let b = beta(&[], &[]);
219
+ assert_eq!(b, 0.0);
220
+ }
221
+
222
+ #[test]
223
+ fn test_beta_mismatched() {
224
+ let asset = vec![0.02, 0.03];
225
+ let market = vec![0.01];
226
+ let b = beta(&asset, &market);
227
+ assert_eq!(b, 0.0);
228
+ }
229
+
230
+ #[test]
231
+ fn test_alpha() {
232
+ let asset = vec![0.02, 0.03, -0.01, 0.04, -0.02];
233
+ let market = vec![0.01, 0.02, -0.005, 0.02, -0.01];
234
+ let a = alpha(&asset, &market, 0.04, 252);
235
+ assert!(a.is_finite());
236
+ }
237
+
238
+ #[test]
239
+ fn test_calculate_beta_alpha() {
240
+ let asset = vec![0.02, 0.03, -0.01, 0.04, -0.02];
241
+ let market = vec![0.01, 0.02, -0.005, 0.02, -0.01];
242
+ let result = calculate_beta_alpha(&asset, &market, 0.04, 252);
243
+ assert!(result.beta > 0.0);
244
+ assert!(result.correlation >= -1.0 && result.correlation <= 1.0);
245
+ assert!(result.r_squared >= 0.0 && result.r_squared <= 1.0);
246
+ assert!(result.alpha.is_finite());
247
+ }
248
+
249
+ #[test]
250
+ fn test_calculate_beta_alpha_perfect_correlation() {
251
+ let asset = vec![0.01, 0.02, -0.01, 0.02];
252
+ let market = vec![0.01, 0.02, -0.01, 0.02];
253
+ let result = calculate_beta_alpha(&asset, &market, 0.04, 252);
254
+ assert!((result.correlation - 1.0).abs() < 1e-10);
255
+ assert!((result.r_squared - 1.0).abs() < 1e-10);
256
+ }
257
+ }
@@ -0,0 +1,256 @@
1
+ //! Drawdown Analysis Functions
2
+ //!
3
+ //! Maximum drawdown, underwater periods, and drawdown analysis.
4
+
5
+ use serde::{Deserialize, Serialize};
6
+
7
+ use crate::utils::min;
8
+
9
+ /// A single drawdown point in time
10
+ #[derive(Debug, Clone, Serialize, Deserialize)]
11
+ pub struct DrawdownPoint {
12
+ /// Start index of the drawdown
13
+ pub start: usize,
14
+ /// End index of the drawdown (recovery point)
15
+ pub end: usize,
16
+ /// Trough (lowest) value during drawdown
17
+ pub trough: f64,
18
+ /// Drawdown magnitude as decimal (e.g., 0.25 = 25%)
19
+ pub drawdown: f64,
20
+ }
21
+
22
+ /// Result of drawdown analysis
23
+ #[derive(Debug, Clone, Serialize, Deserialize)]
24
+ pub struct DrawdownResult {
25
+ /// Maximum drawdown as decimal (e.g., 0.25 = 25%)
26
+ pub max_drawdown: f64,
27
+ /// Duration of maximum drawdown in periods
28
+ pub max_drawdown_duration: usize,
29
+ /// Time to recover from maximum drawdown in periods
30
+ pub recovery_time: usize,
31
+ /// All drawdown periods identified
32
+ pub drawdowns: Vec<DrawdownPoint>,
33
+ }
34
+
35
+ /// Calculate maximum drawdown and related metrics
36
+ ///
37
+ /// Analyzes an equity curve to find maximum drawdown, its duration,
38
+ /// recovery time, and all drawdown periods.
39
+ ///
40
+ /// # Arguments
41
+ /// * `equity_curve` - Array of equity values over time
42
+ ///
43
+ /// # Returns
44
+ /// DrawdownResult with max drawdown, durations, and all drawdown periods
45
+ ///
46
+ /// # Example
47
+ /// ```
48
+ /// use quant_rust::risk::drawdown::calculate_drawdown;
49
+ /// let equity = vec![100.0, 110.0, 105.0, 95.0, 100.0, 90.0, 95.0, 110.0];
50
+ /// let result = calculate_drawdown(&equity);
51
+ /// assert!(result.max_drawdown > 0.0);
52
+ /// assert!(result.max_drawdown <= 1.0);
53
+ /// ```
54
+ pub fn calculate_drawdown(equity_curve: &[f64]) -> DrawdownResult {
55
+ if equity_curve.is_empty() {
56
+ return DrawdownResult {
57
+ max_drawdown: 0.0,
58
+ max_drawdown_duration: 0,
59
+ recovery_time: 0,
60
+ drawdowns: vec![],
61
+ };
62
+ }
63
+
64
+ let mut peak = equity_curve[0];
65
+ let mut max_drawdown = 0.0;
66
+ let mut max_drawdown_start = 0;
67
+ let mut max_drawdown_end = 0;
68
+ let mut current_drawdown_start: Option<usize> = None;
69
+ let mut drawdowns: Vec<DrawdownPoint> = Vec::new();
70
+
71
+ for (i, &equity) in equity_curve.iter().enumerate() {
72
+ if equity > peak {
73
+ // New peak - check if we had a drawdown
74
+ if let Some(start) = current_drawdown_start {
75
+ if start > 0 {
76
+ let trough_slice = &equity_curve[start..i];
77
+ let trough = min(trough_slice);
78
+ let dd = (peak - trough) / peak;
79
+ drawdowns.push(DrawdownPoint {
80
+ start,
81
+ end: i,
82
+ trough,
83
+ drawdown: dd,
84
+ });
85
+ }
86
+ }
87
+ peak = equity;
88
+ current_drawdown_start = None;
89
+ } else {
90
+ if current_drawdown_start.is_none() {
91
+ current_drawdown_start = Some(i);
92
+ }
93
+ let dd = (peak - equity) / peak;
94
+ if dd > max_drawdown {
95
+ max_drawdown = dd;
96
+ max_drawdown_start = current_drawdown_start.unwrap_or(i);
97
+ max_drawdown_end = i;
98
+ }
99
+ }
100
+ }
101
+
102
+ // Handle any remaining drawdown at the end
103
+ if let Some(start) = current_drawdown_start {
104
+ if start > 0 && start < equity_curve.len() {
105
+ let trough_slice = &equity_curve[start..];
106
+ let trough = min(trough_slice);
107
+ let dd = (peak - trough) / peak;
108
+ drawdowns.push(DrawdownPoint {
109
+ start,
110
+ end: equity_curve.len() - 1,
111
+ trough,
112
+ drawdown: dd,
113
+ });
114
+ }
115
+ }
116
+
117
+ let max_drawdown_duration = max_drawdown_end.saturating_sub(max_drawdown_start);
118
+
119
+ // Calculate recovery time (time from trough to new peak)
120
+ let mut recovery_time = 0;
121
+ if max_drawdown > 0.0 && max_drawdown_start < equity_curve.len() {
122
+ let dd_slice = &equity_curve[max_drawdown_start..=max_drawdown_end.min(equity_curve.len() - 1)];
123
+ let trough = min(dd_slice);
124
+
125
+ // Find trough index
126
+ let trough_relative_idx = dd_slice.iter().position(|&x| x == trough).unwrap_or(0);
127
+ let trough_idx = max_drawdown_start + trough_relative_idx;
128
+
129
+ // Peak before drawdown
130
+ let peak_at_trough = if max_drawdown_start > 0 {
131
+ equity_curve[max_drawdown_start - 1]
132
+ } else {
133
+ equity_curve[0]
134
+ };
135
+
136
+ // Find recovery point
137
+ for i in trough_idx..equity_curve.len() {
138
+ if equity_curve[i] >= peak_at_trough {
139
+ recovery_time = i - trough_idx;
140
+ break;
141
+ }
142
+ }
143
+ }
144
+
145
+ DrawdownResult {
146
+ max_drawdown,
147
+ max_drawdown_duration,
148
+ recovery_time,
149
+ drawdowns,
150
+ }
151
+ }
152
+
153
+ /// Calculate underwater periods
154
+ ///
155
+ /// Identifies periods where the equity curve is below its running maximum.
156
+ ///
157
+ /// # Arguments
158
+ /// * `equity_curve` - Array of equity values over time
159
+ ///
160
+ /// # Returns
161
+ /// Vector of (start_index, end_index) tuples representing underwater periods
162
+ ///
163
+ /// # Example
164
+ /// ```
165
+ /// use quant_rust::risk::drawdown::underwater_periods;
166
+ /// let equity = vec![100.0, 110.0, 105.0, 95.0, 100.0, 90.0, 95.0, 110.0];
167
+ /// let periods = underwater_periods(&equity);
168
+ /// assert!(!periods.is_empty());
169
+ /// ```
170
+ pub fn underwater_periods(equity_curve: &[f64]) -> Vec<(usize, usize)> {
171
+ if equity_curve.is_empty() {
172
+ return vec![];
173
+ }
174
+
175
+ let mut periods: Vec<(usize, usize)> = Vec::new();
176
+ let mut peak = equity_curve[0];
177
+ let mut underwater = false;
178
+ let mut start_idx = 0;
179
+
180
+ for (i, &equity) in equity_curve.iter().enumerate() {
181
+ if equity < peak {
182
+ if !underwater {
183
+ underwater = true;
184
+ start_idx = i;
185
+ }
186
+ } else if equity >= peak {
187
+ if underwater {
188
+ periods.push((start_idx, i));
189
+ underwater = false;
190
+ }
191
+ peak = equity;
192
+ }
193
+ }
194
+
195
+ // Handle case where we're still underwater at the end
196
+ if underwater {
197
+ periods.push((start_idx, equity_curve.len() - 1));
198
+ }
199
+
200
+ periods
201
+ }
202
+
203
+ #[cfg(test)]
204
+ mod tests {
205
+ use super::*;
206
+
207
+ #[test]
208
+ fn test_calculate_drawdown() {
209
+ let equity = vec![100.0, 110.0, 105.0, 95.0, 100.0, 90.0, 95.0, 110.0];
210
+ let result = calculate_drawdown(&equity);
211
+ // Max drawdown: from 110 to 90 = 18.18%
212
+ assert!(result.max_drawdown > 0.0);
213
+ assert!(result.max_drawdown <= 1.0);
214
+ assert!(!result.drawdowns.is_empty());
215
+ }
216
+
217
+ #[test]
218
+ fn test_calculate_drawdown_empty() {
219
+ let result = calculate_drawdown(&[]);
220
+ assert_eq!(result.max_drawdown, 0.0);
221
+ assert_eq!(result.max_drawdown_duration, 0);
222
+ }
223
+
224
+ #[test]
225
+ fn test_calculate_drawdown_single() {
226
+ let result = calculate_drawdown(&[100.0]);
227
+ assert_eq!(result.max_drawdown, 0.0);
228
+ }
229
+
230
+ #[test]
231
+ fn test_calculate_drawdown_no_drawdown() {
232
+ let equity = vec![100.0, 110.0, 120.0, 130.0];
233
+ let result = calculate_drawdown(&equity);
234
+ assert_eq!(result.max_drawdown, 0.0);
235
+ }
236
+
237
+ #[test]
238
+ fn test_underwater_periods() {
239
+ let equity = vec![100.0, 110.0, 105.0, 95.0, 100.0, 90.0, 95.0, 110.0];
240
+ let periods = underwater_periods(&equity);
241
+ assert!(!periods.is_empty());
242
+ }
243
+
244
+ #[test]
245
+ fn test_underwater_periods_empty() {
246
+ let periods = underwater_periods(&[]);
247
+ assert!(periods.is_empty());
248
+ }
249
+
250
+ #[test]
251
+ fn test_underwater_periods_no_underwater() {
252
+ let equity = vec![100.0, 110.0, 120.0, 130.0];
253
+ let periods = underwater_periods(&equity);
254
+ assert!(periods.is_empty());
255
+ }
256
+ }
@@ -0,0 +1,201 @@
1
+ //! Leverage and Margin Functions
2
+ //!
3
+ //! Liquidation prices, effective leverage, and margin requirements.
4
+
5
+ use serde::{Deserialize, Serialize};
6
+
7
+ /// Position side for leverage calculations
8
+ #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
9
+ pub enum PositionSide {
10
+ /// Long position
11
+ Long,
12
+ /// Short position
13
+ Short,
14
+ }
15
+
16
+ /// Result of margin calculation
17
+ #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
18
+ pub struct MarginResult {
19
+ /// Price at which position will be liquidated
20
+ pub liquidation_price: f64,
21
+ /// Effective leverage ratio
22
+ pub effective_leverage: f64,
23
+ /// Margin required to open position
24
+ pub margin_required: f64,
25
+ }
26
+
27
+ /// Calculate liquidation price for a leveraged position
28
+ ///
29
+ /// Determines the price at which the position will be forcibly closed
30
+ /// due to insufficient margin.
31
+ ///
32
+ /// # Arguments
33
+ /// * `entry_price` - Entry price of the position
34
+ /// * `leverage` - Leverage ratio (e.g., 10.0 for 10x)
35
+ /// * `side` - Position side (Long or Short)
36
+ /// * `maintenance_margin` - Maintenance margin requirement (default: 0.05)
37
+ ///
38
+ /// # Returns
39
+ /// Liquidation price
40
+ ///
41
+ /// # Example
42
+ /// ```
43
+ /// use quant_rust::risk::leverage::{liquidation_price, PositionSide};
44
+ /// let liq_price = liquidation_price(100.0, 10.0, PositionSide::Long, 0.05);
45
+ /// assert!(liq_price < 100.0);
46
+ /// ```
47
+ pub fn liquidation_price(
48
+ entry_price: f64,
49
+ leverage: f64,
50
+ side: PositionSide,
51
+ maintenance_margin: f64,
52
+ ) -> f64 {
53
+ if leverage <= 0.0 {
54
+ return entry_price;
55
+ }
56
+
57
+ match side {
58
+ PositionSide::Long => entry_price * (1.0 - (1.0 - maintenance_margin) / leverage),
59
+ PositionSide::Short => entry_price * (1.0 + (1.0 - maintenance_margin) / leverage),
60
+ }
61
+ }
62
+
63
+ /// Calculate effective leverage
64
+ ///
65
+ /// The actual leverage being used based on position size and equity.
66
+ ///
67
+ /// # Arguments
68
+ /// * `position_value` - Total value of the position
69
+ /// * `equity` - Account equity
70
+ ///
71
+ /// # Returns
72
+ /// Effective leverage ratio
73
+ ///
74
+ /// # Example
75
+ /// ```
76
+ /// use quant_rust::risk::leverage::effective_leverage;
77
+ /// let lev = effective_leverage(10000.0, 2000.0);
78
+ /// assert!((lev - 5.0).abs() < 1e-10);
79
+ /// ```
80
+ pub fn effective_leverage(position_value: f64, equity: f64) -> f64 {
81
+ if equity == 0.0 {
82
+ return f64::INFINITY;
83
+ }
84
+ position_value / equity
85
+ }
86
+
87
+ /// Calculate margin requirement
88
+ ///
89
+ /// The amount of collateral needed to open a position.
90
+ ///
91
+ /// # Arguments
92
+ /// * `position_value` - Total value of the position
93
+ /// * `initial_margin` - Initial margin requirement (e.g., 0.10 for 10%)
94
+ ///
95
+ /// # Returns
96
+ /// Margin required
97
+ ///
98
+ /// # Example
99
+ /// ```
100
+ /// use quant_rust::risk::leverage::margin_requirement;
101
+ /// let margin = margin_requirement(10000.0, 0.10);
102
+ /// assert!((margin - 1000.0).abs() < 1e-10);
103
+ /// ```
104
+ pub fn margin_requirement(position_value: f64, initial_margin: f64) -> f64 {
105
+ position_value * initial_margin
106
+ }
107
+
108
+ /// Calculate comprehensive margin metrics
109
+ ///
110
+ /// # Arguments
111
+ /// * `entry_price` - Entry price of the position
112
+ /// * `leverage` - Leverage ratio
113
+ /// * `side` - Position side
114
+ /// * `position_value` - Total value of the position
115
+ /// * `equity` - Account equity
116
+ /// * `initial_margin` - Initial margin requirement
117
+ /// * `maintenance_margin` - Maintenance margin requirement
118
+ ///
119
+ /// # Returns
120
+ /// MarginResult with all metrics
121
+ ///
122
+ /// # Example
123
+ /// ```
124
+ /// use quant_rust::risk::leverage::{calculate_margin, PositionSide};
125
+ /// let result = calculate_margin(100.0, 10.0, PositionSide::Long, 10000.0, 2000.0, 0.10, 0.05);
126
+ /// assert!(result.liquidation_price > 0.0);
127
+ /// assert!((result.effective_leverage - 5.0).abs() < 1e-10);
128
+ /// assert!((result.margin_required - 1000.0).abs() < 1e-10);
129
+ /// ```
130
+ pub fn calculate_margin(
131
+ entry_price: f64,
132
+ leverage: f64,
133
+ side: PositionSide,
134
+ position_value: f64,
135
+ equity: f64,
136
+ initial_margin: f64,
137
+ maintenance_margin: f64,
138
+ ) -> MarginResult {
139
+ MarginResult {
140
+ liquidation_price: liquidation_price(entry_price, leverage, side, maintenance_margin),
141
+ effective_leverage: effective_leverage(position_value, equity),
142
+ margin_required: margin_requirement(position_value, initial_margin),
143
+ }
144
+ }
145
+
146
+ #[cfg(test)]
147
+ mod tests {
148
+ use super::*;
149
+
150
+ #[test]
151
+ fn test_liquidation_price_long() {
152
+ let liq_price = liquidation_price(100.0, 10.0, PositionSide::Long, 0.05);
153
+ // Long position: liquidation below entry
154
+ assert!(liq_price < 100.0);
155
+ // 100 * (1 - 0.95/10) = 100 * 0.905 = 90.5
156
+ assert!((liq_price - 90.5).abs() < 1e-10);
157
+ }
158
+
159
+ #[test]
160
+ fn test_liquidation_price_short() {
161
+ let liq_price = liquidation_price(100.0, 10.0, PositionSide::Short, 0.05);
162
+ // Short position: liquidation above entry
163
+ assert!(liq_price > 100.0);
164
+ // 100 * (1 + 0.95/10) = 100 * 1.095 = 109.5
165
+ assert!((liq_price - 109.5).abs() < 1e-10);
166
+ }
167
+
168
+ #[test]
169
+ fn test_effective_leverage() {
170
+ let lev = effective_leverage(10000.0, 2000.0);
171
+ assert!((lev - 5.0).abs() < 1e-10);
172
+ }
173
+
174
+ #[test]
175
+ fn test_effective_leverage_zero_equity() {
176
+ let lev = effective_leverage(10000.0, 0.0);
177
+ assert!(lev.is_infinite());
178
+ }
179
+
180
+ #[test]
181
+ fn test_margin_requirement() {
182
+ let margin = margin_requirement(10000.0, 0.10);
183
+ assert!((margin - 1000.0).abs() < 1e-10);
184
+ }
185
+
186
+ #[test]
187
+ fn test_calculate_margin() {
188
+ let result = calculate_margin(
189
+ 100.0,
190
+ 10.0,
191
+ PositionSide::Long,
192
+ 10000.0,
193
+ 2000.0,
194
+ 0.10,
195
+ 0.05,
196
+ );
197
+ assert!((result.liquidation_price - 90.5).abs() < 1e-10);
198
+ assert!((result.effective_leverage - 5.0).abs() < 1e-10);
199
+ assert!((result.margin_required - 1000.0).abs() < 1e-10);
200
+ }
201
+ }