Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add perp asset contexts #38

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion src/info/info_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
prelude::*,
req::HttpClient,
ws::{Subscription, WsManager},
BaseUrl, Error, Message,
BaseUrl, Error, Message, PerpetualsAssetContextsResponse,
};

use ethers::types::H160;
Expand All @@ -29,6 +29,8 @@ pub struct CandleSnapshotRequest {
#[serde(tag = "type")]
#[serde(rename_all = "camelCase")]
pub enum InfoRequest {
#[serde(rename = "metaAndAssetCtxs")]
PerpetualsMetaAndAssetCtxs,
#[serde(rename = "clearinghouseState")]
UserState {
user: H160,
Expand Down Expand Up @@ -115,6 +117,15 @@ impl InfoClient {
.await
}

/// Retrieve perpetuals asset contexts (includes mark price, current funding, open interest, etc).
pub async fn perpetuals_meta_and_asset_ctxs(&self) -> Result<PerpetualsAssetContextsResponse> {
let input = InfoRequest::PerpetualsMetaAndAssetCtxs;
let data = serde_json::to_string(&input).map_err(|e| Error::JsonParse(e.to_string()))?;

let return_data = self.http_client.post("/info", data).await?;
serde_json::from_str(&return_data).map_err(|e| Error::JsonParse(e.to_string()))
}

pub async fn open_orders(&self, address: H160) -> Result<Vec<OpenOrdersResponse>> {
let input = InfoRequest::OpenOrders { user: address };
let data = serde_json::to_string(&input).map_err(|e| Error::JsonParse(e.to_string()))?;
Expand Down Expand Up @@ -225,3 +236,44 @@ impl InfoClient {
serde_json::from_str(&return_data).map_err(|e| Error::JsonParse(e.to_string()))
}
}

#[cfg(test)]
mod tests {
use std::time::{SystemTime, UNIX_EPOCH};

use super::*;

#[tokio::test]
async fn test_perpetuals_meta_and_asset_ctxs() {
let info_client = InfoClient::new(None, None).await.unwrap();
let result = info_client.perpetuals_meta_and_asset_ctxs().await.unwrap();
dbg!(result.into_universe_and_asset_contexts());
}

#[tokio::test]
async fn test_all_mids() {
let info_client = InfoClient::new(None, None).await.unwrap();
let result = info_client.all_mids().await;
dbg!(result);
}

#[tokio::test]
async fn test_l2_snapshot() {
let info_client = InfoClient::new(None, None).await.unwrap();
let result = info_client.l2_snapshot("ETH".to_string()).await;
dbg!(result);
}

#[tokio::test]
async fn test_candles_snapshot() {
let info_client = InfoClient::new(None, None).await.unwrap();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let result = info_client
.candles_snapshot("ETH".to_string(), "1m".to_string(), now, now)
.await;
dbg!(result);
}
}
108 changes: 106 additions & 2 deletions src/info/response_structs.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,109 @@
use crate::info::{AssetPosition, Level, MarginSummary};
use serde::Deserialize;
use crate::{
info::{AssetPosition, Level, MarginSummary},
PerpetualAssetContext, Universe, UniverseItem,
};
use serde::{Deserialize, Deserializer};
use serde_json::Value;

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PerpetualsAssetContextsResponse([PerpetualsAssetContexts; 2]);

impl PerpetualsAssetContextsResponse {
/// Consumes the response and returns universe.
pub fn into_universe(self) -> Universe {
match self.0[0].clone() {
PerpetualsAssetContexts::Universe(u) => u,
_ => panic!("Expected universe."),
}
}

/// Consumes the response and returns asset contexts.
pub fn into_asset_contexts(self) -> Vec<PerpetualAssetContext> {
match self.0[1].clone() {
PerpetualsAssetContexts::AssetContexts(ac) => ac,
_ => panic!("Expected asset contexts."),
}
}

/// Consumes the response and returns an array of tuples containing an universe item and asset contexts.
/// Each element of universe should match each element of asset contexts.
pub fn into_universe_and_asset_contexts(self) -> Vec<(UniverseItem, PerpetualAssetContext)> {
let universe = match self.0[0].clone() {
PerpetualsAssetContexts::Universe(u) => u,
_ => panic!("Expected universe."),
};
let asset_contexts = match self.0[1].clone() {
PerpetualsAssetContexts::AssetContexts(ac) => ac,
_ => panic!("Expected asset contexts."),
};
universe
.universe
.into_iter()
.zip(asset_contexts.into_iter())
.collect()
}

/// Returns a reference to the universe.
pub fn universe(&self) -> &Universe {
match &self.0[0] {
PerpetualsAssetContexts::Universe(u) => u,
_ => panic!("Expected universe."),
}
}

/// Returns a reference to the asset contexts.
pub fn asset_contexts(&self) -> &Vec<PerpetualAssetContext> {
match &self.0[1] {
PerpetualsAssetContexts::AssetContexts(ac) => ac,
_ => panic!("Expected asset contexts."),
}
}

/// Returns a reference to an array of tuples containing an universe item and asset contexts.
/// Each element of universe should match each element of asset contexts.
pub fn universe_and_asset_contexts(&self) -> Vec<(&UniverseItem, &PerpetualAssetContext)> {
let universe = match &self.0[0] {
PerpetualsAssetContexts::Universe(u) => u,
_ => panic!("Expected universe."),
};
let asset_contexts = match &self.0[1] {
PerpetualsAssetContexts::AssetContexts(ac) => ac,
_ => panic!("Expected asset contexts."),
};
universe
.universe
.iter()
.zip(asset_contexts.iter())
.collect()
}
}

#[derive(Debug, Clone)]
pub enum PerpetualsAssetContexts {
Universe(Universe),
AssetContexts(Vec<PerpetualAssetContext>),
}

impl<'de> Deserialize<'de> for PerpetualsAssetContexts {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value: Value = Deserialize::deserialize(deserializer)?;
if value.is_object() {
Ok(PerpetualsAssetContexts::Universe(
serde_json::from_value(value).map_err(serde::de::Error::custom)?,
))
} else if value.is_array() {
Ok(PerpetualsAssetContexts::AssetContexts(
serde_json::from_value(value).map_err(serde::de::Error::custom)?,
))
} else {
Err(serde::de::Error::custom("Expected object or array"))
}
}
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
Expand Down
32 changes: 32 additions & 0 deletions src/info/sub_structs.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
use serde::Deserialize;

#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Universe {
pub universe: Vec<UniverseItem>,
}

#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct UniverseItem {
pub name: String,
pub sz_decimals: u32,
pub max_leverage: u32,
pub only_isolated: bool,
}

#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct PerpetualAssetContext {
pub day_ntl_vlm: String,
pub funding: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub impact_pxs: Option<Vec<String>>,
pub mark_px: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub mid_px: Option<String>,
pub open_interest: String,
pub oracle_px: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub premium: Option<String>,
pub prev_day_px: String,
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Leverage {
Expand Down