from pydantic import (
BaseModel,
Field,
conint,
field_validator,
ValidationError,
model_validator,
)
from enum import Enum
from typing import Dict, Optional, List, Union
import time
import re
[docs]
class PairInfoFeed(BaseModel):
max_deviation_percentage: float = Field(..., alias="maxDeviationP")
feed_id: str = Field(..., alias="feedId")
[docs]
@field_validator("max_deviation_percentage", mode="before")
def convert_max_deviation(cls, v):
return v / 10**10
[docs]
class PairInfo(BaseModel):
from_: str = Field(..., alias="from")
to: str
feed: PairInfoFeed
backup_feed: PairInfoFeed = Field(..., alias="backupFeed")
constant_spread_bps: float = Field(..., alias="spreadP")
price_impact_parameter: float = Field(..., alias="priceImpactMultiplier")
skew_impact_parameter: float = Field(..., alias="skewImpactMultiplier")
group_index: int = Field(..., alias="groupIndex")
fee_index: int = Field(..., alias="feeIndex")
group_open_interest_percentage: float = Field(
..., alias="groupOpenInterestPecentage"
)
max_wallet_oi: float = Field(..., alias="maxWalletOI")
[docs]
@field_validator("price_impact_parameter", "skew_impact_parameter", mode="before")
def convert_to_float_10(cls, v):
return v / 10**10
[docs]
@field_validator("constant_spread_bps", mode="before")
def convert_to_float_10_bps(cls, v):
return v / 10**10 * 100
[docs]
class Config:
populate_by_name = True
[docs]
class OpenInterest(BaseModel):
long: Dict[str, float]
short: Dict[str, float]
[docs]
class OpenInterestLimits(BaseModel):
limits: Dict[str, float]
[docs]
class Utilization(BaseModel):
utilization: Dict[str, float]
[docs]
class Skew(BaseModel):
skew: Dict[str, float]
[docs]
class MarginFee(BaseModel):
hourly_base_fee_parameter: Dict[str, float]
hourly_margin_fee_long_bps: Dict[str, float]
hourly_margin_fee_short_bps: Dict[str, float]
[docs]
class MarginFeeSingle(BaseModel):
hourly_base_fee_parameter: float
hourly_margin_fee_long_bps: float
hourly_margin_fee_short_bps: float
[docs]
class DepthSingle(BaseModel):
above: float
below: float
[docs]
class PairInfoExtended(PairInfo):
asset_open_interest_limit: float
asset_open_interest: Dict[str, float]
asset_utilization: float
asset_skew: float
blended_utilization: float
blended_skew: float
margin_fee: MarginFeeSingle
one_percent_depth: DepthSingle
new_1k_long_opening_fee_bps: float # Opening fee for new $1,000 long position
new_1k_short_opening_fee_bps: float # Opening fee for new $1,000 short position
new_1k_long_opening_spread_bps: float # Opening spread for new $1,000 long position
new_1k_short_opening_spread_bps: (
float # Opening spread for new $1,000 short position
)
price_impact_spread_long_bps: float
price_impact_spread_short_bps: float
skew_impact_spread_long_bps: float
skew_impact_spread_short_bps: float
[docs]
class Config:
populate_by_name = True
[docs]
class PairSpread(BaseModel):
spread: Dict[str, float]
[docs]
class PriceFeedResponse(BaseModel):
id: str
price: Optional[Union[Dict[str, str], Dict[str, float]]] = None
ema_price: Optional[Union[Dict[str, str], Dict[str, float]]] = None
pair: Optional[str] = None
metadata: Optional[Union[Dict[str, str], Dict[str, float]]] = None
converted_price: float = 0.0
converted_ema_price: float = 0.0
[docs]
@model_validator(mode="before")
def convert_price(cls, values):
price_info = values.get("price")
if price_info:
values["converted_price"] = float(price_info["price"]) / 10 ** -int(
price_info["expo"]
)
ema_price_info = values.get("ema_price")
if ema_price_info:
values["converted_ema_price"] = float(ema_price_info["price"]) / 10 ** -int(
ema_price_info["expo"]
)
return values
[docs]
class PriceFeesUpdateBinary(BaseModel):
encoding: str
data: List[str]
[docs]
class PriceFeedUpdatesResponse(BaseModel):
binary: PriceFeesUpdateBinary
parsed: List[PriceFeedResponse]
[docs]
class Spread(BaseModel):
long: Optional[Dict[str, float]] = None
short: Optional[Dict[str, float]] = None
[docs]
@model_validator(mode="before")
def check_at_least_one(cls, values):
if not values.get("long") and not values.get("short"):
raise ValueError('At least one of "long" or "short" must be present.')
return values
[docs]
class Depth(BaseModel):
above: Optional[Dict[str, float]] = None
below: Optional[Dict[str, float]] = None
[docs]
@model_validator(mode="before")
def check_at_least_one(cls, values):
if not values.get("above") and not values.get("below"):
raise ValueError('At least one of "above" or "below" must be present.')
return values
[docs]
class Fee(BaseModel):
long: Optional[Dict[str, float]] = None
short: Optional[Dict[str, float]] = None
[docs]
@model_validator(mode="before")
def check_at_least_one(cls, values):
if not values.get("long") and not values.get("short"):
raise ValueError('At least one of "long" or "short" must be present.')
return values
[docs]
class TradeResponse(BaseModel):
trader: str
pair_index: int = Field(..., alias="pairIndex")
trade_index: int = Field(0, alias="index")
open_collateral: float = Field(None, alias="initialPosToken")
collateral_in_trade: float = Field(None, alias="positionSizeUSDC")
open_price: float = Field(0, alias="openPrice")
is_long: bool = Field(..., alias="buy")
leverage: float
tp: float
sl: float
timestamp: int
[docs]
@field_validator("trader")
def validate_eth_address(cls, v):
if not re.match(r"^0x[a-fA-F0-9]{40}$", v):
raise ValueError("Invalid Ethereum address")
return v
[docs]
@field_validator("open_price", "tp", "sl", "leverage", mode="before")
def convert_to_float_10(cls, v):
return v / 10**10
[docs]
@field_validator("open_collateral", "collateral_in_trade", mode="before")
def convert_to_float_6(cls, v):
return v / 10**6
[docs]
class Config:
populate_by_name = True
[docs]
class TradeInfo(BaseModel):
open_interest_usdc: float = Field(..., alias="openInterestUSDC")
tp_last_updated: float = Field(..., alias="tpLastUpdated")
sl_last_updated: float = Field(..., alias="slLastUpdated")
being_market_closed: bool = Field(..., alias="beingMarketClosed")
loss_protection_percentage: float = Field(..., alias="lossProtectionPercentage")
[docs]
@field_validator("open_interest_usdc", mode="before")
def convert_to_float_6(cls, v):
return v / 10**6
[docs]
class Config:
populate_by_name = True
[docs]
class TradeExtendedResponse(BaseModel):
trade: TradeResponse
additional_info: TradeInfo
margin_fee: float
liquidation_price: float
[docs]
@field_validator("margin_fee", mode="before")
def convert_to_float_6(cls, v):
return v / 10**6
[docs]
@field_validator("liquidation_price", mode="before")
def convert_to_float_10(cls, v):
return v / 10**10
[docs]
class Config:
populate_by_name = True
[docs]
class PendingLimitOrderResponse(BaseModel):
trader: str
pair_index: int = Field(..., alias="pairIndex")
trade_index: int = Field(0, alias="index")
open_collateral: float = Field(..., alias="positionSize")
buy: bool
leverage: int
tp: float
sl: float
price: float
slippage_percentage: float = Field(..., alias="slippageP")
block: int
[docs]
@field_validator("trader")
def validate_eth_address(cls, v):
if not re.match(r"^0x[a-fA-F0-9]{40}$", v):
raise ValueError("Invalid Ethereum address")
return v
[docs]
@field_validator(
"price", "tp", "sl", "leverage", "slippage_percentage", mode="before"
)
def convert_to_float_10(cls, v):
return v / 10**10
[docs]
@field_validator("open_collateral", mode="before")
def convert_to_float_6(cls, v):
return v / 10**6
[docs]
class Config:
populate_by_name = True
[docs]
class PendingLimitOrderExtendedResponse(PendingLimitOrderResponse):
liquidation_price: float
[docs]
@field_validator("liquidation_price", mode="before")
def convert_liq_to_float_10(cls, v):
return v / 10**10
[docs]
class MarginUpdateType(Enum):
DEPOSIT = 0
WITHDRAW = 1
[docs]
class LossProtectionInfo(BaseModel):
percentage: float
amount: float
[docs]
class SnapshotOpenInterest(BaseModel):
long: float
short: float
[docs]
class SnapshotGroup(BaseModel):
group_open_interest_limit: float
group_open_interest: SnapshotOpenInterest
group_utilization: float
group_skew: float
pairs: Dict[str, PairInfoExtended]
[docs]
class Snapshot(BaseModel):
groups: Dict[conint(ge=0), SnapshotGroup]