use std::num::{NonZeroU32, NonZeroU64};
use chrono::{DateTime, Duration, Utc};
use tokio::time;
use lnm_sdk::{
api_v2::WebSocketClientConfig,
api_v3::{RestClientConfig, models::PercentageCapped},
};
use crate::{
sync::{LNM_OHLC_CANDLE_START, LNM_SETTLEMENT_A_START},
util::DateTimeExt,
};
#[derive(Clone, Debug)]
pub struct LiveTradeConfig {
rest_api_timeout: time::Duration,
rest_api_rate_limit_auth_requests_per_second: NonZeroU32,
rest_api_rate_limit_unauth_requests_per_second: NonZeroU32,
ws_enabled: bool,
ws_api_disconnect_timeout: time::Duration,
rest_api_error_cooldown: time::Duration,
rest_api_error_max_trials: NonZeroU64,
price_history_batch_size: NonZeroU64,
sync_mode_full: bool,
price_history_reach: DateTime<Utc>,
funding_settlement_reach: DateTime<Utc>,
price_history_re_sync_interval: time::Duration,
price_history_re_backfill_interval: time::Duration,
price_history_flag_gap_range: Option<Duration>,
funding_settlement_flag_missing_range: Option<Duration>,
live_price_tick_max_interval: time::Duration,
funding_sync_retry_interval: time::Duration,
sync_update_timeout: time::Duration,
trade_tsl_step_size: PercentageCapped,
startup_clean_up_trades: bool,
startup_recover_trades: bool,
trading_session_refresh_interval: time::Duration,
shutdown_clean_up_trades: bool,
trade_estimated_fee: PercentageCapped,
trade_max_running_qtd: usize,
restart_interval: time::Duration,
shutdown_timeout: time::Duration,
}
impl Default for LiveTradeConfig {
fn default() -> Self {
let rest_config_default = RestClientConfig::default();
let ws_config_default = WebSocketClientConfig::default();
Self {
rest_api_timeout: rest_config_default.timeout(),
rest_api_rate_limit_auth_requests_per_second: rest_config_default
.rate_limit_auth_requests_per_second()
.try_into()
.expect("not zero"),
rest_api_rate_limit_unauth_requests_per_second: rest_config_default
.rate_limit_unauth_requests_per_second()
.try_into()
.expect("not zero"),
ws_enabled: false,
ws_api_disconnect_timeout: ws_config_default.disconnect_timeout(),
rest_api_error_cooldown: time::Duration::from_secs(10),
rest_api_error_max_trials: 3.try_into().expect("not zero"),
price_history_batch_size: 1000.try_into().expect("not zero"),
sync_mode_full: false,
price_history_reach: (Utc::now() - Duration::days(90)).floor_day(),
funding_settlement_reach: (Utc::now() - Duration::days(90))
.floor_funding_settlement_time(),
price_history_re_sync_interval: time::Duration::from_secs(10),
price_history_re_backfill_interval: time::Duration::from_secs(90),
price_history_flag_gap_range: Some(Duration::weeks(4)),
funding_settlement_flag_missing_range: Some(Duration::weeks(4)),
live_price_tick_max_interval: time::Duration::from_secs(3 * 60),
funding_sync_retry_interval: time::Duration::from_secs(60),
sync_update_timeout: time::Duration::from_secs(60),
trade_tsl_step_size: PercentageCapped::MIN,
startup_clean_up_trades: false,
startup_recover_trades: true,
trading_session_refresh_interval: time::Duration::from_millis(1_000),
shutdown_clean_up_trades: false,
trade_estimated_fee: PercentageCapped::try_from(0.1)
.expect("must be valid `PercentageCapped`"),
trade_max_running_qtd: 50,
restart_interval: time::Duration::from_secs(10),
shutdown_timeout: time::Duration::from_secs(6),
}
}
}
impl LiveTradeConfig {
pub fn rest_api_timeout(&self) -> time::Duration {
self.rest_api_timeout
}
pub fn rest_api_rate_limit_auth_requests_per_second(&self) -> NonZeroU32 {
self.rest_api_rate_limit_auth_requests_per_second
}
pub fn rest_api_rate_limit_unauth_requests_per_second(&self) -> NonZeroU32 {
self.rest_api_rate_limit_unauth_requests_per_second
}
pub fn ws_enabled(&self) -> bool {
self.ws_enabled
}
pub fn ws_api_disconnect_timeout(&self) -> time::Duration {
self.ws_api_disconnect_timeout
}
pub fn rest_api_error_cooldown(&self) -> time::Duration {
self.rest_api_error_cooldown
}
pub fn rest_api_error_max_trials(&self) -> NonZeroU64 {
self.rest_api_error_max_trials
}
pub fn price_history_batch_size(&self) -> NonZeroU64 {
self.price_history_batch_size
}
pub fn sync_mode_full(&self) -> bool {
self.sync_mode_full
}
pub fn price_history_reach(&self) -> DateTime<Utc> {
self.price_history_reach
}
pub fn funding_settlement_reach(&self) -> DateTime<Utc> {
self.funding_settlement_reach
}
pub fn price_history_re_sync_interval(&self) -> time::Duration {
self.price_history_re_sync_interval
}
pub fn price_history_re_backfill_interval(&self) -> time::Duration {
self.price_history_re_backfill_interval
}
pub fn price_history_flag_gap_range(&self) -> Option<Duration> {
self.price_history_flag_gap_range
}
pub fn funding_settlement_flag_missing_range(&self) -> Option<Duration> {
self.funding_settlement_flag_missing_range
}
pub fn live_price_tick_max_interval(&self) -> time::Duration {
self.live_price_tick_max_interval
}
pub fn funding_sync_retry_interval(&self) -> time::Duration {
self.funding_sync_retry_interval
}
pub fn sync_update_timeout(&self) -> time::Duration {
self.sync_update_timeout
}
pub fn trailing_stoploss_step_size(&self) -> PercentageCapped {
self.trade_tsl_step_size
}
pub fn startup_clean_up_trades(&self) -> bool {
self.startup_clean_up_trades
}
pub fn startup_recover_trades(&self) -> bool {
self.startup_recover_trades
}
pub fn trading_session_refresh_interval(&self) -> time::Duration {
self.trading_session_refresh_interval
}
pub fn shutdown_clean_up_trades(&self) -> bool {
self.shutdown_clean_up_trades
}
pub fn trade_estimated_fee(&self) -> PercentageCapped {
self.trade_estimated_fee
}
pub fn trade_max_running_qtd(&self) -> usize {
self.trade_max_running_qtd
}
pub fn restart_interval(&self) -> time::Duration {
self.restart_interval
}
pub fn shutdown_timeout(&self) -> time::Duration {
self.shutdown_timeout
}
pub fn with_rest_api_timeout(mut self, secs: u64) -> Self {
self.rest_api_timeout = time::Duration::from_secs(secs);
self
}
pub fn with_rest_api_rate_limit_auth_requests_per_second(mut self, rps: NonZeroU32) -> Self {
self.rest_api_rate_limit_auth_requests_per_second = rps;
self
}
pub fn with_rest_api_rate_limit_unauth_requests_per_second(mut self, rps: NonZeroU32) -> Self {
self.rest_api_rate_limit_unauth_requests_per_second = rps;
self
}
pub fn with_ws_enabled(mut self, enabled: bool) -> Self {
self.ws_enabled = enabled;
self
}
pub fn with_ws_api_disconnect_timeout(mut self, secs: u64) -> Self {
self.ws_api_disconnect_timeout = time::Duration::from_secs(secs);
self
}
pub fn with_api_error_cooldown(mut self, secs: u64) -> Self {
self.rest_api_error_cooldown = time::Duration::from_secs(secs);
self
}
pub fn with_api_error_max_trials(mut self, max_trials: NonZeroU64) -> Self {
self.rest_api_error_max_trials = max_trials;
self
}
pub fn with_price_history_batch_size(mut self, size: NonZeroU64) -> Self {
self.price_history_batch_size = size;
self
}
pub fn with_sync_mode_full(mut self, sync_mode_full: bool) -> Self {
self.sync_mode_full = sync_mode_full;
self
}
pub fn with_price_history_reach(mut self, reach: DateTime<Utc>) -> Self {
self.price_history_reach = reach.floor_day();
self
}
pub fn with_price_history_reach_max(mut self) -> Self {
self.price_history_reach = LNM_OHLC_CANDLE_START;
self
}
pub fn with_funding_settlement_reach(mut self, reach: DateTime<Utc>) -> Self {
self.funding_settlement_reach = reach.floor_funding_settlement_time();
self
}
pub fn with_funding_settlement_reach_max(mut self) -> Self {
self.funding_settlement_reach = LNM_SETTLEMENT_A_START;
self
}
pub fn with_price_history_re_sync_interval(mut self, secs: u64) -> Self {
self.price_history_re_sync_interval = time::Duration::from_secs(secs);
self
}
pub fn with_price_history_re_backfill_interval(mut self, secs: u64) -> Self {
self.price_history_re_backfill_interval = time::Duration::from_secs(secs);
self
}
pub fn with_price_history_flag_gap_range(mut self, hours: Option<u64>) -> Self {
self.price_history_flag_gap_range = hours.map(|h| Duration::hours(h as i64));
self
}
pub fn with_funding_settlement_flag_missing_range(mut self, hours: Option<u64>) -> Self {
self.funding_settlement_flag_missing_range = hours.map(|h| Duration::hours(h as i64));
self
}
pub fn with_live_price_tick_max_interval(mut self, secs: u64) -> Self {
self.live_price_tick_max_interval = time::Duration::from_secs(secs);
self
}
pub fn with_funding_sync_retry_interval(mut self, secs: u64) -> Self {
self.funding_sync_retry_interval = time::Duration::from_secs(secs);
self
}
pub fn with_sync_update_timeout(mut self, secs: u64) -> Self {
self.sync_update_timeout = time::Duration::from_secs(secs);
self
}
pub fn with_trailing_stoploss_step_size(
mut self,
trade_tsl_step_size: PercentageCapped,
) -> Self {
self.trade_tsl_step_size = trade_tsl_step_size;
self
}
pub fn with_startup_clean_up_trades(mut self, startup_clean_up_trades: bool) -> Self {
self.startup_clean_up_trades = startup_clean_up_trades;
self
}
pub fn with_startup_recover_trades(mut self, startup_recover_trades: bool) -> Self {
self.startup_recover_trades = startup_recover_trades;
self
}
pub fn with_trading_session_refresh_interval(mut self, millis: u64) -> Self {
self.trading_session_refresh_interval = time::Duration::from_millis(millis);
self
}
pub fn with_shutdown_clean_up_trades(mut self, shutdown_clean_up_trades: bool) -> Self {
self.shutdown_clean_up_trades = shutdown_clean_up_trades;
self
}
pub fn with_trade_estimated_fee(mut self, trade_estimated_fee: PercentageCapped) -> Self {
self.trade_estimated_fee = trade_estimated_fee;
self
}
pub fn with_trade_max_running_qtd(mut self, trade_max_running_qtd: usize) -> Self {
self.trade_max_running_qtd = trade_max_running_qtd;
self
}
pub fn with_restart_interval(mut self, secs: u64) -> Self {
self.restart_interval = time::Duration::from_secs(secs);
self
}
pub fn with_shutdown_timeout(mut self, secs: u64) -> Self {
self.shutdown_timeout = time::Duration::from_secs(secs);
self
}
}
impl From<&LiveTradeConfig> for RestClientConfig {
fn from(value: &LiveTradeConfig) -> Self {
RestClientConfig::new(value.rest_api_timeout())
.with_rate_limiter_active(true)
.with_rate_limit_auth_requests_per_second(
value.rest_api_rate_limit_auth_requests_per_second(),
)
.with_rate_limit_unauth_requests_per_second(
value.rest_api_rate_limit_unauth_requests_per_second(),
)
}
}
impl From<&LiveTradeConfig> for WebSocketClientConfig {
fn from(value: &LiveTradeConfig) -> Self {
WebSocketClientConfig::new(value.ws_api_disconnect_timeout())
}
}
#[derive(Debug)]
pub(super) struct LiveTradeControllerConfig {
shutdown_timeout: time::Duration,
}
impl LiveTradeControllerConfig {
pub fn shutdown_timeout(&self) -> time::Duration {
self.shutdown_timeout
}
}
impl From<&LiveTradeConfig> for LiveTradeControllerConfig {
fn from(value: &LiveTradeConfig) -> Self {
Self {
shutdown_timeout: value.shutdown_timeout,
}
}
}
#[derive(Debug)]
pub(super) struct LiveProcessConfig {
sync_update_timeout: time::Duration,
restart_interval: time::Duration,
}
impl LiveProcessConfig {
pub fn sync_update_timeout(&self) -> time::Duration {
self.sync_update_timeout
}
pub fn restart_interval(&self) -> time::Duration {
self.restart_interval
}
}
impl From<&LiveTradeConfig> for LiveProcessConfig {
fn from(value: &LiveTradeConfig) -> Self {
Self {
sync_update_timeout: value.sync_update_timeout(),
restart_interval: value.restart_interval(),
}
}
}
pub struct LiveTradeExecutorConfig {
trade_tsl_step_size: PercentageCapped,
startup_clean_up_trades: bool,
startup_recover_trades: bool,
trading_session_refresh_interval: time::Duration,
shutdown_clean_up_trades: bool,
trade_estimated_fee: PercentageCapped,
trade_max_running_qtd: usize,
}
impl LiveTradeExecutorConfig {
pub fn trailing_stoploss_step_size(&self) -> PercentageCapped {
self.trade_tsl_step_size
}
pub fn startup_clean_up_trades(&self) -> bool {
self.startup_clean_up_trades
}
pub fn startup_recover_trades(&self) -> bool {
self.startup_recover_trades
}
pub fn trading_session_refresh_interval(&self) -> time::Duration {
self.trading_session_refresh_interval
}
pub fn shutdown_clean_up_trades(&self) -> bool {
self.shutdown_clean_up_trades
}
pub fn trade_estimated_fee(&self) -> PercentageCapped {
self.trade_estimated_fee
}
pub fn trade_max_running_qtd(&self) -> usize {
self.trade_max_running_qtd
}
pub fn with_trailing_stoploss_step_size(
mut self,
trade_tsl_step_size: PercentageCapped,
) -> Self {
self.trade_tsl_step_size = trade_tsl_step_size;
self
}
pub fn with_startup_clean_up_trades(mut self, startup_clean_up_trades: bool) -> Self {
self.startup_clean_up_trades = startup_clean_up_trades;
self
}
pub fn with_startup_recover_trades(mut self, startup_recover_trades: bool) -> Self {
self.startup_recover_trades = startup_recover_trades;
self
}
pub fn with_trading_session_refresh_interval(mut self, millis: u64) -> Self {
self.trading_session_refresh_interval = time::Duration::from_millis(millis);
self
}
pub fn with_shutdown_clean_up_trades(mut self, shutdown_clean_up_trades: bool) -> Self {
self.shutdown_clean_up_trades = shutdown_clean_up_trades;
self
}
pub fn with_trade_estimated_fee(mut self, trade_estimated_fee: PercentageCapped) -> Self {
self.trade_estimated_fee = trade_estimated_fee;
self
}
pub fn with_trade_max_running_qtd(mut self, trade_max_running_qtd: usize) -> Self {
self.trade_max_running_qtd = trade_max_running_qtd;
self
}
}
impl Default for LiveTradeExecutorConfig {
fn default() -> Self {
Self {
trade_tsl_step_size: PercentageCapped::MIN,
startup_clean_up_trades: false,
startup_recover_trades: true,
trading_session_refresh_interval: time::Duration::from_millis(1_000),
shutdown_clean_up_trades: false,
trade_estimated_fee: PercentageCapped::try_from(0.1)
.expect("must be valid `PercentageCapped`"),
trade_max_running_qtd: 50,
}
}
}
impl From<&LiveTradeConfig> for LiveTradeExecutorConfig {
fn from(value: &LiveTradeConfig) -> Self {
Self {
trade_tsl_step_size: value.trailing_stoploss_step_size(),
startup_clean_up_trades: value.startup_clean_up_trades(),
startup_recover_trades: value.startup_recover_trades(),
trading_session_refresh_interval: value.trading_session_refresh_interval(),
shutdown_clean_up_trades: value.shutdown_clean_up_trades(),
trade_estimated_fee: value.trade_estimated_fee(),
trade_max_running_qtd: value.trade_max_running_qtd(),
}
}
}