defituna_client/implementation/
tuna_position.rs1use crate::accounts::*;
2use crate::consts::HUNDRED_PERCENT;
3use crate::math::fixed::Rounding;
4use crate::math::orca::liquidity::get_amounts_for_liquidity;
5use crate::math::{sqrt_price_x64_to_price_x64, Fixed128};
6use crate::types::*;
7use crate::TunaError as ErrorCode;
8use fixed::types::U64F64;
9use orca_whirlpools_core::{tick_index_to_sqrt_price, MAX_TICK_INDEX, MIN_TICK_INDEX};
10use std::fmt;
11
12impl TunaPosition {
13 pub fn get_total_balance(&self, sqrt_price: u128) -> Result<(u64, u64), ErrorCode> {
15 let lower_sqrt_price_x64 = tick_index_to_sqrt_price(self.tick_lower_index);
16 let upper_sqrt_price_x64 = tick_index_to_sqrt_price(self.tick_upper_index);
17 get_amounts_for_liquidity(sqrt_price, lower_sqrt_price_x64, upper_sqrt_price_x64, self.liquidity)
18 }
19
20 pub fn compute_total_and_debt(&self, sqrt_price: u128, vault_a: &Vault, vault_b: &Vault) -> Result<(u64, u64), ErrorCode> {
22 let (mut total_a, mut total_b) = self.get_total_balance(sqrt_price)?;
23
24 total_a = total_a.checked_add(self.leftovers_a).ok_or(ErrorCode::MathOverflow)?;
26 total_b = total_b.checked_add(self.leftovers_b).ok_or(ErrorCode::MathOverflow)?;
27
28 let price = sqrt_price_x64_to_price_x64(sqrt_price)?;
29
30 let total = U64F64::from(total_a)
31 .checked_mul(price)
32 .ok_or(ErrorCode::MathOverflow)?
33 .to_num::<u64>()
34 .checked_add(total_b)
35 .ok_or(ErrorCode::MathOverflow)?;
36
37 let debt_a = vault_a.calculate_borrowed_funds(self.loan_shares_a, Rounding::Up)?;
38 let debt_b = vault_b.calculate_borrowed_funds(self.loan_shares_b, Rounding::Up)?;
39
40 let debt = U64F64::from(debt_a)
41 .checked_mul(price)
42 .ok_or(ErrorCode::MathOverflow)?
43 .to_num::<u64>()
44 .checked_add(debt_b)
45 .ok_or(ErrorCode::MathOverflow)?;
46
47 Ok((total, debt))
48 }
49
50 pub fn is_healthy(&self, sqrt_price: u128, market: &Market, vault_a: &Vault, vault_b: &Vault) -> Result<(bool, u32), ErrorCode> {
52 if (self.loan_shares_a == 0 && self.loan_shares_b == 0) || self.liquidity == 0 {
53 return Ok((true, 0));
54 }
55
56 if vault_a.mint != self.mint_a || vault_b.mint != self.mint_b {
57 return Err(ErrorCode::InvalidInstructionArguments.into());
58 }
59
60 let (total, debt) = self.compute_total_and_debt(sqrt_price, vault_a, vault_b)?;
61
62 let healthy = total == 0 || debt <= (total as u128 * market.liquidation_threshold as u128 / HUNDRED_PERCENT as u128) as u64;
64 let ratio = if total == 0 {
65 0
66 } else {
67 (debt as u128 * HUNDRED_PERCENT as u128 / total as u128) as u32
68 };
69 Ok((healthy, ratio))
70 }
71
72 pub fn is_liquidated(&self) -> bool {
73 self.state != TunaPositionState::Normal
74 }
75
76 pub fn is_limit_order_reached(&self, sqrt_price: u128) -> bool {
77 if self.version < 4 {
79 return false;
80 }
81
82 if self.tick_stop_loss_index >= MIN_TICK_INDEX {
83 let stop_loss_sqrt_price = tick_index_to_sqrt_price(self.tick_stop_loss_index);
84 if sqrt_price <= stop_loss_sqrt_price {
85 return true;
86 }
87 }
88
89 if self.tick_take_profit_index <= MAX_TICK_INDEX {
90 let take_profit_sqrt_price = tick_index_to_sqrt_price(self.tick_take_profit_index);
91 if sqrt_price >= take_profit_sqrt_price {
92 return true;
93 }
94 }
95
96 false
97 }
98
99 pub fn compute_leverage(&self, sqrt_price: u128, vault_a: &Vault, vault_b: &Vault) -> Result<Fixed128, ErrorCode> {
101 let (total, debt) = self.compute_total_and_debt(sqrt_price, vault_a, vault_b)?;
102
103 if total == 0 {
105 return Ok(Fixed128::ONE);
106 }
107
108 if debt >= total {
109 return Err(ErrorCode::LeverageIsOutOfRange.into());
110 }
111
112 let leverage = Fixed128::from_num(total) / Fixed128::from_num(total - debt);
113 Ok(leverage)
114 }
115
116 pub fn get_pool_key(&self) -> String {
117 self.pool.to_string()
118 }
119}
120
121impl fmt::Display for TunaPosition {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 write!(
124 f,
125 "L={}; shares=[{}; {}]; rng=[{}; {}]; sl/tp=[{}; {}]; pool={}",
126 self.liquidity,
127 self.loan_shares_a,
128 self.loan_shares_b,
129 self.tick_lower_index,
130 self.tick_upper_index,
131 if self.tick_stop_loss_index == i32::MIN {
132 "--".to_string()
133 } else {
134 self.tick_stop_loss_index.to_string()
135 },
136 if self.tick_take_profit_index == i32::MAX {
137 "--".to_string()
138 } else {
139 self.tick_take_profit_index.to_string()
140 },
141 self.pool.to_string()
142 )
143 }
144}