From 2bc79a798f8bb55c6daac084686d16d1e69dcc54 Mon Sep 17 00:00:00 2001 From: Jarry Xiao Date: Thu, 20 Jun 2024 20:06:12 -0400 Subject: [PATCH 1/5] fix simulation with minimal possible code --- rust/crates/phoenix-sdk/src/ladder_utils.rs | 102 ++++++++++++++++---- rust/crates/phoenix-sdk/src/sdk_client.rs | 6 +- 2 files changed, 88 insertions(+), 20 deletions(-) diff --git a/rust/crates/phoenix-sdk/src/ladder_utils.rs b/rust/crates/phoenix-sdk/src/ladder_utils.rs index 2c5d003..d7bd83b 100644 --- a/rust/crates/phoenix-sdk/src/ladder_utils.rs +++ b/rust/crates/phoenix-sdk/src/ladder_utils.rs @@ -1,4 +1,13 @@ -use phoenix::state::{markets::Ladder, Side}; +use std::ops::Deref; + +use phoenix::{ + quantities::WrapperU64, + state::{ + markets::{FIFOOrderId, FIFORestingOrder, Ladder, Market}, + OrderPacket, Side, + }, +}; +use solana_sdk::pubkey::Pubkey; #[derive(Debug, Clone)] pub struct SimulationSummaryInLots { @@ -6,15 +15,57 @@ pub struct SimulationSummaryInLots { pub quote_lots_filled: u64, } +impl Deref for LadderWithAdjustment { + type Target = Ladder; + fn deref(&self) -> &Self::Target { + &self.ladder + } +} + +pub struct LadderWithAdjustment { + pub ladder: Ladder, + pub tick_size_in_quote_lots_per_base_unit: u64, + pub base_lots_per_base_unit: u64, +} + +impl LadderWithAdjustment { + pub fn from_market( + market: &dyn Market, + ) -> Self { + Self { + ladder: market.get_ladder(u64::MAX), + tick_size_in_quote_lots_per_base_unit: market.get_tick_size().as_u64(), + base_lots_per_base_unit: market.get_base_lots_per_base_unit().as_u64(), + } + } + + pub fn from_market_with_expiration( + market: &dyn Market, + last_valid_slot: Option, + last_valid_unix_timestamp_in_seconds: Option, + ) -> Self { + Self { + ladder: market.get_ladder_with_expiration( + u64::MAX, + last_valid_slot, + last_valid_unix_timestamp_in_seconds, + ), + tick_size_in_quote_lots_per_base_unit: market.get_tick_size().as_u64(), + base_lots_per_base_unit: market.get_base_lots_per_base_unit().as_u64(), + } + } +} + pub trait MarketSimulator { fn sell_quote(&self, num_lots_quote: u64) -> SimulationSummaryInLots; fn sell_base(&self, num_lots_base: u64) -> SimulationSummaryInLots; fn simulate_market_sell(&self, side: Side, size_in_lots: u64) -> SimulationSummaryInLots; } -impl MarketSimulator for Ladder { +impl MarketSimulator for LadderWithAdjustment { fn sell_quote(&self, num_lots_quote: u64) -> SimulationSummaryInLots { - let mut remaining_quote_lots = num_lots_quote; + let adjusted_quote_lots = num_lots_quote * self.base_lots_per_base_unit; + let mut remaining_quote_lots = adjusted_quote_lots; let mut base_lots = 0; for ask in self.asks.iter() { @@ -22,13 +73,16 @@ impl MarketSimulator for Ladder { break; } - let max_base_lots_you_can_buy = remaining_quote_lots / ask.price_in_ticks; + let max_base_lots_you_can_buy = remaining_quote_lots + / (ask.price_in_ticks * self.tick_size_in_quote_lots_per_base_unit); let amount_lots_to_buy = max_base_lots_you_can_buy.min(ask.size_in_base_lots); base_lots += amount_lots_to_buy; - remaining_quote_lots -= amount_lots_to_buy * ask.price_in_ticks; + remaining_quote_lots -= amount_lots_to_buy + * (ask.price_in_ticks * self.tick_size_in_quote_lots_per_base_unit); } - let quote_lots_used = num_lots_quote - remaining_quote_lots; + let quote_lots_used = + (adjusted_quote_lots - remaining_quote_lots) / self.base_lots_per_base_unit; SimulationSummaryInLots { base_lots_filled: base_lots, quote_lots_filled: quote_lots_used, @@ -45,14 +99,15 @@ impl MarketSimulator for Ladder { } let lots_to_fill = remaining_base_lots.min(bid.size_in_base_lots); - quote_lots += lots_to_fill * bid.price_in_ticks; + quote_lots += + lots_to_fill * bid.price_in_ticks * self.tick_size_in_quote_lots_per_base_unit; remaining_base_lots -= lots_to_fill; } let base_lots_used = num_lots_base - remaining_base_lots; SimulationSummaryInLots { base_lots_filled: base_lots_used, - quote_lots_filled: quote_lots, + quote_lots_filled: quote_lots / self.base_lots_per_base_unit, } } @@ -70,7 +125,7 @@ mod test { use phoenix::state::markets::LadderOrder; struct Fixture { - pub ladder: Ladder, + pub ladder: LadderWithAdjustment, pub atoms_in_base_lot: f64, pub atoms_in_quote_lot: f64, pub atoms_in_base_unit: f64, @@ -110,7 +165,11 @@ mod test { ], }; let fixture = Fixture { - ladder, + ladder: LadderWithAdjustment { + ladder, + tick_size_in_quote_lots_per_base_unit: 1000, + base_lots_per_base_unit: 1000, + }, atoms_in_base_lot: 1e6, atoms_in_quote_lot: 1., atoms_in_base_unit: 1e9, @@ -127,9 +186,13 @@ mod test { #[test] fn test_empty_ladder_sell() { - let ladder = Ladder { - bids: vec![], - asks: vec![], + let ladder = LadderWithAdjustment { + ladder: Ladder { + bids: vec![], + asks: vec![], + }, + tick_size_in_quote_lots_per_base_unit: 1000, + base_lots_per_base_unit: 1000, }; let result = ladder.simulate_market_sell(Side::Ask, 1000); assert_eq!(result.base_lots_filled, 0); @@ -155,15 +218,20 @@ mod test { let Fixture { ladder, .. } = get_sol_usdc_ladder(); // Compute the max lots you can buy (from available asks) - let max_lots_sellable: u64 = ladder + let max_lots_sellable = ladder .asks .iter() - .map(|ask| ask.size_in_base_lots * ask.price_in_ticks) - .sum(); + .map(|ask| { + ask.size_in_base_lots + * ask.price_in_ticks + * ladder.tick_size_in_quote_lots_per_base_unit + }) + .sum::() + / ladder.base_lots_per_base_unit; // Try to buy twice as much, which means you are selling twice as much base let to_sell = max_lots_sellable * 2; - let result = ladder.simulate_market_sell(Side::Bid, to_sell); + let result: SimulationSummaryInLots = ladder.simulate_market_sell(Side::Bid, to_sell); assert_eq!(result.quote_lots_filled, max_lots_sellable); assert!(result.quote_lots_filled > 0); diff --git a/rust/crates/phoenix-sdk/src/sdk_client.rs b/rust/crates/phoenix-sdk/src/sdk_client.rs index d59b9f6..86cac69 100644 --- a/rust/crates/phoenix-sdk/src/sdk_client.rs +++ b/rust/crates/phoenix-sdk/src/sdk_client.rs @@ -1,4 +1,4 @@ -use crate::ladder_utils::{MarketSimulator, SimulationSummaryInLots}; +use crate::ladder_utils::{LadderWithAdjustment, MarketSimulator, SimulationSummaryInLots}; use crate::order_packet_template::ImmediateOrCancelOrderTemplate; use crate::order_packet_template::LimitOrderTemplate; use crate::order_packet_template::PostOnlyOrderTemplate; @@ -494,8 +494,8 @@ impl SDKClient { })? .inner; - let ladder = market.get_ladder_with_expiration( - u64::MAX, + let ladder = LadderWithAdjustment::from_market_with_expiration( + market, last_valid_slot, last_valid_unix_timestamp_in_seconds, ); From 9e7f3bc49b0b8950a3e2fdd3b7e73534dd908a88 Mon Sep 17 00:00:00 2001 From: Jarry Xiao Date: Thu, 20 Jun 2024 20:06:35 -0400 Subject: [PATCH 2/5] bump version --- rust/crates/Cargo.lock | 2 +- rust/crates/phoenix-sdk/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/crates/Cargo.lock b/rust/crates/Cargo.lock index fdfb49c..c27b0a6 100644 --- a/rust/crates/Cargo.lock +++ b/rust/crates/Cargo.lock @@ -2890,7 +2890,7 @@ dependencies = [ [[package]] name = "phoenix-sdk" -version = "0.7.0" +version = "0.7.1" dependencies = [ "anyhow", "async-trait", diff --git a/rust/crates/phoenix-sdk/Cargo.toml b/rust/crates/phoenix-sdk/Cargo.toml index 84d53dd..2751773 100644 --- a/rust/crates/phoenix-sdk/Cargo.toml +++ b/rust/crates/phoenix-sdk/Cargo.toml @@ -2,7 +2,7 @@ cargo-features = ["workspace-inheritance"] [package] name = "phoenix-sdk" -version = "0.7.0" +version = "0.7.1" description = "SDK for interacting with the Phoenix program" edition = "2021" license = "MIT" From f105466c97d6e13018d99585636ce3cfa3bdd879 Mon Sep 17 00:00:00 2001 From: Jarry Xiao Date: Thu, 20 Jun 2024 20:09:12 -0400 Subject: [PATCH 3/5] naming --- rust/crates/phoenix-sdk/src/ladder_utils.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rust/crates/phoenix-sdk/src/ladder_utils.rs b/rust/crates/phoenix-sdk/src/ladder_utils.rs index d7bd83b..b004cbc 100644 --- a/rust/crates/phoenix-sdk/src/ladder_utils.rs +++ b/rust/crates/phoenix-sdk/src/ladder_utils.rs @@ -65,24 +65,24 @@ pub trait MarketSimulator { impl MarketSimulator for LadderWithAdjustment { fn sell_quote(&self, num_lots_quote: u64) -> SimulationSummaryInLots { let adjusted_quote_lots = num_lots_quote * self.base_lots_per_base_unit; - let mut remaining_quote_lots = adjusted_quote_lots; + let mut remaining_adjusted_quote_lots = adjusted_quote_lots; let mut base_lots = 0; for ask in self.asks.iter() { - if remaining_quote_lots == 0 { + if remaining_adjusted_quote_lots == 0 { break; } - let max_base_lots_you_can_buy = remaining_quote_lots + let max_base_lots_you_can_buy = remaining_adjusted_quote_lots / (ask.price_in_ticks * self.tick_size_in_quote_lots_per_base_unit); let amount_lots_to_buy = max_base_lots_you_can_buy.min(ask.size_in_base_lots); base_lots += amount_lots_to_buy; - remaining_quote_lots -= amount_lots_to_buy + remaining_adjusted_quote_lots -= amount_lots_to_buy * (ask.price_in_ticks * self.tick_size_in_quote_lots_per_base_unit); } let quote_lots_used = - (adjusted_quote_lots - remaining_quote_lots) / self.base_lots_per_base_unit; + (adjusted_quote_lots - remaining_adjusted_quote_lots) / self.base_lots_per_base_unit; SimulationSummaryInLots { base_lots_filled: base_lots, quote_lots_filled: quote_lots_used, @@ -91,7 +91,7 @@ impl MarketSimulator for LadderWithAdjustment { fn sell_base(&self, num_lots_base: u64) -> SimulationSummaryInLots { let mut remaining_base_lots = num_lots_base; - let mut quote_lots = 0; + let mut adjusted_quote_lots = 0; for bid in self.bids.iter() { if remaining_base_lots == 0 { @@ -99,7 +99,7 @@ impl MarketSimulator for LadderWithAdjustment { } let lots_to_fill = remaining_base_lots.min(bid.size_in_base_lots); - quote_lots += + adjusted_quote_lots += lots_to_fill * bid.price_in_ticks * self.tick_size_in_quote_lots_per_base_unit; remaining_base_lots -= lots_to_fill; } @@ -107,7 +107,7 @@ impl MarketSimulator for LadderWithAdjustment { let base_lots_used = num_lots_base - remaining_base_lots; SimulationSummaryInLots { base_lots_filled: base_lots_used, - quote_lots_filled: quote_lots / self.base_lots_per_base_unit, + quote_lots_filled: adjusted_quote_lots / self.base_lots_per_base_unit, } } From ccc88eebdc5452ddbdaa3f73bf0366f219c42fcc Mon Sep 17 00:00:00 2001 From: Jarry Xiao Date: Thu, 20 Jun 2024 20:10:18 -0400 Subject: [PATCH 4/5] make field private to avoid setting the unit adjustment incorrectly --- rust/crates/phoenix-sdk/src/ladder_utils.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/crates/phoenix-sdk/src/ladder_utils.rs b/rust/crates/phoenix-sdk/src/ladder_utils.rs index b004cbc..f6e9f0b 100644 --- a/rust/crates/phoenix-sdk/src/ladder_utils.rs +++ b/rust/crates/phoenix-sdk/src/ladder_utils.rs @@ -23,9 +23,9 @@ impl Deref for LadderWithAdjustment { } pub struct LadderWithAdjustment { - pub ladder: Ladder, - pub tick_size_in_quote_lots_per_base_unit: u64, - pub base_lots_per_base_unit: u64, + ladder: Ladder, + tick_size_in_quote_lots_per_base_unit: u64, + base_lots_per_base_unit: u64, } impl LadderWithAdjustment { From 3de8a5eb1741e5723b9ceddbcea99717971a0a1f Mon Sep 17 00:00:00 2001 From: Jarry Xiao Date: Thu, 20 Jun 2024 20:26:00 -0400 Subject: [PATCH 5/5] Add test with real market to confirm behavior --- rust/crates/phoenix-sdk/src/ladder_utils.rs | 30 +++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/rust/crates/phoenix-sdk/src/ladder_utils.rs b/rust/crates/phoenix-sdk/src/ladder_utils.rs index f6e9f0b..2ba0de8 100644 --- a/rust/crates/phoenix-sdk/src/ladder_utils.rs +++ b/rust/crates/phoenix-sdk/src/ladder_utils.rs @@ -121,8 +121,11 @@ impl MarketSimulator for LadderWithAdjustment { #[cfg(test)] mod test { + use crate::sdk_client::{self, SDKClient}; + use super::*; use phoenix::state::markets::LadderOrder; + use solana_sdk::{pubkey, signature::Keypair}; struct Fixture { pub ladder: LadderWithAdjustment, @@ -285,4 +288,31 @@ mod test { ); } } + + #[tokio::test] + async fn test_with_mainnet_market() { + let mut sdk = SDKClient::new(&Keypair::new(), "https://api.mainnet-beta.solana.com") + .await + .unwrap(); + let solusdc = pubkey!("4DoNfFBfF7UokCC2FQzriy7yHK6DY6NVdYpuekQ5pRgg"); + sdk.add_market(&solusdc).await.unwrap(); + let sol = pubkey!("So11111111111111111111111111111111111111112"); + let usdc = pubkey!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); + let res = sdk + .simulate_market_transaction(&solusdc, &sol, 1_000_000_000, None) + .await + .unwrap(); + println!( + "Sell Price: {}", + (res.quote_atoms_filled as f64 / 1e6) / (res.base_atoms_filled as f64 / 1e9) + ); + let res = sdk + .simulate_market_transaction(&solusdc, &usdc, 1_000_000_000, None) + .await + .unwrap(); + println!( + "Buy Price: {}", + (res.quote_atoms_filled as f64 / 1e6) / (res.base_atoms_filled as f64 / 1e9) + ) + } }