@ebowwa/quant-rust 0.2.0 → 0.2.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ebowwa/quant-rust",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "High-performance quantitative finance library with Rust FFI bindings for Bun",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -68,21 +68,23 @@ pub fn calculate_drawdown(equity_curve: &[f64]) -> DrawdownResult {
68
68
  let mut current_drawdown_start: Option<usize> = None;
69
69
  let mut drawdowns: Vec<DrawdownPoint> = Vec::new();
70
70
 
71
+ let initial_peak = equity_curve[0]; // Track initial peak for start==0 case
72
+
71
73
  for (i, &equity) in equity_curve.iter().enumerate() {
72
74
  if equity > peak {
73
75
  // New peak - check if we had a drawdown
74
76
  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
- }
77
+ // Use initial peak if drawdown started at index 0
78
+ let peak_for_dd = if start == 0 { initial_peak } else { peak };
79
+ let trough_slice = &equity_curve[start..i];
80
+ let trough = min(trough_slice);
81
+ let dd = (peak_for_dd - trough) / peak_for_dd;
82
+ drawdowns.push(DrawdownPoint {
83
+ start,
84
+ end: i,
85
+ trough,
86
+ drawdown: dd,
87
+ });
86
88
  }
87
89
  peak = equity;
88
90
  current_drawdown_start = None;
@@ -101,10 +103,11 @@ pub fn calculate_drawdown(equity_curve: &[f64]) -> DrawdownResult {
101
103
 
102
104
  // Handle any remaining drawdown at the end
103
105
  if let Some(start) = current_drawdown_start {
104
- if start > 0 && start < equity_curve.len() {
106
+ if start < equity_curve.len() {
107
+ let peak_for_dd = if start == 0 { initial_peak } else { peak };
105
108
  let trough_slice = &equity_curve[start..];
106
109
  let trough = min(trough_slice);
107
- let dd = (peak - trough) / peak;
110
+ let dd = (peak_for_dd - trough) / peak_for_dd;
108
111
  drawdowns.push(DrawdownPoint {
109
112
  start,
110
113
  end: equity_curve.len() - 1,
@@ -253,4 +256,28 @@ mod tests {
253
256
  let periods = underwater_periods(&equity);
254
257
  assert!(periods.is_empty());
255
258
  }
259
+
260
+ #[test]
261
+ fn test_calculate_drawdown_start_at_zero() {
262
+ // Edge case: drawdown starting at index 0
263
+ // Initial value 100 is the peak, then decline to 90, recover to 100
264
+ let equity = vec![100.0, 95.0, 90.0, 95.0, 100.0];
265
+ let result = calculate_drawdown(&equity);
266
+ // Should detect drawdown from 100 to 90 = 10%
267
+ assert!((result.max_drawdown - 0.1).abs() < 0.001, "Expected 10% drawdown, got {}", result.max_drawdown);
268
+ // Drawdowns are recorded when recovery happens
269
+ // Since 100 >= 100 at end, drawdown should be recorded
270
+ assert!(!result.drawdowns.is_empty(), "Expected drawdowns to be recorded");
271
+ }
272
+
273
+ #[test]
274
+ fn test_calculate_drawdown_declining_only() {
275
+ // Edge case: only declining, never recovers
276
+ let equity = vec![100.0, 95.0, 90.0, 85.0, 80.0];
277
+ let result = calculate_drawdown(&equity);
278
+ // Max drawdown: 100 to 80 = 20%
279
+ assert!((result.max_drawdown - 0.2).abs() < 0.001, "Expected 20% drawdown, got {}", result.max_drawdown);
280
+ // Should record drawdown at end since never recovered
281
+ assert!(!result.drawdowns.is_empty(), "Expected drawdown to be recorded at end");
282
+ }
256
283
  }