Skip to main content

claw_spawn/domain/
bot.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use strum::{Display, EnumString};
4use uuid::Uuid;
5
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
7pub struct Bot {
8    pub id: Uuid,
9    pub account_id: Uuid,
10    pub name: String,
11    pub persona: Persona,
12    pub status: BotStatus,
13    pub droplet_id: Option<i64>,
14    pub desired_config_version_id: Option<Uuid>,
15    pub applied_config_version_id: Option<Uuid>,
16    pub registration_token: Option<String>,
17    pub created_at: DateTime<Utc>,
18    pub updated_at: DateTime<Utc>,
19    pub last_heartbeat_at: Option<DateTime<Utc>>,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Display, EnumString)]
23#[strum(serialize_all = "snake_case")]
24pub enum Persona {
25    Beginner,
26    Tweaker,
27    QuantLite,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Display, EnumString)]
31#[strum(serialize_all = "snake_case")]
32pub enum BotStatus {
33    Pending,
34    Provisioning,
35    Online,
36    Paused,
37    Error,
38    Destroyed,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct BotConfig {
43    pub id: Uuid,
44    pub bot_id: Uuid,
45    pub version: i32,
46    pub trading_config: TradingConfig,
47    pub risk_config: RiskConfig,
48    pub secrets: BotSecrets,
49    pub created_at: DateTime<Utc>,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct StoredBotConfig {
54    pub id: Uuid,
55    pub bot_id: Uuid,
56    pub version: i32,
57    pub trading_config: TradingConfig,
58    pub risk_config: RiskConfig,
59    pub secrets: EncryptedBotSecrets,
60    pub created_at: DateTime<Utc>,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct TradingConfig {
65    pub asset_focus: AssetFocus,
66    pub algorithm: AlgorithmMode,
67    pub strictness: StrictnessLevel,
68    pub paper_mode: bool,
69    pub signal_knobs: Option<SignalKnobs>,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub enum AssetFocus {
74    Majors,
75    Memes,
76    Custom(Vec<String>),
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub enum AlgorithmMode {
81    Trend,
82    MeanReversion,
83    Breakout,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub enum StrictnessLevel {
88    Low,
89    Medium,
90    High,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct SignalKnobs {
95    pub volume_confirmation: bool,
96    pub volatility_brake: bool,
97    pub liquidity_filter: StrictnessLevel,
98    pub correlation_brake: bool,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct RiskConfig {
103    pub max_position_size_pct: f64,
104    pub max_daily_loss_pct: f64,
105    pub max_drawdown_pct: f64,
106    pub max_trades_per_day: i32,
107}
108
109impl RiskConfig {
110    pub fn validate(&self) -> Result<(), Vec<String>> {
111        let mut errors = Vec::new();
112
113        if !(0.0..=100.0).contains(&self.max_position_size_pct) {
114            errors.push(format!(
115                "max_position_size_pct must be between 0 and 100, got {}",
116                self.max_position_size_pct
117            ));
118        }
119
120        if !(0.0..=100.0).contains(&self.max_daily_loss_pct) {
121            errors.push(format!(
122                "max_daily_loss_pct must be between 0 and 100, got {}",
123                self.max_daily_loss_pct
124            ));
125        }
126
127        if !(0.0..=100.0).contains(&self.max_drawdown_pct) {
128            errors.push(format!(
129                "max_drawdown_pct must be between 0 and 100, got {}",
130                self.max_drawdown_pct
131            ));
132        }
133
134        if self.max_trades_per_day < 0 {
135            errors.push(format!(
136                "max_trades_per_day must be >= 0, got {}",
137                self.max_trades_per_day
138            ));
139        }
140
141        if errors.is_empty() {
142            Ok(())
143        } else {
144            Err(errors)
145        }
146    }
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct BotSecrets {
151    pub llm_provider: String,
152    pub llm_api_key: String,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct EncryptedBotSecrets {
157    pub llm_provider: String,
158    #[serde(with = "serde_bytes")]
159    pub llm_api_key_encrypted: Vec<u8>,
160}
161
162impl Bot {
163    pub fn new(account_id: Uuid, name: String, persona: Persona) -> Self {
164        let now = Utc::now();
165        Self {
166            id: Uuid::new_v4(),
167            account_id,
168            name,
169            persona,
170            status: BotStatus::Pending,
171            droplet_id: None,
172            desired_config_version_id: None,
173            applied_config_version_id: None,
174            registration_token: None,
175            created_at: now,
176            updated_at: now,
177            last_heartbeat_at: None,
178        }
179    }
180}