docs: add examples/stock_anomalies.py — fetch + scan worked example#64
Conversation
A committed, runnable example of using anomalyx on real data and consuming the tq1 contract. Fetches a ticker's daily history from Yahoo Finance (yfinance), enriches with daily-return% and intraday-range%, runs `anomalyx scan`, parses the dense JSON envelope (dict + dense rows), and maps each finding's handle back to a calendar date. Two modes: single-corpus (point/mv/collective anomalies within a series) and --baseline TICKER (distributional drift of one ticker's behavior vs another, exercising dist.ks/psi). Extra args pass through to `anomalyx scan` (--fdr, --top, --min-severity, …); exit code mirrors anomalyx. Lives outside the Cargo workspace (shells out to the installed binary), so it doesn't touch the build or gates. + examples/README.md and a README pointer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR introduces a new example script ChangesStock Anomalies Example Implementation
Sequence DiagramsequenceDiagram
participant User
participant Example as stock_anomalies.py
participant yfinance
participant anomalyx as anomalyx scan
participant JSON as JSON output
User->>Example: Run with ticker + args
Example->>yfinance: Fetch OHLCV history
yfinance-->>Example: DataFrame with dates
Example->>Example: Enrich with return/range
Example->>anomalyx: Run scan on CSV
anomalyx-->>JSON: Emit tq1 envelope
JSON-->>Example: Parsed findings dict
Example->>Example: Map handles to dates
Example-->>User: Print results + exit code
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
examples/stock_anomalies.py (1)
130-149: ⚡ Quick winUse
TemporaryDirectoryto guarantee cleanup.The current temp dir is never removed. Wrapping this in a context manager avoids leaked files and handles exception paths cleanly.
Proposed refactor
- tmp = tempfile.mkdtemp(prefix="anomalyx-stock-") - df = fetch(args.ticker, args.period) - cur_csv = os.path.join(tmp, f"{args.ticker}.csv") - df.to_csv(cur_csv, index=False) - dates = df["Date"].tolist() - - extra = list(scan_args) - if args.baseline: - bdf = fetch(args.baseline, args.baseline_period or args.period) - base_csv = os.path.join(tmp, f"{args.baseline}.csv") - bdf.to_csv(base_csv, index=False) - # Compare the *behavioral* distributions (volume / return / volatility); - # excluding price levels and the Date label keeps drift meaningful. - extra = ["--baseline", base_csv, "--columns", "daily_return_pct,range_pct,Volume", *extra] - print(f"# {args.ticker} ({args.period}) vs baseline {args.baseline} — distributional drift\n") - else: - print(f"# {args.ticker} ({args.period}) — anomalous trading days\n") - - env = anomalyx_scan(cur_csv, extra) - report(env, dates) + with tempfile.TemporaryDirectory(prefix="anomalyx-stock-") as tmp: + df = fetch(args.ticker, args.period) + cur_csv = os.path.join(tmp, f"{args.ticker}.csv") + df.to_csv(cur_csv, index=False) + dates = df["Date"].tolist() + + extra = list(scan_args) + if args.baseline: + bdf = fetch(args.baseline, args.baseline_period or args.period) + base_csv = os.path.join(tmp, f"{args.baseline}.csv") + bdf.to_csv(base_csv, index=False) + extra = ["--baseline", base_csv, "--columns", "daily_return_pct,range_pct,Volume", *extra] + print(f"# {args.ticker} ({args.period}) vs baseline {args.baseline} — distributional drift\n") + else: + print(f"# {args.ticker} ({args.period}) — anomalous trading days\n") + + env = anomalyx_scan(cur_csv, extra) + report(env, dates)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/stock_anomalies.py` around lines 130 - 149, Replace the manual tempfile.mkdtemp usage with a tempfile.TemporaryDirectory context manager so the temporary directory is automatically cleaned up; create cur_csv (and base_csv when args.baseline) inside that context, call anomalyx_scan(cur_csv, extra) and then report(env, dates) before exiting the with-block (ensuring any files written by df.to_csv and bdf.to_csv live inside the TemporaryDirectory), and remove any manual cleanup logic—update the code around tempfile.mkdtemp/tmp, df.to_csv, bdf.to_csv, anomalyx_scan, and report to live inside the TemporaryDirectory context.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@examples/stock_anomalies.py`:
- Line 150: The final process exit currently coerces env["exit"] to 0 or 1;
change the call that uses sys.exit to pass the raw envelope exit code
(env["exit"]) directly so non-zero codes like 2 are preserved—locate the
sys.exit(...) invocation referencing env["exit"] and replace the coercion logic
with a direct sys.exit(env["exit"]) behavior.
- Around line 71-73: The current branch that handles proc.returncode == 2 calls
sys.exit with a string which yields exit code 1; change it to emit the error
message to stderr (e.g., write or print to sys.stderr) and then call sys.exit(2)
so the process actually exits with code 2; locate the check on proc.returncode
in examples/stock_anomalies.py (the block that currently does
sys.exit(f"anomalyx error: {proc.stderr.strip()}")) and replace that single
sys.exit(...) call with stderr output followed by sys.exit(2).
---
Nitpick comments:
In `@examples/stock_anomalies.py`:
- Around line 130-149: Replace the manual tempfile.mkdtemp usage with a
tempfile.TemporaryDirectory context manager so the temporary directory is
automatically cleaned up; create cur_csv (and base_csv when args.baseline)
inside that context, call anomalyx_scan(cur_csv, extra) and then report(env,
dates) before exiting the with-block (ensuring any files written by df.to_csv
and bdf.to_csv live inside the TemporaryDirectory), and remove any manual
cleanup logic—update the code around tempfile.mkdtemp/tmp, df.to_csv,
bdf.to_csv, anomalyx_scan, and report to live inside the TemporaryDirectory
context.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: e2de393f-1d75-4af7-b1d8-f8cb12d88c5c
📒 Files selected for processing (4)
CHANGELOG.mdREADME.mdexamples/README.mdexamples/stock_anomalies.py
| if proc.returncode == 2: # committed: 0 clean, 1 anomalies, 2 tool error | ||
| sys.exit(f"anomalyx error: {proc.stderr.strip()}") | ||
| return json.loads(proc.stdout) |
There was a problem hiding this comment.
Tool-error exit code is not mirrored (returns 1 instead of 2).
sys.exit("...") exits with status code 1, so an anomalyx tool error (returncode == 2) is currently collapsed to 1. That breaks the script’s stated 0/1/2 contract.
Proposed fix
- if proc.returncode == 2: # committed: 0 clean, 1 anomalies, 2 tool error
- sys.exit(f"anomalyx error: {proc.stderr.strip()}")
+ if proc.returncode == 2: # committed: 0 clean, 1 anomalies, 2 tool error
+ print(f"anomalyx error: {proc.stderr.strip()}", file=sys.stderr)
+ sys.exit(2)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if proc.returncode == 2: # committed: 0 clean, 1 anomalies, 2 tool error | |
| sys.exit(f"anomalyx error: {proc.stderr.strip()}") | |
| return json.loads(proc.stdout) | |
| if proc.returncode == 2: # committed: 0 clean, 1 anomalies, 2 tool error | |
| print(f"anomalyx error: {proc.stderr.strip()}", file=sys.stderr) | |
| sys.exit(2) | |
| return json.loads(proc.stdout) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/stock_anomalies.py` around lines 71 - 73, The current branch that
handles proc.returncode == 2 calls sys.exit with a string which yields exit code
1; change it to emit the error message to stderr (e.g., write or print to
sys.stderr) and then call sys.exit(2) so the process actually exits with code 2;
locate the check on proc.returncode in examples/stock_anomalies.py (the block
that currently does sys.exit(f"anomalyx error: {proc.stderr.strip()}")) and
replace that single sys.exit(...) call with stderr output followed by
sys.exit(2).
|
|
||
| env = anomalyx_scan(cur_csv, extra) | ||
| report(env, dates) | ||
| sys.exit(0 if env["exit"] == 0 else 1) |
There was a problem hiding this comment.
Final process exit should mirror env["exit"] directly.
This currently coerces every non-zero to 1. If the envelope exit ever carries 2, that gets lost.
Proposed fix
- sys.exit(0 if env["exit"] == 0 else 1)
+ sys.exit(int(env["exit"]))📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| sys.exit(0 if env["exit"] == 0 else 1) | |
| sys.exit(int(env["exit"])) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/stock_anomalies.py` at line 150, The final process exit currently
coerces env["exit"] to 0 or 1; change the call that uses sys.exit to pass the
raw envelope exit code (env["exit"]) directly so non-zero codes like 2 are
preserved—locate the sys.exit(...) invocation referencing env["exit"] and
replace the coercion logic with a direct sys.exit(env["exit"]) behavior.
What
A committed, runnable example of using anomalyx on real data — and a clean demonstration of consuming the
tq1contract.examples/stock_anomalies.pyfetches a ticker's daily history from Yahoo Finance (yfinance), enriches it with daily-return % and intraday-range %, runsanomalyx scan, then parses the dense JSON envelope (the dictionary + dense finding rows) and maps each finding's handle back to a calendar date — exactly the walk an agent does.python3 examples/stock_anomalies.py NVDA --period 2y python3 examples/stock_anomalies.py NVDA --period 2y --fdr 0.01 --min-severity high python3 examples/stock_anomalies.py NVDA --period 1y --baseline AMD # distributional drift--baseline TICKER→ distributional drift of one ticker's behavior (volume/return/volatility) vs another (dist.ks/psi).anomalyx scan; exit code mirrors anomalyx (0/1/2).On real NVDA history it surfaces the 2025‑01‑27 DeepSeek selloff (top volume + largest multivariate outlier), the April‑2025 tariff volatility, and the H2‑2025 price regime shift — verified by running it.
Scope
Docs/example only — lives outside the Cargo workspace (shells out to the installed binary), so it doesn't touch the build, the gates, or any published crate. No release. Adds
examples/README.mdand a README pointer.🤖 Generated with Claude Code
Summary by CodeRabbit
Documentation
New Examples