rust-okx 0.6.4

Async Rust client for the OKX v5 REST API
Documentation
use std::env;

use rust_okx::api::trade::PlaceOrderRequest;
use rust_okx::model::{OrderSide, OrderType, TradeMode};
use rust_okx::{Credentials, OkxClient, OkxRegion};

const CONFIRM_VALUE: &str = "I_UNDERSTAND";

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let _ = dotenvy::dotenv();

    if env::var("OKX_LIVE_TRADING_CONFIRM").as_deref() != Ok(CONFIRM_VALUE) {
        println!("set OKX_LIVE_TRADING_CONFIRM={CONFIRM_VALUE} to place an order");
        println!("set OKX_DEMO_TRADING=1 to send it to OKX demo trading");
        return Ok(());
    }

    let client = authenticated_client()?;
    let request = PlaceOrderRequest::new(
        env::var("OKX_TRADE_INST_ID")?,
        parse_trade_mode(&env::var("OKX_TRADE_MODE").unwrap_or_else(|_| "cash".to_owned()))?,
        parse_side(&env::var("OKX_TRADE_SIDE")?)?,
        parse_order_type(&env::var("OKX_TRADE_ORDER_TYPE").unwrap_or_else(|_| "limit".to_owned()))?,
        env::var("OKX_TRADE_SIZE")?,
    )
    .price(env::var("OKX_TRADE_PRICE")?)
    .client_order_id(
        env::var("OKX_TRADE_CLIENT_ORDER_ID").unwrap_or_else(|_| "rust-okx-example".to_owned()),
    );

    let placed = client.trade().place_order(&request).await?;
    for row in placed {
        println!(
            "ordId={} clOrdId={} sCode={} sMsg={}",
            row.ord_id, row.cl_ord_id, row.s_code, row.s_msg
        );
    }

    Ok(())
}

fn authenticated_client() -> Result<OkxClient, Box<dyn std::error::Error>> {
    Ok(OkxClient::builder()
        .region(example_region()?)
        .credentials(live_credentials()?)
        .demo_trading(env_flag("OKX_DEMO_TRADING"))
        .build())
}

fn live_credentials() -> Result<Credentials, Box<dyn std::error::Error>> {
    Ok(Credentials::new(
        env::var("OKX_API_KEY")?,
        env::var("OKX_API_SECRET")?,
        env::var("OKX_PASSPHRASE")?,
    ))
}

fn example_region() -> Result<OkxRegion, Box<dyn std::error::Error>> {
    match env::var("OKX_REGION")
        .unwrap_or_else(|_| "global".to_owned())
        .to_ascii_lowercase()
        .as_str()
    {
        "global" => Ok(OkxRegion::Global),
        "us" | "au" => Ok(OkxRegion::Us),
        "eea" | "eu" => Ok(OkxRegion::Eea),
        other => Err(format!("OKX_REGION must be global, us, au, eea, or eu; got {other}").into()),
    }
}

fn env_flag(name: &str) -> bool {
    matches!(
        env::var(name).as_deref(),
        Ok("1") | Ok("true") | Ok("TRUE") | Ok("yes") | Ok("YES")
    )
}

fn parse_side(side: &str) -> Result<OrderSide, Box<dyn std::error::Error>> {
    match side {
        "buy" => Ok(OrderSide::Buy),
        "sell" => Ok(OrderSide::Sell),
        other => Err(format!("OKX_TRADE_SIDE must be buy or sell, got {other}").into()),
    }
}

fn parse_trade_mode(mode: &str) -> Result<TradeMode, Box<dyn std::error::Error>> {
    match mode {
        "cash" => Ok(TradeMode::Cash),
        "cross" => Ok(TradeMode::Cross),
        "isolated" => Ok(TradeMode::Isolated),
        "spot_isolated" => Ok(TradeMode::SpotIsolated),
        other => Err(format!(
            "OKX_TRADE_MODE must be cash, cross, isolated, or spot_isolated; got {other}"
        )
        .into()),
    }
}

fn parse_order_type(order_type: &str) -> Result<OrderType, Box<dyn std::error::Error>> {
    match order_type {
        "limit" => Ok(OrderType::Limit),
        "market" => Ok(OrderType::Market),
        "post_only" => Ok(OrderType::PostOnly),
        "fok" => Ok(OrderType::Fok),
        "ioc" => Ok(OrderType::Ioc),
        "optimal_limit_ioc" => Ok(OrderType::OptimalLimitIoc),
        other => Err(format!("unsupported OKX_TRADE_ORDER_TYPE: {other}").into()),
    }
}