Dilution Risk Monitor

Build a watchlist dashboard that surfaces dilution warning signs across your portfolio in real time.

A dilution risk monitor tracks a set of tickers for signals that a company may be about to raise capital — shelf registrations going effective, cash runway shrinking, Nasdaq compliance issues, or a pattern of frequent offerings. Instead of manually checking SEC filings, you pull structured data from the API and surface alerts when something changes.

This example walks through the data model, the endpoints you'd use, and working code to get started.

What you're building

A dashboard that, for each ticker in your watchlist:

  • Shows the overall dilution risk rating and what's driving it
  • Flags active shelf registrations and how much capacity remains
  • Tracks Nasdaq compliance deficiencies
  • Monitors cash burn and estimates when the company runs out of money
  • Alerts you when any of these signals change

The data sources

You'll pull from four endpoints to build a complete picture:

EndpointWhat it gives you
/v1/dilution-ratingOverall risk score, offering ability, cash burn, cash runway, Reg SHO status
/v1/registrationsActive shelf registrations, ATM programs, raisable amounts, expiration dates
/v1/nasdaq-complianceCompliance deficiencies, risk level, status updates
/v1/float-outstandingCurrent float, outstanding shares, market cap, insider/institutional ownership

Step 1: Pull the dilution rating

The dilution rating endpoint is the starting point — it's a composite score across multiple risk dimensions.

import requests

API_KEY = "your_api_key"
BASE_URL = "https://api.askedgar.com"
HEADERS = {"API-KEY": API_KEY}

def get_dilution_rating(ticker):
    resp = requests.get(
        f"{BASE_URL}/v1/dilution-rating",
        params={"ticker": ticker},
        headers=HEADERS,
    )
    resp.raise_for_status()
    data = resp.json()

    if not data["results"]:
        return None

    return data["results"][0]

The response includes individual scores and their descriptions:

{
  "ticker": "MULN",
  "offering_ability": "High",
  "offering_ability_desc": "Has Shelf Registration",
  "dilution": "High",
  "dilution_desc": "75.2%",
  "offering_frequency": "High",
  "offering_frequency_desc": "Count of offering types in last 2 yrs: Offerings: 3, ...",
  "cash_need": "High",
  "cash_need_desc": "The company has 3.5 months of cash left ...",
  "nasdaq_compliance": "High",
  "nasdaq_compliance_desc": "Non-compliant with minimum bid price",
  "overall_offering_risk": "High",
  "estimated_cash": 14000000.0,
  "cash_burn": 4000000.0,
  "cash_remaining_months": 3.5,
  "regsho": false
}

The _desc fields are human-readable explanations — useful for surfacing in a UI without having to interpret the ratings yourself.

Step 2: Check active registrations

A company with an effective shelf registration can raise capital at any time. Knowing what's on file and how much capacity remains is critical.

def get_active_registrations(ticker):
    resp = requests.get(
        f"{BASE_URL}/v1/registrations",
        params={"ticker": ticker, "effective_status": True, "limit": 50},
        headers=HEADERS,
    )
    resp.raise_for_status()
    return resp.json()["results"]

Key fields to surface in your dashboard:

  • offering_amount — total size of the registration
  • amount_remaining_atm — how much is left to sell on an ATM program
  • expiration_date — when the shelf expires
  • is_atm — whether this is an at-the-market program (can sell shares continuously)
  • over_baby_shelf — whether the company exceeds the baby shelf threshold (limits how much they can raise)

Step 3: Check Nasdaq compliance

Companies facing Nasdaq delisting pressure are more likely to dilute — they may need to reverse split or raise capital to maintain compliance.

def get_compliance_issues(ticker):
    resp = requests.get(
        f"{BASE_URL}/v1/nasdaq-compliance",
        params={"ticker": ticker, "limit": 50},
        headers=HEADERS,
    )
    resp.raise_for_status()
    return resp.json()["results"]

Watch for the deficiency field (e.g., minimum bid price, stockholders' equity) and the status field to track whether the issue is active or resolved.

Step 4: Get float and ownership context

Float size and ownership breakdown help you gauge how much impact a potential offering would have on the stock.

def get_float_data(ticker):
    resp = requests.get(
        f"{BASE_URL}/v1/float-outstanding",
        params={"ticker": ticker},
        headers=HEADERS,
    )
    resp.raise_for_status()
    data = resp.json()

    if not data["results"]:
        return None

    return data["results"][0]

A company with a 2M share float and a 10M share shelf registration is in a very different situation than one with a 500M share float and the same shelf.

Putting it together

Here's a complete function that builds a risk snapshot for a single ticker:

def build_risk_snapshot(ticker):
    rating = get_dilution_rating(ticker)
    registrations = get_active_registrations(ticker)
    compliance = get_compliance_issues(ticker)
    float_data = get_float_data(ticker)

    if not rating:
        return {"ticker": ticker, "error": "No dilution rating available"}

    # Calculate shelf capacity relative to float
    total_shelf_remaining = sum(
        r.get("amount_remaining_atm", 0) or 0
        for r in registrations
        if r.get("is_atm")
    )

    current_float = float_data["float"] if float_data else None

    shelf_to_float_ratio = None
    if current_float and current_float > 0 and total_shelf_remaining > 0:
        shelf_to_float_ratio = total_shelf_remaining / current_float

    return {
        "ticker": ticker,
        "overall_risk": rating["overall_offering_risk"],
        "cash_remaining_months": rating.get("cash_remaining_months"),
        "cash_burn_quarterly": rating.get("cash_burn"),
        "estimated_cash": rating.get("estimated_cash"),
        "offering_ability": rating["offering_ability"],
        "regsho": rating["regsho"],
        "active_registrations": len(registrations),
        "atm_programs": len([r for r in registrations if r.get("is_atm")]),
        "total_atm_remaining": total_shelf_remaining,
        "shelf_to_float_ratio": shelf_to_float_ratio,
        "compliance_issues": len(compliance),
        "deficiencies": [c["deficiency"] for c in compliance if c.get("deficiency")],
        "float": current_float,
        "outstanding": float_data["outstanding"] if float_data else None,
        "market_cap": float_data.get("market_cap_final") if float_data else None,
    }

Running it across a watchlist

watchlist = ["MULN", "FFIE", "NVAX", "SNDL", "MARA"]

snapshots = []
for ticker in watchlist:
    snapshot = build_risk_snapshot(ticker)
    snapshots.append(snapshot)
    print(f"{ticker}: {snapshot['overall_risk']} risk, "
          f"{snapshot.get('cash_remaining_months', '?')} months cash, "
          f"{snapshot['active_registrations']} active registrations")

Alert ideas

Once you're polling this data on a schedule, here are some conditions worth alerting on:

  • Cash runway drops below 6 months — the company will likely need to raise capital soon
  • New shelf registration goes effective — they now have the ability to sell shares
  • Overall risk changes from Medium to High — composite signal that multiple factors are worsening
  • Nasdaq compliance deficiency added — may force a reverse split or capital raise
  • ATM remaining capacity is large relative to float — potential for significant dilution if exercised
  • Reg SHO status changes — may indicate elevated short selling activity

Cost considerations

Each call to the four endpoints costs based on data volume returned. For a 20-ticker watchlist refreshed every 15 minutes during market hours (roughly 26 refreshes/day), you'd make about 2,080 API calls per day. Use the cost estimate endpoint to project your daily spend before going live:

curl "https://api.askedgar.com/estimate?endpoint=dilution-rating&ticker_count=20"

Next steps