Add order types
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2573,6 +2573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@@ -39,7 +39,9 @@ clickhouse = { version = "0.11.6", features = [
|
||||
"time",
|
||||
"uuid",
|
||||
] }
|
||||
uuid = "1.6.1"
|
||||
uuid = { version = "1.6.1", features = [
|
||||
"serde",
|
||||
] }
|
||||
time = { version = "0.3.31", features = [
|
||||
"serde",
|
||||
"formatting",
|
||||
|
@@ -1,4 +1,4 @@
|
||||
use crate::types::alpaca::Source;
|
||||
use crate::types::alpaca::shared::Source;
|
||||
use governor::{DefaultDirectRateLimiter, Quota, RateLimiter};
|
||||
use reqwest::{
|
||||
header::{HeaderMap, HeaderName, HeaderValue},
|
||||
@@ -15,7 +15,9 @@ use std::{env, num::NonZeroU32, path::PathBuf, sync::Arc};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
pub const ALPACA_ASSET_API_URL: &str = "https://api.alpaca.markets/v2/assets";
|
||||
pub const ALPACA_ORDER_API_URL: &str = "https://api.alpaca.markets/v2/orders";
|
||||
pub const ALPACA_CLOCK_API_URL: &str = "https://api.alpaca.markets/v2/clock";
|
||||
|
||||
pub const ALPACA_STOCK_DATA_URL: &str = "https://data.alpaca.markets/v2/stocks/bars";
|
||||
pub const ALPACA_CRYPTO_DATA_URL: &str = "https://data.alpaca.markets/v1beta3/crypto/us/bars";
|
||||
pub const ALPACA_NEWS_DATA_URL: &str = "https://data.alpaca.markets/v1beta1/news";
|
||||
|
@@ -2,3 +2,4 @@ pub mod assets;
|
||||
pub mod backfills;
|
||||
pub mod bars;
|
||||
pub mod news;
|
||||
pub mod orders;
|
||||
|
0
src/database/orders.rs
Normal file
0
src/database/orders.rs
Normal file
@@ -6,7 +6,7 @@ use crate::{
|
||||
alpaca::{
|
||||
self,
|
||||
api::{self, outgoing::Sort},
|
||||
Source,
|
||||
shared::Source,
|
||||
},
|
||||
news::Prediction,
|
||||
Backfill, Bar, Class, News,
|
||||
@@ -248,7 +248,7 @@ impl Handler for BarHandler {
|
||||
async fn backfill(&self, symbol: String, fetch_from: OffsetDateTime, fetch_to: OffsetDateTime) {
|
||||
info!("Backfilling bars for {}.", symbol);
|
||||
|
||||
let mut bars = Vec::new();
|
||||
let mut bars = vec![];
|
||||
let mut next_page_token = None;
|
||||
|
||||
loop {
|
||||
@@ -348,7 +348,7 @@ impl Handler for NewsHandler {
|
||||
async fn backfill(&self, symbol: String, fetch_from: OffsetDateTime, fetch_to: OffsetDateTime) {
|
||||
info!("Backfilling news for {}.", symbol);
|
||||
|
||||
let mut news = Vec::new();
|
||||
let mut news = vec![];
|
||||
let mut next_page_token = None;
|
||||
|
||||
loop {
|
||||
|
@@ -8,7 +8,7 @@ use crate::{
|
||||
},
|
||||
create_send_await, database,
|
||||
types::{alpaca, Asset, Class},
|
||||
utils::{authenticate, backoff, cleanup},
|
||||
utils::{backoff, cleanup},
|
||||
};
|
||||
use futures_util::{future::join_all, StreamExt};
|
||||
use itertools::{Either, Itertools};
|
||||
@@ -19,7 +19,7 @@ use tokio::{
|
||||
};
|
||||
use tokio_tungstenite::connect_async;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Action {
|
||||
Add,
|
||||
Remove,
|
||||
@@ -45,7 +45,7 @@ impl Message {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ThreadType {
|
||||
Bars(Class),
|
||||
News,
|
||||
@@ -107,7 +107,8 @@ async fn init_thread(
|
||||
|
||||
let (websocket, _) = connect_async(websocket_url).await.unwrap();
|
||||
let (mut websocket_sink, mut websocket_stream) = websocket.split();
|
||||
authenticate(&config, &mut websocket_sink, &mut websocket_stream).await;
|
||||
alpaca::websocket::data::authenticate(&config, &mut websocket_sink, &mut websocket_stream)
|
||||
.await;
|
||||
|
||||
let (backfill_sender, backfill_receiver) = mpsc::channel(100);
|
||||
spawn(backfill::run(
|
||||
@@ -160,14 +161,14 @@ async fn handle_message(
|
||||
create_send_await!(
|
||||
bars_us_equity_websocket_sender,
|
||||
websocket::Message::new,
|
||||
message.action.clone().into(),
|
||||
message.action.into(),
|
||||
us_equity_symbols.clone()
|
||||
);
|
||||
|
||||
create_send_await!(
|
||||
bars_us_equity_backfill_sender,
|
||||
backfill::Message::new,
|
||||
message.action.clone().into(),
|
||||
message.action.into(),
|
||||
us_equity_symbols
|
||||
);
|
||||
};
|
||||
@@ -180,14 +181,14 @@ async fn handle_message(
|
||||
create_send_await!(
|
||||
bars_crypto_websocket_sender,
|
||||
websocket::Message::new,
|
||||
message.action.clone().into(),
|
||||
message.action.into(),
|
||||
crypto_symbols.clone()
|
||||
);
|
||||
|
||||
create_send_await!(
|
||||
bars_crypto_backfill_sender,
|
||||
backfill::Message::new,
|
||||
message.action.clone().into(),
|
||||
message.action.into(),
|
||||
crypto_symbols
|
||||
);
|
||||
};
|
||||
@@ -196,14 +197,14 @@ async fn handle_message(
|
||||
create_send_await!(
|
||||
news_websocket_sender,
|
||||
websocket::Message::new,
|
||||
message.action.clone().into(),
|
||||
message.action.into(),
|
||||
symbols.clone()
|
||||
);
|
||||
|
||||
create_send_await!(
|
||||
news_backfill_sender,
|
||||
backfill::Message::new,
|
||||
message.action.clone().into(),
|
||||
message.action.into(),
|
||||
symbols.clone()
|
||||
);
|
||||
};
|
||||
|
@@ -66,11 +66,11 @@ pub trait Handler: Send + Sync {
|
||||
fn create_subscription_message(
|
||||
&self,
|
||||
symbols: Vec<String>,
|
||||
) -> websocket::outgoing::subscribe::Message;
|
||||
) -> websocket::data::outgoing::subscribe::Message;
|
||||
async fn handle_parsed_websocket_message(
|
||||
&self,
|
||||
pending: Arc<RwLock<Pending>>,
|
||||
message: websocket::incoming::Message,
|
||||
message: websocket::data::incoming::Message,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ async fn handle_message(
|
||||
.lock()
|
||||
.await
|
||||
.send(tungstenite::Message::Text(
|
||||
to_string(&websocket::outgoing::Message::Subscribe(
|
||||
to_string(&websocket::data::outgoing::Message::Subscribe(
|
||||
handler.create_subscription_message(message.symbols),
|
||||
))
|
||||
.unwrap(),
|
||||
@@ -168,7 +168,7 @@ async fn handle_message(
|
||||
.lock()
|
||||
.await
|
||||
.send(tungstenite::Message::Text(
|
||||
to_string(&websocket::outgoing::Message::Unsubscribe(
|
||||
to_string(&websocket::data::outgoing::Message::Unsubscribe(
|
||||
handler.create_subscription_message(message.symbols.clone()),
|
||||
))
|
||||
.unwrap(),
|
||||
@@ -191,7 +191,7 @@ async fn handle_websocket_message(
|
||||
) {
|
||||
match message {
|
||||
tungstenite::Message::Text(message) => {
|
||||
let message = from_str::<Vec<websocket::incoming::Message>>(&message);
|
||||
let message = from_str::<Vec<websocket::data::incoming::Message>>(&message);
|
||||
|
||||
if let Ok(message) = message {
|
||||
for message in message {
|
||||
@@ -222,7 +222,8 @@ async fn handle_websocket_message(
|
||||
|
||||
struct BarsHandler {
|
||||
config: Arc<Config>,
|
||||
subscription_message_constructor: fn(Vec<String>) -> websocket::outgoing::subscribe::Message,
|
||||
subscription_message_constructor:
|
||||
fn(Vec<String>) -> websocket::data::outgoing::subscribe::Message,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -230,19 +231,20 @@ impl Handler for BarsHandler {
|
||||
fn create_subscription_message(
|
||||
&self,
|
||||
symbols: Vec<String>,
|
||||
) -> websocket::outgoing::subscribe::Message {
|
||||
) -> websocket::data::outgoing::subscribe::Message {
|
||||
(self.subscription_message_constructor)(symbols)
|
||||
}
|
||||
|
||||
async fn handle_parsed_websocket_message(
|
||||
&self,
|
||||
pending: Arc<RwLock<Pending>>,
|
||||
message: websocket::incoming::Message,
|
||||
message: websocket::data::incoming::Message,
|
||||
) {
|
||||
match message {
|
||||
websocket::incoming::Message::Subscription(message) => {
|
||||
let websocket::incoming::subscription::Message::Market { bars: symbols, .. } =
|
||||
message
|
||||
websocket::data::incoming::Message::Subscription(message) => {
|
||||
let websocket::data::incoming::subscription::Message::Market {
|
||||
bars: symbols, ..
|
||||
} = message
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
@@ -283,8 +285,8 @@ impl Handler for BarsHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
websocket::incoming::Message::Bar(message)
|
||||
| websocket::incoming::Message::UpdatedBar(message) => {
|
||||
websocket::data::incoming::Message::Bar(message)
|
||||
| websocket::data::incoming::Message::UpdatedBar(message) => {
|
||||
let bar = Bar::from(message);
|
||||
debug!("Received bar for {}: {}.", bar.symbol, bar.time);
|
||||
|
||||
@@ -292,15 +294,15 @@ impl Handler for BarsHandler {
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
websocket::incoming::Message::Status(message) => {
|
||||
websocket::data::incoming::Message::Status(message) => {
|
||||
debug!(
|
||||
"Received status message for {}: {}.",
|
||||
message.symbol, message.status_message
|
||||
);
|
||||
|
||||
match message.status {
|
||||
websocket::incoming::status::Status::TradingHalt
|
||||
| websocket::incoming::status::Status::VolatilityTradingPause => {
|
||||
websocket::data::incoming::status::Status::TradingHalt
|
||||
| websocket::data::incoming::status::Status::VolatilityTradingPause => {
|
||||
database::assets::update_status_where_symbol(
|
||||
&self.config.clickhouse_client,
|
||||
&message.symbol,
|
||||
@@ -309,8 +311,8 @@ impl Handler for BarsHandler {
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
websocket::incoming::status::Status::Resume
|
||||
| websocket::incoming::status::Status::TradingResumption => {
|
||||
websocket::data::incoming::status::Status::Resume
|
||||
| websocket::data::incoming::status::Status::TradingResumption => {
|
||||
database::assets::update_status_where_symbol(
|
||||
&self.config.clickhouse_client,
|
||||
&message.symbol,
|
||||
@@ -322,7 +324,7 @@ impl Handler for BarsHandler {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
websocket::incoming::Message::Error(message) => {
|
||||
websocket::data::incoming::Message::Error(message) => {
|
||||
error!("Received error message: {}.", message.message);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
@@ -339,18 +341,19 @@ impl Handler for NewsHandler {
|
||||
fn create_subscription_message(
|
||||
&self,
|
||||
symbols: Vec<String>,
|
||||
) -> websocket::outgoing::subscribe::Message {
|
||||
websocket::outgoing::subscribe::Message::new_news(symbols)
|
||||
) -> websocket::data::outgoing::subscribe::Message {
|
||||
websocket::data::outgoing::subscribe::Message::new_news(symbols)
|
||||
}
|
||||
|
||||
async fn handle_parsed_websocket_message(
|
||||
&self,
|
||||
pending: Arc<RwLock<Pending>>,
|
||||
message: websocket::incoming::Message,
|
||||
message: websocket::data::incoming::Message,
|
||||
) {
|
||||
match message {
|
||||
websocket::incoming::Message::Subscription(message) => {
|
||||
let websocket::incoming::subscription::Message::News { news: symbols } = message
|
||||
websocket::data::incoming::Message::Subscription(message) => {
|
||||
let websocket::data::incoming::subscription::Message::News { news: symbols } =
|
||||
message
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
@@ -396,7 +399,7 @@ impl Handler for NewsHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
websocket::incoming::Message::News(message) => {
|
||||
websocket::data::incoming::Message::News(message) => {
|
||||
let news = News::from(message);
|
||||
|
||||
debug!(
|
||||
@@ -426,7 +429,7 @@ impl Handler for NewsHandler {
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
websocket::incoming::Message::Error(message) => {
|
||||
websocket::data::incoming::Message::Error(message) => {
|
||||
error!("Received error message: {}.", message.message);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
@@ -439,12 +442,12 @@ pub fn create_handler(thread_type: ThreadType, config: Arc<Config>) -> Box<dyn H
|
||||
ThreadType::Bars(Class::UsEquity) => Box::new(BarsHandler {
|
||||
config,
|
||||
subscription_message_constructor:
|
||||
websocket::outgoing::subscribe::Message::new_market_us_equity,
|
||||
websocket::data::outgoing::subscribe::Message::new_market_us_equity,
|
||||
}),
|
||||
ThreadType::Bars(Class::Crypto) => Box::new(BarsHandler {
|
||||
config,
|
||||
subscription_message_constructor:
|
||||
websocket::outgoing::subscribe::Message::new_market_crypto,
|
||||
websocket::data::outgoing::subscribe::Message::new_market_crypto,
|
||||
}),
|
||||
ThreadType::News => Box::new(NewsHandler { config }),
|
||||
}
|
||||
|
@@ -1,2 +1,3 @@
|
||||
pub mod clock;
|
||||
pub mod data;
|
||||
pub mod trading;
|
||||
|
0
src/threads/trading/mod.rs
Normal file
0
src/threads/trading/mod.rs
Normal file
@@ -1,68 +1,21 @@
|
||||
use crate::{
|
||||
config::{Config, ALPACA_ASSET_API_URL},
|
||||
impl_from_enum, types,
|
||||
types::{
|
||||
self,
|
||||
alpaca::shared::asset::{Class, Exchange, Status},
|
||||
},
|
||||
};
|
||||
use backoff::{future::retry_notify, ExponentialBackoff};
|
||||
use log::warn;
|
||||
use reqwest::Error;
|
||||
use serde::Deserialize;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Class {
|
||||
UsEquity,
|
||||
Crypto,
|
||||
}
|
||||
|
||||
impl_from_enum!(types::Class, Class, UsEquity, Crypto);
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub enum Exchange {
|
||||
Amex,
|
||||
Arca,
|
||||
Bats,
|
||||
Nyse,
|
||||
Nasdaq,
|
||||
Nysearca,
|
||||
Otc,
|
||||
Crypto,
|
||||
}
|
||||
|
||||
impl_from_enum!(
|
||||
types::Exchange,
|
||||
Exchange,
|
||||
Amex,
|
||||
Arca,
|
||||
Bats,
|
||||
Nyse,
|
||||
Nasdaq,
|
||||
Nysearca,
|
||||
Otc,
|
||||
Crypto
|
||||
);
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Status {
|
||||
Active,
|
||||
Inactive,
|
||||
}
|
||||
|
||||
impl From<Status> for bool {
|
||||
fn from(status: Status) -> Self {
|
||||
match status {
|
||||
Status::Active => true,
|
||||
Status::Inactive => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
use uuid::Uuid;
|
||||
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
||||
#[derive(Deserialize)]
|
||||
pub struct Asset {
|
||||
pub id: String,
|
||||
pub id: Uuid,
|
||||
pub class: Class,
|
||||
pub exchange: Exchange,
|
||||
pub symbol: String,
|
||||
@@ -78,12 +31,12 @@ pub struct Asset {
|
||||
}
|
||||
|
||||
impl From<Asset> for types::Asset {
|
||||
fn from(item: Asset) -> Self {
|
||||
fn from(asset: Asset) -> Self {
|
||||
Self {
|
||||
symbol: item.symbol,
|
||||
class: item.class.into(),
|
||||
exchange: item.exchange.into(),
|
||||
status: item.status.into(),
|
||||
symbol: asset.symbol,
|
||||
class: asset.class.into(),
|
||||
exchange: asset.exchange.into(),
|
||||
status: asset.status.into(),
|
||||
time_added: time::OffsetDateTime::now_utc(),
|
||||
}
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@ use serde::Deserialize;
|
||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
||||
#[derive(Deserialize)]
|
||||
pub struct Bar {
|
||||
#[serde(rename = "t")]
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
@@ -46,7 +46,7 @@ impl From<(Bar, String)> for types::Bar {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
||||
#[derive(Deserialize)]
|
||||
pub struct Message {
|
||||
pub bars: HashMap<String, Vec<Bar>>,
|
||||
pub next_page_token: Option<String>,
|
||||
|
@@ -6,7 +6,7 @@ use serde::Deserialize;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
||||
#[derive(Deserialize)]
|
||||
pub struct Clock {
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
pub timestamp: OffsetDateTime,
|
||||
|
@@ -2,3 +2,4 @@ pub mod asset;
|
||||
pub mod bar;
|
||||
pub mod clock;
|
||||
pub mod news;
|
||||
pub mod order;
|
||||
|
@@ -10,21 +10,21 @@ use serde::Deserialize;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ImageSize {
|
||||
Thumb,
|
||||
Small,
|
||||
Large,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
||||
#[derive(Deserialize)]
|
||||
pub struct Image {
|
||||
pub size: ImageSize,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
||||
#[derive(Deserialize)]
|
||||
pub struct News {
|
||||
pub id: i64,
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
@@ -66,7 +66,7 @@ impl From<News> for types::News {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
||||
#[derive(Deserialize)]
|
||||
pub struct Message {
|
||||
pub news: Vec<News>,
|
||||
pub next_page_token: Option<String>,
|
||||
|
45
src/types/alpaca/api/incoming/order.rs
Normal file
45
src/types/alpaca/api/incoming/order.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use crate::{
|
||||
config::{Config, ALPACA_ORDER_API_URL},
|
||||
types::alpaca::{api::outgoing, shared},
|
||||
};
|
||||
use backoff::{future::retry_notify, ExponentialBackoff};
|
||||
use log::warn;
|
||||
use reqwest::Error;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
pub use shared::order::Order;
|
||||
|
||||
pub async fn get(
|
||||
config: &Arc<Config>,
|
||||
query: &outgoing::order::Order,
|
||||
backoff: Option<ExponentialBackoff>,
|
||||
) -> Result<Vec<Order>, Error> {
|
||||
retry_notify(
|
||||
backoff.unwrap_or_default(),
|
||||
|| async {
|
||||
config.alpaca_rate_limit.until_ready().await;
|
||||
config
|
||||
.alpaca_client
|
||||
.get(ALPACA_ORDER_API_URL)
|
||||
.query(query)
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()
|
||||
.map_err(|e| match e.status() {
|
||||
Some(reqwest::StatusCode::FORBIDDEN) => backoff::Error::Permanent(e),
|
||||
_ => e.into(),
|
||||
})?
|
||||
.json::<Vec<Order>>()
|
||||
.await
|
||||
.map_err(backoff::Error::Permanent)
|
||||
},
|
||||
|e, duration: Duration| {
|
||||
warn!(
|
||||
"Failed to get orders, will retry in {} seconds: {}",
|
||||
duration.as_secs(),
|
||||
e
|
||||
);
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
use super::{serialize_symbols, Sort};
|
||||
use crate::types::alpaca::Source;
|
||||
use crate::types::alpaca::shared::Source;
|
||||
use serde::Serialize;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
|
@@ -1,10 +1,11 @@
|
||||
pub mod bar;
|
||||
pub mod news;
|
||||
pub mod order;
|
||||
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[allow(dead_code)]
|
||||
pub enum Sort {
|
||||
Asc,
|
||||
@@ -18,3 +19,16 @@ where
|
||||
let string = symbols.join(",");
|
||||
serializer.serialize_str(&string)
|
||||
}
|
||||
|
||||
fn serialize_symbols_option<S>(
|
||||
symbols: &Option<Vec<String>>,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match symbols {
|
||||
Some(symbols) => serialize_symbols(symbols, serializer),
|
||||
None => serializer.serialize_none(),
|
||||
}
|
||||
}
|
||||
|
35
src/types/alpaca/api/outgoing/order.rs
Normal file
35
src/types/alpaca/api/outgoing/order.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use super::{serialize_symbols_option, Sort};
|
||||
use crate::types::alpaca::shared::order::Side;
|
||||
use serde::Serialize;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Status {
|
||||
Open,
|
||||
Closed,
|
||||
All,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Order {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub status: Option<Status>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub limit: Option<i64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(with = "time::serde::rfc3339::option")]
|
||||
pub after: Option<OffsetDateTime>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(with = "time::serde::rfc3339::option")]
|
||||
pub until: Option<OffsetDateTime>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub direction: Option<Sort>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub nested: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(serialize_with = "serialize_symbols_option")]
|
||||
pub symbols: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub side: Option<Side>,
|
||||
}
|
@@ -1,5 +1,3 @@
|
||||
pub mod api;
|
||||
pub mod source;
|
||||
pub mod shared;
|
||||
pub mod websocket;
|
||||
|
||||
pub use source::Source;
|
||||
|
53
src/types/alpaca/shared/asset.rs
Normal file
53
src/types/alpaca/shared/asset.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use crate::{impl_from_enum, types};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Class {
|
||||
UsEquity,
|
||||
Crypto,
|
||||
}
|
||||
|
||||
impl_from_enum!(types::Class, Class, UsEquity, Crypto);
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub enum Exchange {
|
||||
Amex,
|
||||
Arca,
|
||||
Bats,
|
||||
Nyse,
|
||||
Nasdaq,
|
||||
Nysearca,
|
||||
Otc,
|
||||
Crypto,
|
||||
}
|
||||
|
||||
impl_from_enum!(
|
||||
types::Exchange,
|
||||
Exchange,
|
||||
Amex,
|
||||
Arca,
|
||||
Bats,
|
||||
Nyse,
|
||||
Nasdaq,
|
||||
Nysearca,
|
||||
Otc,
|
||||
Crypto
|
||||
);
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Status {
|
||||
Active,
|
||||
Inactive,
|
||||
}
|
||||
|
||||
impl From<Status> for bool {
|
||||
fn from(status: Status) -> Self {
|
||||
match status {
|
||||
Status::Active => true,
|
||||
Status::Inactive => false,
|
||||
}
|
||||
}
|
||||
}
|
5
src/types/alpaca/shared/mod.rs
Normal file
5
src/types/alpaca/shared/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod asset;
|
||||
pub mod order;
|
||||
pub mod source;
|
||||
|
||||
pub use source::Source;
|
214
src/types/alpaca/shared/order.rs
Normal file
214
src/types/alpaca/shared/order.rs
Normal file
@@ -0,0 +1,214 @@
|
||||
use crate::{impl_from_enum, types};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::OffsetDateTime;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Class {
|
||||
#[serde(alias = "")]
|
||||
Simple,
|
||||
Bracket,
|
||||
Oco,
|
||||
Oto,
|
||||
}
|
||||
|
||||
impl_from_enum!(types::order::Class, Class, Simple, Bracket, Oco, Oto);
|
||||
|
||||
#[derive(Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Type {
|
||||
Market,
|
||||
Limit,
|
||||
Stop,
|
||||
StopLimit,
|
||||
TrailingStop,
|
||||
}
|
||||
|
||||
impl_from_enum!(
|
||||
types::order::Type,
|
||||
Type,
|
||||
Market,
|
||||
Limit,
|
||||
Stop,
|
||||
StopLimit,
|
||||
TrailingStop
|
||||
);
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Side {
|
||||
Buy,
|
||||
Sell,
|
||||
}
|
||||
|
||||
impl_from_enum!(types::order::Side, Side, Buy, Sell);
|
||||
|
||||
#[derive(Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TimeInForce {
|
||||
Day,
|
||||
Gtc,
|
||||
Opg,
|
||||
Cls,
|
||||
Ioc,
|
||||
Fok,
|
||||
}
|
||||
|
||||
impl_from_enum!(
|
||||
types::order::TimeInForce,
|
||||
TimeInForce,
|
||||
Day,
|
||||
Gtc,
|
||||
Opg,
|
||||
Cls,
|
||||
Ioc,
|
||||
Fok
|
||||
);
|
||||
|
||||
#[derive(Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Status {
|
||||
New,
|
||||
PartiallyFilled,
|
||||
Filled,
|
||||
DoneForDay,
|
||||
Canceled,
|
||||
Expired,
|
||||
Replaced,
|
||||
PendingCancel,
|
||||
PendingReplace,
|
||||
Accepted,
|
||||
PendingNew,
|
||||
AcceptedForBidding,
|
||||
Stopped,
|
||||
Rejected,
|
||||
Suspended,
|
||||
Calculated,
|
||||
}
|
||||
|
||||
impl_from_enum!(
|
||||
types::order::Status,
|
||||
Status,
|
||||
New,
|
||||
PartiallyFilled,
|
||||
Filled,
|
||||
DoneForDay,
|
||||
Canceled,
|
||||
Expired,
|
||||
Replaced,
|
||||
PendingCancel,
|
||||
PendingReplace,
|
||||
Accepted,
|
||||
PendingNew,
|
||||
AcceptedForBidding,
|
||||
Stopped,
|
||||
Rejected,
|
||||
Suspended,
|
||||
Calculated
|
||||
);
|
||||
|
||||
#[derive(Deserialize, Clone, Debug, PartialEq)]
|
||||
#[allow(clippy::struct_field_names)]
|
||||
pub struct Order {
|
||||
pub id: Uuid,
|
||||
pub client_order_id: Uuid,
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
pub created_at: OffsetDateTime,
|
||||
#[serde(with = "time::serde::rfc3339::option")]
|
||||
pub updated_at: Option<OffsetDateTime>,
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
pub submitted_at: OffsetDateTime,
|
||||
#[serde(with = "time::serde::rfc3339::option")]
|
||||
pub filled_at: Option<OffsetDateTime>,
|
||||
#[serde(with = "time::serde::rfc3339::option")]
|
||||
pub expired_at: Option<OffsetDateTime>,
|
||||
#[serde(with = "time::serde::rfc3339::option")]
|
||||
pub cancel_requested_at: Option<OffsetDateTime>,
|
||||
#[serde(with = "time::serde::rfc3339::option")]
|
||||
pub canceled_at: Option<OffsetDateTime>,
|
||||
#[serde(with = "time::serde::rfc3339::option")]
|
||||
pub failed_at: Option<OffsetDateTime>,
|
||||
#[serde(with = "time::serde::rfc3339::option")]
|
||||
pub replaced_at: Option<OffsetDateTime>,
|
||||
pub replaced_by: Option<Uuid>,
|
||||
pub replaces: Option<Uuid>,
|
||||
pub asset_id: Uuid,
|
||||
pub symbol: String,
|
||||
pub asset_class: super::asset::Class,
|
||||
pub notional: Option<f64>,
|
||||
pub qty: Option<f64>,
|
||||
pub filled_qty: f64,
|
||||
pub filled_avg_price: Option<f64>,
|
||||
pub order_class: Class,
|
||||
#[serde(rename = "type")]
|
||||
pub order_type: Type,
|
||||
pub side: Side,
|
||||
pub time_in_force: TimeInForce,
|
||||
pub limit_price: Option<f64>,
|
||||
pub stop_price: Option<f64>,
|
||||
pub status: Status,
|
||||
pub extended_hours: bool,
|
||||
pub legs: Option<Vec<Order>>,
|
||||
pub trail_percent: Option<f64>,
|
||||
pub trail_price: Option<f64>,
|
||||
pub hwm: Option<f64>,
|
||||
}
|
||||
|
||||
impl From<Order> for types::Order {
|
||||
fn from(order: Order) -> Self {
|
||||
Self {
|
||||
id: order.id,
|
||||
client_order_id: order.client_order_id,
|
||||
time_submitted: order.submitted_at,
|
||||
time_created: order.created_at,
|
||||
time_updated: order.updated_at.unwrap_or(OffsetDateTime::UNIX_EPOCH),
|
||||
time_filled: order.filled_at.unwrap_or(OffsetDateTime::UNIX_EPOCH),
|
||||
time_expired: order.expired_at.unwrap_or(OffsetDateTime::UNIX_EPOCH),
|
||||
time_cancel_requested: order
|
||||
.cancel_requested_at
|
||||
.unwrap_or(OffsetDateTime::UNIX_EPOCH),
|
||||
time_canceled: order.canceled_at.unwrap_or(OffsetDateTime::UNIX_EPOCH),
|
||||
time_failed: order.failed_at.unwrap_or(OffsetDateTime::UNIX_EPOCH),
|
||||
time_replaced: order.replaced_at.unwrap_or(OffsetDateTime::UNIX_EPOCH),
|
||||
replaced_by: order.replaced_by.unwrap_or_default(),
|
||||
replaces: order.replaces.unwrap_or_default(),
|
||||
symbol: order.symbol,
|
||||
order_class: order.order_class.into(),
|
||||
order_type: order.order_type.into(),
|
||||
side: order.side.into(),
|
||||
time_in_force: order.time_in_force.into(),
|
||||
notional: order.notional.unwrap_or_default(),
|
||||
qty: order.qty.unwrap_or_default(),
|
||||
filled_qty: order.filled_qty,
|
||||
filled_avg_price: order.filled_avg_price.unwrap_or_default(),
|
||||
status: order.status.into(),
|
||||
extended_hours: order.extended_hours,
|
||||
limit_price: order.limit_price.unwrap_or_default(),
|
||||
stop_price: order.stop_price.unwrap_or_default(),
|
||||
trail_percent: order.trail_percent.unwrap_or_default(),
|
||||
trail_price: order.trail_price.unwrap_or_default(),
|
||||
hwm: order.hwm.unwrap_or_default(),
|
||||
legs: order
|
||||
.legs
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|order| order.id)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Order {
|
||||
pub fn normalize(self) -> Vec<types::Order> {
|
||||
let mut orders = vec![self.clone().into()];
|
||||
|
||||
if let Some(legs) = self.legs {
|
||||
for leg in legs {
|
||||
orders.extend(leg.normalize());
|
||||
}
|
||||
}
|
||||
|
||||
orders
|
||||
}
|
||||
}
|
@@ -5,7 +5,7 @@ use std::{
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Source {
|
||||
Iex,
|
||||
Sip,
|
@@ -2,7 +2,7 @@ use crate::types;
|
||||
use serde::Deserialize;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
||||
#[derive(Deserialize, Debug, PartialEq)]
|
||||
pub struct Message {
|
||||
#[serde(rename = "t")]
|
||||
#[serde(with = "time::serde::rfc3339")]
|
@@ -1,7 +1,6 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq)]
|
||||
pub struct Message {
|
||||
pub code: u16,
|
||||
#[serde(rename = "msg")]
|
@@ -7,7 +7,7 @@ pub mod success;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
||||
#[derive(Deserialize, Debug, PartialEq)]
|
||||
#[serde(tag = "T")]
|
||||
pub enum Message {
|
||||
#[serde(rename = "success")]
|
@@ -5,7 +5,7 @@ use crate::{
|
||||
use serde::Deserialize;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq)]
|
||||
pub struct Message {
|
||||
pub id: i64,
|
||||
#[serde(with = "time::serde::rfc3339")]
|
@@ -1,7 +1,7 @@
|
||||
use serde::Deserialize;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq)]
|
||||
pub enum Status {
|
||||
#[serde(rename = "2")]
|
||||
#[serde(alias = "H")]
|
||||
@@ -36,7 +36,7 @@ pub enum Status {
|
||||
VolatilityTradingPause,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(tag = "rc", content = "rm")]
|
||||
pub enum Reason {
|
||||
#[serde(rename = "D")]
|
||||
@@ -125,7 +125,7 @@ pub enum Reason {
|
||||
MarketWideCircuitBreakerResumption,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq)]
|
||||
pub enum Tape {
|
||||
A,
|
||||
B,
|
||||
@@ -133,7 +133,7 @@ pub enum Tape {
|
||||
O,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq)]
|
||||
#[allow(clippy::struct_field_names)]
|
||||
pub struct Message {
|
||||
#[serde(rename = "t")]
|
@@ -1,6 +1,6 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(untagged)]
|
||||
pub enum Message {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -15,6 +15,7 @@ pub enum Message {
|
||||
lulds: Option<Vec<String>>,
|
||||
cancel_errors: Option<Vec<String>>,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
News { news: Vec<String> },
|
||||
News {
|
||||
news: Vec<String>,
|
||||
},
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)]
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(tag = "msg")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Message {
|
||||
Connected,
|
||||
Authenticated,
|
@@ -1,3 +1,6 @@
|
||||
pub mod incoming;
|
||||
pub mod outgoing;
|
||||
|
||||
use crate::{config::Config, types::alpaca::websocket};
|
||||
use core::panic;
|
||||
use futures_util::{
|
||||
@@ -16,18 +19,18 @@ pub async fn authenticate(
|
||||
) {
|
||||
match stream.next().await.unwrap().unwrap() {
|
||||
Message::Text(data)
|
||||
if from_str::<Vec<websocket::incoming::Message>>(&data)
|
||||
if from_str::<Vec<websocket::data::incoming::Message>>(&data)
|
||||
.unwrap()
|
||||
.first()
|
||||
== Some(&websocket::incoming::Message::Success(
|
||||
websocket::incoming::success::Message::Connected,
|
||||
== Some(&websocket::data::incoming::Message::Success(
|
||||
websocket::data::incoming::success::Message::Connected,
|
||||
)) => {}
|
||||
_ => panic!("Failed to connect to Alpaca websocket."),
|
||||
}
|
||||
|
||||
sink.send(Message::Text(
|
||||
to_string(&websocket::outgoing::Message::Auth(
|
||||
websocket::outgoing::auth::Message {
|
||||
to_string(&websocket::data::outgoing::Message::Auth(
|
||||
websocket::auth::Message {
|
||||
key: config.alpaca_api_key.clone(),
|
||||
secret: config.alpaca_api_secret.clone(),
|
||||
},
|
||||
@@ -39,11 +42,11 @@ pub async fn authenticate(
|
||||
|
||||
match stream.next().await.unwrap().unwrap() {
|
||||
Message::Text(data)
|
||||
if from_str::<Vec<websocket::incoming::Message>>(&data)
|
||||
if from_str::<Vec<websocket::data::incoming::Message>>(&data)
|
||||
.unwrap()
|
||||
.first()
|
||||
== Some(&websocket::incoming::Message::Success(
|
||||
websocket::incoming::success::Message::Authenticated,
|
||||
== Some(&websocket::data::incoming::Message::Success(
|
||||
websocket::data::incoming::success::Message::Authenticated,
|
||||
)) => {}
|
||||
_ => panic!("Failed to authenticate with Alpaca websocket."),
|
||||
};
|
@@ -1,11 +1,11 @@
|
||||
pub mod auth;
|
||||
pub mod subscribe;
|
||||
|
||||
use crate::types::alpaca::websocket::auth;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(tag = "action")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Message {
|
||||
Auth(auth::Message),
|
||||
Subscribe(subscribe::Message),
|
@@ -21,7 +21,6 @@ pub enum Market {
|
||||
#[serde(untagged)]
|
||||
pub enum Message {
|
||||
Market(Market),
|
||||
#[serde(rename_all = "camelCase")]
|
||||
News {
|
||||
news: Vec<String>,
|
||||
},
|
@@ -1,2 +1,3 @@
|
||||
pub mod incoming;
|
||||
pub mod outgoing;
|
||||
pub mod auth;
|
||||
pub mod data;
|
||||
pub mod trading;
|
||||
|
22
src/types/alpaca/websocket/trading/incoming/auth.rs
Normal file
22
src/types/alpaca/websocket/trading/incoming/auth.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Status {
|
||||
Authorized,
|
||||
Unauthorized,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq)]
|
||||
pub enum Action {
|
||||
#[serde(rename = "authenticate")]
|
||||
Auth,
|
||||
#[serde(rename = "listen")]
|
||||
Subscribe,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq)]
|
||||
pub struct Message {
|
||||
pub status: Status,
|
||||
pub action: Action,
|
||||
}
|
16
src/types/alpaca/websocket/trading/incoming/mod.rs
Normal file
16
src/types/alpaca/websocket/trading/incoming/mod.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
pub mod auth;
|
||||
pub mod order;
|
||||
pub mod subscription;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq)]
|
||||
#[serde(tag = "stream", content = "data")]
|
||||
pub enum Message {
|
||||
#[serde(rename = "authorization")]
|
||||
Auth(auth::Message),
|
||||
#[serde(rename = "listening")]
|
||||
Subscription(subscription::Message),
|
||||
#[serde(rename = "trade_updates")]
|
||||
Order(order::Message),
|
||||
}
|
86
src/types/alpaca/websocket/trading/incoming/order.rs
Normal file
86
src/types/alpaca/websocket/trading/incoming/order.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use crate::types::alpaca::shared;
|
||||
use serde::Deserialize;
|
||||
use time::OffsetDateTime;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub use shared::order::Order;
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[serde(tag = "event")]
|
||||
pub enum Message {
|
||||
New {
|
||||
execution_id: Uuid,
|
||||
order: Order,
|
||||
},
|
||||
Fill {
|
||||
execution_id: Uuid,
|
||||
order: Order,
|
||||
timestamp: OffsetDateTime,
|
||||
position_qty: f64,
|
||||
price: f64,
|
||||
},
|
||||
PartialFill {
|
||||
execution_id: Uuid,
|
||||
order: Order,
|
||||
timestamp: OffsetDateTime,
|
||||
position_qty: f64,
|
||||
price: f64,
|
||||
},
|
||||
Canceled {
|
||||
execution_id: Uuid,
|
||||
order: Order,
|
||||
timestamp: OffsetDateTime,
|
||||
},
|
||||
Expired {
|
||||
execution_id: Uuid,
|
||||
order: Order,
|
||||
timestamp: OffsetDateTime,
|
||||
},
|
||||
DoneForDay {
|
||||
execution_id: Uuid,
|
||||
order: Order,
|
||||
},
|
||||
Replaced {
|
||||
execution_id: Uuid,
|
||||
order: Order,
|
||||
timestamp: OffsetDateTime,
|
||||
},
|
||||
Rejected {
|
||||
execution_id: Uuid,
|
||||
order: Order,
|
||||
timestamp: OffsetDateTime,
|
||||
},
|
||||
PendingNew {
|
||||
execution_id: Uuid,
|
||||
order: Order,
|
||||
},
|
||||
Stopped {
|
||||
execution_id: Uuid,
|
||||
order: Order,
|
||||
},
|
||||
PendingCancel {
|
||||
execution_id: Uuid,
|
||||
order: Order,
|
||||
},
|
||||
PendingReplace {
|
||||
execution_id: Uuid,
|
||||
order: Order,
|
||||
},
|
||||
Calculated {
|
||||
execution_id: Uuid,
|
||||
order: Order,
|
||||
},
|
||||
Suspended {
|
||||
execution_id: Uuid,
|
||||
order: Order,
|
||||
},
|
||||
OrderReplaceRejected {
|
||||
execution_id: Uuid,
|
||||
order: Order,
|
||||
},
|
||||
OrderCancelRejected {
|
||||
execution_id: Uuid,
|
||||
order: Order,
|
||||
},
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq)]
|
||||
pub struct Message {
|
||||
pub streams: Vec<String>,
|
||||
}
|
51
src/types/alpaca/websocket/trading/mod.rs
Normal file
51
src/types/alpaca/websocket/trading/mod.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
pub mod incoming;
|
||||
pub mod outgoing;
|
||||
|
||||
use crate::{config::Config, types::alpaca::websocket};
|
||||
use core::panic;
|
||||
use futures_util::{
|
||||
stream::{SplitSink, SplitStream},
|
||||
SinkExt, StreamExt,
|
||||
};
|
||||
use serde_json::{from_str, to_string};
|
||||
use std::sync::Arc;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_tungstenite::{tungstenite::Message, MaybeTlsStream, WebSocketStream};
|
||||
|
||||
pub async fn authenticate(
|
||||
config: &Arc<Config>,
|
||||
sink: &mut SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>,
|
||||
stream: &mut SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>,
|
||||
) {
|
||||
sink.send(Message::Text(
|
||||
to_string(&websocket::trading::outgoing::Message::Auth(
|
||||
websocket::auth::Message {
|
||||
key: config.alpaca_api_key.clone(),
|
||||
secret: config.alpaca_api_secret.clone(),
|
||||
},
|
||||
))
|
||||
.unwrap(),
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
match stream.next().await.unwrap().unwrap() {
|
||||
Message::Binary(data) => {
|
||||
let data = String::from_utf8(data).unwrap();
|
||||
|
||||
if from_str::<Vec<websocket::trading::incoming::Message>>(&data)
|
||||
.unwrap()
|
||||
.first()
|
||||
!= Some(&websocket::trading::incoming::Message::Auth(
|
||||
websocket::trading::incoming::auth::Message {
|
||||
status: websocket::trading::incoming::auth::Status::Authorized,
|
||||
action: websocket::trading::incoming::auth::Action::Auth,
|
||||
},
|
||||
))
|
||||
{
|
||||
panic!("Failed to authenticate with Alpaca websocket.");
|
||||
}
|
||||
}
|
||||
_ => panic!("Failed to authenticate with Alpaca websocket."),
|
||||
};
|
||||
}
|
15
src/types/alpaca/websocket/trading/outgoing/mod.rs
Normal file
15
src/types/alpaca/websocket/trading/outgoing/mod.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
pub mod subscribe;
|
||||
|
||||
use crate::types::alpaca::websocket::auth;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(tag = "action")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Message {
|
||||
Auth(auth::Message),
|
||||
#[serde(rename = "listen")]
|
||||
Subscribe {
|
||||
data: subscribe::Message,
|
||||
},
|
||||
}
|
6
src/types/alpaca/websocket/trading/outgoing/subscribe.rs
Normal file
6
src/types/alpaca/websocket/trading/outgoing/subscribe.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Message {
|
||||
streams: Vec<String>,
|
||||
}
|
@@ -3,8 +3,10 @@ pub mod asset;
|
||||
pub mod backfill;
|
||||
pub mod bar;
|
||||
pub mod news;
|
||||
pub mod order;
|
||||
|
||||
pub use asset::{Asset, Class, Exchange};
|
||||
pub use backfill::Backfill;
|
||||
pub use bar::Bar;
|
||||
pub use news::News;
|
||||
pub use order::Order;
|
||||
|
107
src/types/order.rs
Normal file
107
src/types/order.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
use clickhouse::Row;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use time::OffsetDateTime;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
|
||||
#[repr(i8)]
|
||||
pub enum Class {
|
||||
Simple = 1,
|
||||
Bracket = 2,
|
||||
Oco = 3,
|
||||
Oto = 4,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
|
||||
#[repr(i8)]
|
||||
pub enum Type {
|
||||
Market = 1,
|
||||
Limit = 2,
|
||||
Stop = 3,
|
||||
StopLimit = 4,
|
||||
TrailingStop = 5,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
|
||||
#[repr(i8)]
|
||||
pub enum Side {
|
||||
Buy = 1,
|
||||
Sell = -1,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
|
||||
#[repr(i8)]
|
||||
pub enum TimeInForce {
|
||||
Day = 1,
|
||||
Gtc = 2,
|
||||
Opg = 3,
|
||||
Cls = 4,
|
||||
Ioc = 5,
|
||||
Fok = 6,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
|
||||
#[repr(i8)]
|
||||
pub enum Status {
|
||||
New = 1,
|
||||
PartiallyFilled = 2,
|
||||
Filled = 3,
|
||||
DoneForDay = 4,
|
||||
Canceled = 5,
|
||||
Expired = 6,
|
||||
Replaced = 7,
|
||||
PendingCancel = 8,
|
||||
PendingReplace = 9,
|
||||
Accepted = 10,
|
||||
PendingNew = 11,
|
||||
AcceptedForBidding = 12,
|
||||
Stopped = 13,
|
||||
Rejected = 14,
|
||||
Suspended = 15,
|
||||
Calculated = 16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Row)]
|
||||
#[allow(clippy::struct_field_names)]
|
||||
pub struct Order {
|
||||
pub id: Uuid,
|
||||
pub client_order_id: Uuid,
|
||||
#[serde(with = "clickhouse::serde::time::datetime")]
|
||||
pub time_submitted: OffsetDateTime,
|
||||
#[serde(with = "clickhouse::serde::time::datetime")]
|
||||
pub time_created: OffsetDateTime,
|
||||
#[serde(with = "clickhouse::serde::time::datetime")]
|
||||
pub time_updated: OffsetDateTime,
|
||||
#[serde(with = "clickhouse::serde::time::datetime")]
|
||||
pub time_filled: OffsetDateTime,
|
||||
#[serde(with = "clickhouse::serde::time::datetime")]
|
||||
pub time_expired: OffsetDateTime,
|
||||
#[serde(with = "clickhouse::serde::time::datetime")]
|
||||
pub time_cancel_requested: OffsetDateTime,
|
||||
#[serde(with = "clickhouse::serde::time::datetime")]
|
||||
pub time_canceled: OffsetDateTime,
|
||||
#[serde(with = "clickhouse::serde::time::datetime")]
|
||||
pub time_failed: OffsetDateTime,
|
||||
#[serde(with = "clickhouse::serde::time::datetime")]
|
||||
pub time_replaced: OffsetDateTime,
|
||||
pub replaced_by: Uuid,
|
||||
pub replaces: Uuid,
|
||||
pub symbol: String,
|
||||
pub order_class: Class,
|
||||
pub order_type: Type,
|
||||
pub side: Side,
|
||||
pub time_in_force: TimeInForce,
|
||||
pub extended_hours: bool,
|
||||
pub notional: f64,
|
||||
pub qty: f64,
|
||||
pub filled_qty: f64,
|
||||
pub filled_avg_price: f64,
|
||||
pub status: Status,
|
||||
pub limit_price: f64,
|
||||
pub stop_price: f64,
|
||||
pub trail_percent: f64,
|
||||
pub trail_price: f64,
|
||||
pub hwm: f64,
|
||||
pub legs: Vec<Uuid>,
|
||||
}
|
@@ -3,9 +3,7 @@ pub mod cleanup;
|
||||
pub mod macros;
|
||||
pub mod news;
|
||||
pub mod time;
|
||||
pub mod websocket;
|
||||
|
||||
pub use cleanup::cleanup;
|
||||
pub use news::{add_slash_to_pair, normalize_news_content, remove_slash_from_pair};
|
||||
pub use time::{duration_until, last_minute, FIFTEEN_MINUTES, ONE_MINUTE, ONE_SECOND};
|
||||
pub use websocket::authenticate;
|
||||
|
@@ -64,3 +64,57 @@ CREATE TABLE IF NOT EXISTS qrust.backfills_news (
|
||||
)
|
||||
ENGINE = ReplacingMergeTree()
|
||||
PRIMARY KEY symbol;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qrust.orders (
|
||||
id UUID,
|
||||
client_order_id UUID,
|
||||
time_submitted DateTime,
|
||||
time_created DateTime,
|
||||
time_updated DateTime,
|
||||
time_filled DateTime,
|
||||
time_expired DateTime,
|
||||
time_cancel_requested DateTime,
|
||||
time_canceled DateTime,
|
||||
time_failed DateTime,
|
||||
time_replaced DateTime,
|
||||
replaced_by UUID,
|
||||
replaces UUID,
|
||||
symbol LowCardinality(String),
|
||||
order_class Enum('simple' = 1, 'bracket' = 2, 'oco' = 3, 'oto' = 4),
|
||||
order_type Enum('market' = 1, 'limit' = 2, 'stop' = 3, 'stop_limit' = 4, 'trailing_stop' = 5),
|
||||
side Enum('buy' = 1, 'sell' = -1),
|
||||
time_in_force Enum('day' = 1, 'gtc' = 2, 'opg' = 3, 'cls' = 4, 'ioc' = 5, 'fok' = 6),
|
||||
extended_hours Boolean,
|
||||
notional Float64,
|
||||
qty Float64,
|
||||
filled_qty Float64,
|
||||
filled_avg_price Float64,
|
||||
status Enum(
|
||||
'new' = 1,
|
||||
'partially_filled' = 2,
|
||||
'filled' = 3,
|
||||
'done_for_day' = 4,
|
||||
'canceled' = 5,
|
||||
'expired' = 6,
|
||||
'replaced' = 7,
|
||||
'pending_cancel' = 8,
|
||||
'pending_replace' = 9,
|
||||
'accepted' = 10,
|
||||
'pending_new' = 11,
|
||||
'accepted_for_bidding' = 12,
|
||||
'stopped' = 13,
|
||||
'rejected' = 14,
|
||||
'suspended' = 15,
|
||||
'calculated' = 16
|
||||
),
|
||||
limit_price Float64,
|
||||
stop_price Float64,
|
||||
trail_percent Float64,
|
||||
trail_price Float64,
|
||||
hwm Float64,
|
||||
legs Array(UUID)
|
||||
)
|
||||
ENGINE = ReplacingMergeTree()
|
||||
PARTITION BY toYYYYMM(time_submitted)
|
||||
PRIMARY KEY id;
|
||||
|
||||
|
Reference in New Issue
Block a user