feat: can get candles from the server

This commit is contained in:
dboures 2023-03-13 00:03:37 -05:00
parent f4e1e5c3bb
commit 352bc32210
No known key found for this signature in database
GPG Key ID: AB3790129D478852
5 changed files with 205 additions and 22 deletions

View File

@ -58,6 +58,83 @@
},
"query": "CREATE INDEX IF NOT EXISTS idx_market_time ON fills (market, time)"
},
"866773102b03b002e9d0535d3173f36264e1d30a46a5ec8240b0ea8076d6d1c5": {
"describe": {
"columns": [
{
"name": "start_time!",
"ordinal": 0,
"type_info": "Timestamptz"
},
{
"name": "end_time!",
"ordinal": 1,
"type_info": "Timestamptz"
},
{
"name": "resolution!",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "market_name!",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "open!",
"ordinal": 4,
"type_info": "Numeric"
},
{
"name": "close!",
"ordinal": 5,
"type_info": "Numeric"
},
{
"name": "high!",
"ordinal": 6,
"type_info": "Numeric"
},
{
"name": "low!",
"ordinal": 7,
"type_info": "Numeric"
},
{
"name": "volume!",
"ordinal": 8,
"type_info": "Numeric"
},
{
"name": "complete!",
"ordinal": 9,
"type_info": "Bool"
}
],
"nullable": [
true,
true,
true,
true,
true,
true,
true,
true,
true,
true
],
"parameters": {
"Left": [
"Text",
"Text",
"Timestamptz",
"Timestamptz"
]
}
},
"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 ORDER BY start_time asc"
},
"98d4c97eeb4f0d14fd9284a968e88837456117a1c1995df37e615902b482ff10": {
"describe": {
"columns": [

53
src/server/candles.rs Normal file
View File

@ -0,0 +1,53 @@
use chrono::{Utc, NaiveDateTime};
use openbook_candles::{utils::WebContext, database::{fetch::fetch_tradingview_candles}, structs::{resolution::Resolution, markets::MarketInfo, tradingview::TvResponse}};
use crate::server_error::ServerError;
use {
actix_web::{get, web, HttpResponse, Scope},
serde::Deserialize,
};
#[derive(Debug, Deserialize)]
pub struct Params {
pub market_name: String,
pub from: u64,
pub to: u64,
pub resolution: String,
}
pub fn service() -> Scope {
web::scope("/tradingview")
.service(get_candles)
}
#[get("/candles")]
pub async fn get_candles(
info: web::Query<Params>,
context: web::Data<WebContext>,
) -> Result<HttpResponse, ServerError> {
let resolution =
Resolution::from_str(info.resolution.as_str()).map_err(|_| ServerError::WrongResolution)?;
if !valid_market(&info.market_name, &context.markets) {
return Err(ServerError::WrongParameters);
}
let from = to_timestampz(info.from);
let to = to_timestampz(info.to);
let candles = match fetch_tradingview_candles(&context.pool, &info.market_name, resolution, from, to).await {
Ok(c) => c,
Err(_) => return Err(ServerError::DbQueryError)
};
Ok(HttpResponse::Ok().json(TvResponse::candles_to_tv(candles)))
}
fn valid_market(market_name: &str, markets: &Vec<MarketInfo>) -> bool {
markets.iter().any(|x| x.name == market_name)
}
fn to_timestampz(seconds: u64) -> chrono::DateTime<Utc> {
chrono::DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(seconds as i64, 0), Utc)
}

View File

@ -5,7 +5,11 @@ use actix_web::{
App, HttpResponse, HttpServer, Responder,
};
use dotenv;
use sqlx::{postgres::PgPoolOptions, Pool, Postgres};
use openbook_candles::{utils::{Config, WebContext}, candle_creation::trade_fetching::scrape::fetch_market_infos, database::initialize::connect_to_database, structs::markets::load_markets};
use sqlx::{Pool, Postgres};
mod candles;
mod server_error;
#[get("/")]
async fn hello() -> impl Responder {
@ -23,41 +27,39 @@ async fn get_total_trades(pool_data: web::Data<Pool<Postgres>>) -> impl Responde
HttpResponse::Ok().json(total_trades)
}
// #[get("/recent-trades")]
// async fn get_recent_trades(pool_data: web::Data<Pool<Postgres>>, market: String) -> impl Responder {
// let pool = pool_data.get_ref();
// let trades_query= sqlx::query!("Select * as total from fills").fetch_one(pool).await.unwrap();
// let total_trades: i64 = total_query.total.unwrap_or_else(|| 0);
// HttpResponse::Ok().json(total_trades)
// }
async fn manual_hello() -> impl Responder {
HttpResponse::Ok().body("Hey there!")
}
#[actix_web::main]
async fn main() -> anyhow::Result<(), std::io::Error> {
async fn main() -> std::io::Result<()> {
dotenv::dotenv().ok();
env_logger::init();
let database_url = dotenv::var("DATABASE_URL").unwrap();
let rpc_url: String = dotenv::var("RPC_URL").unwrap();
let database_url: String = dotenv::var("DATABASE_URL").unwrap();
let max_pg_pool_connections: u32 = dotenv::var("MAX_PG_POOL_CONNS_SERVER").unwrap().parse::<u32>().unwrap();
// let context = Data::new(Context {
// markets: utils::markets::load_markets(markets_path),
// pool,
// });
let config = Config {
rpc_url: rpc_url.clone(),
database_url: database_url.clone(),
max_pg_pool_connections,
};
let pool = PgPoolOptions::new()
.max_connections(15)
.connect(&database_url)
.await
.unwrap();
let markets = load_markets("/Users/dboures/dev/openbook-candles/markets.json");
let market_infos = fetch_market_infos(&config, markets).await.unwrap();
let pool = connect_to_database(&config).await.unwrap();
let context = Data::new(WebContext {
pool,
markets: market_infos,
});
HttpServer::new(move || {
App::new()
.wrap(Logger::default())
.app_data(Data::new(pool.clone()))
.service(get_total_trades)
.app_data(Data::new(context.clone()))
.service(candles::service())
.route("/hey", web::get().to(manual_hello))
})
.bind(("127.0.0.1", 8080))?

1
src/server/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod candles;

View File

@ -0,0 +1,50 @@
use actix_web::{
error,
http::{header::ContentType, StatusCode},
HttpResponse,
};
use derive_more::{Display, Error};
#[derive(Debug, Display, Error)]
pub enum ServerError {
#[display(fmt = "An internal error occurred. Please try again later.")]
InternalError,
#[display(fmt = "Bad request parameters")]
WrongParameters,
#[display(fmt = "Wrong resolution")]
WrongResolution,
#[display(fmt = "DB error")]
DbQueryError,
#[display(fmt = "Error getting connection")]
DbPoolError,
#[display(fmt = "Raw market not found")]
RawMarketNotFound,
#[display(fmt = "Request symbol not found")]
SymbolNotFound,
}
impl error::ResponseError for ServerError {
fn error_response(&self) -> HttpResponse {
HttpResponse::build(self.status_code())
.insert_header(ContentType::html())
.body(self.to_string())
}
fn status_code(&self) -> StatusCode {
match *self {
ServerError::InternalError => StatusCode::INTERNAL_SERVER_ERROR,
ServerError::WrongParameters => StatusCode::BAD_REQUEST,
ServerError::WrongResolution => StatusCode::BAD_REQUEST,
ServerError::DbQueryError => StatusCode::INTERNAL_SERVER_ERROR,
ServerError::DbPoolError => StatusCode::INTERNAL_SERVER_ERROR,
ServerError::RawMarketNotFound => StatusCode::BAD_REQUEST,
ServerError::SymbolNotFound => StatusCode::BAD_REQUEST,
}
}
}
impl From<ServerError> for std::io::Error {
fn from(e: ServerError) -> Self {
std::io::Error::new(std::io::ErrorKind::Other, e.to_string())
}
}