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),
}
}