Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Store price improvement fee #2470

Merged
merged 15 commits into from
Mar 8, 2024
88 changes: 69 additions & 19 deletions crates/autopilot/src/database/fee_policies.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,34 @@
use {
crate::infra::persistence::dto,
crate::{domain, infra::persistence::dto},
sqlx::{PgConnection, QueryBuilder},
};

pub async fn insert_batch(
ex: &mut PgConnection,
fee_policies: impl IntoIterator<Item = dto::FeePolicy>,
auction_id: domain::auction::Id,
fee_policies: impl IntoIterator<Item = (domain::OrderUid, Vec<domain::fee::Policy>)>,
) -> Result<(), sqlx::Error> {
let mut query_builder = QueryBuilder::new(
"INSERT INTO fee_policies (auction_id, order_uid, kind, surplus_factor, \
max_volume_factor, volume_factor) ",
surplus_max_volume_factor, volume_factor, price_improvement_factor, \
price_improvement_max_volume_factor)",
);

let fee_policies = fee_policies.into_iter().flat_map(|(order_uid, policies)| {
policies
.into_iter()
.map(move |policy| dto::FeePolicy::from_domain(auction_id, order_uid, policy))
});

query_builder.push_values(fee_policies, |mut b, fee_policy| {
b.push_bind(fee_policy.auction_id)
.push_bind(fee_policy.order_uid)
.push_bind(fee_policy.kind)
.push_bind(fee_policy.surplus_factor)
.push_bind(fee_policy.max_volume_factor)
.push_bind(fee_policy.volume_factor);
.push_bind(fee_policy.surplus_max_volume_factor)
.push_bind(fee_policy.volume_factor)
.push_bind(fee_policy.price_improvement_factor)
.push_bind(fee_policy.price_improvement_max_volume_factor);
});

query_builder.build().execute(ex).await.map(|_| ())
Expand Down Expand Up @@ -58,45 +68,85 @@ mod tests {
// same primary key for all fee policies
let (auction_id, order_uid) = (1, ByteArray([1; 56]));

// surplus fee policy without caps
let fee_policy_1 = domain::fee::Policy::Surplus {
factor: 0.1,
max_volume_factor: 1.0,
};
// surplus fee policy with caps
let fee_policy_2 = domain::fee::Policy::Surplus {
factor: 0.2,
max_volume_factor: 0.05,
};
// volume based fee policy
let fee_policy_3 = domain::fee::Policy::Volume { factor: 0.06 };
// price improvement fee policy
let fee_policy_4 = domain::fee::Policy::PriceImprovement {
factor: 0.1,
max_volume_factor: 1.0,
quote: domain::fee::Quote {
sell_amount: 10.into(),
buy_amount: 20.into(),
fee: 1.into(),
},
};
let input_policies = vec![fee_policy_1, fee_policy_2, fee_policy_3, fee_policy_4];

insert_batch(
&mut db,
auction_id,
vec![(domain::OrderUid(order_uid.0), input_policies)],
)
.await
.unwrap();

// surplus fee policy without caps
let fee_policy_1 = dto::FeePolicy {
auction_id,
order_uid,
kind: dto::fee_policy::FeePolicyKind::Surplus,
surplus_factor: Some(0.1),
max_volume_factor: Some(1.0),
surplus_max_volume_factor: Some(1.0),
volume_factor: None,
price_improvement_factor: None,
price_improvement_max_volume_factor: None,
};
// surplus fee policy with caps
let fee_policy_2 = dto::FeePolicy {
auction_id,
order_uid,
kind: dto::fee_policy::FeePolicyKind::Surplus,
surplus_factor: Some(0.2),
max_volume_factor: Some(0.05),
surplus_max_volume_factor: Some(0.05),
volume_factor: None,
price_improvement_factor: None,
price_improvement_max_volume_factor: None,
};
// volume based fee policy
let fee_policy_3 = dto::FeePolicy {
auction_id,
order_uid,
kind: dto::fee_policy::FeePolicyKind::Volume,
surplus_factor: None,
max_volume_factor: None,
surplus_max_volume_factor: None,
volume_factor: Some(0.06),
price_improvement_factor: None,
price_improvement_max_volume_factor: None,
};
insert_batch(
&mut db,
vec![
fee_policy_1.clone(),
fee_policy_2.clone(),
fee_policy_3.clone(),
],
)
.await
.unwrap();
// price improvement fee policy
let fee_policy_4 = dto::FeePolicy {
auction_id,
order_uid,
kind: dto::fee_policy::FeePolicyKind::PriceImprovement,
surplus_factor: None,
surplus_max_volume_factor: None,
volume_factor: None,
price_improvement_factor: Some(0.1),
price_improvement_max_volume_factor: Some(1.0),
};
let expected = vec![fee_policy_1, fee_policy_2, fee_policy_3, fee_policy_4];

let output = fetch(&mut db, 1, order_uid).await.unwrap();
assert_eq!(output, vec![fee_policy_1, fee_policy_2, fee_policy_3]);
assert_eq!(output, expected);
}
}
35 changes: 14 additions & 21 deletions crates/autopilot/src/infra/persistence/dto/fee_policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ pub struct FeePolicy {
pub order_uid: boundary::database::OrderUid,
pub kind: FeePolicyKind,
pub surplus_factor: Option<f64>,
pub max_volume_factor: Option<f64>,
pub surplus_max_volume_factor: Option<f64>,
pub volume_factor: Option<f64>,
pub price_improvement_factor: Option<f64>,
pub price_improvement_max_volume_factor: Option<f64>,
}

impl FeePolicy {
Expand All @@ -25,48 +27,39 @@ impl FeePolicy {
order_uid: boundary::database::byte_array::ByteArray(order_uid.0),
kind: FeePolicyKind::Surplus,
surplus_factor: Some(factor),
max_volume_factor: Some(max_volume_factor),
surplus_max_volume_factor: Some(max_volume_factor),
volume_factor: None,
price_improvement_factor: None,
price_improvement_max_volume_factor: None,
},
domain::fee::Policy::Volume { factor } => Self {
auction_id,
order_uid: boundary::database::byte_array::ByteArray(order_uid.0),
kind: FeePolicyKind::Volume,
surplus_factor: None,
max_volume_factor: None,
surplus_max_volume_factor: None,
volume_factor: Some(factor),
price_improvement_factor: None,
price_improvement_max_volume_factor: None,
},
domain::fee::Policy::PriceImprovement {
factor,
max_volume_factor,
..
quote: _,
} => 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),
surplus_factor: None,
surplus_max_volume_factor: None,
volume_factor: None,
price_improvement_factor: Some(factor),
price_improvement_max_volume_factor: Some(max_volume_factor),
},
}
}
}

impl From<FeePolicy> for domain::fee::Policy {
fn from(row: FeePolicy) -> domain::fee::Policy {
match row.kind {
FeePolicyKind::Surplus => domain::fee::Policy::Surplus {
factor: row.surplus_factor.expect("missing surplus factor"),
max_volume_factor: row.max_volume_factor.expect("missing max volume factor"),
},
FeePolicyKind::Volume => domain::fee::Policy::Volume {
factor: row.volume_factor.expect("missing volume factor"),
},
FeePolicyKind::PriceImprovement => todo!(),
}
}
}

#[derive(Debug, Clone, PartialEq, sqlx::Type)]
#[sqlx(type_name = "PolicyKind", rename_all = "lowercase")]
pub enum FeePolicyKind {
Expand Down
12 changes: 1 addition & 11 deletions crates/autopilot/src/infra/persistence/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use {
crate::{boundary, database::Postgres, domain},
anyhow::Context,
chrono::Utc,
itertools::Itertools,
std::sync::Arc,
tokio::time::Instant,
tracing::Instrument,
Expand Down Expand Up @@ -122,18 +121,9 @@ impl Persistence {
auction_id: domain::auction::Id,
fee_policies: Vec<(domain::OrderUid, Vec<domain::fee::Policy>)>,
) -> anyhow::Result<()> {
let fee_policies = fee_policies
.into_iter()
.flat_map(|(order_uid, policies)| {
policies
.into_iter()
.map(move |policy| dto::FeePolicy::from_domain(auction_id, order_uid, policy))
})
.collect_vec();

let mut ex = self.postgres.pool.begin().await.context("begin")?;
for chunk in fee_policies.chunks(self.postgres.config.insert_batch_size.get()) {
crate::database::fee_policies::insert_batch(&mut ex, chunk.iter().cloned())
crate::database::fee_policies::insert_batch(&mut ex, auction_id, chunk.iter().cloned())
.await
.context("fee_policies::insert_batch")?;
}
Expand Down
21 changes: 12 additions & 9 deletions database/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,15 +239,17 @@ Indexes:

Contains all relevant data of fee policies applied to orders during auctions.

Column | Type | Nullable | Details
--------------------------|------------------------------|----------|--------
auction_id | bigint | not null | unique identifier for the auction
order_uid | bytea | not null | 56 bytes identifier linking to the order in the `orders` table
application_order | serial | not null | the order in which the fee policies are inserted and applied
kind | [PolicyKind](#policykind) | not null | type of the fee policy, defined in the PolicyKind enum
surplus_factor | double precision | | percentage of the surplus for fee calculation; value is between 0 and 1
max_volume_factor | double precision | | cap for the fee as a percentage of the order volume; value is between 0 and 1
volume_factor | double precision | | fee percentage of the order volume; value is between 0 and 1
Column | Type | Nullable | Details
-------------------------------------|------------------------------|----------|--------
auction_id | bigint | not null | unique identifier for the auction
order_uid | bytea | not null | 56 bytes identifier linking to the order in the `orders` table
application_order | serial | not null | the order in which the fee policies are inserted and applied
kind | [PolicyKind](#policykind) | not null | type of the fee policy, defined in the PolicyKind enum
surplus_factor | double precision | | percentage of the surplus for fee calculation; value is between 0 and 1
surplus_max_volume_factor | double precision | | cap for the fee as a percentage of the order volume; value is between 0 and 1
volume_factor | double precision | | fee percentage of the order volume; value is between 0 and 1
price_improvement_factor | double precision | | percentage of the price improvement over the best quote received during order creation; value is between 0 and 1
price_improvement_max_volume_factor | double precision | | cap for the fee as a percentage of the order volume; value is between 0 and 1

Indexes:
- PRIMARY KEY: composite key(`auction_id`, `order_uid`, `application_order`)
Expand All @@ -259,6 +261,7 @@ Indexes:

Values:
- `surplus`: The fee is based on the surplus achieved in the trade.
- `priceimprovement`: The fee is based on a better executed price than the top quote.
- `volume`: The fee is based on the volume of the order.

### presignature\_events
Expand Down
11 changes: 11 additions & 0 deletions database/sql/V063__price_improvement_policy_fee.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- Rename max_volume_factor column
ALTER TABLE fee_policies
RENAME COLUMN max_volume_factor TO surplus_max_volume_factor;

-- Add `price_improvement` policy fee kind
ALTER TYPE PolicyKind ADD VALUE 'priceimprovement';

-- Add price improvement fee columns
ALTER TABLE fee_policies
ADD COLUMN price_improvement_factor double precision,
ADD COLUMN price_improvement_max_volume_factor double precision;
Loading