nautilus_infrastructure/python/sql/
cache.rs1use bytes::Bytes;
17use nautilus_common::{
18 cache::database::{CacheDatabaseAdapter, CacheDatabaseFactory},
19 live::get_runtime,
20 python::cache::get_global_cache_database_factory_registry,
21 signal::Signal,
22};
23use nautilus_core::python::to_pyruntime_err;
24use nautilus_model::{
25 data::{Bar, CustomData, DataType, QuoteTick, TradeTick},
26 events::{OrderSnapshot, PositionSnapshot},
27 identifiers::{AccountId, ClientId, ClientOrderId, InstrumentId, PositionId, TraderId},
28 python::{
29 account::{account_any_to_pyobject, pyobject_to_account_any},
30 events::order::pyobject_to_order_event,
31 instruments::{instrument_any_to_pyobject, pyobject_to_instrument_any},
32 orders::{order_any_to_pyobject, pyobject_to_order_any},
33 },
34 types::Currency,
35};
36use pyo3::{IntoPyObjectExt, prelude::*};
37
38use crate::sql::{
39 cache::{PostgresCacheConfig, PostgresCacheDatabase},
40 queries::DatabaseQueries,
41};
42
43#[pymethods]
44impl PostgresCacheDatabase {
45 #[staticmethod]
56 #[pyo3(name = "connect")]
57 #[pyo3(signature = (host=None, port=None, username=None, password=None, database=None, *, trader_id))]
58 fn py_connect(
59 host: Option<String>,
60 port: Option<u16>,
61 username: Option<String>,
62 password: Option<String>,
63 database: Option<String>,
64 trader_id: TraderId,
65 ) -> PyResult<Self> {
66 let result = get_runtime().block_on(async {
67 Self::connect(host, port, username, password, database, trader_id).await
68 });
69 result.map_err(to_pyruntime_err)
70 }
71
72 #[pyo3(name = "close")]
73 fn py_close(&mut self) -> PyResult<()> {
74 self.close().map_err(to_pyruntime_err)
75 }
76
77 #[pyo3(name = "flush_db")]
78 fn py_flush_db(&mut self) -> PyResult<()> {
79 self.flush().map_err(to_pyruntime_err)
80 }
81
82 #[pyo3(name = "load")]
83 fn py_load(&self) -> PyResult<std::collections::HashMap<String, Vec<u8>>> {
84 get_runtime()
85 .block_on(async { DatabaseQueries::load(&self.pool).await })
86 .map(|m| m.into_iter().collect())
87 .map_err(to_pyruntime_err)
88 }
89
90 #[pyo3(name = "load_currency")]
91 fn py_load_currency(&self, code: &str) -> PyResult<Option<Currency>> {
92 let result = get_runtime()
93 .block_on(async { DatabaseQueries::load_currency(&self.pool, code).await });
94 result.map_err(to_pyruntime_err)
95 }
96
97 #[pyo3(name = "load_currencies")]
98 fn py_load_currencies(&self) -> PyResult<Vec<Currency>> {
99 let result =
100 get_runtime().block_on(async { DatabaseQueries::load_currencies(&self.pool).await });
101 result.map_err(to_pyruntime_err)
102 }
103
104 #[pyo3(name = "load_instrument")]
105 fn py_load_instrument(
106 &self,
107 py: Python,
108 instrument_id: InstrumentId,
109 ) -> PyResult<Option<Py<PyAny>>> {
110 get_runtime().block_on(async {
111 let result = DatabaseQueries::load_instrument(&self.pool, &instrument_id)
112 .await
113 .map_err(to_pyruntime_err)?;
114
115 match result {
116 Some(instrument) => {
117 let py_object = instrument_any_to_pyobject(py, instrument)?;
118 Ok(Some(py_object))
119 }
120 None => Ok(None),
121 }
122 })
123 }
124
125 #[pyo3(name = "load_instruments")]
126 fn py_load_instruments(&self, py: Python) -> PyResult<Vec<Py<PyAny>>> {
127 get_runtime().block_on(async {
128 let result = DatabaseQueries::load_instruments(&self.pool)
129 .await
130 .map_err(to_pyruntime_err)?;
131 let mut instruments = Vec::new();
132
133 for instrument in result {
134 let py_object = instrument_any_to_pyobject(py, instrument)?;
135 instruments.push(py_object);
136 }
137 Ok(instruments)
138 })
139 }
140
141 #[pyo3(name = "load_order")]
142 fn py_load_order(
143 &self,
144 py: Python,
145 client_order_id: ClientOrderId,
146 ) -> PyResult<Option<Py<PyAny>>> {
147 get_runtime().block_on(async {
148 let result =
149 DatabaseQueries::load_order(&self.pool, &client_order_id, &self.trader_id())
150 .await
151 .map_err(to_pyruntime_err)?;
152
153 match result {
154 Some(order) => {
155 let py_object = order_any_to_pyobject(py, order)?;
156 Ok(Some(py_object))
157 }
158 None => Ok(None),
159 }
160 })
161 }
162
163 #[pyo3(name = "load_account")]
164 fn py_load_account(&self, py: Python, account_id: AccountId) -> PyResult<Option<Py<PyAny>>> {
165 get_runtime().block_on(async {
166 let result = DatabaseQueries::load_account(&self.pool, &account_id, &self.trader_id())
167 .await
168 .map_err(to_pyruntime_err)?;
169
170 match result {
171 Some(account) => {
172 let py_object = account_any_to_pyobject(py, account)?;
173 Ok(Some(py_object))
174 }
175 None => Ok(None),
176 }
177 })
178 }
179
180 #[pyo3(name = "load_quotes")]
181 fn py_load_quotes(&self, py: Python, instrument_id: InstrumentId) -> PyResult<Vec<Py<PyAny>>> {
182 get_runtime().block_on(async {
183 let result = DatabaseQueries::load_quotes(&self.pool, &instrument_id)
184 .await
185 .map_err(to_pyruntime_err)?;
186 let mut quotes = Vec::new();
187
188 for quote in result {
189 let py_object = quote.into_py_any(py)?;
190 quotes.push(py_object);
191 }
192 Ok(quotes)
193 })
194 }
195
196 #[pyo3(name = "load_trades")]
197 fn py_load_trades(&self, py: Python, instrument_id: InstrumentId) -> PyResult<Vec<Py<PyAny>>> {
198 get_runtime().block_on(async {
199 let result = DatabaseQueries::load_trades(&self.pool, &instrument_id)
200 .await
201 .map_err(to_pyruntime_err)?;
202 let mut trades = Vec::new();
203
204 for trade in result {
205 let py_object = trade.into_py_any(py)?;
206 trades.push(py_object);
207 }
208 Ok(trades)
209 })
210 }
211
212 #[pyo3(name = "load_bars")]
213 fn py_load_bars(&self, py: Python, instrument_id: InstrumentId) -> PyResult<Vec<Py<PyAny>>> {
214 get_runtime().block_on(async {
215 let result = DatabaseQueries::load_bars(&self.pool, &instrument_id)
216 .await
217 .map_err(to_pyruntime_err)?;
218 let mut bars = Vec::new();
219
220 for bar in result {
221 let py_object = bar.into_py_any(py)?;
222 bars.push(py_object);
223 }
224 Ok(bars)
225 })
226 }
227
228 #[pyo3(name = "load_signals")]
229 fn py_load_signals(&self, name: &str) -> PyResult<Vec<Signal>> {
230 get_runtime().block_on(async {
231 DatabaseQueries::load_signals(&self.pool, name)
232 .await
233 .map_err(to_pyruntime_err)
234 })
235 }
236
237 #[pyo3(name = "load_custom_data")]
238 #[expect(clippy::needless_pass_by_value)]
239 fn py_load_custom_data(&self, data_type: DataType) -> PyResult<Vec<CustomData>> {
240 get_runtime()
241 .block_on(async { DatabaseQueries::load_custom_data(&self.pool, &data_type).await })
242 .map_err(to_pyruntime_err)
243 }
244
245 #[pyo3(name = "load_order_snapshot")]
246 fn py_load_order_snapshot(
247 &self,
248 client_order_id: ClientOrderId,
249 ) -> PyResult<Option<OrderSnapshot>> {
250 get_runtime().block_on(async {
251 DatabaseQueries::load_order_snapshot(&self.pool, &client_order_id, &self.trader_id())
252 .await
253 .map_err(to_pyruntime_err)
254 })
255 }
256
257 #[pyo3(name = "load_position_snapshot")]
258 fn py_load_position_snapshot(
259 &self,
260 position_id: PositionId,
261 ) -> PyResult<Option<PositionSnapshot>> {
262 get_runtime().block_on(async {
263 DatabaseQueries::load_position_snapshot(&self.pool, &position_id, &self.trader_id())
264 .await
265 .map_err(to_pyruntime_err)
266 })
267 }
268
269 #[pyo3(name = "add")]
270 fn py_add(&self, key: String, value: Vec<u8>) -> PyResult<()> {
271 self.add(key, Bytes::from(value)).map_err(to_pyruntime_err)
272 }
273
274 #[pyo3(name = "add_currency")]
275 fn py_add_currency(&self, currency: Currency) -> PyResult<()> {
276 self.add_currency(¤cy).map_err(to_pyruntime_err)
277 }
278
279 #[pyo3(name = "add_instrument")]
280 fn py_add_instrument(&self, py: Python, instrument: Py<PyAny>) -> PyResult<()> {
281 let instrument_any = pyobject_to_instrument_any(py, instrument)?;
282 self.add_instrument(&instrument_any)
283 .map_err(to_pyruntime_err)
284 }
285
286 #[pyo3(name = "add_order")]
287 #[pyo3(signature = (order, client_id=None))]
288 fn py_add_order(
289 &self,
290 py: Python,
291 order: Py<PyAny>,
292 client_id: Option<ClientId>,
293 ) -> PyResult<()> {
294 let order_any = pyobject_to_order_any(py, order)?;
295 self.add_order(&order_any, client_id)
296 .map_err(to_pyruntime_err)
297 }
298
299 #[pyo3(name = "add_order_snapshot")]
300 #[expect(clippy::needless_pass_by_value)]
301 fn py_add_order_snapshot(&self, snapshot: OrderSnapshot) -> PyResult<()> {
302 self.add_order_snapshot(&snapshot).map_err(to_pyruntime_err)
303 }
304
305 #[pyo3(name = "add_position_snapshot")]
306 #[expect(clippy::needless_pass_by_value)]
307 fn py_add_position_snapshot(&self, snapshot: PositionSnapshot) -> PyResult<()> {
308 self.add_position_snapshot(&snapshot)
309 .map_err(to_pyruntime_err)
310 }
311
312 #[pyo3(name = "add_account")]
313 fn py_add_account(&self, py: Python, account: Py<PyAny>) -> PyResult<()> {
314 let account_any = pyobject_to_account_any(py, account)?;
315 self.add_account(&account_any).map_err(to_pyruntime_err)
316 }
317
318 #[pyo3(name = "add_quote")]
319 fn py_add_quote(&self, quote: QuoteTick) -> PyResult<()> {
320 self.add_quote("e).map_err(to_pyruntime_err)
321 }
322
323 #[pyo3(name = "add_trade")]
324 fn py_add_trade(&self, trade: TradeTick) -> PyResult<()> {
325 self.add_trade(&trade).map_err(to_pyruntime_err)
326 }
327
328 #[pyo3(name = "add_bar")]
329 fn py_add_bar(&self, bar: Bar) -> PyResult<()> {
330 self.add_bar(&bar).map_err(to_pyruntime_err)
331 }
332
333 #[pyo3(name = "add_signal")]
334 #[expect(clippy::needless_pass_by_value)]
335 fn py_add_signal(&self, signal: Signal) -> PyResult<()> {
336 self.add_signal(&signal).map_err(to_pyruntime_err)
337 }
338
339 #[pyo3(name = "add_custom_data")]
340 #[expect(clippy::needless_pass_by_value)]
341 fn py_add_custom_data(&self, data: CustomData) -> PyResult<()> {
342 self.add_custom_data(&data).map_err(to_pyruntime_err)
343 }
344
345 #[pyo3(name = "update_order")]
346 fn py_update_order(&self, py: Python, order_event: Py<PyAny>) -> PyResult<()> {
347 let event = pyobject_to_order_event(py, order_event)?;
348 self.update_order(&event).map_err(to_pyruntime_err)
349 }
350
351 #[pyo3(name = "update_account")]
352 fn py_update_account(&self, py: Python, order: Py<PyAny>) -> PyResult<()> {
353 let order_any = pyobject_to_account_any(py, order)?;
354 self.update_account(&order_any).map_err(to_pyruntime_err)
355 }
356}
357
358#[pymethods]
359#[pyo3_stub_gen::derive::gen_stub_pymethods]
360impl PostgresCacheConfig {
361 #[new]
365 #[pyo3(signature = (host=None, port=None, username=None, password=None, database=None))]
366 fn py_new(
367 host: Option<String>,
368 port: Option<u16>,
369 username: Option<String>,
370 password: Option<String>,
371 database: Option<String>,
372 ) -> Self {
373 Self {
374 host,
375 port,
376 username,
377 password,
378 database,
379 }
380 }
381
382 #[getter]
383 fn host(&self) -> Option<&str> {
384 self.host.as_deref()
385 }
386
387 #[getter]
388 const fn port(&self) -> Option<u16> {
389 self.port
390 }
391
392 #[getter]
393 fn username(&self) -> Option<&str> {
394 self.username.as_deref()
395 }
396
397 #[getter]
398 fn password(&self) -> Option<&str> {
399 self.password.as_deref()
400 }
401
402 #[getter]
403 fn database(&self) -> Option<&str> {
404 self.database.as_deref()
405 }
406}
407
408#[expect(clippy::needless_pass_by_value)]
409fn extract_postgres_cache_database_factory(
410 py: Python<'_>,
411 factory: Py<PyAny>,
412) -> PyResult<Box<dyn CacheDatabaseFactory>> {
413 Ok(Box::new(factory.extract::<PostgresCacheConfig>(py)?))
414}
415
416pub(in crate::python) fn register_postgres_cache_database_factory() -> PyResult<()> {
417 get_global_cache_database_factory_registry()
418 .register(
419 stringify!(PostgresCacheConfig).to_string(),
420 extract_postgres_cache_database_factory,
421 )
422 .map_err(to_pyruntime_err)
423}