skip to Main Content

I have built a Node.js app to query a couple of API endpoints to gather data on a certain stocks and act as a stock screener/websocket and then upload those final objects to my database for analysis at the end of the day. The app works fine and the objects are successfully inserted but I have to manually shut the app down and restart it after insertion because the websocket client keeps trying to reconnect after the lists are cleared at 8 pm. It will do so until it either crashes or the next cron schedule happens at 6 am the next weekday.

I haven’t found a proper way to shutdown the process after the lists are cleared at the end of the day, so I manually restart the app with "npm run start" which puts the app in waiting state for the next cron schedule each night, but it defeats the purpose of trying to automate my routine, doesn’t it.

Please forgive any oversight or structural errors as I am a self-taught amateur.

Q: How can I restructure my code so that when the lists are cleared and the isFirstCall flag is returned to "true", my app waits for the next cron schedule?

I have tried calling the main() method after clearing the lists with no success, and I can’t call the ws.close() function from the cron schedule at the end of the day because the websocket is initiliazed inside the main() method only after the first lists are populated.

server.js code:

const express = require("express");
const schedule = require("node-schedule");
const mysql = require("mysql");
const fs = require("fs");
const { // Dependencies} = require("./utilities");
const { //  Dependencies } = require("./databaseHandler");
const { // Dependencies} = require("./apihandler");

const cors = require("cors");
const WebSocket = require("ws");
const os = require("os");
require("dotenv").config();
const cron = require("node-cron");
const { v4: uuidv4 } = require("uuid");
const { log } = require("console");
const port = 5000;

// Start API and Websocket server
const app = express();
const wss = new WebSocket.Server({ port: 8080 });

// Define global variables
let stockScreenerList = [];
let batchQuoteList = [];
let premarketRunners = [];
let isFirstCall = true;

let subscriptionList = [];
let authenticated = false;


/**
* MYSQL CONNECTION SECTION
*/
const connection = mysql.createConnection({
  // MYSQL database params
});

// EOD CRON SCHEDULE
cron.schedule(
  "5 20 * * 1-5",
  async () => {
    try {
      if (premarketRunners.length > 0) {
        for (let i = 0; i < premarketRunners.length; i++) {
          const stockItem = premarketRunners[i];
          const stockSymbol = premarketRunners[i].symbol;
        }

        const isInserted = await insertGappersDb(premarketRunners);
        if (isInserted) {
          console.log("Clearing lists.");
          premarketRunners = [];
          stockScreenerList = [];
          batchQuoteList = [];
          subscriptionList = [];
          isFirstCall = true;
        }
      }
    } catch (error) {
      console.error("Error:", error);
    }
  },
  {
    timezone: "America/New_York",
  }
);


// Populate premarket runners list
async function getGappers() {
  let tempList = [];

  if (isFirstCall) {
// populating lists first time of the day



    console.log("First PremarketRunnersList populated...");
    isFirstCall = false;

    const endTime = new Date(); // record end time
    const duration = endTime - startTime; // calculate duration
    console.log(`First api call took ${duration} milliseconds to execute`);

    return gappersList;
  } else {
    console.log("Not the first api call, Checking for new stocks");
    //Logic to populate and return only new stocks to optimize efficiency
    return tempList;
  }
}

var connectWS = function () {
  const apiKey = process.env.WSAPIKEY;
  const loginMessage = JSON.stringify({
    event: "login",
    data: { apiKey },
  });
  var reconnectInterval = 1000 * 10;
  const ws = new WebSocket("wss://websockets.financialmodelingprep.com");

  const login = () => {
    if (!authenticated) {
      console.log("Logging in...");
      ws.send(loginMessage);
    }
  };

  const subscribe = () => {
    if (authenticated) {
      const subscribeMessages = premarketRunners
        .filter(
          (ticker) => !subscriptionList.includes(ticker.symbol.toLowerCase())
        )
        .map((ticker) =>
          JSON.stringify({
            event: "subscribe",
            data: { ticker: ticker.symbol.toLowerCase() },
          })
        );
      subscribeMessages.forEach((message) => ws.send(message));
      subscribeMessages.forEach((message) => console.log(message));
    }
  };

  ws.on("open", () => {
    console.log("WebSocket opened");
    login();
    subscribe();
  });

  ws.on("message", (data) => {
    const message = JSON.parse(data);
    if (message.event === "login") {
      if (message.status !== 200) {
        console.error(`Login failed: ${message.message}`);
        setTimeout(login, 5000);
      } else {
        console.log("Authenticated");
        authenticated = true;
        subscribe();
      }
    }

    if (message.event === "subscribe") {
      if (message.status !== 200) {
        console.error(`Subscription failed: ${message.message}`);
        setTimeout(subscribe, 5000);
      } else {
        const stockSymbol = extractStockSymbol(message.message);
        subscriptionList.push(stockSymbol);
        console.log(`Successfully Subbed to: ${stockSymbol}`);
      }
    }

    if (message.type === "T") {
      if (premarketRunners) {
        //premarket runners list is populated, broadcasting messages.
        const foundStock = premarketRunners.find(
          (item) => item.symbol.toUpperCase() === message.s.toUpperCase()
        );
        if (foundStock) {
          foundStock.premarketPrice = message.lp;
          wss.clients.forEach((client) => {
            if (client.readyState === WebSocket.OPEN) {
              client.send(JSON.stringify(premarketRunners));
            }
          });
        }
      }
    }
  });

  ws.on("error", (err) => {
    console.error(`WebSocket error: ${err.message}`);
  });

  ws.on("close", (code) => {
    console.log(`WebSocket closed with code ${code}`);
    authenticated = false;
    subscriptionList = [];
    setTimeout(connectWS, reconnectInterval);
  });
};

/**
* MAIN SECTION OF THE SERVER
*/

async function main() {
  const startTime = Date.now();
  const todaysDate = new Date(startTime).toLocaleDateString("en-US");

  console.log("Checktime called");
  const timeRight = checkTime();
  const marketStatus = await checkMarketStatus();
  //const market = await isMarketOpen();
  const isPremarket = marketStatus[0];
  const nasdaqStatus = marketStatus[1];
  const serverDate = marketStatus[3];

  if (timeRight && isPremarket && nasdaqStatus !== "open") {
    //console.log('first call');
    premarketRunners = await getGappers();

    connectWS();

    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify(premarketRunners));
      }
    });

    setInterval(async () => {
      const timeRight = checkTime();
      const marketStatus = await checkMarketStatus();

      const isPremarket = marketStatus[0];
      const nasdaqStatus = marketStatus[1];
      const serverDate = marketStatus[3];


      if (timeRight && isPremarket && nasdaqStatus !== "open") {
        // GetGappers already ran once, list ins't empty so just check for new runners.
        console.log(
          `premarketRunners contains ${premarketRunners.length} stocks, nsubscriptionList contains: ${subscriptionList.length} symbols.. nChecking for new tickers`
        );

        const newTickers = await getGappers();

        if (newTickers.length > 0) {
          // Filter out newTickers that are already subscribed to
          const filteredTickers = newTickers.filter(
            (ticker) => !subscriptionList.includes(ticker.symbol.toLowerCase())
          );

          filteredTickers.forEach((item) => {
            console.log(`Found new Ticker: ${item.symbol}... Subbing.`);
          });
          //subscribe to websocket info for each new stock that's not already subscribed to
          const subscribeMessages = filteredTickers.map((ticker) =>
            JSON.stringify({
              event: "subscribe",
              data: { ticker: ticker.symbol.toLowerCase() },
            })
          );

          newTickers.forEach((item) => premarketRunners.push(item));
          // send payload to connected users
          wss.clients.forEach((client) => {
            if (client.readyState === WebSocket.OPEN) {
              client.send(JSON.stringify(premarketRunners));
            }
          });
        } else {
          console.log("No new tickers found");
          // Logic to update existing items then sending them

          console.log("Done updating... Sending Updated payload...");

          wss.clients.forEach((client) => {
            if (client.readyState === WebSocket.OPEN) {
              client.send(JSON.stringify(premarketRunners));
            }
          });
        }
      }
    }, 45 * 1000); // Check for new stocks delay
  }
}

/**
* Run The main function everyday of the week at 6 am EST
*/
cron.schedule(
  "0 6 * * 1-5",
  () => {
    main();
  },
  {
    timezone: "America/New_York",
  }
);

Thanks!

2

Answers


  1. It sounds like you want to automate the process of shutting down and restarting your Node.js app when certain conditions are met, specifically when your lists are cleared and the isFirstCall flag is set to "true". Here’s how you might approach this:

    Refactor Your Code Structure:

    It’s a good idea to refactor your code to make it more modular and organized. This will make it easier to manage different parts of your app.

    javascript

    // main.js
    const main = async () => {
      // Initialize the WebSocket client
      const ws = new WebSocketClient();
    
      // Your data retrieval, processing, and database insertion logic
    
      // Schedule the cron jobs
      scheduleCronJobs();
    };
    
    // Start the main process
    main();
    

    Create a Shutdown Function:

    Create a function that gracefully shuts down your app when called. You can use the process.exit() function to terminate the Node.js process. You can also close the WebSocket connection before exiting.

    javascript

    const shutdown = (ws) => {
      console.log("Shutting down...");
    
      // Close the WebSocket connection
      if (ws) {
        ws.close();
      }
    
      // Exit the process
      process.exit(0);
    };
    

    Use a Signal Handler for Shutdown:

    You can use a signal handler to detect when your app should shut down. For example, you can listen for the SIGINT signal (Ctrl+C) and trigger the shutdown process.

    javascript

    // Listen for the SIGINT signal
    process.on("SIGINT", () => {
      shutdown(ws); // Pass the WebSocket client instance to the shutdown function
    });
    

    Check Conditions for Auto-Restart:

    Modify your code so that it checks whether the conditions for auto-restart are met. If the lists are cleared and the isFirstCall flag is set to "true", you can trigger the shutdown and exit process.

    javascript

    // Inside your data processing logic
    if (listsCleared && isFirstCall) {
      shutdown(ws); // Trigger shutdown when conditions are met
    }
    

    Use a Process Manager:

    To fully automate the restarting process, you can use a process manager like pm2. It can manage your Node.js application, automatically restart it, and provide logging and monitoring capabilities.

    Install pm2 globally:

    npm install -g pm2
    

    Start your app with pm2:

    css

    pm2 start main.js
    

    pm2 will handle the automatic restarts and shutdowns based on your configuration.

    By following this approach, your app will gracefully shut down and exit when the specified conditions are met, and it will be automatically restarted by a process manager like pm2 according to your desired schedule.

    Login or Signup to reply.
  2. Add global variables at the top:

    let timerID = 0;
    let clientWS = null;
    

    Inside your EOD function, add clearInterval and close WebSocket:

    cron.schedule(
        "5 20 * * 1-5",
        async () => {
        try {
            clearInterval(timerID); // stop timer
            timerID = 0;
            clientWS.close();   // stop websocket client
    
            if (premarketRunners.length > 0) {
        ...
    )
    

    Inside your connectWS function, change close handler and at the end return websocket:

    var connectWS = function () {
        ...
        ws.on("close", (code) => {
            console.log(`WebSocket closed with code ${code}`);
            authenticated = false;
            subscriptionList = [];
            if (timerID)        // only try to reconnect if timer is running
                 setTimeout(() => { clientWS = connectWS(); }, reconnectInterval);
        });
        ...
        return ws;   // return websocket so you can close it
    }
    

    Inside your main function, save the setInterval id and the websocket:

     main() {
       ...
       clientWS = connectWS();
       ...
       timerID = setInterval( ...,
       ...);
     }
    

    You could also refactor your code a bit, like having 3 main functions:

    • start function (at 6 am) instead of main
    • an interval function (every xx seconds) instead of a lambda function in main
    • a stop function (at 8 pm) instead of an lambda function in cron.schedule
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search