1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
use reqwest::Method; use serde::{ Deserialize, Serialize }; use snafu::{ ensure, ResultExt }; use std::fmt; use crate::{ error, util, Alpaca, Result }; /// The side of the order - buy or sell #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub enum OrderSide { /// An order to 'buy' Buy, /// An order to 'sell' Sell } /// The current status of the order in its lifecycle #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub enum OrderStatus { /// The order has been received by Alpaca, but hasn’t yet been routed to the execution venue. Accepted, /// The order has been received by exchanges, and is evaluated for pricing. AcceptedForBidding, /// The order has been completed for the day (either filled or done for day), but remaining settlement calculations are still pending Calculated, /// The order has been canceled and no further updates will occur for the order. This can be either due to a cancel request /// by the user, or the order has been canceled by the exchanges due to its time-in-force. Canceled, /// The order is done executing for he day, and will not recieve further updates until the next trading day. DoneForDay, /// The order has expired, and no further updates will occur for the order. Expired, /// The order has been filled, and no further updates will occur for the order. Filled, /// The order has been received by Alpaca and routed to exchanges for execution. This is the usual initial state of an order. New, /// The order has been parially filled. PartiallyFilled, /// The order is waiting to be cancelled. PendingCancel, /// The order has been received by Alpaca, and routed to the exchanges, but has not yet been accepted for execution. PendingNew, /// The order is waiting to be replaced by another order. The order will reject cancel request while in this state. PendingReplace, /// The order has been rejected, and no further updates will occur for the order. Rejected, /// The order was replaced by another order, or was updated due to a market event such as corporate action. Replaced, /// The order has been stopped, and a trade is guaranteed for the order, usually at a stated price or better, but has not yet occurred Stopped, /// The order has been suspended, and is not eligible for trading. Suspended } /// The type of the order #[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "snake_case")] pub enum OrderType { /// Buy or sell a security at a specified price or better. Limit, /// Buy or sell a security at the best available price in the current market. Market, Stop, StopLimit } /// The instruction used when placing a trade to indicate how long the order will remain active before it /// is executed or expires. #[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "lowercase")] pub enum TimeInForce { CLS, /// A day order is eligible for execution only on the day it is live. DAY, /// A Fill or Kill order is only executed if the entire order quantity can be filled, otherwise the order is canceled. FOK, /// The order is good until canceled. Non-marketable GTC limit orders are subject to price adjustments to offset /// corporate actions affecting the issue. We do not currently support Do Not Reduce(DNR) orders to opt out of /// such price adjustments. GTC, /// An Immediate Or Cancel order requires all or part of the order to be executed immediately. /// Any unfilled portion of the order is canceled IOC, OPG } impl fmt::Display for TimeInForce { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } } /// A unique order in the system. /// /// Each order has a unique identifier provided by the client. This client-side unique order ID will be /// automatically generated by the system if not provided by the client, and will be returned as part of /// the order object along with the rest of the fields described below. Once an order is placed, it can /// be queried using the client-side order ID to check the status. #[derive(Debug, Deserialize)] pub struct Order { /// Order ID - a UUID pub id: String, /// Asset class pub asset_class: String, /// Client unique order id pub client_order_id: String, /// If true, eligible for execution outside regular trading hours. #[serde(rename = "extended_hours")] pub is_extended_hours: bool, /// Filled quantity #[serde(deserialize_with = "util::to_u32")] pub filled_qty: u32, /// Filled average price #[serde(deserialize_with = "util::to_optional_f64")] pub filled_avg_price: Option<f64>, /// Limit price #[serde(deserialize_with = "util::to_optional_f64")] pub limit_price: Option<f64>, /// Type of order #[serde(rename = "type")] pub order_type: OrderType, /// Ordered quantity #[serde(deserialize_with = "util::to_u32")] pub qty: u32, /// Direction of trade - buy or sell pub side: OrderSide, /// The status of the order pub status: OrderStatus, //// Stop price #[serde(deserialize_with = "util::to_optional_f64")] pub stop_price: Option<f64>, /// Asset symbol pub symbol: String, /// how long the order is open for pub time_in_force: TimeInForce, } impl Order { /// Attempts to cancel this order. If the order is no longer cancelable (example: status=order_filled), /// an error will be generated. /// /// # Example /// /// To cancel an open order: /// /// ``` no run /// let alpaca = Alpaca::live("KEY_ID", "SECRET").await.unwrap(); /// let open_order = ...; // some buy /// /// open_order.cancel().await?; /// ``` pub async fn cancel(&self, alpaca: &Alpaca) -> Result<()> { let response = alpaca.request(Method::DELETE, format!("v2/orders/{}", self.id).as_str())? .send().await.context(error::RequestFailed)?; if response.status().is_success() { return Ok(()) } match response.status().as_u16() { 404 => error::OrderNotFound { order_id: self.id.clone() }.fail()?, 422 => error::OrderNotCancelable { order_id: self.id.clone() }.fail()?, _ => error::Unknown.fail()? } } /// Attempts to replace this open order. Will fail if the order is not open or the user does not have /// enough buying power to execute it. /// /// # Example /// /// To update the limit price of an open order to $100.0: /// /// ``` no run /// let alpaca = Alpaca::live("KEY_ID", "SECRET").await.unwrap(); /// let open_order = ...; // some buy /// /// open_order.update() /// .limit_price(100.0) /// .place(&alpaca).await.unwrap(); /// ``` pub fn update(&self) -> OrderUpdater { OrderUpdater { id: self.id.clone(), ..Default::default() } } /// Gets a list of all open orders - returns an empty vector if there are no open orders pub async fn get_open(alpaca: &Alpaca) -> Result<Vec<Order>> { let response = alpaca.request(Method::GET, "v2/orders")? .query(&[("status", "open")]) .send().await.context(error::RequestFailed)?; ensure!(response.status().is_success(), error::InvalidCredentials); Ok(response.json::<Vec<Order>>().await.context(error::BadData)?) } /// Requests a new 'buy' order. /// /// # Example /// /// To buy 100 shares of AAPL at a limit price of $100.0 before the end of today: /// /// ``` no run /// let alpaca = Alpaca::live("KEY_ID", "SECRET").await.unwrap(); /// /// let order = Order::buy("AAPL", 100, OrderType::Limit, TimeInForce::DAY) /// .limit_price(100.0) /// .place(&alpaca).await.unwrap(); /// ``` pub fn buy(symbol: &str, qty: u32, order_type: OrderType, time_in_force: TimeInForce) -> OrderBuilder { OrderBuilder { symbol: symbol.to_string(), qty: qty, side: OrderSide::Buy, order_type: order_type, time_in_force: time_in_force, ..Default::default() } } /// Requests a new 'sell' order. /// /// # Example /// /// To sell 100 shares of MSFT at market price before the end of today: /// /// ``` no run /// let alpaca = Alpaca::live("KEY_ID", "SECRET").await.unwrap(); /// /// let order = Order::sell("MSFT", 100, OrderType::Market, TimeInForce::DAY) /// .place(&alpaca).await.unwrap(); /// ``` pub fn sell(symbol: &str, qty: u32, order_type: OrderType, time_in_force: TimeInForce) -> OrderBuilder { OrderBuilder { symbol: symbol.to_string(), qty: qty, side: OrderSide::Sell, order_type: order_type, time_in_force: time_in_force, ..Default::default() } } } /// Builds up a new order and has the logic to submit the order /// /// This structure is not create directly - but is returned from Order.buy or Order.sell #[derive(Debug, Serialize)] pub struct OrderBuilder { /// Defaults to false; if true the order will be eligible to execute in premarket/afterhours. /// Only valid with order_type of Limit and time_in_force of DAY. extended_hours: bool, /// Required if the order_type is Limit or StopLimit #[serde(skip_serializing_if = "Option::is_none", serialize_with = "util::to_optional_string")] limit_price: Option<f64>, /// The type of the order #[serde(rename(serialize="type"))] order_type: OrderType, /// Number of shares to trade #[serde(serialize_with = "util::to_string")] qty: u32, /// The side of the trade - buy or sell side: OrderSide, /// Required if order_type is Stop or StopLimit #[serde(skip_serializing_if = "Option::is_none", serialize_with = "util::to_optional_string")] stop_price: Option<f64>, /// Symbol or asset ID to identify the asset to trade symbol: String, /// How long the order will stay in effect time_in_force: TimeInForce, } impl OrderBuilder { /// Sets the extended hours flag pub fn extended_hours(mut self, extended_hours: bool) -> OrderBuilder { self.extended_hours = extended_hours; self } /// Sets the price limit pub fn limit_price(mut self, limit_price: f64) -> OrderBuilder { self.limit_price = Some(limit_price); self } /// Sets the stop price pub fn stop_price(mut self, stop_price: f64) -> OrderBuilder { self.stop_price = Some(stop_price); self } /// Attempts to place the order. Will fail if certain preconditions aren't met, including: /// * Limit order with no limit price /// * Stop order with no stop price /// * Extended hours requested for non limit orders where time_in_force is not Day // // Will also fail if the buying power or shares are not sufficient. pub async fn place(&self, alpaca: &Alpaca) -> Result<Order> { // pre-conditions if (self.order_type == OrderType::Limit || self.order_type == OrderType::StopLimit) && self.limit_price == None { error::OrderInvalid { reason: "Limit orders need a limit price.".to_string() }.fail()? } if (self.order_type == OrderType::Stop || self.order_type == OrderType::StopLimit) && self.stop_price == None { error::OrderInvalid { reason: "Stop orders need a stop price.".to_string() }.fail()? } if self.extended_hours && (self.order_type != OrderType::Limit && self.time_in_force != TimeInForce::DAY) { error::OrderInvalid { reason: "Extended hours only works with limit orders for today".to_string() }.fail()? } let response = alpaca.request(Method::POST, "v2/orders")? .json::<OrderBuilder>(self) .send() .await.context(error::BadData)?; if response.status().is_success() { return Ok(response.json::<Order>().await.context(error::BadData)?) } match response.status().as_u16() { 403 => error::OrderForbidden.fail()?, _ => error::Unknown.fail()? } } } impl Default for OrderBuilder { fn default() -> Self { OrderBuilder { symbol: "".to_string(), extended_hours: false, qty: 0, side: OrderSide::Buy, order_type: OrderType::Market, time_in_force: TimeInForce::DAY, limit_price: None, stop_price: None } } } /// Builds up a replacement order and has the logic to submit it. /// /// This structure is not create directly - but is returned from Order.update #[derive(Debug, Default, Serialize)] pub struct OrderUpdater { /// The id of the order to replace #[serde(skip_serializing)] id: String, /// Required if the order_type is Limit or StopLimit #[serde(skip_serializing_if = "Option::is_none", serialize_with = "util::to_optional_string")] limit_price: Option<f64>, /// The number of shares to trade #[serde(skip_serializing_if = "Option::is_none", serialize_with = "util::to_optional_string")] qty: Option<u32>, /// Required if order_type is Stop or StopLimit #[serde(skip_serializing_if = "Option::is_none", serialize_with = "util::to_optional_string")] stop_price: Option<f64>, /// How long the order will stay in effect #[serde(skip_serializing_if = "Option::is_none", serialize_with = "util::to_optional_string")] time_in_force: Option<TimeInForce>, } impl OrderUpdater { /// Sets the price limit pub fn limit_price(mut self, limit_price: f64) -> OrderUpdater { self.limit_price = Some(limit_price); self } /// Sets the number of shares to trade pub fn qty(mut self, qty: u32) -> OrderUpdater { self.qty = Some(qty); self } /// Sets the stop price pub fn stop_price(mut self, stop_price: f64) -> OrderUpdater { self.stop_price = Some(stop_price); self } /// Sets the time the order is valid for pub fn time_in_force(mut self, time_in_force: TimeInForce) -> OrderUpdater { self.time_in_force = Some(time_in_force); self } /// Attempts to replace the order. pub async fn place(&self, alpaca: &Alpaca) -> Result<Order> { let response = alpaca.request(Method::PATCH, format!("v2/orders/{}", self.id).as_str())? .json::<OrderUpdater>(self) .send() .await.context(error::BadData)?; if response.status().is_success() { return Ok(response.json::<Order>().await.context(error::BadData)?) } match response.status().as_u16() { 403 => error::OrderForbidden.fail()?, _ => error::Unknown.fail()? } } }