Skip to content

Commit

Permalink
feat(execution): place an order
Browse files Browse the repository at this point in the history
  • Loading branch information
antonio-hickey committed Oct 17, 2024
1 parent 60ed5b9 commit e9bfc29
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 12 deletions.
38 changes: 29 additions & 9 deletions examples/execution.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
//! Example file on basic usage for order execution endpoints
use tradestation::{ClientBuilder, Error, Token};
use tradestation::account::OrderType;
use tradestation::execution::{Duration, OrderRequestBuilder, OrderTimeInForce, TradeAction};
use tradestation::{ClientBuilder, Error};

#[tokio::main]
async fn main() -> Result<(), Error> {
// Create client
let mut client = ClientBuilder::new()?
.set_credentials("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET")?
.set_token(Token {
access_token: String::from("YOUR_ACCESS_TOKEN"),
refresh_token: String::from("YOUR_REFRESH_TOKEN"),
id_token: String::from("YOUR_ID_TOKEN"),
token_type: String::from("Bearer"),
scope: String::from("YOUR_SCOPES SPACE_SEPERATED FOR_EACH_SCOPE"),
expires_in: 1200,
})?
.authorize("YOUR_AUTHORIZATION_CODE")
.await?
.build()
.await?;
println!("Your TradeStation API Bearer Token: {:?}", client.token);

//--
// Example: Fetch a list of routes to send orders for execution.
Expand All @@ -30,5 +27,28 @@ async fn main() -> Result<(), Error> {
println!("Valid activation triggers for order execution: {triggers:?}");
//---

//--
// Example: Place an order to buy 100 shares of JP Morgan (`"JPM"`)
// using a limit order with the limit price of $`"220.50"`, with
// a order duration of Good Till Closed.
let order_req = OrderRequestBuilder::new()
.account_id("YOUR_EQUITIES_ACCOUNT_ID")
.symbol("JPM")
.trade_action(TradeAction::Buy)
.quantity("100")
.order_type(OrderType::Limit)
.limit_price("220.50")
.time_in_force(OrderTimeInForce {
duration: Duration::GTC,
expiration: None,
})
.build()?;

match order_req.place(&mut client).await {
Ok(resp) => println!("Order Response: {resp:?}"),
Err(e) => println!("Order Response: {e:?}"),
}
//--

Ok(())
}
38 changes: 37 additions & 1 deletion src/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use crate::{
account::{AssetType, MarketActivationRule, OrderType, TimeActivationRule, TrailingStop},
responses::execution::{
ConfirmOrderResp, ConfirmOrderRespRaw, GetActivationTriggersResp,
GetActivationTriggersRespRaw, GetExecutionRoutesResp, GetExecutionRoutesRespRaw,
GetActivationTriggersRespRaw, GetExecutionRoutesResp, GetExecutionRoutesRespRaw, OrderResp,
OrderRespRaw,
},
Client, Error,
};
Expand Down Expand Up @@ -131,6 +132,23 @@ impl OrderRequest {
Err(resp.error.unwrap_or(Error::UnknownTradeStationAPIError))
}
}

pub async fn place(self, client: &mut Client) -> Result<Vec<OrderResponse>, Error> {
let endpoint = String::from("orderexecution/orders");

let resp: OrderResp = client
.post(&endpoint, &self)
.await?
.json::<OrderRespRaw>()
.await?
.into();

if let Some(orders) = resp.orders {
Ok(orders)
} else {
Err(resp.error.unwrap_or(Error::UnknownTradeStationAPIError))
}
}
}

#[derive(Debug, Default)]
Expand Down Expand Up @@ -874,3 +892,21 @@ pub struct OrderConfirmation {
/// NOTE: Only valid for futures orders.
pub initial_margin_display: Option<String>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "PascalCase")]
/// The result of placing an order.
///
/// NOTE: The error that would be here would instead
/// be returned as an `Error` variant.
pub struct OrderResponse {
/// Short text summary / description of the order.
pub message: String,

#[serde(rename = "OrderID")]
/// The id of the order.
pub order_id: String,

/// The error for the order.
pub error: Option<String>,
}
52 changes: 50 additions & 2 deletions src/responses/execution.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,55 @@
use crate::execution::{ActivationTrigger, OrderConfirmation};
use crate::{Error, Route};
use crate::{
execution::{ActivationTrigger, OrderConfirmation, OrderResponse},
Error, Route,
};
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "PascalCase")]
/// The TradeStation API Response for confirming
/// an order, but not actually placing it.
pub struct OrderRespRaw {
/// The orders modified, placed, or canceled.
orders: Option<Vec<OrderResponse>>,

/// The error type from TradeStation's API
///
/// NOTE: Will be None if there was no error
error: Option<String>,

/// The error message from TradeStation's API
///
/// NOTE: Will be None if there was no error
message: Option<String>,
}
#[derive(Debug)]
/// The TradeStation API Response for confirming
/// an order, but not actually placing it.
pub struct OrderResp {
/// The order confirmations.
pub orders: Option<Vec<OrderResponse>>,

/// The error from TradeStation's API.
///
/// NOTE: Will be None if there was no error.
pub error: Option<Error>,
}
impl From<OrderRespRaw> for OrderResp {
fn from(raw: OrderRespRaw) -> Self {
let error_enum =
if let (Some(err), Some(msg)) = (raw.error.as_deref(), raw.message.as_deref()) {
Error::from_tradestation_api_error(err, msg)
} else {
None
};

OrderResp {
orders: raw.orders,
error: error_enum,
}
}
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "PascalCase")]
/// The TradeStation API Response for confirming
Expand Down

0 comments on commit e9bfc29

Please sign in to comment.