ChatGPT Plugins: Building a Stock Screener Assistant

In this guide, we'll walk through how to build a ChatGPT Plugin stock screener assistant using the Financial Modeling Prep API.

a year ago   •   6 min read

By Peter Foy

In our previous ChatGPT Plugins tutorials, we've covered how to setup a simple to-do list and build an AI news assistant.

In this guide, we'll build a new ChatGPT Plugin that acts as a stock screener assistant, meaning it retrieves and filters stocks based on the user's query.

To do so, we'll use the Financial Modeling Prep API, specifically the Stock Screener endpoint, and we'll host the code at Replit. To do so, the three main files we'll need to build this plugin include:

  • A main.py file to serve the API's functionality
  • A plugin manifest ai-plugin.json to serve the metadata
  • An openapi.yaml file to document the API for ChatGPT

For example, with this plugin I can ask for things like...

Show me tech companies with a market cap above $1b and dividends above $2/share...

As you can see, the stock screen allows user's to specify their criteria such as:

  • Market cap
  • Sector & industry
  • Dividends
  • Beta, and so on

Alright, now that we know what we're building, let's get started with building the API functionality.

Become a Prompt Engineer: Prompt Engineering & LLMs Track
In this Prompt Engineering track, you’ll learn essential skills, tools, and techniques for working with LLM-enabled applications.

Step 1: Building the API functionality

To start, we'll create a new Python Repl and create a main.py file to serve our API's functionality and the other files we'll need.

Imports and constants

First, we'll import the necessary packages, initialize our Flask app, and define two API constants for the Financial Modeling Prep API:

import os
import logging
from logging import StreamHandler
from waitress import serve
from flask import Flask, request, jsonify, send_from_directory
import requests

app = Flask(__name__)

FMP_API_KEY = "your-api-key"
FMP_API_URL = "https://financialmodelingprep.com/api/v3"

Fetch stock screener data

Next, we'll define a stock_screener function that takes in the user-defined query parameters and sends a request to Financial Modeling Prep:

def stock_screener(**params):
  url = f"{FMP_API_URL}/stock-screener"
  params["apikey"] = FMP_API_KEY
  response = requests.get(url, params=params)
  if response.status_code == 200:
    return response.json()
  else:
    raise Exception(f"Error: {response.status_code}, {response.text}")

API route for fetching stock screener data

Next, we'll create an API route for handling incoming user-defined query parameters, calling the stock_screener function we just created, and returning the result in JSON format:

@app.route('/stock_screener', methods=['GET'])
def fetch_stock_screener():
  app.logger.info("Fetching Stock Screener")
  query_params = request.args.to_dict()
  screener_data = stock_screener(**query_params)
  return jsonify(screener_data)

Serving plugin files

Now that we've got our Financial Modeling Prep API functions in place, we need three more routes to serve the required ChatGPT plugin files, including the ai-plugin.json, the openapi.yaml, and the plugin logo:

@app.route('/.well-known/ai-plugin.json', methods=['GET'])
def send_ai_plugin_json():
  return send_from_directory('.well-known', 'ai-plugin.json')

Serve openapi.yaml:

Next, we need a route for serving the openapi.yaml file, which is used by ChatGPT to understand what the plugin can do and how to interact with the API endpoints:

@app.route('/openapi.yaml', methods=['GET'])
def send_openapi_yaml():
  return send_from_directory('.', 'openapi.yaml')

Serve plugin logo

Lastly, we'll create a route for serving the plugin logo:

@app.route('/logo.png', methods=['GET'])
def send_logo():
  return send_from_directory('.', 'logo.png')

With these API functions and routes in place, our Flask app is now setup to handle incoming requests from ChatGPT and serve the necessary files.

if __name__ == "__main__":
  app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 8080))

Step 2: Creating the plugin manifest

Next, let's create the plugin manifest file, which:

...includes metadata about your plugin (name, logo, etc.), details about authentication required (type of auth, OAuth URLs, etc.), and an OpenAPI spec for the endpoints you want to expose.

For this plugin, we'll include the following information in our manifest:

  • Name & description for both humans and the model
  • User authentication, for this example we'll just set it to none
  • API configuration, which includes the OpenAPI URL that you'll need to update with your Replit URL
  • Logo URL and contact information
{
  "schema_version": "v1",
  "name_for_human": "Stock Screener Assistant",
  "name_for_model": "stockScreener",
  "description_for_human": "Plugin for fetching stocks based on various criteria like market capitalization, price, beta, volume, dividend, etc.",
  "description_for_model": "Plugin for fetching stocks based on various criteria like market capitalization, price, beta, volume, dividend, etc. Help users find stocks that meet their requirements.",
  "auth": {
    "type": "none"
  },
  "api": {
    "type": "openapi",
    "url": "https://your-repl-url.repl.co/openapi.yaml",
    "is_user_authenticated": false
  },
  "logo_url": "https://your-repl-url.repl.co/logo.png",
  "contact_email": "support@example.com",
  "legal_info_url": "http://www.example.com/legal"
}

As mentioned, you'll just need to update your-repl-url wiht the actual URL of your Repl. After creating the ai-plugin.json file, you'll also need to save it in the .well-known folder for ChatGPT to use it.

With our plugin manifest file in place, the last thing we need is to document our API with OpenAPI.

Step 3: Documenting the API with OpenAPI

In this step, we need to create an OpenAPI specification, which as OpenAI highlights:

The model will see the OpenAPI description fields, which can be used to provide a natural language description for the different fields.

In this case, all you'll need to update is the server URL here: https://your-repl-url.username.repl.co.

The OpenAPI specification then defines the /stock_screener endpoint and the parameters that user's can customize, including market cap, price, beta, dividends, and so on. Lastly, the components section defines the schema for JSON responses:

openapi: "3.0.2"
info:
  title: "Stock Screener Assistant API"
  version: "1.0.0"
servers:
  - url: "https://stockscreenerplugin.peterfoy2.repl.co"
paths:
  /stock_screener:
    get:
      operationId: fetchStockScreener
      summary: "Fetch Stock Screener"
      parameters:
        - in: query
          name: marketCapMoreThan
          schema:
            type: number
          required: false
          description: "Market capitalization greater than the specified value."
        - in: query
          name: marketCapLowerThan
          schema:
            type: number
          required: false
          description: "Market capitalization lower than the specified value."
        - in: query
          name: priceMoreThan
          schema:
            type: number
          required: false
          description: "Price greater than the specified value."
        - in: query
          name: priceLowerThan
          schema:
            type: number
          required: false
          description: "Price lower than the specified value."
        - in: query
          name: betaMoreThan
          schema:
            type: number
          required: false
          description: "Beta greater than the specified value."
        - in: query
          name: betaLowerThan
          schema:
            type: number
          required: false
          description: "Beta lower than the specified value."
        - in: query
          name: volumeMoreThan
          schema:
            type: number
          required: false
          description: "Volume greater than the specified value."
        - in: query
          name: volumeLowerThan
          schema:
            type: number
          required: false
          description: "Volume lower than the specified value."
        - in: query
          name: dividendMoreThan
          schema:
            type: number
          required: false
          description: "Dividend greater than the specified value."
        - in: query
          name: dividendLowerThan
          schema:
            type: number
          required: false
          description: "Dividend lower than the specified value."
        - in: query
          name: isEtf
          schema:
            type: boolean
          required: false
          description: "Is ETF (true/false)."
        - in: query
          name: isActivelyTrading
          schema:
            type: boolean
          required: false
          description: "Is actively trading (true/false)."
        - in: query
          name: sector
          schema:
            type: string
          required: false
          description: "Sector (e.g., Technology, Healthcare, etc.)."
        - in: query
          name: industry
          schema:
            type: string
          required: false
          description: "Industry (e.g., Software, Banks, etc.)."
        - in: query
          name: country
          schema:
            type: string
          required: false
          description: "Country (e.g., US, UK, CA, etc.)."
        - in: query
          name: exchange
          schema:
            type: string
          required: false
          description: "Exchange (e.g., NYSE, NASDAQ, etc.)."
        - in: query
          name: limit
          schema:
            type: integer
          required: false
          description: "Limit the number of results."
      responses:
        "200":
          description: "A list of stocks matching the given criteria."
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    symbol:
                      type: string
                    companyName:
                      type: string
                    marketCap:
                      type: number
                    sector:
                      type: string
                    beta:
                      type: number
                    price:
                      type: number
                    lastAnnualDividend:
                      type: number
                    volume:
                      type: number
                    exchange:
                      type: string
                    exchangeShortName:
                      type: string

Step 4: Deploying and testing the ChatGPT plugin

With these three files created, let's now go and deploy and test the new ChatGPT plugin. Here's what we need to do:

  1. After you've added all the necessary files to Replit, click "Run" to start the server and make the plugin available
  2. Head to ChatGPT and navigate to the Plugin store
  3. Select "Develop your own plugin" and enter your Replit URL

After installing and enabling the new plugin, you can test it out and specify your stock search criteria, and I can also confirm the market cap of Apple is correct at the time of writing:

Summary: ChatGPT Stock Screener Plugin

In this guide, we saw how to build a simple stock screener ChatGPT Plugin using the Financial Modeling Prep API. To recap, we:

  • Created a Flask app with the necessary enpoints in the main.py file
  • Defined our plugin's metadeta in the ai-plugin.json manifest file
  • Documented the API using OpenAPI, outlining the /stock_screener endpoint, parameters, and response schemas
  • Hosted the project at Replit and deployed it for testing in the Plugin store

There are still a few more step's we'd want to include to take a plugin like this to production, such as rate limiting and user authentication, which we'll cover in future tutorials.



Spread the word

Keep reading