diff --git a/Cargo.lock b/Cargo.lock index 21c1ab4..a06e047 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1174,9 +1174,9 @@ checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" [[package]] name = "rust_decimal" -version = "1.14.3" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01127cb8617e5e21bcf2e19b5eb48317735ca677f1d0a94833c21c331c446582" +checksum = "c5446d1cf2dfe2d6367c8b27f2082bdf011e60e76fa1fcd140047f535156d6e7" dependencies = [ "arrayvec", "num-traits 0.2.14", @@ -1651,7 +1651,7 @@ dependencies = [ [[package]] name = "trader" -version = "2.0.0" +version = "2.0.1" dependencies = [ "again", "alpaca", @@ -1662,6 +1662,7 @@ dependencies = [ "kafka-settings", "mockito", "rdkafka", + "rust_decimal", "sentry", "serde 1.0.126", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index e54d4dd..b714198 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "trader" -version = "2.0.0" +version = "2.0.1" authors = ["RollenRegistratorBot "] edition = "2018" @@ -15,6 +15,7 @@ dotenv = "0.15" futures = "0.3" kafka-settings = { git = "ssh://git@github.com/Overmuse/kafka-settings", tag = "v0.3.3" } rdkafka = {version = "0.26", features = ["ssl-vendored"]} +rust_decimal = "1.15.0" sentry = "0.23" serde = "1.0" serde_json = "1.0" diff --git a/src/lib.rs b/src/lib.rs index 3a7cec2..20a80a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,8 +7,9 @@ use anyhow::{anyhow, Result}; use futures::StreamExt; use kafka_settings::consumer; use rdkafka::{message::OwnedMessage, Message}; +use rust_decimal::prelude::*; use tracing::{info, warn}; -use trading_base::TradeIntent; +use trading_base::{OrderType, TradeIntent}; mod settings; pub use settings::Settings; @@ -23,6 +24,45 @@ fn parse_message(msg: OwnedMessage) -> Result { } } +fn normalize_intent(ti: &mut TradeIntent) { + let new_order_type = if ti.qty.is_positive() { + match ti.order_type { + OrderType::Market => OrderType::Market, + OrderType::Limit { limit_price } => OrderType::Limit { + limit_price: limit_price.round_dp_with_strategy(2, RoundingStrategy::ToZero), + }, + OrderType::Stop { stop_price } => OrderType::Stop { + stop_price: stop_price.round_dp_with_strategy(2, RoundingStrategy::AwayFromZero), + }, + OrderType::StopLimit { + stop_price, + limit_price, + } => OrderType::StopLimit { + stop_price: stop_price.round_dp_with_strategy(2, RoundingStrategy::AwayFromZero), + limit_price: limit_price.round_dp_with_strategy(2, RoundingStrategy::ToZero), + }, + } + } else { + match ti.order_type { + OrderType::Market => OrderType::Market, + OrderType::Limit { limit_price } => OrderType::Limit { + limit_price: limit_price.round_dp_with_strategy(2, RoundingStrategy::AwayFromZero), + }, + OrderType::Stop { stop_price } => OrderType::Stop { + stop_price: stop_price.round_dp_with_strategy(2, RoundingStrategy::ToZero), + }, + OrderType::StopLimit { + stop_price, + limit_price, + } => OrderType::StopLimit { + stop_price: stop_price.round_dp_with_strategy(2, RoundingStrategy::ToZero), + limit_price: limit_price.round_dp_with_strategy(2, RoundingStrategy::AwayFromZero), + }, + } + }; + ti.order_type = new_order_type; +} + fn translate_intent(ti: TradeIntent) -> OrderIntent { let (qty, side) = if ti.qty > 0 { (ti.qty as usize, Side::Buy) @@ -65,7 +105,8 @@ async fn execute_order(api: &Client, oi: &OrderIntent) -> Result { #[tracing::instrument(name = "Received message", skip(api, msg))] async fn handle_message(api: &Client, msg: OwnedMessage) -> Result { - let trade_intent = parse_message(msg)?; + let mut trade_intent = parse_message(msg)?; + normalize_intent(&mut trade_intent); let order_intent = translate_intent(trade_intent); again::retry(|| execute_order(api, &order_intent)).await } @@ -103,6 +144,31 @@ mod test { use mockito::mock; use rdkafka::message::Timestamp; + #[test] + fn test_normalize_intent() { + let mut ti = TradeIntent::new("AAPL", 100).order_type(OrderType::Limit { + limit_price: Decimal::new(7777, 3), + }); + normalize_intent(&mut ti); + assert_eq!( + ti.order_type, + OrderType::Limit { + limit_price: Decimal::new(777, 2) + } + ); + + let mut ti = TradeIntent::new("AAPL", -100).order_type(OrderType::Limit { + limit_price: Decimal::new(7777, 3), + }); + normalize_intent(&mut ti); + assert_eq!( + ti.order_type, + OrderType::Limit { + limit_price: Decimal::new(778, 2) + } + ); + } + #[test] fn unwrap_msg() { let payload = r#"{