| 150 | } |
| 151 | |
| 152 | pub fn update( |
| 153 | &mut self, |
| 154 | mut buy_volume: f64, |
| 155 | mut sell_volume: f64, |
| 156 | ) -> Result<Option<f64>, StreamingHpcError> { |
| 157 | validate_non_negative_finite("buy_volume", buy_volume)?; |
| 158 | validate_non_negative_finite("sell_volume", sell_volume)?; |
| 159 | let mut remaining = buy_volume + sell_volume; |
| 160 | if remaining == 0.0 { |
| 161 | return Ok(self.current()); |
| 162 | } |
| 163 | while remaining > 0.0 { |
| 164 | let capacity = self.cfg.bucket_volume - self.current_bucket_volume; |
| 165 | let take = remaining.min(capacity); |
| 166 | if take <= 0.0 { |
| 167 | break; |
| 168 | } |
| 169 | // Preserve buy/sell ratio within partial fill. |
| 170 | let ratio_buy = if remaining > 0.0 { buy_volume / remaining } else { 0.5 }; |
| 171 | let used_buy = take * ratio_buy; |
| 172 | let used_sell = take - used_buy; |
| 173 | |
| 174 | self.current_bucket_volume += take; |
| 175 | self.current_bucket_abs_imbalance += (used_buy - used_sell).abs(); |
| 176 | |
| 177 | buy_volume -= used_buy; |
| 178 | sell_volume -= used_sell; |
| 179 | remaining -= take; |
| 180 | |
| 181 | if self.current_bucket_volume >= self.cfg.bucket_volume - 1e-12 { |
| 182 | let toxicity = self.current_bucket_abs_imbalance / self.cfg.bucket_volume; |
| 183 | self.window.push_back(toxicity); |
| 184 | self.window_sum += toxicity; |
| 185 | if self.window.len() > self.cfg.support_buckets { |
| 186 | if let Some(expired) = self.window.pop_front() { |
| 187 | self.window_sum -= expired; |
| 188 | } |
| 189 | } |
| 190 | self.current_bucket_volume = 0.0; |
| 191 | self.current_bucket_abs_imbalance = 0.0; |
| 192 | } |
| 193 | } |
| 194 | Ok(self.current()) |
| 195 | } |
| 196 | |
| 197 | pub fn current(&self) -> Option<f64> { |
| 198 | if self.window.len() < self.cfg.support_buckets { |