feat: can get candles from the server
This commit is contained in:
parent
f4e1e5c3bb
commit
352bc32210
|
@ -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": [
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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))?
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
pub mod candles;
|
|
@ -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())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue