Skip to main content

scope/market/
analytics.rs

1//! DEX analytics utilities.
2//!
3//! Builds synthetic order books from DexScreener liquidity data for
4//! Ethereum and Solana DEX venues.
5
6use super::types::{OrderBook, OrderBookLevel};
7
8/// Builds a synthetic order book from DEX analytics (used for Ethereum/Solana venues).
9pub fn order_book_from_analytics(
10    _chain: &str,
11    pair: &crate::chains::DexPair,
12    symbol: &str,
13) -> OrderBook {
14    let price = pair.price_usd;
15    let liquidity = pair.liquidity_usd;
16    // Synthetic bid/ask spread ±0.1% around mid
17    let bid_price = price * 0.999;
18    let ask_price = price * 1.001;
19    let half_liq = liquidity / 2.0;
20    let bid_qty = if bid_price > 0.0 {
21        half_liq / bid_price
22    } else {
23        0.0
24    };
25    let ask_qty = if ask_price > 0.0 {
26        half_liq / ask_price
27    } else {
28        0.0
29    };
30
31    OrderBook {
32        pair: format!("{}/USDT", symbol),
33        bids: vec![OrderBookLevel {
34            price: bid_price,
35            quantity: bid_qty,
36        }],
37        asks: vec![OrderBookLevel {
38            price: ask_price,
39            quantity: ask_qty,
40        }],
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47    use crate::chains::DexPair;
48
49    fn make_pair(price: f64, liquidity: f64) -> DexPair {
50        DexPair {
51            dex_name: "TestDex".into(),
52            pair_address: "0x0".into(),
53            base_token: "USDC".into(),
54            quote_token: "WETH".into(),
55            price_usd: price,
56            volume_24h: 0.0,
57            liquidity_usd: liquidity,
58            price_change_24h: 0.0,
59            buys_24h: 0,
60            sells_24h: 0,
61            buys_6h: 0,
62            sells_6h: 0,
63            buys_1h: 0,
64            sells_1h: 0,
65            pair_created_at: None,
66            url: None,
67        }
68    }
69
70    #[test]
71    fn test_order_book_from_analytics_normal() {
72        let pair = make_pair(1.0, 100_000.0);
73        let book = order_book_from_analytics("ethereum", &pair, "USDC");
74        assert_eq!(book.pair, "USDC/USDT");
75        assert_eq!(book.bids.len(), 1);
76        assert_eq!(book.asks.len(), 1);
77        assert!(book.bids[0].price > 0.0);
78        assert!(book.asks[0].price > 0.0);
79        assert!(book.bids[0].quantity > 0.0);
80        assert!(book.asks[0].quantity > 0.0);
81    }
82
83    #[test]
84    fn test_order_book_from_analytics_zero_price() {
85        // price_usd = 0.0 -> bid_price = 0 and ask_price = 0
86        // -> both qty branches hit the else { 0.0 }
87        let pair = make_pair(0.0, 100_000.0);
88        let book = order_book_from_analytics("ethereum", &pair, "TOKEN");
89        assert_eq!(book.bids[0].quantity, 0.0);
90        assert_eq!(book.asks[0].quantity, 0.0);
91    }
92
93    #[test]
94    fn test_order_book_from_analytics_zero_liquidity() {
95        let pair = make_pair(1.0, 0.0);
96        let book = order_book_from_analytics("solana", &pair, "SOL");
97        assert_eq!(book.bids[0].quantity, 0.0);
98        assert_eq!(book.asks[0].quantity, 0.0);
99    }
100}