(
df: &DataFrame,
keep_last: bool,
)
| 159 | } |
| 160 | |
| 161 | pub fn clean_ohlcv_df( |
| 162 | df: &DataFrame, |
| 163 | keep_last: bool, |
| 164 | ) -> Result<(DataFrame, DataQualityReport), String> { |
| 165 | require_ohlcv_columns(df)?; |
| 166 | |
| 167 | if df.height() == 0 { |
| 168 | let empty = sort_ohlcv_df(df)?; |
| 169 | let report = DataQualityReport { |
| 170 | row_count: 0, |
| 171 | symbol_count: 0, |
| 172 | duplicate_key_count: 0, |
| 173 | gap_interval_count: 0, |
| 174 | ts_min: None, |
| 175 | ts_max: None, |
| 176 | rows_removed_by_deduplication: 0, |
| 177 | }; |
| 178 | return Ok((empty, report)); |
| 179 | } |
| 180 | |
| 181 | let sorted = sort_ohlcv_df(df)?; |
| 182 | let before = sorted.height(); |
| 183 | |
| 184 | let cleaned = sorted |
| 185 | .unique_stable( |
| 186 | Some(&["symbol".to_string(), "ts_us".to_string()]), |
| 187 | if keep_last { UniqueKeepStrategy::Last } else { UniqueKeepStrategy::First }, |
| 188 | None, |
| 189 | ) |
| 190 | .map_err(|e| format!("polars unique failed: {e}"))?; |
| 191 | |
| 192 | let removed = before.saturating_sub(cleaned.height()); |
| 193 | let mut report = quality_report_from_sorted_df(&cleaned, removed)?; |
| 194 | report.duplicate_key_count = 0; |
| 195 | |
| 196 | Ok((cleaned, report)) |
| 197 | } |
| 198 | |
| 199 | pub fn align_calendar_df(df: &DataFrame, interval_seconds: i64) -> Result<DataFrame, String> { |
| 200 | if interval_seconds <= 0 { |
no test coverage detected