diff --git a/Cargo.lock b/Cargo.lock index 099664b..8fef60c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "again" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05802a5ad4d172eaf796f7047b42d0af9db513585d16d4169660a21613d34b93" +dependencies = [ + "log", + "rand 0.7.3", + "wasm-timer", +] + [[package]] name = "aho-corasick" version = "0.7.15" @@ -597,6 +608,15 @@ dependencies = [ "bytes", ] +[[package]] +name = "instant" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d" +dependencies = [ + "cfg-if", +] + [[package]] name = "ipnet" version = "2.3.0" @@ -670,6 +690,15 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" +[[package]] +name = "lock_api" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.14" @@ -921,6 +950,31 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + [[package]] name = "percent-encoding" version = "2.1.0" @@ -1282,6 +1336,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "security-framework" version = "2.2.0" @@ -1762,8 +1822,9 @@ dependencies = [ [[package]] name = "trader" -version = "1.1.0" +version = "1.1.1" dependencies = [ + "again", "alpaca", "anyhow", "config", @@ -1983,6 +2044,21 @@ version = "0.2.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489" +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.50" diff --git a/Cargo.toml b/Cargo.toml index 30483c5..5777543 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "trader" -version = "1.1.0" +version = "1.1.1" authors = ["RollenRegistratorBot "] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +again = "0.1.2" alpaca = { git = "ssh://git@github.com/Overmuse/alpaca", tag = "v0.7.0" } anyhow = "1.0.40" config = "0.11.0" diff --git a/src/lib.rs b/src/lib.rs index ccc62a1..e7b00b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,15 +23,15 @@ fn parse_message(msg: OwnedMessage) -> Result { } #[tracing::instrument(name = "Executing OrderIntent", skip(api, oi))] -async fn execute_order(api: &Client, oi: OrderIntent) -> Result { +async fn execute_order(api: &Client, oi: &OrderIntent) -> Result { info!("Submitting order intent: {:?}", &oi); - api.send(SubmitOrder(oi)).await.map_err(From::from) + api.send(SubmitOrder(oi.clone())).await.map_err(From::from) } #[tracing::instrument(name = "Received message", skip(api, msg))] async fn handle_message(api: &Client, msg: OwnedMessage) -> Result { let order_intent = parse_message(msg)?; - execute_order(api, order_intent).await + again::retry(|| execute_order(api, &order_intent)).await } pub async fn run(settings: Settings) -> Result<()> { @@ -195,4 +195,74 @@ mod test { handle_message(&client, msg).await.unwrap(); } + + #[tokio::test] + async fn handles_trainsient_errors() { + let payload = r#"{"symbol":"AAPL","qty":"1.5","side":"buy","type":"limit","limit_price":"100","time_in_force":"gtc","extended_hours":false,"client_order_id":"TEST","order_class":"simple"}"#; + let msg = OwnedMessage::new( + Some(payload.as_bytes().to_vec()), // payload + None, // header + "test".into(), // topic + Timestamp::NotAvailable, //timestamp + 1, // partition + 0, // offset + None, // headers + ); + + let m1 = mock("POST", "/orders") + .match_header("apca-api-key-id", "APCA_API_KEY_ID") + .match_header("apca-api-secret-key", "APCA_API_SECRET_KEY") + .match_body(payload) + .with_status(500) + .create(); + let m2 = mock("POST", "/orders") + .match_header("apca-api-key-id", "APCA_API_KEY_ID") + .match_header("apca-api-secret-key", "APCA_API_SECRET_KEY") + .match_body(payload) + .with_body( + r#"{ + "id": "904837e3-3b76-47ec-b432-046db621571b", + "client_order_id": "TEST", + "created_at": "2018-10-05T05:48:59Z", + "updated_at": "2018-10-05T05:48:59Z", + "submitted_at": "2018-10-05T05:48:59Z", + "filled_at": null, + "expired_at": null, + "canceled_at": null, + "failed_at": null, + "replaced_at": null, + "replaced_by": null, + "replaces": null, + "asset_id": "904837e3-3b76-47ec-b432-046db621571b", + "symbol": "AAPL", + "asset_class": "us_equity", + "notional": null, + "qty": "1.5", + "filled_qty": "0", + "filled_avg_price": null, + "type": "limit", + "side": "buy", + "time_in_force": "gtc", + "limit_price": "100.00", + "status": "new", + "extended_hours": false, + "legs": null, + "trail_price": null, + "trail_percent": null, + "hwm": null + }"#, + ) + .create(); + + let client = Client::new( + mockito::server_url(), + "APCA_API_KEY_ID".to_string(), + "APCA_API_SECRET_KEY".to_string(), + ) + .unwrap(); + + handle_message(&client, msg).await.unwrap(); + m1.assert(); + m2.assert(); + } }