diff options
| author | David Vazgenovich Shakaryan <dvshakaryan@gmail.com> | 2021-05-31 06:11:22 -0700 | 
|---|---|---|
| committer | David Vazgenovich Shakaryan <dvshakaryan@gmail.com> | 2021-05-31 06:11:22 -0700 | 
| commit | 18bde7391efb9c6bd6ea7846891ab5d16276a809 (patch) | |
| tree | 237cc152edbdf934ed32e4c8c54c5ce97535e57f /tda.c | |
| download | stonks-18bde7391efb9c6bd6ea7846891ab5d16276a809.tar.gz stonks-18bde7391efb9c6bd6ea7846891ab5d16276a809.tar.xz  | |
A bit messy and missing error handling in some places.
Diffstat (limited to 'tda.c')
| -rw-r--r-- | tda.c | 253 | 
1 files changed, 253 insertions, 0 deletions
@@ -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; +}  | 
