Skip to content

Commit 1805de3

Browse files
authored
Dev (#45)
* fix: watch mark price * fix(binance): correct markPrice stream name and enhance futures parsing - Fixed markPrice stream subscription from "@markprice" to "@markprice" to match Binance API - Added futures contract expiry timestamp parsing from deliveryDate field - Enhanced symbol parsing to include expiry suffix for futures contracts - Updated unified symbol format to include expiry information for futures - Improved mark price subscription logic with proper channel management - Added timestamp to datetime conversion for futures expiry dates
1 parent 8c544ff commit 1805de3

12 files changed

Lines changed: 515 additions & 109 deletions

File tree

‎ccxt-core/src/base_exchange/mod.rs‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,10 @@ impl BaseExchange {
9393
strategy_type: crate::retry_strategy::RetryStrategyType::Fixed,
9494
..crate::retry_strategy::RetryConfig::default()
9595
}),
96-
max_response_size: 10 * 1024 * 1024, // 10MB default
97-
max_request_size: 10 * 1024 * 1024, // 10MB default
98-
circuit_breaker: None, // Disabled by default for backward compatibility
99-
pool_max_idle_per_host: 10, // Default: 10 idle connections per host
96+
max_response_size: 128 * 1024 * 1024, // 128MB default
97+
max_request_size: 10 * 1024 * 1024, // 10MB default
98+
circuit_breaker: None, // Disabled by default for backward compatibility
99+
pool_max_idle_per_host: 10, // Default: 10 idle connections per host
100100
pool_idle_timeout: Duration::from_secs(90), // Default: 90 seconds
101101
};
102102

‎ccxt-core/src/http_client/config.rs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ impl Default for HttpConfig {
7878
proxy: None,
7979
enable_rate_limit: true,
8080
retry_config: None,
81-
max_response_size: 10 * 1024 * 1024,
82-
max_request_size: 10 * 1024 * 1024,
81+
max_response_size: 128 * 1024 * 1024, // 128MB default
82+
max_request_size: 10 * 1024 * 1024, // 10MB default
8383
circuit_breaker: None,
8484
pool_max_idle_per_host: 10,
8585
pool_idle_timeout: Duration::from_secs(90),

‎ccxt-core/src/ws_client/config.rs‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,14 @@ pub struct WsConfig {
156156
///
157157
/// Default: `DropOldest` to ensure most recent data is available.
158158
pub backpressure_strategy: BackpressureStrategy,
159+
/// Maximum message size in bytes.
160+
///
161+
/// Default: 128 MiB (134,217,728 bytes) if None.
162+
pub max_message_size: Option<usize>,
163+
/// Maximum frame size in bytes.
164+
///
165+
/// Default: 32 MiB (33,554,432 bytes) if None.
166+
pub max_frame_size: Option<usize>,
159167
}
160168

161169
impl Default for WsConfig {
@@ -175,6 +183,8 @@ impl Default for WsConfig {
175183
message_channel_capacity: DEFAULT_MESSAGE_CHANNEL_CAPACITY,
176184
write_channel_capacity: DEFAULT_WRITE_CHANNEL_CAPACITY,
177185
backpressure_strategy: BackpressureStrategy::default(),
186+
max_message_size: Some(128 * 1024 * 1024),
187+
max_frame_size: Some(32 * 1024 * 1024),
178188
}
179189
}
180190
}

‎ccxt-core/src/ws_client/mod.rs‎

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ use tokio::net::TcpStream;
3434
use tokio::sync::{Mutex, RwLock, mpsc};
3535
use tokio::time::{Duration, interval};
3636
use tokio_tungstenite::{
37-
MaybeTlsStream, WebSocketStream, connect_async, tungstenite::protocol::Message,
37+
MaybeTlsStream, WebSocketStream, connect_async_with_config,
38+
tungstenite::protocol::{Message, WebSocketConfig},
3839
};
3940
use tokio_util::sync::CancellationToken;
4041
use tracing::{debug, error, info, instrument, warn};
@@ -154,9 +155,17 @@ impl WsClient {
154155
let url = self.config.url.clone();
155156
info!("Initiating WebSocket connection");
156157

158+
let mut ws_config = WebSocketConfig::default();
159+
if let Some(limit) = self.config.max_message_size {
160+
ws_config.max_message_size = Some(limit);
161+
}
162+
if let Some(limit) = self.config.max_frame_size {
163+
ws_config.max_frame_size = Some(limit);
164+
}
165+
157166
match tokio::time::timeout(
158167
Duration::from_millis(self.config.connect_timeout),
159-
connect_async(&url),
168+
connect_async_with_config(&url, Some(ws_config), false),
160169
)
161170
.await
162171
{
@@ -214,6 +223,14 @@ impl WsClient {
214223
self.set_state(WsConnectionState::Connecting);
215224
let url = self.config.url.clone();
216225

226+
let mut ws_config = WebSocketConfig::default();
227+
if let Some(limit) = self.config.max_message_size {
228+
ws_config.max_message_size = Some(limit);
229+
}
230+
if let Some(limit) = self.config.max_frame_size {
231+
ws_config.max_frame_size = Some(limit);
232+
}
233+
217234
tokio::select! {
218235
biased;
219236
() = token.cancelled() => {
@@ -223,7 +240,7 @@ impl WsClient {
223240
}
224241
result = tokio::time::timeout(
225242
Duration::from_millis(self.config.connect_timeout),
226-
connect_async(&url),
243+
connect_async_with_config(&url, Some(ws_config), false),
227244
) => {
228245
match result {
229246
Ok(Ok((ws_stream, response))) => {

‎ccxt-exchanges/src/binance/parser/market.rs‎

Lines changed: 82 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use super::value_to_hashmap;
22
use ccxt_core::{
33
Result,
44
error::{Error, ParseError},
5+
parser_utils::timestamp_to_datetime,
56
types::{Market, MarketLimits, MarketPrecision, MarketType, MinMax},
67
};
78
use rust_decimal::Decimal;
@@ -32,6 +33,61 @@ pub fn parse_market(data: &Value) -> Result<Market> {
3233
let active = status == "TRADING";
3334
let margin = data["isMarginTradingAllowed"].as_bool().unwrap_or(false);
3435

36+
// Check for contract type to determine if this is a futures/swap market
37+
let contract_type = data["contractType"].as_str();
38+
let is_contract = contract_type.is_some();
39+
40+
let market_type = if let Some(ct) = contract_type {
41+
if ct == "PERPETUAL" {
42+
MarketType::Swap
43+
} else {
44+
MarketType::Futures
45+
}
46+
} else {
47+
MarketType::Spot
48+
};
49+
50+
let mut linear = None;
51+
let mut inverse = None;
52+
let mut settle_id = None;
53+
let mut settle = None;
54+
55+
if is_contract {
56+
if let Some(margin_asset) = data["marginAsset"].as_str() {
57+
settle_id = Some(margin_asset.to_string());
58+
settle = Some(margin_asset.to_string()); // In Binance, asset ID is usually the code
59+
60+
if margin_asset == quote_asset {
61+
linear = Some(true);
62+
inverse = Some(false);
63+
} else if margin_asset == base_asset {
64+
linear = Some(false);
65+
inverse = Some(true);
66+
}
67+
}
68+
}
69+
70+
let expiry_timestamp = if market_type == MarketType::Futures {
71+
data["deliveryDate"].as_i64().filter(|ts| *ts > 0)
72+
} else {
73+
None
74+
};
75+
76+
let expiry_datetime = expiry_timestamp.and_then(timestamp_to_datetime);
77+
78+
let expiry_suffix = if market_type == MarketType::Futures {
79+
symbol.rfind('_').and_then(|pos| {
80+
let suffix = &symbol[pos + 1..];
81+
if suffix.len() == 6 && suffix.chars().all(|c| c.is_ascii_digit()) {
82+
Some(suffix.to_string())
83+
} else {
84+
None
85+
}
86+
})
87+
} else {
88+
None
89+
};
90+
3591
let mut price_precision: Option<Decimal> = None;
3692
let mut amount_precision: Option<Decimal> = None;
3793
let mut min_amount: Option<Decimal> = None;
@@ -81,7 +137,24 @@ pub fn parse_market(data: &Value) -> Result<Market> {
81137
}
82138
}
83139

84-
let unified_symbol = format!("{}/{}", base_asset, quote_asset);
140+
let unified_symbol = if is_contract {
141+
if let Some(s) = &settle {
142+
if market_type == MarketType::Futures {
143+
if let Some(expiry) = &expiry_suffix {
144+
format!("{}/{}:{}-{}", base_asset, quote_asset, s, expiry)
145+
} else {
146+
format!("{}/{}:{}", base_asset, quote_asset, s)
147+
}
148+
} else {
149+
format!("{}/{}:{}", base_asset, quote_asset, s)
150+
}
151+
} else {
152+
format!("{}/{}", base_asset, quote_asset)
153+
}
154+
} else {
155+
format!("{}/{}", base_asset, quote_asset)
156+
};
157+
85158
let parsed_symbol = ccxt_core::symbol::SymbolParser::parse(&unified_symbol).ok();
86159

87160
Ok(Market {
@@ -90,21 +163,21 @@ pub fn parse_market(data: &Value) -> Result<Market> {
90163
parsed_symbol,
91164
base: base_asset.clone(),
92165
quote: quote_asset.clone(),
93-
settle: None,
94166
base_id: Some(base_asset),
95167
quote_id: Some(quote_asset),
96-
settle_id: None,
97-
market_type: MarketType::Spot,
168+
settle_id,
169+
settle,
170+
market_type,
98171
active,
99172
margin,
100-
contract: Some(false),
101-
linear: None,
102-
inverse: None,
173+
contract: Some(is_contract),
174+
linear,
175+
inverse,
103176
taker: Decimal::from_str("0.001").ok(),
104177
maker: Decimal::from_str("0.001").ok(),
105178
contract_size: None,
106-
expiry: None,
107-
expiry_datetime: None,
179+
expiry: expiry_timestamp,
180+
expiry_datetime,
108181
strike: None,
109182
option_type: None,
110183
percentage: Some(true),

‎ccxt-exchanges/src/binance/parser/mod.rs‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ pub use balance::{parse_balance, parse_balance_with_type};
1212
pub use funding::{parse_funding_rate, parse_funding_rate_history};
1313
pub use margin::{parse_margin_adjustment, parse_margin_loan};
1414
pub use market::{parse_currencies, parse_market};
15-
pub use misc::{parse_mark_prices, parse_stats_24hr, parse_trading_fee, parse_trading_limits};
15+
pub use misc::{
16+
parse_mark_prices, parse_stats_24hr, parse_trading_fee, parse_trading_limits,
17+
parse_ws_mark_price,
18+
};
1619
pub use ohlcv::{parse_ohlcvs, parse_ws_ohlcv};
1720
pub use order::parse_order;
1821
pub use orderbook::{parse_orderbook, parse_ws_orderbook};

0 commit comments

Comments
 (0)