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