From 7e4e264eb00707395109ed006821726ba2aad98b Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Fri, 31 Jul 2026 20:13:31 +0200 Subject: [PATCH] Risk config --- pulse-sdk/src/general.rs | 71 +++++++++++++++++++++++++++++++++++++- src/engine/store/config.rs | 21 +++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/pulse-sdk/src/general.rs b/pulse-sdk/src/general.rs index 03582af..56e6253 100644 --- a/pulse-sdk/src/general.rs +++ b/pulse-sdk/src/general.rs @@ -1,4 +1,4 @@ -use hypersdk::hypercore::Side; +use hypersdk::{dec, hypercore::Side}; use rust_decimal::Decimal; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -30,6 +30,12 @@ pub enum Mode { Manual, } +#[derive(Debug, Clone)] +pub enum Allocation { + Fixed(Decimal), + Percent(Decimal), +} + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct Signal { pub symbol: String, @@ -101,3 +107,66 @@ impl std::fmt::Display for ItemState { } } } + +impl<'de> serde::Deserialize<'de> for Allocation { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct AllocationVisitor; + + impl<'de> serde::de::Visitor<'de> for AllocationVisitor { + type Value = Allocation; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a number or percentage string") + } + + fn visit_f64(self, value: f64) -> Result + where + E: serde::de::Error, + { + Ok(Allocation::Fixed(Decimal::from_f64_retain(value).unwrap())) + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + if let Some(percent) = value.strip_suffix('%') { + let value = percent.parse::().map_err(E::custom)?; + + Ok(Allocation::Percent(value)) + } else { + Err(E::custom("invalid allocation format")) + } + } + } + + deserializer.deserialize_any(AllocationVisitor) + } +} + +impl serde::Serialize for Allocation { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + Allocation::Fixed(value) => serde::Serialize::serialize(value, serializer), + Allocation::Percent(value) => { + let s = format!("{}%", value); + serializer.serialize_str(&s) + } + } + } +} + +impl Allocation { + pub fn get(&self, full_alloc: Decimal) -> Decimal { + match self { + Self::Fixed(f) => *f, + Self::Percent(p) => (*p / dec!(100.0)) * full_alloc, + } + } +} diff --git a/src/engine/store/config.rs b/src/engine/store/config.rs index 7dfd733..7e321fe 100644 --- a/src/engine/store/config.rs +++ b/src/engine/store/config.rs @@ -1,10 +1,20 @@ use std::collections::HashMap; +use pulse_sdk::general::Allocation; + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct RiskConfig { + pub risk_per_trade: Allocation, + pub max_open_positions: u32, + pub max_daily_loss: Allocation, +} + #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct Config { pub description: Option, pub watchlist: Vec, pub strategy: String, + pub risk: RiskConfig, } #[derive(Debug, serde::Serialize, serde::Deserialize)] @@ -15,12 +25,23 @@ pub struct ConfigManager { pub configs: HashMap, } +impl Default for RiskConfig { + fn default() -> Self { + Self { + risk_per_trade: Allocation::Percent(10.into()), + max_daily_loss: Allocation::Percent(20.into()), + max_open_positions: 1, + } + } +} + impl Default for Config { fn default() -> Self { Self { description: None, watchlist: vec!["BTC".to_string(), "SOL".to_string(), "ETH".to_string()], strategy: String::new(), + risk: Default::default(), } } }