Skip to content

Repository files navigation

Stock Radar

A daily stock monitor for extreme volume increases. It scans a watchlist or the whole S&P 500, flags any symbol whose volume is an outlier versus its own recent baseline, generates a full technical + fundamental report with an interactive HTML chart for each flag, and — via the self-hosted OpenClaw gateway — can wake itself up on a schedule and post a plain-English summary to your chat (Telegram, Discord, WhatsApp, Slack, iMessage…).

Data is fetched live from Yahoo Finance via yfinance.

Workflow

Stock Radar Workflow

What it ships with

Piece Role
market/ The deterministic engine (package) — scoring.py (z-score, RVOL, setups, scan), universe.py (watchlist / S&P 500 resolution + config). No AI, no OpenClaw — fully testable.
mcp/server.py MCP tool server — exposes the engine as tools (screen_universe, get_snapshot, get_support_resistance, get_fundamentals, get_recent_flags, write_memory) so an agent can orchestrate the daily brief instead of running a fixed script. See mcp/README.md.
volume_scan.py Thin CLI (compat + fallback) — scan a watchlist or the S&P 500, generate reports, print a markdown or agent-format digest.
analysis.py The analysis engine — support/resistance, moving averages, EPS/valuation projections, and the HTML chart + report builder the scanner calls
openclaw/ Agent skill + setup guide. Two modes: agentic (gateway → MCP tools → screen/explain-why/deep-dive/remember) or CLI fallback (cron → volume_scan.py --format agent)
fib_risk_reward.py, backtest_2560.py Optional helper scripts for Fibonacci risk/reward and strategy backtests
OLLAMA.md Optional: run OpenClaw 100% self-hosted with a local Ollama model

Agentic monitoring with OpenClaw (MCP)

Stock Radar can run two ways:

  • Agentic (recommended; showcases what OpenClaw does). The gateway connects to mcp/server.py and the agent orchestrates the tools: it screens the watchlist (screen_universe), recognizes what's interesting, uses web_search to explain why a stock moved, deep-dives (get_support_resistance, get_fundamentals), and remembers across days (get_recent_flags, write_memory). The Python math stays deterministic; the agent supplies the judgment — that's the point.
  • CLI fallback. python volume_scan.py --format agent produces the digest/reports without any agent.
# Register the MCP server with OpenClaw, then ask the agent to run the daily brief:
openclaw mcp add stock-radar --command \
  "~/github.com/simagix/stock-radar/venv/bin/python ~/github.com/simagix/stock-radar/mcp/server.py"
openclaw agent --agent main --message "screen the watchlist for volume spikes, explain why, and dig into the top mover"

See openclaw/ for the skill + full setup, and mcp/README.md for the MCP server.

Quick start

git clone https://github.com/simagix/stock-radar.git
cd stock-radar

python -m venv venv            # use python3.14 if it's not your default
source venv/bin/activate       # Windows: venv\Scripts\activate
pip install -r requirements.txt

# Run the daily monitor right now (watchlist, HTML reports for the flags)
python volume_scan.py --universe watchlist --top 5 --html out/

Requirements

  • Python 3.10+ (developed and tested on Python 3.14)
  • Network access (live data from Yahoo Finance)
  • lxml, html5lib, and requests — required for --universe sp500 live fetch (all are installed by pip install -r requirements.txt)

MongoDB persistence

Scans are persisted automatically when a MongoDB URI is configured. Add a "mongo" block to config.json (git-ignored; template in config.example.json):

{
  "mongo": {
    "uri": "mongodb+srv://<user>:<password>@<cluster>/",
    "database": "stock_radar"
  }
}

The URI can also come from the MONGODB_URI environment variable. If neither is set, persistence is silently skipped and the scan continues normally.

Streak query

Use --streaks to read persisted history and print any symbols flagged on consecutive scan dates (no scan is run):

python volume_scan.py --streaks

Collections

Collection Purpose
stock_radar.scan_runs One document per execution (run metadata, counts, errors, duration)
stock_radar.volume_scans One document per symbol per scan date, upserted on {date, symbol}
stock_radar.sp500_cache Cached S&P 500 ticker list (single document, auto-refreshed from Wikipedia)

Scanning (the main command) — volume_scan.py

Scans a watchlist or the S&P 500 and reports today's extreme volume movers.

# Default watchlist → HTML reports for the top 5 flags
python volume_scan.py --universe watchlist --top 5 --html out/

# Whole S&P 500 (first run fetches the constituents and caches them in MongoDB + disk)
python volume_scan.py --universe sp500 --top 5

# S&P 500 from your own list (avoids the live fetch)
python volume_scan.py --universe sp500 --sp500-file sp500.txt --top 5

# Your own list; scan only, no reports
python volume_scan.py --watchlist NVDA AMD MU --dry-run

A symbol is flagged when its z-score ≥ --z-threshold (default 2.5) against its trailing 30-day baseline — i.e. volume that is an outlier for that stock, not just "big" in absolute terms — or when it shows a recognized volume-price setup (an RVOL ≥ 2.0 breakout/breakdown/absorption, or an RVOL ≥ 4.0 climax; see the next section). This means a clean ~2–3×-volume breakout on a low-variance stock surfaces even if the z-score sits below the tunable threshold. Tune the statistical bar with --lookback and --z-threshold; the RVOL/setup cutoffs (2.0 / 4.0) are fixed.

Scan options

Option Description
--universe watchlist|sp500|tickers Which set of symbols to scan (default: watchlist)
--watchlist SYMBOL... Override the watchlist (also required for --universe tickers)
--config FILE Path to config.json (watchlist + WhatsApp E.164; default: ./config.json)
--sp500-file FILE Read the S&P 500 list from a local file (one per line or CSV)
--lookback DAYS Trailing volume baseline window (default: 30)
--z-threshold Z Minimum z-score to flag a spike (default: 2.5)
--top N Number of flags to generate full reports for (default: 5)
--all Generate reports for every flagged symbol
--html DIR HTML report output directory (default: out/)
--dry-run Scan only; don't generate reports
--format markdown|agent Output style: markdown (default table digest) or agent (conversational brief for OpenClaw chat delivery)
--delay SEC Pause between Yahoo Finance calls (default: 0.2, keeps Yahoo happy)

S&P 500 caching: When MongoDB is configured, the S&P 500 ticker list is cached in the sp500_cache collection (primary) with a disk fallback to .cache/sp500.json. The cache is validated to contain at least 400 tickers and is refreshed from Wikipedia when missing or invalid. Use --refresh to force a fresh fetch.

For each flagged symbol the scanner calls analysis.print_analysis(...) to render the same dark-mode HTML report + chart as the single-symbol tool, and prints a compact markdown digest (symbol, z-score, volume ratio, RVOL, setup, price, 1-day % change).

Local config (git-ignored)

Your personal watchlist and the WhatsApp delivery number live in config.json (repo root) — the file is git-ignored so phone numbers and symbol lists stay off GitHub; config.example.json is the committed template.

{
  "whatsapp": { "e164": "+14155550123" },
  "watchlist": ["NVDA", "GOOGL", "MU"]
}
  • --universe watchlist resolves symbols in this order: --watchlist CLI → config.json → built-in defaults.
  • whatsapp.e164 is the WhatsApp number the OpenClaw cron job delivers the daily brief to. After editing it, re-apply it to the cron job:
./venv/bin/python openclaw/apply_config.py

The apply is fail-safe. With a number present, the cron job is set with --best-effort-deliver, so a failed notification never fails (or retries) the job. With the number missing/blank, the helper instead clears the delivery target — OpenClaw then skips outbound and nothing is sent. Re-add the number and re-run to re-enable delivery.

RVOL & price-setup labels

Each flag is annotated with two extra columns, computed from the same daily OHLCV data the scan already fetched (no extra network calls):

  • RVOL — today's volume ÷ the trailing 20-day simple moving average (prior bar). An RVOL of 3.0 means volume was ~3× the recent average. It's a ×-multiple, not a deviation, so it complements the z-score: a low-variance stock can be very active in RVOL terms while its z-score stays below --z-threshold.
  • Setup — what the spike may be signaling for price:
    • ↗ breakout — close above the 20-day high on elevated volume (RVOL ≥ 2.0)
    • ↘ breakdown — close below the 20-day low on elevated volume
    • ⊘ absorption — high volume (RVOL ≥ 2.0) but a narrow range (≤ 75% of the 20-day avg range)
    • ⚡ climax — major event volume (RVOL ≥ 4.0) with an expanded range (> 1.5× the avg range)
    • — elevated but not a distinct setup

Categories: Normal Variance (RVOL < 1.5) · Worth Watching (1.5–2.0) · Actionable High Volume (2.0–4.0) · Major Volume Event / Climax (≥ 4.0). The z-score threshold still gates the statistical flag; RVOL/setup are context layered on top and also surface sub-threshold spikes via their own 2.0 / 4.0 cutoffs.

Reports

Each report is a self-contained HTML file (open in any browser, no server):

  • Chart — 1-year candlesticks, volume subplot, MA lines, dashed S/R levels (line weight reflects strength), analyst target line
  • Draggable legend — click and drag the S/R legend box
  • Analysis section — technical and fundamental detail, styled for the browser

Agent format (--format agent)

For OpenClaw delivery, use --format agent to get a conversational brief instead of the raw markdown table:

python volume_scan.py --universe watchlist --format agent --top 5 --html out/

Output looks like:

# Morning Volume Brief — 2026-08-09

2 symbols flagged today:

- **NVDA** — z=3.2, ↗ breakout
  Broke above recent range on 4.1x volume — breakout candidate (+2.3%)
  [Full report → out/NVDA.html]

- **MU** — z=2.8, ⚡ climax
  Volume hit 2.4x average with a wide range — possible selling climax (-1.1%)
  [Full report → out/MU.html]

_Data via Yahoo Finance. Not financial advice._

The agent can forward this directly to your chat channel. See the memory protocol in openclaw/memory/scans/README.md for how OpenClaw remembers past scans and calls out streaks.


Delivering results to chat (OpenClaw)

OpenClaw is a self-hosted AI-assistant gateway that connects your chat apps to an agent with MCP tools, web search, and file access. It plays the brain + scheduler + delivery + memory role here: the deterministic market engine is exposed as MCP tools, and the agent orchestrates them — it screens the watchlist, cross-references the news to explain why a stock moved, deep-dives into the interesting movers, saves a memory note, and posts the brief to your channel.

Setup in five steps (full detail in openclaw/integration.md):

OpenClaw 2026.7+: the scheduler is openclaw cron (not automations); openclaw agent needs --agent main; exec is configured with tools.exec.security / ask / safeBins (the old tools.exec.allowlist pattern rules are rejected by the schema).

  1. Install & onboard — pick Ollama at the "Model/auth provider" prompt (the wizard auto-pulls & validates a default Ollama model; see OLLAMA.md to choose your own):
    curl -fsSL https://openclaw.ai/install.sh | bash
    openclaw onboard --install-daemon          # pick "More… → Ollama" (or "Skip for now")
    openclaw gateway status                    # expect: LaunchAgent loaded, probe ok
    Non-interactive alternative (skips the wizard):
    openclaw onboard --non-interactive --accept-risk --auth-choice ollama \
      --skip-channels --skip-search --skip-hooks
    openclaw gateway install && openclaw gateway start
    openclaw models status
  2. Install the skill (teaches the agent how/when to scan)
    mkdir -p ~/.openclaw/workspace/skills/volume-scan
    cp openclaw/SKILL.md ~/.openclaw/workspace/skills/volume-scan/SKILL.md
    openclaw agent --agent main --message "scan for volume spikes today"   # smoke test
  3. Connect a channel (Telegram is fastest): openclaw onboarding
  4. Schedule the daily scan — the Gateway must be running for schedules to fire
       openclaw cron add "Daily volume scan" "Run the volume-scan skill now" \
      --cron "0 21 * * 1-5" --agent main --announce
    openclaw cron list          # inspect jobs; openclaw cron status  # scheduler health
  5. (Optional) pre-approve exec + add a standing order — merge openclaw/openclaw.json into ~/.openclaw/openclaw.json (openclaw gateway restart), and add the "Daily volume monitor" program from integration.md to your agent workspace AGENTS.md. This OpenClaw version uses tools.exec.security / ask / safeBins; the old tools.exec.allowlist pattern rules in the repo fragment are rejected — see integration.md.

Files: openclaw/SKILL.md · openclaw/integration.md · openclaw/openclaw.json. To run the agent fully self-hosted with a local LLM, see OLLAMA.md.

Analysis engine (per-symbol) — analysis.py

Use this when you want a deep dive on a specific symbol rather than a market scan. It combines technical analysis (support/resistance, moving averages, interactive charts) with fundamental projections (analyst consensus EPS, forward P/E, price targets). This is the same engine the monitor calls for every flagged symbol.

python analysis.py MSFT
python analysis.py MSFT AAPL GOOGL
python analysis.py SPY QQQ                      # ETFs: technical + chart only
python analysis.py MSFT --growth 0.15 --pe 30   # manual assumptions
python analysis.py -h

Analysis options

Argument Description
SYMBOL One or more ticker symbols (required)
--growth RATE EPS growth rate override (e.g. 0.15 for 15%). Uses analyst consensus if omitted.
--pe RATIO P/E multiple override (e.g. 30). Uses forward P/E if omitted.
--html [PATH] HTML output directory or file (default: out/SYMBOL.html). --no-html skips it.

Analysis features

Technical

  • Support & resistance — clustered local price extrema from the past year
  • Strength scoring — each level rated strong / intermediate / weak from touch count and volume at tests
  • Moving averages — MA 5, 20, 60, and 240 on the chart and in console output
  • Interactive HTML chart — dark-mode candlesticks, volume, S/R lines, analyst target, draggable legend

Fundamental (equities only)

  • Consensus EPS — Wall Street analyst estimates for current and next fiscal year
  • Forward P/E — market-implied multiple (not a hardcoded assumption)
  • Analyst price target — consensus 12-month target as a cross-check
  • Scenarios table — bull / base / bear cases with upside vs current price

Example output

============================================================
  MSFT  @  $425.00  (Software - Infrastructure)
============================================================

PRICE LEVELS
  --------------------------------------------------------
  Strength from touch count + volume at tests (strong: 4+ touches or 3+ w/ high volume)

MOVING AVERAGES
  --------------------------------------------------------
  MA5                $430.00  (price 1.2% below MA)
  MA20               $415.00  (price 2.4% above MA)
  ...

SUPPORT / RESISTANCE
  --------------------------------------------------------
  Support            $410.00  (3.5% below)  [Strong, 5 touches]
  Resistance         $460.00  (+8.2% above)  [Strong, 4 touches]
  Support zones      $395.00 (Intermediate, 3x), ...
  Resistance zones   $475.00 (Weak, 2x), ...

MULTIPLES
  --------------------------------------------------------
  Trailing P/E       35.2x
  Forward P/E        28.5x
  ...

SCENARIOS
  Scenario                  Price    vs Now  Assumption
  --------------------------------------------------------
  Analyst target         $500.00    +17.6%  12-mo consensus (45 analysts)
  Base (next FY)         $425.00      flat  EPS $14.90 @ 28.5x (12.3%)
  This FY (ref)          $400.00    -5.9%  EPS $14.00 @ 28.5x (10.1%)

WHY BASE ≠ ANALYST TARGET
  --------------------------------------------------------
  Base case uses today's forward P/E on consensus EPS — it will always sit near the current price.
  Analyst path needs ~33.6x P/E (vs 28.5x now) or EPS ~$17.54 (vs $14.90 consensus).

CASES
  --------------------------------------------------------
  Bull               Earnings beat and/or P/E expands → $500.00
  Base               Consensus EPS, same multiple → flat
  Bear               Miss or compression → watch $395.00 (Intermediate, 3x), ...
  Technical          Range $410.00 – $460.00

  Report saved → /path/to/MSFT.html

Understanding the output

Technical analysis

Field Meaning
Current Price Latest closing price
Nearest Support / Resistance Closest scored level below / above the current price
Strength Strong, Intermediate, or Weak — from touch count and volume at level tests
Touches Number of distinct price tests of the level
Moving averages Latest MA values and whether price is above or below each
Support / Resistance zones Additional clustered levels below / above current price

Price projections (equities)

Field Meaning
EPS source analyst consensus (default) or projected from TTM EPS (when --growth is used)
P/E used Forward P/E by default; falls back to trailing P/E, then 20x
Base (next FY) consensus EPS × P/E — a valuation estimate, not a price prediction
Analyst target Independent consensus 12-month price target from Wall Street analysts
WHY BASE ≠ ANALYST TARGET Explains why the next-FY implied value sits near the current price
CASES Bull / base / bear / technical framing for decision context

Note: The next-FY base case will typically sit near the current price because forward P/E is defined as price ÷ forward EPS; the analyst target uses separate models and may differ significantly.

ETFs: for ETFs and other instruments without EPS data, the engine prints technical analysis and generates the chart only.

Programmatic Use

The analysis functions can be imported directly:

from analysis import get_support_resistance, estimate_future_price

tech = get_support_resistance("MSFT")
fund = estimate_future_price("MSFT")

print(tech["nearest_support"]["price"])
print(fund["implied_value_next_fy"])

Disclaimer

This tool is for informational and educational purposes only. It is not financial advice. Stock prices are subject to market risk, and analyst estimates may be inaccurate or outdated. Always do your own research before making investment decisions.

About

Evaluate stock valuation

Resources

Stars

3 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages