(target_positions: &[(NaiveDateTime, f64)])
| 32 | //! let hurdle = deflated_sharpe_ratio(0.1, &[0.05, 50.0], 500, 0.0, 3.0, true, true)?; |
| 33 | //! assert!((hurdle - 0.1138).abs() < 1e-4); |
| 34 | //! |
| 35 | //! let min_trl = minimum_track_record_length(0.1, 0.0, 0.0, 3.0, 0.05)?; |
| 36 | //! assert!((min_trl - 272.9).abs() < 0.1); |
| 37 | //! # Ok(()) |
| 38 | //! # } |
| 39 | //! ``` |
| 40 | |
| 41 | use crate::util::stats; |
| 42 | use crate::util::InputError; |
| 43 | use chrono::NaiveDateTime; |
| 44 | use statrs::distribution::{ContinuousCDF, Normal}; |
| 45 | |
| 46 | const EULER_GAMMA: f64 = 0.5772156649015329_f64; |
| 47 | |
| 48 | /// Returns the timestamps at which a position was closed or reversed (AFML Snippet 14.1). |
| 49 | /// |
| 50 | /// A flattening is a bar where the position goes from non-zero to zero; a flip is a bar |
| 51 | /// where the position changes sign. The result is sorted, deduplicated, and always ends with |
| 52 | /// the last timestamp of the input (so an open position is treated as closed there). An |
| 53 | /// empty input gives an empty result. |
| 54 | /// |
| 55 | /// `target_positions` is `(timestamp, position)` pairs in increasing time. |
| 56 | pub fn timing_of_flattening_and_flips( |
| 57 | target_positions: &[(NaiveDateTime, f64)], |
| 58 | ) -> Vec<NaiveDateTime> { |
| 59 | let mut flattenings = Vec::new(); |
| 60 | let mut flips = Vec::new(); |
| 61 | for i in 1..target_positions.len() { |
| 62 | let prev = target_positions[i - 1].1; |
| 63 | let curr = target_positions[i].1; |
| 64 | if curr == 0.0 && prev != 0.0 { |
| 65 | flattenings.push(target_positions[i].0); |
| 66 | } |
| 67 | let mult = curr * prev; |
| 68 | if mult < 0.0 { |
| 69 | flips.push(target_positions[i].0); |
| 70 | } |
| 71 | } |
| 72 | let mut res = flattenings; |
| 73 | res.extend(flips); |
| 74 | res.sort(); |
| 75 | res.dedup(); |
| 76 | if let Some(last) = target_positions.last() |
| 77 | && !res.contains(&last.0) |
no test coverage detected