use crate::config::{AppConfig, ConfigError};
use crate::lino_args::{auto_load_lenv, getenv, getenv_bool, getenv_u64};
use clap::Parser;
use std::path::PathBuf;
use tracing::Level;
#[derive(Parser, Debug)]
#[command(
name = "trader-bot",
version,
about = "A configurable trading bot with multi-exchange support, portfolio balancing, scalping strategies, and multi-account management"
)]
#[allow(clippy::struct_excessive_bools)]
pub struct Cli {
#[arg(short, long, env = "TRADER_BOT_CONFIG")]
pub config: Option<PathBuf>,
#[arg(short, long, env = "LENV_FILE")]
pub lenv: Option<PathBuf>,
#[arg(long, default_value = "info", env = "TRADER_BOT_LOG_LEVEL")]
pub log_level: String,
#[arg(short, long, default_value = "false", env = "TRADER_BOT_VERBOSE")]
pub verbose: bool,
#[arg(long, default_value = "false", env = "TRADER_BOT_DRY_RUN")]
pub dry_run: bool,
#[arg(long, default_value = "false", env = "TRADER_BOT_PLAN")]
pub plan: bool,
#[arg(short, long, env = "TRADER_BOT_ACCOUNT")]
pub account: Option<String>,
#[arg(short, long, env = "TRADER_BOT_USER")]
pub user: Option<String>,
#[arg(long, env = "TRADER_BOT_BALANCE_INTERVAL")]
pub balance_interval: Option<u64>,
#[arg(long, env = "TRADER_BOT_ORDER_DELAY")]
pub order_delay: Option<u64>,
#[arg(long, default_value = "false", env = "TRADER_BOT_RUN_ONCE")]
pub run_once: bool,
#[arg(long, default_value = "false")]
pub demo: bool,
}
impl Cli {
pub fn parse_with_lenv() -> Self {
let _ = auto_load_lenv();
Self::parse()
}
#[must_use]
#[allow(clippy::match_same_arms)]
pub fn get_log_level(&self) -> Level {
match self.log_level.to_lowercase().as_str() {
"trace" => Level::TRACE,
"debug" => Level::DEBUG,
"info" => Level::INFO,
"warn" | "warning" => Level::WARN,
"error" => Level::ERROR,
_ => Level::INFO,
}
}
pub fn load_config(&self) -> Result<Option<AppConfig>, ConfigError> {
if let Some(ref path) = self.config {
let config = AppConfig::from_file(path)?;
config.validate()?;
return Ok(Some(config));
}
let config_path = getenv("TRADER_BOT_CONFIG", "");
if !config_path.is_empty() && std::path::Path::new(&config_path).exists() {
let config = AppConfig::from_file(&config_path)?;
config.validate()?;
return Ok(Some(config));
}
for path in &["config.json", "trader-bot.json", ".trader-bot.json"] {
if std::path::Path::new(path).exists() {
let config = AppConfig::from_file(path)?;
config.validate()?;
return Ok(Some(config));
}
}
Ok(None)
}
pub fn merge_with_config(&self, mut config: AppConfig) -> AppConfig {
if self.verbose {
config.settings.verbose = true;
}
if self.dry_run {
config.settings.dry_run = true;
}
config.settings.log_level.clone_from(&self.log_level);
if let Some(interval) = self.balance_interval {
for account in &mut config.accounts {
account.balance_interval_secs = interval;
}
for user in &mut config.users {
for account in &mut user.accounts {
account.balance_interval_secs = interval;
}
}
}
if let Some(delay) = self.order_delay {
for account in &mut config.accounts {
account.order_delay_ms = delay;
}
for user in &mut config.users {
for account in &mut user.accounts {
account.order_delay_ms = delay;
}
}
}
config
}
}
#[derive(Debug, Clone)]
#[allow(clippy::struct_excessive_bools)]
pub struct RuntimeConfig {
pub config_path: Option<PathBuf>,
pub lenv_path: Option<PathBuf>,
pub log_level: Level,
pub verbose: bool,
pub dry_run: bool,
pub plan: bool,
pub account: Option<String>,
pub user: Option<String>,
pub balance_interval: Option<u64>,
pub order_delay: Option<u64>,
pub run_once: bool,
pub demo: bool,
pub app_config: Option<AppConfig>,
}
impl RuntimeConfig {
pub fn from_cli(cli: Cli, app_config: Option<AppConfig>) -> Self {
let log_level = cli.get_log_level();
Self {
config_path: cli.config,
lenv_path: cli.lenv,
log_level,
verbose: cli.verbose,
dry_run: cli.dry_run,
plan: cli.plan,
account: cli.account,
user: cli.user,
balance_interval: cli.balance_interval,
order_delay: cli.order_delay,
run_once: cli.run_once,
demo: cli.demo,
app_config,
}
}
#[must_use]
#[allow(clippy::match_same_arms)]
pub fn from_env() -> Self {
let _ = auto_load_lenv();
let log_level = match getenv("TRADER_BOT_LOG_LEVEL", "info")
.to_lowercase()
.as_str()
{
"trace" => Level::TRACE,
"debug" => Level::DEBUG,
"info" => Level::INFO,
"warn" | "warning" => Level::WARN,
"error" => Level::ERROR,
_ => Level::INFO,
};
Self {
config_path: std::env::var("TRADER_BOT_CONFIG").ok().map(PathBuf::from),
lenv_path: std::env::var("LENV_FILE").ok().map(PathBuf::from),
log_level,
verbose: getenv_bool("TRADER_BOT_VERBOSE", false),
dry_run: getenv_bool("TRADER_BOT_DRY_RUN", false),
plan: getenv_bool("TRADER_BOT_PLAN", false),
account: std::env::var("TRADER_BOT_ACCOUNT").ok(),
user: std::env::var("TRADER_BOT_USER").ok(),
balance_interval: {
let v = getenv_u64("TRADER_BOT_BALANCE_INTERVAL", 0);
if v > 0 {
Some(v)
} else {
None
}
},
order_delay: {
let v = getenv_u64("TRADER_BOT_ORDER_DELAY", 0);
if v > 0 {
Some(v)
} else {
None
}
},
run_once: getenv_bool("TRADER_BOT_RUN_ONCE", false),
demo: false,
app_config: None,
}
}
}