vectorbt-rust 1.1.0__tar.gz → 1.1.2__tar.gz
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.
- {vectorbt_rust-1.1.0 → vectorbt_rust-1.1.2}/Cargo.lock +1 -1
- {vectorbt_rust-1.1.0 → vectorbt_rust-1.1.2}/Cargo.toml +1 -1
- {vectorbt_rust-1.1.0 → vectorbt_rust-1.1.2}/PKG-INFO +1 -1
- {vectorbt_rust-1.1.0 → vectorbt_rust-1.1.2}/pyproject.toml +1 -1
- {vectorbt_rust-1.1.0 → vectorbt_rust-1.1.2}/src/generic.rs +97 -40
- {vectorbt_rust-1.1.0 → vectorbt_rust-1.1.2}/src/returns.rs +3 -9
- {vectorbt_rust-1.1.0 → vectorbt_rust-1.1.2}/README.md +0 -0
- {vectorbt_rust-1.1.0 → vectorbt_rust-1.1.2}/rustfmt.toml +0 -0
- {vectorbt_rust-1.1.0 → vectorbt_rust-1.1.2}/src/indicators.rs +0 -0
- {vectorbt_rust-1.1.0 → vectorbt_rust-1.1.2}/src/labels.rs +0 -0
- {vectorbt_rust-1.1.0 → vectorbt_rust-1.1.2}/src/lib.rs +0 -0
- {vectorbt_rust-1.1.0 → vectorbt_rust-1.1.2}/src/portfolio.rs +0 -0
- {vectorbt_rust-1.1.0 → vectorbt_rust-1.1.2}/src/records.rs +0 -0
- {vectorbt_rust-1.1.0 → vectorbt_rust-1.1.2}/src/signals.rs +0 -0
|
@@ -18,6 +18,7 @@ pub(crate) const RANGE_OPEN: i64 = 0;
|
|
|
18
18
|
pub(crate) const RANGE_CLOSED: i64 = 1;
|
|
19
19
|
const DRAWDOWN_ACTIVE: i64 = 0;
|
|
20
20
|
const DRAWDOWN_RECOVERED: i64 = 1;
|
|
21
|
+
const INV_COND_TOL: f64 = f64::EPSILON * 1e3;
|
|
21
22
|
|
|
22
23
|
#[pyfunction]
|
|
23
24
|
#[pyo3(signature = (a, seed=None))]
|
|
@@ -703,37 +704,97 @@ pub(crate) fn rolling_mean_2d_c(a: ArrayView2<'_, f64>, window: usize, minp: usi
|
|
|
703
704
|
out
|
|
704
705
|
}
|
|
705
706
|
|
|
707
|
+
#[derive(Clone, Copy, Default)]
|
|
708
|
+
struct RollingVarState {
|
|
709
|
+
count: usize,
|
|
710
|
+
mean: f64,
|
|
711
|
+
m2: f64,
|
|
712
|
+
compensation_add: f64,
|
|
713
|
+
compensation_remove: f64,
|
|
714
|
+
numerically_unstable: bool,
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
impl RollingVarState {
|
|
718
|
+
fn add(&mut self, val: f64) {
|
|
719
|
+
if val.is_nan() {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
let prev_m2 = self.m2;
|
|
723
|
+
self.count += 1;
|
|
724
|
+
let prev_mean = self.mean - self.compensation_add;
|
|
725
|
+
let y = val - self.compensation_add;
|
|
726
|
+
let delta = y - self.mean;
|
|
727
|
+
self.compensation_add = delta + self.mean - y;
|
|
728
|
+
self.mean += delta / self.count as f64;
|
|
729
|
+
self.m2 += (val - prev_mean) * (val - self.mean);
|
|
730
|
+
if prev_m2 * INV_COND_TOL > self.m2 {
|
|
731
|
+
self.numerically_unstable = true;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
fn remove(&mut self, val: f64) {
|
|
736
|
+
if val.is_nan() {
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
let prev_m2 = self.m2;
|
|
740
|
+
self.count -= 1;
|
|
741
|
+
if self.count > 0 {
|
|
742
|
+
let prev_mean = self.mean - self.compensation_remove;
|
|
743
|
+
let y = val - self.compensation_remove;
|
|
744
|
+
let delta = y - self.mean;
|
|
745
|
+
self.compensation_remove = delta + self.mean - y;
|
|
746
|
+
self.mean -= delta / self.count as f64;
|
|
747
|
+
self.m2 -= (val - prev_mean) * (val - self.mean);
|
|
748
|
+
if prev_m2 * INV_COND_TOL > self.m2 {
|
|
749
|
+
self.numerically_unstable = true;
|
|
750
|
+
}
|
|
751
|
+
} else {
|
|
752
|
+
self.mean = 0.0;
|
|
753
|
+
self.m2 = 0.0;
|
|
754
|
+
self.numerically_unstable = false;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
fn reset(&mut self) {
|
|
759
|
+
*self = Self::default();
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
fn std(&self, minp: usize, ddof: usize) -> f64 {
|
|
763
|
+
if self.count >= minp && self.count > ddof {
|
|
764
|
+
(self.m2 / (self.count - ddof) as f64).sqrt()
|
|
765
|
+
} else {
|
|
766
|
+
f64::NAN
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
706
771
|
pub(crate) fn rolling_std_2d_c(a: ArrayView2<'_, f64>, window: usize, minp: usize, ddof: usize) -> Array2<f64> {
|
|
707
772
|
let (nrows, ncols) = a.dim();
|
|
708
773
|
let src = a.as_slice().expect("standard-layout array must be sliceable");
|
|
709
774
|
let mut out = Array2::<f64>::from_elem((nrows, ncols), f64::NAN);
|
|
710
775
|
let dst = out.as_slice_mut().expect("owned array must be sliceable");
|
|
711
|
-
let
|
|
712
|
-
let mut
|
|
713
|
-
let mut counts = vec![0usize; ncols];
|
|
776
|
+
let minp = minp.max(1);
|
|
777
|
+
let mut states = vec![RollingVarState::default(); ncols];
|
|
714
778
|
for row in 0..nrows {
|
|
715
779
|
let row_start = row * ncols;
|
|
716
780
|
for col in 0..ncols {
|
|
717
|
-
let
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
counts[col] += 1;
|
|
781
|
+
let state = &mut states[col];
|
|
782
|
+
let requires_recompute = row == 0 || window == 0;
|
|
783
|
+
if !requires_recompute && row >= window {
|
|
784
|
+
state.remove(src[(row - window) * ncols + col]);
|
|
722
785
|
}
|
|
723
|
-
if
|
|
724
|
-
|
|
725
|
-
if !old.is_nan() {
|
|
726
|
-
sums[col] -= old;
|
|
727
|
-
sums_sq[col] -= old * old;
|
|
728
|
-
counts[col] -= 1;
|
|
729
|
-
}
|
|
786
|
+
if !requires_recompute {
|
|
787
|
+
state.add(src[row_start + col]);
|
|
730
788
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
let
|
|
734
|
-
|
|
735
|
-
|
|
789
|
+
if requires_recompute || state.numerically_unstable {
|
|
790
|
+
state.reset();
|
|
791
|
+
let start = (row + 1).saturating_sub(window);
|
|
792
|
+
for j in start..=row {
|
|
793
|
+
state.add(src[j * ncols + col]);
|
|
794
|
+
}
|
|
795
|
+
state.numerically_unstable = false;
|
|
736
796
|
}
|
|
797
|
+
dst[row_start + col] = state.std(minp, ddof);
|
|
737
798
|
}
|
|
738
799
|
}
|
|
739
800
|
out
|
|
@@ -1129,29 +1190,25 @@ pub(crate) fn rolling_mean_1d(a: &[f64], window: usize, minp: usize) -> Vec<f64>
|
|
|
1129
1190
|
pub(crate) fn rolling_std_1d(a: &[f64], window: usize, minp: usize, ddof: usize) -> Vec<f64> {
|
|
1130
1191
|
let n = a.len();
|
|
1131
1192
|
let mut out = vec![f64::NAN; n];
|
|
1132
|
-
let
|
|
1133
|
-
let mut
|
|
1134
|
-
let mut cnt = 0usize;
|
|
1193
|
+
let minp = minp.max(1);
|
|
1194
|
+
let mut state = RollingVarState::default();
|
|
1135
1195
|
for i in 0..n {
|
|
1136
|
-
let
|
|
1137
|
-
if !
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1196
|
+
let requires_recompute = i == 0 || window == 0;
|
|
1197
|
+
if !requires_recompute && i >= window {
|
|
1198
|
+
state.remove(a[i - window]);
|
|
1199
|
+
}
|
|
1200
|
+
if !requires_recompute {
|
|
1201
|
+
state.add(a[i]);
|
|
1202
|
+
}
|
|
1203
|
+
if requires_recompute || state.numerically_unstable {
|
|
1204
|
+
state.reset();
|
|
1205
|
+
let start = (i + 1).saturating_sub(window);
|
|
1206
|
+
for j in start..=i {
|
|
1207
|
+
state.add(a[j]);
|
|
1148
1208
|
}
|
|
1209
|
+
state.numerically_unstable = false;
|
|
1149
1210
|
}
|
|
1150
|
-
|
|
1151
|
-
let mean = sum / cnt as f64;
|
|
1152
|
-
let variance = (sum_sq - 2.0 * sum * mean + cnt as f64 * mean * mean) / (cnt - ddof) as f64;
|
|
1153
|
-
out[i] = variance.abs().sqrt();
|
|
1154
|
-
}
|
|
1211
|
+
out[i] = state.std(minp, ddof);
|
|
1155
1212
|
}
|
|
1156
1213
|
out
|
|
1157
1214
|
}
|
|
@@ -6,7 +6,6 @@ use ndarray::{Array2, ArrayView2};
|
|
|
6
6
|
use numpy::{PyArray1, PyArray2, PyReadonlyArray1, PyReadonlyArray2};
|
|
7
7
|
use pyo3::exceptions::PyValueError;
|
|
8
8
|
use pyo3::prelude::*;
|
|
9
|
-
use std::cmp::Ordering;
|
|
10
9
|
|
|
11
10
|
fn validate_same_shape_2d(a: ArrayView2<'_, f64>, b: ArrayView2<'_, f64>, name: &str) -> PyResult<()> {
|
|
12
11
|
if a.dim() != b.dim() {
|
|
@@ -762,17 +761,12 @@ pub(crate) fn value_at_risk_1d(returns: &[f64], cutoff: f64) -> f64 {
|
|
|
762
761
|
}
|
|
763
762
|
|
|
764
763
|
pub(crate) fn cond_value_at_risk_1d(returns: &[f64], cutoff: f64) -> f64 {
|
|
765
|
-
|
|
764
|
+
let mut vals: Vec<f64> = returns.iter().copied().filter(|value| !value.is_nan()).collect();
|
|
765
|
+
if vals.is_empty() {
|
|
766
766
|
return f64::NAN;
|
|
767
767
|
}
|
|
768
|
-
let mut vals = returns.to_vec();
|
|
769
768
|
let cutoff_index = ((vals.len() - 1) as f64 * cutoff) as usize;
|
|
770
|
-
vals.select_nth_unstable_by(cutoff_index, |left, right|
|
|
771
|
-
(true, true) => Ordering::Equal,
|
|
772
|
-
(true, false) => Ordering::Greater,
|
|
773
|
-
(false, true) => Ordering::Less,
|
|
774
|
-
(false, false) => left.partial_cmp(right).unwrap(),
|
|
775
|
-
});
|
|
769
|
+
vals.select_nth_unstable_by(cutoff_index, |left, right| left.partial_cmp(right).unwrap());
|
|
776
770
|
mean_strict(&vals[..cutoff_index + 1])
|
|
777
771
|
}
|
|
778
772
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|