Skip to content

Commit

Permalink
Store price improvement fee (#2470)
Browse files Browse the repository at this point in the history
# Description
Fixes #2478

# Changes

- [ ] `max_volume_factor` column is renamed to
`surplus_max_volume_factor` in order to align with the naming convention
- [ ] Postgres roundtrip tests
- [ ] Readme
  • Loading branch information
squadgazzz authored Mar 8, 2024
1 parent 06df38c commit 55d7a6d
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 60 deletions.
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;

0 comments on commit 55d7a6d

Please sign in to comment.