Enable self-trading protection (#533)
Co-authored-by: Christian Kamm <mail@ckamm.de>
This commit is contained in:
parent
9166b761e9
commit
c006948319
|
@ -10,3 +10,9 @@ _work in progress_
|
||||||
### Code style
|
### Code style
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
|
In order to run the tests the `enable_gpl` feature needs to be enabled to not skip essential tests.
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo test-sbf --features enable-gpl
|
||||||
|
```
|
|
@ -335,6 +335,7 @@ impl Rebalancer {
|
||||||
true, // reduce only
|
true, // reduce only
|
||||||
0,
|
0,
|
||||||
10,
|
10,
|
||||||
|
mango_v4::state::SelfTradeBehavior::DecrementTake,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
log::info!(
|
log::info!(
|
||||||
|
|
|
@ -17,8 +17,8 @@ use itertools::Itertools;
|
||||||
|
|
||||||
use mango_v4::accounts_ix::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side};
|
use mango_v4::accounts_ix::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side};
|
||||||
use mango_v4::state::{
|
use mango_v4::state::{
|
||||||
Bank, Group, MangoAccountValue, PerpMarketIndex, PlaceOrderType, Serum3MarketIndex, Side,
|
Bank, Group, MangoAccountValue, PerpMarketIndex, PlaceOrderType, SelfTradeBehavior,
|
||||||
TokenIndex, INSURANCE_TOKEN_INDEX,
|
Serum3MarketIndex, Side, TokenIndex, INSURANCE_TOKEN_INDEX,
|
||||||
};
|
};
|
||||||
|
|
||||||
use solana_address_lookup_table_program::state::AddressLookupTable;
|
use solana_address_lookup_table_program::state::AddressLookupTable;
|
||||||
|
@ -795,6 +795,7 @@ impl MangoClient {
|
||||||
reduce_only: bool,
|
reduce_only: bool,
|
||||||
expiry_timestamp: u64,
|
expiry_timestamp: u64,
|
||||||
limit: u8,
|
limit: u8,
|
||||||
|
self_trade_behavior: SelfTradeBehavior,
|
||||||
) -> anyhow::Result<Instruction> {
|
) -> anyhow::Result<Instruction> {
|
||||||
let perp = self.context.perp(market_index);
|
let perp = self.context.perp(market_index);
|
||||||
let health_remaining_metas = self.context.derive_health_check_remaining_account_metas(
|
let health_remaining_metas = self.context.derive_health_check_remaining_account_metas(
|
||||||
|
@ -823,7 +824,7 @@ impl MangoClient {
|
||||||
ams.extend(health_remaining_metas.into_iter());
|
ams.extend(health_remaining_metas.into_iter());
|
||||||
ams
|
ams
|
||||||
},
|
},
|
||||||
data: anchor_lang::InstructionData::data(&mango_v4::instruction::PerpPlaceOrder {
|
data: anchor_lang::InstructionData::data(&mango_v4::instruction::PerpPlaceOrderV2 {
|
||||||
side,
|
side,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots,
|
max_base_lots,
|
||||||
|
@ -833,6 +834,7 @@ impl MangoClient {
|
||||||
reduce_only,
|
reduce_only,
|
||||||
expiry_timestamp,
|
expiry_timestamp,
|
||||||
limit,
|
limit,
|
||||||
|
self_trade_behavior,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -851,6 +853,7 @@ impl MangoClient {
|
||||||
reduce_only: bool,
|
reduce_only: bool,
|
||||||
expiry_timestamp: u64,
|
expiry_timestamp: u64,
|
||||||
limit: u8,
|
limit: u8,
|
||||||
|
self_trade_behavior: SelfTradeBehavior,
|
||||||
) -> anyhow::Result<Signature> {
|
) -> anyhow::Result<Signature> {
|
||||||
let account = self.mango_account().await?;
|
let account = self.mango_account().await?;
|
||||||
let ix = self.perp_place_order_instruction(
|
let ix = self.perp_place_order_instruction(
|
||||||
|
@ -865,6 +868,7 @@ impl MangoClient {
|
||||||
reduce_only,
|
reduce_only,
|
||||||
expiry_timestamp,
|
expiry_timestamp,
|
||||||
limit,
|
limit,
|
||||||
|
self_trade_behavior,
|
||||||
)?;
|
)?;
|
||||||
self.send_and_confirm_owner_tx(vec![ix]).await
|
self.send_and_confirm_owner_tx(vec![ix]).await
|
||||||
}
|
}
|
||||||
|
|
252
mango_v4.json
252
mango_v4.json
|
@ -3593,6 +3593,112 @@
|
||||||
"option": "u128"
|
"option": "u128"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "perpPlaceOrderV2",
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "group",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "account",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false,
|
||||||
|
"relations": [
|
||||||
|
"group"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "perpMarket",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false,
|
||||||
|
"relations": [
|
||||||
|
"group",
|
||||||
|
"bids",
|
||||||
|
"asks",
|
||||||
|
"event_queue",
|
||||||
|
"oracle"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bids",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "asks",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "eventQueue",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "oracle",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "side",
|
||||||
|
"type": {
|
||||||
|
"defined": "Side"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "priceLots",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maxBaseLots",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maxQuoteLots",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "clientOrderId",
|
||||||
|
"type": "u64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "orderType",
|
||||||
|
"type": {
|
||||||
|
"defined": "PlaceOrderType"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "selfTradeBehavior",
|
||||||
|
"type": {
|
||||||
|
"defined": "SelfTradeBehavior"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "reduceOnly",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "expiryTimestamp",
|
||||||
|
"type": "u64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "limit",
|
||||||
|
"type": "u8"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returns": {
|
||||||
|
"option": "u128"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "perpPlaceOrderPegged",
|
"name": "perpPlaceOrderPegged",
|
||||||
"accounts": [
|
"accounts": [
|
||||||
|
@ -3701,6 +3807,120 @@
|
||||||
"option": "u128"
|
"option": "u128"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "perpPlaceOrderPeggedV2",
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "group",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "account",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false,
|
||||||
|
"relations": [
|
||||||
|
"group"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "perpMarket",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false,
|
||||||
|
"relations": [
|
||||||
|
"group",
|
||||||
|
"bids",
|
||||||
|
"asks",
|
||||||
|
"event_queue",
|
||||||
|
"oracle"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bids",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "asks",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "eventQueue",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "oracle",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "side",
|
||||||
|
"type": {
|
||||||
|
"defined": "Side"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "priceOffsetLots",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pegLimit",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maxBaseLots",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maxQuoteLots",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "clientOrderId",
|
||||||
|
"type": "u64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "orderType",
|
||||||
|
"type": {
|
||||||
|
"defined": "PlaceOrderType"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "selfTradeBehavior",
|
||||||
|
"type": {
|
||||||
|
"defined": "SelfTradeBehavior"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "reduceOnly",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "expiryTimestamp",
|
||||||
|
"type": "u64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "limit",
|
||||||
|
"type": "u8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maxOracleStalenessSlots",
|
||||||
|
"type": "i32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returns": {
|
||||||
|
"option": "u128"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "perpCancelOrder",
|
"name": "perpCancelOrder",
|
||||||
"accounts": [
|
"accounts": [
|
||||||
|
@ -7721,6 +7941,28 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "SelfTradeBehavior",
|
||||||
|
"docs": [
|
||||||
|
"Self trade behavior controls how taker orders interact with resting limit orders of the same account.",
|
||||||
|
"This setting has no influence on placing a resting or oracle pegged limit order that does not match",
|
||||||
|
"immediately, instead it's the responsibility of the user to correctly configure his taker orders."
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "enum",
|
||||||
|
"variants": [
|
||||||
|
{
|
||||||
|
"name": "DecrementTake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CancelProvide"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AbortTransaction"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Side",
|
"name": "Side",
|
||||||
"type": {
|
"type": {
|
||||||
|
@ -9205,6 +9447,11 @@
|
||||||
"type": "i64",
|
"type": "i64",
|
||||||
"index": false
|
"index": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "totalQuoteLotsDecremented",
|
||||||
|
"type": "i64",
|
||||||
|
"index": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "takerFeesPaid",
|
"name": "takerFeesPaid",
|
||||||
"type": "i128",
|
"type": "i128",
|
||||||
|
@ -9553,6 +9800,11 @@
|
||||||
"code": 6047,
|
"code": 6047,
|
||||||
"name": "InvalidHealthAccountCount",
|
"name": "InvalidHealthAccountCount",
|
||||||
"msg": "incorrect number of health accounts"
|
"msg": "incorrect number of health accounts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6048,
|
||||||
|
"name": "WouldSelfTrade",
|
||||||
|
"msg": "would self trade"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -101,6 +101,8 @@ pub enum MangoError {
|
||||||
TokenInForceClose,
|
TokenInForceClose,
|
||||||
#[msg("incorrect number of health accounts")]
|
#[msg("incorrect number of health accounts")]
|
||||||
InvalidHealthAccountCount,
|
InvalidHealthAccountCount,
|
||||||
|
#[msg("would self trade")]
|
||||||
|
WouldSelfTrade,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MangoError {
|
impl MangoError {
|
||||||
|
|
|
@ -210,7 +210,8 @@ mod tests {
|
||||||
client_order_id: 0,
|
client_order_id: 0,
|
||||||
reduce_only: true,
|
reduce_only: true,
|
||||||
time_in_force: 0,
|
time_in_force: 0,
|
||||||
params: OrderParams::Market,
|
self_trade_behavior: SelfTradeBehavior::DecrementTake,
|
||||||
|
params: OrderParams::Market {},
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = reduce_only_max_base_lots(&pp, &order, market_reduce_only);
|
let result = reduce_only_max_base_lots(&pp, &order, market_reduce_only);
|
||||||
|
|
|
@ -31,13 +31,15 @@ pub mod instructions;
|
||||||
compile_error!("compiling the program entrypoint without 'enable-gpl' makes no sense, enable it or use the 'cpi' or 'client' features");
|
compile_error!("compiling the program entrypoint without 'enable-gpl' makes no sense, enable it or use the 'cpi' or 'client' features");
|
||||||
|
|
||||||
use state::{
|
use state::{
|
||||||
OracleConfigParams, PerpMarketIndex, PlaceOrderType, Serum3MarketIndex, Side, TokenIndex,
|
OracleConfigParams, PerpMarketIndex, PlaceOrderType, SelfTradeBehavior, Serum3MarketIndex,
|
||||||
|
Side, TokenIndex,
|
||||||
};
|
};
|
||||||
|
|
||||||
declare_id!("4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg");
|
declare_id!("4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg");
|
||||||
|
|
||||||
#[program]
|
#[program]
|
||||||
pub mod mango_v4 {
|
pub mod mango_v4 {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use error::*;
|
use error::*;
|
||||||
|
|
||||||
|
@ -785,8 +787,74 @@ pub mod mango_v4 {
|
||||||
client_order_id,
|
client_order_id,
|
||||||
reduce_only,
|
reduce_only,
|
||||||
time_in_force,
|
time_in_force,
|
||||||
|
self_trade_behavior: SelfTradeBehavior::default(),
|
||||||
params: match order_type {
|
params: match order_type {
|
||||||
PlaceOrderType::Market => OrderParams::Market,
|
PlaceOrderType::Market => OrderParams::Market {},
|
||||||
|
PlaceOrderType::ImmediateOrCancel => OrderParams::ImmediateOrCancel { price_lots },
|
||||||
|
_ => OrderParams::Fixed {
|
||||||
|
price_lots,
|
||||||
|
order_type: order_type.to_post_order_type()?,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
#[cfg(feature = "enable-gpl")]
|
||||||
|
return instructions::perp_place_order(ctx, order, limit);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "enable-gpl"))]
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn perp_place_order_v2(
|
||||||
|
ctx: Context<PerpPlaceOrder>,
|
||||||
|
side: Side,
|
||||||
|
|
||||||
|
// The price in lots (quote lots per base lots)
|
||||||
|
// - fill orders on the book up to this price or
|
||||||
|
// - place an order on the book at this price.
|
||||||
|
// - ignored for Market orders and potentially adjusted for PostOnlySlide orders.
|
||||||
|
price_lots: i64,
|
||||||
|
|
||||||
|
max_base_lots: i64,
|
||||||
|
max_quote_lots: i64,
|
||||||
|
client_order_id: u64,
|
||||||
|
order_type: PlaceOrderType,
|
||||||
|
self_trade_behavior: SelfTradeBehavior,
|
||||||
|
reduce_only: bool,
|
||||||
|
|
||||||
|
// Timestamp of when order expires
|
||||||
|
//
|
||||||
|
// Send 0 if you want the order to never expire.
|
||||||
|
// Timestamps in the past mean the instruction is skipped.
|
||||||
|
// Timestamps in the future are reduced to now + 65535s.
|
||||||
|
expiry_timestamp: u64,
|
||||||
|
|
||||||
|
// Maximum number of orders from the book to fill.
|
||||||
|
//
|
||||||
|
// Use this to limit compute used during order matching.
|
||||||
|
// When the limit is reached, processing stops and the instruction succeeds.
|
||||||
|
limit: u8,
|
||||||
|
) -> Result<Option<u128>> {
|
||||||
|
require_gte!(price_lots, 0);
|
||||||
|
|
||||||
|
use crate::state::{Order, OrderParams};
|
||||||
|
let time_in_force = match Order::tif_from_expiry(expiry_timestamp) {
|
||||||
|
Some(t) => t,
|
||||||
|
None => {
|
||||||
|
msg!("Order is already expired");
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let order = Order {
|
||||||
|
side,
|
||||||
|
max_base_lots,
|
||||||
|
max_quote_lots,
|
||||||
|
client_order_id,
|
||||||
|
reduce_only,
|
||||||
|
time_in_force,
|
||||||
|
self_trade_behavior,
|
||||||
|
params: match order_type {
|
||||||
|
PlaceOrderType::Market => OrderParams::Market {},
|
||||||
PlaceOrderType::ImmediateOrCancel => OrderParams::ImmediateOrCancel { price_lots },
|
PlaceOrderType::ImmediateOrCancel => OrderParams::ImmediateOrCancel { price_lots },
|
||||||
_ => OrderParams::Fixed {
|
_ => OrderParams::Fixed {
|
||||||
price_lots,
|
price_lots,
|
||||||
|
@ -858,6 +926,80 @@ pub mod mango_v4 {
|
||||||
client_order_id,
|
client_order_id,
|
||||||
reduce_only,
|
reduce_only,
|
||||||
time_in_force,
|
time_in_force,
|
||||||
|
self_trade_behavior: SelfTradeBehavior::DecrementTake,
|
||||||
|
params: OrderParams::OraclePegged {
|
||||||
|
price_offset_lots,
|
||||||
|
order_type: order_type.to_post_order_type()?,
|
||||||
|
peg_limit,
|
||||||
|
max_oracle_staleness_slots,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
#[cfg(feature = "enable-gpl")]
|
||||||
|
return instructions::perp_place_order(ctx, order, limit);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "enable-gpl"))]
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn perp_place_order_pegged_v2(
|
||||||
|
ctx: Context<PerpPlaceOrder>,
|
||||||
|
side: Side,
|
||||||
|
|
||||||
|
// The adjustment from the oracle price, in lots (quote lots per base lots).
|
||||||
|
// Orders on the book may be filled at oracle + adjustment (depends on order type).
|
||||||
|
price_offset_lots: i64,
|
||||||
|
|
||||||
|
// The limit at which the pegged order shall expire.
|
||||||
|
// May be -1 to denote no peg limit.
|
||||||
|
//
|
||||||
|
// Example: An bid pegged to -20 with peg_limit 100 would expire if the oracle hits 121.
|
||||||
|
peg_limit: i64,
|
||||||
|
|
||||||
|
max_base_lots: i64,
|
||||||
|
max_quote_lots: i64,
|
||||||
|
client_order_id: u64,
|
||||||
|
order_type: PlaceOrderType,
|
||||||
|
self_trade_behavior: SelfTradeBehavior,
|
||||||
|
reduce_only: bool,
|
||||||
|
|
||||||
|
// Timestamp of when order expires
|
||||||
|
//
|
||||||
|
// Send 0 if you want the order to never expire.
|
||||||
|
// Timestamps in the past mean the instruction is skipped.
|
||||||
|
// Timestamps in the future are reduced to now + 65535s.
|
||||||
|
expiry_timestamp: u64,
|
||||||
|
|
||||||
|
// Maximum number of orders from the book to fill.
|
||||||
|
//
|
||||||
|
// Use this to limit compute used during order matching.
|
||||||
|
// When the limit is reached, processing stops and the instruction succeeds.
|
||||||
|
limit: u8,
|
||||||
|
|
||||||
|
// Oracle staleness limit, in slots. Set to -1 to disable.
|
||||||
|
//
|
||||||
|
// WARNING: Not currently implemented.
|
||||||
|
max_oracle_staleness_slots: i32,
|
||||||
|
) -> Result<Option<u128>> {
|
||||||
|
require_gte!(peg_limit, -1);
|
||||||
|
require_eq!(max_oracle_staleness_slots, -1); // unimplemented
|
||||||
|
|
||||||
|
use crate::state::{Order, OrderParams};
|
||||||
|
let time_in_force = match Order::tif_from_expiry(expiry_timestamp) {
|
||||||
|
Some(t) => t,
|
||||||
|
None => {
|
||||||
|
msg!("Order is already expired");
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let order = Order {
|
||||||
|
side,
|
||||||
|
max_base_lots,
|
||||||
|
max_quote_lots,
|
||||||
|
client_order_id,
|
||||||
|
reduce_only,
|
||||||
|
time_in_force,
|
||||||
|
self_trade_behavior,
|
||||||
params: OrderParams::OraclePegged {
|
params: OrderParams::OraclePegged {
|
||||||
price_offset_lots,
|
price_offset_lots,
|
||||||
order_type: order_type.to_post_order_type()?,
|
order_type: order_type.to_post_order_type()?,
|
||||||
|
|
|
@ -405,6 +405,7 @@ pub struct PerpTakerTradeLog {
|
||||||
pub taker_side: u8,
|
pub taker_side: u8,
|
||||||
pub total_base_lots_taken: i64,
|
pub total_base_lots_taken: i64,
|
||||||
pub total_quote_lots_taken: i64, // exclusive fees paid
|
pub total_quote_lots_taken: i64, // exclusive fees paid
|
||||||
|
pub total_quote_lots_decremented: i64, // from DecrementTake self-trades
|
||||||
pub taker_fees_paid: i128, // in native quote units
|
pub taker_fees_paid: i128, // in native quote units
|
||||||
pub fee_penalty: i128, // in native quote units
|
pub fee_penalty: i128, // in native quote units
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,8 +78,9 @@ impl<'a> Orderbook<'a> {
|
||||||
// matched_changes/matched_deletes and then applied after this loop.
|
// matched_changes/matched_deletes and then applied after this loop.
|
||||||
let mut remaining_base_lots = order.max_base_lots;
|
let mut remaining_base_lots = order.max_base_lots;
|
||||||
let mut remaining_quote_lots = order.max_quote_lots;
|
let mut remaining_quote_lots = order.max_quote_lots;
|
||||||
let mut matched_order_changes: Vec<(BookSideOrderHandle, i64)> = vec![];
|
let mut decremented_quote_lots = 0i64;
|
||||||
let mut matched_order_deletes: Vec<(BookSideOrderTree, u128)> = vec![];
|
let mut orders_to_change: Vec<(BookSideOrderHandle, i64)> = vec![];
|
||||||
|
let mut orders_to_delete: Vec<(BookSideOrderTree, u128)> = vec![];
|
||||||
let mut number_of_dropped_expired_orders = 0;
|
let mut number_of_dropped_expired_orders = 0;
|
||||||
let opposing_bookside = self.bookside_mut(other_side);
|
let opposing_bookside = self.bookside_mut(other_side);
|
||||||
for best_opposing in opposing_bookside.iter_all_including_invalid(now_ts, oracle_price_lots)
|
for best_opposing in opposing_bookside.iter_all_including_invalid(now_ts, oracle_price_lots)
|
||||||
|
@ -101,7 +102,7 @@ impl<'a> Orderbook<'a> {
|
||||||
best_opposing.node.quantity,
|
best_opposing.node.quantity,
|
||||||
);
|
);
|
||||||
event_queue.push_back(cast(event)).unwrap();
|
event_queue.push_back(cast(event)).unwrap();
|
||||||
matched_order_deletes
|
orders_to_delete
|
||||||
.push((best_opposing.handle.order_tree, best_opposing.node.key));
|
.push((best_opposing.handle.order_tree, best_opposing.node.key));
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
|
@ -129,8 +130,36 @@ impl<'a> Orderbook<'a> {
|
||||||
let match_base_lots = remaining_base_lots
|
let match_base_lots = remaining_base_lots
|
||||||
.min(best_opposing.node.quantity)
|
.min(best_opposing.node.quantity)
|
||||||
.min(max_match_by_quote);
|
.min(max_match_by_quote);
|
||||||
|
|
||||||
let match_quote_lots = match_base_lots * best_opposing_price;
|
let match_quote_lots = match_base_lots * best_opposing_price;
|
||||||
|
|
||||||
|
let order_would_self_trade = *mango_account_pk == best_opposing.node.owner;
|
||||||
|
if order_would_self_trade {
|
||||||
|
match order.self_trade_behavior {
|
||||||
|
SelfTradeBehavior::DecrementTake => {
|
||||||
|
// remember all decremented quote lots to only charge fees on not-self-trades
|
||||||
|
decremented_quote_lots += match_quote_lots;
|
||||||
|
}
|
||||||
|
SelfTradeBehavior::CancelProvide => {
|
||||||
|
let event = OutEvent::new(
|
||||||
|
other_side,
|
||||||
|
best_opposing.node.owner_slot,
|
||||||
|
now_ts,
|
||||||
|
event_queue.header.seq_num,
|
||||||
|
best_opposing.node.owner,
|
||||||
|
best_opposing.node.quantity,
|
||||||
|
);
|
||||||
|
event_queue.push_back(cast(event)).unwrap();
|
||||||
|
orders_to_delete
|
||||||
|
.push((best_opposing.handle.order_tree, best_opposing.node.key));
|
||||||
|
|
||||||
|
// skip actual matching
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
SelfTradeBehavior::AbortTransaction => return err!(MangoError::WouldSelfTrade),
|
||||||
|
}
|
||||||
|
assert!(order.self_trade_behavior == SelfTradeBehavior::DecrementTake);
|
||||||
|
}
|
||||||
|
|
||||||
remaining_base_lots -= match_base_lots;
|
remaining_base_lots -= match_base_lots;
|
||||||
remaining_quote_lots -= match_quote_lots;
|
remaining_quote_lots -= match_quote_lots;
|
||||||
assert!(remaining_quote_lots >= 0);
|
assert!(remaining_quote_lots >= 0);
|
||||||
|
@ -138,12 +167,12 @@ impl<'a> Orderbook<'a> {
|
||||||
let new_best_opposing_quantity = best_opposing.node.quantity - match_base_lots;
|
let new_best_opposing_quantity = best_opposing.node.quantity - match_base_lots;
|
||||||
let maker_out = new_best_opposing_quantity == 0;
|
let maker_out = new_best_opposing_quantity == 0;
|
||||||
if maker_out {
|
if maker_out {
|
||||||
matched_order_deletes
|
orders_to_delete.push((best_opposing.handle.order_tree, best_opposing.node.key));
|
||||||
.push((best_opposing.handle.order_tree, best_opposing.node.key));
|
|
||||||
} else {
|
} else {
|
||||||
matched_order_changes.push((best_opposing.handle, new_best_opposing_quantity));
|
orders_to_change.push((best_opposing.handle, new_best_opposing_quantity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// order_would_self_trade is only true in the DecrementTake case, in which we don't charge fees
|
||||||
let seq_num = event_queue.header.seq_num;
|
let seq_num = event_queue.header.seq_num;
|
||||||
let fill = FillEvent::new(
|
let fill = FillEvent::new(
|
||||||
side,
|
side,
|
||||||
|
@ -153,11 +182,20 @@ impl<'a> Orderbook<'a> {
|
||||||
seq_num,
|
seq_num,
|
||||||
best_opposing.node.owner,
|
best_opposing.node.owner,
|
||||||
best_opposing.node.client_order_id,
|
best_opposing.node.client_order_id,
|
||||||
market.maker_fee,
|
if order_would_self_trade {
|
||||||
|
I80F48::ZERO
|
||||||
|
} else {
|
||||||
|
market.maker_fee
|
||||||
|
},
|
||||||
best_opposing.node.timestamp,
|
best_opposing.node.timestamp,
|
||||||
*mango_account_pk,
|
*mango_account_pk,
|
||||||
order.client_order_id,
|
order.client_order_id,
|
||||||
market.taker_fee,
|
if order_would_self_trade {
|
||||||
|
I80F48::ZERO
|
||||||
|
} else {
|
||||||
|
// NOTE: this does not include the IOC penalty, but this value is not used to calculate fees
|
||||||
|
market.taker_fee
|
||||||
|
},
|
||||||
best_opposing_price,
|
best_opposing_price,
|
||||||
match_base_lots,
|
match_base_lots,
|
||||||
);
|
);
|
||||||
|
@ -179,7 +217,12 @@ impl<'a> Orderbook<'a> {
|
||||||
// realized when the fill event gets executed
|
// realized when the fill event gets executed
|
||||||
if total_quote_lots_taken > 0 || total_base_lots_taken > 0 {
|
if total_quote_lots_taken > 0 || total_base_lots_taken > 0 {
|
||||||
perp_position.add_taker_trade(side, total_base_lots_taken, total_quote_lots_taken);
|
perp_position.add_taker_trade(side, total_base_lots_taken, total_quote_lots_taken);
|
||||||
let taker_fees_paid = apply_fees(market, mango_account, total_quote_lots_taken)?;
|
// reduce fees to apply by decrement take volume
|
||||||
|
let taker_fees_paid = apply_fees(
|
||||||
|
market,
|
||||||
|
mango_account,
|
||||||
|
total_quote_lots_taken - decremented_quote_lots,
|
||||||
|
)?;
|
||||||
emit!(PerpTakerTradeLog {
|
emit!(PerpTakerTradeLog {
|
||||||
mango_group: market.group.key(),
|
mango_group: market.group.key(),
|
||||||
mango_account: *mango_account_pk,
|
mango_account: *mango_account_pk,
|
||||||
|
@ -187,13 +230,14 @@ impl<'a> Orderbook<'a> {
|
||||||
taker_side: side as u8,
|
taker_side: side as u8,
|
||||||
total_base_lots_taken,
|
total_base_lots_taken,
|
||||||
total_quote_lots_taken,
|
total_quote_lots_taken,
|
||||||
|
total_quote_lots_decremented: decremented_quote_lots,
|
||||||
taker_fees_paid: taker_fees_paid.to_bits(),
|
taker_fees_paid: taker_fees_paid.to_bits(),
|
||||||
fee_penalty: fee_penalty.to_bits(),
|
fee_penalty: fee_penalty.to_bits(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply changes to matched asks (handles invalidate on delete!)
|
// Apply changes to matched asks (handles invalidate on delete!)
|
||||||
for (handle, new_quantity) in matched_order_changes {
|
for (handle, new_quantity) in orders_to_change {
|
||||||
opposing_bookside
|
opposing_bookside
|
||||||
.node_mut(handle.node)
|
.node_mut(handle.node)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -201,7 +245,7 @@ impl<'a> Orderbook<'a> {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.quantity = new_quantity;
|
.quantity = new_quantity;
|
||||||
}
|
}
|
||||||
for (component, key) in matched_order_deletes {
|
for (component, key) in orders_to_delete {
|
||||||
let _removed_leaf = opposing_bookside.remove_by_key(component, key).unwrap();
|
let _removed_leaf = opposing_bookside.remove_by_key(component, key).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -396,6 +440,8 @@ fn apply_fees(
|
||||||
|
|
||||||
let perp_position = account.perp_position_mut(market.perp_market_index)?;
|
let perp_position = account.perp_position_mut(market.perp_market_index)?;
|
||||||
perp_position.record_trading_fee(taker_fees);
|
perp_position.record_trading_fee(taker_fees);
|
||||||
|
|
||||||
|
// taker fees are applied to volume during matching, quote volume only during consume
|
||||||
perp_position.taker_volume += taker_fees.to_num::<u64>();
|
perp_position.taker_volume += taker_fees.to_num::<u64>();
|
||||||
|
|
||||||
// Accrue maker fees immediately: they can be negative and applying them later
|
// Accrue maker fees immediately: they can be negative and applying them later
|
||||||
|
|
|
@ -126,6 +126,7 @@ mod tests {
|
||||||
client_order_id: 0,
|
client_order_id: 0,
|
||||||
time_in_force,
|
time_in_force,
|
||||||
reduce_only: false,
|
reduce_only: false,
|
||||||
|
self_trade_behavior: SelfTradeBehavior::DecrementTake,
|
||||||
params: OrderParams::Fixed {
|
params: OrderParams::Fixed {
|
||||||
price_lots,
|
price_lots,
|
||||||
order_type: PostOrderType::Limit,
|
order_type: PostOrderType::Limit,
|
||||||
|
@ -274,6 +275,7 @@ mod tests {
|
||||||
client_order_id: 42,
|
client_order_id: 42,
|
||||||
time_in_force: 0,
|
time_in_force: 0,
|
||||||
reduce_only: false,
|
reduce_only: false,
|
||||||
|
self_trade_behavior: SelfTradeBehavior::DecrementTake,
|
||||||
params: OrderParams::Fixed {
|
params: OrderParams::Fixed {
|
||||||
price_lots,
|
price_lots,
|
||||||
order_type: PostOrderType::Limit,
|
order_type: PostOrderType::Limit,
|
||||||
|
@ -335,6 +337,7 @@ mod tests {
|
||||||
client_order_id: 43,
|
client_order_id: 43,
|
||||||
time_in_force: 0,
|
time_in_force: 0,
|
||||||
reduce_only: false,
|
reduce_only: false,
|
||||||
|
self_trade_behavior: SelfTradeBehavior::DecrementTake,
|
||||||
params: OrderParams::Fixed {
|
params: OrderParams::Fixed {
|
||||||
price_lots,
|
price_lots,
|
||||||
order_type: PostOrderType::Limit,
|
order_type: PostOrderType::Limit,
|
||||||
|
@ -433,27 +436,36 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fee_penalty_applied_only_on_limit_order() -> Result<()> {
|
fn test_fee_penalty_applied_only_on_limit_order() -> Result<()> {
|
||||||
|
// setup market
|
||||||
let (mut market, oracle_price, mut event_queue, book_accs) = test_setup(1000.0);
|
let (mut market, oracle_price, mut event_queue, book_accs) = test_setup(1000.0);
|
||||||
let mut book = book_accs.orderbook();
|
let mut book = book_accs.orderbook();
|
||||||
|
|
||||||
let buffer = MangoAccount::default_for_tests().try_to_vec().unwrap();
|
|
||||||
let mut account = MangoAccountValue::from_bytes(&buffer).unwrap();
|
|
||||||
let taker_pk = Pubkey::new_unique();
|
|
||||||
let now_ts = 1000000;
|
let now_ts = 1000000;
|
||||||
|
|
||||||
market.taker_fee = I80F48::from_num(0.01);
|
market.taker_fee = I80F48::from_num(0.01);
|
||||||
|
market.maker_fee = I80F48::from_num(0.0);
|
||||||
market.fee_penalty = 5.0;
|
market.fee_penalty = 5.0;
|
||||||
account.ensure_perp_position(market.perp_market_index, 0)?;
|
|
||||||
|
|
||||||
// Passive order
|
// setup maker account
|
||||||
|
let maker_buffer = MangoAccount::default_for_tests().try_to_vec().unwrap();
|
||||||
|
let mut maker_account = MangoAccountValue::from_bytes(&maker_buffer).unwrap();
|
||||||
|
let maker_pk = Pubkey::new_unique();
|
||||||
|
maker_account.ensure_perp_position(market.perp_market_index, 0)?;
|
||||||
|
|
||||||
|
// setup taker account
|
||||||
|
let taker_buffer = MangoAccount::default_for_tests().try_to_vec().unwrap();
|
||||||
|
let mut taker_account = MangoAccountValue::from_bytes(&taker_buffer).unwrap();
|
||||||
|
let taker_pk = Pubkey::new_unique();
|
||||||
|
taker_account.ensure_perp_position(market.perp_market_index, 0)?;
|
||||||
|
|
||||||
|
// Maker order
|
||||||
book.new_order(
|
book.new_order(
|
||||||
Order {
|
Order {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
max_base_lots: 2,
|
max_base_lots: 2,
|
||||||
max_quote_lots: i64::MAX,
|
max_quote_lots: i64::MAX,
|
||||||
client_order_id: 43,
|
client_order_id: 42,
|
||||||
time_in_force: 0,
|
time_in_force: 0,
|
||||||
reduce_only: false,
|
reduce_only: false,
|
||||||
|
self_trade_behavior: SelfTradeBehavior::default(),
|
||||||
params: OrderParams::Fixed {
|
params: OrderParams::Fixed {
|
||||||
price_lots: 1000,
|
price_lots: 1000,
|
||||||
order_type: PostOrderType::Limit,
|
order_type: PostOrderType::Limit,
|
||||||
|
@ -462,8 +474,8 @@ mod tests {
|
||||||
&mut market,
|
&mut market,
|
||||||
&mut event_queue,
|
&mut event_queue,
|
||||||
oracle_price,
|
oracle_price,
|
||||||
&mut account.borrow_mut(),
|
&mut maker_account.borrow_mut(),
|
||||||
&taker_pk,
|
&maker_pk,
|
||||||
now_ts,
|
now_ts,
|
||||||
u8::MAX,
|
u8::MAX,
|
||||||
)
|
)
|
||||||
|
@ -478,6 +490,7 @@ mod tests {
|
||||||
client_order_id: 43,
|
client_order_id: 43,
|
||||||
time_in_force: 0,
|
time_in_force: 0,
|
||||||
reduce_only: false,
|
reduce_only: false,
|
||||||
|
self_trade_behavior: SelfTradeBehavior::DecrementTake,
|
||||||
params: OrderParams::Fixed {
|
params: OrderParams::Fixed {
|
||||||
price_lots: 1000,
|
price_lots: 1000,
|
||||||
order_type: PostOrderType::Limit,
|
order_type: PostOrderType::Limit,
|
||||||
|
@ -486,14 +499,14 @@ mod tests {
|
||||||
&mut market,
|
&mut market,
|
||||||
&mut event_queue,
|
&mut event_queue,
|
||||||
oracle_price,
|
oracle_price,
|
||||||
&mut account.borrow_mut(),
|
&mut taker_account.borrow_mut(),
|
||||||
&taker_pk,
|
&taker_pk,
|
||||||
now_ts,
|
now_ts,
|
||||||
u8::MAX,
|
u8::MAX,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let pos = account.perp_position(market.perp_market_index)?;
|
let pos = taker_account.perp_position(market.perp_market_index)?;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pos.quote_position_native().round(),
|
pos.quote_position_native().round(),
|
||||||
|
@ -513,27 +526,28 @@ mod tests {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
max_quote_lots: i64::MAX,
|
||||||
client_order_id: 43,
|
client_order_id: 44,
|
||||||
time_in_force: 0,
|
time_in_force: 0,
|
||||||
reduce_only: false,
|
reduce_only: false,
|
||||||
|
self_trade_behavior: SelfTradeBehavior::DecrementTake,
|
||||||
params: OrderParams::ImmediateOrCancel { price_lots: 1000 },
|
params: OrderParams::ImmediateOrCancel { price_lots: 1000 },
|
||||||
},
|
},
|
||||||
&mut market,
|
&mut market,
|
||||||
&mut event_queue,
|
&mut event_queue,
|
||||||
oracle_price,
|
oracle_price,
|
||||||
&mut account.borrow_mut(),
|
&mut taker_account.borrow_mut(),
|
||||||
&taker_pk,
|
&taker_pk,
|
||||||
now_ts,
|
now_ts,
|
||||||
u8::MAX,
|
u8::MAX,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let pos = account.perp_position(market.perp_market_index)?;
|
let pos = taker_account.perp_position(market.perp_market_index)?;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pos.quote_position_native().round(),
|
pos.quote_position_native().round(),
|
||||||
I80F48::from_num(-25), // -10 - 5
|
I80F48::from_num(-25), // -10 - 5
|
||||||
"Regular fees + fixed penalty applied on IOC order"
|
"No fees, but fixed penalty applied on IOC order"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -574,6 +588,7 @@ mod tests {
|
||||||
client_order_id: 0,
|
client_order_id: 0,
|
||||||
time_in_force: 0,
|
time_in_force: 0,
|
||||||
reduce_only: false,
|
reduce_only: false,
|
||||||
|
self_trade_behavior: SelfTradeBehavior::DecrementTake,
|
||||||
params: OrderParams::Fixed {
|
params: OrderParams::Fixed {
|
||||||
price_lots,
|
price_lots,
|
||||||
order_type: PostOrderType::Limit,
|
order_type: PostOrderType::Limit,
|
||||||
|
@ -606,4 +621,368 @@ mod tests {
|
||||||
new_order(&mut book, &mut event_queue, Side::Bid, 5005, 30, 1);
|
new_order(&mut book, &mut event_queue, Side::Bid, 5005, 30, 1);
|
||||||
assert_eq!(event_queue.len(), 1);
|
assert_eq!(event_queue.len(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_self_trade_decrement_take() -> Result<()> {
|
||||||
|
// setup market
|
||||||
|
let (mut market, oracle_price, mut event_queue, book_accs) = test_setup(1000.0);
|
||||||
|
let mut book = book_accs.orderbook();
|
||||||
|
let now_ts = 1000000;
|
||||||
|
market.taker_fee = I80F48::from_num(0.01);
|
||||||
|
market.maker_fee = I80F48::from_num(0.0);
|
||||||
|
market.fee_penalty = 5.0;
|
||||||
|
|
||||||
|
// setup maker account
|
||||||
|
let maker_buffer = MangoAccount::default_for_tests().try_to_vec().unwrap();
|
||||||
|
let mut maker_account = MangoAccountValue::from_bytes(&maker_buffer).unwrap();
|
||||||
|
let maker_pk = Pubkey::new_unique();
|
||||||
|
maker_account.ensure_perp_position(market.perp_market_index, 0)?;
|
||||||
|
|
||||||
|
// setup taker account
|
||||||
|
let taker_buffer = MangoAccount::default_for_tests().try_to_vec().unwrap();
|
||||||
|
let mut taker_account = MangoAccountValue::from_bytes(&taker_buffer).unwrap();
|
||||||
|
let taker_pk = Pubkey::new_unique();
|
||||||
|
taker_account.ensure_perp_position(market.perp_market_index, 0)?;
|
||||||
|
|
||||||
|
// taker limit order
|
||||||
|
book.new_order(
|
||||||
|
Order {
|
||||||
|
side: Side::Ask,
|
||||||
|
max_base_lots: 2,
|
||||||
|
max_quote_lots: i64::MAX,
|
||||||
|
client_order_id: 1,
|
||||||
|
time_in_force: 0,
|
||||||
|
reduce_only: false,
|
||||||
|
self_trade_behavior: SelfTradeBehavior::default(),
|
||||||
|
params: OrderParams::Fixed {
|
||||||
|
price_lots: 1000,
|
||||||
|
order_type: PostOrderType::Limit,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&mut market,
|
||||||
|
&mut event_queue,
|
||||||
|
oracle_price,
|
||||||
|
&mut taker_account.borrow_mut(),
|
||||||
|
&taker_pk,
|
||||||
|
now_ts,
|
||||||
|
u8::MAX,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// maker limit order
|
||||||
|
book.new_order(
|
||||||
|
Order {
|
||||||
|
side: Side::Ask,
|
||||||
|
max_base_lots: 2,
|
||||||
|
max_quote_lots: i64::MAX,
|
||||||
|
client_order_id: 2,
|
||||||
|
time_in_force: 0,
|
||||||
|
reduce_only: false,
|
||||||
|
self_trade_behavior: SelfTradeBehavior::default(),
|
||||||
|
params: OrderParams::Fixed {
|
||||||
|
price_lots: 1000,
|
||||||
|
order_type: PostOrderType::Limit,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&mut market,
|
||||||
|
&mut event_queue,
|
||||||
|
oracle_price,
|
||||||
|
&mut maker_account.borrow_mut(),
|
||||||
|
&maker_pk,
|
||||||
|
now_ts,
|
||||||
|
u8::MAX,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// taker full self-trade IOC
|
||||||
|
book.new_order(
|
||||||
|
Order {
|
||||||
|
side: Side::Bid,
|
||||||
|
max_base_lots: 1,
|
||||||
|
max_quote_lots: i64::MAX,
|
||||||
|
client_order_id: 3,
|
||||||
|
time_in_force: 0,
|
||||||
|
reduce_only: false,
|
||||||
|
self_trade_behavior: SelfTradeBehavior::DecrementTake,
|
||||||
|
params: OrderParams::ImmediateOrCancel { price_lots: 1000 },
|
||||||
|
},
|
||||||
|
&mut market,
|
||||||
|
&mut event_queue,
|
||||||
|
oracle_price,
|
||||||
|
&mut taker_account.borrow_mut(),
|
||||||
|
&taker_pk,
|
||||||
|
now_ts,
|
||||||
|
u8::MAX,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let pos = taker_account.perp_position(market.perp_market_index)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
pos.quote_position_native().round(),
|
||||||
|
I80F48::from_num(-5),
|
||||||
|
"Penalty applied on ioc self-trade"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
market.fees_accrued.round(),
|
||||||
|
I80F48::from_num(5),
|
||||||
|
"Fees moved to market"
|
||||||
|
);
|
||||||
|
|
||||||
|
let fill_event: FillEvent = event_queue.pop_front()?.try_into()?;
|
||||||
|
assert_eq!(fill_event.quantity, 1);
|
||||||
|
assert_eq!(fill_event.maker, taker_pk);
|
||||||
|
assert_eq!(fill_event.taker, taker_pk);
|
||||||
|
assert_eq!(fill_event.maker_fee, I80F48::ZERO);
|
||||||
|
assert_eq!(fill_event.taker_fee, I80F48::ZERO);
|
||||||
|
|
||||||
|
// taker partial self trade limit
|
||||||
|
book.new_order(
|
||||||
|
Order {
|
||||||
|
side: Side::Bid,
|
||||||
|
max_base_lots: 2,
|
||||||
|
max_quote_lots: i64::MAX,
|
||||||
|
client_order_id: 4,
|
||||||
|
time_in_force: 0,
|
||||||
|
reduce_only: false,
|
||||||
|
self_trade_behavior: SelfTradeBehavior::DecrementTake,
|
||||||
|
params: OrderParams::Fixed {
|
||||||
|
price_lots: 1000,
|
||||||
|
order_type: PostOrderType::Limit,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&mut market,
|
||||||
|
&mut event_queue,
|
||||||
|
oracle_price,
|
||||||
|
&mut taker_account.borrow_mut(),
|
||||||
|
&taker_pk,
|
||||||
|
now_ts,
|
||||||
|
u8::MAX,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let pos = taker_account.perp_position(market.perp_market_index)?;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
pos.quote_position_native().round(),
|
||||||
|
I80F48::from_num(-15), // -0 -10
|
||||||
|
"No fees for self-trade but for maker match"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
market.fees_accrued.round(),
|
||||||
|
I80F48::from_num(15), // 0 +10
|
||||||
|
"Fees moved to market"
|
||||||
|
);
|
||||||
|
|
||||||
|
let fill_event: FillEvent = event_queue.pop_front()?.try_into()?;
|
||||||
|
assert_eq!(fill_event.quantity, 1);
|
||||||
|
assert_eq!(fill_event.maker, taker_pk);
|
||||||
|
assert_eq!(fill_event.taker, taker_pk);
|
||||||
|
assert_eq!(fill_event.maker_fee, I80F48::ZERO);
|
||||||
|
assert_eq!(fill_event.taker_fee, I80F48::ZERO);
|
||||||
|
|
||||||
|
let fill_event: FillEvent = event_queue.pop_front()?.try_into()?;
|
||||||
|
assert_eq!(fill_event.quantity, 1);
|
||||||
|
assert_eq!(fill_event.maker, maker_pk);
|
||||||
|
assert_eq!(fill_event.taker, taker_pk);
|
||||||
|
assert_eq!(fill_event.maker_fee, 0.0);
|
||||||
|
assert_eq!(fill_event.taker_fee, 0.01);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_self_trade_cancel_provide() -> Result<()> {
|
||||||
|
// setup market
|
||||||
|
let (mut market, oracle_price, mut event_queue, book_accs) = test_setup(1000.0);
|
||||||
|
let mut book = book_accs.orderbook();
|
||||||
|
let now_ts = 1000000;
|
||||||
|
market.taker_fee = I80F48::from_num(0.01);
|
||||||
|
market.maker_fee = I80F48::from_num(0.0);
|
||||||
|
market.fee_penalty = 5.0;
|
||||||
|
|
||||||
|
// setup maker account
|
||||||
|
let maker_buffer = MangoAccount::default_for_tests().try_to_vec().unwrap();
|
||||||
|
let mut maker_account = MangoAccountValue::from_bytes(&maker_buffer).unwrap();
|
||||||
|
let maker_pk = Pubkey::new_unique();
|
||||||
|
maker_account.ensure_perp_position(market.perp_market_index, 0)?;
|
||||||
|
|
||||||
|
// setup taker account
|
||||||
|
let taker_buffer = MangoAccount::default_for_tests().try_to_vec().unwrap();
|
||||||
|
let mut taker_account = MangoAccountValue::from_bytes(&taker_buffer).unwrap();
|
||||||
|
let taker_pk = Pubkey::new_unique();
|
||||||
|
taker_account.ensure_perp_position(market.perp_market_index, 0)?;
|
||||||
|
|
||||||
|
// taker limit order
|
||||||
|
book.new_order(
|
||||||
|
Order {
|
||||||
|
side: Side::Ask,
|
||||||
|
max_base_lots: 1,
|
||||||
|
max_quote_lots: i64::MAX,
|
||||||
|
client_order_id: 1,
|
||||||
|
time_in_force: 0,
|
||||||
|
reduce_only: false,
|
||||||
|
self_trade_behavior: SelfTradeBehavior::default(),
|
||||||
|
params: OrderParams::Fixed {
|
||||||
|
price_lots: 1000,
|
||||||
|
order_type: PostOrderType::Limit,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&mut market,
|
||||||
|
&mut event_queue,
|
||||||
|
oracle_price,
|
||||||
|
&mut taker_account.borrow_mut(),
|
||||||
|
&taker_pk,
|
||||||
|
now_ts,
|
||||||
|
u8::MAX,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// maker limit order
|
||||||
|
book.new_order(
|
||||||
|
Order {
|
||||||
|
side: Side::Ask,
|
||||||
|
max_base_lots: 2,
|
||||||
|
max_quote_lots: i64::MAX,
|
||||||
|
client_order_id: 2,
|
||||||
|
time_in_force: 0,
|
||||||
|
reduce_only: false,
|
||||||
|
self_trade_behavior: SelfTradeBehavior::default(),
|
||||||
|
params: OrderParams::Fixed {
|
||||||
|
price_lots: 1000,
|
||||||
|
order_type: PostOrderType::Limit,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&mut market,
|
||||||
|
&mut event_queue,
|
||||||
|
oracle_price,
|
||||||
|
&mut maker_account.borrow_mut(),
|
||||||
|
&maker_pk,
|
||||||
|
now_ts,
|
||||||
|
u8::MAX,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// taker partial self-trade
|
||||||
|
book.new_order(
|
||||||
|
Order {
|
||||||
|
side: Side::Bid,
|
||||||
|
max_base_lots: 1,
|
||||||
|
max_quote_lots: i64::MAX,
|
||||||
|
client_order_id: 3,
|
||||||
|
time_in_force: 0,
|
||||||
|
reduce_only: false,
|
||||||
|
self_trade_behavior: SelfTradeBehavior::CancelProvide,
|
||||||
|
params: OrderParams::Fixed {
|
||||||
|
price_lots: 1000,
|
||||||
|
order_type: PostOrderType::Limit,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&mut market,
|
||||||
|
&mut event_queue,
|
||||||
|
oracle_price,
|
||||||
|
&mut taker_account.borrow_mut(),
|
||||||
|
&taker_pk,
|
||||||
|
now_ts,
|
||||||
|
u8::MAX,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let pos = taker_account.perp_position(market.perp_market_index)?;
|
||||||
|
assert_eq!(
|
||||||
|
pos.quote_position_native().round(),
|
||||||
|
I80F48::from_num(-10), // -0 -10
|
||||||
|
"No fees for self-trade but for maker match"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
market.fees_accrued.round(),
|
||||||
|
I80F48::from_num(10), // 0 +10
|
||||||
|
"Fees moved to market"
|
||||||
|
);
|
||||||
|
|
||||||
|
let out_event: OutEvent = event_queue.pop_front()?.try_into()?;
|
||||||
|
assert_eq!(out_event.owner, taker_pk);
|
||||||
|
|
||||||
|
let fill_event: FillEvent = event_queue.pop_front()?.try_into()?;
|
||||||
|
assert_eq!(fill_event.maker, maker_pk);
|
||||||
|
assert_eq!(fill_event.taker, taker_pk);
|
||||||
|
assert_eq!(fill_event.quantity, 1);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_self_trade_abort_transaction() -> Result<()> {
|
||||||
|
// setup market
|
||||||
|
let (mut market, oracle_price, mut event_queue, book_accs) = test_setup(1000.0);
|
||||||
|
let mut book = book_accs.orderbook();
|
||||||
|
let now_ts = 1000000;
|
||||||
|
market.taker_fee = I80F48::from_num(0.01);
|
||||||
|
market.maker_fee = I80F48::from_num(0.0);
|
||||||
|
market.fee_penalty = 5.0;
|
||||||
|
|
||||||
|
// setup maker account
|
||||||
|
let maker_buffer = MangoAccount::default_for_tests().try_to_vec().unwrap();
|
||||||
|
let mut maker_account = MangoAccountValue::from_bytes(&maker_buffer).unwrap();
|
||||||
|
let maker_pk = Pubkey::new_unique();
|
||||||
|
maker_account.ensure_perp_position(market.perp_market_index, 0)?;
|
||||||
|
|
||||||
|
// setup taker account
|
||||||
|
let taker_buffer = MangoAccount::default_for_tests().try_to_vec().unwrap();
|
||||||
|
let mut taker_account = MangoAccountValue::from_bytes(&taker_buffer).unwrap();
|
||||||
|
let taker_pk = Pubkey::new_unique();
|
||||||
|
taker_account.ensure_perp_position(market.perp_market_index, 0)?;
|
||||||
|
|
||||||
|
// taker limit order
|
||||||
|
book.new_order(
|
||||||
|
Order {
|
||||||
|
side: Side::Ask,
|
||||||
|
max_base_lots: 1,
|
||||||
|
max_quote_lots: i64::MAX,
|
||||||
|
client_order_id: 1,
|
||||||
|
time_in_force: 0,
|
||||||
|
reduce_only: false,
|
||||||
|
self_trade_behavior: SelfTradeBehavior::default(),
|
||||||
|
params: OrderParams::Fixed {
|
||||||
|
price_lots: 1000,
|
||||||
|
order_type: PostOrderType::Limit,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&mut market,
|
||||||
|
&mut event_queue,
|
||||||
|
oracle_price,
|
||||||
|
&mut taker_account.borrow_mut(),
|
||||||
|
&taker_pk,
|
||||||
|
now_ts,
|
||||||
|
u8::MAX,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// taker failing self-trade
|
||||||
|
book.new_order(
|
||||||
|
Order {
|
||||||
|
side: Side::Bid,
|
||||||
|
max_base_lots: 1,
|
||||||
|
max_quote_lots: i64::MAX,
|
||||||
|
client_order_id: 3,
|
||||||
|
time_in_force: 0,
|
||||||
|
reduce_only: false,
|
||||||
|
self_trade_behavior: SelfTradeBehavior::AbortTransaction,
|
||||||
|
params: OrderParams::ImmediateOrCancel { price_lots: 1000 },
|
||||||
|
},
|
||||||
|
&mut market,
|
||||||
|
&mut event_queue,
|
||||||
|
oracle_price,
|
||||||
|
&mut taker_account.borrow_mut(),
|
||||||
|
&taker_pk,
|
||||||
|
now_ts,
|
||||||
|
u8::MAX,
|
||||||
|
)
|
||||||
|
.expect_err("should fail");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,9 @@ pub struct Order {
|
||||||
/// Number of seconds the order shall live, 0 meaning forever
|
/// Number of seconds the order shall live, 0 meaning forever
|
||||||
pub time_in_force: u16,
|
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
|
/// Order type specific params
|
||||||
pub params: OrderParams,
|
pub params: OrderParams,
|
||||||
}
|
}
|
||||||
|
@ -120,8 +123,8 @@ impl Order {
|
||||||
order_book: &Orderbook,
|
order_book: &Orderbook,
|
||||||
) -> Result<(i64, u64)> {
|
) -> Result<(i64, u64)> {
|
||||||
let price_lots = match self.params {
|
let price_lots = match self.params {
|
||||||
OrderParams::Market => market_order_limit_for_side(self.side),
|
OrderParams::Market { .. } => market_order_limit_for_side(self.side),
|
||||||
OrderParams::ImmediateOrCancel { price_lots } => price_lots,
|
OrderParams::ImmediateOrCancel { price_lots, .. } => price_lots,
|
||||||
OrderParams::Fixed {
|
OrderParams::Fixed {
|
||||||
price_lots,
|
price_lots,
|
||||||
order_type,
|
order_type,
|
||||||
|
|
|
@ -79,6 +79,35 @@ pub enum PostOrderType {
|
||||||
PostOnlySlide = 4,
|
PostOnlySlide = 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Eq,
|
||||||
|
PartialEq,
|
||||||
|
Copy,
|
||||||
|
Clone,
|
||||||
|
Default,
|
||||||
|
TryFromPrimitive,
|
||||||
|
IntoPrimitive,
|
||||||
|
Debug,
|
||||||
|
AnchorSerialize,
|
||||||
|
AnchorDeserialize,
|
||||||
|
)]
|
||||||
|
#[repr(u8)]
|
||||||
|
/// Self trade behavior controls how taker orders interact with resting limit orders of the same account.
|
||||||
|
/// This setting has no influence on placing a resting or oracle pegged limit order that does not match
|
||||||
|
/// immediately, instead it's the responsibility of the user to correctly configure his taker orders.
|
||||||
|
pub enum SelfTradeBehavior {
|
||||||
|
/// Both the maker and taker sides of the matched orders are decremented.
|
||||||
|
/// This is equivalent to a normal order match, except for the fact that no fees are applied.
|
||||||
|
#[default]
|
||||||
|
DecrementTake = 0,
|
||||||
|
|
||||||
|
/// Cancels the maker side of the trade, the taker side gets matched with other maker's orders.
|
||||||
|
CancelProvide = 1,
|
||||||
|
|
||||||
|
/// Cancels the whole transaction as soon as a self-matching scenario is encountered.
|
||||||
|
AbortTransaction = 2,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Eq,
|
Eq,
|
||||||
PartialEq,
|
PartialEq,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::error::MangoError;
|
use crate::{error::Contextable, error::MangoError, error_msg};
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
|
use bytemuck::cast_ref;
|
||||||
use fixed::types::I80F48;
|
use fixed::types::I80F48;
|
||||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||||
use static_assertions::const_assert_eq;
|
use static_assertions::const_assert_eq;
|
||||||
|
@ -263,6 +264,36 @@ impl FillEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<AnyEvent> for FillEvent {
|
||||||
|
type Error = error::Error;
|
||||||
|
|
||||||
|
fn try_from(e: AnyEvent) -> Result<Self> {
|
||||||
|
if e.event_type != EventType::Fill as u8 {
|
||||||
|
Err(error_msg!(
|
||||||
|
"could not convert event with type={} to FillEvent",
|
||||||
|
e.event_type
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(*cast_ref(&e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a AnyEvent> for &'a FillEvent {
|
||||||
|
type Error = error::Error;
|
||||||
|
|
||||||
|
fn try_from(e: &'a AnyEvent) -> Result<Self> {
|
||||||
|
if e.event_type != EventType::Fill as u8 {
|
||||||
|
Err(error_msg!(
|
||||||
|
"could not convert event with type={} to FillEvent",
|
||||||
|
e.event_type
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(cast_ref(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable, AnchorSerialize, AnchorDeserialize,
|
Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable, AnchorSerialize, AnchorDeserialize,
|
||||||
)]
|
)]
|
||||||
|
@ -307,3 +338,33 @@ impl OutEvent {
|
||||||
self.side.try_into().unwrap()
|
self.side.try_into().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<AnyEvent> for OutEvent {
|
||||||
|
type Error = error::Error;
|
||||||
|
|
||||||
|
fn try_from(e: AnyEvent) -> Result<Self> {
|
||||||
|
if e.event_type != EventType::Out as u8 {
|
||||||
|
Err(error_msg!(
|
||||||
|
"could not convert event with type={} to OutEvent",
|
||||||
|
e.event_type
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(*cast_ref(&e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a AnyEvent> for &'a OutEvent {
|
||||||
|
type Error = error::Error;
|
||||||
|
|
||||||
|
fn try_from(e: &'a AnyEvent) -> Result<Self> {
|
||||||
|
if e.event_type != EventType::Out as u8 {
|
||||||
|
Err(error_msg!(
|
||||||
|
"could not convert event with type={} to OutEvent",
|
||||||
|
e.event_type
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(cast_ref(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -102,9 +102,8 @@ async fn test_fees_buyback_with_mngo() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 20,
|
max_base_lots: 20,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 5,
|
client_order_id: 5,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -119,9 +118,8 @@ async fn test_fees_buyback_with_mngo() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 20,
|
max_base_lots: 20,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 6,
|
client_order_id: 6,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -320,9 +320,8 @@ async fn test_force_close_perp() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 5,
|
client_order_id: 5,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -338,9 +337,8 @@ async fn test_force_close_perp() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 6,
|
client_order_id: 6,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -243,9 +243,7 @@ async fn test_health_compute_perp() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -162,9 +162,7 @@ async fn test_liq_perps_bankruptcy() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
client_order_id: 0,
|
|
||||||
reduce_only: false,
|
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
tx.add_instruction(PerpPlaceOrderInstruction {
|
tx.add_instruction(PerpPlaceOrderInstruction {
|
||||||
|
@ -174,9 +172,7 @@ async fn test_liq_perps_bankruptcy() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
client_order_id: 0,
|
|
||||||
reduce_only: false,
|
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
tx.add_instruction(PerpConsumeEventsInstruction {
|
tx.add_instruction(PerpConsumeEventsInstruction {
|
||||||
|
@ -195,9 +191,7 @@ async fn test_liq_perps_bankruptcy() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots: adj_price_lots,
|
price_lots: adj_price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
client_order_id: 0,
|
|
||||||
reduce_only: false,
|
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
tx.add_instruction(PerpPlaceOrderInstruction {
|
tx.add_instruction(PerpPlaceOrderInstruction {
|
||||||
|
@ -207,9 +201,7 @@ async fn test_liq_perps_bankruptcy() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots: adj_price_lots,
|
price_lots: adj_price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
client_order_id: 0,
|
|
||||||
reduce_only: false,
|
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
tx.add_instruction(PerpConsumeEventsInstruction {
|
tx.add_instruction(PerpConsumeEventsInstruction {
|
||||||
|
|
|
@ -138,9 +138,7 @@ async fn test_liq_perps_base_and_bankruptcy() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 20,
|
max_base_lots: 20,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -154,9 +152,7 @@ async fn test_liq_perps_base_and_bankruptcy() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 20,
|
max_base_lots: 20,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -106,9 +106,7 @@ async fn test_liq_perps_force_cancel() -> Result<(), TransportError> {
|
||||||
price_lots,
|
price_lots,
|
||||||
// health was 1000 * 0.6 = 600; this order is -14*100*(1.4-1) = -560
|
// health was 1000 * 0.6 = 600; this order is -14*100*(1.4-1) = -560
|
||||||
max_base_lots: 14,
|
max_base_lots: 14,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -158,9 +158,7 @@ async fn test_liq_perps_positive_pnl() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 10,
|
max_base_lots: 10,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -174,9 +172,7 @@ async fn test_liq_perps_positive_pnl() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 10,
|
max_base_lots: 10,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -96,9 +96,7 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -138,9 +136,8 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 1,
|
client_order_id: 1,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -173,9 +170,8 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 2,
|
client_order_id: 2,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -207,9 +203,8 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 4,
|
client_order_id: 4,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -241,9 +236,8 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 5,
|
client_order_id: 5,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -259,9 +253,8 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 6,
|
client_order_id: 6,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -333,9 +326,8 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 7,
|
client_order_id: 7,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -351,9 +343,8 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 8,
|
client_order_id: 8,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -616,9 +607,8 @@ async fn test_perp_oracle_peg() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 6,
|
client_order_id: 6,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -699,9 +689,8 @@ async fn test_perp_oracle_peg() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 60,
|
client_order_id: 60,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -729,9 +718,8 @@ async fn test_perp_oracle_peg() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 2,
|
max_base_lots: 2,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 61,
|
client_order_id: 61,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -781,9 +769,8 @@ async fn test_perp_oracle_peg() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots: price_lots + 2,
|
price_lots: price_lots + 2,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 62,
|
client_order_id: 62,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -811,9 +798,8 @@ async fn test_perp_oracle_peg() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots: price_lots + 3,
|
price_lots: price_lots + 3,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 63,
|
client_order_id: 63,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -933,9 +919,8 @@ async fn test_perp_realize_partially() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 2,
|
max_base_lots: 2,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 5,
|
client_order_id: 5,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -950,9 +935,8 @@ async fn test_perp_realize_partially() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 2,
|
max_base_lots: 2,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 6,
|
client_order_id: 6,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -989,9 +973,8 @@ async fn test_perp_realize_partially() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots: perp_market_data.native_price_to_lot(I80F48::from_num(1500)),
|
price_lots: perp_market_data.native_price_to_lot(I80F48::from_num(1500)),
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 5,
|
client_order_id: 5,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -1006,9 +989,8 @@ async fn test_perp_realize_partially() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots: perp_market_data.native_price_to_lot(I80F48::from_num(1500)),
|
price_lots: perp_market_data.native_price_to_lot(I80F48::from_num(1500)),
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 6,
|
client_order_id: 6,
|
||||||
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -129,9 +129,7 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -146,9 +144,7 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -615,9 +611,7 @@ async fn test_perp_settle_pnl_fees() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -632,9 +626,7 @@ async fn test_perp_settle_pnl_fees() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -884,9 +876,7 @@ async fn test_perp_pnl_settle_limit() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -901,9 +891,7 @@ async fn test_perp_pnl_settle_limit() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -1017,9 +1005,7 @@ async fn test_perp_pnl_settle_limit() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots: 3 * price_lots,
|
price_lots: 3 * price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
client_order_id: 0,
|
|
||||||
reduce_only: false,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -1034,9 +1020,7 @@ async fn test_perp_pnl_settle_limit() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots: 3 * price_lots,
|
price_lots: 3 * price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
client_order_id: 0,
|
|
||||||
reduce_only: false,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -208,9 +208,7 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -225,9 +223,7 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -281,9 +281,7 @@ async fn test_perp_reduce_only() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 2,
|
max_base_lots: 2,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -298,9 +296,7 @@ async fn test_perp_reduce_only() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 2,
|
max_base_lots: 2,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -328,9 +324,7 @@ async fn test_perp_reduce_only() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots: price_lots / 2,
|
price_lots: price_lots / 2,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -347,9 +341,7 @@ async fn test_perp_reduce_only() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -381,9 +373,7 @@ async fn test_perp_reduce_only() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
@ -399,9 +389,8 @@ async fn test_perp_reduce_only() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: true,
|
reduce_only: true,
|
||||||
client_order_id: 0,
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -419,9 +408,7 @@ async fn test_perp_reduce_only() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -439,9 +426,8 @@ async fn test_perp_reduce_only() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: true,
|
reduce_only: true,
|
||||||
client_order_id: 0,
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -459,9 +445,7 @@ async fn test_perp_reduce_only() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
@ -477,9 +461,8 @@ async fn test_perp_reduce_only() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: true,
|
reduce_only: true,
|
||||||
client_order_id: 0,
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -497,9 +480,7 @@ async fn test_perp_reduce_only() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
@ -515,9 +496,8 @@ async fn test_perp_reduce_only() -> Result<(), TransportError> {
|
||||||
side: Side::Ask,
|
side: Side::Ask,
|
||||||
price_lots,
|
price_lots,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: true,
|
reduce_only: true,
|
||||||
client_order_id: 0,
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -535,9 +515,7 @@ async fn test_perp_reduce_only() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots: price_lots / 2,
|
price_lots: price_lots / 2,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -555,9 +533,7 @@ async fn test_perp_reduce_only() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots: price_lots / 2,
|
price_lots: price_lots / 2,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -575,9 +551,7 @@ async fn test_perp_reduce_only() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots: price_lots / 2,
|
price_lots: price_lots / 2,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
..PerpPlaceOrderInstruction::default()
|
||||||
reduce_only: false,
|
|
||||||
client_order_id: 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
@ -593,9 +567,8 @@ async fn test_perp_reduce_only() -> Result<(), TransportError> {
|
||||||
side: Side::Bid,
|
side: Side::Bid,
|
||||||
price_lots: price_lots / 2,
|
price_lots: price_lots / 2,
|
||||||
max_base_lots: 1,
|
max_base_lots: 1,
|
||||||
max_quote_lots: i64::MAX,
|
|
||||||
reduce_only: true,
|
reduce_only: true,
|
||||||
client_order_id: 0,
|
..PerpPlaceOrderInstruction::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -3253,11 +3253,28 @@ pub struct PerpPlaceOrderInstruction {
|
||||||
pub max_quote_lots: i64,
|
pub max_quote_lots: i64,
|
||||||
pub reduce_only: bool,
|
pub reduce_only: bool,
|
||||||
pub client_order_id: u64,
|
pub client_order_id: u64,
|
||||||
|
pub self_trade_behavior: SelfTradeBehavior,
|
||||||
|
}
|
||||||
|
impl Default for PerpPlaceOrderInstruction {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
account: Pubkey::default(),
|
||||||
|
perp_market: Pubkey::default(),
|
||||||
|
owner: TestKeypair::default(),
|
||||||
|
side: Side::Bid,
|
||||||
|
price_lots: 0,
|
||||||
|
max_base_lots: i64::MAX,
|
||||||
|
max_quote_lots: i64::MAX,
|
||||||
|
reduce_only: false,
|
||||||
|
client_order_id: 0,
|
||||||
|
self_trade_behavior: SelfTradeBehavior::DecrementTake,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl ClientInstruction for PerpPlaceOrderInstruction {
|
impl ClientInstruction for PerpPlaceOrderInstruction {
|
||||||
type Accounts = mango_v4::accounts::PerpPlaceOrder;
|
type Accounts = mango_v4::accounts::PerpPlaceOrder;
|
||||||
type Instruction = mango_v4::instruction::PerpPlaceOrder;
|
type Instruction = mango_v4::instruction::PerpPlaceOrderV2;
|
||||||
async fn to_instruction(
|
async fn to_instruction(
|
||||||
&self,
|
&self,
|
||||||
account_loader: impl ClientAccountLoader + 'async_trait,
|
account_loader: impl ClientAccountLoader + 'async_trait,
|
||||||
|
@ -3270,6 +3287,7 @@ impl ClientInstruction for PerpPlaceOrderInstruction {
|
||||||
max_quote_lots: self.max_quote_lots,
|
max_quote_lots: self.max_quote_lots,
|
||||||
client_order_id: self.client_order_id,
|
client_order_id: self.client_order_id,
|
||||||
order_type: PlaceOrderType::Limit,
|
order_type: PlaceOrderType::Limit,
|
||||||
|
self_trade_behavior: self.self_trade_behavior,
|
||||||
reduce_only: self.reduce_only,
|
reduce_only: self.reduce_only,
|
||||||
expiry_timestamp: 0,
|
expiry_timestamp: 0,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
|
@ -3324,7 +3342,7 @@ pub struct PerpPlaceOrderPeggedInstruction {
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait(?Send)]
|
||||||
impl ClientInstruction for PerpPlaceOrderPeggedInstruction {
|
impl ClientInstruction for PerpPlaceOrderPeggedInstruction {
|
||||||
type Accounts = mango_v4::accounts::PerpPlaceOrder;
|
type Accounts = mango_v4::accounts::PerpPlaceOrder;
|
||||||
type Instruction = mango_v4::instruction::PerpPlaceOrderPegged;
|
type Instruction = mango_v4::instruction::PerpPlaceOrderPeggedV2;
|
||||||
async fn to_instruction(
|
async fn to_instruction(
|
||||||
&self,
|
&self,
|
||||||
account_loader: impl ClientAccountLoader + 'async_trait,
|
account_loader: impl ClientAccountLoader + 'async_trait,
|
||||||
|
@ -3340,6 +3358,7 @@ impl ClientInstruction for PerpPlaceOrderPeggedInstruction {
|
||||||
order_type: PlaceOrderType::Limit,
|
order_type: PlaceOrderType::Limit,
|
||||||
reduce_only: false,
|
reduce_only: false,
|
||||||
expiry_timestamp: 0,
|
expiry_timestamp: 0,
|
||||||
|
self_trade_behavior: SelfTradeBehavior::DecrementTake,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
max_oracle_staleness_slots: -1,
|
max_oracle_staleness_slots: -1,
|
||||||
};
|
};
|
||||||
|
|
|
@ -3593,6 +3593,112 @@ export type MangoV4 = {
|
||||||
"option": "u128"
|
"option": "u128"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "perpPlaceOrderV2",
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "group",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "account",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false,
|
||||||
|
"relations": [
|
||||||
|
"group"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "perpMarket",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false,
|
||||||
|
"relations": [
|
||||||
|
"group",
|
||||||
|
"bids",
|
||||||
|
"asks",
|
||||||
|
"event_queue",
|
||||||
|
"oracle"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bids",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "asks",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "eventQueue",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "oracle",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "side",
|
||||||
|
"type": {
|
||||||
|
"defined": "Side"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "priceLots",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maxBaseLots",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maxQuoteLots",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "clientOrderId",
|
||||||
|
"type": "u64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "orderType",
|
||||||
|
"type": {
|
||||||
|
"defined": "PlaceOrderType"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "selfTradeBehavior",
|
||||||
|
"type": {
|
||||||
|
"defined": "SelfTradeBehavior"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "reduceOnly",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "expiryTimestamp",
|
||||||
|
"type": "u64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "limit",
|
||||||
|
"type": "u8"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returns": {
|
||||||
|
"option": "u128"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "perpPlaceOrderPegged",
|
"name": "perpPlaceOrderPegged",
|
||||||
"accounts": [
|
"accounts": [
|
||||||
|
@ -3701,6 +3807,120 @@ export type MangoV4 = {
|
||||||
"option": "u128"
|
"option": "u128"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "perpPlaceOrderPeggedV2",
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "group",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "account",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false,
|
||||||
|
"relations": [
|
||||||
|
"group"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "perpMarket",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false,
|
||||||
|
"relations": [
|
||||||
|
"group",
|
||||||
|
"bids",
|
||||||
|
"asks",
|
||||||
|
"event_queue",
|
||||||
|
"oracle"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bids",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "asks",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "eventQueue",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "oracle",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "side",
|
||||||
|
"type": {
|
||||||
|
"defined": "Side"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "priceOffsetLots",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pegLimit",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maxBaseLots",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maxQuoteLots",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "clientOrderId",
|
||||||
|
"type": "u64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "orderType",
|
||||||
|
"type": {
|
||||||
|
"defined": "PlaceOrderType"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "selfTradeBehavior",
|
||||||
|
"type": {
|
||||||
|
"defined": "SelfTradeBehavior"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "reduceOnly",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "expiryTimestamp",
|
||||||
|
"type": "u64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "limit",
|
||||||
|
"type": "u8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maxOracleStalenessSlots",
|
||||||
|
"type": "i32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returns": {
|
||||||
|
"option": "u128"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "perpCancelOrder",
|
"name": "perpCancelOrder",
|
||||||
"accounts": [
|
"accounts": [
|
||||||
|
@ -7721,6 +7941,28 @@ export type MangoV4 = {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "SelfTradeBehavior",
|
||||||
|
"docs": [
|
||||||
|
"Self trade behavior controls how taker orders interact with resting limit orders of the same account.",
|
||||||
|
"This setting has no influence on placing a resting or oracle pegged limit order that does not match",
|
||||||
|
"immediately, instead it's the responsibility of the user to correctly configure his taker orders."
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "enum",
|
||||||
|
"variants": [
|
||||||
|
{
|
||||||
|
"name": "DecrementTake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CancelProvide"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AbortTransaction"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Side",
|
"name": "Side",
|
||||||
"type": {
|
"type": {
|
||||||
|
@ -9205,6 +9447,11 @@ export type MangoV4 = {
|
||||||
"type": "i64",
|
"type": "i64",
|
||||||
"index": false
|
"index": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "totalQuoteLotsDecremented",
|
||||||
|
"type": "i64",
|
||||||
|
"index": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "takerFeesPaid",
|
"name": "takerFeesPaid",
|
||||||
"type": "i128",
|
"type": "i128",
|
||||||
|
@ -9553,6 +9800,11 @@ export type MangoV4 = {
|
||||||
"code": 6047,
|
"code": 6047,
|
||||||
"name": "InvalidHealthAccountCount",
|
"name": "InvalidHealthAccountCount",
|
||||||
"msg": "incorrect number of health accounts"
|
"msg": "incorrect number of health accounts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6048,
|
||||||
|
"name": "WouldSelfTrade",
|
||||||
|
"msg": "would self trade"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
@ -13152,6 +13404,112 @@ export const IDL: MangoV4 = {
|
||||||
"option": "u128"
|
"option": "u128"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "perpPlaceOrderV2",
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "group",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "account",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false,
|
||||||
|
"relations": [
|
||||||
|
"group"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "perpMarket",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false,
|
||||||
|
"relations": [
|
||||||
|
"group",
|
||||||
|
"bids",
|
||||||
|
"asks",
|
||||||
|
"event_queue",
|
||||||
|
"oracle"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bids",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "asks",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "eventQueue",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "oracle",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "side",
|
||||||
|
"type": {
|
||||||
|
"defined": "Side"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "priceLots",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maxBaseLots",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maxQuoteLots",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "clientOrderId",
|
||||||
|
"type": "u64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "orderType",
|
||||||
|
"type": {
|
||||||
|
"defined": "PlaceOrderType"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "selfTradeBehavior",
|
||||||
|
"type": {
|
||||||
|
"defined": "SelfTradeBehavior"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "reduceOnly",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "expiryTimestamp",
|
||||||
|
"type": "u64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "limit",
|
||||||
|
"type": "u8"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returns": {
|
||||||
|
"option": "u128"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "perpPlaceOrderPegged",
|
"name": "perpPlaceOrderPegged",
|
||||||
"accounts": [
|
"accounts": [
|
||||||
|
@ -13260,6 +13618,120 @@ export const IDL: MangoV4 = {
|
||||||
"option": "u128"
|
"option": "u128"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "perpPlaceOrderPeggedV2",
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "group",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "account",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false,
|
||||||
|
"relations": [
|
||||||
|
"group"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "perpMarket",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false,
|
||||||
|
"relations": [
|
||||||
|
"group",
|
||||||
|
"bids",
|
||||||
|
"asks",
|
||||||
|
"event_queue",
|
||||||
|
"oracle"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bids",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "asks",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "eventQueue",
|
||||||
|
"isMut": true,
|
||||||
|
"isSigner": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "oracle",
|
||||||
|
"isMut": false,
|
||||||
|
"isSigner": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "side",
|
||||||
|
"type": {
|
||||||
|
"defined": "Side"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "priceOffsetLots",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pegLimit",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maxBaseLots",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maxQuoteLots",
|
||||||
|
"type": "i64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "clientOrderId",
|
||||||
|
"type": "u64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "orderType",
|
||||||
|
"type": {
|
||||||
|
"defined": "PlaceOrderType"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "selfTradeBehavior",
|
||||||
|
"type": {
|
||||||
|
"defined": "SelfTradeBehavior"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "reduceOnly",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "expiryTimestamp",
|
||||||
|
"type": "u64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "limit",
|
||||||
|
"type": "u8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maxOracleStalenessSlots",
|
||||||
|
"type": "i32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returns": {
|
||||||
|
"option": "u128"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "perpCancelOrder",
|
"name": "perpCancelOrder",
|
||||||
"accounts": [
|
"accounts": [
|
||||||
|
@ -17280,6 +17752,28 @@ export const IDL: MangoV4 = {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "SelfTradeBehavior",
|
||||||
|
"docs": [
|
||||||
|
"Self trade behavior controls how taker orders interact with resting limit orders of the same account.",
|
||||||
|
"This setting has no influence on placing a resting or oracle pegged limit order that does not match",
|
||||||
|
"immediately, instead it's the responsibility of the user to correctly configure his taker orders."
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "enum",
|
||||||
|
"variants": [
|
||||||
|
{
|
||||||
|
"name": "DecrementTake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CancelProvide"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AbortTransaction"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Side",
|
"name": "Side",
|
||||||
"type": {
|
"type": {
|
||||||
|
@ -18764,6 +19258,11 @@ export const IDL: MangoV4 = {
|
||||||
"type": "i64",
|
"type": "i64",
|
||||||
"index": false
|
"index": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "totalQuoteLotsDecremented",
|
||||||
|
"type": "i64",
|
||||||
|
"index": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "takerFeesPaid",
|
"name": "takerFeesPaid",
|
||||||
"type": "i128",
|
"type": "i128",
|
||||||
|
@ -19112,6 +19611,11 @@ export const IDL: MangoV4 = {
|
||||||
"code": 6047,
|
"code": 6047,
|
||||||
"name": "InvalidHealthAccountCount",
|
"name": "InvalidHealthAccountCount",
|
||||||
"msg": "incorrect number of health accounts"
|
"msg": "incorrect number of health accounts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": 6048,
|
||||||
|
"name": "WouldSelfTrade",
|
||||||
|
"msg": "would self trade"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue