nautilus_model/defi/
amm.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Posei Systems Pty Ltd. All rights reserved.
3//  https://poseitrader.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Data types specific to automated-market-maker (AMM) protocols.
17
18use std::sync::Arc;
19
20use alloy_primitives::Address;
21use nautilus_core::UnixNanos;
22use serde::{Deserialize, Serialize};
23
24use crate::{
25    data::HasTsInit,
26    defi::{chain::SharedChain, dex::Dex, token::Token},
27    identifiers::InstrumentId,
28};
29
30/// Represents a liquidity pool in a decentralized exchange.
31#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
32pub struct Pool {
33    /// The blockchain network where this pool exists.
34    pub chain: SharedChain,
35    /// The decentralized exchange protocol that created and manages this pool.
36    pub dex: Dex,
37    /// The blockchain address of the pool smart contract.
38    pub address: Address,
39    /// The block number when this pool was created on the blockchain.
40    pub creation_block: u64,
41    /// The first token in the trading pair.
42    pub token0: Token,
43    /// The second token in the trading pair.
44    pub token1: Token,
45    /// The trading fee tier used by the pool expressed in hundred-thousandths
46    /// (1e-6) of one unit – identical to Uniswap-V3’s fee representation.
47    ///
48    /// Examples:
49    /// • `500`   →  0.05 %  (5 bps)
50    /// • `3_000` →  0.30 %  (30 bps)
51    /// • `10_000`→  1.00 %
52    pub fee: u32,
53    /// The minimum tick spacing for positions in concentrated liquidity AMMs.
54    pub tick_spacing: u32,
55    /// UNIX timestamp (nanoseconds) when the instance was created.
56    pub ts_init: UnixNanos,
57}
58
59/// A thread-safe shared pointer to a `Pool`, enabling efficient reuse across multiple components.
60pub type SharedPool = Arc<Pool>;
61
62impl Pool {
63    /// Creates a new [`Pool`] instance with the specified properties.
64    #[must_use]
65    #[allow(clippy::too_many_arguments)]
66    pub fn new(
67        chain: SharedChain,
68        dex: Dex,
69        address: Address,
70        creation_block: u64,
71        token0: Token,
72        token1: Token,
73        fee: u32,
74        tick_spacing: u32,
75        ts_init: UnixNanos,
76    ) -> Self {
77        Self {
78            chain,
79            dex,
80            address,
81            creation_block,
82            token0,
83            token1,
84            fee,
85            tick_spacing,
86            ts_init,
87        }
88    }
89
90    /// Returns the ticker symbol for this pool as a formatted string.
91    #[must_use]
92    pub fn ticker(&self) -> String {
93        format!("{}/{}", self.token0.symbol, self.token1.symbol)
94    }
95
96    /// Returns the instrument ID for this pool.
97    #[must_use]
98    pub fn instrument_id(&self) -> InstrumentId {
99        let id_string = format!("{}.{}", self.ticker(), self.dex.name);
100        InstrumentId::from(id_string.as_str())
101    }
102}
103
104impl std::fmt::Display for Pool {
105    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106        write!(
107            f,
108            "Pool(ticker={}, dex={}, fee={}, address={})",
109            self.ticker(),
110            self.dex.name,
111            self.fee,
112            self.address
113        )
114    }
115}
116
117impl HasTsInit for Pool {
118    fn ts_init(&self) -> UnixNanos {
119        self.ts_init
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use std::sync::Arc;
126
127    use rstest::rstest;
128
129    use super::*;
130    use crate::defi::{
131        chain::chains,
132        dex::{AmmType, Dex},
133        token::Token,
134    };
135
136    #[rstest]
137    fn test_pool_constructor_and_methods() {
138        let chain = Arc::new(chains::ETHEREUM.clone());
139        let dex = Dex::new(
140            chains::ETHEREUM.clone(),
141            "UniswapV3",
142            "0x1F98431c8aD98523631AE4a59f267346ea31F984",
143            AmmType::CLAMM,
144            "PoolCreated(address,address,uint24,int24,address)",
145            "Swap(address,address,int256,int256,uint160,uint128,int24)",
146            "Mint(address,address,int24,int24,uint128,uint256,uint256)",
147            "Burn(address,int24,int24,uint128,uint256,uint256)",
148        );
149
150        let token0 = Token::new(
151            chain.clone(),
152            "0xA0b86a33E6441b936662bb6B5d1F8Fb0E2b57A5D"
153                .parse()
154                .unwrap(),
155            "Wrapped Ether".to_string(),
156            "WETH".to_string(),
157            18,
158        );
159
160        let token1 = Token::new(
161            chain.clone(),
162            "0xdAC17F958D2ee523a2206206994597C13D831ec7"
163                .parse()
164                .unwrap(),
165            "Tether USD".to_string(),
166            "USDT".to_string(),
167            6,
168        );
169
170        let pool_address = "0x11b815efB8f581194ae79006d24E0d814B7697F6"
171            .parse()
172            .unwrap();
173        let ts_init = UnixNanos::from(1_234_567_890_000_000_000u64);
174
175        let pool = Pool::new(
176            chain.clone(),
177            dex,
178            pool_address,
179            12345678,
180            token0,
181            token1,
182            3000,
183            60,
184            ts_init,
185        );
186
187        assert_eq!(pool.chain.chain_id, chain.chain_id);
188        assert_eq!(pool.dex.name, "UniswapV3");
189        assert_eq!(pool.address, pool_address);
190        assert_eq!(pool.creation_block, 12345678);
191        assert_eq!(pool.token0.symbol, "WETH");
192        assert_eq!(pool.token1.symbol, "USDT");
193        assert_eq!(pool.fee, 3000);
194        assert_eq!(pool.tick_spacing, 60);
195        assert_eq!(pool.ts_init, ts_init);
196        assert_eq!(pool.ticker(), "WETH/USDT");
197
198        let instrument_id = pool.instrument_id();
199        assert!(instrument_id.to_string().contains("WETH/USDT"));
200        assert!(instrument_id.to_string().contains("UniswapV3"));
201    }
202}