瀏覽代碼

fix: time accuracy on trades aligning with depth on heatmap chart, +feat: dynamic height adj. rectangles to represent depth

Berke 1 年之前
父節點
當前提交
ff2f11dd75
共有 1 個文件被更改,包括 148 次插入129 次删除
  1. 148 129
      src/charts/heatmap.rs

+ 148 - 129
src/charts/heatmap.rs

@@ -75,12 +75,12 @@ impl HeatmapChart {
         let grouped_depth = GroupedDepth {
             time: depth.time,
             bids: depth.bids.iter().fold(BTreeMap::new(), |mut acc, order| {
-                let rounded_price = (order.price * (1.0 / self.tick_size)).round() as i64;
+                let rounded_price = ((order.price * (1.0 / self.tick_size)).floor()) as i64;
                 *acc.entry(rounded_price).or_insert(0.0) += order.qty;
                 acc
             }),
             asks: depth.asks.iter().fold(BTreeMap::new(), |mut acc, order| {
-                let rounded_price = (order.price * (1.0 / self.tick_size)).round() as i64;
+                let rounded_price = ((order.price * (1.0 / self.tick_size)).ceil()) as i64;
                 *acc.entry(rounded_price).or_insert(0.0) += order.qty;
                 acc
             }),
@@ -89,7 +89,7 @@ impl HeatmapChart {
         let grouped_trades: Vec<GroupedTrade> = trades_buffer
             .iter()
             .map(|trade| GroupedTrade {
-                time: trade.time,
+                time: depth_update,
                 is_sell: trade.is_sell,
                 price: (trade.price * (1.0 / self.tick_size)).round() as i64,
                 qty: trade.qty,
@@ -432,6 +432,113 @@ impl canvas::Program<Message> for HeatmapChart {
         
             let mut max_depth_qty: f32 = 0.0;
 
+            let mut bar_height: f32 = 1.0;
+
+            // current orderbook as bars
+            if let Some((&latest_timestamp, (grouped_depth, _))) = self.data_points.iter().last() {
+                let x_position = ((latest_timestamp - earliest) as f32 / (latest - earliest) as f32) * bounds.width;
+
+                if x_position.is_nan() {
+                    return;
+                }
+
+                let latest_bids: Vec<(f32, f32)> = grouped_depth.bids.iter()
+                    .map(|(&price_i64, &qty)| (self.price_to_float(price_i64), qty))
+                    .filter(|&(price, _)| price >= lowest)
+                    .collect();
+
+                let latest_asks: Vec<(f32, f32)> = grouped_depth.asks.iter()
+                    .map(|(&price_i64, &qty)| (self.price_to_float(price_i64), qty))
+                    .filter(|&(price, _)| price <= highest)
+                    .collect();
+
+                let highest_ask_visible = latest_asks.last()
+                    .map(|(price, _)| *price)
+                    .unwrap_or(highest);
+                let highest_ask_y_pos = heatmap_area_height - ((highest_ask_visible - lowest) / y_range * heatmap_area_height);
+
+                let lowest_bid_visible = latest_bids.first()
+                    .map(|(price, _)| *price)
+                    .unwrap_or(lowest); 
+                let lowest_bid_y_pos = heatmap_area_height - ((lowest_bid_visible - lowest) / y_range * heatmap_area_height);
+                
+                bar_height = (highest_ask_y_pos - lowest_bid_y_pos) / (latest_bids.len() + latest_asks.len()) as f32;
+
+                let max_qty = latest_bids.iter()
+                    .map(|(_, qty)| qty)
+                    .chain(latest_asks.iter().map(|(_, qty)| qty))
+                    .fold(f32::MIN, |price: f32, qty: &f32| f32::max(price, *qty));
+
+                for (price, qty) in &latest_bids {     
+                    let y_position = heatmap_area_height - ((price - lowest) / y_range * heatmap_area_height);
+                
+                    let bar_width = (qty / max_qty) * depth_area_width;
+
+                    frame.fill_rectangle(
+                        Point::new(x_position, y_position), 
+                        Size::new(bar_width, bar_height), 
+                        Color::from_rgba8(0, 144, 144, 0.5)
+                    );
+                }
+                
+                for (price, qty) in &latest_asks {
+                    let y_position = heatmap_area_height - ((price - lowest) / y_range * heatmap_area_height);
+                
+                    let bar_width = (qty / max_qty) * depth_area_width;
+
+                    frame.fill_rectangle(
+                        Point::new(x_position, y_position), 
+                        Size::new(bar_width, bar_height), 
+                        Color::from_rgba8(192, 0, 192, 0.5)
+                    );
+                }
+                
+                // the white bar to seperate the heatmap area
+                frame.fill_rectangle(
+                    Point::new(x_position, 0.0), 
+                    Size::new(1.0, bounds.height), 
+                    Color::from_rgba8(100, 100, 100, 0.1)
+                );
+
+                // max bid/ask quantity text
+                let text_size = 9.0;
+                let text_content = format!("{max_qty:.2}");
+                let text_position = Point::new(x_position + depth_area_width, 0.0);
+                frame.fill_text(canvas::Text {
+                    content: text_content,
+                    position: text_position,
+                    size: iced::Pixels(text_size),
+                    color: Color::from_rgba8(81, 81, 81, 1.0),
+                    ..canvas::Text::default()
+                });
+
+                let text_content = format!("{max_volume:.2}");
+                if x_position > bounds.width {      
+                    let text_width = (text_content.len() as f32 * text_size) / 1.5;
+
+                    let text_position = Point::new(bounds.width - text_width, bounds.height - volume_area_height);
+                    
+                    frame.fill_text(canvas::Text {
+                        content: text_content,
+                        position: text_position,
+                        size: iced::Pixels(text_size),
+                        color: Color::from_rgba8(81, 81, 81, 1.0),
+                        ..canvas::Text::default()
+                    });
+
+                } else {
+                    let text_position = Point::new(x_position + 5.0, bounds.height - volume_area_height);
+
+                    frame.fill_text(canvas::Text {
+                        content: text_content,
+                        position: text_position,
+                        size: iced::Pixels(text_size),
+                        color: Color::from_rgba8(81, 81, 81, 1.0),
+                        ..canvas::Text::default()
+                    });
+                }
+            };
+
             if self.data_points.len() > 1 {
                 for (_, (depth, trades)) in self.data_points.range(earliest..=latest) {
                     let mut buy_volume: f32 = 0.0;
@@ -472,47 +579,15 @@ impl canvas::Program<Message> for HeatmapChart {
                 let mut prev_ask_price: Option<f32> = None;
                 let mut prev_ask_qty: Option<f32> = None;
 
-                let mut prev_x_position: Option<f64> = None;
+                let mut prev_x_position: Option<f32> = None;
 
                 for (time, (depth, trades)) in self.data_points.range(earliest..=latest) {
-                    let x_position = ((time - earliest) as f64 / (latest - earliest) as f64) * bounds.width as f64;
+                    let x_position = ((time - earliest) as f32 / (latest - earliest) as f32) * bounds.width;
 
                     if x_position.is_nan() {
                         continue;
                     }
 
-                    let mut buy_volume: f32 = 0.0;
-                    let mut sell_volume: f32 = 0.0;
-
-                    for trade in trades.iter() {
-                        if trade.is_sell {
-                            sell_volume += trade.qty;
-                        } else {
-                            buy_volume += trade.qty;
-                        }
-
-                        let price = self.price_to_float(trade.price);
-
-                        if trade.qty * price > self.size_filter {
-                            let x_position = ((time - earliest) as f64 / (latest - earliest) as f64) * bounds.width as f64;
-                            let y_position = heatmap_area_height - ((price - lowest) / y_range * heatmap_area_height);
-
-                            let color = if trade.is_sell {
-                                Color::from_rgba8(192, 80, 77, 1.0)
-                            } else {
-                                Color::from_rgba8(81, 205, 160, 1.0)
-                            };
-
-                            let radius: f32 = match max_trade_qty == min_trade_qty {
-                                true => 1.0,
-                                false => 1.0 + (trade.qty - min_trade_qty) * (35.0 - 1.0) / (max_trade_qty - min_trade_qty),
-                            };
-
-                            let circle = Path::circle(Point::new(x_position as f32, y_position), radius);
-                            frame.fill(&circle, color);
-                        }
-                    }
-
                     for (&price_i64, &qty) in depth.bids.iter() {
                         let price = self.price_to_float(price_i64);
 
@@ -524,7 +599,7 @@ impl canvas::Program<Message> for HeatmapChart {
                                 if prev_price != price || prev_qty != qty {
                                     frame.fill_rectangle(
                                         Point::new(prev_x as f32,y_position - 0.5),
-                                        Size::new((x_position - prev_x) as f32, 1.0),
+                                        Size::new((x_position - prev_x) as f32, bar_height),
                                         Color::from_rgba8(0, 144, 144, color_alpha)
                                     );
                                 }
@@ -545,7 +620,7 @@ impl canvas::Program<Message> for HeatmapChart {
                                 if prev_price != price || prev_qty != qty {
                                     frame.fill_rectangle(
                                         Point::new(prev_x as f32, y_position - 0.5), 
-                                        Size::new((x_position - prev_x) as f32, 1.0), 
+                                        Size::new((x_position - prev_x) as f32, bar_height), 
                                         Color::from_rgba8(192, 0, 192, color_alpha)
                                     );
                                 }
@@ -557,6 +632,40 @@ impl canvas::Program<Message> for HeatmapChart {
 
                     prev_x_position = Some(x_position);
 
+                    let mut buy_volume: f32 = 0.0;
+                    let mut sell_volume: f32 = 0.0;
+
+                    for trade in trades.iter() {
+                        if trade.is_sell {
+                            sell_volume += trade.qty;
+                        } else {
+                            buy_volume += trade.qty;
+                        }
+
+                        let price = self.price_to_float(trade.price);
+
+                        if trade.qty * price > self.size_filter {
+                            let x_position = (((time - 100) - earliest) as f32 / (latest - earliest) as f32) * bounds.width;
+                            let y_position = heatmap_area_height - ((price - lowest) / y_range * heatmap_area_height);
+
+                            let color = if trade.is_sell {
+                                Color::from_rgba8(192, 80, 77, 1.0)
+                            } else {
+                                Color::from_rgba8(81, 205, 160, 1.0)
+                            };
+
+                            let radius: f32 = match max_trade_qty == min_trade_qty {
+                                true => 1.0,
+                                false => 1.0 + (trade.qty - min_trade_qty) * (35.0 - 1.0) / (max_trade_qty - min_trade_qty),
+                            };
+
+                            frame.fill(
+                                &Path::circle(Point::new(x_position, y_position), radius), 
+                                color
+                            );
+                        }
+                    }
+
                     if max_volume > 0.0 {
                         let buy_bar_height = (buy_volume / max_volume) * volume_area_height;
                         frame.fill_rectangle(
@@ -574,96 +683,6 @@ impl canvas::Program<Message> for HeatmapChart {
                     }
                 };
             };
-        
-            // current orderbook as bars
-            if let Some((&latest_timestamp, (grouped_depth, _))) = self.data_points.iter().last() {
-                let latest_bids: Vec<(f32, f32)> = grouped_depth.bids.iter()
-                    .map(|(&price_i64, &qty)| (self.price_to_float(price_i64), qty))
-                    .filter(|&(price, _)| price >= lowest)
-                    .collect();
-
-                let latest_asks: Vec<(f32, f32)> = grouped_depth.asks.iter()
-                    .map(|(&price_i64, &qty)| (self.price_to_float(price_i64), qty))
-                    .filter(|&(price, _)| price <= highest)
-                    .collect();
-
-                let max_qty = latest_bids.iter().map(|(_, qty)| qty).chain(latest_asks.iter().map(|(_, qty)| qty)).fold(f32::MIN, |arg0: f32, other: &f32| f32::max(arg0, *other));
-
-                let x_position = ((latest_timestamp - earliest) as f32 / (latest - earliest) as f32) * bounds.width;
-
-                if x_position.is_nan() {
-                    return;
-                }
-
-                for (price, qty) in &latest_bids {     
-                    let y_position = heatmap_area_height - ((price - lowest) / y_range * heatmap_area_height);
-                
-                    let bar_width = (qty / max_qty) * depth_area_width;
-
-                    frame.fill_rectangle(
-                        Point::new(x_position, y_position), 
-                        Size::new(bar_width, 1.0), 
-                        Color::from_rgba8(0, 144, 144, 0.5)
-                    );
-                }
-                
-                for (price, qty) in &latest_asks {
-                    let y_position = heatmap_area_height - ((price - lowest) / y_range * heatmap_area_height);
-                
-                    let bar_width = (qty / max_qty) * depth_area_width;
-
-                    frame.fill_rectangle(
-                        Point::new(x_position, y_position), 
-                        Size::new(bar_width, 1.0), 
-                        Color::from_rgba8(192, 0, 192, 0.5)
-                    );
-                }
-                
-                // the white bar to seperate the heatmap area
-                frame.fill_rectangle(
-                    Point::new(x_position, 0.0), 
-                    Size::new(1.0, bounds.height), 
-                    Color::from_rgba8(100, 100, 100, 0.1)
-                );
-
-                // max bid/ask quantity text
-                let text_size = 9.0;
-                let text_content = format!("{max_qty:.2}");
-                let text_position = Point::new(x_position + depth_area_width, 0.0);
-                frame.fill_text(canvas::Text {
-                    content: text_content,
-                    position: text_position,
-                    size: iced::Pixels(text_size),
-                    color: Color::from_rgba8(81, 81, 81, 1.0),
-                    ..canvas::Text::default()
-                });
-
-                let text_content = format!("{max_volume:.2}");
-                if x_position > bounds.width {      
-                    let text_width = (text_content.len() as f32 * text_size) / 1.5;
-
-                    let text_position = Point::new(bounds.width - text_width, bounds.height - volume_area_height);
-                    
-                    frame.fill_text(canvas::Text {
-                        content: text_content,
-                        position: text_position,
-                        size: iced::Pixels(text_size),
-                        color: Color::from_rgba8(81, 81, 81, 1.0),
-                        ..canvas::Text::default()
-                    });
-
-                } else {
-                    let text_position = Point::new(x_position + 5.0, bounds.height - volume_area_height);
-
-                    frame.fill_text(canvas::Text {
-                        content: text_content,
-                        position: text_position,
-                        size: iced::Pixels(text_size),
-                        color: Color::from_rgba8(81, 81, 81, 1.0),
-                        ..canvas::Text::default()
-                    });
-                }
-            };
         });
 
         if chart.crosshair {