diff options
-rw-r--r-- | api.c | 268 | ||||
-rw-r--r-- | api.h | 28 | ||||
-rw-r--r-- | curses_win.c | 6 | ||||
-rw-r--r-- | gtk_win.c | 4 | ||||
-rw-r--r-- | main.c | 2 |
5 files changed, 303 insertions, 5 deletions
@@ -107,6 +107,39 @@ String* api_curl_data(const char* url) { return pString; } +void iex_batch_store_data(Info_Array* pInfo_Array, Data_Level data_level) { + // TO-DO -- MAKE FUNCTION HANDLE MORE THAN 100 SECURITIES (API LIMITATION) + + char iex_api_string[URL_MAX_LENGTH], symbol_list_string[URL_MAX_LENGTH]; + symbol_list_string[0] = '\0'; + for (size_t i = 0; i < pInfo_Array->length; i++) + if (strcmp(pInfo_Array->array[i]->symbol, "USD$") != 0) + sprintf(&symbol_list_string[strlen(symbol_list_string)], "%s,", + pInfo_Array->array[i]->symbol); + + symbol_list_string[strlen(symbol_list_string) - 1] = '\0'; // Remove last comma + + char endpoints[128]; + if (data_level == ALL) + strcpy(endpoints, "quote,chart,company,stats,peers,news,earnings&range=5y"); + else if (data_level == CHECK) + strcpy(endpoints, "quote,chart"); + else strcpy(endpoints, "company,stats,peers,news,earnings&range=5y"); + sprintf(iex_api_string, + "https://api.iextrading.com/1.0/stock/market/batch?symbols=%s&types=%s", + symbol_list_string, endpoints); + + String* pString = api_curl_data(iex_api_string); + if (pString == NULL) + return; + + Json* jobj = json_tokener_parse(pString->data); + info_array_store_all_from_json(pInfo_Array, jobj); + + json_object_put(jobj); + string_destroy(&pString); +} + void* iex_store_company(void* vpInfo) { Info* symbol_info = vpInfo; char iex_api_string[URL_MAX_LENGTH]; @@ -650,6 +683,40 @@ void* api_info_array_store_check_data(void* vpPortfolio_Data) { return ret; } +void api_info_array_store_data_batch(Info_Array* pInfo_Array, Data_Level data_level) { + iex_batch_store_data(pInfo_Array, data_level); + + // All IEX securities are accounted for + Info* pInfo; + pthread_t threads[pInfo_Array->length]; + for (size_t i = 0; i < pInfo_Array->length; i++) { + pInfo = pInfo_Array->array[i]; + if (pInfo->api_provider == EMPTY && strcmp(pInfo->symbol, "USD$") != 0) { + if (strlen(pInfo->symbol) > 5) // Crypto + pthread_create(&threads[i], NULL, coinmarketcap_store_info, pInfo); + else pthread_create(&threads[i], NULL, alphavantage_store_info, pInfo); // AV + } + } + + for (size_t i = 0; i < pInfo_Array->length; i++) { + pInfo = pInfo_Array->array[i]; + if (pInfo->api_provider != IEX && strcmp(pInfo->symbol, "USD$") != 0) { + pthread_join(threads[i], NULL); + if (strlen(pInfo->symbol) <= 5) // Crypto with 5 char or less name + pthread_create(&threads[i], NULL, alphavantage_store_info, pInfo); + } + } + for (size_t i = 0; i < pInfo_Array->length; i++) { + pInfo = pInfo_Array->array[i]; + if (pInfo->api_provider != IEX && pInfo->api_provider != ALPHAVANTAGE && + strlen(pInfo->symbol) <= 5 && strcmp(pInfo->symbol, "USD$") != 0) + pthread_join(threads[i], NULL); // Crypto with 5 char or less name + + info_store_check_data(pInfo_Array->array[i]); + } + info_array_store_totals(pInfo_Array); +} + void info_store_check_data(Info* pInfo) { if (pInfo->amount == EMPTY) return; @@ -722,6 +789,207 @@ Ref_Data* iex_get_valid_symbols(void) { return pRef_Data; } +void info_array_store_all_from_json(Info_Array* pInfo_Array, const Json* jobj) { + Json* jsymbol, * jquote, * jchart, * jcompany, * jstats, * jpeers, * jnews, * jearnings; + Info* pInfo; + for (size_t i = 0; i < pInfo_Array->length; i++) { + jsymbol = json_object_object_get(jobj, pInfo_Array->array[i]->symbol); + if (jsymbol != NULL) { + pInfo = pInfo_Array->array[i]; + pInfo->api_provider = IEX; + jquote = json_object_object_get(jsymbol, "quote"); + jchart = json_object_object_get(jsymbol, "chart"); + jcompany = json_object_object_get(jsymbol, "company"); + jstats = json_object_object_get(jsymbol, "stats"); + jpeers = json_object_object_get(jsymbol, "peers"); + jnews = json_object_object_get(jsymbol, "news"); + jearnings = json_object_object_get(jsymbol, "earnings"); + if (jquote != NULL) + info_store_quote_from_json(pInfo, jquote); + if (jchart != NULL) + info_store_chart_from_json(pInfo, jchart); + if (jcompany != NULL) + info_store_company_from_json(pInfo, jcompany); + if (jstats != NULL) + info_store_stats_from_json(pInfo, jstats); + if (jpeers != NULL) + info_store_peers_from_json(pInfo, jpeers); + if (jnews != NULL) + info_store_news_from_json(pInfo, jnews); + if (jearnings != NULL) + info_store_earnings_from_json(pInfo, jearnings); + } + } +} + +void info_store_quote_from_json(Info* pInfo, const Json* jquote) { + if (json_object_get_int64(json_object_object_get(jquote, "extendedPriceTime")) > + json_object_get_int64(json_object_object_get(jquote, "latestUpdate"))) { + pInfo->price = json_object_get_double(json_object_object_get(jquote, "extendedPrice")); + pInfo->intraday_time = json_object_get_int64(json_object_object_get(jquote, + "extendedPriceTime")) / 1000; + } else { + pInfo->price = json_object_get_double(json_object_object_get(jquote, "latestPrice")); + pInfo->intraday_time = json_object_get_int64(json_object_object_get(jquote, "latestUpdate")) / + 1000; + } + pInfo->price_last_close = json_object_get_double(json_object_object_get(jquote, "previousClose")); + if (pInfo->price_last_close == 0) // May be 0 over weekend + pInfo->price_last_close = EMPTY; + pInfo->marketcap = json_object_get_int64(json_object_object_get(jquote, "marketCap")); + pInfo->volume_1d = json_object_get_int64(json_object_object_get(jquote, "latestVolume")); + pInfo->pe_ratio = json_object_get_double(json_object_object_get(jquote, "peRatio")); +} + + +void info_store_chart_from_json(Info* pInfo, const Json* jchart) { + size_t len = json_object_array_length(jchart); + pInfo->points = calloc(len + 1, sizeof(double)); + pointer_alloc_check(pInfo->points); + for (size_t i = 0; i < len; i++) + pInfo->points[i] = json_object_get_double( + json_object_object_get(json_object_array_get_idx(jchart, i), "close")); + if (pInfo->price_last_close == EMPTY) // May be 0 over weekend, so get last close from points array + pInfo->price_last_close = pInfo->points[len - 1]; + if (len > 5) + pInfo->price_7d = pInfo->points[len - 5]; + if (len > 21) + pInfo->price_30d = pInfo->points[len - 21]; + if (len < 25) // 1 month api data + pInfo->price_30d = pInfo->points[0]; +} + +void info_store_company_from_json(Info* pInfo, const Json* jcompany) { + Json* jsymbol, * jname, * jindustry, * jwebsite, * jdescription, * jceo, * jtype, * jsector; + jsymbol = json_object_object_get(jcompany, "symbol"); + jname = json_object_object_get(jcompany, "companyName"); + jindustry = json_object_object_get(jcompany, "industry"); + jwebsite = json_object_object_get(jcompany, "website"); + jdescription = json_object_object_get(jcompany, "description"); + jceo = json_object_object_get(jcompany, "CEO"); + jtype = json_object_object_get(jcompany, "issueType"); + jsector = json_object_object_get(jcompany, "sector"); + + if (jsymbol != NULL) + strcpy(pInfo->symbol, json_object_get_string(jsymbol)); + if (jname != NULL) + strcpy(pInfo->name, json_object_get_string(jname)); + if (jindustry != NULL) + strcpy(pInfo->industry, json_object_get_string(jindustry)); + if (jwebsite != NULL) + strcpy(pInfo->website, json_object_get_string(jwebsite)); + if (jdescription != NULL) + strcpy(pInfo->description, json_object_get_string(jdescription)); + if (jceo != NULL) + strcpy(pInfo->ceo, json_object_get_string(jceo)); + if (jtype != NULL) + strcpy(pInfo->issue_type, json_object_get_string(jtype)); + if (jsector != NULL) + strcpy(pInfo->sector, json_object_get_string(jsector)); +} + +void info_store_stats_from_json(Info* pInfo, const Json* jstats) { + pInfo->div_yield = json_object_get_double(json_object_object_get(jstats, "dividendYield")); + pInfo->revenue = json_object_get_int64(json_object_object_get(jstats, "revenue")); + pInfo->gross_profit = json_object_get_int64(json_object_object_get(jstats, "grossProfit")); + pInfo->cash = json_object_get_int64(json_object_object_get(jstats, "cash")); + pInfo->debt = json_object_get_int64(json_object_object_get(jstats, "debt")); +} + +void info_store_peers_from_json(Info* pInfo, const Json* jpeers) { + size_t len = json_object_array_length(jpeers); + if (len == 0) + return; + + if (len > MAX_PEERS) + len = MAX_PEERS; + + pInfo->peers = api_info_array_init_from_length(len); + for (size_t i = 0; i < pInfo->peers->length; i++) + strcpy(pInfo->peers->array[i]->symbol, json_object_get_string( + json_object_array_get_idx(jpeers, i))); + + api_info_array_store_data_batch(pInfo->peers, CHECK); +} + +void info_store_news_from_json(Info* pInfo, const Json* jnews) { + Json* idx, * headline, * source, * date, * summary, * url, * related; + size_t len = json_object_array_length(jnews); + if (len < (unsigned) pInfo->num_articles) + pInfo->num_articles = (int)len; + + pInfo->articles = malloc(sizeof(News*) * pInfo->num_articles); + pointer_alloc_check(pInfo->articles); + + for (int i = 0; i < pInfo->num_articles; i++) { + idx = json_object_array_get_idx(jnews, (size_t) i); + headline = json_object_object_get(idx, "headline"); + source = json_object_object_get(idx, "source"); + date = json_object_object_get(idx, "datetime"); + summary = json_object_object_get(idx, "summary"); + url = json_object_object_get(idx, "url"); + related = json_object_object_get(idx, "related"); + + /* + * If two articles in a row are the same, change num_articles and break loop. This will + * happen if there are not enough articles supplied by API. + */ + if (i > 0 && headline != NULL && + strcmp(json_object_get_string(headline), pInfo->articles[i - 1]->headline) == 0) { + pInfo->num_articles = i; + break; + } + + pInfo->articles[i] = api_news_init(); + if (headline != NULL) + strcpy(pInfo->articles[i]->headline, json_object_get_string(headline)); + if (source != NULL) + strcpy(pInfo->articles[i]->source, json_object_get_string(source)); + if (date != NULL) + strncpy(pInfo->articles[i]->date, json_object_get_string(date), 10); + pInfo->articles[i]->date[10] = '\0'; // strncpy doesn't null terminate + if (summary != NULL) + strcpy(pInfo->articles[i]->summary, json_object_get_string(summary)); + strip_tags(pInfo->articles[i]->summary); // Summary will be html formatted, so strip tags + if (url != NULL) + strcpy(pInfo->articles[i]->url, json_object_get_string(url)); + if (related != NULL) + strcpy(pInfo->articles[i]->related, json_object_get_string(related)); + int related_num = 0; + for (size_t j = 0; j < strlen(pInfo->articles[i]->related); j++) { // List only first five related symbols + if (pInfo->articles[i]->related[j] == ',') + related_num++; + if (related_num == 5) { + pInfo->articles[i]->related[j] = '\0'; + break; + } + } + } +} + +void info_store_earnings_from_json(Info* pInfo, const Json* jearnings) { + // ETFs don't report earnings + if (!json_object_is_type(json_object_object_get(jearnings, "earnings"), json_type_array)) + return; + + size_t len = json_object_array_length(json_object_object_get(jearnings, "earnings")); + Json* idx, * period, * end_date, * report_date; + for (size_t i = 0; i < len; i++) { + idx = json_object_array_get_idx(json_object_object_get(jearnings, "earnings"), i); + pInfo->eps[i] = json_object_get_double(json_object_object_get(idx, "actualEPS")); + pInfo->eps_year_ago[i] = json_object_get_double(json_object_object_get(idx, "yearAgo")); + period = json_object_object_get(idx, "fiscalPeriod"); + end_date = json_object_object_get(idx, "fiscalEndDate"); + report_date = json_object_object_get(idx, "EPSReportDate"); + if (period != NULL) + strcpy(pInfo->fiscal_period[i], json_object_get_string(period)); + else if (end_date != NULL) + strcpy(pInfo->fiscal_period[i], json_object_get_string(end_date)); + else if (report_date != NULL) + strcpy(pInfo->fiscal_period[i], json_object_get_string(report_date)); + } +} + Info* info_array_get_info_from_symbol(const Info_Array* pInfo_Array, const char* symbol) { for (size_t i = 0; i < pInfo_Array->length; i++) if (strcmp(symbol, pInfo_Array->array[i]->symbol) == 0) @@ -13,6 +13,14 @@ #define ALPHAVANTAGE 2 #define COINMARKETCAP 3 +typedef enum data_level { + ALL, CHECK, MISC +} Data_Level; + +#define DATA_ALL 0 +#define DATA_CHECK 1 +#define DATA_MISC 2 + #define QUARTERS 4 #define DATE_MAX_LENGTH 32 #define CELL_MAX_LENGTH 16 @@ -183,6 +191,8 @@ size_t api_string_writefunc(void* ptr, size_t size, size_t nmemb, String* pStrin */ String* api_curl_data(const char* url); +void iex_batch_store_data(Info_Array* pInfo_Array, Data_Level data_level); + /** * Designed for threading * @@ -344,6 +354,8 @@ void* api_store_misc_info(void* vpInfo); */ void* api_info_array_store_check_data(void* vpPortfolio_Data); +void api_info_array_store_data_batch(Info_Array* pInfo_Array, Data_Level data_level); + /** * After API data and portfolio have already been collected, uses them to populate the Info fields current_value and * all the profit fields. @@ -363,6 +375,22 @@ void info_array_store_totals(Info_Array* pInfo_Array); */ Ref_Data* iex_get_valid_symbols(void); +void info_array_store_all_from_json(Info_Array* pInfo_Array, const Json* jobj); + +void info_store_quote_from_json(Info* pInfo, const Json* jquote); + +void info_store_chart_from_json(Info* pInfo, const Json* jchart); + +void info_store_company_from_json(Info* pInfo, const Json* jcompany); + +void info_store_stats_from_json(Info* pInfo, const Json* jstats); + +void info_store_peers_from_json(Info* pInfo, const Json* jpeers); + +void info_store_news_from_json(Info* pInfo, const Json* jnews); + +void info_store_earnings_from_json(Info* pInfo, const Json* jearnings); + /** * Searches through an Info_Array and returns a ponter to the Info which has the same symbol as * the function's argument. If not found, returns NULL. diff --git a/curses_win.c b/curses_win.c index 1c419f628197..7621a6ed67eb 100644 --- a/curses_win.c +++ b/curses_win.c @@ -167,9 +167,11 @@ void portfolio_print_stock(const char* symbol) { } void interface_print(const char* symbol) { - Info* symbol_info = api_info_init(); + Info_Array* pInfo_Array = api_info_array_init_from_length(1); + Info* symbol_info = pInfo_Array->array[0]; strcpy(symbol_info->symbol, symbol); - if (api_store_all_info(symbol_info) == NULL) { + api_info_array_store_data_batch(pInfo_Array, ALL); + if (symbol_info->api_provider == EMPTY) { api_info_destroy(&symbol_info); RET_MSG("Invalid symbol.") } diff --git a/gtk_win.c b/gtk_win.c index 8a3da9157609..51fa3130f07f 100644 --- a/gtk_win.c +++ b/gtk_win.c @@ -129,7 +129,7 @@ void on_load_button_clicked(GtkButton* button) { app.portfolio_data = portfolio_info_array_init_from_portfolio_string(app.portfolio_string); if (app.portfolio_data != NULL) { // If file is not a length 0 JSON array check_list_create_from_string(); - api_info_array_store_check_data(app.portfolio_data); + api_info_array_store_data_batch(app.portfolio_data, CHECK); check_list_add_api_data(); } @@ -410,7 +410,7 @@ void list_store_update(void) { // Recreate Info_Array check_list_create_from_string(); // Will set app.portfolio_data if success if (app.portfolio_data != NULL) { - api_info_array_store_check_data(app.portfolio_data); + api_info_array_store_data_batch(app.portfolio_data, CHECK); check_list_add_api_data(); } } @@ -66,7 +66,7 @@ int main(int argc, char* argv[]) { Info_Array* portfolio_data = portfolio_info_array_init_from_portfolio_string( pString); if (portfolio_data != NULL) { - api_info_array_store_check_data(portfolio_data); + api_info_array_store_data_batch(portfolio_data, CHECK); info_array_portfolio_printw(portfolio_data); api_info_array_destroy(&portfolio_data); } |