perp funding (#51)

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
microwavedcola1 2022-05-16 15:34:56 +02:00 committed by GitHub
parent f5c505f847
commit be73106ce1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 113 additions and 10 deletions

View File

@ -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;

View File

@ -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()?;

View File

@ -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(())
}

View File

@ -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)]

View File

@ -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);

View File

@ -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)