Skip to main content

nautilus_betfair/python/
config.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 Betfair configuration.
17
18use nautilus_core::{python::to_pyvalue_err, string::secret::SecretString};
19use nautilus_live::book::DEFAULT_BOOK_SNAPSHOT_TIMEOUT_SECS;
20use nautilus_model::identifiers::AccountId;
21use pyo3::prelude::*;
22use rust_decimal::Decimal;
23
24use crate::config::{BetfairDataClientConfig, BetfairExecutionClientConfig};
25
26fn stringify_ids(values: Option<Vec<u64>>) -> Option<Vec<String>> {
27    values.map(|values| values.into_iter().map(|value| value.to_string()).collect())
28}
29
30#[pymethods]
31#[pyo3_stub_gen::derive::gen_stub_pymethods]
32impl BetfairDataClientConfig {
33    /// Configuration for the Betfair live data client.
34    #[new]
35    #[pyo3(signature = (
36        account_currency = None,
37        username = None,
38        password = None,
39        app_key = None,
40        proxy_url = None,
41        request_rate_per_second = 5,
42        default_min_notional = None,
43        event_type_ids = None,
44        event_type_names = None,
45        event_ids = None,
46        country_codes = None,
47        market_types = None,
48        market_ids = None,
49        min_market_start_time = None,
50        max_market_start_time = None,
51        stream_host = None,
52        stream_port = None,
53        stream_heartbeat_secs = None,
54        stream_heartbeat_timeout_secs = None,
55        stream_reconnect_delay_initial_ms = 2_000,
56        stream_reconnect_delay_max_ms = 30_000,
57        stream_use_tls = true,
58        stream_conflate_ms = None,
59        subscription_delay_secs = None,
60        subscribe_race_data = false,
61        subscribe_cricket_data = false,
62        book_snapshot_timeout_secs = None,
63    ))]
64    #[expect(clippy::too_many_arguments)]
65    fn py_new(
66        account_currency: Option<String>,
67        username: Option<String>,
68        password: Option<String>,
69        app_key: Option<String>,
70        proxy_url: Option<String>,
71        request_rate_per_second: u32,
72        default_min_notional: Option<Decimal>,
73        event_type_ids: Option<Vec<u64>>,
74        event_type_names: Option<Vec<String>>,
75        event_ids: Option<Vec<u64>>,
76        country_codes: Option<Vec<String>>,
77        market_types: Option<Vec<String>>,
78        market_ids: Option<Vec<String>>,
79        min_market_start_time: Option<String>,
80        max_market_start_time: Option<String>,
81        stream_host: Option<String>,
82        stream_port: Option<u16>,
83        stream_heartbeat_secs: Option<u64>,
84        stream_heartbeat_timeout_secs: Option<u64>,
85        stream_reconnect_delay_initial_ms: u64,
86        stream_reconnect_delay_max_ms: u64,
87        stream_use_tls: bool,
88        stream_conflate_ms: Option<u64>,
89        subscription_delay_secs: Option<u64>,
90        subscribe_race_data: bool,
91        subscribe_cricket_data: bool,
92        book_snapshot_timeout_secs: Option<u64>,
93    ) -> PyResult<Self> {
94        let config = Self {
95            account_currency: account_currency.unwrap_or_else(|| "GBP".to_string()),
96            username: username.map(SecretString::from),
97            password: password.map(SecretString::from),
98            app_key: app_key.map(SecretString::from),
99            proxy_url: proxy_url.map(SecretString::from),
100            request_rate_per_second,
101            default_min_notional,
102            event_type_ids: stringify_ids(event_type_ids),
103            event_type_names,
104            event_ids: stringify_ids(event_ids),
105            country_codes,
106            market_types,
107            market_ids,
108            min_market_start_time,
109            max_market_start_time,
110            stream_host,
111            stream_port,
112            stream_heartbeat_secs,
113            stream_heartbeat_timeout_secs,
114            stream_reconnect_delay_initial_ms,
115            stream_reconnect_delay_max_ms,
116            stream_use_tls,
117            stream_conflate_ms,
118            subscription_delay_secs: subscription_delay_secs.unwrap_or(3),
119            subscribe_race_data,
120            subscribe_cricket_data,
121            book_snapshot_timeout_secs: book_snapshot_timeout_secs
122                .unwrap_or(DEFAULT_BOOK_SNAPSHOT_TIMEOUT_SECS),
123        };
124
125        config.validate().map_err(to_pyvalue_err)?;
126        Ok(config)
127    }
128
129    /// Returns the username.
130    #[getter]
131    fn username(&self) -> Option<&str> {
132        self.username.as_ref().map(SecretString::expose_secret)
133    }
134
135    #[getter]
136    const fn has_proxy_url(&self) -> bool {
137        self.proxy_url.is_some()
138    }
139
140    fn __repr__(&self) -> String {
141        stringify!(BetfairDataClientConfig).to_string()
142    }
143}
144
145#[pymethods]
146#[pyo3_stub_gen::derive::gen_stub_pymethods]
147impl BetfairExecutionClientConfig {
148    /// Configuration for the Betfair live execution client.
149    #[new]
150    #[pyo3(signature = (
151        account_id = None,
152        account_currency = None,
153        username = None,
154        password = None,
155        app_key = None,
156        proxy_url = None,
157        request_rate_per_second = 5,
158        order_request_rate_per_second = 20,
159        stream_host = None,
160        stream_port = None,
161        stream_heartbeat_secs = None,
162        stream_heartbeat_timeout_secs = None,
163        stream_reconnect_delay_initial_ms = 2_000,
164        stream_reconnect_delay_max_ms = 30_000,
165        stream_use_tls = true,
166        stream_market_ids_filter = None,
167        ignore_external_orders = false,
168        calculate_account_state = true,
169        request_account_state_secs = 300,
170        reconcile_market_ids_only = false,
171        reconcile_market_ids = None,
172        use_market_version = false,
173        stream_gap_recovery_lookback_mins = 10,
174    ))]
175    #[expect(clippy::too_many_arguments)]
176    fn py_new(
177        account_id: Option<AccountId>,
178        account_currency: Option<String>,
179        username: Option<String>,
180        password: Option<String>,
181        app_key: Option<String>,
182        proxy_url: Option<String>,
183        request_rate_per_second: u32,
184        order_request_rate_per_second: u32,
185        stream_host: Option<String>,
186        stream_port: Option<u16>,
187        stream_heartbeat_secs: Option<u64>,
188        stream_heartbeat_timeout_secs: Option<u64>,
189        stream_reconnect_delay_initial_ms: u64,
190        stream_reconnect_delay_max_ms: u64,
191        stream_use_tls: bool,
192        stream_market_ids_filter: Option<Vec<String>>,
193        ignore_external_orders: bool,
194        calculate_account_state: bool,
195        request_account_state_secs: u64,
196        reconcile_market_ids_only: bool,
197        reconcile_market_ids: Option<Vec<String>>,
198        use_market_version: bool,
199        stream_gap_recovery_lookback_mins: u64,
200    ) -> PyResult<Self> {
201        let config = Self {
202            account_id: account_id.unwrap_or_else(|| AccountId::from("BETFAIR-001")),
203            account_currency: account_currency.unwrap_or_else(|| "GBP".to_string()),
204            username: username.map(SecretString::from),
205            password: password.map(SecretString::from),
206            app_key: app_key.map(SecretString::from),
207            proxy_url: proxy_url.map(SecretString::from),
208            request_rate_per_second,
209            order_request_rate_per_second,
210            stream_host,
211            stream_port,
212            stream_heartbeat_secs,
213            stream_heartbeat_timeout_secs,
214            stream_reconnect_delay_initial_ms,
215            stream_reconnect_delay_max_ms,
216            stream_use_tls,
217            stream_market_ids_filter,
218            ignore_external_orders,
219            calculate_account_state,
220            request_account_state_secs,
221            reconcile_market_ids_only,
222            reconcile_market_ids,
223            use_market_version,
224            stream_gap_recovery_lookback_mins,
225        };
226
227        config.validate().map_err(to_pyvalue_err)?;
228        Ok(config)
229    }
230
231    /// Returns the username.
232    #[getter]
233    fn username(&self) -> Option<&str> {
234        self.username.as_ref().map(SecretString::expose_secret)
235    }
236
237    #[getter]
238    const fn has_proxy_url(&self) -> bool {
239        self.proxy_url.is_some()
240    }
241
242    fn __repr__(&self) -> String {
243        stringify!(BetfairExecutionClientConfig).to_string()
244    }
245}