Skip to content

Commit

Permalink
feat: Display next funding rate in app
Browse files Browse the repository at this point in the history
  • Loading branch information
luckysori committed May 29, 2024
1 parent 2ea8c14 commit 3ce7e2f
Show file tree
Hide file tree
Showing 17 changed files with 218 additions and 95 deletions.
27 changes: 20 additions & 7 deletions coordinator/src/db/funding_rates.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use crate::funding_fee;
use crate::schema::funding_rates;
use crate::to_nearest_hour_in_the_past;
use anyhow::Context;
use anyhow::Result;
use diesel::prelude::*;
use rust_decimal::prelude::FromPrimitive;
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use time::OffsetDateTime;
use xxi_node::commons::to_nearest_hour_in_the_past;

#[derive(Queryable, Debug)]
struct FundingRate {
Expand All @@ -22,7 +21,7 @@ struct FundingRate {

pub(crate) fn insert(
conn: &mut PgConnection,
funding_rates: &[funding_fee::FundingRate],
funding_rates: &[xxi_node::commons::FundingRate],
) -> Result<()> {
conn.transaction(|conn| {
for funding_rate in funding_rates {
Expand All @@ -34,7 +33,7 @@ pub(crate) fn insert(
})
}

fn insert_one(conn: &mut PgConnection, params: &funding_fee::FundingRate) -> QueryResult<()> {
fn insert_one(conn: &mut PgConnection, params: &xxi_node::commons::FundingRate) -> QueryResult<()> {
let affected_rows = diesel::insert_into(funding_rates::table)
.values(&(
funding_rates::start_date.eq(params.start_date()),
Expand All @@ -50,10 +49,24 @@ fn insert_one(conn: &mut PgConnection, params: &funding_fee::FundingRate) -> Que
Ok(())
}

// TODO: Check that `end_date` isn't already in the past.
pub(crate) fn get_next_funding_rate(
conn: &mut PgConnection,
) -> QueryResult<Option<xxi_node::commons::FundingRate>> {
let funding_rate: Option<FundingRate> = funding_rates::table
.order(funding_rates::end_date.desc())
.first::<FundingRate>(conn)
.optional()?;

let funding_rate = funding_rate.map(xxi_node::commons::FundingRate::from);

Ok(funding_rate)
}

/// Get the funding rate with an end date that is equal to the current date to the nearest hour.
pub(crate) fn get_funding_rate_charged_in_the_last_hour(
conn: &mut PgConnection,
) -> QueryResult<Option<funding_fee::FundingRate>> {
) -> QueryResult<Option<xxi_node::commons::FundingRate>> {
let now = OffsetDateTime::now_utc();
let now = to_nearest_hour_in_the_past(now);

Expand All @@ -62,10 +75,10 @@ pub(crate) fn get_funding_rate_charged_in_the_last_hour(
.first::<FundingRate>(conn)
.optional()?;

Ok(funding_rate.map(funding_fee::FundingRate::from))
Ok(funding_rate.map(xxi_node::commons::FundingRate::from))
}

impl From<FundingRate> for funding_fee::FundingRate {
impl From<FundingRate> for xxi_node::commons::FundingRate {
fn from(value: FundingRate) -> Self {
Self::new(
Decimal::from_f32(value.rate).expect("to fit"),
Expand Down
56 changes: 5 additions & 51 deletions coordinator/src/funding_fee.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::db;
use crate::decimal_from_f32;
use crate::message::OrderbookMessage;
use crate::to_nearest_hour_in_the_past;
use anyhow::bail;
use anyhow::Context;
use anyhow::Result;
Expand All @@ -26,51 +25,6 @@ use xxi_node::commons::Message;

const RETRY_INTERVAL: Duration = Duration::from_secs(5);

/// The funding rate for any position opened before the `end_date`, which remained open through the
/// `end_date`.
#[derive(Clone, Debug)]
pub struct FundingRate {
/// A positive funding rate indicates that longs pay shorts; a negative funding rate indicates
/// that shorts pay longs.
rate: Decimal,
/// The start date for the funding rate period. This value is only used for informational
/// purposes.
///
/// The `start_date` is always a whole hour.
start_date: OffsetDateTime,
/// The end date for the funding rate period. When the end date has passed, all active
/// positions that were created before the end date should be charged a funding fee based
/// on the `rate`.
///
/// The `end_date` is always a whole hour.
end_date: OffsetDateTime,
}

impl FundingRate {
pub(crate) fn new(rate: Decimal, start_date: OffsetDateTime, end_date: OffsetDateTime) -> Self {
let start_date = to_nearest_hour_in_the_past(start_date);
let end_date = to_nearest_hour_in_the_past(end_date);

Self {
rate,
start_date,
end_date,
}
}

pub fn rate(&self) -> Decimal {
self.rate
}

pub fn start_date(&self) -> OffsetDateTime {
self.start_date
}

pub fn end_date(&self) -> OffsetDateTime {
self.end_date
}
}

/// A record that a funding fee is owed between the coordinator and a trader.
#[derive(Clone, Copy, Debug)]
pub struct FundingFeeEvent {
Expand Down Expand Up @@ -181,7 +135,7 @@ fn generate_funding_fee_events(
let index_price = match index_price_source {
IndexPriceSource::Bitmex => block_in_place(move || {
let current_index_price =
get_bitmex_index_price(&contract_symbol, funding_rate.end_date)?;
get_bitmex_index_price(&contract_symbol, funding_rate.end_date())?;

anyhow::Ok(current_index_price)
})?,
Expand All @@ -200,12 +154,12 @@ fn generate_funding_fee_events(
// We exclude active positions which were open after this funding period ended.
let positions = db::positions::Position::get_all_active_positions_open_before(
&mut conn,
funding_rate.end_date,
funding_rate.end_date(),
)?;
for position in positions {
let amount = calculate_funding_fee(
position.quantity,
funding_rate.rate,
funding_rate.rate(),
index_price,
position.trader_direction,
);
Expand All @@ -215,9 +169,9 @@ fn generate_funding_fee_events(
amount,
position.trader,
position.id,
funding_rate.end_date,
funding_rate.end_date(),
index_price,
funding_rate.rate,
funding_rate.rate(),
)
.context("Failed to insert funding fee event")?
{
Expand Down
28 changes: 0 additions & 28 deletions coordinator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ use rust_decimal::prelude::FromPrimitive;
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use serde_json::json;
use time::OffsetDateTime;
use time::Time;
use xxi_node::commons;

mod collaborative_revert;
Expand Down Expand Up @@ -124,29 +122,3 @@ pub enum FundingFee {
CoordinatorPays(Amount),
TraderPays(Amount),
}

/// Remove minutes, seconds and nano seconds from a given [`OffsetDateTime`].
pub fn to_nearest_hour_in_the_past(start_date: OffsetDateTime) -> OffsetDateTime {
OffsetDateTime::new_utc(
start_date.date(),
Time::from_hms_nano(start_date.time().hour(), 0, 0, 0).expect("to be valid time"),
)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_remove_small_units() {
let start_date = OffsetDateTime::now_utc();

// Act
let result = to_nearest_hour_in_the_past(start_date);

// Assert
assert_eq!(result.hour(), start_date.time().hour());
assert_eq!(result.minute(), 0);
assert_eq!(result.second(), 0);
}
}
27 changes: 27 additions & 0 deletions coordinator/src/orderbook/websocket.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::db;
use crate::db::funding_fee_events;
use crate::db::funding_rates;
use crate::db::user;
use crate::message::NewUserMessage;
use crate::orderbook::db::orders;
Expand Down Expand Up @@ -276,6 +277,32 @@ pub async fn websocket_connection(stream: WebSocket, state: Arc<AppState>) {
}
}

match funding_rates::get_next_funding_rate(&mut conn) {
Ok(Some(funding_rate)) => {
if let Err(e) = local_sender
.send(Message::NextFundingRate(funding_rate))
.await
{
tracing::error!(
%trader_id,
"Failed to send next funding rate: {e}"
);
}
}
Ok(None) => {
tracing::error!(
%trader_id,
"No next funding rate found in DB"
);
}
Err(e) => {
tracing::error!(
%trader_id,
"Failed to load next funding rate: {e}"
);
}
}

let token = fcm_token.unwrap_or("unavailable".to_string());
if let Err(e) =
user::login_user(&mut conn, trader_id, token, version, os)
Expand Down
7 changes: 3 additions & 4 deletions coordinator/src/routes/admin.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use crate::collaborative_revert;
use crate::db;
use crate::funding_fee;
use crate::parse_dlc_channel_id;
use crate::position::models::Position;
use crate::referrals;
Expand Down Expand Up @@ -682,7 +681,7 @@ pub async fn post_funding_rates(
.0
.iter()
.copied()
.map(funding_fee::FundingRate::from)
.map(xxi_node::commons::FundingRate::from)
.collect::<Vec<_>>();

db::funding_rates::insert(&mut conn, &funding_rates)
Expand All @@ -703,9 +702,9 @@ pub struct FundingRate {
end_date: OffsetDateTime,
}

impl From<FundingRate> for funding_fee::FundingRate {
impl From<FundingRate> for xxi_node::commons::FundingRate {
fn from(value: FundingRate) -> Self {
funding_fee::FundingRate::new(value.rate, value.start_date, value.end_date)
xxi_node::commons::FundingRate::new(value.rate, value.start_date, value.end_date)
}
}

Expand Down
3 changes: 3 additions & 0 deletions crates/tests-e2e/src/test_subscriber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,9 @@ impl Senders {
native::event::EventInternal::FundingFeeEvent(_) => {
// ignored
}
native::event::EventInternal::NextFundingRate(_) => {
// ignored
}
}
Ok(())
}
Expand Down
46 changes: 46 additions & 0 deletions crates/xxi-node/src/commons/funding_fee_event.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::commons::to_nearest_hour_in_the_past;
use crate::commons::ContractSymbol;
use crate::commons::Direction;
use bitcoin::SignedAmount;
Expand All @@ -6,6 +7,51 @@ use serde::Deserialize;
use serde::Serialize;
use time::OffsetDateTime;

/// The funding rate for any position opened before the `end_date`, which remained open through the
/// `end_date`.
#[derive(Serialize, Clone, Copy, Deserialize, Debug)]
pub struct FundingRate {
/// A positive funding rate indicates that longs pay shorts; a negative funding rate indicates
/// that shorts pay longs.
rate: Decimal,
/// The start date for the funding rate period. This value is only used for informational
/// purposes.
///
/// The `start_date` is always a whole hour.
start_date: OffsetDateTime,
/// The end date for the funding rate period. When the end date has passed, all active
/// positions that were created before the end date should be charged a funding fee based
/// on the `rate`.
///
/// The `end_date` is always a whole hour.
end_date: OffsetDateTime,
}

impl FundingRate {
pub fn new(rate: Decimal, start_date: OffsetDateTime, end_date: OffsetDateTime) -> Self {
let start_date = to_nearest_hour_in_the_past(start_date);
let end_date = to_nearest_hour_in_the_past(end_date);

Self {
rate,
start_date,
end_date,
}
}

pub fn rate(&self) -> Decimal {
self.rate
}

pub fn start_date(&self) -> OffsetDateTime {
self.start_date
}

pub fn end_date(&self) -> OffsetDateTime {
self.end_date
}
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct FundingFeeEvent {
pub contract_symbol: ContractSymbol,
Expand Down
3 changes: 3 additions & 0 deletions crates/xxi-node/src/commons/message.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::commons::order::Order;
use crate::commons::signature::Signature;
use crate::commons::FundingRate;
use crate::commons::LiquidityOption;
use crate::commons::NewLimitOrder;
use crate::commons::ReferralStatus;
Expand Down Expand Up @@ -52,6 +53,7 @@ pub enum Message {
},
FundingFeeEvent(FundingFeeEvent),
AllFundingFeeEvents(Vec<FundingFeeEvent>),
NextFundingRate(FundingRate),
}

#[derive(Serialize, Deserialize, Clone, Error, Debug, PartialEq)]
Expand Down Expand Up @@ -116,6 +118,7 @@ impl Display for Message {
Message::LnPaymentReceived { .. } => "LnPaymentReceived",
Message::FundingFeeEvent(_) => "FundingFeeEvent",
Message::AllFundingFeeEvents(_) => "FundingFeeEvent",
Message::NextFundingRate(_) => "NextFundingRate",
};

f.write_str(s)
Expand Down
Loading

0 comments on commit 3ce7e2f

Please sign in to comment.