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