kalshi-pmx 0.1.1

Async Rust client for Kalshi's Trade API and WebSocket streams.
Documentation
use kalshi_pmx::models::{
    AmendOrderRequest, BookSide, CreateOrderRequest, DecreaseOrderRequest, FixedPoint,
    SelfTradePreventionType, TimeInForce,
};

#[test]
fn create_order_request_matches_v2_shape() {
    let order = CreateOrderRequest::bid("HIGHNY-24JAN01-T60", "10.00", "0.5600")
        .client_order_id("client-1")
        .time_in_force(TimeInForce::ImmediateOrCancel);

    let value = serde_json::to_value(order).unwrap();
    assert_eq!(value["ticker"], "HIGHNY-24JAN01-T60");
    assert_eq!(value["side"], "bid");
    assert_eq!(value["count"], "10.00");
    assert_eq!(value["price"], "0.5600");
    assert_eq!(value["time_in_force"], "immediate_or_cancel");
    assert_eq!(value["self_trade_prevention_type"], "taker_at_cross");
    assert_eq!(value["client_order_id"], "client-1");

    let ask = CreateOrderRequest::limit("HIGHNY-24JAN01-T60", BookSide::Ask, "5.00", "0.5800");
    let ask = serde_json::to_value(ask).unwrap();
    assert_eq!(ask["side"], "ask");
}

#[test]
fn amend_and_decrease_requests_match_v2_shape() {
    let mut amend = AmendOrderRequest::new("HIGHNY-24JAN01-T60", BookSide::Ask, "8.00", "0.5700");
    amend.client_order_id = Some("old".to_owned());
    amend.updated_client_order_id = Some("new".to_owned());
    amend.exchange_index = Some(-1);

    let value = serde_json::to_value(amend).unwrap();
    assert_eq!(value["ticker"], "HIGHNY-24JAN01-T60");
    assert_eq!(value["side"], "ask");
    assert_eq!(value["price"], "0.5700");
    assert_eq!(value["count"], "8.00");
    assert_eq!(value["client_order_id"], "old");
    assert_eq!(value["updated_client_order_id"], "new");
    assert_eq!(value["exchange_index"], -1);

    let by = serde_json::to_value(DecreaseOrderRequest::reduce_by("2.00")).unwrap();
    assert_eq!(by["reduce_by"], "2.00");
    assert!(by.get("reduce_to").is_none());

    let to = serde_json::to_value(DecreaseOrderRequest::reduce_to("5.00")).unwrap();
    assert_eq!(to["reduce_to"], "5.00");
    assert!(to.get("reduce_by").is_none());
}

#[test]
fn order_request_builder_options_serialize() {
    let order = CreateOrderRequest::ask("TICKER", "3.00", "0.6200")
        .client_order_id("client-2")
        .time_in_force(TimeInForce::FillOrKill)
        .self_trade_prevention_type(SelfTradePreventionType::Maker)
        .post_only(true)
        .reduce_only(false);

    let value = serde_json::to_value(order).unwrap();
    assert_eq!(value["side"], "ask");
    assert_eq!(value["time_in_force"], "fill_or_kill");
    assert_eq!(value["self_trade_prevention_type"], "maker");
    assert_eq!(value["post_only"], true);
    assert_eq!(value["reduce_only"], false);
}

#[test]
fn fixed_point_rejects_invalid_values() {
    for invalid in ["", ".", "1.23456", "abc", "1.2.3"] {
        assert!(
            invalid.parse::<FixedPoint>().is_err(),
            "{invalid} should be invalid"
        );
    }
}

#[test]
fn fixed_point_formats_orders_and_adds_without_float_rounding() {
    let mut values = [
        "1.0000".parse::<FixedPoint>().unwrap(),
        "-0.0001".parse::<FixedPoint>().unwrap(),
        "0.1000".parse::<FixedPoint>().unwrap(),
    ];
    values.sort();

    assert_eq!(
        values.map(|value| value.to_string()),
        ["-0.0001", "0.1000", "1.0000"]
    );

    let sum = "0.1000"
        .parse::<FixedPoint>()
        .unwrap()
        .checked_add("0.0200".parse().unwrap())
        .unwrap();
    assert_eq!(sum.to_string(), "0.1200");
    assert!(
        "0.0000"
            .parse::<FixedPoint>()
            .unwrap()
            .is_zero_or_negative()
    );
    assert!(
        "-0.0001"
            .parse::<FixedPoint>()
            .unwrap()
            .is_zero_or_negative()
    );
}

#[test]
fn time_in_force_display_and_string_conversion_match_api_values() {
    assert_eq!(TimeInForce::GoodTillCanceled.as_str(), "good_till_canceled");
    assert_eq!(
        TimeInForce::ImmediateOrCancel.to_string(),
        "immediate_or_cancel"
    );

    let as_string: String = TimeInForce::FillOrKill.into();
    assert_eq!(as_string, "fill_or_kill");
}