mango-v4/programs/mango-v4/src/state/perp_market.rs

174 lines
5.2 KiB
Rust

use std::mem::size_of;
use anchor_lang::prelude::*;
use fixed::types::I80F48;
use static_assertions::const_assert_eq;
use crate::state::orderbook::order_type::Side;
use crate::state::{TokenIndex, DAY};
use crate::util::checked_math as cm;
use super::{Book, OracleConfig};
pub type PerpMarketIndex = u16;
#[account(zero_copy)]
pub struct PerpMarket {
pub name: [u8; 16],
pub group: Pubkey,
pub oracle: Pubkey,
pub oracle_config: OracleConfig,
pub bids: Pubkey,
pub asks: Pubkey,
pub event_queue: Pubkey,
/// Number of quote native that reresents min tick
pub quote_lot_size: i64,
/// Represents number of base native quantity
/// e.g. if base decimals for underlying asset are 6, base lot size is 100, and base position is 10000, then
/// UI position is 1
pub base_lot_size: i64,
// These weights apply to the base asset, the quote token is always assumed to be
// the health-reference token and have 1 for price and weights
pub maint_asset_weight: I80F48,
pub init_asset_weight: I80F48,
pub maint_liab_weight: I80F48,
pub init_liab_weight: I80F48,
// TODO docs
pub liquidation_fee: I80F48,
pub maker_fee: I80F48,
pub taker_fee: I80F48,
pub min_funding: I80F48,
pub max_funding: I80F48,
pub impact_quantity: i64,
pub long_funding: I80F48,
pub short_funding: I80F48,
pub funding_last_updated: i64,
///
pub open_interest: i64,
/// Total number of orders seen
pub seq_num: u64,
/// Fees accrued in native quote currency
pub fees_accrued: I80F48,
/// Liquidity mining metadata
/// pub liquidity_mining_info: LiquidityMiningInfo,
/// Token vault which holds mango tokens to be disbursed as liquidity incentives for this perp market
/// pub mngo_vault: Pubkey,
/// PDA bump
pub bump: u8,
pub base_token_decimals: u8,
/// Lookup indices
pub perp_market_index: PerpMarketIndex,
pub base_token_index: TokenIndex,
/// Cannot be chosen freely, must be the health-reference token, same for all PerpMarkets
pub quote_token_index: TokenIndex,
}
const_assert_eq!(
size_of::<PerpMarket>(),
16 + 32 * 2 + 16 + 32 * 3 + 8 * 2 + 16 * 11 + 8 * 2 + 8 * 2 + 16 + 8
);
const_assert_eq!(size_of::<PerpMarket>() % 8, 0);
impl PerpMarket {
pub fn name(&self) -> &str {
std::str::from_utf8(&self.name)
.unwrap()
.trim_matches(char::from(0))
}
pub fn gen_order_id(&mut self, side: Side, price: i64) -> i128 {
self.seq_num += 1;
let upper = (price as i128) << 64;
match side {
Side::Bid => upper | (!self.seq_num as i128),
Side::Ask => upper | (self.seq_num as i128),
}
}
/// Use current order book price and index price to update the instantaneous funding
pub fn update_funding(&mut self, book: &Book, oracle_price: I80F48, now_ts: u64) -> Result<()> {
let index_price = oracle_price;
// Get current book price & compare it to index price
let bid = book.get_impact_price(Side::Bid, self.impact_quantity, now_ts);
let ask = book.get_impact_price(Side::Ask, self.impact_quantity, now_ts);
let diff_price = match (bid, ask) {
(Some(bid), Some(ask)) => {
// calculate mid-market rate
let mid_price = bid.checked_add(ask).unwrap() / 2;
let book_price = self.lot_to_native_price(mid_price);
let diff = cm!(book_price / index_price - I80F48::ONE);
diff.clamp(self.min_funding, self.max_funding)
}
(Some(_bid), None) => self.max_funding,
(None, Some(_ask)) => self.min_funding,
(None, None) => I80F48::ZERO,
};
let diff_ts = I80F48::from_num(now_ts - self.funding_last_updated as u64);
let time_factor = cm!(diff_ts / DAY);
let base_lot_size = I80F48::from_num(self.base_lot_size);
let funding_delta = cm!(index_price * diff_price * base_lot_size * time_factor);
self.long_funding += funding_delta;
self.short_funding += funding_delta;
self.funding_last_updated = now_ts as i64;
Ok(())
}
/// Convert from the price stored on the book to the price used in value calculations
pub fn lot_to_native_price(&self, price: i64) -> I80F48 {
I80F48::from_num(price)
.checked_mul(I80F48::from_num(self.quote_lot_size))
.unwrap()
.checked_div(I80F48::from_num(self.base_lot_size))
.unwrap()
}
pub fn native_price_to_lot(&self, price: I80F48) -> i64 {
price
.checked_mul(I80F48::from_num(self.base_lot_size))
.unwrap()
.checked_div(I80F48::from_num(self.quote_lot_size))
.unwrap()
.to_num()
}
/// Is `native_price` an acceptable order for the `side` of this market, given `oracle_price`?
pub fn inside_price_limit(
&self,
side: Side,
native_price: I80F48,
oracle_price: I80F48,
) -> bool {
match side {
Side::Bid => native_price <= cm!(self.maint_liab_weight * oracle_price),
Side::Ask => native_price >= cm!(self.maint_asset_weight * oracle_price),
}
}
}