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.
| 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 |
Stock Radar can run two ways:
- Agentic (recommended; showcases what OpenClaw does). The gateway connects to
mcp/server.pyand the agent orchestrates the tools: it screens the watchlist (screen_universe), recognizes what's interesting, usesweb_searchto 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 agentproduces 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.
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/- Python 3.10+ (developed and tested on Python 3.14)
- Network access (live data from Yahoo Finance)
lxml,html5lib, andrequests— required for--universe sp500live fetch (all are installed bypip install -r requirements.txt)
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.
Use --streaks to read persisted history and print any symbols flagged on
consecutive scan dates (no scan is run):
python volume_scan.py --streaks| 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) |
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-runA 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.
| 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).
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 watchlistresolves symbols in this order:--watchlistCLI →config.json→ built-in defaults.whatsapp.e164is 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.pyThe 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.
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.0means 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.
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
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.
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(notautomations);openclaw agentneeds--agent main; exec is configured withtools.exec.security/ask/safeBins(the oldtools.exec.allowlistpattern rules are rejected by the schema).
- 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):
Non-interactive alternative (skips the wizard):
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
openclaw onboard --non-interactive --accept-risk --auth-choice ollama \ --skip-channels --skip-search --skip-hooks openclaw gateway install && openclaw gateway start openclaw models status - 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
- Connect a channel (Telegram is fastest):
openclaw onboarding - 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
- (Optional) pre-approve exec + add a standing order — merge
openclaw/openclaw.jsoninto~/.openclaw/openclaw.json(openclaw gateway restart), and add the "Daily volume monitor" program fromintegration.mdto your agent workspaceAGENTS.md. This OpenClaw version usestools.exec.security/ask/safeBins; the oldtools.exec.allowlistpattern 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.
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| 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. |
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
============================================================
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
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.
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"])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.
