Skip to content

Commit

Permalink
Price improvemenet fee policy autopilot domain (#2378)
Browse files Browse the repository at this point in the history
# Description
Closes task n2 from #2287
  • Loading branch information
squadgazzz authored Feb 12, 2024
1 parent 72cac8c commit 96f4f7b
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 73 deletions.
18 changes: 1 addition & 17 deletions crates/autopilot/src/arguments.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use {
crate::{domain, infra},
crate::infra,
primitive_types::{H160, U256},
shared::{
arguments::{display_list, display_option, ExternalSolver},
Expand Down Expand Up @@ -367,22 +367,6 @@ pub struct FeePolicy {
pub fee_policy_skip_market_orders: bool,
}

impl FeePolicy {
pub fn to_domain(self) -> domain::fee::Policy {
match self.fee_policy_kind {
FeePolicyKind::Surplus {
factor,
max_volume_factor,
} => domain::fee::Policy::Surplus {
factor,
max_volume_factor,
},
FeePolicyKind::PriceImprovement { .. } => todo!(),
FeePolicyKind::Volume { factor } => domain::fee::Policy::Volume { factor },
}
}
}

#[derive(clap::Parser, Debug, Clone)]
pub enum FeePolicyKind {
/// How much of the order's surplus should be taken as a protocol fee.
Expand Down
98 changes: 51 additions & 47 deletions crates/autopilot/src/domain/fee/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,65 +4,41 @@
//! we define the way to calculate the protocol fee based on the configuration
//! parameters.
use crate::{
boundary::{self},
domain,
mod policy;

use {
crate::{
arguments,
boundary::{self},
domain,
},
itertools::Itertools,
primitive_types::U256,
};

/// Constructs fee policies based on the current configuration.
#[derive(Debug)]
pub struct ProtocolFee {
policy: Policy,
fee_policy_skip_market_orders: bool,
policy: policy::Policy,
}

impl ProtocolFee {
pub fn new(policy: Policy, fee_policy_skip_market_orders: bool) -> Self {
pub fn new(fee_policy_args: arguments::FeePolicy) -> Self {
Self {
policy,
fee_policy_skip_market_orders,
policy: fee_policy_args.into(),
}
}

/// Get policies for order.
pub fn get(&self, order: &boundary::Order, quote: Option<&domain::Quote>) -> Vec<Policy> {
match order.metadata.class {
boundary::OrderClass::Market => {
if self.fee_policy_skip_market_orders {
vec![]
} else {
vec![self.policy]
}
}
boundary::OrderClass::Liquidity => vec![],
boundary::OrderClass::Limit => {
if !self.fee_policy_skip_market_orders {
return vec![self.policy];
}

// if the quote is missing, we can't determine if the order is outside the
// market price so we protect the user and not charge a fee
let Some(quote) = quote else {
return vec![];
};

let order_ = boundary::Amounts {
sell: order.data.sell_amount,
buy: order.data.buy_amount,
fee: order.data.fee_amount,
};
let quote = boundary::Amounts {
sell: quote.sell_amount,
buy: quote.buy_amount,
fee: quote.fee,
};
if boundary::is_order_outside_market_price(&order_, &quote) {
vec![self.policy]
} else {
vec![]
}
}
/// Converts an order from the boundary layer to the domain layer, applying
/// protocol fees if necessary.
pub fn apply(&self, order: boundary::Order, quote: &domain::Quote) -> domain::Order {
let protocol_fees = match &self.policy {
policy::Policy::Surplus(variant) => variant.apply(&order, quote),
policy::Policy::PriceImprovement(variant) => variant.apply(&order, quote),
policy::Policy::Volume(variant) => variant.apply(&order),
}
.into_iter()
.collect_vec();
boundary::order::to_domain(order, protocol_fees)
}
}

Expand All @@ -83,6 +59,14 @@ pub enum Policy {
/// Cap protocol fee with a percentage of the order's volume.
max_volume_factor: f64,
},
/// A price improvement corresponds to a situation where the order is
/// executed at a better price than the top quote. The protocol fee in such
/// case is calculated from a cut of this price improvement.
PriceImprovement {
factor: f64,
max_volume_factor: f64,
quote: Quote,
},
/// How much of the order's volume should be taken as a protocol fee.
/// The fee is taken in `sell` token for `sell` orders and in `buy`
/// token for `buy` orders.
Expand All @@ -92,3 +76,23 @@ pub enum Policy {
factor: f64,
},
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Quote {
/// The amount of the sell token.
pub sell_amount: U256,
/// The amount of the buy token.
pub buy_amount: U256,
/// The amount that needs to be paid, denominated in the sell token.
pub fee: U256,
}

impl From<domain::Quote> for Quote {
fn from(value: domain::Quote) -> Self {
Self {
sell_amount: value.sell_amount,
buy_amount: value.buy_amount,
fee: value.fee,
}
}
}
110 changes: 110 additions & 0 deletions crates/autopilot/src/domain/fee/policy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
use crate::{arguments, boundary, domain};

pub enum Policy {
Surplus(Surplus),
PriceImprovement(PriceImprovement),
Volume(Volume),
}

pub struct Surplus {
factor: f64,
max_volume_factor: f64,
skip_market_orders: bool,
}

pub struct PriceImprovement {
factor: f64,
max_volume_factor: f64,
}

pub struct Volume {
factor: f64,
}

impl From<arguments::FeePolicy> for Policy {
fn from(policy_arg: arguments::FeePolicy) -> Self {
match policy_arg.fee_policy_kind {
arguments::FeePolicyKind::Surplus {
factor,
max_volume_factor,
} => Policy::Surplus(Surplus {
factor,
max_volume_factor,
skip_market_orders: policy_arg.fee_policy_skip_market_orders,
}),
arguments::FeePolicyKind::PriceImprovement {
factor,
max_volume_factor,
} => Policy::PriceImprovement(PriceImprovement {
factor,
max_volume_factor,
}),
arguments::FeePolicyKind::Volume { factor } => Policy::Volume(Volume { factor }),
}
}
}

impl Surplus {
pub fn apply(
&self,
order: &boundary::Order,
quote: &domain::Quote,
) -> Option<domain::fee::Policy> {
match order.metadata.class {
boundary::OrderClass::Market => None,
boundary::OrderClass::Liquidity => None,
boundary::OrderClass::Limit => {
let policy = domain::fee::Policy::Surplus {
factor: self.factor,
max_volume_factor: self.max_volume_factor,
};
if !self.skip_market_orders {
Some(policy)
} else {
let order_ = boundary::Amounts {
sell: order.data.sell_amount,
buy: order.data.buy_amount,
fee: order.data.fee_amount,
};
let quote_ = boundary::Amounts {
sell: quote.sell_amount,
buy: quote.buy_amount,
fee: quote.fee,
};

boundary::is_order_outside_market_price(&order_, &quote_).then_some(policy)
}
}
}
}
}

impl PriceImprovement {
pub fn apply(
&self,
order: &boundary::Order,
quote: &domain::Quote,
) -> Option<domain::fee::Policy> {
match order.metadata.class {
boundary::OrderClass::Market => None,
boundary::OrderClass::Liquidity => None,
boundary::OrderClass::Limit => Some(domain::fee::Policy::PriceImprovement {
factor: self.factor,
max_volume_factor: self.max_volume_factor,
quote: quote.clone().into(),
}),
}
}
}

impl Volume {
pub fn apply(&self, order: &boundary::Order) -> Option<domain::fee::Policy> {
match order.metadata.class {
boundary::OrderClass::Market => None,
boundary::OrderClass::Liquidity => None,
boundary::OrderClass::Limit => Some(domain::fee::Policy::Volume {
factor: self.factor,
}),
}
}
}
14 changes: 14 additions & 0 deletions crates/autopilot/src/infra/persistence/dto/fee_policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ impl FeePolicy {
max_volume_factor: None,
volume_factor: Some(factor),
},
domain::fee::Policy::PriceImprovement {
factor,
max_volume_factor,
..
} => Self {
auction_id,
order_uid: boundary::database::byte_array::ByteArray(order_uid.0),
kind: FeePolicyKind::PriceImprovement,
surplus_factor: Some(factor),
max_volume_factor: Some(max_volume_factor),
volume_factor: None,
},
}
}
}
Expand All @@ -50,6 +62,7 @@ impl From<FeePolicy> for domain::fee::Policy {
FeePolicyKind::Volume => domain::fee::Policy::Volume {
factor: row.volume_factor.expect("missing volume factor"),
},
FeePolicyKind::PriceImprovement => todo!(),
}
}
}
Expand All @@ -59,4 +72,5 @@ impl From<FeePolicy> for domain::fee::Policy {
pub enum FeePolicyKind {
Surplus,
Volume,
PriceImprovement,
}
41 changes: 41 additions & 0 deletions crates/autopilot/src/infra/persistence/dto/order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,9 +268,24 @@ pub enum FeePolicy {
#[serde(rename_all = "camelCase")]
Surplus { factor: f64, max_volume_factor: f64 },
#[serde(rename_all = "camelCase")]
PriceImprovement {
factor: f64,
max_volume_factor: f64,
quote: Quote,
},
#[serde(rename_all = "camelCase")]
Volume { factor: f64 },
}

#[serde_as]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Quote {
pub sell_amount: U256,
pub buy_amount: U256,
pub fee: U256,
}

impl From<domain::fee::Policy> for FeePolicy {
fn from(policy: domain::fee::Policy) -> Self {
match policy {
Expand All @@ -281,6 +296,19 @@ impl From<domain::fee::Policy> for FeePolicy {
factor,
max_volume_factor,
},
domain::fee::Policy::PriceImprovement {
factor,
max_volume_factor,
quote,
} => Self::PriceImprovement {
factor,
max_volume_factor,
quote: Quote {
sell_amount: quote.sell_amount,
buy_amount: quote.buy_amount,
fee: quote.fee,
},
},
domain::fee::Policy::Volume { factor } => Self::Volume { factor },
}
}
Expand All @@ -296,6 +324,19 @@ impl From<FeePolicy> for domain::fee::Policy {
factor,
max_volume_factor,
},
FeePolicy::PriceImprovement {
factor,
max_volume_factor,
quote,
} => Self::PriceImprovement {
factor,
max_volume_factor,
quote: domain::fee::Quote {
sell_amount: quote.sell_amount,
buy_amount: quote.buy_amount,
fee: quote.fee,
},
},
FeePolicy::Volume { factor } => Self::Volume { factor },
}
}
Expand Down
5 changes: 1 addition & 4 deletions crates/autopilot/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -553,10 +553,7 @@ pub async fn run(args: Arguments) {
args.limit_order_price_factor
.try_into()
.expect("limit order price factor can't be converted to BigDecimal"),
domain::ProtocolFee::new(
args.fee_policy.clone().to_domain(),
args.fee_policy.fee_policy_skip_market_orders,
),
domain::ProtocolFee::new(args.fee_policy.clone()),
);
solvable_orders_cache
.update(block)
Expand Down
Loading

0 comments on commit 96f4f7b

Please sign in to comment.