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

577 lines
20 KiB
Rust
Raw Normal View History

use anchor_lang::prelude::*;
use checked_math as cm;
use derivative::Derivative;
use fixed::types::I80F48;
use static_assertions::const_assert_eq;
use std::cmp::Ordering;
use std::mem::size_of;
use crate::state::*;
pub const FREE_ORDER_SLOT: PerpMarketIndex = PerpMarketIndex::MAX;
#[zero_copy]
#[derive(AnchorDeserialize, AnchorSerialize, Derivative)]
#[derivative(Debug)]
pub struct TokenPosition {
// TODO: Why did we have deposits and borrows as two different values
// if only one of them was allowed to be != 0 at a time?
// todo: maybe we want to split collateral and lending?
// todo: see https://github.com/blockworks-foundation/mango-v4/issues/1
// todo: how does ftx do this?
/// The deposit_index (if positive) or borrow_index (if negative) scaled position
pub indexed_position: I80F48,
/// index into Group.tokens
pub token_index: TokenIndex,
/// incremented when a market requires this position to stay alive
pub in_use_count: u8,
#[derivative(Debug = "ignore")]
pub padding: [u8; 5],
#[derivative(Debug = "ignore")]
pub reserved: [u8; 40],
}
unsafe impl bytemuck::Pod for TokenPosition {}
unsafe impl bytemuck::Zeroable for TokenPosition {}
const_assert_eq!(size_of::<TokenPosition>(), 24 + 40);
const_assert_eq!(size_of::<TokenPosition>() % 8, 0);
impl Default for TokenPosition {
fn default() -> Self {
TokenPosition {
indexed_position: I80F48::ZERO,
token_index: TokenIndex::MAX,
in_use_count: 0,
padding: Default::default(),
reserved: [0; 40],
}
}
}
impl TokenPosition {
pub fn is_active(&self) -> bool {
self.token_index != TokenIndex::MAX
}
pub fn is_active_for_token(&self, token_index: TokenIndex) -> bool {
self.token_index == token_index
}
pub fn native(&self, bank: &Bank) -> I80F48 {
if self.indexed_position.is_positive() {
self.indexed_position * bank.deposit_index
} else {
self.indexed_position * bank.borrow_index
}
}
2022-08-14 13:18:36 -07:00
#[cfg(feature = "client")]
pub fn ui(&self, bank: &Bank) -> I80F48 {
if self.indexed_position.is_positive() {
(self.indexed_position * bank.deposit_index)
/ I80F48::from_num(10u64.pow(bank.mint_decimals as u32))
} else {
(self.indexed_position * bank.borrow_index)
/ I80F48::from_num(10u64.pow(bank.mint_decimals as u32))
}
}
pub fn is_in_use(&self) -> bool {
self.in_use_count > 0
}
}
#[zero_copy]
#[derive(AnchorSerialize, AnchorDeserialize, Derivative)]
#[derivative(Debug)]
pub struct Serum3Orders {
pub open_orders: Pubkey,
/// Tracks the amount of borrows that have flowed into the serum open orders account.
/// These borrows did not have the loan origination fee applied, and that may happen
/// later (in serum3_settle_funds) if we can guarantee that the funds were used.
/// In particular a place-on-book, cancel, settle should not cost fees.
pub base_borrows_without_fee: u64,
pub quote_borrows_without_fee: u64,
pub market_index: Serum3MarketIndex,
/// Store the base/quote token index, so health computations don't need
/// to get passed the static SerumMarket to find which tokens a market
/// uses and look up the correct oracles.
pub base_token_index: TokenIndex,
pub quote_token_index: TokenIndex,
#[derivative(Debug = "ignore")]
pub padding: [u8; 2],
#[derivative(Debug = "ignore")]
pub reserved: [u8; 64],
}
const_assert_eq!(size_of::<Serum3Orders>(), 32 + 8 * 2 + 2 * 3 + 2 + 64);
const_assert_eq!(size_of::<Serum3Orders>() % 8, 0);
unsafe impl bytemuck::Pod for Serum3Orders {}
unsafe impl bytemuck::Zeroable for Serum3Orders {}
impl Serum3Orders {
pub fn is_active(&self) -> bool {
self.market_index != Serum3MarketIndex::MAX
}
pub fn is_active_for_market(&self, market_index: Serum3MarketIndex) -> bool {
self.market_index == market_index
}
}
impl Default for Serum3Orders {
fn default() -> Self {
Self {
open_orders: Pubkey::default(),
market_index: Serum3MarketIndex::MAX,
base_token_index: TokenIndex::MAX,
quote_token_index: TokenIndex::MAX,
reserved: [0; 64],
padding: Default::default(),
base_borrows_without_fee: 0,
quote_borrows_without_fee: 0,
}
}
}
#[zero_copy]
#[derive(AnchorSerialize, AnchorDeserialize, Derivative)]
#[derivative(Debug)]
pub struct PerpPosition {
pub market_index: PerpMarketIndex,
#[derivative(Debug = "ignore")]
pub padding: [u8; 6],
/// Active position size, measured in base lots
base_position_lots: i64,
/// Active position in quote (conversation rate is that of the time the order was settled)
/// measured in native quote
quote_position_native: I80F48,
2022-08-11 07:58:47 -07:00
/// Tracks what the position is to calculate average entry & break even price
2022-08-04 07:53:14 -07:00
pub quote_entry_native: i64,
2022-08-11 07:58:47 -07:00
pub quote_running_native: i64,
2022-08-04 07:53:14 -07:00
/// Already settled funding
pub long_settled_funding: I80F48,
pub short_settled_funding: I80F48,
/// Base lots in bids
pub bids_base_lots: i64,
/// Base lots in asks
pub asks_base_lots: i64,
/// Liquidity mining rewards
// pub mngo_accrued: u64,
/// Amount that's on EventQueue waiting to be processed
pub taker_base_lots: i64,
pub taker_quote_lots: i64,
#[derivative(Debug = "ignore")]
pub reserved: [u8; 64],
}
const_assert_eq!(size_of::<PerpPosition>(), 8 + 7 * 8 + 3 * 16 + 64);
const_assert_eq!(size_of::<PerpPosition>() % 8, 0);
unsafe impl bytemuck::Pod for PerpPosition {}
unsafe impl bytemuck::Zeroable for PerpPosition {}
impl Default for PerpPosition {
fn default() -> Self {
Self {
market_index: PerpMarketIndex::MAX,
base_position_lots: 0,
quote_position_native: I80F48::ZERO,
2022-08-04 07:53:14 -07:00
quote_entry_native: 0,
2022-08-11 07:58:47 -07:00
quote_running_native: 0,
bids_base_lots: 0,
asks_base_lots: 0,
taker_base_lots: 0,
taker_quote_lots: 0,
reserved: [0; 64],
long_settled_funding: I80F48::ZERO,
short_settled_funding: I80F48::ZERO,
padding: Default::default(),
}
}
}
impl PerpPosition {
/// Add taker trade after it has been matched but before it has been process on EventQueue
pub fn add_taker_trade(&mut self, side: Side, base_lots: i64, quote_lots: i64) {
match side {
Side::Bid => {
2022-09-05 05:14:42 -07:00
cm!(self.taker_base_lots += base_lots);
cm!(self.taker_quote_lots -= quote_lots);
}
Side::Ask => {
2022-09-05 05:14:42 -07:00
cm!(self.taker_base_lots -= base_lots);
cm!(self.taker_quote_lots += quote_lots);
}
}
}
/// Remove taker trade after it has been processed on EventQueue
pub fn remove_taker_trade(&mut self, base_change: i64, quote_change: i64) {
2022-09-05 05:14:42 -07:00
cm!(self.taker_base_lots -= base_change);
cm!(self.taker_quote_lots -= quote_change);
}
pub fn is_active(&self) -> bool {
self.market_index != PerpMarketIndex::MAX
}
pub fn is_active_for_market(&self, market_index: PerpMarketIndex) -> bool {
self.market_index == market_index
}
2022-09-01 09:07:57 -07:00
// Return base position in native units for a perp market
pub fn base_position_native(&self, market: &PerpMarket) -> I80F48 {
I80F48::from(cm!(self.base_position_lots * market.base_lot_size))
}
pub fn base_position_lots(&self) -> i64 {
self.base_position_lots
}
pub fn quote_position_native(&self) -> I80F48 {
self.quote_position_native
}
/// This assumes settle_funding was already called
fn change_base_position(&mut self, perp_market: &mut PerpMarket, base_change: i64) {
let start = self.base_position_lots;
self.base_position_lots += base_change;
perp_market.open_interest += self.base_position_lots.abs() - start.abs();
}
/// The amount of funding this account still needs to pay, in native quote
pub fn unsettled_funding(&self, perp_market: &PerpMarket) -> I80F48 {
match self.base_position_lots.cmp(&0) {
Ordering::Greater => {
cm!((perp_market.long_funding - self.long_settled_funding)
* I80F48::from_num(self.base_position_lots))
}
Ordering::Less => {
cm!((perp_market.short_funding - self.short_settled_funding)
* I80F48::from_num(self.base_position_lots))
}
Ordering::Equal => I80F48::ZERO,
}
}
/// Move unrealized funding payments into the quote_position
pub fn settle_funding(&mut self, perp_market: &PerpMarket) {
let funding = self.unsettled_funding(perp_market);
cm!(self.quote_position_native -= funding);
self.long_settled_funding = perp_market.long_funding;
self.short_settled_funding = perp_market.short_funding;
}
2022-08-04 07:53:14 -07:00
/// Update the quote entry position
fn update_entry_price(&mut self, base_change: i64, quote_change_native: i64) {
2022-08-04 07:53:14 -07:00
if base_change == 0 {
return;
}
let old_position = self.base_position_lots;
let is_increasing = old_position == 0 || old_position.signum() == base_change.signum();
cm!(self.quote_running_native += quote_change_native);
2022-08-04 07:53:14 -07:00
match is_increasing {
true => {
cm!(self.quote_entry_native += quote_change_native);
2022-08-04 07:53:14 -07:00
}
false => {
let new_position = cm!(old_position + base_change);
2022-08-11 07:58:47 -07:00
let changes_side = old_position.signum() == -new_position.signum();
self.quote_entry_native = if changes_side {
cm!(((new_position as f64) * (quote_change_native as f64)
/ (base_change as f64))
.round()) as i64
2022-08-11 07:58:47 -07:00
} else {
let remaining_frac =
(1f64 - (base_change.abs() as f64) / (old_position.abs() as f64)).max(0f64);
let initial_entry = self.quote_entry_native as f64;
(initial_entry * remaining_frac).round() as i64
2022-08-04 07:53:14 -07:00
}
}
}
}
/// Change the base and quote positions as the result of a trade
pub fn change_base_and_quote_positions(
2022-08-04 07:53:14 -07:00
&mut self,
perp_market: &mut PerpMarket,
base_change: i64,
quote_change_native: I80F48,
2022-08-04 07:53:14 -07:00
) {
self.update_entry_price(
base_change,
quote_change_native.round().checked_to_num().unwrap(),
);
2022-08-04 07:53:14 -07:00
self.change_base_position(perp_market, base_change);
self.change_quote_position(quote_change_native);
}
pub fn change_quote_position(&mut self, quote_change_native: I80F48) {
cm!(self.quote_position_native += quote_change_native);
2022-08-04 07:53:14 -07:00
}
2022-09-07 07:10:49 -07:00
/// Does the perp position have any open orders or fill events?
pub fn has_open_orders(&self) -> bool {
self.asks_base_lots != 0
|| self.bids_base_lots != 0
|| self.taker_base_lots != 0
|| self.taker_quote_lots != 0
}
2022-08-04 07:53:14 -07:00
/// Calculate the average entry price of the position
pub fn avg_entry_price(&self) -> I80F48 {
2022-08-11 07:58:47 -07:00
if self.base_position_lots == 0 {
2022-08-04 07:53:14 -07:00
return I80F48::ZERO; // TODO: What should this actually return? Error? NaN?
}
2022-08-11 07:58:47 -07:00
(I80F48::from(self.quote_entry_native) / I80F48::from(self.base_position_lots)).abs()
2022-08-04 07:53:14 -07:00
}
/// Calculate the break even price of the position
pub fn break_even_price(&self) -> I80F48 {
2022-08-04 07:53:14 -07:00
if self.base_position_lots == 0 {
return I80F48::ZERO; // TODO: What should this actually return? Error? NaN?
}
2022-08-11 07:58:47 -07:00
(I80F48::from(self.quote_running_native) / I80F48::from(self.base_position_lots)).abs()
2022-08-04 07:53:14 -07:00
}
}
#[zero_copy]
#[derive(AnchorSerialize, AnchorDeserialize, Debug)]
pub struct PerpOpenOrder {
pub order_side: Side, // TODO: storing enums isn't POD
pub padding1: [u8; 1],
pub order_market: PerpMarketIndex,
pub padding2: [u8; 4],
pub client_order_id: u64,
pub order_id: i128,
pub reserved: [u8; 64],
}
impl Default for PerpOpenOrder {
fn default() -> Self {
Self {
order_side: Side::Bid,
padding1: Default::default(),
order_market: FREE_ORDER_SLOT,
padding2: Default::default(),
client_order_id: 0,
order_id: 0,
reserved: [0; 64],
}
}
}
unsafe impl bytemuck::Pod for PerpOpenOrder {}
unsafe impl bytemuck::Zeroable for PerpOpenOrder {}
const_assert_eq!(size_of::<PerpOpenOrder>(), 1 + 1 + 2 + 4 + 8 + 16 + 64);
const_assert_eq!(size_of::<PerpOpenOrder>() % 8, 0);
#[macro_export]
macro_rules! account_seeds {
( $account:expr ) => {
&[
b"MangoAccount".as_ref(),
$account.group.as_ref(),
$account.owner.as_ref(),
&$account.account_num.to_le_bytes(),
&[$account.bump],
]
};
}
pub use account_seeds;
2022-08-04 07:53:14 -07:00
#[cfg(test)]
mod tests {
2022-09-22 09:55:12 -07:00
use crate::state::PerpMarket;
2022-08-04 07:53:14 -07:00
use fixed::types::I80F48;
use rand::Rng;
use super::PerpPosition;
2022-08-04 07:53:14 -07:00
fn create_perp_position(base_pos: i64, quote_pos: i64, entry_pos: i64) -> PerpPosition {
let mut pos = PerpPosition::default();
2022-08-04 07:53:14 -07:00
pos.base_position_lots = base_pos;
pos.quote_position_native = I80F48::from(quote_pos);
pos.quote_entry_native = entry_pos;
2022-08-11 07:58:47 -07:00
pos.quote_running_native = quote_pos;
2022-08-04 07:53:14 -07:00
pos
}
#[test]
fn test_quote_entry_long_increasing_from_zero() {
2022-09-22 09:55:12 -07:00
let mut market = PerpMarket::default_for_tests();
2022-08-04 07:53:14 -07:00
let mut pos = create_perp_position(0, 0, 0);
// Go long 10 @ 10
pos.change_base_and_quote_positions(&mut market, 10, I80F48::from(-100));
2022-08-04 07:53:14 -07:00
assert_eq!(pos.quote_entry_native, -100);
2022-08-11 07:58:47 -07:00
assert_eq!(pos.quote_running_native, -100);
assert_eq!(pos.avg_entry_price(), I80F48::from(10));
2022-08-04 07:53:14 -07:00
}
#[test]
fn test_quote_entry_short_increasing_from_zero() {
2022-09-22 09:55:12 -07:00
let mut market = PerpMarket::default_for_tests();
2022-08-04 07:53:14 -07:00
let mut pos = create_perp_position(0, 0, 0);
// Go short 10 @ 10
pos.change_base_and_quote_positions(&mut market, -10, I80F48::from(100));
2022-08-04 07:53:14 -07:00
assert_eq!(pos.quote_entry_native, 100);
2022-08-11 07:58:47 -07:00
assert_eq!(pos.quote_running_native, 100);
assert_eq!(pos.avg_entry_price(), I80F48::from(10));
2022-08-04 07:53:14 -07:00
}
#[test]
fn test_quote_entry_long_increasing_from_long() {
2022-09-22 09:55:12 -07:00
let mut market = PerpMarket::default_for_tests();
2022-08-04 07:53:14 -07:00
let mut pos = create_perp_position(10, -100, -100);
// Go long 10 @ 30
pos.change_base_and_quote_positions(&mut market, 10, I80F48::from(-300));
2022-08-04 07:53:14 -07:00
assert_eq!(pos.quote_entry_native, -400);
2022-08-11 07:58:47 -07:00
assert_eq!(pos.quote_running_native, -400);
assert_eq!(pos.avg_entry_price(), I80F48::from(20));
2022-08-04 07:53:14 -07:00
}
#[test]
fn test_quote_entry_short_increasing_from_short() {
2022-09-22 09:55:12 -07:00
let mut market = PerpMarket::default_for_tests();
2022-08-04 07:53:14 -07:00
let mut pos = create_perp_position(-10, 100, 100);
// Go short 10 @ 10
pos.change_base_and_quote_positions(&mut market, -10, I80F48::from(300));
2022-08-04 07:53:14 -07:00
assert_eq!(pos.quote_entry_native, 400);
2022-08-11 07:58:47 -07:00
assert_eq!(pos.quote_running_native, 400);
assert_eq!(pos.avg_entry_price(), I80F48::from(20));
2022-08-04 07:53:14 -07:00
}
#[test]
fn test_quote_entry_long_decreasing_from_short() {
2022-09-22 09:55:12 -07:00
let mut market = PerpMarket::default_for_tests();
2022-08-04 07:53:14 -07:00
let mut pos = create_perp_position(-10, 100, 100);
// Go long 5 @ 50
pos.change_base_and_quote_positions(&mut market, 5, I80F48::from(-250));
2022-08-11 07:58:47 -07:00
assert_eq!(pos.quote_entry_native, 50);
assert_eq!(pos.quote_running_native, -150);
assert_eq!(pos.avg_entry_price(), I80F48::from(10)); // Entry price remains the same when decreasing
2022-08-04 07:53:14 -07:00
}
#[test]
fn test_quote_entry_short_decreasing_from_long() {
2022-09-22 09:55:12 -07:00
let mut market = PerpMarket::default_for_tests();
2022-08-04 07:53:14 -07:00
let mut pos = create_perp_position(10, -100, -100);
// Go short 5 @ 50
pos.change_base_and_quote_positions(&mut market, -5, I80F48::from(250));
2022-08-11 07:58:47 -07:00
assert_eq!(pos.quote_entry_native, -50);
assert_eq!(pos.quote_running_native, 150);
assert_eq!(pos.avg_entry_price(), I80F48::from(10)); // Entry price remains the same when decreasing
2022-08-04 07:53:14 -07:00
}
#[test]
fn test_quote_entry_long_close_with_short() {
2022-09-22 09:55:12 -07:00
let mut market = PerpMarket::default_for_tests();
2022-08-04 07:53:14 -07:00
let mut pos = create_perp_position(10, -100, -100);
// Go short 10 @ 50
pos.change_base_and_quote_positions(&mut market, -10, I80F48::from(250));
2022-08-04 07:53:14 -07:00
assert_eq!(pos.quote_entry_native, 0);
2022-08-11 07:58:47 -07:00
assert_eq!(pos.quote_running_native, 150);
assert_eq!(pos.avg_entry_price(), I80F48::from(0)); // Entry price zero when no position
2022-08-04 07:53:14 -07:00
}
#[test]
fn test_quote_entry_short_close_with_long() {
2022-09-22 09:55:12 -07:00
let mut market = PerpMarket::default_for_tests();
2022-08-04 07:53:14 -07:00
let mut pos = create_perp_position(-10, 100, 100);
// Go long 10 @ 50
pos.change_base_and_quote_positions(&mut market, 10, I80F48::from(-250));
2022-08-04 07:53:14 -07:00
assert_eq!(pos.quote_entry_native, 0);
2022-08-11 07:58:47 -07:00
assert_eq!(pos.quote_running_native, -150);
assert_eq!(pos.avg_entry_price(), I80F48::from(0)); // Entry price zero when no position
2022-08-04 07:53:14 -07:00
}
#[test]
fn test_quote_entry_long_close_short_with_overflow() {
2022-09-22 09:55:12 -07:00
let mut market = PerpMarket::default_for_tests();
2022-08-04 07:53:14 -07:00
let mut pos = create_perp_position(10, -100, -100);
// Go short 15 @ 20
pos.change_base_and_quote_positions(&mut market, -15, I80F48::from(300));
2022-08-04 07:53:14 -07:00
assert_eq!(pos.quote_entry_native, 100);
2022-08-11 07:58:47 -07:00
assert_eq!(pos.quote_running_native, 200);
assert_eq!(pos.avg_entry_price(), I80F48::from(20)); // Entry price zero when no position
2022-08-04 07:53:14 -07:00
}
#[test]
fn test_quote_entry_short_close_long_with_overflow() {
2022-09-22 09:55:12 -07:00
let mut market = PerpMarket::default_for_tests();
2022-08-04 07:53:14 -07:00
let mut pos = create_perp_position(-10, 100, 100);
// Go short 15 @ 20
pos.change_base_and_quote_positions(&mut market, 15, I80F48::from(-300));
2022-08-04 07:53:14 -07:00
assert_eq!(pos.quote_entry_native, -100);
2022-08-11 07:58:47 -07:00
assert_eq!(pos.quote_running_native, -200);
assert_eq!(pos.avg_entry_price(), I80F48::from(20)); // Entry price zero when no position
2022-08-04 07:53:14 -07:00
}
#[test]
fn test_quote_entry_break_even_price() {
2022-09-22 09:55:12 -07:00
let mut market = PerpMarket::default_for_tests();
2022-08-04 07:53:14 -07:00
let mut pos = create_perp_position(0, 0, 0);
// Buy 11 @ 10,000
pos.change_base_and_quote_positions(&mut market, 11, I80F48::from(-11 * 10_000));
2022-08-04 07:53:14 -07:00
// Sell 1 @ 12,000
pos.change_base_and_quote_positions(&mut market, -1, I80F48::from(12_000));
2022-08-11 07:58:47 -07:00
assert_eq!(pos.quote_entry_native, -10 * 10_000);
assert_eq!(pos.quote_running_native, -98_000);
2022-08-04 07:53:14 -07:00
assert_eq!(pos.base_position_lots, 10);
assert_eq!(pos.break_even_price(), I80F48::from(9_800)); // We made 2k on the trade, so we can sell our contract up to a loss of 200 each
2022-08-04 07:53:14 -07:00
}
#[test]
fn test_quote_entry_multiple_and_reversed_changes_return_entry_to_zero() {
2022-09-22 09:55:12 -07:00
let mut market = PerpMarket::default_for_tests();
2022-08-04 07:53:14 -07:00
let mut pos = create_perp_position(0, 0, 0);
// Generate array of random trades
let mut rng = rand::thread_rng();
let mut trades: Vec<[i64; 2]> = Vec::with_capacity(500);
for _ in 0..trades.capacity() {
let qty: i64 = rng.gen_range(-1000..=1000);
let px: f64 = rng.gen_range(0.1..=100.0);
let quote: i64 = (-qty as f64 * px).round() as i64;
trades.push([qty, quote]);
}
// Apply all of the trades going forward
trades.iter().for_each(|[qty, quote]| {
pos.change_base_and_quote_positions(&mut market, *qty, I80F48::from(*quote));
2022-08-04 07:53:14 -07:00
});
// base_position should be sum of all base quantities
assert_eq!(
pos.base_position_lots,
trades.iter().map(|[qty, _]| qty).sum::<i64>()
);
// Reverse out all the trades
trades.iter().for_each(|[qty, quote]| {
pos.change_base_and_quote_positions(&mut market, -*qty, I80F48::from(-*quote));
2022-08-04 07:53:14 -07:00
});
// base position should be 0
assert_eq!(pos.base_position_lots, 0);
// quote entry position should be 0
assert_eq!(pos.quote_entry_native, 0);
2022-08-11 07:58:47 -07:00
// running quote should be 0
assert_eq!(pos.quote_running_native, 0);
2022-08-04 07:53:14 -07:00
}
}