rpaca 0.8.1

A rust crate wrapping the Alpaca API
Documentation
//! Positions module for Alpaca API v2.
//!
//! This module provides functionality for managing trading positions through Alpaca's trading API.
//! It supports retrieving, closing, and exercising positions for various asset types.
//!
//! The module includes functionality for:
//! - Retrieving all open positions
//! - Getting information about a specific position by symbol
//! - Closing individual positions (fully or partially)
//! - Closing all positions at once
//! - Exercising options positions

use crate::auth::{Alpaca, TradingType};
use crate::request::create_trading_request;
use crate::trading::v2::orders::{Order, OrderRequest, create_order};
use reqwest::Method;
use serde::{Deserialize, Serialize};
use typed_builder::TypedBuilder;

#[derive(Debug, Serialize, Deserialize)]
pub struct Position {
    pub asset_id: String,
    pub symbol: String,
    pub exchange: String,
    pub asset_class: String,
    pub asset_marginable: bool,
    pub qty: String,
    pub avg_entry_price: String,
    pub side: String,
    pub market_value: String,
    pub cost_basis: String,
    pub unrealized_pl: String,
    pub unrealized_plpc: String,
    pub unrealized_intraday_pl: String,
    pub unrealized_intraday_plpc: String,
    pub current_price: String,
    pub lastday_price: String,
    pub change_today: String,
    pub qty_available: String,
}
pub async fn get_positions(alpaca: &Alpaca) -> Result<Vec<Position>, Box<dyn std::error::Error>> {
    let endpoint = "/v2/positions".to_string();
    let response = create_trading_request::<()>(alpaca, Method::GET, &endpoint, None).await?;
    if !response.status().is_success() {
        let text = response.text().await.unwrap_or_default();
        return Err(format!("Getting positions failed: {}", text).into());
    }
    let positions: Vec<Position> = response.json().await?;
    Ok(positions)
}

pub async fn get_single_position(
    alpaca: &Alpaca,
    symbol: String,
) -> Result<Position, Box<dyn std::error::Error>> {
    let endpoint = format!("/v2/positions/{symbol}");
    let response = create_trading_request::<()>(alpaca, Method::GET, &endpoint, None).await?;
    if !response.status().is_success() {
        let text = response.text().await.unwrap_or_default();
        return Err(format!("Getting single position failed: {}", text).into());
    }
    let p: Position = response.json().await?;
    Ok(p)
}
#[derive(TypedBuilder)]
pub struct ClosePositionParams {
    pub symbol: String,
    #[builder(default, setter(strip_option))]
    pub qty: Option<f64>,
    #[builder(default, setter(strip_option))]
    pub percentage: Option<f64>,
}
pub async fn close_position(
    alpaca: &Alpaca,
    params: ClosePositionParams,
) -> Result<Order, Box<dyn std::error::Error>> {
    let mut endpoint = format!("/v2/positions/{}", params.symbol);
    if params.qty.is_some() {
        let qty = params.qty.unwrap();
        endpoint = format!("{}?qty={}", endpoint, qty);
    } else if params.percentage.is_some() {
        let percentage = params.percentage.unwrap();
        endpoint = format!("{}?percentage={}", endpoint, percentage);
    }
    let response = create_trading_request::<()>(alpaca, Method::DELETE, &endpoint, None).await?;
    if !response.status().is_success() {
        let text = response.text().await.unwrap_or_default();
        return Err(format!("Closing position failed: {}", text).into());
    }
    let order: Order = response.json().await?;
    Ok(order)
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ClosedPositions {
    pub symbol: String,
    pub status: i128,
    pub body: Order,
}
pub async fn close_all_positions(
    alpaca: &Alpaca,
    cancel_orders: bool,
) -> Result<Vec<ClosedPositions>, Box<dyn std::error::Error>> {
    let response =
        create_trading_request::<()>(alpaca, Method::DELETE, "/v2/positions", None).await?;
    if !response.status().is_success() {
        let text = response.text().await.unwrap_or_default();
        return Err(format!("Closing all positions failed: {}", text).into());
    }
    Ok(response.json().await?)
}

pub async fn exercise_options_position(
    alpaca: &Alpaca,
    symbol: String,
) -> Result<(), Box<dyn std::error::Error>> {
    let endpoint = format!("/v2/positions/{}/exercise", symbol);
    let response = create_trading_request::<()>(alpaca, Method::POST, &endpoint, None).await?;
    if !response.status().is_success() {
        let text = response.text().await.unwrap_or_default();
        return Err(format!("Exercise options position failed: {}", text).into());
    }
    Ok(())
}

#[tokio::test]
async fn test_position() {
    let alpaca = Alpaca::from_env(TradingType::Paper).unwrap();
    let order = match create_order(
        &alpaca,
        OrderRequest::builder()
            .symbol("GOOG")
            .qty("2")
            .side("buy")
            .order_type("market")
            .time_in_force("day")
            .build(),
    )
    .await
    {
        Ok(order) => order,
        Err(e) => panic!("Failed to create order for positions{e}"),
    };

    tokio::time::sleep(std::time::Duration::from_secs(5)).await;

    let position = match get_positions(&alpaca).await {
        Ok(positions) => match positions.into_iter().find(|p| p.symbol == "GOOG") {
            Some(p) => p,
            None => panic!("GOOG position not found"),
        },
        Err(e) => panic!("Failed to get positions: {}", e),
    };

    assert_eq!(position.qty, "2");

    let single_position = match get_single_position(&alpaca, "GOOG".to_string()).await {
        Ok(p) => p,
        Err(e) => panic!("Failed to get single position: {e}"),
    };

    assert_eq!(position.qty, single_position.qty);
    assert_eq!(position.side, single_position.side);
    assert_eq!(position.symbol, single_position.symbol);
    assert_eq!(position.exchange, single_position.exchange);

    let close_position = match close_position(
        &alpaca,
        ClosePositionParams::builder()
            .symbol("GOOG".to_string())
            .qty(1.0)
            .build(),
    )
    .await
    {
        Ok(order) => order,
        Err(e) => panic!("Failed to close position: {}", e),
    };

    assert_eq!(close_position.symbol, "GOOG");
    tokio::time::sleep(std::time::Duration::from_secs(5)).await;
    let close_all_positions = match close_all_positions(&alpaca, true).await {
        Ok(positions) => positions,
        Err(e) => panic!("Failed to close all positions: {e}"),
    };
    tokio::time::sleep(std::time::Duration::from_secs(5)).await;
    match get_positions(&alpaca).await {
        Ok(positions) => {
            assert_eq!(positions.len(), 0);
        }
        Err(e) => panic!("Failed to get positions: {e}"),
    };
}