diff --git a/Cargo.lock b/Cargo.lock index 2c2af78..520dcec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -442,7 +442,7 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "tdacli" -version = "0.2.1" +version = "0.2.2" dependencies = [ "clap", "tdameritradeclient", @@ -450,8 +450,8 @@ dependencies = [ [[package]] name = "tdameritradeclient" -version = "0.2.1" -source = "git+https://github.com/jbertovic/tdameritradeclient.git?tag=v0.2.1#c86666045979a510b4009d508bfc983f36057a9a" +version = "0.2.2" +source = "git+https://github.com/jbertovic/tdameritradeclient.git?tag=v0.2.2#1190975e3c3dd74b98bb8464bf6620e0bf5bb0af" dependencies = [ "attohttpc", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index b0651b1..4899b67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,9 @@ [package] name = "tdacli" -version = "0.2.1" +version = "0.2.2" authors = ["Jas Bertovic "] edition = "2018" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] -#tdameritradeclient = { path = "../tdameritradeclient" } -tdameritradeclient = { git = "https://github.com/jbertovic/tdameritradeclient.git", tag = "v0.2.1" } +tdameritradeclient = { git = "https://github.com/jbertovic/tdameritradeclient.git", tag = "v0.2.2" } clap = "2.33" \ No newline at end of file diff --git a/Readme.md b/Readme.md index 0705c01..a46cae7 100644 --- a/Readme.md +++ b/Readme.md @@ -1,8 +1,8 @@ ## TDACLI -A simple CLI wrapper around my tdameritradeclient library. For help enter `tdacli --help`. This will give you access to help and further subcommands. +A simple CLI wrapper around my tdameritradeclient library. For help enter `tdacli --help`. This will give you access to help and further subcommand help categories. -Current subcommands are: quote, history, optionchain, userprincipals, account, auth, refresh, weblink. See below for description on output of `tdaci --help` in the CLI Commands section +Current subcommands are: userprincipals, account, quote, history, optionchain, instrument, transaction, auth, refresh, weblink. See below for description on output of `tdaci --help` in the CLI Commands section Environmental Variable Requirements: - `TDAUTHTOKEN` on subcommands: account, history, optionchain, quote, userprincipals @@ -105,9 +105,11 @@ SUBCOMMANDS: auth Retrieves refresh_token using authorization_code grant type help Prints this message or the help of the given subcommand(s) history Retrieve history for one . + instrument Retrieve instrument information or search for instrument optionchain Retrieve option chain for one quote Retrieve quotes for requested symbols refresh Fetch valid token or renew refresh_token using a refresh_token grant type + transaction Retrieve transaction history userprincipals Retrieves User Principals weblink Gives you the url to get authorization code from TDAmeritrade @@ -156,5 +158,4 @@ and the period as the term or total length of the history. - [ ] add option for date calculations on History epoch data stamps - [ ] Consider adding date conversion using chrono package -- [ ] orders - adding, deleting, listing -- [ ] potential to integrate a json query language on output +- [ ] orders - a way to handle orders with library adding and deleting diff --git a/src/account.rs b/src/account.rs index 7488717..0b03125 100644 --- a/src/account.rs +++ b/src/account.rs @@ -1,5 +1,5 @@ use clap::ArgMatches; -use tdameritradeclient::{TDAClient, Account}; +use tdameritradeclient::{Account, TDAClient, Transactions}; /// Grabs the userprincipals data from tdameritrade /// calls `tdameritradeclient::getuserprincipals()` @@ -7,23 +7,19 @@ pub fn userprincipals(c: &TDAClient) { let resp: String = c.getuserprincipals(); println!("{}", &resp); } - /// Grabs the account information with options for Positions, Orders or both /// calls `tdameritradeclient::getaccount(account_id, Account param) pub fn account(c: &TDAClient, args: &ArgMatches) { match args.value_of("account_id") { Some(account) => { let resp: String; - if args.is_present("positions")&&args.is_present("orders") { + if args.is_present("positions") && args.is_present("orders") { resp = c.getaccount(&account, &[Account::PositionsAndOrders]); - } - else if args.is_present("positions") { + } else if args.is_present("positions") { resp = c.getaccount(&account, &[Account::Positions]); - } - else if args.is_present("orders") { + } else if args.is_present("orders") { resp = c.getaccount(&account, &[Account::Orders]); - } - else { + } else { resp = c.getaccount(&account, &[]); } println!("{}", resp) @@ -31,3 +27,40 @@ pub fn account(c: &TDAClient, args: &ArgMatches) { None => println!("{{ \"error\": \"Missing account_id\" }}"), } } +/// Grabs transaction detail with options for date range, or one transaction id +/// or restricted to symbol or type +/// calls `tdameritradeclient::transactions(account_id, transaction param) +/// or calls `tdameritradeclient::transaction(account_id, transaction_id) +pub fn transaction(c: &TDAClient, args: &ArgMatches) { + match args.value_of("account_id") { + Some(account) => { + let resp: String; + // if transaction_id is supplied than just grab that transaction and + // ignore all other options + if args.is_present("trans_id") { + resp = c.gettransaction(&account, args.value_of("trans_id").unwrap()); + } else { + let mut param: Vec = Vec::new(); + if args.is_present("transaction_type") { + param.push(Transactions::TransactionType( + args.value_of("transaction_type").unwrap(), + )); + } + if args.is_present("symbol") { + param.push(Transactions::Symbol(args.value_of("symbol").unwrap())); + } + if args.is_present("start_date") { + param.push(Transactions::StartDate( + args.value_of("start_date").unwrap(), + )); + } + if args.is_present("end_date") { + param.push(Transactions::EndDate(args.value_of("end_date").unwrap())); + } + resp = c.gettransactions(&account, ¶m); + } + println!("{}", resp) + } + None => println!("{{ \"error\": \"Missing account_id\" }}"), + } +} diff --git a/src/auth.rs b/src/auth.rs index 078c20d..23106b1 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,25 +1,30 @@ use clap::ArgMatches; -use tdameritradeclient::auth::{getcodeweblink, gettoken_fromrefresh, getrefresh_fromrefresh, TDauth}; +use tdameritradeclient::auth::{ + getcodeweblink, getrefresh_fromrefresh, gettoken_fromrefresh, TDauth, +}; /// Create a link to use to get an authorization_code from tdameritrade /// Code can be used to fetch a token and refresh token in the `auth` subcommand pub fn weblink(args: &ArgMatches) { - println!("{}", - getcodeweblink(args.value_of("clientid").unwrap(), args.value_of("redirect").unwrap())); + println!( + "{}", + getcodeweblink( + args.value_of("clientid").unwrap(), + args.value_of("redirect").unwrap() + ) + ); } -/// Fetch updated `token` or `refresh_token` from a current `refresh_token` +/// Fetch updated `refresh_token` from a current `authorization_code` as retrieved from `weblink` pub fn auth(args: &ArgMatches, code: String) { match args.value_of("clientid") { - Some(clientid) => { - match args.value_of("redirect") { - Some(redirect) => { - let decoded = !args.is_present("decoded"); - let tdauth = TDauth::new_fromcode(&code, clientid, redirect, decoded); - let (t, r) = tdauth.gettokens(); - println!("{{\"Token\": \"{}\", \"Refresh\": \"{}\"}}", t, r) - }, - None => println!("{{ \"error\": \"Missing redirect\"}}"), + Some(clientid) => match args.value_of("redirect") { + Some(redirect) => { + let decoded = !args.is_present("decoded"); + let tdauth = TDauth::new_fromcode(&code, clientid, redirect, decoded); + let (_t, refresh) = tdauth.gettokens(); + println!("{}", refresh); } + None => println!("{{ \"error\": \"Missing redirect\"}}"), }, None => println!("{{ \"error\": \"Missing clientid\"}}"), } @@ -39,4 +44,4 @@ pub fn refresh(args: &ArgMatches, rtoken: String) { } None => println!("{{ \"error\": \"Missing clientid\"}}"), } -} \ No newline at end of file +} diff --git a/src/cli.rs b/src/cli.rs index d8b7caa..9995fe3 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -82,7 +82,7 @@ pub fn cli_matches<'a>() -> ArgMatches<'a> { .help("includes account orders") ) .after_help("NOTE: Token must be set in env variable: TDAUTHTOKEN.") - ) + ) .subcommand( SubCommand::with_name("quote") .about("Retrieve quotes for requested symbols") @@ -91,7 +91,77 @@ pub fn cli_matches<'a>() -> ArgMatches<'a> { .help("Retrieves quotes of supplied in format \"sym1,sym2,sym3\"" )) .after_help("NOTE: Token must be set in env variable: TDAUTHTOKEN.") - ) + ) + .subcommand( + SubCommand::with_name("transaction") + .about("Retrieve transaction history") + .arg( + Arg::with_name("account_id") + .takes_value(true) + .required(true) + .help("Retrieves transactions for linked account_id") + ) + .arg( // symbol shouldn't be an argument with value but ONLY the value + Arg::with_name("transaction_type") + .long("type") + .takes_value(true) + .help("Specify Transaction Type otherwise ALL (See below)") + ) + .arg( // symbol shouldn't be an argument with value but ONLY the value + Arg::with_name("trans_id") + .help("(OPTIONAL) Grab one transaction by ID. Ignores all other options.") + ) + .arg( // symbol shouldn't be an argument with value but ONLY the value + Arg::with_name("symbol") + .long("symbol") + .takes_value(true) + .help("Specify symbol, otherwise all symbols") + ) + .arg( // symbol shouldn't be an argument with value but ONLY the value + Arg::with_name("start_date") + .long("sdate") + .takes_value(true) + .help("Start date in yyyy-mm-dd. Max range is 1 year") + ) + .arg( // symbol shouldn't be an argument with value but ONLY the value + Arg::with_name("end_date") + .long("edate") + .takes_value(true) + .help("End date in yyyy-mm-dd. Max range is 1 year") + ) + .after_help("NOTE: Token must be set in env variable: TDAUTHTOKEN. \n\n\r\ + Transaction Types: ALL, TRADE, BUY_ONLY, SELL_ONLY, CASH_IN_OR_CASH_OUT, CHECKING, \n\ + DIVIDEND, INTEREST, OTHER, ADVISOR_FEES") + ) + .subcommand( + SubCommand::with_name("instrument") + .about("Retrieve instrument information or search for instrument") + .arg( // symbol shouldn't be an argument with value but ONLY the value + Arg::with_name("search") + .required(true) + .takes_value(true) + .help("Symbol of instrument or search parameter") + ) + .arg( // symbol shouldn't be an argument with value but ONLY the value + Arg::with_name("search_type") + .required(true) + .takes_value(true) + .help("Specifies type of request") + ) + .after_help("NOTE: Token must be set in env variable: TDAUTHTOKEN. \n\r\n\r\ + Type of Request \n\r\ + - symbol-search: Retrieve instrument data of a specific symbol or cusip \n\r\ + - symbol-regex: Retrieve instrument data for all symbols matching regex. \n\r\ + Example: search = XYZ.* will return all symbols beginning with XYZ \n\r\ + - desc-search: Retrieve instrument data for instruments whose description \n\r\ + contains the word supplied. Example: search = FakeCompany will return \n\r\ + all instruments with FakeCompany in the description. \n\r\ + - desc-regex: Search description with full regex support. \n\r\ + Example: search = XYZ.[A-C] returns all instruments whose descriptions \n\r\ + contain a word beginning with XYZ followed by a character A through C. \n\r\ + - fundamental: Returns fundamental data for a single instrument specified by exact symbol.\n\r\ + - cusip: use the supplied cusip to look up details of instrument.") + ) .subcommand( SubCommand::with_name("history") .about("Retrieve history for one .") diff --git a/src/main.rs b/src/main.rs index 68cbef7..f4402d7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,10 +7,11 @@ //! //! module -> subcommands that match tdameritrade::TDAClient //! -//! `account -> userprinicipals(), account(account_id)` +//! `account -> userprinicipals(), account(account_id), transactions(account_id), transaction(account_id, transaction_id)` //! Defines items that deal with account and user information //! //! `quote -> quote(symbols), history(symbol, History param), optionchain(symbol, OptionChain param)` +//! -> instruments(search), instrument(cusip) //! Defines items that deal with quotes either live or historical and expanded option quotes //! //! `orders` -> getsavedorders(accoundid), getorders(accountid)` @@ -19,26 +20,20 @@ #[macro_use(crate_version)] extern crate clap; -pub mod cli; pub mod account; -pub mod quote; pub mod auth; +pub mod cli; +pub mod quote; use std::env; use tdameritradeclient::TDAClient; fn main() { let matches = cli::cli_matches(); - - //TODO: add orders subcommand - //TODO: orders: output filled, working, all - //TODO: orders: add (need to determine how to specify order desc json) - //TODO: orders: delete match matches.subcommand() { (cmd, Some(sub_m)) => { match cmd { - // relies on NO Env Variables "weblink" => auth::weblink(&sub_m), @@ -47,31 +42,34 @@ fn main() { let refresh = env::var("TDREFRESHTOKEN") .expect("Token is missing inside env variable TDREFRESHTOKEN"); auth::refresh(sub_m, refresh); - } + } // relies on env variable TDCODE "auth" => { - let code = env::var("TDCODE") - .expect("Code is missing inside env variable TDCODE"); + let code = + env::var("TDCODE").expect("Code is missing inside env variable TDCODE"); auth::auth(sub_m, code); - } + } // relies on env variable TDAUTHTOKEN and tdameritradeclient::TDClient _ => { - let c = TDAClient::new(env::var("TDAUTHTOKEN") - .expect("Token is missing inside env variable TDAUTHTOKEN")); + let c = TDAClient::new( + env::var("TDAUTHTOKEN") + .expect("Token is missing inside env variable TDAUTHTOKEN"), + ); match cmd { "userprincipals" => account::userprincipals(&c), "account" => account::account(&c, &sub_m), "quote" => quote::quote(&c, sub_m), + "transaction" => account::transaction(&c, sub_m), + "instrument" => quote::instrument(&c, sub_m), "history" => quote::history(&c, sub_m), "optionchain" => quote::optionchain(&c, sub_m), - _ => {}, + _ => {} } } } } - _ => {println!("Subcommand must be specified. For more information try --help")} + _ => println!("Subcommand must be specified. For more information try --help"), } } - diff --git a/src/quote.rs b/src/quote.rs index 80d2a5a..0c6b7f4 100644 --- a/src/quote.rs +++ b/src/quote.rs @@ -1,5 +1,5 @@ use clap::ArgMatches; -use tdameritradeclient::{TDAClient, History, OptionChain}; +use tdameritradeclient::{History, Instruments, OptionChain, TDAClient}; /// Grabs the quote for symbols supplied xxx,yyy,zzz /// calls `tdameritradeclient::getquote(symbols) @@ -12,6 +12,31 @@ pub fn quote(c: &TDAClient, args: &ArgMatches) { None => missing_symbol(), } } +/// Grabs the instrument information or search +/// calls `tdameritradeclient::getinstruments(Instrument param) +/// OR calls `tdameritradeclient::getinstrument(cusip) +pub fn instrument(c: &TDAClient, args: &ArgMatches) { + match args.value_of("search") { + Some(search) => { + let mut param: Vec = Vec::new(); + param.push(Instruments::Symbol(search)); + // if cusip is supplied than use invoke tdameritradeclient::getinstrument + // otherwise use tdameritradeclient::getinstruments <- ending in 's' + let stype = args.value_of("search_type").unwrap(); + let resp: String; + if stype.contains("cusip") { + resp = c.getinstrument(search); + } else { + param.push(Instruments::SearchType( + args.value_of("search_type").unwrap(), + )); + resp = c.getinstruments(¶m); + } + println!("{}", resp); + } + None => missing_symbol(), + } +} /// Grabs the quote history for a symbol using param defining the query /// calls `tdameritradeclient::gethistory(symbol, History param) @@ -21,26 +46,42 @@ pub fn history(c: &TDAClient, args: &ArgMatches) { let mut param: Vec = Vec::new(); // determine query parameters if args.is_present("period") { - param.push(History::Period(args.value_of("period").unwrap() - .parse().expect("period should be a number. Check --help"))); + param.push(History::Period( + args.value_of("period") + .unwrap() + .parse() + .expect("period should be a number. Check --help"), + )); } if args.is_present("period_type") { param.push(History::PeriodType(args.value_of("period_type").unwrap())); } if args.is_present("freq") { - param.push(History::Frequency(args.value_of("freq").unwrap() - .parse().expect("freq should be a number. Check --help"))); + param.push(History::Frequency( + args.value_of("freq") + .unwrap() + .parse() + .expect("freq should be a number. Check --help"), + )); } if args.is_present("freq_type") { param.push(History::FrequencyType(args.value_of("freq_type").unwrap())); } if args.is_present("startdate") { - param.push(History::StartDate(args.value_of("startdate").unwrap() - .parse().expect("start date should be a number specifying epoch type"))); + param.push(History::StartDate( + args.value_of("startdate") + .unwrap() + .parse() + .expect("start date should be a number specifying epoch type"), + )); } if args.is_present("enddate") { - param.push(History::EndDate(args.value_of("enddate").unwrap() - .parse().expect("end date should be a number specifying epoch type"))); + param.push(History::EndDate( + args.value_of("enddate") + .unwrap() + .parse() + .expect("end date should be a number specifying epoch type"), + )); } let response: String = c.gethistory(&symbol, ¶m); println!("{}", response) @@ -56,6 +97,7 @@ pub fn optionchain(c: &TDAClient, args: &ArgMatches) { Some(symbol) => { let mut param: Vec = Vec::new(); // determine query parameters + param.push(OptionChain::Symbol(symbol)); if args.is_present("call") { param.push(OptionChain::ContractType("CALL")); } @@ -63,8 +105,12 @@ pub fn optionchain(c: &TDAClient, args: &ArgMatches) { param.push(OptionChain::ContractType("PUT")); } if args.is_present("strike_count") { - param.push(OptionChain::StrikeCount(args.value_of("srike_count").unwrap() - .parse().expect("strike count should be a positive integer"))); + param.push(OptionChain::StrikeCount( + args.value_of("srike_count") + .unwrap() + .parse() + .expect("strike count should be a positive integer"), + )); } if args.is_present("quotes") { param.push(OptionChain::IncludeQuotes(true)); @@ -73,12 +119,20 @@ pub fn optionchain(c: &TDAClient, args: &ArgMatches) { param.push(OptionChain::Strategy(args.value_of("strategy").unwrap())); } if args.is_present("interval") { - param.push(OptionChain::Interval(args.value_of("interval").unwrap() - .parse().expect("strike interval should be a number"))); + param.push(OptionChain::Interval( + args.value_of("interval") + .unwrap() + .parse() + .expect("strike interval should be a number"), + )); } if args.is_present("strike") { - param.push(OptionChain::Strike(args.value_of("strike").unwrap() - .parse().expect("specified strike price should be a number"))); + param.push(OptionChain::Strike( + args.value_of("strike") + .unwrap() + .parse() + .expect("specified strike price should be a number"), + )); } if args.is_present("range") { param.push(OptionChain::Range(args.value_of("range").unwrap())); @@ -90,7 +144,9 @@ pub fn optionchain(c: &TDAClient, args: &ArgMatches) { param.push(OptionChain::ToDate(args.value_of("to").unwrap())); } if args.is_present("exp_month") { - param.push(OptionChain::ExpireMonth(args.value_of("exp_month").unwrap())); + param.push(OptionChain::ExpireMonth( + args.value_of("exp_month").unwrap(), + )); } if args.is_present("typeS") { param.push(OptionChain::OptionType("S")); @@ -98,15 +154,13 @@ pub fn optionchain(c: &TDAClient, args: &ArgMatches) { if args.is_present("typeNS") { param.push(OptionChain::OptionType("NS")); } - let response: String = c.getoptionchain(&symbol, ¶m); + let response: String = c.getoptionchain(¶m); println!("{}", response) } None => missing_symbol(), } } - - fn missing_symbol() { - println!("{{ \"error\": \"Missing symbols\"}}"); -} \ No newline at end of file + println!("{{ \"error\": \"Missing symbol\"}}"); +}