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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
use anchor_lang::prelude::*;

use super::*;

/// Perp order parameters
pub struct Order {
    pub side: Side,

    /// Max base lots to buy/sell.
    pub max_base_lots: i64,

    /// Max quote lots to pay/receive (not taking fees into account).
    pub max_quote_lots: i64,

    /// Arbitrary user-controlled order id.
    pub client_order_id: u64,

    /// Reduce only
    pub reduce_only: bool,

    /// Number of seconds the order shall live, 0 meaning forever
    pub time_in_force: u16,

    /// Configure how matches with order of the same owner are handled
    pub self_trade_behavior: SelfTradeBehavior,

    /// Order type specific params
    pub params: OrderParams,
}

pub enum OrderParams {
    Market,
    ImmediateOrCancel {
        price_lots: i64,
    },
    Fixed {
        price_lots: i64,
        order_type: PostOrderType,
    },
    OraclePegged {
        price_offset_lots: i64,
        order_type: PostOrderType,
        peg_limit: i64,
        max_oracle_staleness_slots: i32,
    },
}

impl Order {
    /// Convert an input expiry timestamp to a time_in_force value
    pub fn tif_from_expiry(expiry_timestamp: u64) -> Option<u16> {
        let now_ts: u64 = Clock::get().unwrap().unix_timestamp.try_into().unwrap();
        if expiry_timestamp != 0 {
            // If expiry is far in the future, clamp to u16::MAX seconds
            let tif = expiry_timestamp.saturating_sub(now_ts).min(u16::MAX.into());
            if tif == 0 {
                // If expiry is in the past, ignore the order
                return None;
            }
            Some(tif as u16)
        } else {
            // Never expire
            Some(0)
        }
    }

    /// Should this order be penalized with an extra fee?
    ///
    /// Some programs opportunistically call ioc orders, wasting lots of compute. This
    /// is intended to encourage people to be smarter about it.
    pub fn needs_penalty_fee(&self) -> bool {
        matches!(self.params, OrderParams::ImmediateOrCancel { .. })
    }

    /// Is this order required to be posted to the orderbook? It will fail if it would take.
    pub fn is_post_only(&self) -> bool {
        let order_type = match self.params {
            OrderParams::Fixed { order_type, .. } => order_type,
            OrderParams::OraclePegged { order_type, .. } => order_type,
            _ => return false,
        };
        order_type == PostOrderType::PostOnly || order_type == PostOrderType::PostOnlySlide
    }

    /// Order tree that this order should be added to
    pub fn post_target(&self) -> Option<BookSideOrderTree> {
        match self.params {
            OrderParams::Fixed { .. } => Some(BookSideOrderTree::Fixed),
            OrderParams::OraclePegged { .. } => Some(BookSideOrderTree::OraclePegged),
            _ => None,
        }
    }

    /// Some order types (PostOnlySlide) may override the price that is passed in,
    /// this function computes the order-type-adjusted price.
    fn price_for_order_type(
        &self,
        now_ts: u64,
        oracle_price_lots: i64,
        price_lots: i64,
        order_type: PostOrderType,
        order_book: &Orderbook,
    ) -> i64 {
        if order_type == PostOrderType::PostOnlySlide {
            if let Some(best_other_price) = order_book
                .bookside(self.side.invert_side())
                .best_price(now_ts, oracle_price_lots)
            {
                post_only_slide_limit(self.side, best_other_price, price_lots)
            } else {
                price_lots
            }
        } else {
            price_lots
        }
    }

    /// Compute the price_lots this order is currently at, as well as the price_data that
    /// would be stored in its OrderTree node if the order is posted to the orderbook.
    pub fn price(
        &self,
        now_ts: u64,
        oracle_price_lots: i64,
        order_book: &Orderbook,
    ) -> Result<(i64, u64)> {
        let price_lots = match self.params {
            OrderParams::Market { .. } => market_order_limit_for_side(self.side),
            OrderParams::ImmediateOrCancel { price_lots, .. } => price_lots,
            OrderParams::Fixed {
                price_lots,
                order_type,
            } => self.price_for_order_type(
                now_ts,
                oracle_price_lots,
                price_lots,
                order_type,
                order_book,
            ),
            OrderParams::OraclePegged {
                price_offset_lots,
                order_type,
                ..
            } => {
                let price_lots = oracle_price_lots + price_offset_lots;
                self.price_for_order_type(
                    now_ts,
                    oracle_price_lots,
                    price_lots,
                    order_type,
                    order_book,
                )
            }
        };
        let price_data = match self.params {
            OrderParams::OraclePegged { .. } => {
                oracle_pegged_price_data(price_lots - oracle_price_lots)
            }
            _ => fixed_price_data(price_lots)?,
        };
        require_gte!(price_lots, 1);
        Ok((price_lots, price_data))
    }

    /// pegging limit for oracle peg orders, otherwise -1
    pub fn peg_limit(&self) -> i64 {
        match self.params {
            OrderParams::OraclePegged { peg_limit, .. } => peg_limit,
            _ => -1,
        }
    }
}

/// The implicit limit price to use for market orders
fn market_order_limit_for_side(side: Side) -> i64 {
    match side {
        Side::Bid => i64::MAX,
        Side::Ask => 1,
    }
}

/// The limit to use for PostOnlySlide orders: the tinyest bit better than
/// the best opposing order
fn post_only_slide_limit(side: Side, best_other_side: i64, limit: i64) -> i64 {
    match side {
        Side::Bid => limit.min(best_other_side - 1),
        Side::Ask => limit.max(best_other_side + 1),
    }
}