1use scirs2_core::ndarray::{Array1, Array2};
14use scirs2_core::random::essentials::Normal;
15use scirs2_core::random::{thread_rng, Distribution};
16use sklears_core::{
17 error::{Result, SklearsError},
18 prelude::{Fit, Transform},
19 traits::{Trained, Untrained},
20 types::Float,
21};
22use std::marker::PhantomData;
23
24pub struct FinancialKernel<State = Untrained> {
36 n_components: usize,
38 window_size: usize,
40 include_volatility: bool,
42 projection: Option<Array2<Float>>,
44 feature_weights: Option<Array1<Float>>,
46 _state: PhantomData<State>,
48}
49
50impl FinancialKernel<Untrained> {
51 pub fn new(n_components: usize, window_size: usize) -> Self {
53 Self {
54 n_components,
55 window_size,
56 include_volatility: true,
57 projection: None,
58 feature_weights: None,
59 _state: PhantomData,
60 }
61 }
62
63 pub fn include_volatility(mut self, include_volatility: bool) -> Self {
65 self.include_volatility = include_volatility;
66 self
67 }
68}
69
70impl Default for FinancialKernel<Untrained> {
71 fn default() -> Self {
72 Self::new(100, 20)
73 }
74}
75
76impl Fit<Array2<Float>, ()> for FinancialKernel<Untrained> {
77 type Fitted = FinancialKernel<Trained>;
78
79 fn fit(self, x: &Array2<Float>, _y: &()) -> Result<Self::Fitted> {
80 let n_samples = x.nrows();
81 if n_samples == 0 {
82 return Err(SklearsError::InvalidInput(
83 "Input array cannot be empty".to_string(),
84 ));
85 }
86
87 let base_features = 10; let volatility_features = if self.include_volatility { 5 } else { 0 };
90 let technical_features = 8; let feature_dim = base_features + volatility_features + technical_features;
92
93 let mut rng = thread_rng();
94 let normal = Normal::new(0.0, 1.0 / (feature_dim as Float).sqrt())
95 .expect("operation should succeed");
96
97 let mut projection = Array2::zeros((feature_dim, self.n_components));
99 for i in 0..feature_dim {
100 for j in 0..self.n_components {
101 projection[[i, j]] = normal.sample(&mut rng);
102 }
103 }
104
105 let mut feature_weights = Array1::zeros(3);
107 feature_weights[0] = 1.0; feature_weights[1] = 0.7; feature_weights[2] = 0.5; Ok(FinancialKernel {
112 n_components: self.n_components,
113 window_size: self.window_size,
114 include_volatility: self.include_volatility,
115 projection: Some(projection),
116 feature_weights: Some(feature_weights),
117 _state: PhantomData,
118 })
119 }
120}
121
122impl Transform<Array2<Float>, Array2<Float>> for FinancialKernel<Trained> {
123 fn transform(&self, x: &Array2<Float>) -> Result<Array2<Float>> {
124 let n_samples = x.nrows();
125 let n_features = x.ncols();
126
127 if n_samples == 0 {
128 return Err(SklearsError::InvalidInput(
129 "Input array cannot be empty".to_string(),
130 ));
131 }
132
133 let projection = self.projection.as_ref().expect("operation should succeed");
134 let feature_dim = projection.nrows();
135 let feature_weights = self
136 .feature_weights
137 .as_ref()
138 .expect("operation should succeed");
139
140 let mut financial_features = Array2::zeros((n_samples, feature_dim));
142
143 for i in 0..n_samples {
144 let mut feat_idx = 0;
145
146 for j in 0..10.min(n_features) {
148 if j + 1 < n_features {
149 let ret = (x[[i, j + 1]] / (x[[i, j]] + 1e-8)).ln();
150 financial_features[[i, feat_idx]] = ret * feature_weights[0];
151 feat_idx += 1;
152 }
153 }
154
155 if self.include_volatility {
157 for j in 0..5.min(n_features / 2) {
158 let mut sum_sq = 0.0;
159 let window = self.window_size.min(n_features - j);
160 for k in 0..window {
161 if j + k < n_features {
162 sum_sq += (x[[i, j + k]] - x[[i, j]]).powi(2);
163 }
164 }
165 let volatility = (sum_sq / window as Float).sqrt();
166 if feat_idx < feature_dim {
167 financial_features[[i, feat_idx]] = volatility * feature_weights[1];
168 feat_idx += 1;
169 }
170 }
171 }
172
173 for j in 0..8.min(n_features / 3) {
175 let window = self.window_size.min(n_features - j);
176 let mut sma = 0.0;
177 for k in 0..window {
178 if j + k < n_features {
179 sma += x[[i, j + k]];
180 }
181 }
182 sma /= window as Float;
183 if feat_idx < feature_dim {
184 financial_features[[i, feat_idx]] = sma * feature_weights[2];
185 feat_idx += 1;
186 }
187 }
188 }
189
190 let features = financial_features.dot(projection);
192
193 Ok(features)
194 }
195}
196
197#[derive(Debug, Clone, Copy)]
203pub enum VolatilityModel {
204 Historical,
206 EWMA,
208 GARCH,
210 Realized,
212}
213
214pub struct VolatilityKernel<State = Untrained> {
221 n_components: usize,
223 model: VolatilityModel,
225 decay_factor: Float,
227 projection: Option<Array2<Float>>,
229 _state: PhantomData<State>,
231}
232
233impl VolatilityKernel<Untrained> {
234 pub fn new(n_components: usize, model: VolatilityModel) -> Self {
236 Self {
237 n_components,
238 model,
239 decay_factor: 0.94,
240 projection: None,
241 _state: PhantomData,
242 }
243 }
244
245 pub fn decay_factor(mut self, decay_factor: Float) -> Self {
247 self.decay_factor = decay_factor;
248 self
249 }
250}
251
252impl Default for VolatilityKernel<Untrained> {
253 fn default() -> Self {
254 Self::new(100, VolatilityModel::EWMA)
255 }
256}
257
258impl Fit<Array2<Float>, ()> for VolatilityKernel<Untrained> {
259 type Fitted = VolatilityKernel<Trained>;
260
261 fn fit(self, x: &Array2<Float>, _y: &()) -> Result<Self::Fitted> {
262 let n_samples = x.nrows();
263 if n_samples == 0 {
264 return Err(SklearsError::InvalidInput(
265 "Input array cannot be empty".to_string(),
266 ));
267 }
268
269 let feature_dim = 20;
271
272 let mut rng = thread_rng();
273 let normal = Normal::new(0.0, 1.0 / (feature_dim as Float).sqrt())
274 .expect("operation should succeed");
275
276 let mut projection = Array2::zeros((feature_dim, self.n_components));
278 for i in 0..feature_dim {
279 for j in 0..self.n_components {
280 projection[[i, j]] = normal.sample(&mut rng);
281 }
282 }
283
284 Ok(VolatilityKernel {
285 n_components: self.n_components,
286 model: self.model,
287 decay_factor: self.decay_factor,
288 projection: Some(projection),
289 _state: PhantomData,
290 })
291 }
292}
293
294impl Transform<Array2<Float>, Array2<Float>> for VolatilityKernel<Trained> {
295 fn transform(&self, x: &Array2<Float>) -> Result<Array2<Float>> {
296 let n_samples = x.nrows();
297 let n_features = x.ncols();
298
299 if n_samples == 0 {
300 return Err(SklearsError::InvalidInput(
301 "Input array cannot be empty".to_string(),
302 ));
303 }
304
305 let projection = self.projection.as_ref().expect("operation should succeed");
306 let feature_dim = projection.nrows();
307
308 let mut volatility_features = Array2::zeros((n_samples, feature_dim));
310
311 for i in 0..n_samples {
312 match self.model {
313 VolatilityModel::Historical => {
314 for j in 0..feature_dim.min(n_features) {
316 let window = 5 + j;
317 let mut sum_sq = 0.0;
318 let count = window.min(n_features);
319 for k in 0..count {
320 sum_sq += x[[i, k]].powi(2);
321 }
322 volatility_features[[i, j]] = (sum_sq / count as Float).sqrt();
323 }
324 }
325 VolatilityModel::EWMA => {
326 for j in 0..feature_dim.min(n_features) {
328 let mut ewma_var = x[[i, 0]].powi(2);
329 for k in 1..n_features {
330 ewma_var = self.decay_factor * ewma_var
331 + (1.0 - self.decay_factor) * x[[i, k]].powi(2);
332 }
333 volatility_features[[i, j]] = ewma_var.sqrt();
334 }
335 }
336 VolatilityModel::GARCH => {
337 for j in 0..feature_dim.min(n_features) {
339 let alpha = 0.1;
340 let beta = 0.85;
341 let omega = 0.05;
342 let mut variance = x[[i, 0]].powi(2);
343 for k in 1..n_features {
344 variance = omega + alpha * x[[i, k - 1]].powi(2) + beta * variance;
345 }
346 volatility_features[[i, j]] = variance.sqrt();
347 }
348 }
349 VolatilityModel::Realized => {
350 for j in 0..feature_dim.min(n_features) {
352 let mut realized_var = 0.0;
353 for k in 0..n_features {
354 realized_var += x[[i, k]].powi(2);
355 }
356 volatility_features[[i, j]] = (realized_var / n_features as Float).sqrt();
357 }
358 }
359 }
360 }
361
362 let features = volatility_features.dot(projection);
364
365 Ok(features)
366 }
367}
368
369pub struct EconometricKernel<State = Untrained> {
381 n_components: usize,
383 ar_order: usize,
385 include_differences: bool,
387 projection: Option<Array2<Float>>,
389 _state: PhantomData<State>,
391}
392
393impl EconometricKernel<Untrained> {
394 pub fn new(n_components: usize, ar_order: usize) -> Self {
396 Self {
397 n_components,
398 ar_order,
399 include_differences: true,
400 projection: None,
401 _state: PhantomData,
402 }
403 }
404
405 pub fn include_differences(mut self, include_differences: bool) -> Self {
407 self.include_differences = include_differences;
408 self
409 }
410}
411
412impl Default for EconometricKernel<Untrained> {
413 fn default() -> Self {
414 Self::new(100, 4)
415 }
416}
417
418impl Fit<Array2<Float>, ()> for EconometricKernel<Untrained> {
419 type Fitted = EconometricKernel<Trained>;
420
421 fn fit(self, x: &Array2<Float>, _y: &()) -> Result<Self::Fitted> {
422 let n_samples = x.nrows();
423 if n_samples == 0 {
424 return Err(SklearsError::InvalidInput(
425 "Input array cannot be empty".to_string(),
426 ));
427 }
428
429 let base_dim = self.ar_order * 2;
431 let diff_dim = if self.include_differences { 10 } else { 0 };
432 let feature_dim = base_dim + diff_dim;
433
434 let mut rng = thread_rng();
435 let normal = Normal::new(0.0, 1.0 / (feature_dim as Float).sqrt())
436 .expect("operation should succeed");
437
438 let mut projection = Array2::zeros((feature_dim, self.n_components));
440 for i in 0..feature_dim {
441 for j in 0..self.n_components {
442 projection[[i, j]] = normal.sample(&mut rng);
443 }
444 }
445
446 Ok(EconometricKernel {
447 n_components: self.n_components,
448 ar_order: self.ar_order,
449 include_differences: self.include_differences,
450 projection: Some(projection),
451 _state: PhantomData,
452 })
453 }
454}
455
456impl Transform<Array2<Float>, Array2<Float>> for EconometricKernel<Trained> {
457 fn transform(&self, x: &Array2<Float>) -> Result<Array2<Float>> {
458 let n_samples = x.nrows();
459 let n_features = x.ncols();
460
461 if n_samples == 0 {
462 return Err(SklearsError::InvalidInput(
463 "Input array cannot be empty".to_string(),
464 ));
465 }
466
467 let projection = self.projection.as_ref().expect("operation should succeed");
468 let feature_dim = projection.nrows();
469
470 let mut econ_features = Array2::zeros((n_samples, feature_dim));
472
473 for i in 0..n_samples {
474 let mut feat_idx = 0;
475
476 for lag in 1..=self.ar_order {
478 if lag < n_features && feat_idx < feature_dim {
479 econ_features[[i, feat_idx]] = x[[i, n_features - lag - 1]];
480 feat_idx += 1;
481 }
482 }
483
484 for lag in 1..=self.ar_order {
486 if lag < n_features && feat_idx < feature_dim {
487 let mut autocorr = 0.0;
488 let count = n_features - lag;
489 for k in 0..count {
490 autocorr += x[[i, k]] * x[[i, k + lag]];
491 }
492 econ_features[[i, feat_idx]] = autocorr / count as Float;
493 feat_idx += 1;
494 }
495 }
496
497 if self.include_differences {
499 for j in 0..10.min(n_features - 1) {
500 if feat_idx < feature_dim {
501 let diff1 = x[[i, j + 1]] - x[[i, j]];
502 econ_features[[i, feat_idx]] = diff1;
503 feat_idx += 1;
504 }
505 }
506 }
507 }
508
509 let features = econ_features.dot(projection);
511
512 Ok(features)
513 }
514}
515
516pub struct PortfolioKernel<State = Untrained> {
528 n_components: usize,
530 n_assets: usize,
532 risk_free_rate: Float,
534 projection: Option<Array2<Float>>,
536 _state: PhantomData<State>,
538}
539
540impl PortfolioKernel<Untrained> {
541 pub fn new(n_components: usize, n_assets: usize) -> Self {
543 Self {
544 n_components,
545 n_assets,
546 risk_free_rate: 0.02,
547 projection: None,
548 _state: PhantomData,
549 }
550 }
551
552 pub fn risk_free_rate(mut self, risk_free_rate: Float) -> Self {
554 self.risk_free_rate = risk_free_rate;
555 self
556 }
557}
558
559impl Default for PortfolioKernel<Untrained> {
560 fn default() -> Self {
561 Self::new(100, 10)
562 }
563}
564
565impl Fit<Array2<Float>, ()> for PortfolioKernel<Untrained> {
566 type Fitted = PortfolioKernel<Trained>;
567
568 fn fit(self, x: &Array2<Float>, _y: &()) -> Result<Self::Fitted> {
569 let n_samples = x.nrows();
570 if n_samples == 0 {
571 return Err(SklearsError::InvalidInput(
572 "Input array cannot be empty".to_string(),
573 ));
574 }
575
576 let feature_dim = 30;
578
579 let mut rng = thread_rng();
580 let normal = Normal::new(0.0, 1.0 / (feature_dim as Float).sqrt())
581 .expect("operation should succeed");
582
583 let mut projection = Array2::zeros((feature_dim, self.n_components));
585 for i in 0..feature_dim {
586 for j in 0..self.n_components {
587 projection[[i, j]] = normal.sample(&mut rng);
588 }
589 }
590
591 Ok(PortfolioKernel {
592 n_components: self.n_components,
593 n_assets: self.n_assets,
594 risk_free_rate: self.risk_free_rate,
595 projection: Some(projection),
596 _state: PhantomData,
597 })
598 }
599}
600
601impl Transform<Array2<Float>, Array2<Float>> for PortfolioKernel<Trained> {
602 fn transform(&self, x: &Array2<Float>) -> Result<Array2<Float>> {
603 let n_samples = x.nrows();
604 let n_features = x.ncols();
605
606 if n_samples == 0 {
607 return Err(SklearsError::InvalidInput(
608 "Input array cannot be empty".to_string(),
609 ));
610 }
611
612 let projection = self.projection.as_ref().expect("operation should succeed");
613 let feature_dim = projection.nrows();
614
615 let mut portfolio_features = Array2::zeros((n_samples, feature_dim));
617
618 for i in 0..n_samples {
619 let mut feat_idx = 0;
620
621 let mut mean_return = 0.0;
623 for j in 0..n_features {
624 mean_return += x[[i, j]];
625 }
626 mean_return /= n_features as Float;
627 if feat_idx < feature_dim {
628 portfolio_features[[i, feat_idx]] = mean_return;
629 feat_idx += 1;
630 }
631
632 let mut variance = 0.0;
634 for j in 0..n_features {
635 variance += (x[[i, j]] - mean_return).powi(2);
636 }
637 let volatility = (variance / n_features as Float).sqrt();
638 if feat_idx < feature_dim {
639 portfolio_features[[i, feat_idx]] = volatility;
640 feat_idx += 1;
641 }
642
643 if volatility > 1e-8 && feat_idx < feature_dim {
645 let sharpe = (mean_return - self.risk_free_rate) / volatility;
646 portfolio_features[[i, feat_idx]] = sharpe;
647 feat_idx += 1;
648 }
649
650 let mut herfindahl = 0.0;
652 let weight = 1.0 / n_features as Float;
653 for _ in 0..n_features {
654 herfindahl += weight.powi(2);
655 }
656 let diversification = 1.0 - herfindahl;
657 if feat_idx < feature_dim {
658 portfolio_features[[i, feat_idx]] = diversification;
659 feat_idx += 1;
660 }
661
662 for j in 0..10.min(n_features) {
664 if j + 1 < n_features && feat_idx < feature_dim {
665 let corr_approx = x[[i, j]] * x[[i, j + 1]];
666 portfolio_features[[i, feat_idx]] = corr_approx;
667 feat_idx += 1;
668 }
669 }
670 }
671
672 let features = portfolio_features.dot(projection);
674
675 Ok(features)
676 }
677}
678
679pub struct RiskKernel<State = Untrained> {
691 n_components: usize,
693 confidence_level: Float,
695 include_tail_risk: bool,
697 projection: Option<Array2<Float>>,
699 _state: PhantomData<State>,
701}
702
703impl RiskKernel<Untrained> {
704 pub fn new(n_components: usize, confidence_level: Float) -> Self {
706 Self {
707 n_components,
708 confidence_level,
709 include_tail_risk: true,
710 projection: None,
711 _state: PhantomData,
712 }
713 }
714
715 pub fn include_tail_risk(mut self, include_tail_risk: bool) -> Self {
717 self.include_tail_risk = include_tail_risk;
718 self
719 }
720}
721
722impl Default for RiskKernel<Untrained> {
723 fn default() -> Self {
724 Self::new(100, 0.95)
725 }
726}
727
728impl Fit<Array2<Float>, ()> for RiskKernel<Untrained> {
729 type Fitted = RiskKernel<Trained>;
730
731 fn fit(self, x: &Array2<Float>, _y: &()) -> Result<Self::Fitted> {
732 let n_samples = x.nrows();
733 if n_samples == 0 {
734 return Err(SklearsError::InvalidInput(
735 "Input array cannot be empty".to_string(),
736 ));
737 }
738
739 let base_dim = 15;
741 let tail_dim = if self.include_tail_risk { 10 } else { 0 };
742 let feature_dim = base_dim + tail_dim;
743
744 let mut rng = thread_rng();
745 let normal = Normal::new(0.0, 1.0 / (feature_dim as Float).sqrt())
746 .expect("operation should succeed");
747
748 let mut projection = Array2::zeros((feature_dim, self.n_components));
750 for i in 0..feature_dim {
751 for j in 0..self.n_components {
752 projection[[i, j]] = normal.sample(&mut rng);
753 }
754 }
755
756 Ok(RiskKernel {
757 n_components: self.n_components,
758 confidence_level: self.confidence_level,
759 include_tail_risk: self.include_tail_risk,
760 projection: Some(projection),
761 _state: PhantomData,
762 })
763 }
764}
765
766impl Transform<Array2<Float>, Array2<Float>> for RiskKernel<Trained> {
767 fn transform(&self, x: &Array2<Float>) -> Result<Array2<Float>> {
768 let n_samples = x.nrows();
769 let n_features = x.ncols();
770
771 if n_samples == 0 {
772 return Err(SklearsError::InvalidInput(
773 "Input array cannot be empty".to_string(),
774 ));
775 }
776
777 let projection = self.projection.as_ref().expect("operation should succeed");
778 let feature_dim = projection.nrows();
779
780 let mut risk_features = Array2::zeros((n_samples, feature_dim));
782
783 for i in 0..n_samples {
784 let mut feat_idx = 0;
785
786 let mut mean = 0.0;
788 let mut variance = 0.0;
789 for j in 0..n_features {
790 mean += x[[i, j]];
791 }
792 mean /= n_features as Float;
793
794 for j in 0..n_features {
795 variance += (x[[i, j]] - mean).powi(2);
796 }
797 variance /= n_features as Float;
798 let std_dev = variance.sqrt();
799
800 let z_score = 1.645; let var = mean - z_score * std_dev;
803 if feat_idx < feature_dim {
804 risk_features[[i, feat_idx]] = var;
805 feat_idx += 1;
806 }
807
808 let cvar = mean - (2.5 * std_dev); if feat_idx < feature_dim {
811 risk_features[[i, feat_idx]] = cvar;
812 feat_idx += 1;
813 }
814
815 let mut downside_var = 0.0;
817 let mut downside_count = 0;
818 for j in 0..n_features {
819 if x[[i, j]] < mean {
820 downside_var += (x[[i, j]] - mean).powi(2);
821 downside_count += 1;
822 }
823 }
824 if downside_count > 0 {
825 let downside_dev = (downside_var / downside_count as Float).sqrt();
826 if feat_idx < feature_dim {
827 risk_features[[i, feat_idx]] = downside_dev;
828 feat_idx += 1;
829 }
830 }
831
832 let mut max_dd = 0.0;
834 let mut peak = x[[i, 0]];
835 for j in 0..n_features {
836 if x[[i, j]] > peak {
837 peak = x[[i, j]];
838 }
839 let dd = (peak - x[[i, j]]) / (peak.abs() + 1e-8);
840 if dd > max_dd {
841 max_dd = dd;
842 }
843 }
844 if feat_idx < feature_dim {
845 risk_features[[i, feat_idx]] = max_dd;
846 feat_idx += 1;
847 }
848
849 if self.include_tail_risk {
851 let mut skew = 0.0;
853 for j in 0..n_features {
854 skew += ((x[[i, j]] - mean) / (std_dev + 1e-8)).powi(3);
855 }
856 skew /= n_features as Float;
857 if feat_idx < feature_dim {
858 risk_features[[i, feat_idx]] = skew;
859 feat_idx += 1;
860 }
861
862 let mut kurt = 0.0;
864 for j in 0..n_features {
865 kurt += ((x[[i, j]] - mean) / (std_dev + 1e-8)).powi(4);
866 }
867 kurt /= n_features as Float;
868 if feat_idx < feature_dim {
869 risk_features[[i, feat_idx]] = kurt - 3.0; }
871 }
872 }
873
874 let features = risk_features.dot(projection);
876
877 Ok(features)
878 }
879}
880
881#[cfg(test)]
886mod tests {
887 use super::*;
888 use scirs2_core::ndarray::array;
889
890 #[test]
891 fn test_financial_kernel_basic() {
892 let x = array![[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]];
893
894 let kernel = FinancialKernel::new(50, 10);
895 let fitted = kernel.fit(&x, &()).expect("operation should succeed");
896 let features = fitted.transform(&x).expect("operation should succeed");
897
898 assert_eq!(features.shape(), &[2, 50]);
899 }
900
901 #[test]
902 fn test_financial_kernel_volatility() {
903 let x = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
904
905 let kernel = FinancialKernel::new(40, 5).include_volatility(true);
906 let fitted = kernel.fit(&x, &()).expect("operation should succeed");
907 let features = fitted.transform(&x).expect("operation should succeed");
908
909 assert_eq!(features.shape(), &[2, 40]);
910 }
911
912 #[test]
913 fn test_volatility_kernel_ewma() {
914 let x = array![[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]];
915
916 let kernel = VolatilityKernel::new(50, VolatilityModel::EWMA).decay_factor(0.94);
917 let fitted = kernel.fit(&x, &()).expect("operation should succeed");
918 let features = fitted.transform(&x).expect("operation should succeed");
919
920 assert_eq!(features.shape(), &[2, 50]);
921 }
922
923 #[test]
924 fn test_volatility_models() {
925 let x = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
926
927 let models = vec![
928 VolatilityModel::Historical,
929 VolatilityModel::EWMA,
930 VolatilityModel::GARCH,
931 VolatilityModel::Realized,
932 ];
933
934 for model in models {
935 let kernel = VolatilityKernel::new(30, model);
936 let fitted = kernel.fit(&x, &()).expect("operation should succeed");
937 let features = fitted.transform(&x).expect("operation should succeed");
938 assert_eq!(features.shape(), &[2, 30]);
939 }
940 }
941
942 #[test]
943 fn test_econometric_kernel_basic() {
944 let x = array![[1.0, 2.0, 3.0, 4.0, 5.0], [6.0, 7.0, 8.0, 9.0, 10.0]];
945
946 let kernel = EconometricKernel::new(50, 3);
947 let fitted = kernel.fit(&x, &()).expect("operation should succeed");
948 let features = fitted.transform(&x).expect("operation should succeed");
949
950 assert_eq!(features.shape(), &[2, 50]);
951 }
952
953 #[test]
954 fn test_econometric_kernel_differences() {
955 let x = array![[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]];
956
957 let kernel = EconometricKernel::new(40, 2).include_differences(true);
958 let fitted = kernel.fit(&x, &()).expect("operation should succeed");
959 let features = fitted.transform(&x).expect("operation should succeed");
960
961 assert_eq!(features.shape(), &[2, 40]);
962 }
963
964 #[test]
965 fn test_portfolio_kernel_basic() {
966 let x = array![[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]];
967
968 let kernel = PortfolioKernel::new(50, 4);
969 let fitted = kernel.fit(&x, &()).expect("operation should succeed");
970 let features = fitted.transform(&x).expect("operation should succeed");
971
972 assert_eq!(features.shape(), &[2, 50]);
973 }
974
975 #[test]
976 fn test_portfolio_kernel_risk_free_rate() {
977 let x = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
978
979 let kernel = PortfolioKernel::new(40, 3).risk_free_rate(0.03);
980 let fitted = kernel.fit(&x, &()).expect("operation should succeed");
981 let features = fitted.transform(&x).expect("operation should succeed");
982
983 assert_eq!(features.shape(), &[2, 40]);
984 }
985
986 #[test]
987 fn test_risk_kernel_basic() {
988 let x = array![[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]];
989
990 let kernel = RiskKernel::new(50, 0.95);
991 let fitted = kernel.fit(&x, &()).expect("operation should succeed");
992 let features = fitted.transform(&x).expect("operation should succeed");
993
994 assert_eq!(features.shape(), &[2, 50]);
995 }
996
997 #[test]
998 fn test_risk_kernel_tail_risk() {
999 let x = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
1000
1001 let kernel = RiskKernel::new(40, 0.99).include_tail_risk(true);
1002 let fitted = kernel.fit(&x, &()).expect("operation should succeed");
1003 let features = fitted.transform(&x).expect("operation should succeed");
1004
1005 assert_eq!(features.shape(), &[2, 40]);
1006 }
1007
1008 #[test]
1009 fn test_empty_input_error() {
1010 let x_empty: Array2<Float> = Array2::zeros((0, 3));
1011
1012 let kernel = FinancialKernel::new(50, 10);
1013 assert!(kernel.fit(&x_empty, &()).is_err());
1014
1015 let kernel2 = VolatilityKernel::new(50, VolatilityModel::EWMA);
1016 assert!(kernel2.fit(&x_empty, &()).is_err());
1017 }
1018}