1use std::{any::Any, cell::RefCell, rc::Rc, sync::Arc};
19
20use nautilus_common::{
21 cache::Cache,
22 clients::{DataClient, ExecutionClient},
23 clock::Clock,
24 factories::{ClientConfig, DataClientFactory, ExecutionClientFactory},
25};
26use nautilus_live::ExecutionClientCore;
27use nautilus_model::{
28 enums::{AccountType, OmsType},
29 identifiers::ClientId,
30};
31use nautilus_network::retry::RetryConfig;
32
33use crate::{
34 common::consts::POLYMARKET_VENUE,
35 config::{PolymarketDataClientConfig, PolymarketExecClientConfig},
36 data::PolymarketDataClient,
37 execution::PolymarketExecutionClient,
38 http::{
39 clob::PolymarketClobPublicClient, data_api::PolymarketDataApiHttpClient,
40 gamma::PolymarketGammaHttpClient,
41 },
42 websocket::client::PolymarketWebSocketClient,
43};
44
45impl ClientConfig for PolymarketDataClientConfig {
46 fn as_any(&self) -> &dyn Any {
47 self
48 }
49}
50
51#[cfg_attr(
53 feature = "python",
54 pyo3::pyclass(
55 module = "nautilus_trader.core.nautilus_pyo3.polymarket",
56 from_py_object
57 )
58)]
59#[derive(Debug, Clone)]
60pub struct PolymarketDataClientFactory;
61
62impl DataClientFactory for PolymarketDataClientFactory {
63 fn create(
64 &self,
65 name: &str,
66 config: &dyn ClientConfig,
67 _cache: Rc<RefCell<Cache>>,
68 _clock: Rc<RefCell<dyn Clock>>,
69 ) -> anyhow::Result<Box<dyn DataClient>> {
70 let polymarket_config = config
71 .as_any()
72 .downcast_ref::<PolymarketDataClientConfig>()
73 .ok_or_else(|| {
74 anyhow::anyhow!(
75 "Invalid config type for PolymarketDataClientFactory. Expected PolymarketDataClientConfig, was {config:?}",
76 )
77 })?
78 .clone();
79
80 let client_id = ClientId::from(name);
81
82 let gamma_client = PolymarketGammaHttpClient::new(
83 Some(polymarket_config.gamma_url()),
84 polymarket_config.http_timeout_secs,
85 RetryConfig {
86 max_retries: 10,
87 initial_delay_ms: 5_000,
88 max_delay_ms: 30_000,
89 backoff_factor: 1.5,
90 jitter_ms: 2_000,
91 operation_timeout_ms: Some(30_000),
92 immediate_first: true,
93 max_elapsed_ms: Some(300_000),
94 },
95 )?;
96
97 let clob_public_client = PolymarketClobPublicClient::new(
98 polymarket_config.base_url_http.clone(),
99 polymarket_config.http_timeout_secs,
100 )?;
101
102 let data_api_client = PolymarketDataApiHttpClient::new(
103 Some(polymarket_config.data_api_url()),
104 polymarket_config.http_timeout_secs,
105 )?;
106
107 let ws_client = PolymarketWebSocketClient::new_market(
108 polymarket_config.base_url_ws.clone(),
109 polymarket_config.subscribe_new_markets,
110 polymarket_config.transport_backend,
111 );
112
113 let mut client = PolymarketDataClient::new(
114 client_id,
115 polymarket_config.clone(),
116 gamma_client,
117 clob_public_client,
118 data_api_client,
119 ws_client,
120 );
121
122 for filter in &polymarket_config.filters {
123 client.add_instrument_filter(Arc::clone(filter));
124 }
125
126 Ok(Box::new(client))
127 }
128
129 fn name(&self) -> &'static str {
130 "POLYMARKET"
131 }
132
133 fn config_type(&self) -> &'static str {
134 "PolymarketDataClientConfig"
135 }
136}
137
138impl ClientConfig for PolymarketExecClientConfig {
139 fn as_any(&self) -> &dyn Any {
140 self
141 }
142}
143
144#[cfg_attr(
146 feature = "python",
147 pyo3::pyclass(
148 module = "nautilus_trader.core.nautilus_pyo3.polymarket",
149 from_py_object
150 )
151)]
152#[derive(Debug, Clone)]
153pub struct PolymarketExecutionClientFactory;
154
155impl ExecutionClientFactory for PolymarketExecutionClientFactory {
156 fn create(
157 &self,
158 name: &str,
159 config: &dyn ClientConfig,
160 cache: Rc<RefCell<Cache>>,
161 ) -> anyhow::Result<Box<dyn ExecutionClient>> {
162 let polymarket_config = config
163 .as_any()
164 .downcast_ref::<PolymarketExecClientConfig>()
165 .ok_or_else(|| {
166 anyhow::anyhow!(
167 "Invalid config type for PolymarketExecutionClientFactory. Expected PolymarketExecClientConfig, was {config:?}",
168 )
169 })?
170 .clone();
171
172 let oms_type = OmsType::Netting;
173 let account_type = AccountType::Cash;
174
175 let client_id = ClientId::from(name);
176 let core = ExecutionClientCore::new(
177 polymarket_config.trader_id,
178 client_id,
179 *POLYMARKET_VENUE,
180 oms_type,
181 polymarket_config.account_id,
182 account_type,
183 None, cache,
185 );
186
187 let client = PolymarketExecutionClient::new(core, polymarket_config)?;
188
189 Ok(Box::new(client))
190 }
191
192 fn name(&self) -> &'static str {
193 "POLYMARKET"
194 }
195
196 fn config_type(&self) -> &'static str {
197 "PolymarketExecClientConfig"
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use std::{cell::RefCell, rc::Rc};
204
205 use nautilus_common::{
206 cache::Cache,
207 clock::TestClock,
208 factories::{ClientConfig, DataClientFactory, ExecutionClientFactory},
209 };
210 use rstest::rstest;
211
212 use super::*;
213 use crate::config::{PolymarketDataClientConfig, PolymarketExecClientConfig};
214
215 #[derive(Debug)]
216 struct WrongConfig;
217
218 impl ClientConfig for WrongConfig {
219 fn as_any(&self) -> &dyn std::any::Any {
220 self
221 }
222 }
223
224 #[rstest]
225 fn test_polymarket_data_client_factory_creation() {
226 let factory = PolymarketDataClientFactory;
227 assert_eq!(factory.name(), "POLYMARKET");
228 assert_eq!(factory.config_type(), "PolymarketDataClientConfig");
229 }
230
231 #[rstest]
232 fn test_polymarket_data_client_config_implements_client_config() {
233 let config = PolymarketDataClientConfig::default();
234 let boxed_config: Box<dyn ClientConfig> = Box::new(config);
235 let downcasted = boxed_config
236 .as_any()
237 .downcast_ref::<PolymarketDataClientConfig>();
238 assert!(downcasted.is_some());
239 }
240
241 #[rstest]
242 fn test_polymarket_data_client_factory_rejects_wrong_config_type() {
243 let factory = PolymarketDataClientFactory;
244 let wrong_config = WrongConfig;
245 let cache = Rc::new(RefCell::new(Cache::default()));
246 let clock = Rc::new(RefCell::new(TestClock::new()));
247
248 let result = factory.create("POLYMARKET", &wrong_config, cache, clock);
249 assert!(result.is_err());
250 assert!(
251 result
252 .err()
253 .unwrap()
254 .to_string()
255 .contains("Invalid config type")
256 );
257 }
258
259 #[rstest]
260 fn test_polymarket_execution_client_factory_creation() {
261 let factory = PolymarketExecutionClientFactory;
262 assert_eq!(factory.name(), "POLYMARKET");
263 assert_eq!(factory.config_type(), "PolymarketExecClientConfig");
264 }
265
266 #[rstest]
267 fn test_polymarket_exec_client_config_implements_client_config() {
268 let config = PolymarketExecClientConfig::default();
269 let boxed_config: Box<dyn ClientConfig> = Box::new(config);
270 let downcasted = boxed_config
271 .as_any()
272 .downcast_ref::<PolymarketExecClientConfig>();
273 assert!(downcasted.is_some());
274 }
275
276 #[rstest]
277 fn test_polymarket_execution_client_factory_rejects_wrong_config_type() {
278 let factory = PolymarketExecutionClientFactory;
279 let wrong_config = WrongConfig;
280 let cache = Rc::new(RefCell::new(Cache::default()));
281
282 let result = factory.create("POLYMARKET", &wrong_config, cache);
283 assert!(result.is_err());
284 assert!(
285 result
286 .err()
287 .unwrap()
288 .to_string()
289 .contains("Invalid config type")
290 );
291 }
292}