Clean up error propagation
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
14
Cargo.lock
generated
14
Cargo.lock
generated
@@ -1180,9 +1180,9 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jobserver"
|
name = "jobserver"
|
||||||
version = "0.1.27"
|
version = "0.1.28"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
|
checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
@@ -1419,19 +1419,18 @@ checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.45"
|
version = "0.1.46"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.17"
|
version = "0.2.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
|
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
@@ -1666,6 +1665,7 @@ dependencies = [
|
|||||||
"html-escape",
|
"html-escape",
|
||||||
"http 1.0.0",
|
"http 1.0.0",
|
||||||
"itertools 0.12.1",
|
"itertools 0.12.1",
|
||||||
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"log4rs",
|
"log4rs",
|
||||||
"regex",
|
"regex",
|
||||||
|
@@ -54,3 +54,4 @@ html-escape = "0.2.13"
|
|||||||
rust-bert = "0.22.0"
|
rust-bert = "0.22.0"
|
||||||
async-trait = "0.1.77"
|
async-trait = "0.1.77"
|
||||||
itertools = "0.12.1"
|
itertools = "0.12.1"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
@@ -1,16 +1,18 @@
|
|||||||
use crate::types::Asset;
|
use crate::types::Asset;
|
||||||
use clickhouse::Client;
|
use clickhouse::{error::Error, Client};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
pub async fn select(clickhouse_client: &Client) -> Vec<Asset> {
|
pub async fn select(clickhouse_client: &Client) -> Result<Vec<Asset>, Error> {
|
||||||
clickhouse_client
|
clickhouse_client
|
||||||
.query("SELECT ?fields FROM assets FINAL")
|
.query("SELECT ?fields FROM assets FINAL")
|
||||||
.fetch_all::<Asset>()
|
.fetch_all::<Asset>()
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn select_where_symbol<T>(clickhouse_client: &Client, symbol: &T) -> Option<Asset>
|
pub async fn select_where_symbol<T>(
|
||||||
|
clickhouse_client: &Client,
|
||||||
|
symbol: &T,
|
||||||
|
) -> Result<Option<Asset>, Error>
|
||||||
where
|
where
|
||||||
T: AsRef<str> + Serialize + Send + Sync,
|
T: AsRef<str> + Serialize + Send + Sync,
|
||||||
{
|
{
|
||||||
@@ -19,22 +21,21 @@ where
|
|||||||
.bind(symbol)
|
.bind(symbol)
|
||||||
.fetch_optional::<Asset>()
|
.fetch_optional::<Asset>()
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn upsert_batch<T>(clickhouse_client: &Client, assets: T)
|
pub async fn upsert_batch<T>(clickhouse_client: &Client, assets: T) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
T: IntoIterator<Item = Asset> + Send + Sync,
|
T: IntoIterator<Item = Asset> + Send + Sync,
|
||||||
T::IntoIter: Send,
|
T::IntoIter: Send,
|
||||||
{
|
{
|
||||||
let mut insert = clickhouse_client.insert("assets").unwrap();
|
let mut insert = clickhouse_client.insert("assets")?;
|
||||||
for asset in assets {
|
for asset in assets {
|
||||||
insert.write(&asset).await.unwrap();
|
insert.write(&asset).await?;
|
||||||
}
|
}
|
||||||
insert.end().await.unwrap();
|
insert.end().await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_where_symbols<T>(clickhouse_client: &Client, symbols: &[T])
|
pub async fn delete_where_symbols<T>(clickhouse_client: &Client, symbols: &[T]) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
T: AsRef<str> + Serialize + Send + Sync,
|
T: AsRef<str> + Serialize + Send + Sync,
|
||||||
{
|
{
|
||||||
@@ -43,5 +44,4 @@ where
|
|||||||
.bind(symbols)
|
.bind(symbols)
|
||||||
.execute()
|
.execute()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
use crate::types::Backfill;
|
use crate::types::Backfill;
|
||||||
use clickhouse::Client;
|
use clickhouse::{error::Error, Client};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::fmt::Display;
|
use std::fmt::{Display, Formatter};
|
||||||
use tokio::join;
|
use tokio::try_join;
|
||||||
|
|
||||||
pub enum Table {
|
pub enum Table {
|
||||||
Bars,
|
Bars,
|
||||||
@@ -10,7 +10,7 @@ pub enum Table {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Table {
|
impl Display for Table {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Bars => write!(f, "backfills_bars"),
|
Self::Bars => write!(f, "backfills_bars"),
|
||||||
Self::News => write!(f, "backfills_news"),
|
Self::News => write!(f, "backfills_news"),
|
||||||
@@ -22,7 +22,7 @@ pub async fn select_latest_where_symbol<T>(
|
|||||||
clickhouse_client: &Client,
|
clickhouse_client: &Client,
|
||||||
table: &Table,
|
table: &Table,
|
||||||
symbol: &T,
|
symbol: &T,
|
||||||
) -> Option<Backfill>
|
) -> Result<Option<Backfill>, Error>
|
||||||
where
|
where
|
||||||
T: AsRef<str> + Serialize + Send + Sync,
|
T: AsRef<str> + Serialize + Send + Sync,
|
||||||
{
|
{
|
||||||
@@ -33,16 +33,23 @@ where
|
|||||||
.bind(symbol)
|
.bind(symbol)
|
||||||
.fetch_optional::<Backfill>()
|
.fetch_optional::<Backfill>()
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn upsert(clickhouse_client: &Client, table: &Table, backfill: &Backfill) {
|
pub async fn upsert(
|
||||||
let mut insert = clickhouse_client.insert(&table.to_string()).unwrap();
|
clickhouse_client: &Client,
|
||||||
insert.write(backfill).await.unwrap();
|
table: &Table,
|
||||||
insert.end().await.unwrap();
|
backfill: &Backfill,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut insert = clickhouse_client.insert(&table.to_string())?;
|
||||||
|
insert.write(backfill).await?;
|
||||||
|
insert.end().await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_where_symbols<T>(clickhouse_client: &Client, table: &Table, symbols: &[T])
|
pub async fn delete_where_symbols<T>(
|
||||||
|
clickhouse_client: &Client,
|
||||||
|
table: &Table,
|
||||||
|
symbols: &[T],
|
||||||
|
) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
T: AsRef<str> + Serialize + Send + Sync,
|
T: AsRef<str> + Serialize + Send + Sync,
|
||||||
{
|
{
|
||||||
@@ -51,16 +58,14 @@ where
|
|||||||
.bind(symbols)
|
.bind(symbols)
|
||||||
.execute()
|
.execute()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn cleanup(clickhouse_client: &Client) {
|
pub async fn cleanup(clickhouse_client: &Client) -> Result<(), Error> {
|
||||||
let delete_bars_future = async {
|
let delete_bars_future = async {
|
||||||
clickhouse_client
|
clickhouse_client
|
||||||
.query("DELETE FROM backfills_bars WHERE symbol NOT IN (SELECT symbol FROM assets)")
|
.query("DELETE FROM backfills_bars WHERE symbol NOT IN (SELECT symbol FROM assets)")
|
||||||
.execute()
|
.execute()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let delete_news_future = async {
|
let delete_news_future = async {
|
||||||
@@ -68,8 +73,7 @@ pub async fn cleanup(clickhouse_client: &Client) {
|
|||||||
.query("DELETE FROM backfills_news WHERE symbol NOT IN (SELECT symbol FROM assets)")
|
.query("DELETE FROM backfills_news WHERE symbol NOT IN (SELECT symbol FROM assets)")
|
||||||
.execute()
|
.execute()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
join!(delete_bars_future, delete_news_future);
|
try_join!(delete_bars_future, delete_news_future).map(|_| ())
|
||||||
}
|
}
|
||||||
|
@@ -1,26 +1,26 @@
|
|||||||
use crate::types::Bar;
|
use crate::types::Bar;
|
||||||
use clickhouse::Client;
|
use clickhouse::{error::Error, Client};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
pub async fn upsert(clickhouse_client: &Client, bar: &Bar) {
|
pub async fn upsert(clickhouse_client: &Client, bar: &Bar) -> Result<(), Error> {
|
||||||
let mut insert = clickhouse_client.insert("bars").unwrap();
|
let mut insert = clickhouse_client.insert("bars")?;
|
||||||
insert.write(bar).await.unwrap();
|
insert.write(bar).await?;
|
||||||
insert.end().await.unwrap();
|
insert.end().await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn upsert_batch<T>(clickhouse_client: &Client, bars: T)
|
pub async fn upsert_batch<T>(clickhouse_client: &Client, bars: T) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
T: IntoIterator<Item = Bar> + Send + Sync,
|
T: IntoIterator<Item = Bar> + Send + Sync,
|
||||||
T::IntoIter: Send,
|
T::IntoIter: Send,
|
||||||
{
|
{
|
||||||
let mut insert = clickhouse_client.insert("bars").unwrap();
|
let mut insert = clickhouse_client.insert("bars")?;
|
||||||
for bar in bars {
|
for bar in bars {
|
||||||
insert.write(&bar).await.unwrap();
|
insert.write(&bar).await?;
|
||||||
}
|
}
|
||||||
insert.end().await.unwrap();
|
insert.end().await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_where_symbols<T>(clickhouse_client: &Client, symbols: &[T])
|
pub async fn delete_where_symbols<T>(clickhouse_client: &Client, symbols: &[T]) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
T: AsRef<str> + Serialize + Send + Sync,
|
T: AsRef<str> + Serialize + Send + Sync,
|
||||||
{
|
{
|
||||||
@@ -29,13 +29,11 @@ where
|
|||||||
.bind(symbols)
|
.bind(symbols)
|
||||||
.execute()
|
.execute()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn cleanup(clickhouse_client: &Client) {
|
pub async fn cleanup(clickhouse_client: &Client) -> Result<(), Error> {
|
||||||
clickhouse_client
|
clickhouse_client
|
||||||
.query("DELETE FROM bars WHERE symbol NOT IN (SELECT symbol FROM assets)")
|
.query("DELETE FROM bars WHERE symbol NOT IN (SELECT symbol FROM assets)")
|
||||||
.execute()
|
.execute()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
@@ -1,26 +1,26 @@
|
|||||||
use crate::types::News;
|
use crate::types::News;
|
||||||
use clickhouse::Client;
|
use clickhouse::{error::Error, Client};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
pub async fn upsert(clickhouse_client: &Client, news: &News) {
|
pub async fn upsert(clickhouse_client: &Client, news: &News) -> Result<(), Error> {
|
||||||
let mut insert = clickhouse_client.insert("news").unwrap();
|
let mut insert = clickhouse_client.insert("news")?;
|
||||||
insert.write(news).await.unwrap();
|
insert.write(news).await?;
|
||||||
insert.end().await.unwrap();
|
insert.end().await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn upsert_batch<T>(clickhouse_client: &Client, news: T)
|
pub async fn upsert_batch<T>(clickhouse_client: &Client, news: T) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
T: IntoIterator<Item = News> + Send + Sync,
|
T: IntoIterator<Item = News> + Send + Sync,
|
||||||
T::IntoIter: Send,
|
T::IntoIter: Send,
|
||||||
{
|
{
|
||||||
let mut insert = clickhouse_client.insert("news").unwrap();
|
let mut insert = clickhouse_client.insert("news")?;
|
||||||
for news in news {
|
for news in news {
|
||||||
insert.write(&news).await.unwrap();
|
insert.write(&news).await?;
|
||||||
}
|
}
|
||||||
insert.end().await.unwrap();
|
insert.end().await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_where_symbols<T>(clickhouse_client: &Client, symbols: &[T])
|
pub async fn delete_where_symbols<T>(clickhouse_client: &Client, symbols: &[T]) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
T: AsRef<str> + Serialize + Send + Sync,
|
T: AsRef<str> + Serialize + Send + Sync,
|
||||||
{
|
{
|
||||||
@@ -29,15 +29,13 @@ where
|
|||||||
.bind(symbols)
|
.bind(symbols)
|
||||||
.execute()
|
.execute()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn cleanup(clickhouse_client: &Client) {
|
pub async fn cleanup(clickhouse_client: &Client) -> Result<(), Error> {
|
||||||
clickhouse_client
|
clickhouse_client
|
||||||
.query(
|
.query(
|
||||||
"DELETE FROM news WHERE NOT hasAny(symbols, (SELECT groupArray(symbol) FROM assets))",
|
"DELETE FROM news WHERE NOT hasAny(symbols, (SELECT groupArray(symbol) FROM assets))",
|
||||||
)
|
)
|
||||||
.execute()
|
.execute()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
23
src/main.rs
23
src/main.rs
@@ -19,31 +19,34 @@ use tokio::{spawn, sync::mpsc};
|
|||||||
async fn main() {
|
async fn main() {
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
log4rs::init_file("log4rs.yaml", Deserializers::default()).unwrap();
|
log4rs::init_file("log4rs.yaml", Deserializers::default()).unwrap();
|
||||||
let app_config = Config::arc_from_env();
|
let config = Config::arc_from_env();
|
||||||
|
|
||||||
cleanup(&app_config.clickhouse_client).await;
|
cleanup(&config.clickhouse_client).await.unwrap();
|
||||||
|
|
||||||
let (data_sender, data_receiver) = mpsc::channel::<threads::data::Message>(100);
|
let (data_sender, data_receiver) = mpsc::channel::<threads::data::Message>(100);
|
||||||
let (clock_sender, clock_receiver) = mpsc::channel::<threads::clock::Message>(1);
|
let (clock_sender, clock_receiver) = mpsc::channel::<threads::clock::Message>(1);
|
||||||
|
|
||||||
spawn(threads::data::run(
|
spawn(threads::data::run(
|
||||||
app_config.clone(),
|
config.clone(),
|
||||||
data_receiver,
|
data_receiver,
|
||||||
clock_receiver,
|
clock_receiver,
|
||||||
));
|
));
|
||||||
|
|
||||||
spawn(threads::clock::run(app_config.clone(), clock_sender));
|
spawn(threads::clock::run(config.clone(), clock_sender));
|
||||||
|
|
||||||
let assets = database::assets::select(&app_config.clickhouse_client)
|
let assets = database::assets::select(&config.clickhouse_client)
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|asset| (asset.symbol, asset.class))
|
.map(|asset| (asset.symbol, asset.class))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let (data_message, data_receiver) =
|
create_send_await!(
|
||||||
threads::data::Message::new(threads::data::Action::Add, assets);
|
data_sender,
|
||||||
data_sender.send(data_message).await.unwrap();
|
threads::data::Message::new,
|
||||||
data_receiver.await.unwrap();
|
threads::data::Action::Add,
|
||||||
|
assets
|
||||||
|
);
|
||||||
|
|
||||||
routes::run(app_config, data_sender).await;
|
routes::run(config, data_sender).await;
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
config::Config,
|
config::Config,
|
||||||
database, threads,
|
create_send_await, database, threads,
|
||||||
types::{alpaca::api::incoming, Asset},
|
types::{alpaca, Asset},
|
||||||
};
|
};
|
||||||
use axum::{extract::Path, Extension, Json};
|
use axum::{extract::Path, Extension, Json};
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
@@ -10,17 +10,23 @@ use std::sync::Arc;
|
|||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
Extension(app_config): Extension<Arc<Config>>,
|
Extension(config): Extension<Arc<Config>>,
|
||||||
) -> Result<(StatusCode, Json<Vec<Asset>>), StatusCode> {
|
) -> Result<(StatusCode, Json<Vec<Asset>>), StatusCode> {
|
||||||
let assets = database::assets::select(&app_config.clickhouse_client).await;
|
let assets = database::assets::select(&config.clickhouse_client)
|
||||||
|
.await
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
Ok((StatusCode::OK, Json(assets)))
|
Ok((StatusCode::OK, Json(assets)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_where_symbol(
|
pub async fn get_where_symbol(
|
||||||
Extension(app_config): Extension<Arc<Config>>,
|
Extension(config): Extension<Arc<Config>>,
|
||||||
Path(symbol): Path<String>,
|
Path(symbol): Path<String>,
|
||||||
) -> Result<(StatusCode, Json<Asset>), StatusCode> {
|
) -> Result<(StatusCode, Json<Asset>), StatusCode> {
|
||||||
let asset = database::assets::select_where_symbol(&app_config.clickhouse_client, &symbol).await;
|
let asset = database::assets::select_where_symbol(&config.clickhouse_client, &symbol)
|
||||||
|
.await
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
asset.map_or(Err(StatusCode::NOT_FOUND), |asset| {
|
asset.map_or(Err(StatusCode::NOT_FOUND), |asset| {
|
||||||
Ok((StatusCode::OK, Json(asset)))
|
Ok((StatusCode::OK, Json(asset)))
|
||||||
})
|
})
|
||||||
@@ -32,50 +38,58 @@ pub struct AddAssetRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add(
|
pub async fn add(
|
||||||
Extension(app_config): Extension<Arc<Config>>,
|
Extension(config): Extension<Arc<Config>>,
|
||||||
Extension(data_sender): Extension<mpsc::Sender<threads::data::Message>>,
|
Extension(data_sender): Extension<mpsc::Sender<threads::data::Message>>,
|
||||||
Json(request): Json<AddAssetRequest>,
|
Json(request): Json<AddAssetRequest>,
|
||||||
) -> Result<StatusCode, StatusCode> {
|
) -> Result<StatusCode, StatusCode> {
|
||||||
if database::assets::select_where_symbol(&app_config.clickhouse_client, &request.symbol)
|
if database::assets::select_where_symbol(&config.clickhouse_client, &request.symbol)
|
||||||
.await
|
.await
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
return Err(StatusCode::CONFLICT);
|
return Err(StatusCode::CONFLICT);
|
||||||
}
|
}
|
||||||
|
|
||||||
let asset = incoming::asset::get_by_symbol(&app_config, &request.symbol).await?;
|
let asset = alpaca::api::incoming::asset::get_by_symbol(&config, &request.symbol, None)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
e.status()
|
||||||
|
.map_or(StatusCode::INTERNAL_SERVER_ERROR, |status| {
|
||||||
|
StatusCode::from_u16(status.as_u16()).unwrap()
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
if !asset.tradable || !asset.fractionable {
|
if !asset.tradable || !asset.fractionable {
|
||||||
return Err(StatusCode::FORBIDDEN);
|
return Err(StatusCode::FORBIDDEN);
|
||||||
}
|
}
|
||||||
let asset = Asset::from(asset);
|
let asset = Asset::from(asset);
|
||||||
|
|
||||||
let (data_message, data_response) = threads::data::Message::new(
|
create_send_await!(
|
||||||
|
data_sender,
|
||||||
|
threads::data::Message::new,
|
||||||
threads::data::Action::Add,
|
threads::data::Action::Add,
|
||||||
vec![(asset.symbol, asset.class)],
|
vec![(asset.symbol, asset.class)]
|
||||||
);
|
);
|
||||||
|
|
||||||
data_sender.send(data_message).await.unwrap();
|
|
||||||
data_response.await.unwrap();
|
|
||||||
|
|
||||||
Ok(StatusCode::CREATED)
|
Ok(StatusCode::CREATED)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete(
|
pub async fn delete(
|
||||||
Extension(app_config): Extension<Arc<Config>>,
|
Extension(config): Extension<Arc<Config>>,
|
||||||
Extension(data_sender): Extension<mpsc::Sender<threads::data::Message>>,
|
Extension(data_sender): Extension<mpsc::Sender<threads::data::Message>>,
|
||||||
Path(symbol): Path<String>,
|
Path(symbol): Path<String>,
|
||||||
) -> Result<StatusCode, StatusCode> {
|
) -> Result<StatusCode, StatusCode> {
|
||||||
let asset = database::assets::select_where_symbol(&app_config.clickhouse_client, &symbol)
|
let asset = database::assets::select_where_symbol(&config.clickhouse_client, &symbol)
|
||||||
.await
|
.await
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||||
.ok_or(StatusCode::NOT_FOUND)?;
|
.ok_or(StatusCode::NOT_FOUND)?;
|
||||||
|
|
||||||
let (asset_status_message, asset_status_response) = threads::data::Message::new(
|
create_send_await!(
|
||||||
|
data_sender,
|
||||||
|
threads::data::Message::new,
|
||||||
threads::data::Action::Remove,
|
threads::data::Action::Remove,
|
||||||
vec![(asset.symbol, asset.class)],
|
vec![(asset.symbol, asset.class)]
|
||||||
);
|
);
|
||||||
|
|
||||||
data_sender.send(asset_status_message).await.unwrap();
|
|
||||||
asset_status_response.await.unwrap();
|
|
||||||
|
|
||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
|
@@ -10,14 +10,14 @@ use log::info;
|
|||||||
use std::{net::SocketAddr, sync::Arc};
|
use std::{net::SocketAddr, sync::Arc};
|
||||||
use tokio::{net::TcpListener, sync::mpsc};
|
use tokio::{net::TcpListener, sync::mpsc};
|
||||||
|
|
||||||
pub async fn run(app_config: Arc<Config>, data_sender: mpsc::Sender<threads::data::Message>) {
|
pub async fn run(config: Arc<Config>, data_sender: mpsc::Sender<threads::data::Message>) {
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/health", get(health::get))
|
.route("/health", get(health::get))
|
||||||
.route("/assets", get(assets::get))
|
.route("/assets", get(assets::get))
|
||||||
.route("/assets/:symbol", get(assets::get_where_symbol))
|
.route("/assets/:symbol", get(assets::get_where_symbol))
|
||||||
.route("/assets", post(assets::add))
|
.route("/assets", post(assets::add))
|
||||||
.route("/assets/:symbol", delete(assets::delete))
|
.route("/assets/:symbol", delete(assets::delete))
|
||||||
.layer(Extension(app_config))
|
.layer(Extension(config))
|
||||||
.layer(Extension(data_sender));
|
.layer(Extension(data_sender));
|
||||||
|
|
||||||
let addr = SocketAddr::from(([0, 0, 0, 0], 7878));
|
let addr = SocketAddr::from(([0, 0, 0, 0], 7878));
|
||||||
|
@@ -1,4 +1,8 @@
|
|||||||
use crate::{config::Config, types::alpaca, utils::duration_until};
|
use crate::{
|
||||||
|
config::Config,
|
||||||
|
types::alpaca,
|
||||||
|
utils::{backoff, duration_until},
|
||||||
|
};
|
||||||
use log::info;
|
use log::info;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
@@ -30,9 +34,11 @@ impl From<alpaca::api::incoming::clock::Clock> for Message {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(app_config: Arc<Config>, sender: mpsc::Sender<Message>) {
|
pub async fn run(config: Arc<Config>, sender: mpsc::Sender<Message>) {
|
||||||
loop {
|
loop {
|
||||||
let clock = alpaca::api::incoming::clock::get(&app_config).await;
|
let clock = alpaca::api::incoming::clock::get(&config, Some(backoff::infinite()))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let sleep_until = duration_until(if clock.is_open {
|
let sleep_until = duration_until(if clock.is_open {
|
||||||
info!("Market is open, will close at {}.", clock.next_close);
|
info!("Market is open, will close at {}.", clock.next_close);
|
||||||
|
@@ -9,7 +9,7 @@ use crate::{
|
|||||||
Source,
|
Source,
|
||||||
},
|
},
|
||||||
news::Prediction,
|
news::Prediction,
|
||||||
Bar, Class, News,
|
Backfill, Bar, Class, News,
|
||||||
},
|
},
|
||||||
utils::{
|
utils::{
|
||||||
duration_until, last_minute, remove_slash_from_pair, FIFTEEN_MINUTES, ONE_MINUTE,
|
duration_until, last_minute, remove_slash_from_pair, FIFTEEN_MINUTES, ONE_MINUTE,
|
||||||
@@ -18,14 +18,15 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use futures_util::future::join_all;
|
use futures_util::future::join_all;
|
||||||
use log::{info, warn};
|
use log::{error, info, warn};
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
join, spawn,
|
spawn,
|
||||||
sync::{mpsc, oneshot, Mutex},
|
sync::{mpsc, oneshot, Mutex},
|
||||||
task::{block_in_place, JoinHandle},
|
task::{block_in_place, JoinHandle},
|
||||||
time::sleep,
|
time::sleep,
|
||||||
|
try_join,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
@@ -64,9 +65,12 @@ impl Message {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Handler: Send + Sync {
|
pub trait Handler: Send + Sync {
|
||||||
async fn select_latest_backfill(&self, symbol: String) -> Option<crate::types::Backfill>;
|
async fn select_latest_backfill(
|
||||||
async fn delete_backfills(&self, symbol: &[String]);
|
&self,
|
||||||
async fn delete_data(&self, symbol: &[String]);
|
symbol: String,
|
||||||
|
) -> Result<Option<Backfill>, clickhouse::error::Error>;
|
||||||
|
async fn delete_backfills(&self, symbol: &[String]) -> Result<(), clickhouse::error::Error>;
|
||||||
|
async fn delete_data(&self, symbol: &[String]) -> Result<(), clickhouse::error::Error>;
|
||||||
async fn queue_backfill(&self, symbol: &str, fetch_to: OffsetDateTime);
|
async fn queue_backfill(&self, symbol: &str, fetch_to: OffsetDateTime);
|
||||||
async fn backfill(&self, symbol: String, fetch_from: OffsetDateTime, fetch_to: OffsetDateTime);
|
async fn backfill(&self, symbol: String, fetch_from: OffsetDateTime, fetch_to: OffsetDateTime);
|
||||||
fn log_string(&self) -> &'static str;
|
fn log_string(&self) -> &'static str;
|
||||||
@@ -111,13 +115,14 @@ async fn handle_backfill_message(
|
|||||||
backfill_jobs.insert(
|
backfill_jobs.insert(
|
||||||
symbol.clone(),
|
symbol.clone(),
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
let fetch_from = handler
|
let fetch_from = match handler
|
||||||
.select_latest_backfill(symbol.clone())
|
.select_latest_backfill(symbol.clone())
|
||||||
.await
|
.await
|
||||||
.as_ref()
|
.unwrap()
|
||||||
.map_or(OffsetDateTime::UNIX_EPOCH, |backfill| {
|
{
|
||||||
backfill.time + ONE_SECOND
|
Some(latest_backfill) => latest_backfill.time + ONE_SECOND,
|
||||||
});
|
None => OffsetDateTime::UNIX_EPOCH,
|
||||||
|
};
|
||||||
|
|
||||||
let fetch_to = last_minute();
|
let fetch_to = last_minute();
|
||||||
|
|
||||||
@@ -142,10 +147,11 @@ async fn handle_backfill_message(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
join!(
|
try_join!(
|
||||||
handler.delete_backfills(&message.symbols),
|
handler.delete_backfills(&message.symbols),
|
||||||
handler.delete_data(&message.symbols)
|
handler.delete_data(&message.symbols)
|
||||||
);
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,10 +159,10 @@ async fn handle_backfill_message(
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct BarHandler {
|
struct BarHandler {
|
||||||
app_config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
data_url: &'static str,
|
data_url: &'static str,
|
||||||
api_query_constructor: fn(
|
api_query_constructor: fn(
|
||||||
app_config: &Arc<Config>,
|
config: &Arc<Config>,
|
||||||
symbol: String,
|
symbol: String,
|
||||||
fetch_from: OffsetDateTime,
|
fetch_from: OffsetDateTime,
|
||||||
fetch_to: OffsetDateTime,
|
fetch_to: OffsetDateTime,
|
||||||
@@ -165,7 +171,7 @@ struct BarHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn us_equity_query_constructor(
|
fn us_equity_query_constructor(
|
||||||
app_config: &Arc<Config>,
|
config: &Arc<Config>,
|
||||||
symbol: String,
|
symbol: String,
|
||||||
fetch_from: OffsetDateTime,
|
fetch_from: OffsetDateTime,
|
||||||
fetch_to: OffsetDateTime,
|
fetch_to: OffsetDateTime,
|
||||||
@@ -179,7 +185,7 @@ fn us_equity_query_constructor(
|
|||||||
limit: Some(10000),
|
limit: Some(10000),
|
||||||
adjustment: None,
|
adjustment: None,
|
||||||
asof: None,
|
asof: None,
|
||||||
feed: Some(app_config.alpaca_source),
|
feed: Some(config.alpaca_source),
|
||||||
currency: None,
|
currency: None,
|
||||||
page_token: next_page_token,
|
page_token: next_page_token,
|
||||||
sort: Some(Sort::Asc),
|
sort: Some(Sort::Asc),
|
||||||
@@ -206,30 +212,33 @@ fn crypto_query_constructor(
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Handler for BarHandler {
|
impl Handler for BarHandler {
|
||||||
async fn select_latest_backfill(&self, symbol: String) -> Option<crate::types::Backfill> {
|
async fn select_latest_backfill(
|
||||||
|
&self,
|
||||||
|
symbol: String,
|
||||||
|
) -> Result<Option<Backfill>, clickhouse::error::Error> {
|
||||||
database::backfills::select_latest_where_symbol(
|
database::backfills::select_latest_where_symbol(
|
||||||
&self.app_config.clickhouse_client,
|
&self.config.clickhouse_client,
|
||||||
&database::backfills::Table::Bars,
|
&database::backfills::Table::Bars,
|
||||||
&symbol,
|
&symbol,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_backfills(&self, symbols: &[String]) {
|
async fn delete_backfills(&self, symbols: &[String]) -> Result<(), clickhouse::error::Error> {
|
||||||
database::backfills::delete_where_symbols(
|
database::backfills::delete_where_symbols(
|
||||||
&self.app_config.clickhouse_client,
|
&self.config.clickhouse_client,
|
||||||
&database::backfills::Table::Bars,
|
&database::backfills::Table::Bars,
|
||||||
symbols,
|
symbols,
|
||||||
)
|
)
|
||||||
.await;
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_data(&self, symbols: &[String]) {
|
async fn delete_data(&self, symbols: &[String]) -> Result<(), clickhouse::error::Error> {
|
||||||
database::bars::delete_where_symbols(&self.app_config.clickhouse_client, symbols).await;
|
database::bars::delete_where_symbols(&self.config.clickhouse_client, symbols).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn queue_backfill(&self, symbol: &str, fetch_to: OffsetDateTime) {
|
async fn queue_backfill(&self, symbol: &str, fetch_to: OffsetDateTime) {
|
||||||
if self.app_config.alpaca_source == Source::Iex {
|
if self.config.alpaca_source == Source::Iex {
|
||||||
let run_delay = duration_until(fetch_to + FIFTEEN_MINUTES + ONE_MINUTE);
|
let run_delay = duration_until(fetch_to + FIFTEEN_MINUTES + ONE_MINUTE);
|
||||||
info!("Queing bar backfill for {} in {:?}.", symbol, run_delay);
|
info!("Queing bar backfill for {} in {:?}.", symbol, run_delay);
|
||||||
sleep(run_delay).await;
|
sleep(run_delay).await;
|
||||||
@@ -243,18 +252,23 @@ impl Handler for BarHandler {
|
|||||||
let mut next_page_token = None;
|
let mut next_page_token = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let message = alpaca::api::incoming::bar::get_historical(
|
let Ok(message) = alpaca::api::incoming::bar::get_historical(
|
||||||
&self.app_config,
|
&self.config,
|
||||||
self.data_url,
|
self.data_url,
|
||||||
&(self.api_query_constructor)(
|
&(self.api_query_constructor)(
|
||||||
&self.app_config,
|
&self.config,
|
||||||
symbol.clone(),
|
symbol.clone(),
|
||||||
fetch_from,
|
fetch_from,
|
||||||
fetch_to,
|
fetch_to,
|
||||||
next_page_token.clone(),
|
next_page_token.clone(),
|
||||||
),
|
),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await;
|
.await
|
||||||
|
else {
|
||||||
|
error!("Failed to backfill bars for {}.", symbol);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
message.bars.into_iter().for_each(|(symbol, bar_vec)| {
|
message.bars.into_iter().for_each(|(symbol, bar_vec)| {
|
||||||
for bar in bar_vec {
|
for bar in bar_vec {
|
||||||
@@ -274,13 +288,17 @@ impl Handler for BarHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let backfill = bars.last().unwrap().clone().into();
|
let backfill = bars.last().unwrap().clone().into();
|
||||||
database::bars::upsert_batch(&self.app_config.clickhouse_client, bars).await;
|
|
||||||
|
database::bars::upsert_batch(&self.config.clickhouse_client, bars)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
database::backfills::upsert(
|
database::backfills::upsert(
|
||||||
&self.app_config.clickhouse_client,
|
&self.config.clickhouse_client,
|
||||||
&database::backfills::Table::Bars,
|
&database::backfills::Table::Bars,
|
||||||
&backfill,
|
&backfill,
|
||||||
)
|
)
|
||||||
.await;
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
info!("Backfilled bars for {}.", symbol);
|
info!("Backfilled bars for {}.", symbol);
|
||||||
}
|
}
|
||||||
@@ -291,31 +309,34 @@ impl Handler for BarHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct NewsHandler {
|
struct NewsHandler {
|
||||||
app_config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Handler for NewsHandler {
|
impl Handler for NewsHandler {
|
||||||
async fn select_latest_backfill(&self, symbol: String) -> Option<crate::types::Backfill> {
|
async fn select_latest_backfill(
|
||||||
|
&self,
|
||||||
|
symbol: String,
|
||||||
|
) -> Result<Option<Backfill>, clickhouse::error::Error> {
|
||||||
database::backfills::select_latest_where_symbol(
|
database::backfills::select_latest_where_symbol(
|
||||||
&self.app_config.clickhouse_client,
|
&self.config.clickhouse_client,
|
||||||
&database::backfills::Table::News,
|
&database::backfills::Table::News,
|
||||||
&symbol,
|
&symbol,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_backfills(&self, symbols: &[String]) {
|
async fn delete_backfills(&self, symbols: &[String]) -> Result<(), clickhouse::error::Error> {
|
||||||
database::backfills::delete_where_symbols(
|
database::backfills::delete_where_symbols(
|
||||||
&self.app_config.clickhouse_client,
|
&self.config.clickhouse_client,
|
||||||
&database::backfills::Table::News,
|
&database::backfills::Table::News,
|
||||||
symbols,
|
symbols,
|
||||||
)
|
)
|
||||||
.await;
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_data(&self, symbols: &[String]) {
|
async fn delete_data(&self, symbols: &[String]) -> Result<(), clickhouse::error::Error> {
|
||||||
database::news::delete_where_symbols(&self.app_config.clickhouse_client, symbols).await;
|
database::news::delete_where_symbols(&self.config.clickhouse_client, symbols).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn queue_backfill(&self, symbol: &str, fetch_to: OffsetDateTime) {
|
async fn queue_backfill(&self, symbol: &str, fetch_to: OffsetDateTime) {
|
||||||
@@ -331,8 +352,8 @@ impl Handler for NewsHandler {
|
|||||||
let mut next_page_token = None;
|
let mut next_page_token = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let message = alpaca::api::incoming::news::get_historical(
|
let Ok(message) = alpaca::api::incoming::news::get_historical(
|
||||||
&self.app_config,
|
&self.config,
|
||||||
&api::outgoing::news::News {
|
&api::outgoing::news::News {
|
||||||
symbols: vec![remove_slash_from_pair(&symbol)],
|
symbols: vec![remove_slash_from_pair(&symbol)],
|
||||||
start: Some(fetch_from),
|
start: Some(fetch_from),
|
||||||
@@ -343,8 +364,13 @@ impl Handler for NewsHandler {
|
|||||||
page_token: next_page_token.clone(),
|
page_token: next_page_token.clone(),
|
||||||
sort: Some(Sort::Asc),
|
sort: Some(Sort::Asc),
|
||||||
},
|
},
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await;
|
.await
|
||||||
|
else {
|
||||||
|
error!("Failed to backfill news for {}.", symbol);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
message.news.into_iter().for_each(|news_item| {
|
message.news.into_iter().for_each(|news_item| {
|
||||||
news.push(News::from(news_item));
|
news.push(News::from(news_item));
|
||||||
@@ -366,23 +392,19 @@ impl Handler for NewsHandler {
|
|||||||
.map(|news| format!("{}\n\n{}", news.headline, news.content))
|
.map(|news| format!("{}\n\n{}", news.headline, news.content))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let predictions = join_all(
|
let predictions = join_all(inputs.chunks(self.config.max_bert_inputs).map(|inputs| {
|
||||||
inputs
|
let sequence_classifier = self.config.sequence_classifier.clone();
|
||||||
.chunks(self.app_config.max_bert_inputs)
|
async move {
|
||||||
.map(|inputs| {
|
let sequence_classifier = sequence_classifier.lock().await;
|
||||||
let sequence_classifier = self.app_config.sequence_classifier.clone();
|
block_in_place(|| {
|
||||||
async move {
|
sequence_classifier
|
||||||
let sequence_classifier = sequence_classifier.lock().await;
|
.predict(inputs.iter().map(String::as_str).collect::<Vec<_>>())
|
||||||
block_in_place(|| {
|
.into_iter()
|
||||||
sequence_classifier
|
.map(|label| Prediction::try_from(label).unwrap())
|
||||||
.predict(inputs.iter().map(String::as_str).collect::<Vec<_>>())
|
.collect::<Vec<_>>()
|
||||||
.into_iter()
|
})
|
||||||
.map(|label| Prediction::try_from(label).unwrap())
|
}
|
||||||
.collect::<Vec<_>>()
|
}))
|
||||||
})
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten();
|
.flatten();
|
||||||
@@ -398,13 +420,17 @@ impl Handler for NewsHandler {
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let backfill = (news.last().unwrap().clone(), symbol.clone()).into();
|
let backfill = (news.last().unwrap().clone(), symbol.clone()).into();
|
||||||
database::news::upsert_batch(&self.app_config.clickhouse_client, news).await;
|
|
||||||
|
database::news::upsert_batch(&self.config.clickhouse_client, news)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
database::backfills::upsert(
|
database::backfills::upsert(
|
||||||
&self.app_config.clickhouse_client,
|
&self.config.clickhouse_client,
|
||||||
&database::backfills::Table::News,
|
&database::backfills::Table::News,
|
||||||
&backfill,
|
&backfill,
|
||||||
)
|
)
|
||||||
.await;
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
info!("Backfilled news for {}.", symbol);
|
info!("Backfilled news for {}.", symbol);
|
||||||
}
|
}
|
||||||
@@ -414,18 +440,18 @@ impl Handler for NewsHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_handler(thread_type: ThreadType, app_config: Arc<Config>) -> Box<dyn Handler> {
|
pub fn create_handler(thread_type: ThreadType, config: Arc<Config>) -> Box<dyn Handler> {
|
||||||
match thread_type {
|
match thread_type {
|
||||||
ThreadType::Bars(Class::UsEquity) => Box::new(BarHandler {
|
ThreadType::Bars(Class::UsEquity) => Box::new(BarHandler {
|
||||||
app_config,
|
config,
|
||||||
data_url: ALPACA_STOCK_DATA_URL,
|
data_url: ALPACA_STOCK_DATA_URL,
|
||||||
api_query_constructor: us_equity_query_constructor,
|
api_query_constructor: us_equity_query_constructor,
|
||||||
}),
|
}),
|
||||||
ThreadType::Bars(Class::Crypto) => Box::new(BarHandler {
|
ThreadType::Bars(Class::Crypto) => Box::new(BarHandler {
|
||||||
app_config,
|
config,
|
||||||
data_url: ALPACA_CRYPTO_DATA_URL,
|
data_url: ALPACA_CRYPTO_DATA_URL,
|
||||||
api_query_constructor: crypto_query_constructor,
|
api_query_constructor: crypto_query_constructor,
|
||||||
}),
|
}),
|
||||||
ThreadType::News => Box::new(NewsHandler { app_config }),
|
ThreadType::News => Box::new(NewsHandler { config }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,9 +6,9 @@ use crate::{
|
|||||||
config::{
|
config::{
|
||||||
Config, ALPACA_CRYPTO_WEBSOCKET_URL, ALPACA_NEWS_WEBSOCKET_URL, ALPACA_STOCK_WEBSOCKET_URL,
|
Config, ALPACA_CRYPTO_WEBSOCKET_URL, ALPACA_NEWS_WEBSOCKET_URL, ALPACA_STOCK_WEBSOCKET_URL,
|
||||||
},
|
},
|
||||||
database,
|
create_send_await, database,
|
||||||
types::{alpaca, Asset, Class},
|
types::{alpaca, Asset, Class},
|
||||||
utils::{authenticate, cleanup},
|
utils::{authenticate, backoff, cleanup},
|
||||||
};
|
};
|
||||||
use futures_util::{future::join_all, StreamExt};
|
use futures_util::{future::join_all, StreamExt};
|
||||||
use itertools::{Either, Itertools};
|
use itertools::{Either, Itertools};
|
||||||
@@ -52,22 +52,22 @@ pub enum ThreadType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(
|
pub async fn run(
|
||||||
app_config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
mut receiver: mpsc::Receiver<Message>,
|
mut receiver: mpsc::Receiver<Message>,
|
||||||
mut clock_receiver: mpsc::Receiver<clock::Message>,
|
mut clock_receiver: mpsc::Receiver<clock::Message>,
|
||||||
) {
|
) {
|
||||||
let (bars_us_equity_websocket_sender, bars_us_equity_backfill_sender) =
|
let (bars_us_equity_websocket_sender, bars_us_equity_backfill_sender) =
|
||||||
init_thread(app_config.clone(), ThreadType::Bars(Class::UsEquity)).await;
|
init_thread(config.clone(), ThreadType::Bars(Class::UsEquity)).await;
|
||||||
let (bars_crypto_websocket_sender, bars_crypto_backfill_sender) =
|
let (bars_crypto_websocket_sender, bars_crypto_backfill_sender) =
|
||||||
init_thread(app_config.clone(), ThreadType::Bars(Class::Crypto)).await;
|
init_thread(config.clone(), ThreadType::Bars(Class::Crypto)).await;
|
||||||
let (news_websocket_sender, news_backfill_sender) =
|
let (news_websocket_sender, news_backfill_sender) =
|
||||||
init_thread(app_config.clone(), ThreadType::News).await;
|
init_thread(config.clone(), ThreadType::News).await;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
select! {
|
select! {
|
||||||
Some(message) = receiver.recv() => {
|
Some(message) = receiver.recv() => {
|
||||||
spawn(handle_message(
|
spawn(handle_message(
|
||||||
app_config.clone(),
|
config.clone(),
|
||||||
bars_us_equity_websocket_sender.clone(),
|
bars_us_equity_websocket_sender.clone(),
|
||||||
bars_us_equity_backfill_sender.clone(),
|
bars_us_equity_backfill_sender.clone(),
|
||||||
bars_crypto_websocket_sender.clone(),
|
bars_crypto_websocket_sender.clone(),
|
||||||
@@ -79,7 +79,7 @@ pub async fn run(
|
|||||||
}
|
}
|
||||||
Some(_) = clock_receiver.recv() => {
|
Some(_) = clock_receiver.recv() => {
|
||||||
spawn(handle_clock_message(
|
spawn(handle_clock_message(
|
||||||
app_config.clone(),
|
config.clone(),
|
||||||
bars_us_equity_backfill_sender.clone(),
|
bars_us_equity_backfill_sender.clone(),
|
||||||
bars_crypto_backfill_sender.clone(),
|
bars_crypto_backfill_sender.clone(),
|
||||||
news_backfill_sender.clone(),
|
news_backfill_sender.clone(),
|
||||||
@@ -91,34 +91,33 @@ pub async fn run(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn init_thread(
|
async fn init_thread(
|
||||||
app_config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
thread_type: ThreadType,
|
thread_type: ThreadType,
|
||||||
) -> (
|
) -> (
|
||||||
mpsc::Sender<websocket::Message>,
|
mpsc::Sender<websocket::Message>,
|
||||||
mpsc::Sender<backfill::Message>,
|
mpsc::Sender<backfill::Message>,
|
||||||
) {
|
) {
|
||||||
let websocket_url = match thread_type {
|
let websocket_url = match thread_type {
|
||||||
ThreadType::Bars(Class::UsEquity) => format!(
|
ThreadType::Bars(Class::UsEquity) => {
|
||||||
"{}/{}",
|
format!("{}/{}", ALPACA_STOCK_WEBSOCKET_URL, &config.alpaca_source)
|
||||||
ALPACA_STOCK_WEBSOCKET_URL, &app_config.alpaca_source
|
}
|
||||||
),
|
|
||||||
ThreadType::Bars(Class::Crypto) => ALPACA_CRYPTO_WEBSOCKET_URL.into(),
|
ThreadType::Bars(Class::Crypto) => ALPACA_CRYPTO_WEBSOCKET_URL.into(),
|
||||||
ThreadType::News => ALPACA_NEWS_WEBSOCKET_URL.into(),
|
ThreadType::News => ALPACA_NEWS_WEBSOCKET_URL.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let (websocket, _) = connect_async(websocket_url).await.unwrap();
|
let (websocket, _) = connect_async(websocket_url).await.unwrap();
|
||||||
let (mut websocket_sink, mut websocket_stream) = websocket.split();
|
let (mut websocket_sink, mut websocket_stream) = websocket.split();
|
||||||
authenticate(&app_config, &mut websocket_sink, &mut websocket_stream).await;
|
authenticate(&config, &mut websocket_sink, &mut websocket_stream).await;
|
||||||
|
|
||||||
let (backfill_sender, backfill_receiver) = mpsc::channel(100);
|
let (backfill_sender, backfill_receiver) = mpsc::channel(100);
|
||||||
spawn(backfill::run(
|
spawn(backfill::run(
|
||||||
Arc::new(backfill::create_handler(thread_type, app_config.clone())),
|
Arc::new(backfill::create_handler(thread_type, config.clone())),
|
||||||
backfill_receiver,
|
backfill_receiver,
|
||||||
));
|
));
|
||||||
|
|
||||||
let (websocket_sender, websocket_receiver) = mpsc::channel(100);
|
let (websocket_sender, websocket_receiver) = mpsc::channel(100);
|
||||||
spawn(websocket::run(
|
spawn(websocket::run(
|
||||||
Arc::new(websocket::create_handler(thread_type, app_config.clone())),
|
Arc::new(websocket::create_handler(thread_type, config.clone())),
|
||||||
websocket_receiver,
|
websocket_receiver,
|
||||||
websocket_stream,
|
websocket_stream,
|
||||||
websocket_sink,
|
websocket_sink,
|
||||||
@@ -127,17 +126,9 @@ async fn init_thread(
|
|||||||
(websocket_sender, backfill_sender)
|
(websocket_sender, backfill_sender)
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! create_send_await {
|
|
||||||
($sender:expr, $action:expr, $($contents:expr),*) => {
|
|
||||||
let (message, receiver) = $action($($contents),*);
|
|
||||||
$sender.send(message).await.unwrap();
|
|
||||||
receiver.await.unwrap();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn handle_message(
|
async fn handle_message(
|
||||||
app_config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
bars_us_equity_websocket_sender: mpsc::Sender<websocket::Message>,
|
bars_us_equity_websocket_sender: mpsc::Sender<websocket::Message>,
|
||||||
bars_us_equity_backfill_sender: mpsc::Sender<backfill::Message>,
|
bars_us_equity_backfill_sender: mpsc::Sender<backfill::Message>,
|
||||||
bars_crypto_websocket_sender: mpsc::Sender<websocket::Message>,
|
bars_crypto_websocket_sender: mpsc::Sender<websocket::Message>,
|
||||||
@@ -221,22 +212,30 @@ async fn handle_message(
|
|||||||
|
|
||||||
match message.action {
|
match message.action {
|
||||||
Action::Add => {
|
Action::Add => {
|
||||||
let assets =
|
let assets = join_all(symbols.into_iter().map(|symbol| {
|
||||||
join_all(symbols.into_iter().map(|symbol| {
|
let config = config.clone();
|
||||||
let app_config = app_config.clone();
|
async move {
|
||||||
async move {
|
Asset::from(
|
||||||
alpaca::api::incoming::asset::get_by_symbol(&app_config, &symbol).await
|
alpaca::api::incoming::asset::get_by_symbol(
|
||||||
}
|
&config,
|
||||||
}))
|
&symbol,
|
||||||
.await
|
Some(backoff::infinite()),
|
||||||
.into_iter()
|
)
|
||||||
.map(|result| Asset::from(result.unwrap()))
|
.await
|
||||||
.collect::<Vec<_>>();
|
.unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
|
||||||
database::assets::upsert_batch(&app_config.clickhouse_client, assets).await;
|
database::assets::upsert_batch(&config.clickhouse_client, assets)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
Action::Remove => {
|
Action::Remove => {
|
||||||
database::assets::delete_where_symbols(&app_config.clickhouse_client, &symbols).await;
|
database::assets::delete_where_symbols(&config.clickhouse_client, &symbols)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,14 +243,16 @@ async fn handle_message(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_clock_message(
|
async fn handle_clock_message(
|
||||||
app_config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
bars_us_equity_backfill_sender: mpsc::Sender<backfill::Message>,
|
bars_us_equity_backfill_sender: mpsc::Sender<backfill::Message>,
|
||||||
bars_crypto_backfill_sender: mpsc::Sender<backfill::Message>,
|
bars_crypto_backfill_sender: mpsc::Sender<backfill::Message>,
|
||||||
news_backfill_sender: mpsc::Sender<backfill::Message>,
|
news_backfill_sender: mpsc::Sender<backfill::Message>,
|
||||||
) {
|
) {
|
||||||
cleanup(&app_config.clickhouse_client).await;
|
cleanup(&config.clickhouse_client).await.unwrap();
|
||||||
|
|
||||||
let assets = database::assets::select(&app_config.clickhouse_client).await;
|
let assets = database::assets::select(&config.clickhouse_client)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let (us_equity_symbols, crypto_symbols): (Vec<_>, Vec<_>) = assets
|
let (us_equity_symbols, crypto_symbols): (Vec<_>, Vec<_>) = assets
|
||||||
.clone()
|
.clone()
|
||||||
|
@@ -221,7 +221,7 @@ async fn handle_websocket_message(
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct BarsHandler {
|
struct BarsHandler {
|
||||||
app_config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -286,7 +286,10 @@ impl Handler for BarsHandler {
|
|||||||
| websocket::incoming::Message::UpdatedBar(message) => {
|
| websocket::incoming::Message::UpdatedBar(message) => {
|
||||||
let bar = Bar::from(message);
|
let bar = Bar::from(message);
|
||||||
debug!("Received bar for {}: {}.", bar.symbol, bar.time);
|
debug!("Received bar for {}: {}.", bar.symbol, bar.time);
|
||||||
database::bars::upsert(&self.app_config.clickhouse_client, &bar).await;
|
|
||||||
|
database::bars::upsert(&self.config.clickhouse_client, &bar)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
websocket::incoming::Message::Success(_) => {}
|
websocket::incoming::Message::Success(_) => {}
|
||||||
websocket::incoming::Message::Error(message) => {
|
websocket::incoming::Message::Error(message) => {
|
||||||
@@ -298,7 +301,7 @@ impl Handler for BarsHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct NewsHandler {
|
struct NewsHandler {
|
||||||
app_config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -373,7 +376,7 @@ impl Handler for NewsHandler {
|
|||||||
|
|
||||||
let input = format!("{}\n\n{}", news.headline, news.content);
|
let input = format!("{}\n\n{}", news.headline, news.content);
|
||||||
|
|
||||||
let sequence_classifier = self.app_config.sequence_classifier.lock().await;
|
let sequence_classifier = self.config.sequence_classifier.lock().await;
|
||||||
let prediction = block_in_place(|| {
|
let prediction = block_in_place(|| {
|
||||||
sequence_classifier
|
sequence_classifier
|
||||||
.predict(vec![input.as_str()])
|
.predict(vec![input.as_str()])
|
||||||
@@ -388,7 +391,10 @@ impl Handler for NewsHandler {
|
|||||||
confidence: prediction.confidence,
|
confidence: prediction.confidence,
|
||||||
..news
|
..news
|
||||||
};
|
};
|
||||||
database::news::upsert(&self.app_config.clickhouse_client, &news).await;
|
|
||||||
|
database::news::upsert(&self.config.clickhouse_client, &news)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
websocket::incoming::Message::Success(_) => {}
|
websocket::incoming::Message::Success(_) => {}
|
||||||
websocket::incoming::Message::Error(message) => {
|
websocket::incoming::Message::Error(message) => {
|
||||||
@@ -401,9 +407,9 @@ impl Handler for NewsHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_handler(thread_type: ThreadType, app_config: Arc<Config>) -> Box<dyn Handler> {
|
pub fn create_handler(thread_type: ThreadType, config: Arc<Config>) -> Box<dyn Handler> {
|
||||||
match thread_type {
|
match thread_type {
|
||||||
ThreadType::Bars(_) => Box::new(BarsHandler { app_config }),
|
ThreadType::Bars(_) => Box::new(BarsHandler { config }),
|
||||||
ThreadType::News => Box::new(NewsHandler { app_config }),
|
ThreadType::News => Box::new(NewsHandler { config }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
config::{Config, ALPACA_ASSET_API_URL},
|
config::{Config, ALPACA_ASSET_API_URL},
|
||||||
types::{self, alpaca::api::impl_from_enum},
|
impl_from_enum, types,
|
||||||
};
|
};
|
||||||
use backoff::{future::retry, ExponentialBackoff};
|
use backoff::{future::retry_notify, ExponentialBackoff};
|
||||||
use http::StatusCode;
|
use log::warn;
|
||||||
|
use reqwest::Error;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::sync::Arc;
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
@@ -87,26 +88,38 @@ impl From<Asset> for types::Asset {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_by_symbol(app_config: &Arc<Config>, symbol: &str) -> Result<Asset, StatusCode> {
|
pub async fn get_by_symbol(
|
||||||
retry(ExponentialBackoff::default(), || async {
|
config: &Arc<Config>,
|
||||||
app_config.alpaca_rate_limit.until_ready().await;
|
symbol: &str,
|
||||||
app_config
|
backoff: Option<ExponentialBackoff>,
|
||||||
.alpaca_client
|
) -> Result<Asset, Error> {
|
||||||
.get(&format!("{ALPACA_ASSET_API_URL}/{symbol}"))
|
retry_notify(
|
||||||
.send()
|
backoff.unwrap_or_default(),
|
||||||
.await?
|
|| async {
|
||||||
.error_for_status()
|
config.alpaca_rate_limit.until_ready().await;
|
||||||
.map_err(|e| match e.status() {
|
config
|
||||||
Some(reqwest::StatusCode::NOT_FOUND) => backoff::Error::Permanent(e),
|
.alpaca_client
|
||||||
_ => e.into(),
|
.get(&format!("{ALPACA_ASSET_API_URL}/{symbol}"))
|
||||||
})?
|
.send()
|
||||||
.json::<Asset>()
|
.await?
|
||||||
.await
|
.error_for_status()
|
||||||
.map_err(backoff::Error::Permanent)
|
.map_err(|e| match e.status() {
|
||||||
})
|
Some(reqwest::StatusCode::FORBIDDEN | reqwest::StatusCode::NOT_FOUND) => {
|
||||||
|
backoff::Error::Permanent(e)
|
||||||
|
}
|
||||||
|
_ => e.into(),
|
||||||
|
})?
|
||||||
|
.json::<Asset>()
|
||||||
|
.await
|
||||||
|
.map_err(backoff::Error::Permanent)
|
||||||
|
},
|
||||||
|
|e, duration: Duration| {
|
||||||
|
warn!(
|
||||||
|
"Failed to get asset, will retry in {} seconds: {}",
|
||||||
|
duration.as_secs(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| match e.status() {
|
|
||||||
Some(reqwest::StatusCode::NOT_FOUND) => StatusCode::NOT_FOUND,
|
|
||||||
_ => panic!("Unexpected error: {e}."),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@@ -2,9 +2,11 @@ use crate::{
|
|||||||
config::Config,
|
config::Config,
|
||||||
types::{self, alpaca::api::outgoing},
|
types::{self, alpaca::api::outgoing},
|
||||||
};
|
};
|
||||||
use backoff::{future::retry, ExponentialBackoff};
|
use backoff::{future::retry_notify, ExponentialBackoff};
|
||||||
|
use log::warn;
|
||||||
|
use reqwest::Error;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
||||||
@@ -51,23 +53,37 @@ pub struct Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_historical(
|
pub async fn get_historical(
|
||||||
app_config: &Arc<Config>,
|
config: &Arc<Config>,
|
||||||
data_url: &str,
|
data_url: &str,
|
||||||
query: &outgoing::bar::Bar,
|
query: &outgoing::bar::Bar,
|
||||||
) -> Message {
|
backoff: Option<ExponentialBackoff>,
|
||||||
retry(ExponentialBackoff::default(), || async {
|
) -> Result<Message, Error> {
|
||||||
app_config.alpaca_rate_limit.until_ready().await;
|
retry_notify(
|
||||||
app_config
|
backoff.unwrap_or_default(),
|
||||||
.alpaca_client
|
|| async {
|
||||||
.get(data_url)
|
config.alpaca_rate_limit.until_ready().await;
|
||||||
.query(query)
|
config
|
||||||
.send()
|
.alpaca_client
|
||||||
.await?
|
.get(data_url)
|
||||||
.error_for_status()?
|
.query(query)
|
||||||
.json::<Message>()
|
.send()
|
||||||
.await
|
.await?
|
||||||
.map_err(backoff::Error::Permanent)
|
.error_for_status()
|
||||||
})
|
.map_err(|e| match e.status() {
|
||||||
|
Some(reqwest::StatusCode::FORBIDDEN) => backoff::Error::Permanent(e),
|
||||||
|
_ => e.into(),
|
||||||
|
})?
|
||||||
|
.json::<Message>()
|
||||||
|
.await
|
||||||
|
.map_err(backoff::Error::Permanent)
|
||||||
|
},
|
||||||
|
|e, duration: Duration| {
|
||||||
|
warn!(
|
||||||
|
"Failed to get historical bars, will retry in {} seconds: {}",
|
||||||
|
duration.as_secs(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
use crate::config::{Config, ALPACA_CLOCK_API_URL};
|
use crate::config::{Config, ALPACA_CLOCK_API_URL};
|
||||||
use backoff::{future::retry, ExponentialBackoff};
|
use backoff::{future::retry_notify, ExponentialBackoff};
|
||||||
|
use log::warn;
|
||||||
|
use reqwest::Error;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::sync::Arc;
|
use std::{sync::Arc, time::Duration};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
||||||
@@ -15,18 +17,35 @@ pub struct Clock {
|
|||||||
pub next_close: OffsetDateTime,
|
pub next_close: OffsetDateTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get(app_config: &Arc<Config>) -> Clock {
|
pub async fn get(
|
||||||
retry(ExponentialBackoff::default(), || async {
|
config: &Arc<Config>,
|
||||||
app_config.alpaca_rate_limit.until_ready().await;
|
backoff: Option<ExponentialBackoff>,
|
||||||
app_config
|
) -> Result<Clock, Error> {
|
||||||
.alpaca_client
|
retry_notify(
|
||||||
.get(ALPACA_CLOCK_API_URL)
|
backoff.unwrap_or_default(),
|
||||||
.send()
|
|| async {
|
||||||
.await?
|
config.alpaca_rate_limit.until_ready().await;
|
||||||
.json::<Clock>()
|
config
|
||||||
.await
|
.alpaca_client
|
||||||
.map_err(backoff::Error::Permanent)
|
.get(ALPACA_CLOCK_API_URL)
|
||||||
})
|
.send()
|
||||||
|
.await?
|
||||||
|
.error_for_status()
|
||||||
|
.map_err(|e| match e.status() {
|
||||||
|
Some(reqwest::StatusCode::FORBIDDEN) => backoff::Error::Permanent(e),
|
||||||
|
_ => e.into(),
|
||||||
|
})?
|
||||||
|
.json::<Clock>()
|
||||||
|
.await
|
||||||
|
.map_err(backoff::Error::Permanent)
|
||||||
|
},
|
||||||
|
|e, duration: Duration| {
|
||||||
|
warn!(
|
||||||
|
"Failed to get clock, will retry in {} seconds: {}",
|
||||||
|
duration.as_secs(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
@@ -3,9 +3,11 @@ use crate::{
|
|||||||
types::{self, alpaca::api::outgoing},
|
types::{self, alpaca::api::outgoing},
|
||||||
utils::{add_slash_to_pair, normalize_news_content},
|
utils::{add_slash_to_pair, normalize_news_content},
|
||||||
};
|
};
|
||||||
use backoff::{future::retry, ExponentialBackoff};
|
use backoff::{future::retry_notify, ExponentialBackoff};
|
||||||
|
use log::warn;
|
||||||
|
use reqwest::Error;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::sync::Arc;
|
use std::{sync::Arc, time::Duration};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
||||||
@@ -70,20 +72,33 @@ pub struct Message {
|
|||||||
pub next_page_token: Option<String>,
|
pub next_page_token: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_historical(app_config: &Arc<Config>, query: &outgoing::news::News) -> Message {
|
pub async fn get_historical(
|
||||||
retry(ExponentialBackoff::default(), || async {
|
config: &Arc<Config>,
|
||||||
app_config.alpaca_rate_limit.until_ready().await;
|
query: &outgoing::news::News,
|
||||||
app_config
|
backoff: Option<ExponentialBackoff>,
|
||||||
.alpaca_client
|
) -> Result<Message, Error> {
|
||||||
.get(ALPACA_NEWS_DATA_URL)
|
retry_notify(
|
||||||
.query(query)
|
backoff.unwrap_or_default(),
|
||||||
.send()
|
|| async {
|
||||||
.await?
|
config.alpaca_rate_limit.until_ready().await;
|
||||||
.error_for_status()?
|
config
|
||||||
.json::<Message>()
|
.alpaca_client
|
||||||
.await
|
.get(ALPACA_NEWS_DATA_URL)
|
||||||
.map_err(backoff::Error::Permanent)
|
.query(query)
|
||||||
})
|
.send()
|
||||||
|
.await?
|
||||||
|
.error_for_status()?
|
||||||
|
.json::<Message>()
|
||||||
|
.await
|
||||||
|
.map_err(backoff::Error::Permanent)
|
||||||
|
},
|
||||||
|
|e, duration: Duration| {
|
||||||
|
warn!(
|
||||||
|
"Failed to get historical news, will retry in {} seconds: {}",
|
||||||
|
duration.as_secs(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
@@ -1,24 +1,2 @@
|
|||||||
pub mod incoming;
|
pub mod incoming;
|
||||||
pub mod outgoing;
|
pub mod outgoing;
|
||||||
|
|
||||||
macro_rules! impl_from_enum {
|
|
||||||
($source:ty, $target:ty, $( $variant:ident ),* ) => {
|
|
||||||
impl From<$source> for $target {
|
|
||||||
fn from(item: $source) -> Self {
|
|
||||||
match item {
|
|
||||||
$( <$source>::$variant => <$target>::$variant, )*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<$target> for $source {
|
|
||||||
fn from(item: $target) -> Self {
|
|
||||||
match item {
|
|
||||||
$( <$target>::$variant => <$source>::$variant, )*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
use impl_from_enum;
|
|
||||||
|
8
src/utils/backoff.rs
Normal file
8
src/utils/backoff.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
use backoff::ExponentialBackoff;
|
||||||
|
|
||||||
|
pub fn infinite() -> ExponentialBackoff {
|
||||||
|
ExponentialBackoff {
|
||||||
|
max_elapsed_time: None,
|
||||||
|
..ExponentialBackoff::default()
|
||||||
|
}
|
||||||
|
}
|
@@ -1,11 +1,12 @@
|
|||||||
use crate::database;
|
use crate::database;
|
||||||
use clickhouse::Client;
|
use clickhouse::{error::Error, Client};
|
||||||
use tokio::join;
|
use tokio::try_join;
|
||||||
|
|
||||||
pub async fn cleanup(clickhouse_client: &Client) {
|
pub async fn cleanup(clickhouse_client: &Client) -> Result<(), Error> {
|
||||||
join!(
|
try_join!(
|
||||||
database::bars::cleanup(clickhouse_client),
|
database::bars::cleanup(clickhouse_client),
|
||||||
database::news::cleanup(clickhouse_client),
|
database::news::cleanup(clickhouse_client),
|
||||||
database::backfills::cleanup(clickhouse_client)
|
database::backfills::cleanup(clickhouse_client)
|
||||||
);
|
)
|
||||||
|
.map(|_| ())
|
||||||
}
|
}
|
||||||
|
29
src/utils/macros.rs
Normal file
29
src/utils/macros.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#[macro_export]
|
||||||
|
macro_rules! impl_from_enum {
|
||||||
|
($source:ty, $target:ty, $( $variant:ident ),* ) => {
|
||||||
|
impl From<$source> for $target {
|
||||||
|
fn from(item: $source) -> Self {
|
||||||
|
match item {
|
||||||
|
$( <$source>::$variant => <$target>::$variant, )*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<$target> for $source {
|
||||||
|
fn from(item: $target) -> Self {
|
||||||
|
match item {
|
||||||
|
$( <$target>::$variant => <$source>::$variant, )*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! create_send_await {
|
||||||
|
($sender:expr, $action:expr, $($contents:expr),*) => {
|
||||||
|
let (message, receiver) = $action($($contents),*);
|
||||||
|
$sender.send(message).await.unwrap();
|
||||||
|
receiver.await.unwrap()
|
||||||
|
};
|
||||||
|
}
|
@@ -1,4 +1,6 @@
|
|||||||
|
pub mod backoff;
|
||||||
pub mod cleanup;
|
pub mod cleanup;
|
||||||
|
pub mod macros;
|
||||||
pub mod news;
|
pub mod news;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
pub mod websocket;
|
pub mod websocket;
|
||||||
|
@@ -1,13 +1,17 @@
|
|||||||
use html_escape::decode_html_entities;
|
use html_escape::decode_html_entities;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
pub fn normalize_news_content(content: &str) -> String {
|
lazy_static! {
|
||||||
let re_tags = Regex::new("<[^>]+>").unwrap();
|
static ref RE_TAGS: Regex = Regex::new("<[^>]+>").unwrap();
|
||||||
let re_spaces = Regex::new("[\\u00A0\\s]+").unwrap();
|
static ref RE_SPACES: Regex = Regex::new("[\\u00A0\\s]+").unwrap();
|
||||||
|
static ref RE_SLASH: Regex = Regex::new(r"^(.+)(BTC|USD.?)$").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn normalize_news_content(content: &str) -> String {
|
||||||
let content = content.replace('\n', " ");
|
let content = content.replace('\n', " ");
|
||||||
let content = re_tags.replace_all(&content, "");
|
let content = RE_TAGS.replace_all(&content, "");
|
||||||
let content = re_spaces.replace_all(&content, " ");
|
let content = RE_SPACES.replace_all(&content, " ");
|
||||||
let content = decode_html_entities(&content);
|
let content = decode_html_entities(&content);
|
||||||
let content = content.trim();
|
let content = content.trim();
|
||||||
|
|
||||||
@@ -15,9 +19,7 @@ pub fn normalize_news_content(content: &str) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_slash_to_pair(pair: &str) -> String {
|
pub fn add_slash_to_pair(pair: &str) -> String {
|
||||||
let regex = Regex::new(r"^(.+)(BTC|USD.?)$").unwrap();
|
RE_SLASH.captures(pair).map_or_else(
|
||||||
|
|
||||||
regex.captures(pair).map_or_else(
|
|
||||||
|| pair.to_string(),
|
|| pair.to_string(),
|
||||||
|caps| format!("{}/{}", &caps[1], &caps[2]),
|
|caps| format!("{}/{}", &caps[1], &caps[2]),
|
||||||
)
|
)
|
||||||
|
@@ -10,7 +10,7 @@ use tokio::net::TcpStream;
|
|||||||
use tokio_tungstenite::{tungstenite::Message, MaybeTlsStream, WebSocketStream};
|
use tokio_tungstenite::{tungstenite::Message, MaybeTlsStream, WebSocketStream};
|
||||||
|
|
||||||
pub async fn authenticate(
|
pub async fn authenticate(
|
||||||
app_config: &Arc<Config>,
|
config: &Arc<Config>,
|
||||||
sink: &mut SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>,
|
sink: &mut SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>,
|
||||||
stream: &mut SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>,
|
stream: &mut SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>,
|
||||||
) {
|
) {
|
||||||
@@ -28,8 +28,8 @@ pub async fn authenticate(
|
|||||||
sink.send(Message::Text(
|
sink.send(Message::Text(
|
||||||
to_string(&websocket::outgoing::Message::Auth(
|
to_string(&websocket::outgoing::Message::Auth(
|
||||||
websocket::outgoing::auth::Message {
|
websocket::outgoing::auth::Message {
|
||||||
key: app_config.alpaca_api_key.clone(),
|
key: config.alpaca_api_key.clone(),
|
||||||
secret: app_config.alpaca_api_secret.clone(),
|
secret: config.alpaca_api_secret.clone(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
Reference in New Issue
Block a user