1use std::{fmt::Display, sync::Arc};
19
20use alloy_primitives::{Address, U160};
21use nautilus_core::UnixNanos;
22use serde::{Deserialize, Serialize};
23
24use crate::{
25 data::HasTsInit,
26 defi::{
27 Blockchain, PoolIdentifier, SharedDex, chain::SharedChain, dex::Dex,
28 tick_map::tick_math::get_tick_at_sqrt_ratio, token::Token,
29 },
30 identifiers::{InstrumentId, Symbol, Venue},
31};
32
33#[cfg_attr(
58 feature = "python",
59 pyo3::pyclass(module = "nautilus_trader.model", from_py_object)
60)]
61#[cfg_attr(
62 feature = "python",
63 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
64)]
65#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
66pub struct Pool {
67 pub chain: SharedChain,
69 pub dex: SharedDex,
71 pub address: Address,
73 pub pool_identifier: PoolIdentifier,
75 pub instrument_id: InstrumentId,
77 pub creation_block: u64,
79 pub token0: Token,
81 pub token1: Token,
83 pub fee: Option<u32>,
91 pub tick_spacing: Option<u32>,
93 pub initial_tick: Option<i32>,
95 pub initial_sqrt_price_x96: Option<U160>,
97 pub hooks: Option<Address>,
100 #[serde(default)]
102 pub ts_event: UnixNanos,
103 pub ts_init: UnixNanos,
105}
106
107pub type SharedPool = Arc<Pool>;
109
110impl Pool {
111 #[must_use]
113 #[expect(clippy::too_many_arguments)]
114 pub fn new(
115 chain: SharedChain,
116 dex: SharedDex,
117 address: Address,
118 pool_identifier: PoolIdentifier,
119 creation_block: u64,
120 token0: Token,
121 token1: Token,
122 fee: Option<u32>,
123 tick_spacing: Option<u32>,
124 ts_init: UnixNanos,
125 ) -> Self {
126 let instrument_id = Self::create_instrument_id(chain.name, &dex, pool_identifier.as_str());
127
128 Self {
129 chain,
130 dex,
131 address,
132 pool_identifier,
133 instrument_id,
134 creation_block,
135 token0,
136 token1,
137 fee,
138 tick_spacing,
139 initial_tick: None,
140 initial_sqrt_price_x96: None,
141 hooks: None,
142 ts_event: ts_init,
143 ts_init,
144 }
145 }
146
147 #[must_use]
149 pub fn to_full_spec_string(&self) -> String {
150 format!(
151 "{}/{}-{}.{}",
152 self.token0.symbol,
153 self.token1.symbol,
154 self.fee.unwrap_or(0),
155 self.instrument_id.venue
156 )
157 }
158
159 pub fn initialize(&mut self, sqrt_price_x96: U160, tick: i32) {
168 let calculated_tick = get_tick_at_sqrt_ratio(sqrt_price_x96);
169
170 assert_eq!(
171 tick, calculated_tick,
172 "Provided tick {tick} does not match calculated tick {calculated_tick} for sqrt_price_x96 {sqrt_price_x96}",
173 );
174
175 self.initial_sqrt_price_x96 = Some(sqrt_price_x96);
176 self.initial_tick = Some(tick);
177 }
178
179 pub fn set_hooks(&mut self, hooks: Address) {
183 self.hooks = Some(hooks);
184 }
185
186 #[must_use]
187 pub fn create_instrument_id(
188 chain: Blockchain,
189 dex: &Dex,
190 pool_identifier: &str,
191 ) -> InstrumentId {
192 let symbol = Symbol::new(pool_identifier);
193 let venue = Venue::new(format!("{}:{}", chain, dex.name));
194 InstrumentId::new(symbol, venue)
195 }
196
197 #[must_use]
204 pub fn get_base_token(&self) -> &Token {
205 let priority0 = self.token0.get_token_priority();
206 let priority1 = self.token1.get_token_priority();
207
208 if priority0 < priority1 {
209 &self.token1
210 } else {
211 &self.token0
212 }
213 }
214
215 #[must_use]
221 pub fn get_quote_token(&self) -> &Token {
222 let priority0 = self.token0.get_token_priority();
223 let priority1 = self.token1.get_token_priority();
224
225 if priority0 < priority1 {
226 &self.token0
227 } else {
228 &self.token1
229 }
230 }
231
232 #[must_use]
242 pub fn is_base_quote_inverted(&self) -> bool {
243 let priority0 = self.token0.get_token_priority();
244 let priority1 = self.token1.get_token_priority();
245
246 priority0 < priority1
248 }
249}
250
251impl Display for Pool {
252 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
253 write!(
254 f,
255 "Pool(instrument_id={}, dex={}, fee={}, address={})",
256 self.instrument_id,
257 self.dex.name,
258 self.fee.map_or("None".to_string(), |fee| fee.to_string()),
259 self.address
260 )
261 }
262}
263
264impl HasTsInit for Pool {
265 fn ts_init(&self) -> UnixNanos {
266 self.ts_init
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use std::sync::Arc;
273
274 use rstest::rstest;
275
276 use super::*;
277 use crate::defi::{
278 chain::chains,
279 dex::{AmmType, Dex, DexType},
280 token::Token,
281 };
282
283 #[rstest]
284 fn test_pool_constructor_and_methods() {
285 let chain = Arc::new(chains::ETHEREUM.clone());
286 let dex = Dex::new(
287 chains::ETHEREUM.clone(),
288 DexType::UniswapV3,
289 "0x1F98431c8aD98523631AE4a59f267346ea31F984",
290 0,
291 AmmType::CLAMM,
292 "PoolCreated(address,address,uint24,int24,address)",
293 "Swap(address,address,int256,int256,uint160,uint128,int24)",
294 "Mint(address,address,int24,int24,uint128,uint256,uint256)",
295 "Burn(address,int24,int24,uint128,uint256,uint256)",
296 "Collect(address,address,int24,int24,uint128,uint128)",
297 );
298
299 let token0 = Token::new(
300 Arc::clone(&chain),
301 "0xA0b86a33E6441b936662bb6B5d1F8Fb0E2b57A5D"
302 .parse()
303 .unwrap(),
304 "Wrapped Ether".to_string(),
305 "WETH".to_string(),
306 18,
307 );
308
309 let token1 = Token::new(
310 Arc::clone(&chain),
311 "0xdAC17F958D2ee523a2206206994597C13D831ec7"
312 .parse()
313 .unwrap(),
314 "Tether USD".to_string(),
315 "USDT".to_string(),
316 6,
317 );
318
319 let pool_address: Address = "0x11b815efB8f581194ae79006d24E0d814B7697F6"
320 .parse()
321 .unwrap();
322 let pool_identifier = PoolIdentifier::from_address(pool_address);
323 let ts_init = UnixNanos::from(1_234_567_890_000_000_000u64);
324
325 let pool = Pool::new(
326 Arc::clone(&chain),
327 Arc::new(dex),
328 pool_address,
329 pool_identifier,
330 12_345_678,
331 token0,
332 token1,
333 Some(3000),
334 Some(60),
335 ts_init,
336 );
337
338 assert_eq!(pool.chain.chain_id, chain.chain_id);
339 assert_eq!(pool.dex.name, DexType::UniswapV3);
340 assert_eq!(pool.address, pool_address);
341 assert_eq!(pool.creation_block, 12_345_678);
342 assert_eq!(pool.token0.symbol, "WETH");
343 assert_eq!(pool.token1.symbol, "USDT");
344 assert_eq!(pool.fee.unwrap(), 3000);
345 assert_eq!(pool.tick_spacing.unwrap(), 60);
346 assert_eq!(pool.ts_init, ts_init);
347 assert_eq!(
348 pool.instrument_id.symbol.as_str(),
349 "0x11b815efB8f581194ae79006d24E0d814B7697F6"
350 );
351 assert_eq!(pool.instrument_id.venue.as_str(), "Ethereum:UniswapV3");
352 assert_eq!(pool.get_base_token().symbol, "WETH");
354 assert_eq!(pool.get_quote_token().symbol, "USDT");
355 assert!(!pool.is_base_quote_inverted());
356 assert_eq!(
357 pool.to_full_spec_string(),
358 "WETH/USDT-3000.Ethereum:UniswapV3"
359 );
360 assert_eq!(
361 pool.to_string(),
362 "Pool(instrument_id=0x11b815efB8f581194ae79006d24E0d814B7697F6.Ethereum:UniswapV3, \
363 dex=UniswapV3, fee=3000, address=0x11b815efB8f581194ae79006d24E0d814B7697F6)"
364 );
365 }
366
367 #[rstest]
368 fn test_pool_instrument_id_format() {
369 let chain = Arc::new(chains::ETHEREUM.clone());
370 let factory_address = "0x1F98431c8aD98523631AE4a59f267346ea31F984";
371
372 let dex = Dex::new(
373 chains::ETHEREUM.clone(),
374 DexType::UniswapV3,
375 factory_address,
376 0,
377 AmmType::CLAMM,
378 "PoolCreated(address,address,uint24,int24,address)",
379 "Swap(address,address,int256,int256,uint160,uint128,int24)",
380 "Mint(address,address,int24,int24,uint128,uint256,uint256)",
381 "Burn(address,int24,int24,uint128,uint256,uint256)",
382 "Collect(address,address,int24,int24,uint128,uint128)",
383 );
384
385 let token0 = Token::new(
386 Arc::clone(&chain),
387 "0xA0b86a33E6441b936662bb6B5d1F8Fb0E2b57A5D"
388 .parse()
389 .unwrap(),
390 "Wrapped Ether".to_string(),
391 "WETH".to_string(),
392 18,
393 );
394
395 let token1 = Token::new(
396 Arc::clone(&chain),
397 "0xdAC17F958D2ee523a2206206994597C13D831ec7"
398 .parse()
399 .unwrap(),
400 "Tether USD".to_string(),
401 "USDT".to_string(),
402 6,
403 );
404
405 let pool_address = "0x11b815efB8f581194ae79006d24E0d814B7697F6"
406 .parse()
407 .unwrap();
408
409 let pool = Pool::new(
410 chain,
411 Arc::new(dex),
412 pool_address,
413 PoolIdentifier::from_address(pool_address),
414 0,
415 token0,
416 token1,
417 Some(3000),
418 Some(60),
419 UnixNanos::default(),
420 );
421
422 assert_eq!(
423 pool.instrument_id.to_string(),
424 "0x11b815efB8f581194ae79006d24E0d814B7697F6.Ethereum:UniswapV3"
425 );
426 }
427}