use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Side {
Buy,
Sell,
}
impl std::fmt::Display for Side {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Side::Buy => write!(f, "buy"),
Side::Sell => write!(f, "sell"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SignalSource {
Playbook { strategy_id: String, regime: String },
Manual,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SignalAction {
Open {
side: Side,
size: f64,
price: Option<f64>,
},
Close {
side: Side,
size: f64,
},
CloseAll {
reason: String,
},
SetStopLoss {
trigger_price: f64,
},
SetTakeProfit {
trigger_price: f64,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradeSignal {
pub id: String,
pub timestamp: u64,
pub source: SignalSource,
pub symbol: String,
pub action: SignalAction,
pub reason: String,
}
impl TradeSignal {
pub fn manual(symbol: String, action: SignalAction, reason: String) -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
source: SignalSource::Manual,
symbol,
action,
reason,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn side_display() {
assert_eq!(Side::Buy.to_string(), "buy");
assert_eq!(Side::Sell.to_string(), "sell");
}
#[test]
fn side_serde_roundtrip() {
let json = serde_json::to_string(&Side::Buy).unwrap();
let back: Side = serde_json::from_str(&json).unwrap();
assert_eq!(back, Side::Buy);
}
#[test]
fn trade_signal_manual_constructor() {
let sig = TradeSignal::manual(
"BTC-PERP".into(),
SignalAction::Open {
side: Side::Buy,
size: 0.01,
price: Some(65000.0),
},
"manual order".into(),
);
assert_eq!(sig.symbol, "BTC-PERP");
assert!(matches!(sig.source, SignalSource::Manual));
assert!(!sig.id.is_empty());
assert!(sig.timestamp > 0);
}
#[test]
fn trade_signal_serde_roundtrip() {
let sig = TradeSignal::manual(
"ETH-PERP".into(),
SignalAction::SetStopLoss {
trigger_price: 3000.0,
},
"SL set".into(),
);
let json = serde_json::to_string(&sig).unwrap();
let back: TradeSignal = serde_json::from_str(&json).unwrap();
assert_eq!(back.symbol, "ETH-PERP");
}
}