use super::{Bybit, BybitOptions};
use ccxt_core::config::{ProxyConfig, RetryPolicy};
use ccxt_core::types::default_type::{DefaultSubType, DefaultType};
use ccxt_core::{ExchangeConfig, Result};
use serde_json::Value;
use std::collections::HashMap;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct BybitBuilder {
config: ExchangeConfig,
options: BybitOptions,
}
impl Default for BybitBuilder {
fn default() -> Self {
Self::new()
}
}
impl BybitBuilder {
pub fn new() -> Self {
Self {
config: ExchangeConfig {
id: "bybit".to_string(),
name: "Bybit".to_string(),
..Default::default()
},
options: BybitOptions::default(),
}
}
pub fn api_key(mut self, key: impl Into<String>) -> Self {
self.config.api_key = Some(ccxt_core::SecretString::new(key));
self
}
pub fn secret(mut self, secret: impl Into<String>) -> Self {
self.config.secret = Some(ccxt_core::SecretString::new(secret));
self
}
pub fn sandbox(mut self, enabled: bool) -> Self {
self.config.sandbox = enabled;
self.options.testnet = enabled;
self
}
pub fn testnet(mut self, enabled: bool) -> Self {
self.config.sandbox = enabled;
self.options.testnet = enabled;
self
}
pub fn account_type(mut self, account_type: impl Into<String>) -> Self {
self.options.account_type = account_type.into();
self
}
pub fn default_type(mut self, default_type: impl Into<DefaultType>) -> Self {
self.options.default_type = default_type.into();
self
}
pub fn default_sub_type(mut self, sub_type: DefaultSubType) -> Self {
self.options.default_sub_type = Some(sub_type);
self
}
pub fn recv_window(mut self, millis: u64) -> Self {
self.options.recv_window = millis;
self
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.config.timeout = timeout;
self
}
pub fn timeout_secs(mut self, seconds: u64) -> Self {
self.config.timeout = Duration::from_secs(seconds);
self
}
pub fn connect_timeout(mut self, timeout: Duration) -> Self {
self.config.connect_timeout = timeout;
self
}
pub fn connect_timeout_secs(mut self, seconds: u64) -> Self {
self.config.connect_timeout = Duration::from_secs(seconds);
self
}
pub fn retry_policy(mut self, policy: RetryPolicy) -> Self {
self.config.retry_policy = Some(policy);
self
}
pub fn enable_rate_limit(mut self, enabled: bool) -> Self {
self.config.enable_rate_limit = enabled;
self
}
pub fn proxy(mut self, proxy: ProxyConfig) -> Self {
self.config.proxy = Some(proxy);
self
}
pub fn proxy_url(mut self, url: impl Into<String>) -> Self {
self.config.proxy = Some(ProxyConfig::new(url));
self
}
pub fn verbose(mut self, enabled: bool) -> Self {
self.config.verbose = enabled;
self
}
pub fn option(mut self, key: impl Into<String>, value: Value) -> Self {
self.config.options.insert(key.into(), value);
self
}
pub fn options(mut self, options: HashMap<String, Value>) -> Self {
self.config.options.extend(options);
self
}
#[cfg(test)]
pub fn get_config(&self) -> &ExchangeConfig {
&self.config
}
#[cfg(test)]
pub fn get_options(&self) -> &BybitOptions {
&self.options
}
pub fn build(self) -> Result<Bybit> {
Bybit::new_with_options(self.config, self.options)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_default() {
let builder = BybitBuilder::new();
assert_eq!(builder.config.id, "bybit");
assert_eq!(builder.config.name, "Bybit");
assert!(!builder.config.sandbox);
assert_eq!(builder.options.account_type, "UNIFIED");
assert_eq!(builder.options.recv_window, 5000);
}
#[test]
fn test_builder_api_key() {
let builder = BybitBuilder::new().api_key("test-key");
assert_eq!(
builder.config.api_key.as_ref().map(|s| s.expose_secret()),
Some("test-key")
);
}
#[test]
fn test_builder_secret() {
let builder = BybitBuilder::new().secret("test-secret");
assert_eq!(
builder.config.secret.as_ref().map(|s| s.expose_secret()),
Some("test-secret")
);
}
#[test]
fn test_builder_sandbox() {
let builder = BybitBuilder::new().sandbox(true);
assert!(builder.config.sandbox);
assert!(builder.options.testnet);
}
#[test]
fn test_builder_testnet() {
let builder = BybitBuilder::new().testnet(true);
assert!(builder.config.sandbox);
assert!(builder.options.testnet);
}
#[test]
fn test_builder_sandbox_testnet_equivalence() {
let sandbox_builder = BybitBuilder::new().sandbox(true);
let testnet_builder = BybitBuilder::new().testnet(true);
assert_eq!(
sandbox_builder.config.sandbox,
testnet_builder.config.sandbox
);
assert_eq!(
sandbox_builder.options.testnet,
testnet_builder.options.testnet
);
}
#[test]
fn test_builder_account_type() {
let builder = BybitBuilder::new().account_type("CONTRACT");
assert_eq!(builder.options.account_type, "CONTRACT");
}
#[test]
fn test_builder_default_type() {
let builder = BybitBuilder::new().default_type(DefaultType::Swap);
assert_eq!(builder.options.default_type, DefaultType::Swap);
}
#[test]
fn test_builder_default_type_from_string() {
let builder = BybitBuilder::new().default_type("futures");
assert_eq!(builder.options.default_type, DefaultType::Futures);
}
#[test]
fn test_builder_default_sub_type() {
let builder = BybitBuilder::new().default_sub_type(DefaultSubType::Inverse);
assert_eq!(
builder.options.default_sub_type,
Some(DefaultSubType::Inverse)
);
}
#[test]
fn test_builder_default_type_and_sub_type() {
let builder = BybitBuilder::new()
.default_type(DefaultType::Swap)
.default_sub_type(DefaultSubType::Linear);
assert_eq!(builder.options.default_type, DefaultType::Swap);
assert_eq!(
builder.options.default_sub_type,
Some(DefaultSubType::Linear)
);
}
#[test]
fn test_builder_recv_window() {
let builder = BybitBuilder::new().recv_window(10000);
assert_eq!(builder.options.recv_window, 10000);
}
#[test]
fn test_builder_timeout() {
let builder = BybitBuilder::new().timeout(Duration::from_secs(60));
assert_eq!(builder.config.timeout, Duration::from_secs(60));
}
#[test]
fn test_builder_connect_timeout() {
let builder = BybitBuilder::new().connect_timeout(Duration::from_secs(15));
assert_eq!(builder.config.connect_timeout, Duration::from_secs(15));
}
#[test]
fn test_builder_connect_timeout_secs() {
let builder = BybitBuilder::new().connect_timeout_secs(20);
assert_eq!(builder.config.connect_timeout, Duration::from_secs(20));
}
#[test]
fn test_builder_chaining() {
let builder = BybitBuilder::new()
.api_key("key")
.secret("secret")
.testnet(true)
.timeout(Duration::from_secs(30))
.recv_window(10000)
.account_type("SPOT")
.default_type(DefaultType::Swap)
.default_sub_type(DefaultSubType::Linear);
assert_eq!(
builder.config.api_key.as_ref().map(|s| s.expose_secret()),
Some("key")
);
assert_eq!(
builder.config.secret.as_ref().map(|s| s.expose_secret()),
Some("secret")
);
assert!(builder.config.sandbox);
assert_eq!(builder.config.timeout, Duration::from_secs(30));
assert_eq!(builder.options.recv_window, 10000);
assert_eq!(builder.options.account_type, "SPOT");
assert_eq!(builder.options.default_type, DefaultType::Swap);
assert_eq!(
builder.options.default_sub_type,
Some(DefaultSubType::Linear)
);
}
#[test]
fn test_builder_build() {
let result = BybitBuilder::new().build();
assert!(result.is_ok());
let bybit = result.unwrap();
assert_eq!(bybit.id(), "bybit");
assert_eq!(bybit.name(), "Bybit");
}
#[test]
fn test_builder_build_with_credentials() {
let result = BybitBuilder::new()
.api_key("test-key")
.secret("test-secret")
.build();
assert!(result.is_ok());
}
#[test]
fn test_builder_enable_rate_limit() {
let builder = BybitBuilder::new().enable_rate_limit(false);
assert!(!builder.config.enable_rate_limit);
}
#[test]
fn test_builder_proxy() {
let builder = BybitBuilder::new().proxy(ProxyConfig::new("http://proxy.example.com:8080"));
assert_eq!(
builder.config.proxy,
Some(ProxyConfig::new("http://proxy.example.com:8080"))
);
}
#[test]
fn test_builder_verbose() {
let builder = BybitBuilder::new().verbose(true);
assert!(builder.config.verbose);
}
}