From f9c67ce923a9b8c867349af1f18f4b4bb9cf5bd8 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 31 Jul 2026 17:02:38 +0200 Subject: [PATCH] Config presets --- pulse-sdk/src/terminal.rs | 1 + src/engine/engine/command.rs | 118 +++++++++++++++++++++++++--------- src/engine/engine/mod.rs | 9 +-- src/engine/engine/terminal.rs | 7 +- src/engine/store/config.rs | 45 +++++++++++-- src/terminal/formatting.rs | 4 +- 6 files changed, 141 insertions(+), 43 deletions(-) diff --git a/pulse-sdk/src/terminal.rs b/pulse-sdk/src/terminal.rs index c82562b..168cc73 100644 --- a/pulse-sdk/src/terminal.rs +++ b/pulse-sdk/src/terminal.rs @@ -63,5 +63,6 @@ pub enum InspectItem { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct EngineConfig { + pub preset: String, pub strategy: StrategyManifest, } diff --git a/src/engine/engine/command.rs b/src/engine/engine/command.rs index 0a815e1..06724f6 100644 --- a/src/engine/engine/command.rs +++ b/src/engine/engine/command.rs @@ -1,4 +1,4 @@ -use crate::{engine::Engine, store::config::Config}; +use crate::{engine::Engine, store::config::ConfigManager}; use pulse_sdk::prelude::*; use toml::Value; @@ -21,7 +21,7 @@ impl Engine { match args[0] { "reload" => { - *self.config.lock().await = Config::new().await?; + *self.config.lock().await = ConfigManager::new().await?; self.terminal_server .info("engine::config", "successfully reloaded") .await?; @@ -50,17 +50,11 @@ impl Engine { if args.len() == 3 { match args[1] { "watchlist" | "watch" | "wl" => { - set_cfg!(v, self.config.lock().await.watchlist = v); + set_cfg!(v, self.config.lock().await.get_mut()?.watchlist = v); self.terminal_server .info("config::set", "watchlist set successfully, use `config save` to persist changes") .await?; - - self.terminal_server - .broadcast(TerminalServerMessage::ConfigUpdated( - self.get_config_status().await, - )) - .await?; } "strategy" | "strat" | "sg" => { @@ -81,30 +75,12 @@ impl Engine { } self.strategy_engine.reload(id.as_str()).await?; - self.config.lock().await.strategy = id; + self.config.lock().await.get_mut()?.strategy = id; }); self.terminal_server .info("config::set", "strategy set successfully, use `config save` to persist changes") .await?; - - self.terminal_server - .broadcast(TerminalServerMessage::ConfigUpdated( - self.get_config_status().await, - )) - .await?; - } - - "cooldown" | "cool" | "cd" => { - set_cfg!(cooldown, { - self.config.lock().await.cooldown = cooldown; - }); - - self.terminal_server - .broadcast(TerminalServerMessage::ConfigUpdated( - self.get_config_status().await, - )) - .await?; } _ => { @@ -113,6 +89,12 @@ impl Engine { .await?; } } + + self.terminal_server + .broadcast(TerminalServerMessage::ConfigUpdated( + self.get_config_status().await, + )) + .await?; } else { self.invalid_command_usage("engine::config").await?; } @@ -169,7 +151,7 @@ impl Engine { .terminal_server .error( "engine::account", - &format!("Account not found ({new_active})"), + &format!("account not found ({new_active})"), ) .await; } @@ -179,7 +161,7 @@ impl Engine { self.terminal_server .info( "engine::account", - &format!("Account set to {new_active} successfully!"), + &format!("account set to {new_active} successfully!"), ) .await?; } @@ -190,6 +172,80 @@ impl Engine { } } + "preset" => { + if args.len() == 0 { + return self.invalid_command_usage("engine::preset").await; + } + + match args[0] { + "list" | "ls" => { + self.terminal_server + .info("engine::preset", "PRESET LIST") + .await?; + + let config = self.config.lock().await; + + for (name, cfg) in &config.configs { + let description = if let Some(desc) = &cfg.description { + format!("- {desc}") + } else { + String::new() + }; + + self.terminal_server + .info( + "engine::preset", + &if name == &config.preset { + format!("{name} (active) {description}") + } else { + format!("{name} {description}") + }, + ) + .await?; + } + } + + "use" | "set" => { + if args.len() < 2 { + return self.invalid_command_usage("engine::preset").await; + } + + let new_active = args[1]; + + let mut config = self.config.lock().await; + + if !config.configs.contains_key(new_active) { + return self + .terminal_server + .error( + "engine::preset", + &format!("preset not found ({new_active})"), + ) + .await; + } + + config.preset = new_active.to_string(); + + self.terminal_server + .info( + "engine::preset", + &format!("preset set to {new_active} successfully!"), + ) + .await?; + + self.terminal_server + .broadcast(TerminalServerMessage::ConfigUpdated( + self.get_config_status().await, + )) + .await?; + } + + _ => { + return self.invalid_command_usage("engine::preset").await; + } + } + } + "strategy" | "strat" | "sg" => { let Some(strategy_command) = args.drain(0..=0).next() else { return self.invalid_command_usage("strategy").await; @@ -211,7 +267,7 @@ impl Engine { _ => { self.terminal_server - .error("engine::cmd", &format!("Command '{}' not found", command)) + .error("engine::cmd", &format!("command '{}' not found", command)) .await?; } } diff --git a/src/engine/engine/mod.rs b/src/engine/engine/mod.rs index 492dc7f..8e3aa98 100644 --- a/src/engine/engine/mod.rs +++ b/src/engine/engine/mod.rs @@ -5,7 +5,7 @@ pub mod terminal; use crate::{ engine::{strategy::StrategyEngine, terminal::TerminalServer}, - store::{accounts::AccountList, config::Config}, + store::{accounts::AccountList, config::ConfigManager}, }; use hypersdk::hypercore::ws::ConnectionStream; use pulse_sdk::prelude::*; @@ -26,7 +26,7 @@ pub struct Engine { pub ws_stream: Arc>, // data - pub config: Arc>, + pub config: Arc>, pub accounts: Arc>, // live data / status @@ -37,11 +37,11 @@ pub struct Engine { impl Engine { pub async fn new() -> tokio::io::Result> { - let config = Config::new().await?; + let config = ConfigManager::new().await?; let (ws_handle, ws_stream) = hypersdk::hypercore::mainnet_ws().split(); - let strategy = StrategyEngine::new(&config.strategy, ws_handle).await?; + let strategy = StrategyEngine::new(&config.get_ref()?.strategy, ws_handle).await?; let accounts = Arc::new(Mutex::new(AccountList::new().await?)); let config = Arc::new(Mutex::new(config)); @@ -91,6 +91,7 @@ impl Engine { pub async fn get_config_status(&self) -> EngineConfig { EngineConfig { + preset: self.config.lock().await.preset.clone(), strategy: self.strategy_engine.strategy.lock().await.manifest.clone(), } } diff --git a/src/engine/engine/terminal.rs b/src/engine/engine/terminal.rs index f2e6211..39003ad 100644 --- a/src/engine/engine/terminal.rs +++ b/src/engine/engine/terminal.rs @@ -194,8 +194,11 @@ impl TerminalServer { loop { refresh.tick().await; - match crate::fetch::fetch_watch_list(&client, &engine.config.lock().await.watchlist) - .await + match crate::fetch::fetch_watch_list( + &client, + &engine.config.lock().await.get_ref()?.watchlist, + ) + .await { Ok(watch_list) => { *engine.watch_list.lock().await = watch_list.clone(); diff --git a/src/engine/store/config.rs b/src/engine/store/config.rs index ba42aa2..7dfd733 100644 --- a/src/engine/store/config.rs +++ b/src/engine/store/config.rs @@ -1,23 +1,60 @@ -use hypersdk::hypercore::CandleInterval; +use std::collections::HashMap; #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct Config { + pub description: Option, pub watchlist: Vec, pub strategy: String, - pub cooldown: CandleInterval, +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct ConfigManager { + pub preset: String, + + #[serde(flatten)] + pub configs: HashMap, } impl Default for Config { fn default() -> Self { Self { + description: None, watchlist: vec!["BTC".to_string(), "SOL".to_string(), "ETH".to_string()], strategy: String::new(), - cooldown: CandleInterval::ThirtyMinutes, } } } -impl Config { +impl Default for ConfigManager { + fn default() -> Self { + Self { + preset: "Main".to_string(), + configs: HashMap::from([("Main".to_string(), Config::default())]), + } + } +} + +impl ConfigManager { + pub fn get_ref(&self) -> tokio::io::Result<&Config> { + self.configs.get(&self.preset).ok_or_else(|| { + tokio::io::Error::new( + std::io::ErrorKind::NotFound, + format!("Failed to get preset '{}'", self.preset), + ) + }) + } + + pub fn get_mut(&mut self) -> tokio::io::Result<&mut Config> { + self.configs.get_mut(&self.preset).ok_or_else(|| { + tokio::io::Error::new( + std::io::ErrorKind::NotFound, + format!("Failed to get preset '{}'", self.preset), + ) + }) + } +} + +impl ConfigManager { pub async fn new() -> tokio::io::Result { let path = crate::store::pulse_config_file()?; diff --git a/src/terminal/formatting.rs b/src/terminal/formatting.rs index 64744a6..b8fb1a3 100644 --- a/src/terminal/formatting.rs +++ b/src/terminal/formatting.rs @@ -217,11 +217,11 @@ impl Formatted for EngineConfig { fn get_formatted(&self) -> Vec { vec![ Triple( + "\x1b[2mPreset\x1b[0m", "\x1b[2mStrategy\x1b[0m", "\x1b[2m..\x1b[0m", - "\x1b[2m..\x1b[0m", ), - Triple(&self.strategy.name, "..", ".."), + Triple(&self.preset, &self.strategy.name, ".."), ] .get_formatted() }