Building a Custom Stock Alert System with Google Apps Script

A lean, zero-cost programmatic guide to monitoring stock floors using Google Sheets and Gmail

Google Finance provides a powerful interface for tracking tickers and building watchlists. However, it lacks a native automated mechanism to alert you when a stock falls below a specific target or threshold. For developers looking for a fast, minimalist workaround, you can construct a self-contained alert agent inside Google Drive at zero cost.

This guide details how to leverage the native =GOOGLEFINANCE() formula inside Google Sheets, back it with a short Google Apps Script macro, and schedule an automated trigger to email you the moment a floor price is breached.

Step 1: Construct the Spreadsheet Architecture

Create a new Google Sheet. Configure the headers exactly as follows on the first row:

Column A (Ticker) Column B (Target Floor) Column C (Current Price) Column D (Circuit Breaker)
NASDAQ:AAPL 150.00 =GOOGLEFINANCE(A2, "price")
NYSE:BRK.B 380.00 =GOOGLEFINANCE(A3, "price")

Design Principle: Column D acts as an operational circuit breaker. Without this state verification, a script scheduled to run every 5 minutes would continuously spam your inbox for as long as the asset stays below your target floor. Leave column D empty to begin with.

Step 2: Implement the Apps Script Execution Layer

Open the internal script editor by navigating to Extensions > Apps Script. Clear the default scaffold code and inject the optimized monitoring subroutine below:

function checkStockPrices() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  const lastRow = sheet.getLastRow();
  
  // Guard clause against empty sheets
  if (lastRow < 2) return;
  
  // Fetch columns A through D dynamically
  const dataRange = sheet.getRange("A2:D" + lastRow);
  const data = dataRange.getValues();
  const emailAddress = Session.getActiveUser().getEmail(); 

  data.forEach((row, index) => {
    const ticker = row[0];
    const targetPrice = row[1];
    const currentPrice = row[2];
    const emailSentFlag = row[3];

    // Evaluate conditions for dropping below floor target
    if (ticker && currentPrice <= targetPrice && emailSentFlag !== "YES") {
      const subject = `[Market Alert] ${ticker} Drop Detected`;
      const body = `Alert Triggered: ${ticker} is currently trading at ${currentPrice}, ` +
                   `dropping below your target threshold of ${targetPrice}.\n\n` +
                   `Spreadsheet source: ${SpreadsheetApp.getActiveSpreadsheet().getUrl()}`;
      
      // Dispatch alert via Gmail infrastructure
      MailApp.sendEmail(emailAddress, subject, body);
      
      // Toggle the state flag inside column D to prevent notification loops
      sheet.getRange(index + 2, 4).setValue("YES"); 
    }
  });
}

Step 3: Establish the Cron Trigger Topology

To run this check completely headless without requiring the spreadsheet UI to remain open, configure a time-driven event trigger within Google infrastructure:

  1. Inside the Apps Script interface, click on the Triggers icon (represented by an alarm clock on the left sidebar).
  2. Click the blue + Add Trigger button in the bottom right corner.
  3. Configure the parameters as follows:
  • Choose which function to run: checkStockPrices
  • Choose which deployment should run: Head
  • Select event source: Time-driven
  • Select type of time-based trigger: Minutes timer
  • Select minute interval: Every 5 minutes (or daily/hourly depending on your preferences)
  1. Click Save.

Step 4: Navigate the Security Consent Boundary

Upon saving your initial trigger settings, Google will execute an authorization flow. Because your account is both the author and consumer of this custom binary, Google flags this code as an unverified script.

Security Verification Sandbox: When the warning modal displays “Google hasn’t verified this app”, click Advanced down at the bottom left. Select the hidden link labeled Go to [Your Script Name] (unsafe). On the succeeding summary modal, select Allow to grant your macro permission to read your spreadsheet data and dispatch emails on your behalf.

Operational Mechanics & Limitations

  • Data Latency: Tickers queried via =GOOGLEFINANCE() are subject to historical reporting cache lag of up to 20 minutes depending on exchange protocols. Do not deploy this macro framework for active algorithmic day trading or high-frequency options plays.
  • State Resets: Once an alert triggers and logs a YES flag in Column D, it is locked out from issuing subsequent notifications. To clear out expired signals automatically at market open every day, you can append a second maintenance script bound to a Daily Timer trigger set to clear column D content programmatically.
  • Runtime Limits: Standard Google workspace users enjoy 100 free email dispatches per day via MailApp, running for a combined maximum execution window of 90 minutes across all concurrent scripts—vastly out-scaling personal monitoring requirements.

Optional Improvement

The original code will save the Google Sheet as it updates every cell. This is probably going to be fine for most users but if you have hundreds of rows that need to be updated, its better to batch the updates in 1 call. You can do it using below script:

function checkStockPrices() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  const lastRow = sheet.getLastRow();
  
  // Guard clause against empty sheets
  if (lastRow < 2) return;
  
  // Fetch columns A through D dynamically
  const dataRange = sheet.getRange("A2:D" + lastRow);
  const data = dataRange.getValues();
  const emailAddress = Session.getActiveUser().getEmail(); 
  let updatesMade = false;
  
  data.forEach((row, index) => {
    const ticker = row[0];
    const targetPrice = row[1];
    const currentPrice = row[2];
    const emailSentFlag = row[3];

    // Evaluate conditions for dropping below floor target
    if (ticker && currentPrice <= targetPrice && emailSentFlag !== "YES") {
      const subject = `[Market Alert] ${ticker} Drop Detected`;
      const body = `Alert Triggered: ${ticker} is currently trading at ${currentPrice}, ` +
                   `dropping below your target threshold of ${targetPrice}.\n\n` +
                   `Spreadsheet source: ${SpreadsheetApp.getActiveSpreadsheet().getUrl()}`;
      
      // Dispatch alert via Gmail infrastructure
      MailApp.sendEmail(emailAddress, subject, body);
      
      // Toggle the state flag inside column D to prevent notification loops
      row[3] = "YES"
      updatesMade = true;
    }
  });

  // Write all changes back to the sheet in one go
  if (updatesMade) {
    dataRange.setValues(data);
  }
}

Let me know what you think

This entry was posted in Money. Bookmark the permalink.

Leave a Reply