Skip to main content
This guide covers how to query and manage your perp and spot positions, calculate margin requirements, and assess account health.

Querying Positions

Get All Positions

Retrieve all open positions for an account:
use drift_rs::Wallet;

let subaccount = wallet.default_sub_account();
let (spot_positions, perp_positions) = drift_client
    .all_positions(&subaccount)
    .await?;

println!("Open spot positions: {}", spot_positions.len());
println!("Open perp positions: {}", perp_positions.len());

// Iterate through perp positions
for position in perp_positions {
    println!(
        "Market {}: {} @ {} size",
        position.market_index,
        if position.base_asset_amount > 0 { "LONG" } else { "SHORT" },
        position.base_asset_amount.abs()
    );
}

Get Specific Perp Position

let market_index = 0;  // SOL-PERP

if let Some(position) = drift_client
    .perp_position(&subaccount, market_index)
    .await?
{
    println!("Position market: {}", position.market_index);
    println!("Base asset amount: {}", position.base_asset_amount);
    println!("Quote asset amount: {}", position.quote_asset_amount);
    println!("Last cumulative funding rate: {}", position.last_cumulative_funding_rate);
} else {
    println!("No position in market {}", market_index);
}

Get Specific Spot Position

let market_index = 1;  // SOL spot

if let Some(position) = drift_client
    .spot_position(&subaccount, market_index)
    .await?
{
    println!("Token amount: {}", position.scaled_balance);
    println!("Cumulative deposits: {}", position.cumulative_deposits);
} else {
    println!("No spot position in market {}", market_index);
}

Get Unsettled Positions

Find positions with unsettled PnL:
let unsettled = drift_client
    .unsettled_positions(&subaccount)
    .await?;

println!("Unsettled positions: {}", unsettled.len());

for position in unsettled {
    println!(
        "Market {}: unsettled quote = {}",
        position.market_index,
        position.quote_asset_amount
    );
}

Calculating Position Value and PnL

Get Unrealized PnL

use drift_rs::MarketId;

let subaccount = wallet.default_sub_account();
let user_account = drift_client.get_user_account(&subaccount).await?;

// Get oracle price for the market
let market_index = 0;
let oracle_data = drift_client
    .get_oracle_price_data_and_slot(MarketId::perp(market_index))
    .await?;

// Get position
if let Ok(position) = user_account.get_perp_position(market_index) {
    // Calculate unrealized PnL
    let upnl = position.get_unrealized_pnl(oracle_data.data.price)?;
    
    println!("Position value: ${:.2}", 
        position.quote_asset_amount as f64 / 1_000_000.0);
    println!("Unrealized PnL: ${:.2}", 
        upnl as f64 / 1_000_000.0);
}

Calculate Position Size in USD

use drift_rs::math::constants::BASE_PRECISION_I64;

let market_index = 0;
let user_account = drift_client.get_user_account(&subaccount).await?;

if let Ok(position) = user_account.get_perp_position(market_index) {
    let oracle_price = drift_client
        .oracle_price(MarketId::perp(market_index))
        .await? as i64;
    
    // Calculate notional value
    let size_in_base = position.base_asset_amount as f64 
        / BASE_PRECISION_I64 as f64;
    let price = oracle_price as f64 / 1_000_000.0;
    let notional_value = size_in_base.abs() * price;
    
    println!("Position size: {} SOL", size_in_base);
    println!("Notional value: ${:.2}", notional_value);
}

Querying Open Orders

Get All Open Orders

let orders = drift_client.all_orders(&subaccount).await?;

println!("Open orders: {}", orders.len());

for order in orders {
    println!(
        "Order {}: {} {} @ {} on market {}",
        order.order_id,
        if order.direction == drift_rs::types::PositionDirection::Long {
            "BUY"
        } else {
            "SELL"
        },
        order.base_asset_amount,
        order.price,
        order.market_index
    );
}

Get Order by ID

let order_id = 42;

if let Some(order) = drift_client
    .get_order_by_id(&subaccount, order_id)
    .await?
{
    println!("Found order: {:?}", order);
} else {
    println!("Order {} not found", order_id);
}

Get Order by User Order ID

let user_order_id = 1;  // Your self-assigned ID (0-255)

if let Some(order) = drift_client
    .get_order_by_user_id(&subaccount, user_order_id)
    .await?
{
    println!("Order status: {:?}", order.status);
    println!("Filled: {} / {}", 
        order.base_asset_amount_filled,
        order.base_asset_amount
    );
}

Calculating Margin and Leverage

Get Account Equity

use drift_rs::math::margin::calculate_margin_requirement_and_total_collateral;

let subaccount = wallet.default_sub_account();
let user_account = drift_client.get_user_account(&subaccount).await?;

// You'll need market data for margin calculations
let spot_market_0 = drift_client.get_spot_market_account(0).await?;
let perp_market_0 = drift_client.get_perp_market_account(0).await?;

// Get oracle prices
let spot_oracle_0 = drift_client
    .get_oracle_price_data_and_slot(MarketId::spot(0))
    .await?;
let perp_oracle_0 = drift_client
    .get_oracle_price_data_and_slot(MarketId::perp(0))
    .await?;

// Calculate total collateral (simplified - you'd need all markets)
let total_collateral = user_account.spot_positions
    .iter()
    .filter(|p| !p.is_available())
    .fold(0_i128, |acc, pos| {
        // Calculate collateral value for each position
        acc + (pos.scaled_balance as i128)
    });

println!("Total collateral: ${:.2}", 
    total_collateral as f64 / 1_000_000.0);
Drift uses a sophisticated margin system with initial and maintenance margin requirements. For production use, consider using the margin calculation functions from the drift_rs::math::margin module.

Check Account Health

// Get current account data
let user_account = drift_client.get_user_account(&subaccount).await?;

// Check if account has positions
let has_open_positions = user_account.perp_positions
    .iter()
    .any(|p| p.is_open_position());

if has_open_positions {
    println!("Account has open positions");
    
    // Calculate total position value
    let mut total_position_value = 0_i64;
    for position in user_account.perp_positions.iter() {
        if position.is_open_position() {
            let oracle_price = drift_client
                .oracle_price(MarketId::perp(position.market_index))
                .await?;
            
            let position_value = (position.base_asset_amount as i64)
                .saturating_mul(oracle_price) / 1_000_000_000;
            
            total_position_value = total_position_value
                .saturating_add(position_value.abs());
        }
    }
    
    println!("Total position value: ${:.2}", 
        total_position_value as f64 / 1_000_000.0);
}

Risk Management

Set Stop Loss and Take Profit Orders

use drift_rs::types::{
    OrderParams, OrderType, PositionDirection, 
    MarketType, OrderTriggerCondition
};
use drift_rs::math::constants::PRICE_PRECISION_U64;

// Current position: LONG 5 SOL at $120
let entry_price = 120 * PRICE_PRECISION_U64;
let stop_loss_price = 115 * PRICE_PRECISION_U64;  // -4.2% loss
let take_profit_price = 130 * PRICE_PRECISION_U64;  // +8.3% profit

let orders = vec![
    // Stop loss (trigger market sell)
    OrderParams {
        order_type: OrderType::TriggerMarket,
        market_type: MarketType::Perp,
        market_index: 0,
        direction: PositionDirection::Short,  // Close long
        base_asset_amount: 5 * drift_rs::math::constants::BASE_PRECISION_U64,
        trigger_price: Some(stop_loss_price),
        trigger_condition: OrderTriggerCondition::Below,
        reduce_only: true,
        ..Default::default()
    },
    // Take profit (trigger limit sell)
    OrderParams {
        order_type: OrderType::TriggerLimit,
        market_type: MarketType::Perp,
        market_index: 0,
        direction: PositionDirection::Short,  // Close long
        base_asset_amount: 5 * drift_rs::math::constants::BASE_PRECISION_U64,
        price: take_profit_price,
        trigger_price: Some(take_profit_price),
        trigger_condition: OrderTriggerCondition::Above,
        reduce_only: true,
        ..Default::default()
    },
];

let subaccount_data = drift_client.get_user_account(&subaccount).await?;
let tx = drift_rs::TransactionBuilder::new(
    drift_client.program_data(),
    subaccount,
    std::borrow::Cow::Borrowed(&subaccount_data),
    false,
)
.place_orders(orders)
.build();

let sig = drift_client.sign_and_send(tx).await?;
println!("Stop loss and take profit orders placed: {}", sig);

Monitor Position Risk

use std::time::Duration;
use tokio::time::interval;

async fn monitor_position_risk(
    drift: &drift_rs::DriftClient,
    subaccount: &solana_sdk::pubkey::Pubkey,
    market_index: u16,
    max_loss_usd: f64,
) -> Result<(), Box<dyn std::error::Error>> {
    let mut check_interval = interval(Duration::from_secs(5));
    
    loop {
        check_interval.tick().await;
        
        let user_account = drift.get_user_account(subaccount).await?;
        let position = user_account.get_perp_position(market_index)?;
        
        let oracle_data = drift
            .get_oracle_price_data_and_slot(
                drift_rs::MarketId::perp(market_index)
            )
            .await?;
        
        let upnl = position.get_unrealized_pnl(oracle_data.data.price)?;
        let upnl_usd = upnl as f64 / 1_000_000.0;
        
        println!("Current PnL: ${:.2}", upnl_usd);
        
        if upnl_usd < -max_loss_usd {
            println!("⚠️  Loss limit exceeded! Current loss: ${:.2}", 
                upnl_usd.abs());
            // Close position or take other action
            break;
        }
    }
    
    Ok(())
}

Complete Example: Position Dashboard

use drift_rs::{
    DriftClient, Wallet, MarketId,
    math::constants::BASE_PRECISION_I64,
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    dotenv::dotenv().ok();
    env_logger::init();
    
    let wallet: Wallet = drift_rs::utils::load_keypair_multi_format(
        &std::env::var("PRIVATE_KEY")?,
    )?
    .into();
    
    let drift = DriftClient::new(
        drift_rs::Context::MainNet,
        drift_rs::RpcClient::new(
            std::env::var("RPC_URL")
                .unwrap_or_else(|_| "https://api.mainnet-beta.solana.com".to_string())
        ),
        wallet.clone(),
    )
    .await?;
    
    let subaccount = wallet.default_sub_account();
    
    println!("\n📈 Position Dashboard");
    println!("=".repeat(50));
    
    // Get all positions
    let (spot_positions, perp_positions) = drift.all_positions(&subaccount).await?;
    
    println!("\n🔹 Perp Positions: {}", perp_positions.len());
    for position in perp_positions {
        let market_index = position.market_index;
        let oracle_price = drift.oracle_price(MarketId::perp(market_index)).await?;
        
        let size = position.base_asset_amount as f64 / BASE_PRECISION_I64 as f64;
        let price = oracle_price as f64 / 1_000_000.0;
        let notional = size.abs() * price;
        
        let upnl = position.get_unrealized_pnl(oracle_price)?;
        let upnl_usd = upnl as f64 / 1_000_000.0;
        
        println!("\n  Market {}: {}", 
            market_index,
            if size > 0.0 { "LONG" } else { "SHORT" }
        );
        println!("  Size: {:.4}", size.abs());
        println!("  Entry value: ${:.2}", 
            position.quote_asset_amount as f64 / 1_000_000.0);
        println!("  Current value: ${:.2}", notional);
        println!("  Unrealized PnL: ${:.2}", upnl_usd);
    }
    
    // Get open orders
    let orders = drift.all_orders(&subaccount).await?;
    println!("\n📝 Open Orders: {}", orders.len());
    for order in orders.iter().take(5) {
        println!("  Order {}: {} {} @ ${:.2}",
            order.order_id,
            if order.direction == drift_rs::types::PositionDirection::Long {
                "BUY"
            } else {
                "SELL"
            },
            order.base_asset_amount as f64 / BASE_PRECISION_I64 as f64,
            order.price as f64 / 1_000_000.0
        );
    }
    
    Ok(())
}

Best Practices

  1. Always use reduce_only for stop loss and take profit orders to avoid flipping positions
  2. Monitor unrealized PnL regularly to assess position health
  3. Calculate margin requirements before opening large positions
  4. Use trigger orders for automated risk management
  5. Check account health before adding to positions
Position calculations require up-to-date oracle prices. Always fetch fresh oracle data before making position management decisions.

Next Steps

Build docs developers (and LLMs) love