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_register_market::*;
|
||||||
pub use serum3_settle_funds::*;
|
pub use serum3_settle_funds::*;
|
||||||
pub use set_stub_oracle::*;
|
pub use set_stub_oracle::*;
|
||||||
|
pub use update_funding::*;
|
||||||
pub use update_index::*;
|
pub use update_index::*;
|
||||||
pub use withdraw::*;
|
pub use withdraw::*;
|
||||||
|
|
||||||
|
@ -47,5 +48,6 @@ mod serum3_place_order;
|
||||||
mod serum3_register_market;
|
mod serum3_register_market;
|
||||||
mod serum3_settle_funds;
|
mod serum3_settle_funds;
|
||||||
mod set_stub_oracle;
|
mod set_stub_oracle;
|
||||||
|
mod update_funding;
|
||||||
mod update_index;
|
mod update_index;
|
||||||
mod withdraw;
|
mod withdraw;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
use fixed::types::I80F48;
|
use fixed::types::I80F48;
|
||||||
|
use fixed_macro::types::I80F48;
|
||||||
|
|
||||||
use crate::error::MangoError;
|
use crate::error::MangoError;
|
||||||
use crate::state::*;
|
use crate::state::*;
|
||||||
|
@ -56,6 +57,9 @@ pub fn perp_create_market(
|
||||||
liquidation_fee: f32,
|
liquidation_fee: f32,
|
||||||
maker_fee: f32,
|
maker_fee: f32,
|
||||||
taker_fee: f32,
|
taker_fee: f32,
|
||||||
|
max_funding: f32,
|
||||||
|
min_funding: f32,
|
||||||
|
impact_quantity: u64,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut perp_market = ctx.accounts.perp_market.load_init()?;
|
let mut perp_market = ctx.accounts.perp_market.load_init()?;
|
||||||
*perp_market = PerpMarket {
|
*perp_market = PerpMarket {
|
||||||
|
@ -74,14 +78,20 @@ pub fn perp_create_market(
|
||||||
liquidation_fee: I80F48::from_num(liquidation_fee),
|
liquidation_fee: I80F48::from_num(liquidation_fee),
|
||||||
maker_fee: I80F48::from_num(maker_fee),
|
maker_fee: I80F48::from_num(maker_fee),
|
||||||
taker_fee: I80F48::from_num(taker_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,
|
open_interest: 0,
|
||||||
seq_num: 0,
|
seq_num: 0,
|
||||||
fees_accrued: I80F48::ZERO,
|
fees_accrued: I80F48::ZERO,
|
||||||
bump: *ctx.bumps.get("perp_market").ok_or(MangoError::SomeError)?,
|
bump: *ctx.bumps.get("perp_market").ok_or(MangoError::SomeError)?,
|
||||||
|
reserved: Default::default(),
|
||||||
perp_market_index,
|
perp_market_index,
|
||||||
base_token_index: base_token_index_opt.ok_or(TokenIndex::MAX).unwrap(),
|
base_token_index: base_token_index_opt.ok_or(TokenIndex::MAX).unwrap(),
|
||||||
quote_token_index,
|
quote_token_index,
|
||||||
reserved: Default::default(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut bids = ctx.accounts.bids.load_init()?;
|
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 static_assertions::const_assert_eq;
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
pub const DAY: I80F48 = I80F48!(86400);
|
||||||
pub const YEAR: I80F48 = I80F48!(31536000);
|
pub const YEAR: I80F48 = I80F48!(31536000);
|
||||||
|
|
||||||
#[account(zero_copy)]
|
#[account(zero_copy)]
|
||||||
|
|
|
@ -288,8 +288,8 @@ pub struct PerpAccount {
|
||||||
pub quote_position_native: I80F48,
|
pub quote_position_native: I80F48,
|
||||||
|
|
||||||
/// Already settled funding
|
/// Already settled funding
|
||||||
// pub long_settled_funding: I80F48,
|
pub long_settled_funding: I80F48,
|
||||||
// pub short_settled_funding: I80F48,
|
pub short_settled_funding: I80F48,
|
||||||
|
|
||||||
/// Base lots in bids
|
/// Base lots in bids
|
||||||
pub bids_base_lots: i64,
|
pub bids_base_lots: i64,
|
||||||
|
@ -317,7 +317,7 @@ impl std::fmt::Debug for PerpAccount {
|
||||||
.finish()
|
.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);
|
const_assert_eq!(size_of::<PerpAccount>() % 8, 0);
|
||||||
|
|
||||||
impl Default for PerpAccount {
|
impl Default for PerpAccount {
|
||||||
|
@ -331,6 +331,8 @@ impl Default for PerpAccount {
|
||||||
taker_base_lots: 0,
|
taker_base_lots: 0,
|
||||||
taker_quote_lots: 0,
|
taker_quote_lots: 0,
|
||||||
reserved: Default::default(),
|
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;
|
self.base_position_lots += base_change;
|
||||||
perp_market.open_interest += self.base_position_lots.abs() - start.abs();
|
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]
|
#[zero_copy]
|
||||||
|
@ -547,7 +562,7 @@ impl MangoAccountPerps {
|
||||||
fill: &FillEvent,
|
fill: &FillEvent,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let pa = self.get_account_mut_or_create(perp_market_index).unwrap().0;
|
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 side = fill.taker_side.invert_side();
|
||||||
let (base_change, quote_change) = fill.base_quote_change(side);
|
let (base_change, quote_change) = fill.base_quote_change(side);
|
||||||
|
@ -586,6 +601,8 @@ impl MangoAccountPerps {
|
||||||
fill: &FillEvent,
|
fill: &FillEvent,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let pa = self.get_account_mut_or_create(perp_market_index).unwrap().0;
|
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);
|
let (base_change, quote_change) = fill.base_quote_change(fill.taker_side);
|
||||||
pa.remove_taker_trade(base_change, quote_change);
|
pa.remove_taker_trade(base_change, quote_change);
|
||||||
pa.change_base_position(perp_market, base_change);
|
pa.change_base_position(perp_market, base_change);
|
||||||
|
|
|
@ -2,12 +2,15 @@ use std::mem::size_of;
|
||||||
|
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
use fixed::types::I80F48;
|
use fixed::types::I80F48;
|
||||||
|
|
||||||
use static_assertions::const_assert_eq;
|
use static_assertions::const_assert_eq;
|
||||||
|
|
||||||
use crate::state::orderbook::order_type::Side;
|
use crate::state::orderbook::order_type::Side;
|
||||||
use crate::state::TokenIndex;
|
use crate::state::{TokenIndex, DAY};
|
||||||
use crate::util::checked_math as cm;
|
use crate::util::checked_math as cm;
|
||||||
|
|
||||||
|
use super::Book;
|
||||||
|
|
||||||
pub type PerpMarketIndex = u16;
|
pub type PerpMarketIndex = u16;
|
||||||
|
|
||||||
#[account(zero_copy)]
|
#[account(zero_copy)]
|
||||||
|
@ -44,9 +47,12 @@ pub struct PerpMarket {
|
||||||
pub maker_fee: I80F48,
|
pub maker_fee: I80F48,
|
||||||
pub taker_fee: I80F48,
|
pub taker_fee: I80F48,
|
||||||
|
|
||||||
/// pub long_funding: I80F48,
|
pub max_funding: I80F48,
|
||||||
/// pub short_funding: I80F48,
|
pub min_funding: I80F48,
|
||||||
/// pub funding_last_updated: u64,
|
pub long_funding: I80F48,
|
||||||
|
pub short_funding: I80F48,
|
||||||
|
pub funding_last_updated: i64,
|
||||||
|
pub impact_quantity: i64,
|
||||||
|
|
||||||
///
|
///
|
||||||
pub open_interest: i64,
|
pub open_interest: i64,
|
||||||
|
@ -77,7 +83,7 @@ pub struct PerpMarket {
|
||||||
|
|
||||||
const_assert_eq!(
|
const_assert_eq!(
|
||||||
size_of::<PerpMarket>(),
|
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);
|
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
|
/// 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 {
|
pub fn lot_to_native_price(&self, price: i64) -> I80F48 {
|
||||||
I80F48::from_num(price)
|
I80F48::from_num(price)
|
||||||
|
|
Loading…
Reference in New Issue