Skip to content

Commit

Permalink
fix: make sure to sort bonus tiers by fee rebate
Browse files Browse the repository at this point in the history
  • Loading branch information
bonomat committed Apr 8, 2024
1 parent e49ee5b commit eda9f1a
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 90 deletions.
1 change: 0 additions & 1 deletion coordinator/src/db/bonus_status.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::db::bonus_tiers;
use crate::db::referral_tiers::tier_by_tier_level;
use crate::schema::bonus_status;
use crate::schema::sql_types::BonusStatusType;
use bitcoin::secp256k1::PublicKey;
Expand Down
70 changes: 44 additions & 26 deletions coordinator/src/orderbook/trading.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use futures::future::RemoteHandle;
use futures::FutureExt;
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use rust_decimal::RoundingStrategy;
use std::cmp::Ordering;
use time::OffsetDateTime;
use tokio::sync::broadcast;
Expand Down Expand Up @@ -222,28 +223,33 @@ pub async fn process_new_market_order(
trader_pubkey = trader_pubkey_string,
%fee_discount, total_fee_percent = %fee_percent, "Fee discount calculated");

let matched_orders =
match match_order(&order, opposite_direction_limit_orders, network, oracle_pk) {
Ok(Some(matched_orders)) => matched_orders,
Ok(None) => {
// TODO(holzeis): Currently we still respond to the user immediately if there
// has been a match or not, that's the reason why we also have to set the order
// to failed here. But actually we could keep the order until either expired or
// a match has been found and then update the state accordingly.

orders::set_order_state(&mut conn, order.id, OrderState::Failed)
.map_err(|e| anyhow!("{e:#}"))?;
return Err(TradingError::NoMatchFound(format!(
"Could not match order {}",
order.id
)));
}
Err(e) => {
orders::set_order_state(&mut conn, order.id, OrderState::Failed)
.map_err(|e| anyhow!("{e:#}"))?;
return Err(TradingError::Other(format!("Failed to match order: {e:#}")));
}
};
let matched_orders = match match_order(
&order,
opposite_direction_limit_orders,
network,
oracle_pk,
fee_percent,
) {
Ok(Some(matched_orders)) => matched_orders,
Ok(None) => {
// TODO(holzeis): Currently we still respond to the user immediately if there
// has been a match or not, that's the reason why we also have to set the order
// to failed here. But actually we could keep the order until either expired or
// a match has been found and then update the state accordingly.

orders::set_order_state(&mut conn, order.id, OrderState::Failed)
.map_err(|e| anyhow!("{e:#}"))?;
return Err(TradingError::NoMatchFound(format!(
"Could not match order {}",
order.id
)));
}
Err(e) => {
orders::set_order_state(&mut conn, order.id, OrderState::Failed)
.map_err(|e| anyhow!("{e:#}"))?;
return Err(TradingError::Other(format!("Failed to match order: {e:#}")));
}
};

tracing::info!(
trader_id=%order.trader_id,
Expand Down Expand Up @@ -356,6 +362,7 @@ fn match_order(
opposite_direction_orders: Vec<Order>,
network: Network,
oracle_pk: XOnlyPublicKey,
fee_percent: Decimal,
) -> Result<Option<MatchParams>> {
if market_order.order_type == OrderType::Limit {
// We don't match limit orders with other limit orders at the moment.
Expand Down Expand Up @@ -392,13 +399,21 @@ fn match_order(

let expiry_timestamp = commons::calculate_next_expiry(OffsetDateTime::now_utc(), network);

// TODO(bonomat:ordermatching): get this from somewhere. Get the tier for the trader and the fee
// from settings
let matching_fee = Amount::ZERO;

let matches = matched_orders
.iter()
.map(|maker_order| {
let matching_fee = market_order.quantity / maker_order.price * fee_percent;
let matching_fee = matching_fee.round_dp_with_strategy(8, RoundingStrategy::MidpointAwayFromZero);
let matching_fee = match Amount::from_btc(matching_fee.to_f64().expect("to fit")) {
Ok(fee) => {fee}
Err(err) => {
tracing::error!(
trader_pubkey = maker_order.trader_id.to_string(),
order_id = maker_order.id.to_string(),
"Failed calculating order matching fee for order {err:?}. Falling back to 0");
Amount::ZERO
}
};
(
TraderMatchParams {
trader_id: maker_order.trader_id,
Expand Down Expand Up @@ -655,6 +670,7 @@ mod tests {
all_orders,
Network::Bitcoin,
get_oracle_public_key(),
Decimal::ZERO,
)
.unwrap()
.unwrap();
Expand Down Expand Up @@ -737,6 +753,7 @@ mod tests {
all_orders,
Network::Bitcoin,
get_oracle_public_key(),
Decimal::ZERO,
)
.is_err());
}
Expand Down Expand Up @@ -794,6 +811,7 @@ mod tests {
all_orders,
Network::Bitcoin,
get_oracle_public_key(),
Decimal::ZERO,
)
.unwrap();

Expand Down
61 changes: 11 additions & 50 deletions coordinator/src/referrals.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use crate::db;
use crate::db::bonus_status::BonusStatus;
use crate::db::bonus_status::BonusType;
use crate::db::bonus_tiers::BonusTier;
use crate::db::user::User;
use anyhow::Context;
use anyhow::Result;
use bitcoin::secp256k1::PublicKey;
Expand All @@ -26,14 +24,17 @@ pub fn get_referral_status(
let mut bonus_status = db::bonus_status::active_status_for_user(connection, &trader_pubkey)?;
let user = db::user::get_user(connection, &trader_pubkey)?.context("User not found")?;

// we sort by tier_level, higher tier means better bonus
bonus_status.sort_by(|a, b| b.tier_level.cmp(&a.tier_level));
// we sort by fee_rebate
bonus_status.sort_by(|a, b| {
b.fee_rebate
.partial_cmp(&a.fee_rebate)
.expect("to be able to sort")
});

// next we pick the highest
if let Some(bonus) = bonus_status.first() {
let referrals =
db::bonus_tiers::all_referrals_by_referring_user(connection, &trader_pubkey)?;
let tier = db::bonus_tiers::tier_by_tier_level(connection, bonus.tier_level)?;

return Ok(ReferralStatus {
referral_code: user.referral_code,
Expand All @@ -42,49 +43,16 @@ pub fn get_referral_status(
.filter(|referral| referral.referred_user_total_quantity.floor() > 0.0)
.count(),
number_of_total_referrals: referrals.len(),
referral_tier: tier.tier_level as usize,
referral_fee_bonus: Decimal::from_f32(tier.fee_rebate).expect("to fit"),
referral_tier: bonus.tier_level as usize,
referral_fee_bonus: Decimal::from_f32(bonus.fee_rebate).expect("to fit"),
bonus_status_type: Some(bonus.bonus_type.into()),
});
}

// if he has no one referred so far, we check for an active referent bonus
if let Some(status) = has_active_referent_bonus(connection, bonus_status.as_slice(), &user)? {
return Ok(status);
}

// None of the above, user is a boring normal user
Ok(ReferralStatus::new(trader_pubkey))
}

fn has_active_referent_bonus(
connection: &mut PooledConnection<ConnectionManager<PgConnection>>,
bonus_status: &[BonusStatus],
user: &User,
) -> Result<Option<ReferralStatus>> {
if let Some(bonus) = bonus_status
.iter()
.find(|status| BonusType::Referent == status.bonus_type)
{
tracing::debug!(
trader_pubkey = bonus.trader_pubkey,
bonus_type = ?bonus.bonus_type,
tier_level = bonus.tier_level,
"Trader was referred");
let tier = db::bonus_tiers::tier_by_tier_level(connection, bonus.tier_level)?;

return Ok(Some(ReferralStatus {
referral_code: user.referral_code.clone(),
number_of_activated_referrals: 0,
number_of_total_referrals: 0,
referral_tier: tier.tier_level as usize,
referral_fee_bonus: Decimal::from_f32(tier.fee_rebate).expect("to fit"),
bonus_status_type: Some(bonus.bonus_type.into()),
}));
}
Ok(None)
}

pub fn update_referral_status(
connection: &mut PooledConnection<ConnectionManager<PgConnection>>,
) -> Result<usize> {
Expand Down Expand Up @@ -186,18 +154,11 @@ pub fn update_referral_status_for_user(

tracing::debug!(
trader_pubkey = trader_pubkey.to_string(),
"Trader doesn't have any referral status yet"
"Trader doesn't have any new referral status yet"
);

// User doesn't have any referral status yet
Ok(ReferralStatus {
referral_code,
number_of_activated_referrals: 0,
number_of_total_referrals: 0,
referral_tier: 0,
referral_fee_bonus: Decimal::ZERO,
bonus_status_type: None,
})
// User doesn't have any new referral status yet
Ok(status)
}

/// Returns the tier_level of the calculated tier.
Expand Down
9 changes: 8 additions & 1 deletion coordinator/src/scheduler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,14 @@ fn build_update_bonus_status_job(
pool: Pool<ConnectionManager<PgConnection>>,
) -> Result<Job, JobSchedulerError> {
Job::new_async(schedule, move |_, _| {
let mut conn = pool.get().expect("To be able to get a db connection");
let mut conn = match pool.get() {
Ok(conn) => conn,
Err(e) => {
return Box::pin(async move {
tracing::error!("Failed to get connection. Error: {e:#}")
});
}
};

match referrals::update_referral_status(&mut conn) {
Ok(number_of_updated_users) => Box::pin({
Expand Down
12 changes: 0 additions & 12 deletions coordinator/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,17 +383,6 @@ diesel::table! {
}
}

diesel::table! {
referral_tiers (id) {
id -> Int4,
tier_level -> Int4,
min_users_to_refer -> Int4,
fee_rebate -> Float4,
number_of_trades -> Int4,
active -> Bool,
}
}

diesel::table! {
routing_fees (id) {
id -> Int4,
Expand Down Expand Up @@ -503,7 +492,6 @@ diesel::allow_tables_to_appear_in_same_query!(
polls,
polls_whitelist,
positions,
referral_tiers,
routing_fees,
spendable_outputs,
trade_params,
Expand Down

0 comments on commit eda9f1a

Please sign in to comment.