hyper-agent-core 0.1.0

Core domain logic for hyper-agent: pipeline, executor, signals, positions
Documentation
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");
    }
}