Skip to main content

nautilus_okx/common/
urls.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//! URL helpers and endpoint metadata for OKX services.
17
18use crate::common::enums::{OKXEnvironment, OKXRegion};
19
20// Global region (accounts registered on www.okx.com).
21const OKX_HTTP_URL: &str = "https://www.okx.com";
22const OKX_WS_PUBLIC_URL: &str = "wss://ws.okx.com:8443/ws/v5/public";
23const OKX_WS_PRIVATE_URL: &str = "wss://ws.okx.com:8443/ws/v5/private";
24const OKX_WS_BUSINESS_URL: &str = "wss://ws.okx.com:8443/ws/v5/business";
25const OKX_DEMO_WS_PUBLIC_URL: &str = "wss://wspap.okx.com:8443/ws/v5/public";
26const OKX_DEMO_WS_PRIVATE_URL: &str = "wss://wspap.okx.com:8443/ws/v5/private";
27const OKX_DEMO_WS_BUSINESS_URL: &str = "wss://wspap.okx.com:8443/ws/v5/business";
28
29// EEA region (European Economic Area, accounts registered on my.okx.com).
30const OKX_EEA_HTTP_URL: &str = "https://eea.okx.com";
31const OKX_EEA_WS_PUBLIC_URL: &str = "wss://wseea.okx.com:8443/ws/v5/public";
32const OKX_EEA_WS_PRIVATE_URL: &str = "wss://wseea.okx.com:8443/ws/v5/private";
33const OKX_EEA_WS_BUSINESS_URL: &str = "wss://wseea.okx.com:8443/ws/v5/business";
34const OKX_EEA_DEMO_WS_PUBLIC_URL: &str = "wss://wseeapap.okx.com:8443/ws/v5/public";
35const OKX_EEA_DEMO_WS_PRIVATE_URL: &str = "wss://wseeapap.okx.com:8443/ws/v5/private";
36const OKX_EEA_DEMO_WS_BUSINESS_URL: &str = "wss://wseeapap.okx.com:8443/ws/v5/business";
37
38// US region (United States and Australia, accounts registered on app.okx.com).
39const OKX_US_HTTP_URL: &str = "https://us.okx.com";
40const OKX_US_WS_PUBLIC_URL: &str = "wss://wsus.okx.com:8443/ws/v5/public";
41const OKX_US_WS_PRIVATE_URL: &str = "wss://wsus.okx.com:8443/ws/v5/private";
42const OKX_US_WS_BUSINESS_URL: &str = "wss://wsus.okx.com:8443/ws/v5/business";
43const OKX_US_DEMO_WS_PUBLIC_URL: &str = "wss://wsuspap.okx.com:8443/ws/v5/public";
44const OKX_US_DEMO_WS_PRIVATE_URL: &str = "wss://wsuspap.okx.com:8443/ws/v5/private";
45const OKX_US_DEMO_WS_BUSINESS_URL: &str = "wss://wsuspap.okx.com:8443/ws/v5/business";
46
47/// OKX endpoint types for determining URL and authentication requirements.
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49#[cfg_attr(feature = "python", pyo3::pyclass(from_py_object))]
50#[cfg_attr(
51    feature = "python",
52    pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.okx")
53)]
54pub enum OKXEndpointType {
55    Public,
56    Private,
57    Business,
58}
59
60/// Checks if endpoint requires authentication.
61pub fn requires_authentication(endpoint_type: OKXEndpointType) -> bool {
62    matches!(
63        endpoint_type,
64        OKXEndpointType::Private | OKXEndpointType::Business
65    )
66}
67
68/// Returns the HTTP base URL for the given region.
69///
70/// The REST host is region-only; demo trading reuses the live host together
71/// with the `x-simulated-trading` header.
72#[must_use]
73pub const fn get_http_base_url(region: OKXRegion) -> &'static str {
74    match region {
75        OKXRegion::Global => OKX_HTTP_URL,
76        OKXRegion::Eea => OKX_EEA_HTTP_URL,
77        OKXRegion::Us => OKX_US_HTTP_URL,
78    }
79}
80
81/// Returns the WebSocket base URL for public data (market data).
82#[must_use]
83pub fn get_ws_base_url_public(region: OKXRegion, environment: OKXEnvironment) -> &'static str {
84    match (region, environment) {
85        (OKXRegion::Global, OKXEnvironment::Live) => OKX_WS_PUBLIC_URL,
86        (OKXRegion::Global, OKXEnvironment::Demo) => OKX_DEMO_WS_PUBLIC_URL,
87        (OKXRegion::Eea, OKXEnvironment::Live) => OKX_EEA_WS_PUBLIC_URL,
88        (OKXRegion::Eea, OKXEnvironment::Demo) => OKX_EEA_DEMO_WS_PUBLIC_URL,
89        (OKXRegion::Us, OKXEnvironment::Live) => OKX_US_WS_PUBLIC_URL,
90        (OKXRegion::Us, OKXEnvironment::Demo) => OKX_US_DEMO_WS_PUBLIC_URL,
91    }
92}
93
94/// Returns the WebSocket base URL for private data (account/order management).
95#[must_use]
96pub fn get_ws_base_url_private(region: OKXRegion, environment: OKXEnvironment) -> &'static str {
97    match (region, environment) {
98        (OKXRegion::Global, OKXEnvironment::Live) => OKX_WS_PRIVATE_URL,
99        (OKXRegion::Global, OKXEnvironment::Demo) => OKX_DEMO_WS_PRIVATE_URL,
100        (OKXRegion::Eea, OKXEnvironment::Live) => OKX_EEA_WS_PRIVATE_URL,
101        (OKXRegion::Eea, OKXEnvironment::Demo) => OKX_EEA_DEMO_WS_PRIVATE_URL,
102        (OKXRegion::Us, OKXEnvironment::Live) => OKX_US_WS_PRIVATE_URL,
103        (OKXRegion::Us, OKXEnvironment::Demo) => OKX_US_DEMO_WS_PRIVATE_URL,
104    }
105}
106
107/// Returns the WebSocket base URL for business data (bars/candlesticks).
108#[must_use]
109pub fn get_ws_base_url_business(region: OKXRegion, environment: OKXEnvironment) -> &'static str {
110    match (region, environment) {
111        (OKXRegion::Global, OKXEnvironment::Live) => OKX_WS_BUSINESS_URL,
112        (OKXRegion::Global, OKXEnvironment::Demo) => OKX_DEMO_WS_BUSINESS_URL,
113        (OKXRegion::Eea, OKXEnvironment::Live) => OKX_EEA_WS_BUSINESS_URL,
114        (OKXRegion::Eea, OKXEnvironment::Demo) => OKX_EEA_DEMO_WS_BUSINESS_URL,
115        (OKXRegion::Us, OKXEnvironment::Live) => OKX_US_WS_BUSINESS_URL,
116        (OKXRegion::Us, OKXEnvironment::Demo) => OKX_US_DEMO_WS_BUSINESS_URL,
117    }
118}
119
120/// Derives a WebSocket URL for a given channel from a base URL.
121///
122/// Replaces the last path segment (`/public`, `/private`, or `/business`)
123/// with the target channel. If no recognized segment is found, appends
124/// `/{channel}` to the path.
125#[must_use]
126pub fn derive_ws_url(base_url: &str, channel: &str) -> String {
127    let url = base_url.trim_end_matches('/');
128    for suffix in ["/public", "/private", "/business"] {
129        if let Some(base) = url.strip_suffix(suffix) {
130            return format!("{base}/{channel}");
131        }
132    }
133    format!("{url}/{channel}")
134}
135
136/// Returns WebSocket URL by endpoint type.
137#[must_use]
138pub fn get_ws_url(
139    endpoint_type: OKXEndpointType,
140    region: OKXRegion,
141    environment: OKXEnvironment,
142) -> &'static str {
143    match endpoint_type {
144        OKXEndpointType::Public => get_ws_base_url_public(region, environment),
145        OKXEndpointType::Private => get_ws_base_url_private(region, environment),
146        OKXEndpointType::Business => get_ws_base_url_business(region, environment),
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use rstest::rstest;
153
154    use super::*;
155
156    #[rstest]
157    fn test_endpoint_authentication() {
158        assert!(!requires_authentication(OKXEndpointType::Public));
159        assert!(requires_authentication(OKXEndpointType::Private));
160        assert!(requires_authentication(OKXEndpointType::Business));
161    }
162
163    #[rstest]
164    #[case(OKXRegion::Global, "https://www.okx.com")]
165    #[case(OKXRegion::Eea, "https://eea.okx.com")]
166    #[case(OKXRegion::Us, "https://us.okx.com")]
167    fn test_http_base_url(#[case] region: OKXRegion, #[case] expected: &str) {
168        assert_eq!(get_http_base_url(region), expected);
169    }
170
171    #[rstest]
172    #[case(
173        OKXRegion::Global,
174        "wss://ws.okx.com:8443/ws/v5/public",
175        "wss://ws.okx.com:8443/ws/v5/private",
176        "wss://ws.okx.com:8443/ws/v5/business"
177    )]
178    #[case(
179        OKXRegion::Eea,
180        "wss://wseea.okx.com:8443/ws/v5/public",
181        "wss://wseea.okx.com:8443/ws/v5/private",
182        "wss://wseea.okx.com:8443/ws/v5/business"
183    )]
184    #[case(
185        OKXRegion::Us,
186        "wss://wsus.okx.com:8443/ws/v5/public",
187        "wss://wsus.okx.com:8443/ws/v5/private",
188        "wss://wsus.okx.com:8443/ws/v5/business"
189    )]
190    fn test_ws_urls_live(
191        #[case] region: OKXRegion,
192        #[case] public: &str,
193        #[case] private: &str,
194        #[case] business: &str,
195    ) {
196        assert_eq!(get_ws_base_url_public(region, OKXEnvironment::Live), public);
197        assert_eq!(
198            get_ws_base_url_private(region, OKXEnvironment::Live),
199            private
200        );
201        assert_eq!(
202            get_ws_base_url_business(region, OKXEnvironment::Live),
203            business
204        );
205    }
206
207    #[rstest]
208    #[case(
209        OKXRegion::Global,
210        "wss://wspap.okx.com:8443/ws/v5/public",
211        "wss://wspap.okx.com:8443/ws/v5/private",
212        "wss://wspap.okx.com:8443/ws/v5/business"
213    )]
214    #[case(
215        OKXRegion::Eea,
216        "wss://wseeapap.okx.com:8443/ws/v5/public",
217        "wss://wseeapap.okx.com:8443/ws/v5/private",
218        "wss://wseeapap.okx.com:8443/ws/v5/business"
219    )]
220    #[case(
221        OKXRegion::Us,
222        "wss://wsuspap.okx.com:8443/ws/v5/public",
223        "wss://wsuspap.okx.com:8443/ws/v5/private",
224        "wss://wsuspap.okx.com:8443/ws/v5/business"
225    )]
226    fn test_ws_urls_demo(
227        #[case] region: OKXRegion,
228        #[case] public: &str,
229        #[case] private: &str,
230        #[case] business: &str,
231    ) {
232        assert_eq!(get_ws_base_url_public(region, OKXEnvironment::Demo), public);
233        assert_eq!(
234            get_ws_base_url_private(region, OKXEnvironment::Demo),
235            private
236        );
237        assert_eq!(
238            get_ws_base_url_business(region, OKXEnvironment::Demo),
239            business
240        );
241    }
242
243    #[rstest]
244    #[case(
245        "wss://ws.okx.com:8443/ws/v5/public",
246        "business",
247        "wss://ws.okx.com:8443/ws/v5/business"
248    )]
249    #[case(
250        "wss://wseea.okx.com:8443/ws/v5/public",
251        "private",
252        "wss://wseea.okx.com:8443/ws/v5/private"
253    )]
254    #[case(
255        "wss://wseea.okx.com:8443/ws/v5/private",
256        "business",
257        "wss://wseea.okx.com:8443/ws/v5/business"
258    )]
259    #[case(
260        "wss://wseea.okx.com:8443/ws/v5/private/",
261        "business",
262        "wss://wseea.okx.com:8443/ws/v5/business"
263    )]
264    #[case(
265        "wss://custom.proxy:8443/ws/v5",
266        "business",
267        "wss://custom.proxy:8443/ws/v5/business"
268    )]
269    fn test_derive_ws_url(#[case] base_url: &str, #[case] channel: &str, #[case] expected: &str) {
270        assert_eq!(derive_ws_url(base_url, channel), expected);
271    }
272
273    #[rstest]
274    #[case(OKXRegion::Global)]
275    #[case(OKXRegion::Eea)]
276    #[case(OKXRegion::Us)]
277    fn test_get_ws_url_by_type(#[case] region: OKXRegion) {
278        assert_eq!(
279            get_ws_url(OKXEndpointType::Public, region, OKXEnvironment::Live),
280            get_ws_base_url_public(region, OKXEnvironment::Live)
281        );
282        assert_eq!(
283            get_ws_url(OKXEndpointType::Private, region, OKXEnvironment::Live),
284            get_ws_base_url_private(region, OKXEnvironment::Live)
285        );
286        assert_eq!(
287            get_ws_url(OKXEndpointType::Business, region, OKXEnvironment::Live),
288            get_ws_base_url_business(region, OKXEnvironment::Live)
289        );
290    }
291}