hypixel-sdk 0.1.0

Async client for the Hypixel Public API with typed models and SkyBlock helpers
Documentation
use std::collections::HashMap;

use crate::models::skyblock::{Bazaar, BazaarProduct, SkyBlockAuction};

/// A concise view of a single bazaar product's order book.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BazaarSpread {
    /// Highest price a buy order is currently offering (instant-sell price).
    pub buy_order_price: f64,
    /// Lowest price a sell order is currently asking (instant-buy price).
    pub sell_order_price: f64,
}

impl BazaarSpread {
    /// Absolute difference between the instant-buy and instant-sell prices.
    pub fn margin(&self) -> f64 {
        (self.sell_order_price - self.buy_order_price).abs()
    }
}

/// Compute the top-of-book spread for a bazaar product, if both sides have orders.
///
/// On the bazaar, `buy_summary` holds outstanding buy orders and `sell_summary`
/// holds outstanding sell offers; the best of each defines the spread.
pub fn bazaar_spread(product: &BazaarProduct) -> Option<BazaarSpread> {
    let sell_order_price = product.sell_summary.first()?.price_per_unit;
    let buy_order_price = product.buy_summary.first()?.price_per_unit;
    Some(BazaarSpread {
        buy_order_price,
        sell_order_price,
    })
}

/// Compute spreads for every product in a bazaar snapshot.
pub fn bazaar_spreads(bazaar: &Bazaar) -> HashMap<String, BazaarSpread> {
    bazaar
        .products
        .iter()
        .filter_map(|(id, product)| bazaar_spread(product).map(|s| (id.clone(), s)))
        .collect()
}

/// Aggregate the lowest Buy-It-Now price per item name across a set of auctions.
///
/// Only active BIN auctions are considered. The returned map is keyed by the
/// auction `item_name`.
pub fn lowest_bin(auctions: &[SkyBlockAuction]) -> HashMap<String, i64> {
    let mut lowest: HashMap<String, i64> = HashMap::new();
    for auction in auctions {
        if !auction.bin {
            continue;
        }
        lowest
            .entry(auction.item_name.clone())
            .and_modify(|price| {
                if auction.starting_bid < *price {
                    *price = auction.starting_bid;
                }
            })
            .or_insert(auction.starting_bid);
    }
    lowest
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::models::skyblock::{BazaarOrder, BazaarProduct};

    fn product(sell: f64, buy: f64) -> BazaarProduct {
        BazaarProduct {
            product_id: "X".into(),
            sell_summary: vec![BazaarOrder {
                amount: 1,
                price_per_unit: sell,
                orders: 1,
            }],
            buy_summary: vec![BazaarOrder {
                amount: 1,
                price_per_unit: buy,
                orders: 1,
            }],
            quick_status: None,
        }
    }

    #[test]
    fn computes_spread_margin() {
        let spread = bazaar_spread(&product(5.0, 4.2)).unwrap();
        assert_eq!(spread.sell_order_price, 5.0);
        assert_eq!(spread.buy_order_price, 4.2);
        assert!((spread.margin() - 0.8).abs() < 1e-9);
    }
}