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 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";
|
||||
|
@@ -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);
|
||||
|
@@ -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)
|
||||
|
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 bar;
|
||||
pub mod clock;
|
||||
|
@@ -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,
|
||||
|
Reference in New Issue
Block a user