Perp health: Some code and comments

This commit is contained in:
Christian Kamm 2022-04-04 15:17:27 +02:00
parent 256397593d
commit 68ef58248c
2 changed files with 94 additions and 2 deletions

View File

@ -2,6 +2,7 @@ use anchor_lang::prelude::*;
use fixed::types::I80F48;
use serum_dex::state::OpenOrders;
use std::cell::{Ref, RefMut};
use std::cmp::min;
use std::collections::HashMap;
use crate::error::MangoError;
@ -347,6 +348,28 @@ fn health_contribution(
Ok(cm!(balance * weight))
}
/// Weigh a perp base balance (in lots) with the appropriate health weight
#[inline(always)]
fn health_weighted_perp_base_lots(
health_type: HealthType,
market: &PerpMarket,
lots: i64,
) -> Result<I80F48> {
let weight = if lots.is_negative() {
match health_type {
HealthType::Init => market.init_liab_weight,
HealthType::Maint => market.maint_liab_weight,
}
} else {
match health_type {
HealthType::Init => market.init_asset_weight,
HealthType::Maint => market.maint_asset_weight,
}
};
let lots = I80F48::from(lots);
Ok(cm!(weight * lots))
}
/// Compute health contribution of two tokens - pure convenience
#[inline(always)]
fn pair_health(
@ -449,7 +472,71 @@ fn compute_health_detail<'a, 'b: 'a>(
// health contribution from perp accounts
for (i, perp_account) in account.perps.iter_active_accounts().enumerate() {
let _perp_market = retriever.perp_market(&account.group, i, perp_account.market_index)?;
let perp_market = retriever.perp_market(&account.group, i, perp_account.market_index)?;
// find the TokenInfos for the market's base and quote tokens
let base_index = token_infos
.iter()
.position(|ti| ti.token_index == perp_market.base_token_index)
.ok_or_else(|| error!(MangoError::SomeError))?;
let base_info = &token_infos[base_index];
let base_lots = cm!(perp_account.base_position_lots + perp_account.taker_base_lots);
let taker_quote = I80F48::from(cm!(
perp_account.taker_quote_lots * perp_market.quote_lot_size
));
let quote = cm!(perp_account.quote_position_native + taker_quote);
// Two scenarios:
// 1. The price goes low and all bids execute, converting to base.
// The health for this case is:
// (weighted(base_lots + bids) - bids) * base_lots * price + quote
// 2. The price goes high and all asks execute, converting to quote.
// The health for this case is:
// (weighted(base_lots - asks) + asks) * base_lots * price + quote
//
// Comparing these makes it clear we need to pick the worse subfactor
// weighted(base_lots + bids) - bids
// or
// weighted(base_lots - asks) + asks
let weighted_base_lots_bids = health_weighted_perp_base_lots(
health_type,
&perp_market,
cm!(base_lots + perp_account.bids_base_lots),
)?;
let bids_base_lots = I80F48::from(perp_account.bids_base_lots);
let scenario1 = cm!(weighted_base_lots_bids - bids_base_lots);
let weighted_base_lots_asks = health_weighted_perp_base_lots(
health_type,
&perp_market,
cm!(base_lots - perp_account.asks_base_lots),
)?;
let asks_base_lots = I80F48::from(perp_account.asks_base_lots);
let scenario2 = cm!(weighted_base_lots_asks + asks_base_lots);
let worse_scenario = min(scenario1, scenario2);
let base_lot_size = I80F48::from(perp_market.base_lot_size);
let _health = cm!(worse_scenario * base_lot_size * base_info.oracle_price + quote);
// The above choice between scenario1 and 2 depends on the asset_weight and
// liab weight. Thus it needs to be redone for init and maint health.
//
// The condition for the choice to be the same is:
// (1 - init_asset_weight) / (init_liab_weight - 1)
// == (1 - maint_asset_weight) / (maint_liab_weight - 1)
//
// Which can be derived by noticing that health for both scenarios is
// weighted(x) + y - x
// and that the only interesting case is
// asks_net = base_lots - asks < 0 and
// bids_net = base_lots + bids > 0.
// Then
// health_bids_scenario < health_asks_scenario
// iff (asset_weight - 1) * bids_net < (liab_weight - 1) * asks_net
// iff (1 - asset_weightt) / (liab_weight - 1) bids_net > abs(asks_net)
// Probably the resolution here is to go to v3's assumption that there's an x
// such that asset_weight = 1-x and liab_weight = 1+x.
// This is ok as long as perp markets are strictly isolated.
}
Ok(HealthCache { token_infos })

View File

@ -27,11 +27,14 @@ pub struct PerpMarket {
/// UI position is 1
pub base_lot_size: i64,
// TODO docs
// These weights apply to the base asset, the quote token is always assumed to be
// the health-reference token and have 1 for price and weights
pub maint_asset_weight: I80F48,
pub init_asset_weight: I80F48,
pub maint_liab_weight: I80F48,
pub init_liab_weight: I80F48,
// TODO docs
pub liquidation_fee: I80F48,
pub maker_fee: I80F48,
pub taker_fee: I80F48,
@ -61,6 +64,8 @@ pub struct PerpMarket {
/// Lookup indices
pub perp_market_index: PerpMarketIndex,
pub base_token_index: TokenIndex,
/// Cannot be chosen freely, must be the health-reference token, same for all PerpMarkets
pub quote_token_index: TokenIndex,
}