Skip to main content

hypixel/util/
market.rs

1use std::collections::HashMap;
2
3use crate::models::skyblock::{Bazaar, BazaarProduct, SkyBlockAuction};
4
5/// A concise view of a single bazaar product's order book.
6#[derive(Debug, Clone, Copy, PartialEq)]
7pub struct BazaarSpread {
8    /// Highest price a buy order is currently offering (instant-sell price).
9    pub buy_order_price: f64,
10    /// Lowest price a sell order is currently asking (instant-buy price).
11    pub sell_order_price: f64,
12}
13
14impl BazaarSpread {
15    /// Absolute difference between the instant-buy and instant-sell prices.
16    pub fn margin(&self) -> f64 {
17        (self.sell_order_price - self.buy_order_price).abs()
18    }
19}
20
21/// Compute the top-of-book spread for a bazaar product, if both sides have orders.
22///
23/// On the bazaar, `buy_summary` holds outstanding buy orders and `sell_summary`
24/// holds outstanding sell offers; the best of each defines the spread.
25pub fn bazaar_spread(product: &BazaarProduct) -> Option<BazaarSpread> {
26    let sell_order_price = product.sell_summary.first()?.price_per_unit;
27    let buy_order_price = product.buy_summary.first()?.price_per_unit;
28    Some(BazaarSpread {
29        buy_order_price,
30        sell_order_price,
31    })
32}
33
34/// Compute spreads for every product in a bazaar snapshot.
35pub fn bazaar_spreads(bazaar: &Bazaar) -> HashMap<String, BazaarSpread> {
36    bazaar
37        .products
38        .iter()
39        .filter_map(|(id, product)| bazaar_spread(product).map(|s| (id.clone(), s)))
40        .collect()
41}
42
43/// Aggregate the lowest Buy-It-Now price per item name across a set of auctions.
44///
45/// Only active BIN auctions are considered. The returned map is keyed by the
46/// auction `item_name`.
47pub fn lowest_bin(auctions: &[SkyBlockAuction]) -> HashMap<String, i64> {
48    let mut lowest: HashMap<String, i64> = HashMap::new();
49    for auction in auctions {
50        if !auction.bin {
51            continue;
52        }
53        lowest
54            .entry(auction.item_name.clone())
55            .and_modify(|price| {
56                if auction.starting_bid < *price {
57                    *price = auction.starting_bid;
58                }
59            })
60            .or_insert(auction.starting_bid);
61    }
62    lowest
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use crate::models::skyblock::{BazaarOrder, BazaarProduct};
69
70    fn product(sell: f64, buy: f64) -> BazaarProduct {
71        BazaarProduct {
72            product_id: "X".into(),
73            sell_summary: vec![BazaarOrder {
74                amount: 1,
75                price_per_unit: sell,
76                orders: 1,
77            }],
78            buy_summary: vec![BazaarOrder {
79                amount: 1,
80                price_per_unit: buy,
81                orders: 1,
82            }],
83            quick_status: None,
84        }
85    }
86
87    #[test]
88    fn computes_spread_margin() {
89        let spread = bazaar_spread(&product(5.0, 4.2)).unwrap();
90        assert_eq!(spread.sell_order_price, 5.0);
91        assert_eq!(spread.buy_order_price, 4.2);
92        assert!((spread.margin() - 0.8).abs() < 1e-9);
93    }
94}