use scirs2_core::ndarray::{Array1, Array2};
use sklears_core::error::Result as SklResult;
type Result<T> = SklResult<T>;
type Float = f64;
pub fn detect_market_regimes(x: &Array2<Float>, method: &str) -> Result<Array2<Float>> {
match method {
"volatility" => detect_regimes_by_volatility(x),
"momentum" => detect_regimes_by_momentum(x),
"hmm" => detect_market_regimes_hmm(x, 2),
_ => detect_regimes_by_volatility(x),
}
}
fn detect_regimes_by_volatility(x: &Array2<Float>) -> Result<Array2<Float>> {
let n_samples = x.nrows();
let n_features = x.ncols();
let mut regimes = Array2::zeros((n_samples, 2));
for i in 0..n_features {
let feature = x.column(i);
let window_size = 20.min(n_samples);
for j in window_size..n_samples {
let window = feature.slice(scirs2_core::ndarray::s![j - window_size..j]);
let mean = window.mean().unwrap_or(0.0);
let volatility = window
.mapv(|x| (x - mean).powi(2))
.mean()
.unwrap_or(0.0)
.sqrt();
let threshold = window.std(0.0);
if volatility > threshold {
regimes[[j, 1]] += 1.0;
} else {
regimes[[j, 0]] += 1.0;
}
}
}
for i in 0..n_samples {
let sum = regimes[[i, 0]] + regimes[[i, 1]];
if sum > 1e-10 {
regimes[[i, 0]] /= sum;
regimes[[i, 1]] /= sum;
}
}
Ok(regimes)
}
fn detect_regimes_by_momentum(x: &Array2<Float>) -> Result<Array2<Float>> {
let n_samples = x.nrows();
let n_features = x.ncols();
let mut regimes = Array2::zeros((n_samples, 2));
for i in 0..n_features {
let feature = x.column(i);
for j in 1..n_samples {
let momentum = feature[j] - feature[j - 1];
if momentum > 0.0 {
regimes[[j, 1]] += 1.0; } else {
regimes[[j, 0]] += 1.0; }
}
}
for i in 0..n_samples {
let sum = regimes[[i, 0]] + regimes[[i, 1]];
if sum > 1e-10 {
regimes[[i, 0]] /= sum;
regimes[[i, 1]] /= sum;
}
}
Ok(regimes)
}
pub fn detect_market_regimes_hmm(x: &Array2<Float>, n_regimes: usize) -> Result<Array2<Float>> {
let n_samples = x.nrows();
let mut regime_probs = Array2::zeros((n_samples, n_regimes));
let mut sample_stats = Vec::with_capacity(n_samples);
for i in 0..n_samples {
let row = x.row(i);
let mean = row.mean().unwrap_or(0.0);
let std = row.std(0.0);
sample_stats.push((mean, std));
}
let mean_min = sample_stats
.iter()
.map(|(m, _)| *m)
.fold(Float::INFINITY, Float::min);
let mean_max = sample_stats
.iter()
.map(|(m, _)| *m)
.fold(Float::NEG_INFINITY, Float::max);
for i in 0..n_samples {
let (mean, _std) = sample_stats[i];
let normalized = if mean_max - mean_min > 1e-10 {
(mean - mean_min) / (mean_max - mean_min)
} else {
0.5
};
let regime_idx = (normalized * n_regimes as Float).floor() as usize;
let regime_idx = regime_idx.min(n_regimes - 1);
regime_probs[[i, regime_idx]] = 1.0;
}
Ok(regime_probs)
}
pub fn compute_regime_transitions(regime_probs: &Array2<Float>) -> Result<Array2<Float>> {
let n_regimes = regime_probs.ncols();
let n_samples = regime_probs.nrows();
let mut transition_matrix = Array2::zeros((n_regimes, n_regimes));
for t in 1..n_samples {
let prev_regime = regime_probs
.row(t - 1)
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).expect("operation should succeed"))
.map(|(idx, _)| idx)
.unwrap_or(0);
let curr_regime = regime_probs
.row(t)
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).expect("operation should succeed"))
.map(|(idx, _)| idx)
.unwrap_or(0);
transition_matrix[[prev_regime, curr_regime]] += 1.0;
}
for i in 0..n_regimes {
let row_sum = transition_matrix.row(i).sum();
if row_sum > 1e-10 {
for j in 0..n_regimes {
transition_matrix[[i, j]] /= row_sum;
}
}
}
Ok(transition_matrix)
}
pub fn compute_regime_based_scores(
x: &Array2<Float>,
_y: &Array1<Float>,
method: &str,
) -> Result<Array1<Float>> {
let regimes = detect_market_regimes(x, method)?;
let n_features = x.ncols();
let mut scores = Array1::zeros(n_features);
for i in 0..n_features {
let feature = x.column(i);
let most_likely_regime = Array1::from_shape_fn(regimes.nrows(), |j| {
regimes
.row(j)
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).expect("operation should succeed"))
.map(|(idx, _)| idx as Float)
.unwrap_or(0.0)
});
let corr =
super::utilities::compute_pearson_correlation(&feature, &most_likely_regime.view());
scores[i] = corr.abs();
}
Ok(scores)
}