use reqwest::Method;
use uuid::Uuid;
use crate::auth::Credentials;
use crate::config::BaseUrl;
use crate::error::Result;
use crate::rest::{Empty, RestClient, RestConfig};
use crate::trading::models::{
AccountConfiguration, Activity, Asset, Calendar, Clock, ClosePositionResponse,
CorporateActionAnnouncement, OptionContract, OptionContractsResponse, Order, PortfolioHistory,
Position, TradeAccount, Watchlist,
};
use crate::trading::requests::{
CancelOrderResponse, ClosePositionRequest, CreateWatchlistRequest,
GetCorporateAnnouncementsRequest, GetOptionContractsRequest, GetOrderByIdRequest,
GetOrdersRequest, GetPortfolioHistoryRequest, OrderRequest, ReplaceOrderRequest,
UpdateWatchlistRequest,
};
use crate::types::AssetIdent;
#[derive(Debug, Clone)]
pub struct TradingClient {
rest: RestClient,
}
impl TradingClient {
pub fn new(credentials: &Credentials, paper: bool) -> Result<Self> {
Self::with_config(credentials, RestConfig::from(BaseUrl::trading(paper)))
}
pub fn with_config(credentials: &Credentials, config: RestConfig) -> Result<Self> {
Ok(Self {
rest: RestClient::new(credentials, config)?,
})
}
#[must_use]
pub fn rest(&self) -> &RestClient {
&self.rest
}
async fn send_void(&self, method: Method, path: &str) -> Result<()> {
self.rest
.request_raw(method, path, None::<&Empty>, None::<&Empty>)
.await?;
Ok(())
}
pub async fn submit_order(&self, order: &OrderRequest) -> Result<Order> {
order.validate()?;
self.rest.post("/orders", order).await
}
pub async fn get_orders(&self, filter: Option<&GetOrdersRequest>) -> Result<Vec<Order>> {
match filter {
Some(filter) => self.rest.get("/orders", filter).await,
None => self.rest.get("/orders", &Empty).await,
}
}
pub async fn get_order_by_id(
&self,
order_id: Uuid,
filter: Option<&GetOrderByIdRequest>,
) -> Result<Order> {
let path = format!("/orders/{order_id}");
match filter {
Some(filter) => self.rest.get(&path, filter).await,
None => self.rest.get(&path, &Empty).await,
}
}
pub async fn get_order_by_client_id(&self, client_order_id: &str) -> Result<Order> {
self.rest
.get(
"/orders:by_client_order_id",
&[("client_order_id", client_order_id)],
)
.await
}
pub async fn replace_order_by_id(
&self,
order_id: Uuid,
replacement: Option<&ReplaceOrderRequest>,
) -> Result<Order> {
let path = format!("/orders/{order_id}");
match replacement {
Some(replacement) => {
replacement.validate()?;
self.rest.patch(&path, replacement).await
}
None => self.rest.patch(&path, &Empty).await,
}
}
pub async fn cancel_orders(&self) -> Result<Vec<CancelOrderResponse>> {
self.rest.delete("/orders", &Empty).await
}
pub async fn cancel_order_by_id(&self, order_id: Uuid) -> Result<()> {
self.send_void(Method::DELETE, &format!("/orders/{order_id}"))
.await
}
pub async fn get_all_positions(&self) -> Result<Vec<Position>> {
self.rest.get("/positions", &Empty).await
}
pub async fn get_open_position(&self, asset: &AssetIdent) -> Result<Position> {
self.rest.get(&format!("/positions/{asset}"), &Empty).await
}
pub async fn close_all_positions(
&self,
cancel_orders: Option<bool>,
) -> Result<Vec<ClosePositionResponse>> {
match cancel_orders {
Some(cancel) => {
self.rest
.delete("/positions", &[("cancel_orders", cancel)])
.await
}
None => self.rest.delete("/positions", &Empty).await,
}
}
pub async fn close_position(
&self,
asset: &AssetIdent,
close: Option<ClosePositionRequest>,
) -> Result<Order> {
let path = format!("/positions/{asset}");
match close {
Some(close) => self.rest.delete(&path, &close.to_query()).await,
None => self.rest.delete(&path, &Empty).await,
}
}
pub async fn exercise_options_position(&self, contract: &AssetIdent) -> Result<()> {
self.send_void(Method::POST, &format!("/positions/{contract}/exercise"))
.await
}
pub async fn get_account(&self) -> Result<TradeAccount> {
self.rest.get("/account", &Empty).await
}
pub async fn get_account_configurations(&self) -> Result<AccountConfiguration> {
self.rest.get("/account/configurations", &Empty).await
}
pub async fn set_account_configurations(
&self,
configuration: &AccountConfiguration,
) -> Result<AccountConfiguration> {
self.rest
.patch("/account/configurations", configuration)
.await
}
pub async fn get_portfolio_history(
&self,
filter: Option<&GetPortfolioHistoryRequest>,
) -> Result<PortfolioHistory> {
let path = "/account/portfolio/history";
match filter {
Some(filter) => self.rest.get(path, filter).await,
None => self.rest.get(path, &Empty).await,
}
}
pub async fn get_account_activities(&self, query: &[(&str, String)]) -> Result<Vec<Activity>> {
self.rest.get("/account/activities", query).await
}
pub async fn get_all_assets(
&self,
filter: Option<&crate::trading::requests::GetAssetsRequest>,
) -> Result<Vec<Asset>> {
match filter {
Some(filter) => self.rest.get("/assets", filter).await,
None => self.rest.get("/assets", &Empty).await,
}
}
pub async fn get_asset(&self, asset: &AssetIdent) -> Result<Asset> {
self.rest.get(&format!("/assets/{asset}"), &Empty).await
}
pub async fn get_clock(&self) -> Result<Clock> {
self.rest.get("/clock", &Empty).await
}
pub async fn get_calendar(
&self,
filter: Option<&crate::trading::requests::GetCalendarRequest>,
) -> Result<Vec<Calendar>> {
match filter {
Some(filter) => self.rest.get("/calendar", filter).await,
None => self.rest.get("/calendar", &Empty).await,
}
}
pub async fn get_watchlists(&self) -> Result<Vec<Watchlist>> {
self.rest.get("/watchlists", &Empty).await
}
pub async fn get_watchlist_by_id(&self, watchlist_id: Uuid) -> Result<Watchlist> {
self.rest
.get(&format!("/watchlists/{watchlist_id}"), &Empty)
.await
}
pub async fn create_watchlist(&self, watchlist: &CreateWatchlistRequest) -> Result<Watchlist> {
self.rest.post("/watchlists", watchlist).await
}
pub async fn update_watchlist_by_id(
&self,
watchlist_id: Uuid,
update: &UpdateWatchlistRequest,
) -> Result<Watchlist> {
update.validate()?;
self.rest
.put(&format!("/watchlists/{watchlist_id}"), update)
.await
}
pub async fn add_asset_to_watchlist_by_id(
&self,
watchlist_id: Uuid,
symbol: &str,
) -> Result<Watchlist> {
self.rest
.post(
&format!("/watchlists/{watchlist_id}"),
&serde_json::json!({ "symbol": symbol }),
)
.await
}
pub async fn remove_asset_from_watchlist_by_id(
&self,
watchlist_id: Uuid,
symbol: &str,
) -> Result<Watchlist> {
self.rest
.delete(&format!("/watchlists/{watchlist_id}/{symbol}"), &Empty)
.await
}
pub async fn delete_watchlist_by_id(&self, watchlist_id: Uuid) -> Result<()> {
self.send_void(Method::DELETE, &format!("/watchlists/{watchlist_id}"))
.await
}
#[deprecated(
since = "0.1.0",
note = "Alpaca deprecated this route; use the corporate actions market data endpoint instead"
)]
pub async fn get_corporate_announcements(
&self,
filter: &GetCorporateAnnouncementsRequest,
) -> Result<Vec<CorporateActionAnnouncement>> {
filter.validate()?;
self.rest
.get("/corporate_actions/announcements", &filter.to_query())
.await
}
#[deprecated(
since = "0.1.0",
note = "Alpaca deprecated this route; use the corporate actions market data endpoint instead"
)]
pub async fn get_corporate_announcement_by_id(
&self,
announcement_id: Uuid,
) -> Result<CorporateActionAnnouncement> {
self.rest
.get(
&format!("/corporate_actions/announcements/{announcement_id}"),
&Empty,
)
.await
}
pub async fn get_option_contracts(
&self,
filter: &GetOptionContractsRequest,
) -> Result<OptionContractsResponse> {
self.rest.get("/options/contracts", filter).await
}
pub async fn get_option_contract(&self, contract: &AssetIdent) -> Result<OptionContract> {
self.rest
.get(&format!("/options/contracts/{contract}"), &Empty)
.await
}
}