feat: can query traders by quote volume

This commit is contained in:
dboures 2023-03-13 16:18:18 -05:00
parent c01e53b41c
commit 4abadc9589
No known key found for this signature in database
GPG Key ID: AB3790129D478852
7 changed files with 228 additions and 86 deletions

View File

@ -1,5 +1,39 @@
{
"db": "PostgreSQL",
"21b633d5aec33394129b051ea1df0ee9ab097626d74d8943f6323f9fb42723b5": {
"describe": {
"columns": [
{
"name": "open_orders_owner",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "raw_ask_size!",
"ordinal": 1,
"type_info": "Numeric"
},
{
"name": "raw_bid_size!",
"ordinal": 2,
"type_info": "Numeric"
}
],
"nullable": [
false,
null,
null
],
"parameters": {
"Left": [
"Text",
"Timestamptz",
"Timestamptz"
]
}
},
"query": "SELECT \n open_orders_owner, \n sum(\n native_qty_received * CASE bid WHEN true THEN 0 WHEN false THEN 1 END\n ) as \"raw_ask_size!\",\n sum(\n native_qty_paid * CASE bid WHEN true THEN 1 WHEN false THEN 0 END\n ) as \"raw_bid_size!\"\n FROM fills\n WHERE market = $1\n AND time >= $2\n AND time < $3\n GROUP BY open_orders_owner\n ORDER BY \n sum(native_qty_received * CASE bid WHEN true THEN 0 WHEN false THEN 1 END) \n + \n sum(native_qty_paid * CASE bid WHEN true THEN 1 WHEN false THEN 0 END) \nDESC \nLIMIT 10000"
},
"35e8220c601aca620da1cfcb978c8b7a64dcbf15550521b418cf509015cd88d8": {
"describe": {
"columns": [],
@ -269,6 +303,40 @@
},
"query": "SELECT \n start_time as \"start_time!\",\n end_time as \"end_time!\",\n resolution as \"resolution!\",\n market_name as \"market_name!\",\n open as \"open!\",\n close as \"close!\",\n high as \"high!\",\n low as \"low!\",\n volume as \"volume!\",\n complete as \"complete!\"\n from candles\n where market_name = $1\n and resolution = $2\n and start_time >= $3\n and end_time <= $4\n and complete = true\n ORDER BY start_time asc"
},
"aee3a3e04f837bd62e263452bfbaf5d7dff271799c80d5efd22a54954ac212c4": {
"describe": {
"columns": [
{
"name": "open_orders_owner",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "raw_ask_size!",
"ordinal": 1,
"type_info": "Numeric"
},
{
"name": "raw_bid_size!",
"ordinal": 2,
"type_info": "Numeric"
}
],
"nullable": [
false,
null,
null
],
"parameters": {
"Left": [
"Text",
"Timestamptz",
"Timestamptz"
]
}
},
"query": "SELECT \n open_orders_owner, \n sum(\n native_qty_paid * CASE bid WHEN true THEN 0 WHEN false THEN 1 END\n ) as \"raw_ask_size!\",\n sum(\n native_qty_received * CASE bid WHEN true THEN 1 WHEN false THEN 0 END\n ) as \"raw_bid_size!\"\n FROM fills\n WHERE market = $1\n AND time >= $2\n AND time < $3\n GROUP BY open_orders_owner\n ORDER BY \n sum(native_qty_paid * CASE bid WHEN true THEN 0 WHEN false THEN 1 END) \n + \n sum(native_qty_received * CASE bid WHEN true THEN 1 WHEN false THEN 0 END) \nDESC \nLIMIT 10000"
},
"b259d64b388eb675b727ee511529472177b59ea616041360217afc2d928f33ed": {
"describe": {
"columns": [],
@ -331,40 +399,6 @@
},
"query": "SELECT \n time as \"time!\",\n bid as \"bid!\",\n maker as \"maker!\",\n native_qty_paid as \"native_qty_paid!\",\n native_qty_received as \"native_qty_received!\",\n native_fee_or_rebate as \"native_fee_or_rebate!\" \n from fills \n where market = $1\n and time >= $2\n and time < $3 \n and maker = true\n ORDER BY time asc"
},
"dc367af7bb8da9d57033833da06567f3f3cfdcff72e5b140ccd3355f91b4c5a7": {
"describe": {
"columns": [
{
"name": "open_orders_owner",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "raw_ask_size!",
"ordinal": 1,
"type_info": "Numeric"
},
{
"name": "raw_bid_size!",
"ordinal": 2,
"type_info": "Numeric"
}
],
"nullable": [
false,
null,
null
],
"parameters": {
"Left": [
"Text",
"Timestamptz",
"Timestamptz"
]
}
},
"query": "SELECT \n open_orders_owner, \n sum(\n native_qty_paid * CASE bid WHEN true THEN 0 WHEN false THEN 1 END\n ) as \"raw_ask_size!\",\n sum(\n native_qty_received * CASE bid WHEN true THEN 1 WHEN false THEN 0 END\n ) as \"raw_bid_size!\"\n FROM public.\"fills\"\n WHERE market = $1\n AND time >= $2\n AND time < $3\n GROUP BY open_orders_owner\n ORDER BY \n sum(native_qty_paid * CASE bid WHEN true THEN 0 WHEN false THEN 1 END) \n + \n sum(native_qty_received * CASE bid WHEN true THEN 1 WHEN false THEN 0 END) \nDESC "
},
"dc7c7c04b6870b9617e1e869aa4b7027baddaeeb22f2792f2e9c40f643f863c7": {
"describe": {
"columns": [

View File

@ -2,11 +2,7 @@ use chrono::{DateTime, Utc};
use sqlx::{Pool, Postgres};
use crate::{
structs::{
candle::Candle,
openbook::{PgOpenBookFill, PgTrader},
resolution::Resolution,
},
structs::{candle::Candle, openbook::PgOpenBookFill, resolution::Resolution, trader::PgTrader},
utils::AnyhowWrap,
};
@ -198,7 +194,7 @@ pub async fn fetch_tradingview_candles(
.map_err_anyhow()
}
pub async fn fetch_top_traders_by_volume_from(
pub async fn fetch_top_traders_by_base_volume_from(
pool: &Pool<Postgres>,
market_address_string: &str,
start_time: DateTime<Utc>,
@ -214,7 +210,7 @@ pub async fn fetch_top_traders_by_volume_from(
sum(
native_qty_received * CASE bid WHEN true THEN 1 WHEN false THEN 0 END
) as "raw_bid_size!"
FROM public."fills"
FROM fills
WHERE market = $1
AND time >= $2
AND time < $3
@ -223,7 +219,8 @@ pub async fn fetch_top_traders_by_volume_from(
sum(native_qty_paid * CASE bid WHEN true THEN 0 WHEN false THEN 1 END)
+
sum(native_qty_received * CASE bid WHEN true THEN 1 WHEN false THEN 0 END)
DESC "#,
DESC
LIMIT 10000"#,
market_address_string,
start_time,
end_time
@ -233,4 +230,38 @@ DESC "#,
.map_err_anyhow()
}
// pub async fn fetch_traders_above_x_dollars
pub async fn fetch_top_traders_by_quote_volume_from(
pool: &Pool<Postgres>,
market_address_string: &str,
start_time: DateTime<Utc>,
end_time: DateTime<Utc>,
) -> anyhow::Result<Vec<PgTrader>> {
sqlx::query_as!(
PgTrader,
r#"SELECT
open_orders_owner,
sum(
native_qty_received * CASE bid WHEN true THEN 0 WHEN false THEN 1 END
) as "raw_ask_size!",
sum(
native_qty_paid * CASE bid WHEN true THEN 1 WHEN false THEN 0 END
) as "raw_bid_size!"
FROM fills
WHERE market = $1
AND time >= $2
AND time < $3
GROUP BY open_orders_owner
ORDER BY
sum(native_qty_received * CASE bid WHEN true THEN 0 WHEN false THEN 1 END)
+
sum(native_qty_paid * CASE bid WHEN true THEN 1 WHEN false THEN 0 END)
DESC
LIMIT 10000"#,
market_address_string,
start_time,
end_time
)
.fetch_all(pool)
.await
.map_err_anyhow()
}

View File

@ -1,8 +1,7 @@
use actix_web::{
get,
middleware::Logger,
web::{self, Data},
App, HttpResponse, HttpServer, Responder,
App, HttpServer,
};
use candles::get_candles;
use dotenv;
@ -12,14 +11,14 @@ use openbook_candles::{
structs::markets::load_markets,
utils::{Config, WebContext},
};
use sqlx::{Pool, Postgres};
use traders::get_top_traders_by_base_volume;
use crate::traders::get_top_traders_by_quote_volume;
mod candles;
mod server_error;
mod traders;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
dotenv::dotenv().ok();
@ -56,7 +55,7 @@ async fn main() -> std::io::Result<()> {
web::scope("/api")
.service(get_candles)
.service(get_top_traders_by_base_volume)
// .service(get_top_traders_by_quote_volume)
.service(get_top_traders_by_quote_volume),
)
})
.bind(("127.0.0.1", 8080))?

View File

@ -1,7 +1,9 @@
use crate::server_error::ServerError;
use openbook_candles::{
database::fetch::fetch_top_traders_by_volume_from,
structs::openbook::{calculate_trader_volume, Trader},
database::fetch::{
fetch_top_traders_by_base_volume_from, fetch_top_traders_by_quote_volume_from,
},
structs::trader::{calculate_trader_volume, Trader, TraderResponse, VolumeType},
utils::{to_timestampz, WebContext},
};
use {
@ -29,19 +31,67 @@ pub async fn get_top_traders_by_base_volume(
let from = to_timestampz(info.from);
let to = to_timestampz(info.to);
let raw_traders =
match fetch_top_traders_by_volume_from(&context.pool, &selected_market.address, from, to)
.await
{
Ok(c) => c,
Err(_) => return Err(ServerError::DbQueryError),
};
let raw_traders = match fetch_top_traders_by_base_volume_from(
&context.pool,
&selected_market.address,
from,
to,
)
.await
{
Ok(c) => c,
Err(_) => return Err(ServerError::DbQueryError),
};
let traders = raw_traders
.into_iter()
.map(|t| calculate_trader_volume(t, selected_market.base_decimals))
.collect::<Vec<Trader>>();
// TODO: add start and end in response?
Ok(HttpResponse::Ok().json(traders))
let response = TraderResponse {
start_time: info.from,
end_time: info.to,
traders: traders,
volume_type: VolumeType::Base.to_string(),
};
Ok(HttpResponse::Ok().json(response))
}
#[get("/traders/quote-volume")]
pub async fn get_top_traders_by_quote_volume(
info: web::Query<Params>,
context: web::Data<WebContext>,
) -> Result<HttpResponse, ServerError> {
let selected_market = context.markets.iter().find(|x| x.name == info.market_name);
if selected_market.is_none() {
return Err(ServerError::MarketNotFound);
}
let selected_market = selected_market.unwrap();
let from = to_timestampz(info.from);
let to = to_timestampz(info.to);
let raw_traders = match fetch_top_traders_by_quote_volume_from(
&context.pool,
&selected_market.address,
from,
to,
)
.await
{
Ok(c) => c,
Err(_) => return Err(ServerError::DbQueryError),
};
let traders = raw_traders
.into_iter()
.map(|t| calculate_trader_volume(t, selected_market.quote_decimals))
.collect::<Vec<Trader>>();
let response = TraderResponse {
start_time: info.from,
end_time: info.to,
traders: traders,
volume_type: VolumeType::Quote.to_string(),
};
Ok(HttpResponse::Ok().json(response))
}

View File

@ -2,4 +2,5 @@ pub mod candle;
pub mod markets;
pub mod openbook;
pub mod resolution;
pub mod trader;
pub mod tradingview;

View File

@ -1,7 +1,6 @@
use anchor_lang::{event, AnchorDeserialize, AnchorSerialize};
use chrono::{DateTime, Utc};
use num_traits::{FromPrimitive, ToPrimitive};
use serde::Serialize;
use num_traits::FromPrimitive;
use solana_sdk::pubkey::Pubkey;
use sqlx::types::Decimal;
@ -33,19 +32,6 @@ pub struct PgOpenBookFill {
pub native_fee_or_rebate: Decimal,
}
#[derive(Clone, Debug, PartialEq)]
pub struct PgTrader {
pub open_orders_owner: String,
pub raw_ask_size: Decimal,
pub raw_bid_size: Decimal,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct Trader {
pub pubkey: String,
pub volume_base_units: f64,
}
#[derive(Copy, Clone, AnchorDeserialize)]
#[cfg_attr(target_endian = "little", derive(Debug))]
#[repr(packed)]
@ -129,17 +115,6 @@ pub fn calculate_fill_price_and_size(
}
}
pub fn calculate_trader_volume(trader: PgTrader, base_decimals: u8) -> Trader {
let bid_size = trader.raw_bid_size / token_factor(base_decimals);
let ask_size = trader.raw_ask_size / token_factor(base_decimals);
Trader {
pubkey: trader.open_orders_owner,
volume_base_units: (bid_size + ask_size).to_f64().unwrap(),
// TODO: quote volume
}
}
fn token_factor(decimals: u8) -> Decimal {
pub fn token_factor(decimals: u8) -> Decimal {
Decimal::from_u64(10u64.pow(decimals as u32)).unwrap()
}

52
src/structs/trader.rs Normal file
View File

@ -0,0 +1,52 @@
use std::fmt;
use num_traits::ToPrimitive;
use serde::Serialize;
use sqlx::types::Decimal;
use super::openbook::token_factor;
#[derive(Clone, Debug, PartialEq)]
pub struct PgTrader {
pub open_orders_owner: String,
pub raw_ask_size: Decimal,
pub raw_bid_size: Decimal,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub enum VolumeType {
Base,
Quote,
}
impl fmt::Display for VolumeType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
VolumeType::Base => write!(f, "Base"),
VolumeType::Quote => write!(f, "Quote"),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct Trader {
pub pubkey: String,
pub volume: f64,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct TraderResponse {
pub start_time: u64,
pub end_time: u64,
pub volume_type: String,
pub traders: Vec<Trader>,
}
pub fn calculate_trader_volume(trader: PgTrader, decimals: u8) -> Trader {
let bid_size = trader.raw_bid_size / token_factor(decimals);
let ask_size = trader.raw_ask_size / token_factor(decimals);
Trader {
pubkey: trader.open_orders_owner,
volume: (bid_size + ask_size).to_f64().unwrap(),
}
}