Adjust avg entry price / break even price computation (#304)
- Don't return result as I80F48, f64 is more honest - Allow access to avg entry price per base lot without division - Reset break even price when position changes sign - Fix occasionally-failing fast division test - Fix sign of break even price -- it can now be negative if the position has broken even.
This commit is contained in:
parent
f236a0521a
commit
502f0767a8
|
@ -226,9 +226,9 @@ mod tests {
|
|||
};
|
||||
let sign_ok = actual.is_negative() == expected.is_negative();
|
||||
|
||||
// either at least 51 bits of precision, or all but the last bit correct (last bit may round differently)
|
||||
// either at least 50 bits of precision, or all but the last bit correct (last bit may round differently)
|
||||
assert!(
|
||||
(precision_bits >= 51 && sign_ok) || (actual - expected).abs() <= I80F48::DELTA
|
||||
(precision_bits >= 50 && sign_ok) || (actual - expected).abs() <= I80F48::DELTA
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -169,16 +169,8 @@ pub fn perp_liq_base_position(
|
|||
|
||||
// Execute the transfer. This is essentially a forced trade and updates the
|
||||
// liqee and liqors entry and break even prices.
|
||||
liqee_perp_position.change_base_and_quote_positions(
|
||||
&mut perp_market,
|
||||
-base_transfer,
|
||||
-quote_transfer,
|
||||
);
|
||||
liqor_perp_position.change_base_and_quote_positions(
|
||||
&mut perp_market,
|
||||
base_transfer,
|
||||
quote_transfer,
|
||||
);
|
||||
liqee_perp_position.record_trade(&mut perp_market, -base_transfer, -quote_transfer);
|
||||
liqor_perp_position.record_trade(&mut perp_market, base_transfer, quote_transfer);
|
||||
|
||||
emit_perp_balances(
|
||||
ctx.accounts.group.key(),
|
||||
|
|
|
@ -1601,7 +1601,7 @@ mod tests {
|
|||
|
||||
let mut perp1 = mock_perp_market(group, oracle2.pubkey, 5.0, 9, 0.2, 0.1);
|
||||
let perpaccount = account.ensure_perp_position(9, 1).unwrap().0;
|
||||
perpaccount.change_base_and_quote_positions(perp1.data(), 3, -I80F48::from(310u16));
|
||||
perpaccount.record_trade(perp1.data(), 3, -I80F48::from(310u16));
|
||||
perpaccount.bids_base_lots = 7;
|
||||
perpaccount.asks_base_lots = 11;
|
||||
perpaccount.taker_base_lots = 1;
|
||||
|
@ -1795,7 +1795,7 @@ mod tests {
|
|||
|
||||
let mut perp1 = mock_perp_market(group, oracle2.pubkey, 5.0, 9, 0.2, 0.1);
|
||||
let perpaccount = account.ensure_perp_position(9, 1).unwrap().0;
|
||||
perpaccount.change_base_and_quote_positions(
|
||||
perpaccount.record_trade(
|
||||
perp1.data(),
|
||||
testcase.perp1.0,
|
||||
I80F48::from(testcase.perp1.1),
|
||||
|
@ -2291,7 +2291,7 @@ mod tests {
|
|||
let mut perp1 = mock_perp_market(group, oracle1.pubkey, 1.0, 9, 0.2, 0.1);
|
||||
perp1.data().long_funding = I80F48::from_num(10.1);
|
||||
let perpaccount = account.ensure_perp_position(9, 1).unwrap().0;
|
||||
perpaccount.change_base_and_quote_positions(perp1.data(), 10, I80F48::from(-110));
|
||||
perpaccount.record_trade(perp1.data(), 10, I80F48::from(-110));
|
||||
perpaccount.long_settled_funding = I80F48::from_num(10.0);
|
||||
|
||||
let oracle1_ai = oracle1.as_account_info();
|
||||
|
@ -2379,7 +2379,7 @@ mod tests {
|
|||
let mut perp1 = mock_perp_market(group, oracle1.pubkey, 1.0, 9, 0.2, 0.1);
|
||||
perp1.data().stable_price_model.stable_price = 0.5;
|
||||
let perpaccount = account3.ensure_perp_position(9, 1).unwrap().0;
|
||||
perpaccount.change_base_and_quote_positions(perp1.data(), 10, I80F48::from(-100));
|
||||
perpaccount.record_trade(perp1.data(), 10, I80F48::from(-100));
|
||||
|
||||
let oracle1_ai = oracle1.as_account_info();
|
||||
let ais = vec![
|
||||
|
|
|
@ -868,7 +868,7 @@ impl<
|
|||
cm!(perp_market.fees_accrued += fees);
|
||||
}
|
||||
let quote_change_native = cm!(quote - fees);
|
||||
pa.change_base_and_quote_positions(perp_market, base_change, quote_change_native);
|
||||
pa.record_trade(perp_market, base_change, quote_change_native);
|
||||
|
||||
cm!(pa.maker_volume += quote.abs().to_num::<u64>());
|
||||
|
||||
|
@ -901,7 +901,7 @@ impl<
|
|||
// fees are assessed at time of trade; no need to assess fees here
|
||||
let quote_change_native =
|
||||
cm!(I80F48::from(perp_market.quote_lot_size) * I80F48::from(quote_change));
|
||||
pa.change_base_and_quote_positions(perp_market, base_change, quote_change_native);
|
||||
pa.record_trade(perp_market, base_change, quote_change_native);
|
||||
|
||||
cm!(pa.taker_volume += quote_change_native.abs().to_num::<u64>());
|
||||
|
||||
|
|
|
@ -173,7 +173,7 @@ pub struct PerpPosition {
|
|||
quote_position_native: I80F48,
|
||||
|
||||
/// Tracks what the position is to calculate average entry & break even price
|
||||
pub quote_entry_native: i64,
|
||||
pub padding2: [u8; 8],
|
||||
pub quote_running_native: i64,
|
||||
|
||||
/// Already settled funding
|
||||
|
@ -208,10 +208,12 @@ pub struct PerpPosition {
|
|||
// Cumulative realized pnl in quote native units
|
||||
pub perp_spot_transfers: i64,
|
||||
|
||||
pub avg_entry_price_per_base_lot: f64,
|
||||
|
||||
#[derivative(Debug = "ignore")]
|
||||
pub reserved: [u8; 24],
|
||||
pub reserved: [u8; 16],
|
||||
}
|
||||
const_assert_eq!(size_of::<PerpPosition>(), 8 + 7 * 8 + 3 * 16 + 64);
|
||||
const_assert_eq!(size_of::<PerpPosition>(), 176);
|
||||
const_assert_eq!(size_of::<PerpPosition>() % 8, 0);
|
||||
|
||||
unsafe impl bytemuck::Pod for PerpPosition {}
|
||||
|
@ -223,7 +225,7 @@ impl Default for PerpPosition {
|
|||
market_index: PerpMarketIndex::MAX,
|
||||
base_position_lots: 0,
|
||||
quote_position_native: I80F48::ZERO,
|
||||
quote_entry_native: 0,
|
||||
padding2: Default::default(),
|
||||
quote_running_native: 0,
|
||||
bids_base_lots: 0,
|
||||
asks_base_lots: 0,
|
||||
|
@ -237,7 +239,8 @@ impl Default for PerpPosition {
|
|||
maker_volume: 0,
|
||||
taker_volume: 0,
|
||||
perp_spot_transfers: 0,
|
||||
reserved: [0; 24],
|
||||
avg_entry_price_per_base_lot: 0.0,
|
||||
reserved: [0; 16],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -325,37 +328,45 @@ impl PerpPosition {
|
|||
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);
|
||||
match is_increasing {
|
||||
true => {
|
||||
cm!(self.quote_entry_native += quote_change_native);
|
||||
}
|
||||
false => {
|
||||
let new_position = cm!(old_position + base_change);
|
||||
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
|
||||
} 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
|
||||
}
|
||||
}
|
||||
let new_position = cm!(old_position + base_change);
|
||||
|
||||
if new_position == 0 {
|
||||
self.avg_entry_price_per_base_lot = 0.0;
|
||||
self.quote_running_native = 0;
|
||||
return;
|
||||
} else if old_position.signum() != new_position.signum() {
|
||||
// If the base position changes sign, reset
|
||||
self.avg_entry_price_per_base_lot =
|
||||
((quote_change_native as f64) / (base_change as f64)).abs();
|
||||
self.quote_running_native =
|
||||
-((new_position as f64) * self.avg_entry_price_per_base_lot).round() as i64;
|
||||
return;
|
||||
}
|
||||
|
||||
// Track all quote changes as long as the base position sign stays the same
|
||||
cm!(self.quote_running_native += quote_change_native);
|
||||
|
||||
let is_increasing = old_position.signum() == base_change.signum();
|
||||
if is_increasing {
|
||||
let new_position_quote_value = (old_position.abs() as f64)
|
||||
* self.avg_entry_price_per_base_lot
|
||||
+ (quote_change_native.abs() as f64);
|
||||
self.avg_entry_price_per_base_lot =
|
||||
new_position_quote_value / (new_position.abs() as f64);
|
||||
}
|
||||
// The average entry price does not change when the position decreases while keeping sign.
|
||||
}
|
||||
|
||||
/// Change the base and quote positions as the result of a trade
|
||||
pub fn change_base_and_quote_positions(
|
||||
pub fn record_trade(
|
||||
&mut self,
|
||||
perp_market: &mut PerpMarket,
|
||||
base_change: i64,
|
||||
quote_change_native: I80F48,
|
||||
) {
|
||||
assert_eq!(perp_market.perp_market_index, self.market_index);
|
||||
self.update_entry_price(
|
||||
base_change,
|
||||
quote_change_native.round().checked_to_num().unwrap(),
|
||||
|
@ -376,24 +387,20 @@ impl PerpPosition {
|
|||
|| self.taker_quote_lots != 0
|
||||
}
|
||||
|
||||
/// Calculate the average entry price of the position
|
||||
pub fn avg_entry_price(&self, market: &PerpMarket) -> I80F48 {
|
||||
if self.base_position_lots == 0 {
|
||||
return I80F48::ZERO; // TODO: What should this actually return? Error? NaN?
|
||||
}
|
||||
cm!(I80F48::from(self.quote_entry_native)
|
||||
/ (I80F48::from(self.base_position_lots) * I80F48::from(market.base_lot_size)))
|
||||
.abs()
|
||||
/// Calculate the average entry price of the position, in native/native units
|
||||
pub fn avg_entry_price(&self, market: &PerpMarket) -> f64 {
|
||||
assert_eq!(self.market_index, market.perp_market_index);
|
||||
self.avg_entry_price_per_base_lot / (market.base_lot_size as f64)
|
||||
}
|
||||
|
||||
/// Calculate the break even price of the position
|
||||
pub fn break_even_price(&self, market: &PerpMarket) -> I80F48 {
|
||||
/// Calculate the break even price of the position, in native/native units
|
||||
pub fn break_even_price(&self, market: &PerpMarket) -> f64 {
|
||||
if self.base_position_lots == 0 {
|
||||
return I80F48::ZERO; // TODO: What should this actually return? Error? NaN?
|
||||
return 0.0;
|
||||
}
|
||||
cm!(I80F48::from(self.quote_running_native)
|
||||
/ (I80F48::from(self.base_position_lots) * I80F48::from(market.base_lot_size)))
|
||||
.abs()
|
||||
assert_eq!(self.market_index, market.perp_market_index);
|
||||
-(self.quote_running_native as f64)
|
||||
/ (cm!(self.base_position_lots * market.base_lot_size) as f64)
|
||||
}
|
||||
|
||||
/// Calculate the PnL of the position for a given price
|
||||
|
@ -460,143 +467,137 @@ mod tests {
|
|||
|
||||
use super::PerpPosition;
|
||||
|
||||
fn create_perp_position(
|
||||
market: &PerpMarket,
|
||||
base_pos: i64,
|
||||
quote_pos: i64,
|
||||
entry_pos: i64,
|
||||
) -> PerpPosition {
|
||||
fn create_perp_position(market: &PerpMarket, base_pos: i64, quote_pos: i64) -> PerpPosition {
|
||||
let mut pos = PerpPosition::default();
|
||||
pos.market_index = market.perp_market_index;
|
||||
pos.base_position_lots = base_pos;
|
||||
pos.quote_position_native = I80F48::from(quote_pos);
|
||||
pos.quote_entry_native = entry_pos;
|
||||
pos.quote_running_native = quote_pos;
|
||||
pos.avg_entry_price_per_base_lot = ((quote_pos as f64) / (base_pos as f64)).abs();
|
||||
pos
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quote_entry_long_increasing_from_zero() {
|
||||
let mut market = PerpMarket::default_for_tests();
|
||||
let mut pos = create_perp_position(&market, 0, 0, 0);
|
||||
let mut pos = create_perp_position(&market, 0, 0);
|
||||
// Go long 10 @ 10
|
||||
pos.change_base_and_quote_positions(&mut market, 10, I80F48::from(-100));
|
||||
assert_eq!(pos.quote_entry_native, -100);
|
||||
pos.record_trade(&mut market, 10, I80F48::from(-100));
|
||||
assert_eq!(pos.quote_running_native, -100);
|
||||
assert_eq!(pos.avg_entry_price(&market), I80F48::from(10));
|
||||
assert_eq!(pos.avg_entry_price(&market), 10.0);
|
||||
assert_eq!(pos.break_even_price(&market), 10.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quote_entry_short_increasing_from_zero() {
|
||||
let mut market = PerpMarket::default_for_tests();
|
||||
let mut pos = create_perp_position(&market, 0, 0, 0);
|
||||
let mut pos = create_perp_position(&market, 0, 0);
|
||||
// Go short 10 @ 10
|
||||
pos.change_base_and_quote_positions(&mut market, -10, I80F48::from(100));
|
||||
assert_eq!(pos.quote_entry_native, 100);
|
||||
pos.record_trade(&mut market, -10, I80F48::from(100));
|
||||
assert_eq!(pos.quote_running_native, 100);
|
||||
assert_eq!(pos.avg_entry_price(&market), I80F48::from(10));
|
||||
assert_eq!(pos.avg_entry_price(&market), 10.0);
|
||||
assert_eq!(pos.break_even_price(&market), 10.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quote_entry_long_increasing_from_long() {
|
||||
let mut market = PerpMarket::default_for_tests();
|
||||
let mut pos = create_perp_position(&market, 10, -100, -100);
|
||||
let mut pos = create_perp_position(&market, 10, -100);
|
||||
// Go long 10 @ 30
|
||||
pos.change_base_and_quote_positions(&mut market, 10, I80F48::from(-300));
|
||||
assert_eq!(pos.quote_entry_native, -400);
|
||||
pos.record_trade(&mut market, 10, I80F48::from(-300));
|
||||
assert_eq!(pos.quote_running_native, -400);
|
||||
assert_eq!(pos.avg_entry_price(&market), I80F48::from(20));
|
||||
assert_eq!(pos.avg_entry_price(&market), 20.0);
|
||||
assert_eq!(pos.break_even_price(&market), 20.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quote_entry_short_increasing_from_short() {
|
||||
let mut market = PerpMarket::default_for_tests();
|
||||
let mut pos = create_perp_position(&market, -10, 100, 100);
|
||||
let mut pos = create_perp_position(&market, -10, 100);
|
||||
// Go short 10 @ 10
|
||||
pos.change_base_and_quote_positions(&mut market, -10, I80F48::from(300));
|
||||
assert_eq!(pos.quote_entry_native, 400);
|
||||
pos.record_trade(&mut market, -10, I80F48::from(300));
|
||||
assert_eq!(pos.quote_running_native, 400);
|
||||
assert_eq!(pos.avg_entry_price(&market), I80F48::from(20));
|
||||
assert_eq!(pos.avg_entry_price(&market), 20.0);
|
||||
assert_eq!(pos.break_even_price(&market), 20.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quote_entry_long_decreasing_from_short() {
|
||||
let mut market = PerpMarket::default_for_tests();
|
||||
let mut pos = create_perp_position(&market, -10, 100, 100);
|
||||
let mut pos = create_perp_position(&market, -10, 100);
|
||||
// Go long 5 @ 50
|
||||
pos.change_base_and_quote_positions(&mut market, 5, I80F48::from(-250));
|
||||
assert_eq!(pos.quote_entry_native, 50);
|
||||
pos.record_trade(&mut market, 5, I80F48::from(-250));
|
||||
assert_eq!(pos.quote_running_native, -150);
|
||||
assert_eq!(pos.avg_entry_price(&market), I80F48::from(10)); // Entry price remains the same when decreasing
|
||||
assert_eq!(pos.avg_entry_price(&market), 10.0); // Entry price remains the same when decreasing
|
||||
assert_eq!(pos.break_even_price(&market), -30.0); // Already broke even
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quote_entry_short_decreasing_from_long() {
|
||||
let mut market = PerpMarket::default_for_tests();
|
||||
let mut pos = create_perp_position(&market, 10, -100, -100);
|
||||
let mut pos = create_perp_position(&market, 10, -100);
|
||||
// Go short 5 @ 50
|
||||
pos.change_base_and_quote_positions(&mut market, -5, I80F48::from(250));
|
||||
assert_eq!(pos.quote_entry_native, -50);
|
||||
pos.record_trade(&mut market, -5, I80F48::from(250));
|
||||
assert_eq!(pos.quote_running_native, 150);
|
||||
assert_eq!(pos.avg_entry_price(&market), I80F48::from(10)); // Entry price remains the same when decreasing
|
||||
assert_eq!(pos.avg_entry_price(&market), 10.0); // Entry price remains the same when decreasing
|
||||
assert_eq!(pos.break_even_price(&market), -30.0); // Already broke even
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quote_entry_long_close_with_short() {
|
||||
let mut market = PerpMarket::default_for_tests();
|
||||
let mut pos = create_perp_position(&market, 10, -100, -100);
|
||||
let mut pos = create_perp_position(&market, 10, -100);
|
||||
// Go short 10 @ 50
|
||||
pos.change_base_and_quote_positions(&mut market, -10, I80F48::from(250));
|
||||
assert_eq!(pos.quote_entry_native, 0);
|
||||
assert_eq!(pos.quote_running_native, 150);
|
||||
assert_eq!(pos.avg_entry_price(&market), I80F48::from(0)); // Entry price zero when no position
|
||||
pos.record_trade(&mut market, -10, I80F48::from(250));
|
||||
assert_eq!(pos.quote_running_native, 0);
|
||||
assert_eq!(pos.avg_entry_price(&market), 0.0); // Entry price zero when no position
|
||||
assert_eq!(pos.break_even_price(&market), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quote_entry_short_close_with_long() {
|
||||
let mut market = PerpMarket::default_for_tests();
|
||||
let mut pos = create_perp_position(&market, -10, 100, 100);
|
||||
let mut pos = create_perp_position(&market, -10, 100);
|
||||
// Go long 10 @ 50
|
||||
pos.change_base_and_quote_positions(&mut market, 10, I80F48::from(-250));
|
||||
assert_eq!(pos.quote_entry_native, 0);
|
||||
assert_eq!(pos.quote_running_native, -150);
|
||||
assert_eq!(pos.avg_entry_price(&market), I80F48::from(0)); // Entry price zero when no position
|
||||
pos.record_trade(&mut market, 10, I80F48::from(-250));
|
||||
assert_eq!(pos.quote_running_native, 0);
|
||||
assert_eq!(pos.avg_entry_price(&market), 0.0); // Entry price zero when no position
|
||||
assert_eq!(pos.break_even_price(&market), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quote_entry_long_close_short_with_overflow() {
|
||||
let mut market = PerpMarket::default_for_tests();
|
||||
let mut pos = create_perp_position(&market, 10, -100, -100);
|
||||
let mut pos = create_perp_position(&market, 10, -100);
|
||||
// Go short 15 @ 20
|
||||
pos.change_base_and_quote_positions(&mut market, -15, I80F48::from(300));
|
||||
assert_eq!(pos.quote_entry_native, 100);
|
||||
assert_eq!(pos.quote_running_native, 200);
|
||||
assert_eq!(pos.avg_entry_price(&market), I80F48::from(20)); // Entry price zero when no position
|
||||
pos.record_trade(&mut market, -15, I80F48::from(300));
|
||||
assert_eq!(pos.quote_running_native, 100);
|
||||
assert_eq!(pos.avg_entry_price(&market), 20.0);
|
||||
assert_eq!(pos.break_even_price(&market), 20.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quote_entry_short_close_long_with_overflow() {
|
||||
let mut market = PerpMarket::default_for_tests();
|
||||
let mut pos = create_perp_position(&market, -10, 100, 100);
|
||||
let mut pos = create_perp_position(&market, -10, 100);
|
||||
// Go short 15 @ 20
|
||||
pos.change_base_and_quote_positions(&mut market, 15, I80F48::from(-300));
|
||||
assert_eq!(pos.quote_entry_native, -100);
|
||||
assert_eq!(pos.quote_running_native, -200);
|
||||
assert_eq!(pos.avg_entry_price(&market), I80F48::from(20)); // Entry price zero when no position
|
||||
pos.record_trade(&mut market, 15, I80F48::from(-300));
|
||||
assert_eq!(pos.quote_running_native, -100);
|
||||
assert_eq!(pos.avg_entry_price(&market), 20.0);
|
||||
assert_eq!(pos.break_even_price(&market), 20.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quote_entry_break_even_price() {
|
||||
let mut market = PerpMarket::default_for_tests();
|
||||
let mut pos = create_perp_position(&market, 0, 0, 0);
|
||||
let mut pos = create_perp_position(&market, 0, 0);
|
||||
// Buy 11 @ 10,000
|
||||
pos.change_base_and_quote_positions(&mut market, 11, I80F48::from(-11 * 10_000));
|
||||
pos.record_trade(&mut market, 11, I80F48::from(-11 * 10_000));
|
||||
// Sell 1 @ 12,000
|
||||
pos.change_base_and_quote_positions(&mut market, -1, I80F48::from(12_000));
|
||||
assert_eq!(pos.quote_entry_native, -10 * 10_000);
|
||||
pos.record_trade(&mut market, -1, I80F48::from(12_000));
|
||||
assert_eq!(pos.quote_running_native, -98_000);
|
||||
assert_eq!(pos.base_position_lots, 10);
|
||||
assert_eq!(pos.break_even_price(&market), I80F48::from(9_800)); // We made 2k on the trade, so we can sell our contract up to a loss of 200 each
|
||||
assert_eq!(pos.break_even_price(&market), 9_800.0); // We made 2k on the trade, so we can sell our contract up to a loss of 200 each
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -604,51 +605,52 @@ mod tests {
|
|||
let mut market = PerpMarket::default_for_tests();
|
||||
market.base_lot_size = 10;
|
||||
|
||||
let mut pos = create_perp_position(&market, 0, 0, 0);
|
||||
let mut pos = create_perp_position(&market, 0, 0);
|
||||
// Buy 110 @ 10,000
|
||||
pos.change_base_and_quote_positions(&mut market, 11, I80F48::from(-110 * 10_000));
|
||||
pos.record_trade(&mut market, 11, I80F48::from(-11 * 10 * 10_000));
|
||||
// Sell 10 @ 12,000
|
||||
pos.change_base_and_quote_positions(&mut market, -1, I80F48::from(120_000));
|
||||
assert_eq!(pos.quote_entry_native, -100 * 10_000);
|
||||
pos.record_trade(&mut market, -1, I80F48::from(1 * 10 * 12_000));
|
||||
assert_eq!(pos.quote_running_native, -980_000);
|
||||
assert_eq!(pos.base_position_lots, 10);
|
||||
assert_eq!(pos.avg_entry_price(&market), I80F48::from(10_000));
|
||||
assert_eq!(pos.break_even_price(&market), I80F48::from(9_800));
|
||||
assert_eq!(pos.avg_entry_price_per_base_lot, 100_000.0);
|
||||
assert_eq!(pos.avg_entry_price(&market), 10_000.0);
|
||||
assert_eq!(pos.break_even_price(&market), 9_800.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quote_entry_multiple_and_reversed_changes_return_entry_to_zero() {
|
||||
fn test_perp_entry_multiple_random_long() {
|
||||
let mut market = PerpMarket::default_for_tests();
|
||||
let mut pos = create_perp_position(&market, 0, 0, 0);
|
||||
let mut pos = create_perp_position(&market, 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 qty: i64 = rng.gen_range(1..=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
|
||||
let mut total_qty = 0;
|
||||
let mut total_quote = 0;
|
||||
trades.iter().for_each(|[qty, quote]| {
|
||||
pos.change_base_and_quote_positions(&mut market, *qty, I80F48::from(*quote));
|
||||
pos.record_trade(&mut market, *qty, I80F48::from(*quote));
|
||||
total_qty += qty.abs();
|
||||
total_quote += quote.abs();
|
||||
let entry_actual = pos.avg_entry_price(&market);
|
||||
let entry_expected = total_quote as f64 / total_qty as f64;
|
||||
assert!(((entry_actual - entry_expected) / entry_expected).abs() < 10.0 * f64::EPSILON);
|
||||
});
|
||||
// base_position should be sum of all base quantities
|
||||
assert_eq!(
|
||||
pos.base_position_lots,
|
||||
trades.iter().map(|[qty, _]| qty).sum::<i64>()
|
||||
);
|
||||
assert_eq!(pos.base_position_lots, total_qty);
|
||||
// Reverse out all the trades
|
||||
trades.iter().for_each(|[qty, quote]| {
|
||||
pos.change_base_and_quote_positions(&mut market, -*qty, I80F48::from(-*quote));
|
||||
pos.record_trade(&mut market, -*qty, I80F48::from(-*quote));
|
||||
});
|
||||
// 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);
|
||||
// running quote should be 0
|
||||
assert_eq!(pos.quote_running_native, 0);
|
||||
assert_eq!(pos.avg_entry_price_per_base_lot, 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -656,13 +658,13 @@ mod tests {
|
|||
let mut market = PerpMarket::default_for_tests();
|
||||
market.base_lot_size = 10;
|
||||
|
||||
let long_pos = create_perp_position(&market, 50, -5000, -5000);
|
||||
let long_pos = create_perp_position(&market, 50, -5000);
|
||||
let pnl = long_pos.pnl_for_price(&market, I80F48::from(11)).unwrap();
|
||||
assert_eq!(pnl, I80F48::from(50 * 10 * 1), "long profitable");
|
||||
let pnl = long_pos.pnl_for_price(&market, I80F48::from(9)).unwrap();
|
||||
assert_eq!(pnl, I80F48::from(50 * 10 * -1), "long unprofitable");
|
||||
|
||||
let short_pos = create_perp_position(&market, -50, 5000, 5000);
|
||||
let short_pos = create_perp_position(&market, -50, 5000);
|
||||
let pnl = short_pos.pnl_for_price(&market, I80F48::from(11)).unwrap();
|
||||
assert_eq!(pnl, I80F48::from(50 * 10 * -1), "short unprofitable");
|
||||
let pnl = short_pos.pnl_for_price(&market, I80F48::from(9)).unwrap();
|
||||
|
|
Loading…
Reference in New Issue