use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::error::OpenPxError;
use crate::models::{
CreateOrderRequest, Fill, Market, MarketLineage, MarketTrade, Order, Orderbook,
OrderbookImpact, OrderbookMicrostructure, OrderbookStats, Position,
};
use super::config::FetchMarketsParams;
use super::manifest::ExchangeManifest;
#[allow(async_fn_in_trait)]
pub trait Exchange: Send + Sync {
fn id(&self) -> &'static str;
fn name(&self) -> &'static str;
async fn fetch_markets(
&self,
params: &FetchMarketsParams,
) -> Result<(Vec<Market>, Option<String>), OpenPxError>;
async fn create_order(&self, req: CreateOrderRequest) -> Result<Order, OpenPxError>;
async fn cancel_order(&self, order_id: &str) -> Result<Order, OpenPxError>;
async fn fetch_order(&self, order_id: &str) -> Result<Order, OpenPxError>;
async fn fetch_open_orders(&self, asset_id: Option<&str>) -> Result<Vec<Order>, OpenPxError>;
async fn fetch_positions(
&self,
market_ticker: Option<&str>,
) -> Result<Vec<Position>, OpenPxError>;
async fn fetch_balance(&self) -> Result<HashMap<String, f64>, OpenPxError>;
async fn refresh_balance(&self) -> Result<(), OpenPxError> {
Ok(())
}
async fn fetch_orderbook(&self, asset_id: &str) -> Result<Orderbook, OpenPxError> {
let _ = asset_id;
Err(OpenPxError::Exchange(
crate::error::ExchangeError::NotSupported("fetch_orderbook".into()),
))
}
async fn fetch_trades(
&self,
req: TradesRequest,
) -> Result<(Vec<MarketTrade>, Option<String>), OpenPxError> {
let _ = req;
Err(OpenPxError::Exchange(
crate::error::ExchangeError::NotSupported("fetch_trades".into()),
))
}
async fn fetch_fills(
&self,
market_ticker: Option<&str>,
limit: Option<usize>,
) -> Result<Vec<Fill>, OpenPxError> {
let _ = (market_ticker, limit);
Err(OpenPxError::Exchange(
crate::error::ExchangeError::NotSupported("fetch_fills".into()),
))
}
async fn fetch_server_time(&self) -> Result<DateTime<Utc>, OpenPxError> {
Err(OpenPxError::Exchange(
crate::error::ExchangeError::NotSupported("fetch_server_time".into()),
))
}
async fn fetch_market_lineage(
&self,
market_ticker: &str,
) -> Result<MarketLineage, OpenPxError> {
let _ = market_ticker;
Err(OpenPxError::Exchange(
crate::error::ExchangeError::NotSupported("fetch_market_lineage".into()),
))
}
async fn fetch_orderbooks_batch(
&self,
asset_ids: Vec<String>,
) -> Result<Vec<Orderbook>, OpenPxError> {
let _ = asset_ids;
Err(OpenPxError::Exchange(
crate::error::ExchangeError::NotSupported("fetch_orderbooks_batch".into()),
))
}
async fn fetch_orderbook_stats(&self, asset_id: &str) -> Result<OrderbookStats, OpenPxError> {
let book = self.fetch_orderbook(asset_id).await?;
Ok(crate::models::orderbook_stats(&book))
}
async fn fetch_orderbook_impact(
&self,
asset_id: &str,
size: f64,
) -> Result<OrderbookImpact, OpenPxError> {
if size <= 0.0 {
return Err(OpenPxError::InvalidInput("size must be > 0".into()));
}
let book = self.fetch_orderbook(asset_id).await?;
Ok(crate::models::orderbook_impact(&book, size))
}
async fn fetch_orderbook_microstructure(
&self,
asset_id: &str,
) -> Result<OrderbookMicrostructure, OpenPxError> {
let book = self.fetch_orderbook(asset_id).await?;
Ok(crate::models::orderbook_microstructure(&book))
}
async fn cancel_all_orders(&self, asset_id: Option<&str>) -> Result<Vec<Order>, OpenPxError> {
let _ = asset_id;
Err(OpenPxError::Exchange(
crate::error::ExchangeError::NotSupported("cancel_all_orders".into()),
))
}
async fn create_orders_batch(
&self,
reqs: Vec<CreateOrderRequest>,
) -> Result<Vec<Order>, OpenPxError> {
let _ = reqs;
Err(OpenPxError::Exchange(
crate::error::ExchangeError::NotSupported("create_orders_batch".into()),
))
}
fn describe(&self) -> ExchangeInfo {
ExchangeInfo {
id: self.id(),
name: self.name(),
has_fetch_markets: true,
has_create_order: true,
has_cancel_order: true,
has_fetch_positions: true,
has_fetch_balance: true,
has_fetch_orderbook: false,
has_fetch_trades: false,
has_fetch_fills: false,
has_fetch_server_time: false,
has_approvals: false,
has_refresh_balance: false,
has_websocket: false,
has_fetch_market_lineage: false,
has_fetch_orderbooks_batch: false,
has_cancel_all_orders: false,
has_create_orders_batch: false,
}
}
fn manifest(&self) -> &'static ExchangeManifest;
}
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ExchangeInfo {
pub id: &'static str,
pub name: &'static str,
pub has_fetch_markets: bool,
pub has_create_order: bool,
pub has_cancel_order: bool,
pub has_fetch_positions: bool,
pub has_fetch_balance: bool,
pub has_fetch_orderbook: bool,
pub has_fetch_trades: bool,
pub has_fetch_fills: bool,
pub has_fetch_server_time: bool,
pub has_approvals: bool,
pub has_refresh_balance: bool,
pub has_websocket: bool,
pub has_fetch_market_lineage: bool,
pub has_fetch_orderbooks_batch: bool,
pub has_cancel_all_orders: bool,
pub has_create_orders_batch: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct TradesRequest {
pub asset_id: String,
pub start_ts: Option<i64>,
pub end_ts: Option<i64>,
pub limit: Option<usize>,
pub cursor: Option<String>,
}