trading_core 0.0.2-alpha

Core types, traits, and utilites for trading algorithms.
Documentation
use std::fmt;
use serde::de::{Unexpected, Visitor};
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::{Display, Formatter};

const BUY: isize = 1;
const SELL: isize = -1;

#[derive(Debug, Clone, Copy, PartialEq)]
/// Abstracts indicator outputs
pub enum Signal {
    Sell = SELL,
    Hold = 0,
    Buy = BUY,
}

impl Into<i8> for Signal {
    fn into(self) -> i8 {
        match self {
            Signal::Buy => BUY as i8,
            Signal::Hold => 0,
            Signal::Sell => SELL as i8,
        }
    }
}

impl From<i8> for Signal {
    fn from(value: i8) -> Self {
        match value as isize {
            SELL => Signal::Sell,
            0 => Signal::Hold,
            BUY => Signal::Buy,
            _ => panic!("Invalid signal value: {}", value),
        }
    }
}

impl Display for Signal {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            Signal::Buy => write!(f, "BUY"),
            Signal::Hold => write!(f, "HOLD"),
            Signal::Sell => write!(f, "SELL"),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq)]
/// Abstracts types of trades
pub enum Side {
    Sell = SELL,
    Buy = BUY,
}

impl Display for Side {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            Side::Buy => write!(f, "BUY"),
            Side::Sell => write!(f, "SELL"),
        }
    }
}

impl Serialize for Side {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let s = match *self {
            Side::Buy => "BUY",
            Side::Sell => "SELL",
        };
        serializer.serialize_str(s)
    }
}

impl<'de> Deserialize<'de> for Side {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct SideVisitor;

        impl<'de> Visitor<'de> for SideVisitor {
            type Value = Side;

            fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
                formatter.write_str("a string representing a side (BUY or SELL)")
            }

            fn visit_str<E>(self, value: &str) -> Result<Side, E>
            where
                E: de::Error,
            {
                match value.to_uppercase().as_str() {
                    "BUY" => Ok(Side::Buy),
                    "SELL" => Ok(Side::Sell),
                    _ => Err(de::Error::invalid_value(Unexpected::Str(value), &self)),
                }
            }
        }

        deserializer.deserialize_str(SideVisitor)
    }
}

impl Into<i8> for Side {
    fn into(self) -> i8 {
        match self {
            Side::Buy => BUY as i8,
            Side::Sell => SELL as i8,
        }
    }
}

impl TryFrom<Signal> for Side {
    type Error = &'static str;

    fn try_from(value: Signal) -> Result<Self, Self::Error> {
        match value {
            Signal::Buy => Ok(Side::Buy),
            Signal::Sell => Ok(Side::Sell),
            Signal::Hold => Err("Cannot convert Signal::Hold to Side"),
        }
    }
}

impl From<i8> for Side {
    fn from(value: i8) -> Self {
        match value as isize {
            SELL => Side::Sell,
            BUY => Side::Buy,
            _ => panic!("Invalid side value: {}", value),
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_side_from_i8() {
        assert_eq!(Side::from(1), Side::Buy);
        assert_eq!(Side::from(-1), Side::Sell);
    }

    #[test]
    #[should_panic]
    fn test_side_from_i8_panic() {
        let _ = Side::from(0);
    }

    #[test]
    fn test_side_serialize() {
        assert_eq!(serde_json::to_string(&Side::Buy).unwrap(), "\"buy\"");
        assert_eq!(serde_json::to_string(&Side::Sell).unwrap(), "\"sell\"");
    }

    #[test]
    fn test_side_deserialize() {
        assert_eq!(serde_json::from_str::<Side>("\"buy\"").unwrap(), Side::Buy);
        assert_eq!(
            serde_json::from_str::<Side>("\"sell\"").unwrap(),
            Side::Sell
        );
    }
}