1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
use std::mem::size_of;

use phoenix::{
    program::{load_with_dispatch, MarketHeader},
    quantities::WrapperU64,
    state::{markets::RestingOrder, Side},
};
use phoenix_sdk::sdk_client::*;
use solana_sdk::{clock::Clock, commitment_config::CommitmentConfig, pubkey::Pubkey, sysvar};

use crate::helpers::print_helpers::{print_book_with_trader, LadderLevelEntry};

pub async fn process_get_book_levels(
    market_pubkey: &Pubkey,
    sdk: &SDKClient,
    levels: u64,
) -> anyhow::Result<()> {
    let mut ask_entries: Vec<LadderLevelEntry> = Vec::with_capacity(levels as usize);
    let mut bid_entries: Vec<LadderLevelEntry> = Vec::with_capacity(levels as usize);

    // Get market account
    let mut market_and_clock = sdk
        .client
        .get_multiple_accounts_with_commitment(
            &[*market_pubkey, sysvar::clock::id()],
            CommitmentConfig::confirmed(),
        )
        .await?
        .value;

    let market_account_data = market_and_clock
        .remove(0)
        .ok_or_else(|| anyhow::Error::msg("Market account not found"))?
        .data;

    let clock_account_data = market_and_clock
        .remove(0)
        .ok_or_else(|| anyhow::Error::msg("Clock account not found"))?
        .data;

    let clock: Clock = bincode::deserialize(&clock_account_data)
        .map_err(|_| anyhow::Error::msg("Error deserializing clock"))?;

    let (header_bytes, market_bytes) = market_account_data.split_at(size_of::<MarketHeader>());
    let header: &MarketHeader = bytemuck::try_from_bytes(header_bytes)
        .map_err(|e| anyhow::anyhow!("Error getting market header. Error: {:?}", e))?;

    // Derserialize data and load into correct type
    let market = load_with_dispatch(&header.market_size_params, market_bytes)?.inner;

    // If not present, use u32::MAX instead of aborting.
    // This will simply not print any markers.
    let trader_index = market.get_trader_index(&sdk.trader).unwrap_or(u32::MAX);
    let book_bids = market.get_book(Side::Bid);
    let book_asks = market.get_book(Side::Ask);

    let mut open_bids = vec![];
    open_bids.push(format!(
        "{0: <20} | {1: <20} | {2: <10} | {3: <10} | {4: <15} | {5: <15} ",
        "ID", "Price (ticks)", "Price", "Quantity", "Slots Remaining", "Seconds Remaining"
    ));

    for (order_id, order) in book_bids.iter() {
        // Check if order is expired
        if order.is_expired(clock.slot, clock.unix_timestamp as u64) {
            continue;
        }

        // Check if entry is present
        if let Some(ref mut entry) = bid_entries
            .iter_mut()
            .find(|entry| entry.tick == order_id.price_in_ticks)
        {
            // If entry is present, add to amount
            entry.lots += order.num_base_lots.as_u64();

            // Flag trader if present
            entry.trader_present |= order.trader_index == trader_index as u64;
        }

        // Otherwise, check length before attempting to add entry
        if bid_entries.len() < levels as usize {
            bid_entries.push(LadderLevelEntry {
                tick: order_id.price_in_ticks.as_u64(),
                lots: order.num_base_lots.as_u64(),
                trader_present: order.trader_index == trader_index as u64,
            })
        } else {
            break;
        }
    }

    for (order_id, order) in book_asks.iter() {
        // Check if order is expired
        if order.is_expired(clock.slot, clock.unix_timestamp as u64) {
            continue;
        }

        // Check if entry is present
        if let Some(ref mut entry) = ask_entries
            .iter_mut()
            .find(|entry| entry.tick == order_id.price_in_ticks)
        {
            // If entry is present, add to amount
            entry.lots += order.num_base_lots.as_u64();

            // Flag trader if present
            entry.trader_present |= order.trader_index == trader_index as u64;
        }

        // Otherwise, check length before attempting to add entry
        if ask_entries.len() < levels as usize {
            ask_entries.push(LadderLevelEntry {
                tick: order_id.price_in_ticks.as_u64(),
                lots: order.num_base_lots.as_u64(),
                trader_present: order.trader_index == trader_index as u64,
            })
        } else {
            break;
        }
    }

    print_book_with_trader(sdk, market_pubkey, &bid_entries, &ask_entries)?;

    Ok(())
}