Skip to main content

nautilus_model/python/defi/
types.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.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//! Python bindings for DeFi types.
17
18use std::{
19    collections::hash_map::DefaultHasher,
20    hash::{Hash, Hasher},
21    str::FromStr,
22    sync::Arc,
23};
24
25use nautilus_core::python::to_pyvalue_err;
26use pyo3::{basic::CompareOp, prelude::*};
27
28use crate::{
29    defi::{AmmType, Blockchain, Chain, Dex, DexType, Pool, Token, chain::chains},
30    identifiers::InstrumentId,
31};
32
33#[pymethods]
34impl Chain {
35    #[new]
36    fn py_new(name: Blockchain, chain_id: u32) -> Self {
37        Self::new(name, chain_id)
38    }
39
40    fn __str__(&self) -> String {
41        self.to_string()
42    }
43
44    fn __repr__(&self) -> String {
45        format!("{self:?}")
46    }
47
48    fn __hash__(&self) -> u64 {
49        let mut hasher = DefaultHasher::new();
50        self.chain_id.hash(&mut hasher);
51        hasher.finish()
52    }
53
54    fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
55        match op {
56            CompareOp::Eq => self == other,
57            CompareOp::Ne => self != other,
58            _ => panic!("Unsupported comparison for Chain"),
59        }
60    }
61
62    #[getter]
63    #[pyo3(name = "name")]
64    fn py_name(&self) -> Blockchain {
65        self.name
66    }
67
68    #[getter]
69    #[pyo3(name = "chain_id")]
70    fn py_chain_id(&self) -> u32 {
71        self.chain_id
72    }
73
74    #[getter]
75    #[pyo3(name = "hypersync_url")]
76    fn py_hypersync_url(&self) -> &str {
77        &self.hypersync_url
78    }
79
80    #[getter]
81    #[pyo3(name = "rpc_url")]
82    fn py_rpc_url(&self) -> Option<&str> {
83        self.rpc_url.as_deref()
84    }
85
86    #[getter]
87    #[pyo3(name = "native_currency_decimals")]
88    fn py_native_currency_decimals(&self) -> u8 {
89        self.native_currency_decimals
90    }
91
92    #[pyo3(name = "set_rpc_url")]
93    fn py_set_rpc_url(&mut self, rpc_url: String) {
94        self.set_rpc_url(rpc_url);
95    }
96
97    #[staticmethod]
98    #[pyo3(name = "from_chain_name")]
99    fn py_from_chain_name(chain_name: &str) -> PyResult<Self> {
100        Self::from_chain_name(chain_name).cloned().ok_or_else(|| {
101            to_pyvalue_err(format!("`chain_name` '{chain_name}' is not recognized",))
102        })
103    }
104
105    #[staticmethod]
106    #[pyo3(name = "from_chain_id")]
107    fn py_from_chain_id(chain_id: u32) -> Option<Self> {
108        Self::from_chain_id(chain_id).cloned()
109    }
110
111    #[staticmethod]
112    #[pyo3(name = "ARBITRUM")]
113    fn py_arbitrum_chain() -> Self {
114        chains::ARBITRUM.clone()
115    }
116}
117
118#[pymethods]
119impl Token {
120    #[new]
121    fn py_new(
122        chain: Chain,
123        address: String,
124        name: String,
125        symbol: String,
126        decimals: u8,
127    ) -> PyResult<Self> {
128        let address = address.parse().map_err(to_pyvalue_err)?;
129        Ok(Self::new(Arc::new(chain), address, name, symbol, decimals))
130    }
131
132    fn __str__(&self) -> String {
133        self.to_string()
134    }
135
136    fn __repr__(&self) -> String {
137        format!("{self:?}")
138    }
139
140    fn __hash__(&self) -> u64 {
141        let mut hasher = DefaultHasher::new();
142        self.chain.chain_id.hash(&mut hasher);
143        self.address.hash(&mut hasher);
144        hasher.finish()
145    }
146
147    fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
148        match op {
149            CompareOp::Eq => self == other,
150            CompareOp::Ne => self != other,
151            _ => panic!("Unsupported comparison for Token"),
152        }
153    }
154
155    #[getter]
156    #[pyo3(name = "chain")]
157    fn py_chain(&self) -> PyResult<Chain> {
158        Ok(self.chain.as_ref().clone())
159    }
160
161    #[getter]
162    #[pyo3(name = "address")]
163    fn py_address(&self) -> String {
164        self.address.to_string()
165    }
166
167    #[getter]
168    #[pyo3(name = "name")]
169    fn py_name(&self) -> &str {
170        &self.name
171    }
172
173    #[getter]
174    #[pyo3(name = "symbol")]
175    fn py_symbol(&self) -> &str {
176        &self.symbol
177    }
178
179    #[getter]
180    #[pyo3(name = "decimals")]
181    fn py_decimals(&self) -> u8 {
182        self.decimals
183    }
184}
185
186#[pymethods]
187impl Dex {
188    #[new]
189    #[allow(clippy::too_many_arguments)]
190    fn py_new(
191        chain: Chain,
192        name: String,
193        factory: String,
194        factory_creation_block: u64,
195        amm_type: String,
196        pool_created_event: &str,
197        swap_event: &str,
198        mint_event: &str,
199        burn_event: &str,
200        collect_event: &str,
201    ) -> PyResult<Self> {
202        let amm_type = AmmType::from_str(&amm_type).map_err(to_pyvalue_err)?;
203        let dex_type = DexType::from_dex_name(&name)
204            .ok_or_else(|| to_pyvalue_err(format!("Invalid DEX name: {name}")))?;
205        Ok(Self::new(
206            chain,
207            dex_type,
208            &factory,
209            factory_creation_block,
210            amm_type,
211            pool_created_event,
212            swap_event,
213            mint_event,
214            burn_event,
215            collect_event,
216        ))
217    }
218
219    fn __str__(&self) -> String {
220        self.to_string()
221    }
222
223    fn __repr__(&self) -> String {
224        format!("{self:?}")
225    }
226
227    fn __hash__(&self) -> u64 {
228        let mut hasher = DefaultHasher::new();
229        self.chain.chain_id.hash(&mut hasher);
230        self.name.hash(&mut hasher);
231        self.factory.hash(&mut hasher);
232        hasher.finish()
233    }
234
235    fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
236        match op {
237            CompareOp::Eq => self == other,
238            CompareOp::Ne => self != other,
239            _ => panic!("Unsupported comparison for Dex"),
240        }
241    }
242
243    #[getter]
244    #[pyo3(name = "chain")]
245    fn py_chain(&self) -> Chain {
246        self.chain.clone()
247    }
248
249    #[getter]
250    #[pyo3(name = "name")]
251    fn py_name(&self) -> DexType {
252        self.name
253    }
254
255    #[getter]
256    #[pyo3(name = "factory")]
257    fn py_factory(&self) -> String {
258        self.factory.to_string()
259    }
260
261    #[getter]
262    #[pyo3(name = "factory_creation_block")]
263    fn py_factory_creation_block(&self) -> u64 {
264        self.factory_creation_block
265    }
266
267    #[getter]
268    #[pyo3(name = "pool_created_event")]
269    fn py_pool_created_event(&self) -> &str {
270        &self.pool_created_event
271    }
272
273    #[getter]
274    #[pyo3(name = "swap_created_event")]
275    fn py_swap_created_event(&self) -> &str {
276        &self.swap_created_event
277    }
278
279    #[getter]
280    #[pyo3(name = "mint_created_event")]
281    fn py_mint_created_event(&self) -> &str {
282        &self.mint_created_event
283    }
284
285    #[getter]
286    #[pyo3(name = "burn_created_event")]
287    fn py_burn_created_event(&self) -> &str {
288        &self.burn_created_event
289    }
290
291    #[getter]
292    #[pyo3(name = "amm_type")]
293    fn py_amm_type(&self) -> AmmType {
294        self.amm_type
295    }
296}
297
298#[pymethods]
299impl Pool {
300    #[new]
301    #[allow(clippy::too_many_arguments)]
302    fn py_new(
303        chain: Chain,
304        dex: Dex,
305        address: String,
306        pool_identifier: String,
307        creation_block: u64,
308        token0: Token,
309        token1: Token,
310        fee: Option<u32>,
311        tick_spacing: Option<u32>,
312        ts_init: u64,
313    ) -> PyResult<Self> {
314        let address = address.parse().map_err(to_pyvalue_err)?;
315        let pool_identifier = pool_identifier.parse().map_err(to_pyvalue_err)?;
316        Ok(Self::new(
317            Arc::new(chain),
318            Arc::new(dex),
319            address,
320            pool_identifier,
321            creation_block,
322            token0,
323            token1,
324            fee,
325            tick_spacing,
326            ts_init.into(),
327        ))
328    }
329
330    fn __str__(&self) -> String {
331        self.to_string()
332    }
333
334    fn __repr__(&self) -> String {
335        format!("{self:?}")
336    }
337
338    fn __hash__(&self) -> u64 {
339        let mut hasher = DefaultHasher::new();
340        self.chain.chain_id.hash(&mut hasher);
341        self.address.hash(&mut hasher);
342        hasher.finish()
343    }
344
345    fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
346        match op {
347            CompareOp::Eq => self == other,
348            CompareOp::Ne => self != other,
349            _ => panic!("Unsupported comparison for Pool"),
350        }
351    }
352
353    #[getter]
354    #[pyo3(name = "chain")]
355    fn py_chain(&self) -> PyResult<Chain> {
356        Ok(self.chain.as_ref().clone())
357    }
358
359    #[getter]
360    #[pyo3(name = "dex")]
361    fn py_dex(&self) -> PyResult<Dex> {
362        Ok(self.dex.as_ref().clone())
363    }
364
365    #[getter]
366    #[pyo3(name = "instrument_id")]
367    fn py_instrument_id(&self) -> InstrumentId {
368        self.instrument_id
369    }
370
371    #[getter]
372    #[pyo3(name = "address")]
373    fn py_address(&self) -> String {
374        self.address.to_string()
375    }
376
377    #[getter]
378    #[pyo3(name = "creation_block")]
379    fn py_creation_block(&self) -> u64 {
380        self.creation_block
381    }
382
383    #[getter]
384    #[pyo3(name = "token0")]
385    fn py_token0(&self) -> Token {
386        self.token0.clone()
387    }
388
389    #[getter]
390    #[pyo3(name = "token1")]
391    fn py_token1(&self) -> Token {
392        self.token1.clone()
393    }
394
395    #[getter]
396    #[pyo3(name = "fee")]
397    fn py_fee(&self) -> Option<u32> {
398        self.fee
399    }
400
401    #[getter]
402    #[pyo3(name = "tick_spacing")]
403    fn py_tick_spacing(&self) -> Option<u32> {
404        self.tick_spacing
405    }
406
407    #[getter]
408    #[pyo3(name = "ts_init")]
409    fn py_ts_init(&self) -> u64 {
410        self.ts_init.as_u64()
411    }
412}