perp funding (#51)
Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
parent
f5c505f847
commit
be73106ce1
|
@ -21,6 +21,7 @@ pub use serum3_place_order::*;
|
|||
pub use serum3_register_market::*;
|
||||
pub use serum3_settle_funds::*;
|
||||
pub use set_stub_oracle::*;
|
||||
pub use update_funding::*;
|
||||
pub use update_index::*;
|
||||
pub use withdraw::*;
|
||||
|
||||
|
@ -47,5 +48,6 @@ mod serum3_place_order;
|
|||
mod serum3_register_market;
|
||||
mod serum3_settle_funds;
|
||||
mod set_stub_oracle;
|
||||
mod update_funding;
|
||||
mod update_index;
|
||||
mod withdraw;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use fixed::types::I80F48;
|
||||
use fixed_macro::types::I80F48;
|
||||
|
||||
use crate::error::MangoError;
|
||||
use crate::state::*;
|
||||
|
@ -56,6 +57,9 @@ pub fn perp_create_market(
|
|||
liquidation_fee: f32,
|
||||
maker_fee: f32,
|
||||
taker_fee: f32,
|
||||
max_funding: f32,
|
||||
min_funding: f32,
|
||||
impact_quantity: u64,
|
||||
) -> Result<()> {
|
||||
let mut perp_market = ctx.accounts.perp_market.load_init()?;
|
||||
*perp_market = PerpMarket {
|
||||
|
@ -74,14 +78,20 @@ pub fn perp_create_market(
|
|||
liquidation_fee: I80F48::from_num(liquidation_fee),
|
||||
maker_fee: I80F48::from_num(maker_fee),
|
||||
taker_fee: I80F48::from_num(taker_fee),
|
||||
max_funding: I80F48!(0.05),
|
||||
min_funding: I80F48!(0.05),
|
||||
long_funding: I80F48::ZERO,
|
||||
short_funding: I80F48::ZERO,
|
||||
funding_last_updated: Clock::get()?.unix_timestamp,
|
||||
impact_quantity: 100,
|
||||
open_interest: 0,
|
||||
seq_num: 0,
|
||||
fees_accrued: I80F48::ZERO,
|
||||
bump: *ctx.bumps.get("perp_market").ok_or(MangoError::SomeError)?,
|
||||
reserved: Default::default(),
|
||||
perp_market_index,
|
||||
base_token_index: base_token_index_opt.ok_or(TokenIndex::MAX).unwrap(),
|
||||
quote_token_index,
|
||||
reserved: Default::default(),
|
||||
};
|
||||
|
||||
let mut bids = ctx.accounts.bids.load_init()?;
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
use crate::state::{oracle_price, Book, PerpMarket};
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct UpdateFunding<'info> {
|
||||
#[account(
|
||||
mut,
|
||||
has_one = bids,
|
||||
has_one = asks,
|
||||
has_one = oracle,
|
||||
)]
|
||||
pub perp_market: AccountLoader<'info, PerpMarket>,
|
||||
#[account(mut)]
|
||||
pub asks: UncheckedAccount<'info>,
|
||||
#[account(mut)]
|
||||
pub bids: UncheckedAccount<'info>,
|
||||
|
||||
pub oracle: UncheckedAccount<'info>,
|
||||
}
|
||||
pub fn update_funding(ctx: Context<UpdateFunding>) -> Result<()> {
|
||||
// TODO: should we enforce a minimum window between 2 update_funding ix calls?
|
||||
let now_ts = Clock::get()?.unix_timestamp;
|
||||
|
||||
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
|
||||
let bids = &ctx.accounts.bids.to_account_info();
|
||||
let asks = &ctx.accounts.asks.to_account_info();
|
||||
let book = Book::load_mut(bids, asks, &perp_market)?;
|
||||
|
||||
let oracle_price = oracle_price(&ctx.accounts.oracle.to_account_info())?;
|
||||
|
||||
perp_market.update_funding(&book, oracle_price, now_ts as u64)?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -7,6 +7,7 @@ use fixed_macro::types::I80F48;
|
|||
use static_assertions::const_assert_eq;
|
||||
use std::mem::size_of;
|
||||
|
||||
pub const DAY: I80F48 = I80F48!(86400);
|
||||
pub const YEAR: I80F48 = I80F48!(31536000);
|
||||
|
||||
#[account(zero_copy)]
|
||||
|
|
|
@ -288,8 +288,8 @@ pub struct PerpAccount {
|
|||
pub quote_position_native: I80F48,
|
||||
|
||||
/// Already settled funding
|
||||
// pub long_settled_funding: I80F48,
|
||||
// pub short_settled_funding: I80F48,
|
||||
pub long_settled_funding: I80F48,
|
||||
pub short_settled_funding: I80F48,
|
||||
|
||||
/// Base lots in bids
|
||||
pub bids_base_lots: i64,
|
||||
|
@ -317,7 +317,7 @@ impl std::fmt::Debug for PerpAccount {
|
|||
.finish()
|
||||
}
|
||||
}
|
||||
const_assert_eq!(size_of::<PerpAccount>(), 8 + 8 * 5 + 16);
|
||||
const_assert_eq!(size_of::<PerpAccount>(), 8 + 8 * 5 + 3 * 16);
|
||||
const_assert_eq!(size_of::<PerpAccount>() % 8, 0);
|
||||
|
||||
impl Default for PerpAccount {
|
||||
|
@ -331,6 +331,8 @@ impl Default for PerpAccount {
|
|||
taker_base_lots: 0,
|
||||
taker_quote_lots: 0,
|
||||
reserved: Default::default(),
|
||||
long_settled_funding: I80F48::ZERO,
|
||||
short_settled_funding: I80F48::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -369,6 +371,19 @@ impl PerpAccount {
|
|||
self.base_position_lots += base_change;
|
||||
perp_market.open_interest += self.base_position_lots.abs() - start.abs();
|
||||
}
|
||||
|
||||
/// Move unrealized funding payments into the quote_position
|
||||
pub fn settle_funding(&mut self, perp_market: &PerpMarket) {
|
||||
if self.base_position_lots > 0 {
|
||||
self.quote_position_native -= (perp_market.long_funding - self.long_settled_funding)
|
||||
* I80F48::from_num(self.base_position_lots);
|
||||
} else if self.base_position_lots < 0 {
|
||||
self.quote_position_native -= (perp_market.short_funding - self.short_settled_funding)
|
||||
* I80F48::from_num(self.base_position_lots);
|
||||
}
|
||||
self.long_settled_funding = perp_market.long_funding;
|
||||
self.short_settled_funding = perp_market.short_funding;
|
||||
}
|
||||
}
|
||||
|
||||
#[zero_copy]
|
||||
|
@ -547,7 +562,7 @@ impl MangoAccountPerps {
|
|||
fill: &FillEvent,
|
||||
) -> Result<()> {
|
||||
let pa = self.get_account_mut_or_create(perp_market_index).unwrap().0;
|
||||
// pa.settle_funding(cache);
|
||||
pa.settle_funding(perp_market);
|
||||
|
||||
let side = fill.taker_side.invert_side();
|
||||
let (base_change, quote_change) = fill.base_quote_change(side);
|
||||
|
@ -586,6 +601,8 @@ impl MangoAccountPerps {
|
|||
fill: &FillEvent,
|
||||
) -> Result<()> {
|
||||
let pa = self.get_account_mut_or_create(perp_market_index).unwrap().0;
|
||||
pa.settle_funding(perp_market);
|
||||
|
||||
let (base_change, quote_change) = fill.base_quote_change(fill.taker_side);
|
||||
pa.remove_taker_trade(base_change, quote_change);
|
||||
pa.change_base_position(perp_market, base_change);
|
||||
|
|
|
@ -2,12 +2,15 @@ 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;
|
||||
use crate::state::{TokenIndex, DAY};
|
||||
use crate::util::checked_math as cm;
|
||||
|
||||
use super::Book;
|
||||
|
||||
pub type PerpMarketIndex = u16;
|
||||
|
||||
#[account(zero_copy)]
|
||||
|
@ -44,9 +47,12 @@ pub struct PerpMarket {
|
|||
pub maker_fee: I80F48,
|
||||
pub taker_fee: I80F48,
|
||||
|
||||
/// pub long_funding: I80F48,
|
||||
/// pub short_funding: I80F48,
|
||||
/// pub funding_last_updated: u64,
|
||||
pub max_funding: I80F48,
|
||||
pub min_funding: I80F48,
|
||||
pub long_funding: I80F48,
|
||||
pub short_funding: I80F48,
|
||||
pub funding_last_updated: i64,
|
||||
pub impact_quantity: i64,
|
||||
|
||||
///
|
||||
pub open_interest: i64,
|
||||
|
@ -77,7 +83,7 @@ pub struct PerpMarket {
|
|||
|
||||
const_assert_eq!(
|
||||
size_of::<PerpMarket>(),
|
||||
16 + 32 * 5 + 8 * 2 + 16 * 7 + 8 * 2 + 16 + 8
|
||||
16 + 32 * 5 + 8 * 2 + 16 * 11 + 8 * 2 + 8 * 2 + 16 + 8
|
||||
);
|
||||
const_assert_eq!(size_of::<PerpMarket>() % 8, 0);
|
||||
|
||||
|
@ -98,6 +104,38 @@ impl PerpMarket {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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 book_price = self.lot_to_native_price((bid + ask) / 2);
|
||||
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)
|
||||
|
|
Loading…
Reference in New Issue