Add partial account management
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
@@ -1,3 +1,3 @@
|
|||||||
# QRust
|
# qrust
|
||||||
|
|
||||||
QRust (/kɹʌst/, QuantitativeRust) is an algorithmic trading library written in Rust.
|
`qrust` (/kɹʌst/, QuantitativeRust) is an algorithmic trading library written in Rust.
|
||||||
|
@@ -14,6 +14,7 @@ use rust_bert::{
|
|||||||
use std::{env, num::NonZeroU32, path::PathBuf, sync::Arc};
|
use std::{env, num::NonZeroU32, path::PathBuf, sync::Arc};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
pub const ALPACA_ACCOUNT_API_URL: &str = "https://api.alpaca.markets/v2/account";
|
||||||
pub const ALPACA_ASSET_API_URL: &str = "https://api.alpaca.markets/v2/assets";
|
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_ORDER_API_URL: &str = "https://api.alpaca.markets/v2/orders";
|
||||||
pub const ALPACA_POSITION_API_URL: &str = "https://api.alpaca.markets/v2/positions";
|
pub const ALPACA_POSITION_API_URL: &str = "https://api.alpaca.markets/v2/positions";
|
||||||
|
@@ -33,6 +33,9 @@ async fn main() {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
threads::trading::check_account(&config).await;
|
||||||
|
threads::trading::check_positions(&config).await;
|
||||||
|
|
||||||
spawn(threads::trading::run(config.clone()));
|
spawn(threads::trading::run(config.clone()));
|
||||||
|
|
||||||
let (data_sender, data_receiver) = mpsc::channel::<threads::data::Message>(100);
|
let (data_sender, data_receiver) = mpsc::channel::<threads::data::Message>(100);
|
||||||
|
@@ -22,11 +22,36 @@ pub async fn run(config: Arc<Config>) {
|
|||||||
alpaca::websocket::trading::subscribe(&mut websocket_sink, &mut websocket_stream).await;
|
alpaca::websocket::trading::subscribe(&mut websocket_sink, &mut websocket_stream).await;
|
||||||
|
|
||||||
rehydrate(&config).await;
|
rehydrate(&config).await;
|
||||||
check_positions(&config).await;
|
|
||||||
|
|
||||||
spawn(websocket::run(config, websocket_stream, websocket_sink));
|
spawn(websocket::run(config, websocket_stream, websocket_sink));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn check_account(config: &Arc<Config>) {
|
||||||
|
let account = alpaca::api::incoming::account::get(config, None)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!(account.status != alpaca::api::incoming::account::Status::Active),
|
||||||
|
"Account status is not active: {:?}.",
|
||||||
|
account.status
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!account.trade_suspend_by_user,
|
||||||
|
"Account trading is suspended by user."
|
||||||
|
);
|
||||||
|
assert!(!account.trading_blocked, "Account trading is blocked.");
|
||||||
|
assert!(!account.blocked, "Account is blocked.");
|
||||||
|
|
||||||
|
if account.cash == 0.0 {
|
||||||
|
warn!("Account cash is zero, qrust will not be able to trade.");
|
||||||
|
}
|
||||||
|
|
||||||
|
warn!(
|
||||||
|
"qrust active with {}{}, avoid transferring funds without shutting down.",
|
||||||
|
account.currency, account.cash
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn check_positions(config: &Arc<Config>) {
|
pub async fn check_positions(config: &Arc<Config>) {
|
||||||
let positions_future = async {
|
let positions_future = async {
|
||||||
alpaca::api::incoming::position::get(config, None)
|
alpaca::api::incoming::position::get(config, None)
|
||||||
|
91
src/types/alpaca/api/incoming/account.rs
Normal file
91
src/types/alpaca/api/incoming/account.rs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
use backoff::{future::retry_notify, ExponentialBackoff};
|
||||||
|
use log::warn;
|
||||||
|
use reqwest::Error;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::{sync::Arc, time::Duration};
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::config::{Config, ALPACA_ACCOUNT_API_URL};
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum Status {
|
||||||
|
Onboarding,
|
||||||
|
SubmissionFailed,
|
||||||
|
Submitted,
|
||||||
|
AccountUpdated,
|
||||||
|
ApprovalPending,
|
||||||
|
Active,
|
||||||
|
Rejected,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
|
pub struct Account {
|
||||||
|
pub id: Uuid,
|
||||||
|
#[serde(rename = "account_number")]
|
||||||
|
pub number: i64,
|
||||||
|
pub status: Status,
|
||||||
|
pub currency: String,
|
||||||
|
pub cash: f64,
|
||||||
|
pub non_marginable_buying_power: f64,
|
||||||
|
pub accrued_fees: f64,
|
||||||
|
pub pending_transfer_in: f64,
|
||||||
|
pub pending_transfer_out: f64,
|
||||||
|
pub pattern_day_trader: bool,
|
||||||
|
pub trade_suspend_by_user: bool,
|
||||||
|
pub trading_blocked: bool,
|
||||||
|
pub transfers_blocked: bool,
|
||||||
|
#[serde(rename = "account_blocked")]
|
||||||
|
pub blocked: bool,
|
||||||
|
#[serde(with = "time::serde::rfc3339")]
|
||||||
|
pub created_at: OffsetDateTime,
|
||||||
|
pub shorting_enabled: bool,
|
||||||
|
pub long_market_value: f64,
|
||||||
|
pub short_market_value: f64,
|
||||||
|
pub equity: f64,
|
||||||
|
pub last_equity: f64,
|
||||||
|
pub multiplier: i8,
|
||||||
|
pub buying_power: f64,
|
||||||
|
pub initial_margin: f64,
|
||||||
|
pub maintenance_margin: f64,
|
||||||
|
pub sma: f64,
|
||||||
|
pub daytrade_count: i64,
|
||||||
|
pub last_maintenance_margin: f64,
|
||||||
|
pub daytrading_buying_power: f64,
|
||||||
|
pub regt_buying_power: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get(
|
||||||
|
config: &Arc<Config>,
|
||||||
|
backoff: Option<ExponentialBackoff>,
|
||||||
|
) -> Result<Account, Error> {
|
||||||
|
retry_notify(
|
||||||
|
backoff.unwrap_or_default(),
|
||||||
|
|| async {
|
||||||
|
config.alpaca_rate_limit.until_ready().await;
|
||||||
|
config
|
||||||
|
.alpaca_client
|
||||||
|
.get(ALPACA_ACCOUNT_API_URL)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.error_for_status()
|
||||||
|
.map_err(|e| match e.status() {
|
||||||
|
Some(reqwest::StatusCode::FORBIDDEN) => backoff::Error::Permanent(e),
|
||||||
|
_ => e.into(),
|
||||||
|
})?
|
||||||
|
.json::<Account>()
|
||||||
|
.await
|
||||||
|
.map_err(backoff::Error::Permanent)
|
||||||
|
},
|
||||||
|
|e, duration: Duration| {
|
||||||
|
warn!(
|
||||||
|
"Failed to get account, will retry in {} seconds: {}",
|
||||||
|
duration.as_secs(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
@@ -1,3 +1,4 @@
|
|||||||
|
pub mod account;
|
||||||
pub mod asset;
|
pub mod asset;
|
||||||
pub mod bar;
|
pub mod bar;
|
||||||
pub mod clock;
|
pub mod clock;
|
||||||
|
@@ -11,7 +11,7 @@ pub enum Class {
|
|||||||
impl_from_enum!(types::Class, Class, UsEquity, Crypto);
|
impl_from_enum!(types::Class, Class, UsEquity, Crypto);
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "UPPERCASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum Exchange {
|
pub enum Exchange {
|
||||||
Amex,
|
Amex,
|
||||||
Arca,
|
Arca,
|
||||||
|
Reference in New Issue
Block a user