All nessesary orders in backend

master
Griffiths Lott 3 years ago
parent 9469a2a06a
commit e3dbe48ee8
  1. 2
      .gitignore
  2. 1966
      backend/secret_trader/Cargo.lock
  3. 14
      backend/secret_trader/Cargo.toml
  4. 18
      backend/secret_trader/TODO.txt
  5. 4
      backend/secret_trader/src/errors.rs
  6. 27
      backend/secret_trader/src/main.rs
  7. 58
      backend/secret_trader/src/order.rs
  8. 180
      backend/secret_trader/src/trade_mgmt.rs

2
.gitignore vendored

@ -1,7 +1,9 @@
.DS_Store
node_modules
/dist
target
tokens.json
# local env files
.env.local

File diff suppressed because it is too large Load Diff

@ -0,0 +1,14 @@
[package]
name = "secret_trader"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tdapi = { path = "/home/g/Code/Rust/tdapi" }
serde = "1.0.152"
serde_json = "1.0.91"
reqwest = "0.11.13"
futures = "0.3.25"
actix-web = "4"

@ -0,0 +1,18 @@
# Trade Mgmt
[X] Function to get account #s
[X] Function to get current open orders
[X] Function to submit market equity orders
[X] Function to cancel orders
# API
[ ] GET account IDs
[ ] GET open orders
[ ] POST new secret stop loss
[ ] POST cancel order
# DB
[ ] Store new account
- username
- user pw hash
- client id
- refresh token

@ -0,0 +1,4 @@
use std::fmt::{Display};
pub type DynResult<T> = Result<T, Box<dyn std::error::Error>>;

@ -0,0 +1,27 @@
use tdapi::account::tda_account::ApiAccount;
mod trade_mgmt;
mod errors;
mod order;
#[actix_web::main]
async fn main() {
let TEST_ACT = "PNNX2GJXDTQNWY0SRCYGZMUHBFUUQHW7";
let TEST_CALLBACK_URI = "https://localhost";
let mut tda = match ApiAccount::new(TEST_ACT, TEST_CALLBACK_URI) {
Ok(act) => act,
Err(e) => {println!("Failed to create TD act:\n{e:?}"); return;}
};
let act_nums = trade_mgmt::get_account_ids(&mut tda).await;
println!("act_nums = {:?}", act_nums);
let main_act = 686092736;
let buy_order = order::MarketOrder::new("SPY", 300.0, 1.0, order::Direction::BUY);
println!("buy_order = {:?}", buy_order);
let order_send = trade_mgmt::send_order(&mut tda, main_act, &buy_order).await;
println!("order_send = {:?}", order_send);
}

@ -0,0 +1,58 @@
use serde::Deserialize;
use serde::Serialize;
use tdapi::helpers::ToHashMap;
pub enum Direction {
BUY,
SELL
}
impl Direction {
fn to_string(self) -> String {
match self {
BUY => "BUY".to_string(),
SELL => "Sell".to_string()
}
}
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MarketOrder {
pub order_type: String,
pub session: String,
pub duration: String,
pub order_strategy_type: String,
pub order_leg_collection: Vec<OrderLegCollection>,
}
impl MarketOrder {
pub fn new(symbol: &str, price: f64, quantity: f64, direction: Direction) -> Self {
MarketOrder {
order_type: "MARKET".to_string(),
session: "SEAMLESS".to_string(),
duration: "GOOD_TILL_CANCEL".to_string(),
order_strategy_type: "SINGLE".to_string(),
order_leg_collection: vec![OrderLegCollection {
instruction: direction.to_string(),
quantity: quantity,
instrument: Instrument { symbol: symbol.to_string(), asset_type: "EQUITY".to_string() }
}]
}
}
}
impl ToHashMap for MarketOrder {}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OrderLegCollection {
pub instruction: String,
pub quantity: f64,
pub instrument: Instrument,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Instrument {
pub symbol: String,
pub asset_type: String,
}

@ -0,0 +1,180 @@
use std::collections::HashMap;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Value;
use tdapi::account::tda_account::ApiAccount;
use tdapi::helpers::ToHashMap;
use crate::errors::{DynResult};
use crate::order::MarketOrder;
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Position {
pub average_price: f64,
pub current_day_cost: f64,
pub current_day_profit_loss: f64,
pub current_day_profit_loss_percentage: f64,
pub instrument: Instrument,
pub long_quantity: f64,
pub maintenance_requirement: f64,
pub market_value: f64,
pub previous_session_long_quantity: f64,
pub settled_long_quantity: f64,
pub settled_short_quantity: f64,
pub short_quantity: f64,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Instrument {
pub asset_type: String,
pub cusip: String,
pub description: Option<String>,
pub symbol: String,
#[serde(rename = "type")]
pub type_field: Option<String>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OrderLeg {
pub instruction: String,
pub instrument: Instrument,
pub leg_id: i64,
pub order_leg_type: String,
pub position_effect: String,
pub quantity: f64,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OpenOrderDetails {
pub symbol: String,
pub price: f64,
pub quantity: f64,
pub instruction: String,
pub order_type: String,
pub order_id: u64
}
/// Returns current TD account ID's along with their current liquidation values
pub async fn get_account_ids<'a>( td_act: &mut ApiAccount<'a>) -> DynResult<Vec<(u32,f64)>>{
let sec_acts = td_act.get_accounts().await?;
let mut act_nums = vec![];
for act in sec_acts {
act_nums.push( (act.account_id.parse::<u32>()?, act.current_balances.liquidation_value))
}
Ok(act_nums)
}
pub async fn get_open_positions<'a>(td_act: &mut ApiAccount<'a>, act_num: u32) -> DynResult<Vec<Position>> {
let mut query = HashMap::new();
query.insert("fields".to_string(), "positions".to_string());
let td_resp = td_act.perform_get(&format!("https://api.tdameritrade.com/v1/accounts/{act_num}"), Some(query)).await?;
let td_obj = td_resp.as_object()
.ok_or("Failed to create object from td_resp")?;
// Get the securitiesAccount subjection of the return
// This has slightly different components than the regular accounts call so Securities struct is not used (for now)
let sec_act = td_obj.get("securitiesAccount")
.ok_or("securitiesAccount key not found in TD resp")?
.as_object().ok_or("securitiesAccount could not be made into an object")?;
let position_values= sec_act.get("positions").ok_or("No positions key found")?
.as_array().ok_or("Positions is not an array!")?;
let mut positions = vec![];
for p in position_values {
let position: Position = serde_json::from_value(p.clone())?;
if position.instrument.asset_type != "CASH_EQUIVALENT" {
positions.push(position);
}
}
Ok(positions)
}
pub async fn get_open_orders<'a>( td_act: &mut ApiAccount<'a>, act_num: u32) ->DynResult<Vec<OpenOrderDetails>> {
let mut query = HashMap::new();
query.insert("fields".to_string(), "orders".to_string());
let td_resp = td_act.perform_get(&format!("https://api.tdameritrade.com/v1/accounts/{act_num}"), Some(query)).await?;
let td_obj = td_resp.as_object()
.ok_or("Failed to create object from td_resp")?.to_owned();
// Get the securitiesAccount subjection of the return
// This has slightly different components than the regular accounts call so Securities struct is not used (for now)
let sec_act = td_obj.get("securitiesAccount")
.ok_or("securitiesAccount key not found in TD resp")?
.as_object().ok_or("securitiesAccount could not be made into an object")?.to_owned();
let order_strats = sec_act.get("orderStrategies").ok_or("No orderStrategies included in securities account!")?
.as_array().ok_or("Order strategies is not an array")?;
let mut open_orders = vec![];
for order_value in order_strats {
let order = order_value.as_object().ok_or("Order value is not obj")?;
// If an order isn't cancelable, we don't care about it
if order.get("cancelable").ok_or("Order does not have key: cancelable")?.as_bool().unwrap() {
let price = order.get("price").ok_or("price not present")?.as_f64().ok_or("Price is not f64")?;
let quantity = order.get("remainingQuantity").ok_or("quantity not present")?.as_f64().ok_or("Quant is not f64")?;
let order_type = order.get("orderType").ok_or("orderType not present")?.as_str().ok_or("orderType is not str")?;
let order_id = order.get("orderId").ok_or("orderId not present")?.as_u64().ok_or("orderId is not u64")?;
let order_leg: OrderLeg = serde_json::from_value(order.get("orderLegCollection")
.ok_or("orderLegCollection not present")?.as_array()
.ok_or("Not an array")?[0]
.clone())?;
let open_order = OpenOrderDetails {
symbol: order_leg.instrument.symbol,
order_id: order_id,
price: price,
quantity: quantity,
order_type : order_type.to_string(),
instruction : order_leg.order_leg_type
};
open_orders.push(open_order);
}
}
Ok(open_orders)
}
/// This works despite returning an error ¯\_('_')_/¯
pub async fn cancel_order<'a>(td_act: &mut ApiAccount<'a>, account_id: u32, order_id: u64) -> DynResult<serde_json::Value> {
let resp = td_act.perform_delete(&format!("https://api.tdameritrade.com/v1/accounts/{account_id}/orders/{order_id}")).await;
resp
}
pub async fn send_order<'a>(td_act: &mut ApiAccount<'a>, act_id: u32, order: &MarketOrder) -> DynResult<Value> {
let td_resp: Value = match td_act.perform_post(&format!("https://api.tdameritrade.com/v1/accounts/{act_id}/orders"), order).await {
Ok(good_resp) => {
match good_resp.as_object() {
Some(resp_obj) => {
if resp_obj.contains_key("error") {
return Err(format!("TD Resp error: {:?}", resp_obj.get("error").unwrap().as_str().unwrap()).into())
}
good_resp
},
None => return Err("TD resp is not an object!".into())
}
},
Err(e) => return Err(e)
};
Ok(td_resp)
}
Loading…
Cancel
Save