nautilus_okx/common/consts.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//! Core constants shared across the OKX adapter components.
17
18use std::sync::LazyLock;
19
20use ahash::AHashSet;
21use nautilus_model::{
22 enums::{OrderType, TimeInForce},
23 identifiers::Venue,
24};
25use ustr::Ustr;
26
27use super::enums::OKXInstrumentType;
28
29pub const OKX: &str = "OKX";
30pub static OKX_VENUE: LazyLock<Venue> = LazyLock::new(|| Venue::new(Ustr::from(OKX)));
31
32/// See <https://www.okx.com/docs-v5/en/#overview-broker-program> for further details.
33pub const OKX_NAUTILUS_BROKER_ID: &str = "5328c82e5542BCDE";
34
35// Use the canonical host with www to avoid cross-domain redirects which may
36// strip authentication headers in some HTTP clients and middleboxes.
37pub const OKX_HTTP_URL: &str = "https://www.okx.com";
38pub const OKX_WS_PUBLIC_URL: &str = "wss://ws.okx.com:8443/ws/v5/public";
39pub const OKX_WS_PRIVATE_URL: &str = "wss://ws.okx.com:8443/ws/v5/private";
40pub const OKX_WS_BUSINESS_URL: &str = "wss://ws.okx.com:8443/ws/v5/business";
41pub const OKX_WS_DEMO_PUBLIC_URL: &str = "wss://wspap.okx.com:8443/ws/v5/public";
42pub const OKX_WS_DEMO_PRIVATE_URL: &str = "wss://wspap.okx.com:8443/ws/v5/private";
43pub const OKX_WS_DEMO_BUSINESS_URL: &str = "wss://wspap.okx.com:8443/ws/v5/business";
44
45pub const OKX_WS_TOPIC_DELIMITER: char = ':';
46
47/// WebSocket heartbeat (ping/pong) interval in seconds.
48pub const OKX_WS_HEARTBEAT_SECS: u64 = 20;
49
50/// OKX success response code for WebSocket operations.
51pub const OKX_SUCCESS_CODE: &str = "0";
52
53/// JSON field key for sub-error code in order operation responses.
54pub const OKX_FIELD_SCODE: &str = "sCode";
55
56/// JSON field key for sub-error message in order operation responses.
57pub const OKX_FIELD_SMSG: &str = "sMsg";
58
59/// JSON field key for client order ID in order operation responses.
60pub const OKX_FIELD_CLORDID: &str = "clOrdId";
61
62/// OKX supported order time in force.
63///
64/// # Notes
65///
66/// - OKX implements IOC and FOK as order types rather than separate time-in-force parameters.
67/// - FOK is only supported with Limit orders (Market + FOK is not supported).
68/// - IOC with Market orders uses OptimalLimitIoc, with Limit orders uses Ioc.
69/// - GTD is supported via expire_time parameter.
70pub const OKX_SUPPORTED_TIME_IN_FORCE: &[TimeInForce] = &[
71 TimeInForce::Gtc, // Good Till Cancel (default)
72 TimeInForce::Ioc, // Immediate or Cancel (mapped to OKXOrderType::Ioc or OptimalLimitIoc)
73 TimeInForce::Fok, // Fill or Kill (only with Limit orders, mapped to OKXOrderType::Fok)
74];
75
76/// OKX supported order types.
77///
78/// # Notes
79///
80/// - PostOnly is supported as a flag on limit orders.
81/// - Conditional orders (stop/trigger) are supported via algo orders.
82pub const OKX_SUPPORTED_ORDER_TYPES: &[OrderType] = &[
83 OrderType::Market,
84 OrderType::Limit,
85 OrderType::MarketToLimit, // Mapped to IOC when no price is specified
86 OrderType::StopMarket, // Supported via algo order API
87 OrderType::StopLimit, // Supported via algo order API
88 OrderType::MarketIfTouched, // Supported via algo order API
89 OrderType::LimitIfTouched, // Supported via algo order API
90 OrderType::TrailingStopMarket, // Supported via algo order API (move_order_stop)
91];
92
93/// Conditional order types that require the OKX algo order API.
94pub const OKX_CONDITIONAL_ORDER_TYPES: &[OrderType] = &[
95 OrderType::StopMarket,
96 OrderType::StopLimit,
97 OrderType::MarketIfTouched,
98 OrderType::LimitIfTouched,
99 OrderType::TrailingStopMarket,
100];
101
102/// Advance algo order types that require `cancel-advance-algos` for cancellation.
103/// These cannot be cancelled via the standard `cancel-algos` endpoint.
104pub const OKX_ADVANCE_ALGO_ORDER_TYPES: &[OrderType] = &[OrderType::TrailingStopMarket];
105
106/// OKX error codes that should trigger retries.
107///
108/// Only retry on temporary network/system issues.
109///
110/// # References
111///
112/// Based on OKX API documentation: <https://www.okx.com/docs-v5/en/#error-codes>
113pub static OKX_RETRY_ERROR_CODES: LazyLock<AHashSet<&'static str>> = LazyLock::new(|| {
114 let mut codes = AHashSet::new();
115
116 // Temporary system errors
117 codes.insert("50001"); // Service temporarily unavailable
118 codes.insert("50004"); // API endpoint request timeout (does not mean that the request was successful or failed, please check the request result)
119 codes.insert("50005"); // API is offline or unavailable
120 codes.insert("50013"); // System busy, please try again later
121 codes.insert("50026"); // System error, please try again later
122
123 // Rate limit errors (temporary)
124 codes.insert("50011"); // Request too frequent
125 codes.insert("50113"); // API requests exceed the limit
126
127 // WebSocket connection issues (temporary)
128 codes.insert("60001"); // OK not received in time
129 codes.insert("60005"); // Connection closed as there was no data transmission in the last 30 seconds
130
131 codes
132});
133
134/// Determines if an OKX error code should trigger a retry.
135pub fn should_retry_error_code(error_code: &str) -> bool {
136 OKX_RETRY_ERROR_CODES.contains(error_code)
137}
138
139/// OKX error code returned when a post-only order would immediately take liquidity.
140pub const OKX_POST_ONLY_ERROR_CODE: &str = "51019";
141
142/// OKX cancel source code used when a post-only order is auto-cancelled for taking liquidity.
143pub const OKX_POST_ONLY_CANCEL_SOURCE: &str = "31";
144
145/// Human-readable reason used when a post-only order is auto-cancelled for taking liquidity.
146pub const OKX_POST_ONLY_CANCEL_REASON: &str = "POST_ONLY would take liquidity";
147
148/// Target currency literal for base currency.
149pub const OKX_TARGET_CCY_BASE: &str = "base_ccy";
150
151/// Target currency literal for quote currency.
152pub const OKX_TARGET_CCY_QUOTE: &str = "quote_ccy";
153
154/// Resolves instrument families for a given instrument type.
155///
156/// Returns `Some(families)` when the type supports family filtering, or `None`
157/// to skip the instrument type entirely (Option without configured families).
158/// An empty vec means no family filter is needed (Spot, Margin).
159pub fn resolve_instrument_families(
160 configured: &Option<Vec<String>>,
161 inst_type: OKXInstrumentType,
162) -> Option<Vec<String>> {
163 match (configured, inst_type) {
164 (Some(families), OKXInstrumentType::Option) => Some(families.clone()),
165 (Some(families), OKXInstrumentType::Futures | OKXInstrumentType::Swap) => {
166 Some(families.clone())
167 }
168 (None, OKXInstrumentType::Option) => {
169 log::warn!("Skipping OPTION type: instrument_families required but not configured");
170 None
171 }
172 _ => Some(vec![]),
173 }
174}
175
176/// Clamps a requested book depth to the nearest OKX-supported value.
177///
178/// OKX WebSocket channels support depths of 50 and 400. Depth 0 means
179/// auto-select based on VIP level. Any other value rounds up to the nearest
180/// supported depth so the subscription succeeds and the data engine can
181/// truncate to the originally requested depth.
182pub fn resolve_book_depth(raw_depth: usize) -> usize {
183 match raw_depth {
184 0 | 400 => raw_depth,
185 1..=50 => 50,
186 _ => 400,
187 }
188}