#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; }