Add partial account management

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2024-02-14 17:38:56 +00:00
parent 648d413ac7
commit 6adf2b46c8
7 changed files with 126 additions and 5 deletions

View File

@@ -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.

View File

@@ -14,6 +14,7 @@ use rust_bert::{
use std::{env, num::NonZeroU32, path::PathBuf, sync::Arc};
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_ORDER_API_URL: &str = "https://api.alpaca.markets/v2/orders";
pub const ALPACA_POSITION_API_URL: &str = "https://api.alpaca.markets/v2/positions";

View File

@@ -33,6 +33,9 @@ async fn main() {
)
.unwrap();
threads::trading::check_account(&config).await;
threads::trading::check_positions(&config).await;
spawn(threads::trading::run(config.clone()));
let (data_sender, data_receiver) = mpsc::channel::<threads::data::Message>(100);

View File

@@ -22,11 +22,36 @@ pub async fn run(config: Arc<Config>) {
alpaca::websocket::trading::subscribe(&mut websocket_sink, &mut websocket_stream).await;
rehydrate(&config).await;
check_positions(&config).await;
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>) {
let positions_future = async {
alpaca::api::incoming::position::get(config, None)

View 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
}

View File

@@ -1,3 +1,4 @@
pub mod account;
pub mod asset;
pub mod bar;
pub mod clock;

View File

@@ -11,7 +11,7 @@ pub enum Class {
impl_from_enum!(types::Class, Class, UsEquity, Crypto);
#[derive(Deserialize)]
#[serde(rename_all = "UPPERCASE")]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Exchange {
Amex,
Arca,