nautilus_model/python/defi/
data.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 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 data types.
17
18use std::{
19    collections::hash_map::DefaultHasher,
20    hash::{Hash, Hasher},
21    str::FromStr,
22    sync::Arc,
23};
24
25use alloy_primitives::Address;
26use nautilus_core::python::to_pyvalue_err;
27use pyo3::{basic::CompareOp, prelude::*};
28
29use crate::{
30    defi::{
31        Chain, Dex,
32        chain::Blockchain,
33        data::{Block, PoolLiquidityUpdate, PoolLiquidityUpdateType, PoolSwap},
34    },
35    enums::OrderSide,
36    identifiers::InstrumentId,
37    types::{Price, Quantity},
38};
39
40#[pymethods]
41impl PoolSwap {
42    #[new]
43    #[allow(clippy::too_many_arguments)]
44    fn py_new(
45        chain: Chain,
46        dex: Dex,
47        instrument_id: InstrumentId,
48        pool_address: String,
49        block: u64,
50        transaction_hash: String,
51        transaction_index: u32,
52        log_index: u32,
53        timestamp: u64,
54        sender: String,
55        side: OrderSide,
56        size: Quantity,
57        price: Price,
58    ) -> PyResult<Self> {
59        let sender = sender.parse().map_err(to_pyvalue_err)?;
60        Ok(Self::new(
61            Arc::new(chain),
62            Arc::new(dex),
63            instrument_id,
64            Address::from_str(&pool_address).map_err(to_pyvalue_err)?,
65            block,
66            transaction_hash,
67            transaction_index,
68            log_index,
69            Some(timestamp.into()),
70            sender,
71            side,
72            size,
73            price,
74        ))
75    }
76
77    #[getter]
78    #[pyo3(name = "chain")]
79    fn py_chain(&self) -> PyResult<Chain> {
80        Ok(self.chain.as_ref().clone())
81    }
82
83    #[getter]
84    #[pyo3(name = "dex")]
85    fn py_dex(&self) -> PyResult<Dex> {
86        Ok(self.dex.as_ref().clone())
87    }
88
89    #[getter]
90    #[pyo3(name = "instrument_id")]
91    fn py_instrument_id(&self) -> InstrumentId {
92        self.instrument_id
93    }
94
95    #[getter]
96    #[pyo3(name = "pool_address")]
97    fn py_pool_address(&self) -> String {
98        self.pool_address.to_string()
99    }
100
101    #[getter]
102    #[pyo3(name = "block")]
103    fn py_block(&self) -> u64 {
104        self.block
105    }
106
107    #[getter]
108    #[pyo3(name = "transaction_hash")]
109    fn py_transaction_hash(&self) -> &str {
110        &self.transaction_hash
111    }
112
113    #[getter]
114    #[pyo3(name = "transaction_index")]
115    fn py_transaction_index(&self) -> u32 {
116        self.transaction_index
117    }
118
119    #[getter]
120    #[pyo3(name = "log_index")]
121    fn py_log_index(&self) -> u32 {
122        self.log_index
123    }
124
125    #[getter]
126    #[pyo3(name = "sender")]
127    fn py_sender(&self) -> String {
128        self.sender.to_string()
129    }
130
131    #[getter]
132    #[pyo3(name = "side")]
133    fn py_side(&self) -> OrderSide {
134        self.side
135    }
136
137    #[getter]
138    #[pyo3(name = "size")]
139    fn py_size(&self) -> Quantity {
140        self.size
141    }
142
143    #[getter]
144    #[pyo3(name = "price")]
145    fn py_price(&self) -> Price {
146        self.price
147    }
148
149    #[getter]
150    #[pyo3(name = "timestamp")]
151    fn py_timestamp(&self) -> Option<u64> {
152        self.timestamp.map(|x| x.as_u64())
153    }
154
155    #[getter]
156    #[pyo3(name = "ts_init")]
157    fn py_ts_init(&self) -> Option<u64> {
158        self.ts_init.map(|x| x.as_u64())
159    }
160
161    fn __str__(&self) -> String {
162        self.to_string()
163    }
164
165    fn __repr__(&self) -> String {
166        format!("{self:?}")
167    }
168
169    fn __hash__(&self) -> u64 {
170        let mut hasher = DefaultHasher::new();
171        self.chain.chain_id.hash(&mut hasher);
172        self.transaction_hash.hash(&mut hasher);
173        self.log_index.hash(&mut hasher);
174        hasher.finish()
175    }
176
177    fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
178        match op {
179            CompareOp::Eq => self == other,
180            CompareOp::Ne => self != other,
181            _ => panic!("Unsupported comparison for PoolSwap"),
182        }
183    }
184}
185
186#[pymethods]
187impl PoolLiquidityUpdate {
188    #[new]
189    #[allow(clippy::too_many_arguments)]
190    fn py_new(
191        chain: Chain,
192        dex: Dex,
193        instrument_id: InstrumentId,
194        pool_address: String,
195        kind: PoolLiquidityUpdateType,
196        block: u64,
197        transaction_hash: String,
198        transaction_index: u32,
199        log_index: u32,
200        sender: Option<String>,
201        owner: String,
202        position_liquidity: Quantity,
203        amount0: Quantity,
204        amount1: Quantity,
205        tick_lower: i32,
206        tick_upper: i32,
207        timestamp: u64,
208    ) -> PyResult<Self> {
209        let sender = sender
210            .map(|s| s.parse())
211            .transpose()
212            .map_err(to_pyvalue_err)?;
213        let owner = owner.parse().map_err(to_pyvalue_err)?;
214        Ok(Self::new(
215            Arc::new(chain),
216            Arc::new(dex),
217            instrument_id,
218            Address::from_str(&pool_address).map_err(to_pyvalue_err)?,
219            kind,
220            block,
221            transaction_hash,
222            transaction_index,
223            log_index,
224            sender,
225            owner,
226            position_liquidity,
227            amount0,
228            amount1,
229            tick_lower,
230            tick_upper,
231            Some(timestamp.into()),
232        ))
233    }
234
235    #[getter]
236    #[pyo3(name = "chain")]
237    fn py_chain(&self) -> PyResult<Chain> {
238        Ok(self.chain.as_ref().clone())
239    }
240
241    #[getter]
242    #[pyo3(name = "dex")]
243    fn py_dex(&self) -> PyResult<Dex> {
244        Ok(self.dex.as_ref().clone())
245    }
246
247    #[getter]
248    #[pyo3(name = "instrument_id")]
249    fn py_instrument_id(&self) -> crate::identifiers::InstrumentId {
250        self.instrument_id
251    }
252
253    #[getter]
254    #[pyo3(name = "pool_address")]
255    fn py_pool_address(&self) -> String {
256        self.pool_address.to_string()
257    }
258
259    #[getter]
260    #[pyo3(name = "kind")]
261    fn py_kind(&self) -> PoolLiquidityUpdateType {
262        self.kind
263    }
264
265    #[getter]
266    #[pyo3(name = "block")]
267    fn py_block(&self) -> u64 {
268        self.block
269    }
270
271    #[getter]
272    #[pyo3(name = "transaction_hash")]
273    fn py_transaction_hash(&self) -> &str {
274        &self.transaction_hash
275    }
276
277    #[getter]
278    #[pyo3(name = "transaction_index")]
279    fn py_transaction_index(&self) -> u32 {
280        self.transaction_index
281    }
282
283    #[getter]
284    #[pyo3(name = "log_index")]
285    fn py_log_index(&self) -> u32 {
286        self.log_index
287    }
288
289    #[getter]
290    #[pyo3(name = "sender")]
291    fn py_sender(&self) -> Option<String> {
292        self.sender.map(|s| s.to_string())
293    }
294
295    #[getter]
296    #[pyo3(name = "owner")]
297    fn py_owner(&self) -> String {
298        self.owner.to_string()
299    }
300
301    #[getter]
302    #[pyo3(name = "position_liquidity")]
303    fn py_position_liquidity(&self) -> Quantity {
304        self.position_liquidity
305    }
306
307    #[getter]
308    #[pyo3(name = "amount0")]
309    fn py_amount0(&self) -> Quantity {
310        self.amount0
311    }
312
313    #[getter]
314    #[pyo3(name = "amount1")]
315    fn py_amount1(&self) -> Quantity {
316        self.amount1
317    }
318
319    #[getter]
320    #[pyo3(name = "tick_lower")]
321    fn py_tick_lower(&self) -> i32 {
322        self.tick_lower
323    }
324
325    #[getter]
326    #[pyo3(name = "tick_upper")]
327    fn py_tick_upper(&self) -> i32 {
328        self.tick_upper
329    }
330
331    #[getter]
332    #[pyo3(name = "timestamp")]
333    fn py_timestamp(&self) -> Option<u64> {
334        self.timestamp.map(|x| x.as_u64())
335    }
336
337    #[getter]
338    #[pyo3(name = "ts_init")]
339    fn py_ts_init(&self) -> Option<u64> {
340        self.ts_init.map(|x| x.as_u64())
341    }
342
343    fn __str__(&self) -> String {
344        self.to_string()
345    }
346
347    fn __repr__(&self) -> String {
348        format!("{self:?}")
349    }
350
351    fn __hash__(&self) -> u64 {
352        let mut hasher = DefaultHasher::new();
353        self.chain.chain_id.hash(&mut hasher);
354        self.transaction_hash.hash(&mut hasher);
355        self.log_index.hash(&mut hasher);
356        hasher.finish()
357    }
358
359    fn __richcmp__(&self, other: &Self, op: pyo3::pyclass::CompareOp) -> bool {
360        match op {
361            CompareOp::Eq => self == other,
362            CompareOp::Ne => self != other,
363            _ => panic!("Unsupported comparison for PoolLiquidityUpdate"),
364        }
365    }
366}
367
368#[pymethods]
369impl Block {
370    #[getter]
371    #[pyo3(name = "chain")]
372    fn py_chain(&self) -> Option<Blockchain> {
373        self.chain
374    }
375
376    #[getter]
377    #[pyo3(name = "hash")]
378    fn py_hash(&self) -> &str {
379        &self.hash
380    }
381
382    #[getter]
383    #[pyo3(name = "number")]
384    fn py_number(&self) -> u64 {
385        self.number
386    }
387
388    #[getter]
389    #[pyo3(name = "parent_hash")]
390    fn py_parent_hash(&self) -> &str {
391        &self.parent_hash
392    }
393
394    #[getter]
395    #[pyo3(name = "miner")]
396    fn py_miner(&self) -> &str {
397        &self.miner
398    }
399
400    #[getter]
401    #[pyo3(name = "gas_limit")]
402    fn py_gas_limit(&self) -> u64 {
403        self.gas_limit
404    }
405
406    #[getter]
407    #[pyo3(name = "gas_used")]
408    fn py_gas_used(&self) -> u64 {
409        self.gas_used
410    }
411
412    #[getter]
413    #[pyo3(name = "base_fee_per_gas")]
414    fn py_base_fee_per_gas(&self) -> Option<String> {
415        self.base_fee_per_gas.map(|x| x.to_string())
416    }
417
418    #[getter]
419    #[pyo3(name = "blob_gas_used")]
420    fn py_blob_gas_used(&self) -> Option<String> {
421        self.blob_gas_used.map(|x| x.to_string())
422    }
423
424    #[getter]
425    #[pyo3(name = "excess_blob_gas")]
426    fn py_excess_blob_gas(&self) -> Option<String> {
427        self.excess_blob_gas.map(|x| x.to_string())
428    }
429
430    #[getter]
431    #[pyo3(name = "l1_gas_price")]
432    fn py_l1_gas_price(&self) -> Option<String> {
433        self.l1_gas_price.map(|x| x.to_string())
434    }
435
436    #[getter]
437    #[pyo3(name = "l1_gas_used")]
438    fn py_l1_gas_used(&self) -> Option<u64> {
439        self.l1_gas_used
440    }
441
442    #[getter]
443    #[pyo3(name = "l1_fee_scalar")]
444    fn py_l1_fee_scalar(&self) -> Option<u64> {
445        self.l1_fee_scalar
446    }
447
448    #[getter]
449    #[pyo3(name = "timestamp")]
450    fn py_timestamp(&self) -> u64 {
451        self.timestamp.as_u64()
452    }
453
454    fn __str__(&self) -> String {
455        self.to_string()
456    }
457
458    fn __repr__(&self) -> String {
459        format!("{self:?}")
460    }
461
462    fn __hash__(&self) -> u64 {
463        let mut hasher = DefaultHasher::new();
464        self.hash.hash(&mut hasher);
465        hasher.finish()
466    }
467}