Leverage & Balance Sheet helpers (#637)
This commit is contained in:
parent
6dcd5c925c
commit
e4162a8281
|
@ -686,6 +686,66 @@ impl HealthCache {
|
||||||
(total_assets, total_liabs)
|
(total_assets, total_liabs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Computes the account assets and liabilities marked to market.
|
||||||
|
///
|
||||||
|
/// Contrary to health_assets_and_liabs, there's no health weighing or adjustment
|
||||||
|
/// for stable prices. It uses oracle prices directly.
|
||||||
|
///
|
||||||
|
/// Returns (assets, liabilities)
|
||||||
|
pub fn assets_and_liabs(&self) -> (I80F48, I80F48) {
|
||||||
|
let mut assets = I80F48::ZERO;
|
||||||
|
let mut liabs = I80F48::ZERO;
|
||||||
|
|
||||||
|
for token_info in self.token_infos.iter() {
|
||||||
|
if token_info.balance_spot.is_negative() {
|
||||||
|
liabs -= token_info.balance_spot * token_info.prices.oracle;
|
||||||
|
} else {
|
||||||
|
assets += token_info.balance_spot * token_info.prices.oracle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for serum_info in self.serum3_infos.iter() {
|
||||||
|
let quote = &self.token_infos[serum_info.quote_info_index];
|
||||||
|
let base = &self.token_infos[serum_info.base_info_index];
|
||||||
|
assets += serum_info.reserved_base * base.prices.oracle;
|
||||||
|
assets += serum_info.reserved_quote * quote.prices.oracle;
|
||||||
|
}
|
||||||
|
|
||||||
|
for perp_info in self.perp_infos.iter() {
|
||||||
|
let quote_price = self.token_infos[perp_info.settle_token_index as usize]
|
||||||
|
.prices
|
||||||
|
.oracle;
|
||||||
|
let quote_position_value = perp_info.quote * quote_price;
|
||||||
|
if perp_info.quote.is_negative() {
|
||||||
|
liabs -= quote_position_value;
|
||||||
|
} else {
|
||||||
|
assets += quote_position_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
let base_position_value = I80F48::from(perp_info.base_lots * perp_info.base_lot_size)
|
||||||
|
* perp_info.base_prices.oracle
|
||||||
|
* quote_price;
|
||||||
|
if base_position_value.is_negative() {
|
||||||
|
liabs -= base_position_value;
|
||||||
|
} else {
|
||||||
|
assets += base_position_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (assets, liabs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the account leverage as ratio of liabs / (assets - liabs)
|
||||||
|
///
|
||||||
|
/// The goal of this function is to provide a quick overview over the accounts balance sheet.
|
||||||
|
/// It's not actually used to make any margin decisions internally and doesn't account for
|
||||||
|
/// open orders or stable / oracle price differences. Use health_ratio to make risk decisions.
|
||||||
|
pub fn leverage(&self) -> I80F48 {
|
||||||
|
let (assets, liabs) = self.assets_and_liabs();
|
||||||
|
let equity = assets - liabs;
|
||||||
|
liabs / equity.max(I80F48::from_num(0.001))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn token_info(&self, token_index: TokenIndex) -> Result<&TokenInfo> {
|
pub fn token_info(&self, token_index: TokenIndex) -> Result<&TokenInfo> {
|
||||||
Ok(&self.token_infos[self.token_info_index(token_index)?])
|
Ok(&self.token_infos[self.token_info_index(token_index)?])
|
||||||
}
|
}
|
||||||
|
|
|
@ -610,6 +610,16 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn leverage_eq(h: &HealthCache, b: f64) -> bool {
|
||||||
|
let a = h.leverage();
|
||||||
|
if (a - I80F48::from_num(b)).abs() < 0.001 {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
println!("leverage is {}, but expected {}", a, b);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn default_token_info(x: f64, price: f64) -> TokenInfo {
|
fn default_token_info(x: f64, price: f64) -> TokenInfo {
|
||||||
TokenInfo {
|
TokenInfo {
|
||||||
token_index: 0,
|
token_index: 0,
|
||||||
|
@ -624,7 +634,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_perp_info(x: f64) -> PerpInfo {
|
fn default_perp_info(x: f64, price: f64) -> PerpInfo {
|
||||||
PerpInfo {
|
PerpInfo {
|
||||||
perp_market_index: 0,
|
perp_market_index: 0,
|
||||||
settle_token_index: 0,
|
settle_token_index: 0,
|
||||||
|
@ -639,7 +649,7 @@ mod tests {
|
||||||
bids_base_lots: 0,
|
bids_base_lots: 0,
|
||||||
asks_base_lots: 0,
|
asks_base_lots: 0,
|
||||||
quote: I80F48::ZERO,
|
quote: I80F48::ZERO,
|
||||||
base_prices: Prices::new_single_price(I80F48::from_num(2.0)),
|
base_prices: Prices::new_single_price(I80F48::from_num(price)),
|
||||||
has_open_orders: false,
|
has_open_orders: false,
|
||||||
has_open_fills: false,
|
has_open_fills: false,
|
||||||
}
|
}
|
||||||
|
@ -1008,7 +1018,7 @@ mod tests {
|
||||||
health_cache.perp_infos.push(PerpInfo {
|
health_cache.perp_infos.push(PerpInfo {
|
||||||
perp_market_index: 0,
|
perp_market_index: 0,
|
||||||
settle_token_index: 1,
|
settle_token_index: 1,
|
||||||
..default_perp_info(0.3)
|
..default_perp_info(0.3, 2.0)
|
||||||
});
|
});
|
||||||
adjust_by_usdc(&mut health_cache, 0, 60.0);
|
adjust_by_usdc(&mut health_cache, 0, 60.0);
|
||||||
|
|
||||||
|
@ -1048,7 +1058,7 @@ mod tests {
|
||||||
perp_market_index: 0,
|
perp_market_index: 0,
|
||||||
settle_token_index: 1,
|
settle_token_index: 1,
|
||||||
base_lot_size,
|
base_lot_size,
|
||||||
..default_perp_info(0.3)
|
..default_perp_info(0.3, 2.0)
|
||||||
}],
|
}],
|
||||||
being_liquidated: false,
|
being_liquidated: false,
|
||||||
};
|
};
|
||||||
|
@ -1425,7 +1435,7 @@ mod tests {
|
||||||
perp_infos: vec![PerpInfo {
|
perp_infos: vec![PerpInfo {
|
||||||
perp_market_index: 0,
|
perp_market_index: 0,
|
||||||
settle_token_index: 0,
|
settle_token_index: 0,
|
||||||
..default_perp_info(0.3)
|
..default_perp_info(0.3, 2.0)
|
||||||
}],
|
}],
|
||||||
being_liquidated: false,
|
being_liquidated: false,
|
||||||
};
|
};
|
||||||
|
@ -1458,4 +1468,100 @@ mod tests {
|
||||||
assert!((assets.to_num::<f64>() - 2.0 * (10.0 * 1.2 + 2.0 * 0.8)) < 0.01);
|
assert!((assets.to_num::<f64>() - 2.0 * (10.0 * 1.2 + 2.0 * 0.8)) < 0.01);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_leverage() {
|
||||||
|
// only deposits
|
||||||
|
let health_cache = HealthCache {
|
||||||
|
token_infos: vec![
|
||||||
|
TokenInfo {
|
||||||
|
token_index: 0,
|
||||||
|
balance_spot: I80F48::ONE,
|
||||||
|
..default_token_info(0.0, 1.0)
|
||||||
|
},
|
||||||
|
TokenInfo {
|
||||||
|
token_index: 1,
|
||||||
|
..default_token_info(0.2, 2.0)
|
||||||
|
},
|
||||||
|
],
|
||||||
|
serum3_infos: vec![],
|
||||||
|
perp_infos: vec![],
|
||||||
|
being_liquidated: false,
|
||||||
|
};
|
||||||
|
assert!(leverage_eq(&health_cache, 0.0));
|
||||||
|
|
||||||
|
// deposits and borrows: assets = 10, equity = 1
|
||||||
|
let health_cache = HealthCache {
|
||||||
|
token_infos: vec![
|
||||||
|
TokenInfo {
|
||||||
|
token_index: 0,
|
||||||
|
balance_spot: I80F48::from_num(-9),
|
||||||
|
..default_token_info(0.0, 1.0)
|
||||||
|
},
|
||||||
|
TokenInfo {
|
||||||
|
token_index: 1,
|
||||||
|
balance_spot: I80F48::from_num(5),
|
||||||
|
..default_token_info(0.2, 2.0)
|
||||||
|
},
|
||||||
|
],
|
||||||
|
serum3_infos: vec![],
|
||||||
|
perp_infos: vec![],
|
||||||
|
being_liquidated: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(leverage_eq(&health_cache, 9.0));
|
||||||
|
|
||||||
|
// perp trade: assets = 1 + 9.9, equity = 1
|
||||||
|
let health_cache = HealthCache {
|
||||||
|
token_infos: vec![
|
||||||
|
TokenInfo {
|
||||||
|
token_index: 0,
|
||||||
|
balance_spot: I80F48::ONE,
|
||||||
|
..default_token_info(0.0, 1.0)
|
||||||
|
},
|
||||||
|
TokenInfo {
|
||||||
|
token_index: 1,
|
||||||
|
..default_token_info(0.2, 2.0)
|
||||||
|
},
|
||||||
|
],
|
||||||
|
serum3_infos: vec![],
|
||||||
|
perp_infos: vec![PerpInfo {
|
||||||
|
perp_market_index: 0,
|
||||||
|
base_lot_size: 3,
|
||||||
|
base_lots: -3,
|
||||||
|
quote: I80F48::from_num(9.9),
|
||||||
|
..default_perp_info(0.1, 1.1)
|
||||||
|
}],
|
||||||
|
being_liquidated: false,
|
||||||
|
};
|
||||||
|
assert!(leverage_eq(&health_cache, 9.9));
|
||||||
|
|
||||||
|
// open orders: assets = 3, equity = 1
|
||||||
|
let health_cache = HealthCache {
|
||||||
|
token_infos: vec![
|
||||||
|
TokenInfo {
|
||||||
|
token_index: 0,
|
||||||
|
balance_spot: I80F48::ONE,
|
||||||
|
..default_token_info(0.0, 1.0)
|
||||||
|
},
|
||||||
|
TokenInfo {
|
||||||
|
token_index: 1,
|
||||||
|
balance_spot: I80F48::from_num(-1),
|
||||||
|
..default_token_info(0.2, 2.0)
|
||||||
|
},
|
||||||
|
],
|
||||||
|
serum3_infos: vec![Serum3Info {
|
||||||
|
reserved_base: I80F48::ONE,
|
||||||
|
reserved_quote: I80F48::ZERO,
|
||||||
|
base_info_index: 1,
|
||||||
|
quote_info_index: 0,
|
||||||
|
market_index: 0,
|
||||||
|
has_zero_funds: true,
|
||||||
|
}],
|
||||||
|
perp_infos: vec![],
|
||||||
|
being_liquidated: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(leverage_eq(&health_cache, 2.0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue