parent
9469a2a06a
commit
e3dbe48ee8
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…
Reference in new issue