nautilus_coinbase_intx/python/
http.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
16use chrono::{DateTime, Utc};
17use nautilus_core::python::{IntoPyObjectNautilusExt, serialization::to_dict_pyo3, to_pyvalue_err};
18use nautilus_model::{
19    enums::{OrderSide, OrderType, TimeInForce},
20    identifiers::{AccountId, ClientOrderId, Symbol, VenueOrderId},
21    python::instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
22    types::{Price, Quantity},
23};
24use pyo3::{prelude::*, types::PyList};
25
26use crate::http::client::CoinbaseIntxHttpClient;
27
28#[pymethods]
29impl CoinbaseIntxHttpClient {
30    #[new]
31    #[pyo3(signature = (api_key=None, api_secret=None, api_passphrase=None, base_url=None, timeout_secs=None))]
32    fn py_new(
33        api_key: Option<String>,
34        api_secret: Option<String>,
35        api_passphrase: Option<String>,
36        base_url: Option<String>,
37        timeout_secs: Option<u64>,
38    ) -> PyResult<Self> {
39        Self::with_credentials(api_key, api_secret, api_passphrase, base_url, timeout_secs)
40            .map_err(to_pyvalue_err)
41    }
42
43    #[getter]
44    #[pyo3(name = "base_url")]
45    #[must_use]
46    pub fn py_base_url(&self) -> &str {
47        self.base_url()
48    }
49
50    #[getter]
51    #[pyo3(name = "api_key")]
52    #[must_use]
53    pub fn py_api_key(&self) -> Option<&str> {
54        self.api_key()
55    }
56
57    #[getter]
58    #[pyo3(name = "api_key_masked")]
59    #[must_use]
60    pub fn py_api_key_masked(&self) -> Option<String> {
61        self.api_key_masked()
62    }
63
64    #[pyo3(name = "is_initialized")]
65    #[must_use]
66    pub const fn py_is_initialized(&self) -> bool {
67        self.is_initialized()
68    }
69
70    #[pyo3(name = "get_cached_symbols")]
71    #[must_use]
72    pub fn py_get_cached_symbols(&self) -> Vec<String> {
73        self.get_cached_symbols()
74    }
75
76    /// # Errors
77    ///
78    /// Returns a Python exception if adding the instrument to the cache fails.
79    #[pyo3(name = "add_instrument")]
80    pub fn py_add_instrument(&mut self, py: Python<'_>, instrument: Py<PyAny>) -> PyResult<()> {
81        self.add_instrument(pyobject_to_instrument_any(py, instrument)?);
82        Ok(())
83    }
84
85    /// # Errors
86    ///
87    /// Returns a Python exception if retrieving or serializing portfolios fails.
88    #[pyo3(name = "list_portfolios")]
89    pub fn py_list_portfolios<'py>(&mut self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
90        let client = self.clone();
91
92        pyo3_async_runtimes::tokio::future_into_py(py, async move {
93            let response = client.list_portfolios().await.map_err(to_pyvalue_err)?;
94
95            Python::attach(|py| {
96                let py_list = PyList::empty(py);
97
98                for portfolio in response {
99                    let dict = to_dict_pyo3(py, &portfolio)?;
100                    py_list.append(dict)?;
101                }
102
103                Ok(py_list.into_any().unbind())
104            })
105        })
106    }
107
108    #[pyo3(name = "request_account_state")]
109    fn py_request_account_state<'py>(
110        &self,
111        py: Python<'py>,
112        account_id: AccountId,
113    ) -> PyResult<Bound<'py, PyAny>> {
114        let client = self.clone();
115
116        pyo3_async_runtimes::tokio::future_into_py(py, async move {
117            let account_state = client
118                .request_account_state(account_id)
119                .await
120                .map_err(to_pyvalue_err)?;
121
122            Ok(Python::attach(|py| account_state.into_py_any_unwrap(py)))
123        })
124    }
125
126    #[pyo3(name = "request_instruments")]
127    fn py_request_instruments<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
128        let client = self.clone();
129
130        pyo3_async_runtimes::tokio::future_into_py(py, async move {
131            let instruments = client.request_instruments().await.map_err(to_pyvalue_err)?;
132
133            Python::attach(|py| {
134                let py_instruments: PyResult<Vec<_>> = instruments
135                    .into_iter()
136                    .map(|inst| instrument_any_to_pyobject(py, inst))
137                    .collect();
138                let pylist = PyList::new(py, py_instruments?)
139                    .unwrap()
140                    .into_any()
141                    .unbind();
142                Ok(pylist)
143            })
144        })
145    }
146
147    #[pyo3(name = "request_instrument")]
148    fn py_request_instrument<'py>(
149        &self,
150        py: Python<'py>,
151        symbol: Symbol,
152    ) -> PyResult<Bound<'py, PyAny>> {
153        let client = self.clone();
154
155        pyo3_async_runtimes::tokio::future_into_py(py, async move {
156            let instrument = client
157                .request_instrument(&symbol)
158                .await
159                .map_err(to_pyvalue_err)?;
160
161            Ok(Python::attach(|py| {
162                instrument_any_to_pyobject(py, instrument)
163                    .expect("Failed parsing instrument")
164                    .into_py_any_unwrap(py)
165            }))
166        })
167    }
168
169    #[pyo3(name = "request_order_status_report")]
170    fn py_request_order_status_report<'py>(
171        &self,
172        py: Python<'py>,
173        account_id: AccountId,
174        venue_order_id: VenueOrderId,
175    ) -> PyResult<Bound<'py, PyAny>> {
176        let client = self.clone();
177
178        pyo3_async_runtimes::tokio::future_into_py(py, async move {
179            let report = client
180                .request_order_status_report(account_id, venue_order_id)
181                .await
182                .map_err(to_pyvalue_err)?;
183
184            Python::attach(|py| Ok(report.into_py_any_unwrap(py)))
185        })
186    }
187
188    #[pyo3(name = "request_order_status_reports")]
189    #[pyo3(signature = (account_id, symbol))]
190    fn py_request_order_status_reports<'py>(
191        &self,
192        py: Python<'py>,
193        account_id: AccountId,
194        symbol: Symbol,
195    ) -> PyResult<Bound<'py, PyAny>> {
196        let client = self.clone();
197
198        pyo3_async_runtimes::tokio::future_into_py(py, async move {
199            let reports = client
200                .request_order_status_reports(account_id, symbol)
201                .await
202                .map_err(to_pyvalue_err)?;
203
204            Python::attach(|py| {
205                let pylist =
206                    PyList::new(py, reports.into_iter().map(|t| t.into_py_any_unwrap(py)))?;
207                Ok(pylist.into_py_any_unwrap(py))
208            })
209        })
210    }
211
212    #[pyo3(name = "request_fill_reports")]
213    #[pyo3(signature = (account_id, client_order_id=None, start=None))]
214    fn py_request_fill_reports<'py>(
215        &self,
216        py: Python<'py>,
217        account_id: AccountId,
218        client_order_id: Option<ClientOrderId>,
219        start: Option<DateTime<Utc>>,
220    ) -> PyResult<Bound<'py, PyAny>> {
221        let client = self.clone();
222
223        pyo3_async_runtimes::tokio::future_into_py(py, async move {
224            let reports = client
225                .request_fill_reports(account_id, client_order_id, start)
226                .await
227                .map_err(to_pyvalue_err)?;
228
229            Python::attach(|py| {
230                let pylist =
231                    PyList::new(py, reports.into_iter().map(|t| t.into_py_any_unwrap(py)))?;
232                Ok(pylist.into_py_any_unwrap(py))
233            })
234        })
235    }
236
237    #[pyo3(name = "request_position_status_report")]
238    fn py_request_position_status_report<'py>(
239        &self,
240        py: Python<'py>,
241        account_id: AccountId,
242        symbol: Symbol,
243    ) -> PyResult<Bound<'py, PyAny>> {
244        let client = self.clone();
245
246        pyo3_async_runtimes::tokio::future_into_py(py, async move {
247            let report = client
248                .request_position_status_report(account_id, symbol)
249                .await
250                .map_err(to_pyvalue_err)?;
251
252            Python::attach(|py| Ok(report.into_py_any_unwrap(py)))
253        })
254    }
255
256    #[pyo3(name = "request_position_status_reports")]
257    fn py_request_position_status_reports<'py>(
258        &self,
259        py: Python<'py>,
260        account_id: AccountId,
261    ) -> PyResult<Bound<'py, PyAny>> {
262        let client = self.clone();
263
264        pyo3_async_runtimes::tokio::future_into_py(py, async move {
265            let reports = client
266                .request_position_status_reports(account_id)
267                .await
268                .map_err(to_pyvalue_err)?;
269
270            Python::attach(|py| {
271                let pylist =
272                    PyList::new(py, reports.into_iter().map(|t| t.into_py_any_unwrap(py)))?;
273                Ok(pylist.into_py_any_unwrap(py))
274            })
275        })
276    }
277
278    #[allow(clippy::too_many_arguments)]
279    #[pyo3(name = "submit_order")]
280    #[pyo3(signature = (account_id, symbol, client_order_id, order_type, order_side, quantity, time_in_force, expire_time=None, price=None, trigger_price=None, post_only=None, reduce_only=None))]
281    fn py_submit_order<'py>(
282        &self,
283        py: Python<'py>,
284        account_id: AccountId,
285        symbol: Symbol,
286        client_order_id: ClientOrderId,
287        order_type: OrderType,
288        order_side: OrderSide,
289        quantity: Quantity,
290        time_in_force: TimeInForce,
291        expire_time: Option<DateTime<Utc>>,
292        price: Option<Price>,
293        trigger_price: Option<Price>,
294        post_only: Option<bool>,
295        reduce_only: Option<bool>,
296    ) -> PyResult<Bound<'py, PyAny>> {
297        let client = self.clone();
298
299        pyo3_async_runtimes::tokio::future_into_py(py, async move {
300            client
301                .submit_order(
302                    account_id,
303                    client_order_id,
304                    symbol,
305                    order_side,
306                    order_type,
307                    quantity,
308                    time_in_force,
309                    expire_time,
310                    price,
311                    trigger_price,
312                    post_only,
313                    reduce_only,
314                )
315                .await
316                .map_err(to_pyvalue_err)
317        })
318    }
319
320    #[pyo3(name = "cancel_order")]
321    fn py_cancel_order<'py>(
322        &self,
323        py: Python<'py>,
324        account_id: AccountId,
325        client_order_id: ClientOrderId,
326    ) -> PyResult<Bound<'py, PyAny>> {
327        let client = self.clone();
328
329        pyo3_async_runtimes::tokio::future_into_py(py, async move {
330            client
331                .cancel_order(account_id, client_order_id)
332                .await
333                .map_err(to_pyvalue_err)
334        })
335    }
336
337    #[pyo3(name = "cancel_orders")]
338    #[pyo3(signature = (account_id, symbol, order_side=None))]
339    fn py_cancel_orders<'py>(
340        &self,
341        py: Python<'py>,
342        account_id: AccountId,
343        symbol: Symbol,
344        order_side: Option<OrderSide>,
345    ) -> PyResult<Bound<'py, PyAny>> {
346        let client = self.clone();
347
348        pyo3_async_runtimes::tokio::future_into_py(py, async move {
349            client
350                .cancel_orders(account_id, symbol, order_side)
351                .await
352                .map_err(to_pyvalue_err)
353        })
354    }
355
356    #[pyo3(name = "modify_order")]
357    #[pyo3(signature = (account_id, client_order_id, new_client_order_id, price=None, trigger_price=None, quantity=None))]
358    #[allow(clippy::too_many_arguments)]
359    fn py_modify_order<'py>(
360        &self,
361        py: Python<'py>,
362        account_id: AccountId,
363        client_order_id: ClientOrderId,
364        new_client_order_id: ClientOrderId,
365        price: Option<Price>,
366        trigger_price: Option<Price>,
367        quantity: Option<Quantity>,
368    ) -> PyResult<Bound<'py, PyAny>> {
369        let client = self.clone();
370
371        pyo3_async_runtimes::tokio::future_into_py(py, async move {
372            client
373                .modify_order(
374                    account_id,
375                    client_order_id,
376                    new_client_order_id,
377                    price,
378                    trigger_price,
379                    quantity,
380                )
381                .await
382                .map_err(to_pyvalue_err)
383        })
384    }
385}