Python Beginner

Proposed Sale Tracker

Monitor Form 144 filings to spot planned insider sales before they happen.

Note: The code examples in this tutorial have not yet been verified against the live API. If you encounter issues, please let us know.

What You'll Build

A command-line Python script that monitors SEC Form 144 filings to spot planned insider sales before they happen. Form 144 is the "Notice of Proposed Sale" that corporate insiders must file with the SEC when they intend to sell restricted or control securities. Unlike Form 4, which reports trades that have already occurred, Form 144 gives you advance notice -- the insider is declaring their intention to sell, but hasn't done it yet.

The script will:

  • Fetch recent proposed sale filings from the stockdata.dev API
  • Display a formatted table with filing date, ticker, person, title, shares, estimated value, approximate sale date, and exchange
  • Sort results by estimated value descending to surface the largest planned sales
  • Support filtering by ticker or time range

Prerequisites

  • Python 3.7 or newer
  • The requests library (pip install requests)
  • A stockdata.dev API key (get one free)

The Code

Create a file called proposed_sales.py and paste the following:

Python
import argparse
import requests

API_KEY = "your_api_key_here"
BASE_URL = "https://api.stockdata.dev/v1"


def fetch_proposed_sales(ticker=None, days=7):
    """Fetch proposed sale filings from the API."""
    headers = {"X-API-Key": API_KEY}
    url = f"{BASE_URL}/proposed-sales"
    params = {"days": days}
    if ticker:
        params["ticker"] = ticker.upper()

    resp = requests.get(url, headers=headers, params=params)
    resp.raise_for_status()
    return resp.json()["filings"]


def format_value(value):
    """Format a dollar value with commas."""
    if value >= 1_000_000:
        return f"${value / 1_000_000:.1f}M"
    if value >= 1_000:
        return f"${value:,.0f}"
    return f"${value:,.2f}"


def print_sales(filings, title):
    """Print proposed sales as a formatted table."""
    if not filings:
        print("No proposed sales found.")
        return

    # Sort by estimated value descending
    filings.sort(key=lambda f: f.get("aggregate_market_value", 0), reverse=True)

    print(f"\n{title}")
    print("=" * 110)
    print(
        f"{'Filed':12}"
        f"{'Ticker':8}"
        f"{'Person':22}"
        f"{'Title':18}"
        f"{'Shares':>10}"
        f"{'Est. Value':>14}"
        f"{'Sale Date':>12}"
        f"{'Exchange':>10}"
    )
    print("─" * 110)

    for f in filings:
        person = f["reporting_person"][:21]
        title_str = f.get("title", "")[:17]
        shares = f"{f['shares_to_sell']:>,.0f}"
        value = format_value(f.get("aggregate_market_value", 0))
        sale_date = f.get("approximate_sale_date", "")
        exchange = f.get("exchange", "")

        print(
            f"{f['filing_date']:12}"
            f"{f['ticker']:8}"
            f"{person:22}"
            f"{title_str:18}"
            f"{shares:>10}"
            f"{value:>14}"
            f"{sale_date:>12}"
            f"{exchange:>10}"
        )

    print(f"\nTotal: {len(filings)} proposed sales")


def main():
    parser = argparse.ArgumentParser(description="Track proposed insider sales (Form 144)")
    parser.add_argument("--ticker", help="Filter to a specific company")
    parser.add_argument("--days", type=int, default=7,
                        help="Look back N days (default: 7)")
    args = parser.parse_args()

    filings = fetch_proposed_sales(ticker=args.ticker, days=args.days)

    if args.ticker:
        title = f"Proposed Sales for {args.ticker.upper()} (last {args.days} days)"
    else:
        title = f"Proposed Insider Sales (last {args.days} days)"

    print_sales(filings, title)


if __name__ == "__main__":
    main()

Replace "your_api_key_here" with your actual API key. For production scripts, consider using an environment variable instead of hardcoding the key.

How It Works

When a corporate insider plans to sell restricted or control securities, they must file Form 144 with the SEC. This filing is a notice of intent -- it tells the market that a sale is planned but has not yet happened. The insider has up to 90 days from the filing date to complete the sale, and they are not obligated to sell at all. Think of it as a leading indicator: you see the intention before the action.

Form 144 filings contain several useful fields:

  • Reporting person and title -- Who is planning to sell, and what is their role at the company (CEO, Director, VP, etc.). Sales by C-suite executives carry more weight than sales by former employees exercising old options.
  • Shares to sell -- The number of shares the insider intends to sell. Compare this to the company's average daily volume to gauge the potential market impact.
  • Aggregate market value -- The estimated dollar value of the planned sale based on recent market prices. This is the best single number for ranking the significance of a filing.
  • Approximate sale date -- When the insider expects to execute the sale. This can be the filing date itself or a future date.
  • Broker -- The brokerage firm handling the sale. Useful for tracking patterns in how insiders route their trades.

Form 144 is often filed on the same day the insider plans to sell, so the "advance notice" window can be very short. However, some filings are made days or weeks ahead, giving you time to research the company before the sale happens. The approximate_sale_date field tells you when to expect the trade.

The key difference between Form 144 and Form 4:

  • Form 144 -- Filed before the sale. A declaration of intent. The sale may or may not happen.
  • Form 4 -- Filed after the trade. A report of what actually occurred. See the Insider Trade Scanner tutorial for working with Form 4 data.

By monitoring both forms, you can see the full lifecycle of an insider sale: the planned sale via Form 144, then the completed trade via Form 4.

Running It

Scan for all recent proposed sales:

Shell
python proposed_sales.py

Example output:

Output
Proposed Insider Sales (last 7 days)
==============================================================================================================
Filed       Ticker  Person                Title             Shares    Est. Value   Sale Date  Exchange
──────────────────────────────────────────────────────────────────────────────────────────────────────────────
2026-02-18  ORCL    Ellison Lawrence J    Chairman          500,000      $85.2M  2026-02-20      NYSE
2026-02-17  CRM     Benioff Marc          CEO               200,000      $52.4M  2026-02-19    NASDAQ
2026-02-16  NFLX    Sarandos Ted          Co-CEO             50,000      $47.1M  2026-02-18    NASDAQ
2026-02-15  NOW     McDermott William     President          80,000      $38.9M  2026-02-17      NYSE
2026-02-18  SNSE    Peyer James           Former Director       950     $25,769  2026-02-18    NASDAQ

Total: 5 proposed sales

Filter to a specific company:

Shell
python proposed_sales.py --ticker ORCL --days 30

Example output:

Output
Proposed Sales for ORCL (last 30 days)
==============================================================================================================
Filed       Ticker  Person                Title             Shares    Est. Value   Sale Date  Exchange
──────────────────────────────────────────────────────────────────────────────────────────────────────────────
2026-02-18  ORCL    Ellison Lawrence J    Chairman          500,000      $85.2M  2026-02-20      NYSE
2026-02-03  ORCL    Catz Safra A          CEO               150,000      $25.6M  2026-02-05      NYSE
2026-01-22  ORCL    Henley Jeffrey O      EVP                30,000       $5.1M  2026-01-24      NYSE

Total: 3 proposed sales

Look further back to spot patterns:

Shell
python proposed_sales.py --days 30

Enhancements

Here are some ways to extend the proposed sale tracker:

  • Cross-reference with Form 4 -- After a Form 144 is filed, check whether the corresponding Form 4 sale actually happened. Use the Insider Trade Scanner to look up completed trades by the same person and ticker. If the insider filed Form 144 but never sold, that could be a signal they changed their mind about the stock.
  • Volume impact analysis -- Compare the planned share count against the stock's average daily volume to estimate market impact. A proposed sale of 500,000 shares in a stock that trades 2 million shares daily is significant; the same sale in a stock that trades 50 million shares daily is noise.
  • Save to CSV -- Add a --csv output.csv flag to export proposed sales for analysis in Excel or Google Sheets. Use Python's built-in csv module.
  • Email alerts -- Run the script on a schedule with a cron job and send an email when new large proposed sales appear. See the SEC Filing Alert Email tutorial for a notification pattern you can adapt.
  • Filter by value -- Add a --min-value flag to only show proposed sales above a certain dollar threshold. This helps filter out small, routine sales and focus on the ones that matter.
  • Amendment tracking -- The API response includes an is_amendment field. Amended filings mean the insider changed the terms of a previously filed Form 144. Track amendments to see if insiders are increasing or decreasing their planned sales.

A Form 144 filing does not guarantee a sale will happen. Insiders can change their plans. Always cross-reference with actual Form 4 trade reports and do your own research before making investment decisions.

Ready to build?

Get your free API key and start coding in minutes.

Get Free API Key