use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use super::{OrderState, OrderType, Side};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GetAccountInfoRequest {
pub oms_id: u64,
pub account_id: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct AccountInfo {
#[serde(rename = "OMSID")]
pub omsid: u64,
pub account_id: u64,
pub account_name: String,
pub account_handle: String,
pub firm_id: String,
pub firm_name: String,
pub account_type: i32,
#[serde(rename = "FeeGroupID")]
pub fee_group_id: i64,
#[serde(rename = "ParentID")]
pub parent_id: i64,
pub risk_type: i32,
pub verification_level: i32,
pub fee_product_type: i32,
pub fee_product: i64,
pub referer_id: i64,
#[serde(default)]
pub supported_venue_ids: Vec<i64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct GetAccountPositionsRequest {
pub account_id: u64,
#[serde(rename = "OMSId")]
pub oms_id: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct AccountPosition {
#[serde(rename = "OMSId")]
pub oms_id: u64,
pub account_id: u64,
pub product_symbol: String,
pub product_id: u64,
pub amount: Decimal,
pub hold: Decimal,
pub pending_deposits: Decimal,
pub pending_withdraws: Decimal,
pub total_day_deposits: Decimal,
pub total_day_withdraws: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct SubscribeAccountEventsRequest {
pub account_id: u64,
#[serde(rename = "OMSId")]
pub oms_id: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct SubscribeAccountEventsResponse {
#[serde(alias = "Subscribe")]
pub subscribed: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct AccountPositionEvent {
#[serde(rename = "OMSId")]
pub oms_id: u64,
pub account_id: u64,
pub product_symbol: String,
pub product_id: u64,
pub amount: Decimal,
pub hold: Decimal,
pub pending_deposits: Decimal,
pub pending_withdraws: Decimal,
pub total_day_deposits: Decimal,
pub total_day_withdraws: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct OrderStateEvent {
pub side: Side,
pub order_id: u64,
pub price: Decimal,
pub quantity: Decimal,
pub instrument: u64,
pub account: u64,
pub order_type: OrderType,
pub client_order_id: u64,
pub order_state: OrderState,
pub receive_time: serde_json::Value,
pub orig_quantity: Decimal,
pub quantity_executed: Decimal,
pub avg_price: Decimal,
pub change_reason: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct OrderTradeEvent {
#[serde(rename = "OMSId")]
pub oms_id: u64,
pub trade_id: u64,
pub order_id: u64,
pub account_id: u64,
pub client_order_id: u64,
pub instrument_id: u64,
pub side: Side,
pub quantity: Decimal,
pub price: Decimal,
pub value: Decimal,
pub trade_time: serde_json::Value,
#[serde(default)]
pub contra_acct_id: Option<u64>,
#[serde(default)]
pub order_trade_revision: Option<u64>,
#[serde(default)]
pub direction: Option<String>,
#[serde(default)]
pub fee: Option<Decimal>,
#[serde(default)]
pub fee_product_id: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct NewOrderRejectEvent {
#[serde(rename = "OMSId")]
pub oms_id: u64,
pub account_id: u64,
pub client_order_id: u64,
pub status: String,
pub reject_reason: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct CancelOrderRejectEvent {
#[serde(rename = "OMSId")]
pub oms_id: u64,
pub account_id: u64,
pub order_id: u64,
pub order_revision: u64,
pub order_type: OrderType,
pub instrument_id: u64,
pub status: String,
pub reject_reason: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AccountEventType {
Position,
OrderState,
OrderTrade,
NewOrderReject,
CancelOrderReject,
}
impl AccountEventType {
pub fn from_name(name: &str) -> Option<Self> {
match name {
"AccountPositionEvent" => Some(AccountEventType::Position),
"OrderStateEvent" => Some(AccountEventType::OrderState),
"OrderTradeEvent" => Some(AccountEventType::OrderTrade),
"NewOrderRejectEvent" => Some(AccountEventType::NewOrderReject),
"CancelOrderRejectEvent" => Some(AccountEventType::CancelOrderReject),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_account_position_deserialize() {
let json = r#"[
{"OMSId":1,"AccountId":12345,"ProductSymbol":"BTC","ProductId":1,"Amount":1.5,"Hold":0.1,"PendingDeposits":0,"PendingWithdraws":0,"TotalDayDeposits":0,"TotalDayWithdraws":0},
{"OMSId":1,"AccountId":12345,"ProductSymbol":"CAD","ProductId":2,"Amount":50000.00,"Hold":1000.00,"PendingDeposits":500,"PendingWithdraws":0,"TotalDayDeposits":100,"TotalDayWithdraws":50}
]"#;
let positions: Vec<AccountPosition> = serde_json::from_str(json).unwrap();
assert_eq!(positions.len(), 2);
assert_eq!(positions[0].oms_id, 1);
assert_eq!(positions[0].account_id, 12345);
assert_eq!(positions[0].product_symbol, "BTC");
assert_eq!(positions[0].product_id, 1);
assert_eq!(positions[0].amount, Decimal::new(15, 1));
assert_eq!(positions[0].hold, Decimal::new(1, 1));
assert_eq!(positions[1].product_symbol, "CAD");
assert_eq!(positions[1].amount, Decimal::new(5000000, 2));
assert_eq!(positions[1].hold, Decimal::new(100000, 2));
}
#[test]
fn test_order_state_event_deserialize() {
let json = r#"{
"Side":0,
"OrderId":98765,
"Price":50000.00,
"Quantity":0.001,
"Instrument":1,
"Account":12345,
"OrderType":2,
"ClientOrderId":0,
"OrderState":0,
"ReceiveTime":"2024-01-15T10:30:00Z",
"OrigQuantity":0.001,
"QuantityExecuted":0,
"AvgPrice":0,
"ChangeReason":"NewInputAccepted"
}"#;
let event: OrderStateEvent = serde_json::from_str(json).unwrap();
assert_eq!(event.side, Side::Buy);
assert_eq!(event.order_id, 98765);
assert_eq!(event.price, Decimal::new(5000000, 2));
assert_eq!(event.quantity, Decimal::new(1, 3));
assert_eq!(event.instrument, 1);
assert_eq!(event.account, 12345);
assert_eq!(event.order_type, OrderType::Limit);
assert_eq!(event.client_order_id, 0);
assert_eq!(event.order_state, OrderState::Working);
assert_eq!(event.orig_quantity, Decimal::new(1, 3));
assert_eq!(event.quantity_executed, Decimal::ZERO);
assert_eq!(event.avg_price, Decimal::ZERO);
}
#[test]
fn test_order_trade_event_deserialize() {
let json = r#"{
"OMSId":1,
"TradeId":11111,
"OrderId":98765,
"AccountId":12345,
"ClientOrderId":0,
"InstrumentId":1,
"Side":0,
"Quantity":0.001,
"Price":50000.00,
"Value":50.00,
"TradeTime":"2024-01-15T10:30:05Z",
"Fee":0.25,
"FeeProductId":2
}"#;
let event: OrderTradeEvent = serde_json::from_str(json).unwrap();
assert_eq!(event.oms_id, 1);
assert_eq!(event.trade_id, 11111);
assert_eq!(event.order_id, 98765);
assert_eq!(event.account_id, 12345);
assert_eq!(event.client_order_id, 0);
assert_eq!(event.instrument_id, 1);
assert_eq!(event.side, Side::Buy);
assert_eq!(event.quantity, Decimal::new(1, 3));
assert_eq!(event.price, Decimal::new(5000000, 2));
assert_eq!(event.value, Decimal::new(5000, 2));
assert_eq!(event.fee, Some(Decimal::new(25, 2)));
assert_eq!(event.fee_product_id, Some(2));
}
#[test]
fn test_account_event_type_from_name() {
assert_eq!(
AccountEventType::from_name("AccountPositionEvent"),
Some(AccountEventType::Position)
);
assert_eq!(
AccountEventType::from_name("OrderStateEvent"),
Some(AccountEventType::OrderState)
);
assert_eq!(
AccountEventType::from_name("OrderTradeEvent"),
Some(AccountEventType::OrderTrade)
);
assert_eq!(
AccountEventType::from_name("NewOrderRejectEvent"),
Some(AccountEventType::NewOrderReject)
);
assert_eq!(
AccountEventType::from_name("CancelOrderRejectEvent"),
Some(AccountEventType::CancelOrderReject)
);
assert_eq!(AccountEventType::from_name("UnknownEvent"), None);
}
#[test]
fn test_get_account_info_request_serialize() {
let request = GetAccountInfoRequest {
oms_id: 1,
account_id: 12345,
};
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("\"omsId\":1"));
assert!(json.contains("\"accountId\":12345"));
}
#[test]
fn test_get_account_positions_request_serialize() {
let request = GetAccountPositionsRequest {
account_id: 12345,
oms_id: 1,
};
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("\"AccountId\":12345"));
assert!(json.contains("\"OMSId\":1"));
}
#[test]
fn test_subscribe_account_events_request_serialize() {
let request = SubscribeAccountEventsRequest {
account_id: 12345,
oms_id: 1,
};
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("\"AccountId\":12345"));
assert!(json.contains("\"OMSId\":1"));
}
#[test]
fn test_subscribe_account_events_response_deserialize() {
let json = r#"{"Subscribe": true}"#;
let response: SubscribeAccountEventsResponse =
serde_json::from_str(json).unwrap();
assert!(response.subscribed);
let json2 = r#"{"Subscribed": false}"#;
let response2: SubscribeAccountEventsResponse =
serde_json::from_str(json2).unwrap();
assert!(!response2.subscribed);
}
#[test]
fn test_new_order_reject_event_deserialize() {
let json = r#"{
"OMSId": 1,
"AccountId": 12345,
"ClientOrderId": 9999,
"Status": "Rejected",
"RejectReason": "No More Market"
}"#;
let event: NewOrderRejectEvent = serde_json::from_str(json).unwrap();
assert_eq!(event.oms_id, 1);
assert_eq!(event.account_id, 12345);
assert_eq!(event.client_order_id, 9999);
assert_eq!(event.status, "Rejected");
assert_eq!(event.reject_reason, "No More Market");
}
#[test]
fn test_cancel_order_reject_event_deserialize() {
let json = r#"{
"OMSId": 1,
"AccountId": 12345,
"OrderId": 9876,
"OrderRevision": 1,
"OrderType": 2,
"InstrumentId": 1,
"Status": "Rejected",
"RejectReason": "Order Not Found"
}"#;
let event: CancelOrderRejectEvent = serde_json::from_str(json).unwrap();
assert_eq!(event.oms_id, 1);
assert_eq!(event.account_id, 12345);
assert_eq!(event.order_id, 9876);
assert_eq!(event.order_revision, 1);
assert_eq!(event.order_type, OrderType::Limit);
assert_eq!(event.instrument_id, 1);
assert_eq!(event.status, "Rejected");
assert_eq!(event.reject_reason, "Order Not Found");
}
#[test]
fn test_account_position_event_deserialize() {
let json = r#"{
"OMSId":1,
"AccountId":12345,
"ProductSymbol":"CAD",
"ProductId":2,
"Amount":50000.00,
"Hold":1000.00,
"PendingDeposits":0,
"PendingWithdraws":0,
"TotalDayDeposits":0,
"TotalDayWithdraws":0
}"#;
let event: AccountPositionEvent = serde_json::from_str(json).unwrap();
assert_eq!(event.oms_id, 1);
assert_eq!(event.account_id, 12345);
assert_eq!(event.product_symbol, "CAD");
assert_eq!(event.product_id, 2);
assert_eq!(event.amount, Decimal::new(5000000, 2));
assert_eq!(event.hold, Decimal::new(100000, 2));
}
#[test]
fn test_account_info_deserialize() {
let json = r#"{
"OMSID": 1,
"AccountId": 12345,
"AccountName": "Test Account",
"AccountHandle": "test_handle",
"FirmId": "FIRM1",
"FirmName": "Test Firm",
"AccountType": 0,
"FeeGroupID": 1,
"ParentID": 0,
"RiskType": 1,
"VerificationLevel": 3,
"FeeProductType": 0,
"FeeProduct": 2,
"RefererId": 0,
"SupportedVenueIds": [1, 2, 3]
}"#;
let info: AccountInfo = serde_json::from_str(json).unwrap();
assert_eq!(info.omsid, 1);
assert_eq!(info.account_id, 12345);
assert_eq!(info.account_name, "Test Account");
assert_eq!(info.account_handle, "test_handle");
assert_eq!(info.firm_id, "FIRM1");
assert_eq!(info.firm_name, "Test Firm");
assert_eq!(info.account_type, 0);
assert_eq!(info.fee_group_id, 1);
assert_eq!(info.parent_id, 0);
assert_eq!(info.risk_type, 1);
assert_eq!(info.verification_level, 3);
assert_eq!(info.fee_product_type, 0);
assert_eq!(info.fee_product, 2);
assert_eq!(info.referer_id, 0);
assert_eq!(info.supported_venue_ids, vec![1, 2, 3]);
}
}