2022-11-08 06:27:56 -08:00
|
|
|
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,
|
|
|
|
|
2022-11-09 00:59:34 -08:00
|
|
|
/// Reduce only
|
|
|
|
pub reduce_only: bool,
|
|
|
|
|
2022-11-08 06:27:56 -08:00
|
|
|
/// Number of seconds the order shall live, 0 meaning forever
|
2022-12-05 06:21:24 -08:00
|
|
|
pub time_in_force: u16,
|
2022-11-08 06:27:56 -08:00
|
|
|
|
2023-05-15 01:40:41 -07:00
|
|
|
/// Configure how matches with order of the same owner are handled
|
|
|
|
pub self_trade_behavior: SelfTradeBehavior,
|
|
|
|
|
2022-11-08 06:27:56 -08:00
|
|
|
/// 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,
|
2022-12-01 01:43:47 -08:00
|
|
|
max_oracle_staleness_slots: i32,
|
2022-11-08 06:27:56 -08:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Order {
|
|
|
|
/// Convert an input expiry timestamp to a time_in_force value
|
2022-12-05 06:21:24 -08:00
|
|
|
pub fn tif_from_expiry(expiry_timestamp: u64) -> Option<u16> {
|
2022-11-10 05:41:20 -08:00
|
|
|
let now_ts: u64 = Clock::get().unwrap().unix_timestamp.try_into().unwrap();
|
2022-11-08 06:27:56 -08:00
|
|
|
if expiry_timestamp != 0 {
|
2022-12-05 06:21:24 -08:00
|
|
|
// If expiry is far in the future, clamp to u16::MAX seconds
|
|
|
|
let tif = expiry_timestamp.saturating_sub(now_ts).min(u16::MAX.into());
|
2022-11-08 06:27:56 -08:00
|
|
|
if tif == 0 {
|
|
|
|
// If expiry is in the past, ignore the order
|
|
|
|
return None;
|
|
|
|
}
|
2022-12-05 06:21:24 -08:00
|
|
|
Some(tif as u16)
|
2022-11-08 06:27:56 -08:00
|
|
|
} 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,
|
2022-11-21 10:34:41 -08:00
|
|
|
order_book: &Orderbook,
|
2022-11-08 06:27:56 -08:00
|
|
|
) -> i64 {
|
|
|
|
if order_type == PostOrderType::PostOnlySlide {
|
2023-01-18 04:45:36 -08:00
|
|
|
if let Some(best_other_price) = order_book
|
|
|
|
.bookside(self.side.invert_side())
|
|
|
|
.best_price(now_ts, oracle_price_lots)
|
2022-11-08 06:27:56 -08:00
|
|
|
{
|
|
|
|
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,
|
2022-11-21 10:34:41 -08:00
|
|
|
order_book: &Orderbook,
|
2022-11-08 06:27:56 -08:00
|
|
|
) -> Result<(i64, u64)> {
|
|
|
|
let price_lots = match self.params {
|
2023-05-15 01:40:41 -07:00
|
|
|
OrderParams::Market { .. } => market_order_limit_for_side(self.side),
|
|
|
|
OrderParams::ImmediateOrCancel { price_lots, .. } => price_lots,
|
2022-11-08 06:27:56 -08:00
|
|
|
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,
|
|
|
|
..
|
|
|
|
} => {
|
2023-02-24 02:56:33 -08:00
|
|
|
let price_lots = oracle_price_lots + price_offset_lots;
|
2022-11-08 06:27:56 -08:00
|
|
|
self.price_for_order_type(
|
|
|
|
now_ts,
|
|
|
|
oracle_price_lots,
|
|
|
|
price_lots,
|
|
|
|
order_type,
|
|
|
|
order_book,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
let price_data = match self.params {
|
|
|
|
OrderParams::OraclePegged { .. } => {
|
2023-02-24 02:56:33 -08:00
|
|
|
oracle_pegged_price_data(price_lots - oracle_price_lots)
|
2022-11-08 06:27:56 -08:00
|
|
|
}
|
|
|
|
_ => fixed_price_data(price_lots)?,
|
|
|
|
};
|
2023-01-20 04:24:52 -08:00
|
|
|
require_gte!(price_lots, 1);
|
2022-11-08 06:27:56 -08:00
|
|
|
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 {
|
2023-02-24 02:56:33 -08:00
|
|
|
Side::Bid => limit.min(best_other_side - 1),
|
|
|
|
Side::Ask => limit.max(best_other_side + 1),
|
2022-11-08 06:27:56 -08:00
|
|
|
}
|
|
|
|
}
|