From 18bde7391efb9c6bd6ea7846891ab5d16276a809 Mon Sep 17 00:00:00 2001 From: David Vazgenovich Shakaryan Date: Mon, 31 May 2021 06:11:22 -0700 Subject: initial import A bit messy and missing error handling in some places. --- tda.c | 253 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 tda.c (limited to 'tda.c') diff --git a/tda.c b/tda.c new file mode 100644 index 0000000..affe1d3 --- /dev/null +++ b/tda.c @@ -0,0 +1,253 @@ +#include "assets.h" +#include "json_curl.h" +#include "quotes.h" +#include "tda.h" + +#include +#include +#include + +#include + +static json_object *tda_init_req(struct tda_session *sess, + const char *refresh_token) +{ + json_object *json = NULL; + CURL *curl = curl_easy_init(); + if (!curl) return NULL; + char *esc = curl_easy_escape(curl, refresh_token, 0); + if (!esc) goto err_free_curl; + + char buf[4096] = { 0 }; + char fmt[] = "client_id=%s&grant_type=refresh_token&access_type=offline&" + "refresh_token=%s"; + int c = snprintf(buf, sizeof(buf), fmt, sess->client_id, esc); + if (c < 0 || (unsigned)c >= sizeof(buf) || + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, buf) != CURLE_OK) + goto err_free_esc; + + json = json_curl_perform(curl, + "https://api.tdameritrade.com/v1/oauth2/token"); + +err_free_esc: + curl_free(esc); +err_free_curl: + curl_easy_cleanup(curl); + return json; +} + +struct tda_session *tda_init(const char *client_id, const char *account_id, + const char *refresh_token) +{ + struct tda_session *sess = calloc(1, sizeof(*sess)); + if (!sess) return NULL; + + json_object *json; + if (!((sess->client_id = strdup(client_id)) && + (sess->account_id = strdup(account_id)) && + (json = tda_init_req(sess, refresh_token)))) + goto err_free_sess; + + json_object *tmp = json; + int err = !json_object_object_get_ex(json, "refresh_token", &tmp) || + !(sess->refresh_token = strdup(json_object_get_string(tmp))) || + !json_object_object_get_ex(json, "access_token", &tmp) || + !(sess->access_token = strdup(json_object_get_string(tmp))); + json_object_put(json); + if (err) goto err_free_sess; + + return sess; + +err_free_sess: + tda_cleanup(sess); + return NULL; +} + +void tda_cleanup(struct tda_session *sess) +{ + free(sess->client_id); + free(sess->account_id); + free(sess->access_token); + free(sess->refresh_token); + free(sess); +} + +static json_object *oauth_json_curl(struct tda_session *sess, const char *uri) +{ + CURL *curl = curl_easy_init(); + if (!curl) return NULL; + + json_object *json = NULL; + if (curl_easy_setopt(curl, CURLOPT_HTTPAUTH, + CURLAUTH_BEARER) == CURLE_OK && + curl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, + sess->access_token) == CURLE_OK) + json = json_curl_perform(curl, uri); + + curl_easy_cleanup(curl); + return json; +} + +static json_object *fetch_positions(struct tda_session *sess) +{ + char uri[2048]; + int c = snprintf(uri, sizeof(uri), + "https://api.tdameritrade.com/v1/accounts/%s?fields=positions", + sess->account_id); + if (c < 0 || (unsigned)c >= sizeof(uri)) + return NULL; + + return oauth_json_curl(sess, uri); +} + +static json_object *fetch_quotes(struct tda_session *sess, const char *symbols) +{ + char uri[2048]; + int c = snprintf(uri, sizeof(uri), + "https://api.tdameritrade.com/v1/marketdata/quotes?symbol=%s", + symbols); + if (c < 0 || (unsigned)c >= sizeof(uri)) + return NULL; + + return oauth_json_curl(sess, uri); +} + +static struct asset *gen_asset(json_object *json) +{ + json_object *tmp; + + if (!json_object_object_get_ex(json, "instrument", &tmp) || + !json_object_object_get_ex(tmp, "assetType", &tmp)) + return NULL; + + const char *type = json_object_get_string(tmp); + enum asset_type at; + if (strcmp(type, "EQUITY") == 0) + at = STOCK; + else if (strcmp(type, "OPTION") == 0) + at = OPTION; + else + return NULL; + + struct asset *a = calloc(1, sizeof(*a)); + if (!a) return NULL; + a->type = at; + + if (!json_object_object_get_ex(json, "longQuantity", &tmp)) + goto err; + a->amt = json_object_get_double(tmp); + + if (!json_object_object_get_ex(json, "instrument", &tmp) || + !json_object_object_get_ex(tmp, "symbol", &tmp) || + !(a->symbol = strdup(json_object_get_string(tmp)))) + goto err; + + return a; + +err: + free(a); + return NULL; +} + +int tda_load_assets(struct tda_session *sess, struct asset **assets) +{ + json_object *tmp, *json = fetch_positions(sess); + if (!json || + !json_object_object_get_ex(json, "securitiesAccount", &tmp) || + !json_object_object_get_ex(tmp, "positions", &tmp)) + return -1; + + int n = 0; + + for (size_t i = 0; i < json_object_array_length(tmp); ++i) { + json_object *p_json = json_object_array_get_idx(tmp, i); + struct asset *a = gen_asset(p_json); + if (a) assets[n++] = a; + } + + json_object_put(json); + return n; +} + +int tda_get_quote_symbols(struct asset **assets, int n, char **bufptr) +{ + size_t bufsize = 1024; + char *buf = malloc(bufsize); + if (!buf) return -1; + int len = 0; + + for (int i = 0; i < n; ++i) { + if (assets[i]->type != CRYPTO) { +retry:; + char *tmp = memccpy(buf + len, assets[i]->symbol, '\0', + bufsize - len - 1); // -1 for ',' + if (tmp) { + len = tmp - buf; + buf[len - 1] = ','; + } else { + char *tmpbuf = realloc(buf, (bufsize *= 2)); + if (tmpbuf) { + buf = tmpbuf; + } else { + free(buf); + return -1; + } + + goto retry; + } + } + } + + buf[--len] = '\0'; + *bufptr = buf; + return len; +} + +static struct quote *gen_quote(const json_object *json) +{ + struct quote *q = calloc(1, sizeof(*q)); + if (!q) return NULL; + + json_object *tmp; + if (!json_object_object_get_ex(json, "symbol", &tmp) || + !(q->symbol = strdup(json_object_get_string(tmp))) || + !(q->currency = strdup("USD"))) + goto err; + + if (json_object_object_get_ex(json, "lastPrice", &tmp)) + q->price = json_object_get_double(tmp); + if (json_object_object_get_ex(json, "multiplier", &tmp)) + q->multiplier = json_object_get_double(tmp); + if (json_object_object_get_ex(json, "netPercentChangeInDouble", &tmp)) + q->change_percent = json_object_get_double(tmp); + + return q; + +err: + quote_free(q); + return NULL; +} + +int tda_get_quotes(struct tda_session *sess, const char *symbols, + struct quote ***quotes_ptr) +{ + json_object *json = fetch_quotes(sess, symbols); + if (!json) return -1; + + int n = 0; + struct lh_table *table = json_object_get_object(json); + struct quote **quotes = calloc(table->count, sizeof(*quotes)); + if (!quotes) { + json_object_put(json); + return -1; + } + + for (struct lh_entry *entry = table->head; entry; entry = entry->next) { + struct quote *q = gen_quote(entry->v); + if (q) quotes[n++] = q; + } + + json_object_put(json); + *quotes_ptr = quotes; + return n; +} -- cgit v1.2.3-70-g09d2