top of page

Part 1 : Stock Picker using API and ESP32

Hey everyone, welcome back to Electronics Eternity! In this tutorial, we'll be building a stock picker using an API and market data to help decide whether a stock is a good investment. We’ll use the ESP32 microcontroller to perform simple calculations, like cash flow and the ratio of current assets to liabilities.


While APIs allow for a wide range of market data evaluations, we’ll focus on these key metrics which are cash flow and current asset vs liability evaluation for simplicity and to demonstrate the ESP32's capabilities in performing these tasks.


For this tutorial, we'll be using the API from financialdatasets.ai, which is an excellent resource for both historical and real-time market data. The platform is easy to navigate, with interactive guides that make it accessible for all skill levels. Based on my experience, I’d give it 5 stars for its user-friendliness and reliability. Their customer service is also fantastic, especially in their Discord channel, where you can get support and advice. If you're looking for a solid market data API, I highly recommend checking out financialdatasets.ai.

Financial Datasets AI

This tutorial will have 2 parts, where we will be focusing on Cash Flow analysis in this 1st part.


Below is the complete code that we will be using for this tutorial :


#include <WiFi.h>
#include <time.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

//Wifi Creds
const char* ssid = "XXXXX";
const char* password = "XXXXX";

//Time Function Variables
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 28800;
const int daylightOffset_sec = 0;
int thisYear, lastYear, lastLastYear;
char thisYearChar[5], lastYearChar[5], lastLastYearChar[5];
boolean timeSuccess = false;


char* test[] = { "AAPL" };
char* year[] = { thisYearChar, lastYearChar, lastLastYearChar };

JsonDocument doc;
float cash_flow[3];

void setup() {
  Serial.begin(115200);
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected.");

  // Init and get the time
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  //printLocalTime();
  delay(5000);

  if (timeSuccess == false) {
    printLocalTime();
  }

  int noOftickers = sizeof(test) / sizeof(test[0]);
  for (int i = 0; i < noOftickers; i++) {
    cashflow(test[i]);
  }
}


void loop() {
}

void printLocalTime() {
  struct tm timeinfo;

  if (!getLocalTime(&timeinfo)) {
    Serial.print("Failed to obtain time");
    while (!getLocalTime(&timeinfo)) {
      Serial.print(".");
    }
  }

  timeSuccess = true;
  //Serial.print("TimeInfo: ");
  //Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");

  //Serial.print("Year of the Decade :");
  char timeYear[5];
  //strftime(char * destination, size, char format, struct tm * datetime);
  strftime(timeYear, 5, "%Y", &timeinfo);
  //Serial.println(timeYear);

  thisYear = atoi(timeYear) - 1;
  lastYear = thisYear - 1;
  lastLastYear = lastYear - 1;

  itoa(thisYear, thisYearChar, 10);
  itoa(lastYear, lastYearChar, 10);
  itoa(lastLastYear, lastLastYearChar, 10);
}

void cashflow(char ticker[]) {
  //ticker[] instead of ticker as the char size can be random
  Serial.println("Starting Void Cashflow");
  Serial.println("");
  
  int noOfYears = sizeof(year) / sizeof(year[0]);
  for (int i = 0; i < noOfYears; i++) {

    char header[255] = "https://api.financialdatasets.ai/financials/cash-flow-statements/?ticker=";
    char mid1[] = "&limit=1&period=ttm&report_period_gte=";
    char mid2[] = "-01-01&report_period_lte=";
    char footer[] = "-12-31";

    strcat(header, ticker);
    strcat(header, mid1);
    strcat(header, year[i]);
    strcat(header, mid2);
    strcat(header, year[i]);
    strcat(header, footer);

    //Serial.println(header);

    HTTPClient http;
    http.begin(header);
    //http.addHeader("X-API-KEY", "99ab1234-abcd-efgh-abef-dfasf23c1234");

    int httpCode = http.GET();
    if (httpCode > 0) {
      String payload = http.getString();
      //Serial.println(httpCode);
      //Serial.print("payload :");
      //Serial.println(payload);
      payload.remove(0, 2);
      payload.replace("cash_flow_statements", "");
      payload.remove(0, 3);
      //Serial.print("Edited Payload :");
      //Serial.println(payload);

      int stringLen = payload.length() + 1;
      char json[stringLen];
      payload.toCharArray(json, stringLen);

      DeserializationError error = deserializeJson(doc, json);
      if (error) {
        Serial.print(F("deserializeJson() failed: "));
        Serial.println(error.f_str());
        return;
      }
      //Serial.print("Json :");
      //Serial.println(json);

      const char* stock = doc["ticker"];

      cash_flow[i] = doc["free_cash_flow"];

      Serial.print("Ticker :");
      Serial.println(stock);
      Serial.print(year[i]);
      Serial.print(" Cash Flow : ");
      Serial.println(cash_flow[i] / 1000);

    } else {
      Serial.println("Error on HTTP request");
    }

    http.end();  //Free the resources
    Serial.println("");
    delay(1000);
  }

  if (cash_flow[0] > cash_flow[1] || cash_flow[1] > cash_flow[2]) {
    Serial.println("Cash Flow is Good");
  } else {
    Serial.println("Cash Flow is BAD");
  }
}

Let's look into the detail breakdown of this code :

#include <WiFi.h>
#include <time.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

Line 1-4: We begin by including the necessary libraries:

  • WiFi.h for handling wireless connectivity.

  • time.h for retrieving and managing real-time data.

  • HTTPClient.h for making HTTP requests to fetch financial data.

  • ArduinoJson.h for parsing the JSON response from the API.


//Wifi Creds
const char* ssid = "XXXXX";
const char* password = "XXXXX";

Line 6-9: We define WiFi credentials.

  • ssid and password hold the network details required to connect the ESP32 to the internet.


//Time Function Variables
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 28800;
const int daylightOffset_sec = 0;
int thisYear, lastYear, lastLastYear;
char thisYearChar[5], lastYearChar[5], lastLastYearChar[5];
boolean timeSuccess = false;

Line 11-16: We set up variables for time management

  • ntpServer specifies the NTP (Network Time Protocol) server for retrieving the correct time.

  • gmtOffset_sec sets the timezone offset; in this case, it is UTC+8 (Singapore time).

  • daylightOffset_sec remains 0 since no daylight savings time adjustments are required.

  • thisYear, lastYear, and lastLastYear store the calculated years dynamically.

  • Character arrays thisYearChar, lastYearChar, and lastLastYearChar store year values as strings for API requests.


boolean timeSuccess = false;

Line 18-19:* Boolean variable timeSuccess ensures that time retrieval is successful before proceeding.


char* test[] = { "AAPL" };
char* year[] = { thisYearChar, lastYearChar, lastLastYearChar };

Line 21-22:

  • test[] stores the stock ticker symbols to analyze. Currently, only *AAPL (Apple Inc.)* is included.

  • year[] is an array containing dynamically generated year strings used in API requests.


JsonDocument doc;
float cash_flow[3];

Line 24-25:

  • JsonDocument doc; is used to store parsed JSON data from the API response.

  • float cash_flow[3]; holds the free cash flow values for the past three years.


void setup() {
  Serial.begin(115200);
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected.");

Line 27-32: Setup Function (setup())

  • We start serial communication at a baud rate of *115200* for debugging.

  • The ESP32 attempts to connect to WiFi using WiFi.begin().

  • The program waits in a loop (while (WiFi.status() != WL_CONNECTED)) until a connection is established.

  • Upon successful connection, it prints a confirmation message as "WiFi connected."


  // Init and get the time
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  //printLocalTime();
  delay(5000);

Line 34-37:

  • configTime() is used to initialize the time service with the specified NTP server and timezone offsets.

  • A delay of *5000 milliseconds* is added to ensure the time sync process is complete.


  if (timeSuccess == false) {
    printLocalTime();
  }

Line 39-41:

  • If timeSuccess is still false, we call printLocalTime() to retrieve and store the correct year values.


  int noOftickers = sizeof(test) / sizeof(test[0]);
  for (int i = 0; i < noOftickers; i++) {
    cashflow(test[i]);
  }
}

Line 43-46:

  • The program determines the number of stock tickers to process by calculating the size of test[].

  • It then iterates over each ticker and calls the cashflow() function to retrieve and analyze financial data.


void loop() {
}

Line 49-50: Loop Function (loop())

  • The loop() function remains empty since all operations are performed once during the setup() phase.


void printLocalTime() {
  struct tm timeinfo;

  if (!getLocalTime(&timeinfo)) {
    Serial.print("Failed to obtain time");
    while (!getLocalTime(&timeinfo)) {
      Serial.print(".");
    }
  }

Line 52-55: Time Retrieval Function

  • The function attempts to fetch the local time using getLocalTime().

  • If the request fails, it prints an error and retries until a valid time is obtained.


  timeSuccess = true;
  //Serial.print("TimeInfo: ");
  //Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");

  //Serial.print("Year of the Decade :");
  char timeYear[5];
  //strftime(char * destination, size, char format, struct tm * datetime);
  strftime(timeYear, 5, "%Y", &timeinfo);
  //Serial.println(timeYear);

  thisYear = atoi(timeYear) - 1;
  lastYear = thisYear - 1;
  lastLastYear = lastYear - 1;

  itoa(thisYear, thisYearChar, 10);
  itoa(lastYear, lastYearChar, 10);
  itoa(lastLastYear, lastLastYearChar, 10);
}

Line 57-66:

  • When time information is succesfully retrieved, we convert the boolean value of timeSuccess= true;

  • The retrieved time information is stored in a struct tm timeinfo object.

  • strftime(timeYear, 5, "%Y", &timeinfo); extracts the four-digit year.

  • We calculate the previous two years dynamically by converting timeYear to an integer and subtracting values.

  • itoa() converts the integer years back into character arrays for use in API calls in the later part of our code.


void cashflow(char ticker[]) {
  //ticker[] instead of ticker as the char size can be random
  Serial.println("Starting Void Cashflow");
  Serial.println("");
  
  int noOfYears = sizeof(year) / sizeof(year[0]);
  for (int i = 0; i < noOfYears; i++) {

Line 68-72: Financial Data Retrieval

  • The function begins by printing a message to indicate data retrieval has started.

  • The number of years to process is determined by calculating the size of year[]


    char header[255] = "https://api.financialdatasets.ai/financials/cash-flow-statements/?ticker=";
    char mid1[] = "&limit=1&period=ttm&report_period_gte=";
    char mid2[] = "-01-01&report_period_lte=";
    char footer[] = "-12-31";

    strcat(header, ticker);
    strcat(header, mid1);
    strcat(header, year[i]);
    strcat(header, mid2);
    strcat(header, year[i]);
    strcat(header, footer);

Line 74-83:

  • A URL is dynamically constructed using the ticker symbol and years:

  • header[] stores the base API URL.

  • mid1[], mid2[], and footer[] hold additional query parameters.

  • strcat() is used to concatenate the final request URL.


HTTPClient http;
http.begin(header);
//http.addHeader("X-API-KEY", "99ab1234-abcd-efgh-abef-dfasf23c2725");

int httpCode = http.GET();
    if (httpCode > 0) {
      String payload = http.getString();
      //Serial.println(httpCode);
      //Serial.print("payload :");
      //Serial.println(payload);
      payload.remove(0, 2);
      payload.replace("cash_flow_statements", "");
      payload.remove(0, 3);
      //Serial.print("Edited Payload :");
      //Serial.println(payload);

      int stringLen = payload.length() + 1;
      char json[stringLen];
      payload.toCharArray(json, stringLen);

      DeserializationError error = deserializeJson(doc, json);
      if (error) {
        Serial.print(F("deserializeJson() failed: "));
        Serial.println(error.f_str());
        return;
      }

Line 85-95:

  • The HTTPClient library is used to send a GET request to the API.

  • The response is stored in payload.

  • Formatting modifications are made to clean the JSON data.

  • The deserializeJson() function extracts relevant information.

  • If JSON parsing fails, an error message is printed.


      const char* stock = doc["ticker"];
      cash_flow[i] = doc["free_cash_flow"];
      Serial.print("Ticker :");
      Serial.println(stock);
      Serial.print(year[i]);
      Serial.print(" Cash Flow : ");
      Serial.println(cash_flow[i] / 1000);

Line 97-104:

  • The extracted stock ticker and free cash flow values are stored in cash_flow[]

  • The retrieved values are printed to the serial monitor.


  if (cash_flow[0] > cash_flow[1] || cash_flow[1] > cash_flow[2]) {
    Serial.println("Cash Flow is Good");
  } else {
    Serial.println("Cash Flow is BAD");
  }

Line 106-110:

  • A simple financial analysis is performed

  • If the cash flow trend is increasing or stable over three years, the program prints *"Cash Flow is Good"*.

  • Otherwise, it prints *"Cash Flow is BAD"*.


With that, we have come to the end of this 1st part of our tutorial. For more detailed instruction, feel free to check out my YouTube video :


Please don't Forget to Like and Subscribe. Thank you and look forward to see you in Part 2.

 

Comments


bottom of page