summaryrefslogtreecommitdiff
path: root/tda.c
diff options
context:
space:
mode:
authorDavid Vazgenovich Shakaryan <dvshakaryan@gmail.com>2021-05-31 06:11:22 -0700
committerDavid Vazgenovich Shakaryan <dvshakaryan@gmail.com>2021-05-31 06:11:22 -0700
commit18bde7391efb9c6bd6ea7846891ab5d16276a809 (patch)
tree237cc152edbdf934ed32e4c8c54c5ce97535e57f /tda.c
downloadstonks-master.tar.gz
stonks-master.tar.xz
initial importHEADmaster
A bit messy and missing error handling in some places.
Diffstat (limited to 'tda.c')
-rw-r--r--tda.c253
1 files changed, 253 insertions, 0 deletions
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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <json-c/json.h>
+
+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;
+}