From 4795286dbd001b3a9046d6a0b1b6767370fa456d Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Mon, 5 Sep 2022 18:41:20 +0200 Subject: [PATCH] Perps: Account for unsettled funding in health --- programs/mango-v4/src/state/health.rs | 124 +++++++++++------- .../src/state/mango_account_components.rs | 22 ++-- 2 files changed, 93 insertions(+), 53 deletions(-) diff --git a/programs/mango-v4/src/state/health.rs b/programs/mango-v4/src/state/health.rs index 52cc8dc06..f01cf5d56 100644 --- a/programs/mango-v4/src/state/health.rs +++ b/programs/mango-v4/src/state/health.rs @@ -498,12 +498,14 @@ impl PerpInfo { let base_info = &token_infos[base_index]; let base_lot_size = I80F48::from(perp_market.base_lot_size); - let base_lots = cm!(perp_position.base_position_lots + perp_position.taker_base_lots); + + let unsettled_funding = perp_position.unsettled_funding(&perp_market); let taker_quote = I80F48::from(cm!( perp_position.taker_quote_lots * perp_market.quote_lot_size )); - let quote_current = cm!(perp_position.quote_position_native + taker_quote); + let quote_current = + cm!(perp_position.quote_position_native - unsettled_funding + taker_quote); // Two scenarios: // 1. The price goes low and all bids execute, converting to base. @@ -1021,7 +1023,6 @@ pub fn new_health_cache( }); } - // TODO: also account for perp funding in health // health contribution from perp accounts let mut perp_infos = Vec::with_capacity(account.active_perp_positions().count()); for (i, perp_position) in account.active_perp_positions().enumerate() { @@ -1158,6 +1159,35 @@ mod tests { (bank, oracle) } + fn mock_perp_market( + group: Pubkey, + market_index: PerpMarketIndex, + base_token: TokenIndex, + init_weights: f64, + maint_weights: f64, + ) -> TestAccount { + let mut pm = TestAccount::::new_zeroed(); + pm.data().group = group; + pm.data().perp_market_index = market_index; + pm.data().base_token_index = base_token; + pm.data().init_asset_weight = I80F48::from_num(1.0 - init_weights); + pm.data().init_liab_weight = I80F48::from_num(1.0 + init_weights); + pm.data().maint_asset_weight = I80F48::from_num(1.0 - maint_weights); + pm.data().maint_liab_weight = I80F48::from_num(1.0 + maint_weights); + pm.data().quote_lot_size = 100; + pm.data().base_lot_size = 10; + pm + } + + fn health_eq(a: I80F48, b: f64) -> bool { + if (a - I80F48::from_num(b)).abs() < 0.001 { + true + } else { + println!("health is {}, but expected {}", a, b); + false + } + } + // Run a health test that includes all the side values (like referrer_rebates_accrued) #[test] fn test_health0() { @@ -1194,16 +1224,7 @@ mod tests { oo1.data().native_coin_free = 3; oo1.data().referrer_rebates_accrued = 2; - let mut perp1 = TestAccount::::new_zeroed(); - perp1.data().group = group; - perp1.data().perp_market_index = 9; - perp1.data().base_token_index = 4; - perp1.data().init_asset_weight = I80F48::from_num(1.0 - 0.2f64); - perp1.data().init_liab_weight = I80F48::from_num(1.0 + 0.2f64); - perp1.data().maint_asset_weight = I80F48::from_num(1.0 - 0.1f64); - perp1.data().maint_liab_weight = I80F48::from_num(1.0 + 0.1f64); - perp1.data().quote_lot_size = 100; - perp1.data().base_lot_size = 10; + let mut perp1 = mock_perp_market(group, 9, 4, 0.2, 0.1); let perpaccount = account.ensure_perp_position(9).unwrap().0; perpaccount.base_position_lots = 3; perpaccount.quote_position_native = -I80F48::from(310u16); @@ -1223,15 +1244,6 @@ mod tests { let retriever = ScanningAccountRetriever::new(&ais, &group).unwrap(); - let health_eq = |a: I80F48, b: f64| { - if (a - I80F48::from_num(b)).abs() < 0.001 { - true - } else { - println!("health is {}, but expected {}", a, b); - false - } - }; - // for bank1/oracle1, including open orders (scenario: bids execute) let health1 = (100.0 + 1.0 + 2.0 + (20.0 + 15.0 * 5.0)) * 0.8; // for bank2/oracle2 @@ -1260,9 +1272,7 @@ mod tests { let oo1key = oo1.pubkey; oo1.data().native_pc_total = 20; - let mut perp1 = TestAccount::::new_zeroed(); - perp1.data().group = group; - perp1.data().perp_market_index = 9; + let mut perp1 = mock_perp_market(group, 9, 4, 0.2, 0.1); let oracle2_account_info = oracle2.as_account_info(); let ais = vec![ @@ -1386,16 +1396,7 @@ mod tests { oo2.data().native_pc_total = testcase.oo_1_3.0; oo2.data().native_coin_total = testcase.oo_1_3.1; - let mut perp1 = TestAccount::::new_zeroed(); - perp1.data().group = group; - perp1.data().perp_market_index = 9; - perp1.data().base_token_index = 4; - perp1.data().init_asset_weight = I80F48::from_num(1.0 - 0.2f64); - perp1.data().init_liab_weight = I80F48::from_num(1.0 + 0.2f64); - perp1.data().maint_asset_weight = I80F48::from_num(1.0 - 0.1f64); - perp1.data().maint_liab_weight = I80F48::from_num(1.0 + 0.1f64); - perp1.data().quote_lot_size = 100; - perp1.data().base_lot_size = 10; + let mut perp1 = mock_perp_market(group, 9, 4, 0.2, 0.1); let perpaccount = account.ensure_perp_position(9).unwrap().0; perpaccount.base_position_lots = testcase.perp1.0; perpaccount.quote_position_native = I80F48::from(testcase.perp1.1); @@ -1416,15 +1417,6 @@ mod tests { let retriever = ScanningAccountRetriever::new(&ais, &group).unwrap(); - let health_eq = |a: I80F48, b: f64| { - if (a - I80F48::from_num(b)).abs() < 0.001 { - true - } else { - println!("health is {}, but expected {}", a, b); - false - } - }; - assert!(health_eq( compute_health(&account.borrow(), HealthType::Init, &retriever).unwrap(), testcase.expected_health @@ -1696,4 +1688,48 @@ mod tests { check_max_swap_result(&health_cache, 0, 1, 4.0); } } + + #[test] + fn test_health_perp_funding() { + let buffer = MangoAccount::default_for_tests().try_to_vec().unwrap(); + let mut account = MangoAccountValue::from_bytes(&buffer).unwrap(); + + let group = Pubkey::new_unique(); + + let (mut bank1, mut oracle1) = mock_bank_and_oracle(group, 1, 1.0, 0.2, 0.1); + bank1 + .data() + .change_without_fee( + account.ensure_token_position(1).unwrap().0, + I80F48::from(100), + ) + .unwrap(); + + let mut perp1 = mock_perp_market(group, 9, 1, 0.2, 0.1); + perp1.data().long_funding = I80F48::from_num(10.1); + let perpaccount = account.ensure_perp_position(9).unwrap().0; + perpaccount.base_position_lots = 10; // 100 base native + perpaccount.quote_position_native = I80F48::from(-110); + perpaccount.long_settled_funding = I80F48::from_num(10.0); + + let ais = vec![ + bank1.as_account_info(), + oracle1.as_account_info(), + perp1.as_account_info(), + ]; + + let retriever = ScanningAccountRetriever::new(&ais, &group).unwrap(); + + assert!(health_eq( + compute_health(&account.borrow(), HealthType::Init, &retriever).unwrap(), + // token + 0.8 * 100.0 + // perp base + + 0.8 * 100.0 + // perp quote + - 110.0 + // perp funding (10 * (10.1 - 10.0)) + - 1.0 + )); + } } diff --git a/programs/mango-v4/src/state/mango_account_components.rs b/programs/mango-v4/src/state/mango_account_components.rs index 02dc4d5e5..baf33010b 100644 --- a/programs/mango-v4/src/state/mango_account_components.rs +++ b/programs/mango-v4/src/state/mango_account_components.rs @@ -247,21 +247,25 @@ impl PerpPosition { 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) { + /// 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 => { - self.quote_position_native -= (perp_market.long_funding - - self.long_settled_funding) - * I80F48::from_num(self.base_position_lots); + cm!((perp_market.long_funding - self.long_settled_funding) + * I80F48::from_num(self.base_position_lots)) } Ordering::Less => { - self.quote_position_native -= (perp_market.short_funding - - self.short_settled_funding) - * I80F48::from_num(self.base_position_lots); + cm!((perp_market.short_funding - self.short_settled_funding) + * I80F48::from_num(self.base_position_lots)) } - Ordering::Equal => (), + 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; }