pricelevel 0.8.0

A high-performance, lock-free price level implementation for limit order books in Rust. This library provides the building blocks for creating efficient trading systems with support for multiple order types and concurrent access patterns.
Documentation
#[cfg(test)]
mod tests {
    use crate::errors::PriceLevelError;
    use crate::orders::time_in_force::TimeInForce;
    use std::str::FromStr;

    #[test]
    fn test_is_immediate() {
        assert!(TimeInForce::Ioc.is_immediate());
        assert!(TimeInForce::Fok.is_immediate());
        assert!(!TimeInForce::Gtc.is_immediate());
        assert!(!TimeInForce::Gtd(1000).is_immediate());
        assert!(!TimeInForce::Day.is_immediate());
    }

    #[test]
    fn test_has_expiry() {
        assert!(TimeInForce::Gtd(1000).has_expiry());
        assert!(TimeInForce::Day.has_expiry());
        assert!(!TimeInForce::Gtc.has_expiry());
        assert!(!TimeInForce::Ioc.has_expiry());
        assert!(!TimeInForce::Fok.has_expiry());
    }

    #[test]
    fn test_is_expired_gtd() {
        let expiry_time = 1000;
        let tif = TimeInForce::Gtd(expiry_time);
        assert!(!tif.is_expired(999, None));
        assert!(tif.is_expired(1000, None));
        assert!(tif.is_expired(1001, None));
    }

    #[test]
    fn test_is_expired_day() {
        let tif = TimeInForce::Day;
        let market_close = 1600;
        assert!(!tif.is_expired(1500, None));
        assert!(!tif.is_expired(1500, Some(market_close)));
        assert!(tif.is_expired(1600, Some(market_close)));
        assert!(tif.is_expired(1700, Some(market_close)));
    }

    #[test]
    fn test_non_expiring_types() {
        assert!(!TimeInForce::Gtc.is_expired(9999, Some(1000)));
        assert!(!TimeInForce::Ioc.is_expired(9999, Some(1000)));
        assert!(!TimeInForce::Fok.is_expired(9999, Some(1000)));
    }

    #[test]
    fn test_serialize_basic_types() {
        assert_eq!(serde_json::to_string(&TimeInForce::Gtc).unwrap(), "\"GTC\"");
        assert_eq!(serde_json::to_string(&TimeInForce::Ioc).unwrap(), "\"IOC\"");
        assert_eq!(serde_json::to_string(&TimeInForce::Fok).unwrap(), "\"FOK\"");
        assert_eq!(serde_json::to_string(&TimeInForce::Day).unwrap(), "\"DAY\"");
    }

    #[test]
    fn test_serialize_gtd() {
        assert_eq!(
            serde_json::to_string(&TimeInForce::Gtd(12345)).unwrap(),
            "{\"GTD\":12345}"
        );
    }

    #[test]
    fn test_deserialize_standard_format() {
        assert_eq!(
            serde_json::from_str::<TimeInForce>("\"Gtc\"").unwrap(),
            TimeInForce::Gtc
        );
        assert_eq!(
            serde_json::from_str::<TimeInForce>("\"GTC\"").unwrap(),
            TimeInForce::Gtc
        );
        assert_eq!(
            serde_json::from_str::<TimeInForce>("\"Ioc\"").unwrap(),
            TimeInForce::Ioc
        );
        assert_eq!(
            serde_json::from_str::<TimeInForce>("\"IOC\"").unwrap(),
            TimeInForce::Ioc
        );
        assert_eq!(
            serde_json::from_str::<TimeInForce>("\"Fok\"").unwrap(),
            TimeInForce::Fok
        );
        assert_eq!(
            serde_json::from_str::<TimeInForce>("\"FOK\"").unwrap(),
            TimeInForce::Fok
        );
        assert_eq!(
            serde_json::from_str::<TimeInForce>("\"Day\"").unwrap(),
            TimeInForce::Day
        );
        assert_eq!(
            serde_json::from_str::<TimeInForce>("\"DAY\"").unwrap(),
            TimeInForce::Day
        );
    }

    #[test]
    fn test_deserialize_mixed_case() {
        assert_eq!(
            serde_json::from_str::<TimeInForce>("\"Gtc\"").unwrap(),
            TimeInForce::Gtc
        );
        assert_eq!(
            serde_json::from_str::<TimeInForce>("\"gtc\"").unwrap(),
            TimeInForce::Gtc
        );
        // assert_eq!(serde_json::from_str::<TimeInForce>("\"iOc\"").unwrap(), TimeInForce::Ioc); // Esto fallará
        assert_eq!(
            serde_json::from_str::<TimeInForce>("\"Ioc\"").unwrap(),
            TimeInForce::Ioc
        );
        assert_eq!(
            serde_json::from_str::<TimeInForce>("\"ioc\"").unwrap(),
            TimeInForce::Ioc
        );
        // assert_eq!(serde_json::from_str::<TimeInForce>("\"fOk\"").unwrap(), TimeInForce::Fok); // Esto fallará
        assert_eq!(
            serde_json::from_str::<TimeInForce>("\"Fok\"").unwrap(),
            TimeInForce::Fok
        );
        assert_eq!(
            serde_json::from_str::<TimeInForce>("\"fok\"").unwrap(),
            TimeInForce::Fok
        );
        // assert_eq!(serde_json::from_str::<TimeInForce>("\"dAy\"").unwrap(), TimeInForce::Day); // Esto fallará
        assert_eq!(
            serde_json::from_str::<TimeInForce>("\"Day\"").unwrap(),
            TimeInForce::Day
        );
        assert_eq!(
            serde_json::from_str::<TimeInForce>("\"day\"").unwrap(),
            TimeInForce::Day
        );
    }

    #[test]
    fn test_deserialize_lowercase() {
        assert_eq!(
            serde_json::from_str::<TimeInForce>("\"gtc\"").unwrap(),
            TimeInForce::Gtc
        );
        assert_eq!(
            serde_json::from_str::<TimeInForce>("\"ioc\"").unwrap(),
            TimeInForce::Ioc
        );
        assert_eq!(
            serde_json::from_str::<TimeInForce>("\"fok\"").unwrap(),
            TimeInForce::Fok
        );
        assert_eq!(
            serde_json::from_str::<TimeInForce>("\"day\"").unwrap(),
            TimeInForce::Day
        );
    }

    #[test]
    fn test_deserialize_gtd() {
        assert_eq!(
            serde_json::from_str::<TimeInForce>("{\"GTD\":12345}").unwrap(),
            TimeInForce::Gtd(12345)
        );

        assert_eq!(
            serde_json::from_str::<TimeInForce>("{\"Gtd\":12345}").unwrap(),
            TimeInForce::Gtd(12345)
        );

        assert_eq!(
            serde_json::from_str::<TimeInForce>("{\"gtd\":54321}").unwrap(),
            TimeInForce::Gtd(54321)
        );
    }

    #[test]
    fn test_round_trip_serialization() {
        let test_cases = vec![
            TimeInForce::Gtc,
            TimeInForce::Ioc,
            TimeInForce::Fok,
            TimeInForce::Gtd(12345),
            TimeInForce::Day,
        ];

        for tif in test_cases {
            let serialized = serde_json::to_string(&tif).unwrap();
            let deserialized: TimeInForce = serde_json::from_str(&serialized).unwrap();
            assert_eq!(tif, deserialized);
        }
    }

    #[test]
    fn test_invalid_deserialization() {
        assert!(serde_json::from_str::<TimeInForce>("\"Invalid\"").is_err());
        assert!(serde_json::from_str::<TimeInForce>("{\"GTD\":\"not_a_number\"}").is_err());
        assert!(serde_json::from_str::<TimeInForce>("{\"InvalidType\":12345}").is_err());
    }

    #[test]
    fn test_serialize_uppercase_consistency() {
        let test_cases = [
            (TimeInForce::Gtc, "\"GTC\""),
            (TimeInForce::Ioc, "\"IOC\""),
            (TimeInForce::Fok, "\"FOK\""),
            (TimeInForce::Day, "\"DAY\""),
        ];

        for (tif, expected) in test_cases {
            assert_eq!(serde_json::to_string(&tif).unwrap(), expected);
        }
    }

    #[test]
    fn test_display() {
        assert_eq!(TimeInForce::Gtc.to_string(), "GTC");
        assert_eq!(TimeInForce::Ioc.to_string(), "IOC");
        assert_eq!(TimeInForce::Fok.to_string(), "FOK");
        assert_eq!(
            TimeInForce::Gtd(1616823000000).to_string(),
            "GTD-1616823000000"
        );
        assert_eq!(TimeInForce::Day.to_string(), "DAY");
    }

    #[test]
    fn test_from_str_valid() {
        assert_eq!(TimeInForce::from_str("GTC").unwrap(), TimeInForce::Gtc);
        assert_eq!(TimeInForce::from_str("IOC").unwrap(), TimeInForce::Ioc);
        assert_eq!(TimeInForce::from_str("FOK").unwrap(), TimeInForce::Fok);
        assert_eq!(TimeInForce::from_str("DAY").unwrap(), TimeInForce::Day);
        assert_eq!(
            TimeInForce::from_str("GTD-1616823000000").unwrap(),
            TimeInForce::Gtd(1616823000000)
        );

        // Test case insensitivity
        assert_eq!(TimeInForce::from_str("gtc").unwrap(), TimeInForce::Gtc);
        assert_eq!(TimeInForce::from_str("ioc").unwrap(), TimeInForce::Ioc);
        assert_eq!(TimeInForce::from_str("fok").unwrap(), TimeInForce::Fok);
        assert_eq!(TimeInForce::from_str("day").unwrap(), TimeInForce::Day);
        assert_eq!(
            TimeInForce::from_str("gtd-1616823000000").unwrap(),
            TimeInForce::Gtd(1616823000000)
        );

        // Test mixed case
        assert_eq!(TimeInForce::from_str("Gtc").unwrap(), TimeInForce::Gtc);
        assert_eq!(TimeInForce::from_str("IoC").unwrap(), TimeInForce::Ioc);
    }

    #[test]
    fn test_from_str_invalid() {
        // Test invalid time-in-force values
        assert!(TimeInForce::from_str("").is_err());
        assert!(TimeInForce::from_str("INVALID").is_err());
        assert!(TimeInForce::from_str("GTD").is_err());
        assert!(TimeInForce::from_str("GTD-").is_err());
        assert!(TimeInForce::from_str("GTD-INVALID").is_err());

        // Test error messages
        let error = TimeInForce::from_str("INVALID").unwrap_err();
        match error {
            crate::errors::PriceLevelError::ParseError { message } => {
                assert!(message.contains("Invalid TimeInForce: INVALID"));
            }
            _ => panic!("Expected ParseError"),
        }

        let error = TimeInForce::from_str("GTD-INVALID").unwrap_err();
        match error {
            crate::errors::PriceLevelError::ParseError { message } => {
                assert!(message.contains("Invalid expiry timestamp in GTD: INVALID"));
            }
            _ => panic!("Expected ParseError"),
        }
    }

    #[test]
    fn test_roundtrip() {
        // Test round-trip conversion (Display -> FromStr)
        let time_in_force_values = [
            TimeInForce::Gtc,
            TimeInForce::Ioc,
            TimeInForce::Fok,
            TimeInForce::Gtd(1616823000000),
            TimeInForce::Day,
        ];

        for &original in &time_in_force_values {
            let string_representation = original.to_string();
            let parsed = TimeInForce::from_str(&string_representation).unwrap();
            assert_eq!(original, parsed);
        }
    }

    #[test]
    fn test_time_in_force_from_str_invalid_format() {
        // Test invalid GTD format (missing expiry)
        let result = TimeInForce::from_str("GTD-");
        assert!(result.is_err());

        // Verify error message
        match result {
            Err(PriceLevelError::ParseError { message }) => {
                assert!(message.contains("Invalid expiry timestamp in GTD"));
            }
            _ => panic!("Expected ParseError"),
        }

        // Test with completely invalid format
        let result = TimeInForce::from_str("INVALID");
        assert!(result.is_err());

        // Verify error message
        match result {
            Err(PriceLevelError::ParseError { message }) => {
                assert!(message.contains("Invalid TimeInForce"));
            }
            _ => panic!("Expected ParseError"),
        }
    }

    #[test]
    fn test_time_in_force_display_day() {
        let time_in_force = TimeInForce::Day;
        assert_eq!(time_in_force.to_string(), "DAY");
    }

    // After dropping the redundant `alias = "Gtc"` / `"Ioc"` / `"Fok"` / `"Gtd"`
    // / `"Day"` (the variant names serde accepts on deserialize by default),
    // the uppercase serialize form (kept as an alias), the lowercase form
    // (kept as an alias), and the bare variant name must all still deserialize,
    // and every variant must round-trip through its serialized wire form.
    #[test]
    fn test_deserialize_all_accepted_forms_after_alias_cleanup() {
        let unit_cases = [
            (["\"GTC\"", "\"gtc\"", "\"Gtc\""], TimeInForce::Gtc),
            (["\"IOC\"", "\"ioc\"", "\"Ioc\""], TimeInForce::Ioc),
            (["\"FOK\"", "\"fok\"", "\"Fok\""], TimeInForce::Fok),
            (["\"DAY\"", "\"day\"", "\"Day\""], TimeInForce::Day),
        ];
        for (forms, expected) in unit_cases {
            for s in forms {
                assert_eq!(
                    serde_json::from_str::<TimeInForce>(s).unwrap(),
                    expected,
                    "{expected:?} should deserialize from {s}"
                );
            }
        }

        // Gtd carries a payload; the uppercase, lowercase, and variant-name
        // keys must all still deserialize.
        for s in ["{\"GTD\":12345}", "{\"gtd\":12345}", "{\"Gtd\":12345}"] {
            assert_eq!(
                serde_json::from_str::<TimeInForce>(s).unwrap(),
                TimeInForce::Gtd(12345),
                "Gtd should deserialize from {s}"
            );
        }

        // Wire format proof: every variant round-trips through its serialized
        // form via the kept uppercase alias.
        for tif in [
            TimeInForce::Gtc,
            TimeInForce::Ioc,
            TimeInForce::Fok,
            TimeInForce::Gtd(12345),
            TimeInForce::Day,
        ] {
            let wire = serde_json::to_string(&tif).unwrap();
            assert_eq!(serde_json::from_str::<TimeInForce>(&wire).unwrap(), tif);
        }
    }
}