Skip to content

docs: add examples/stock_anomalies.py — fetch + scan worked example#64

Merged
copyleftdev merged 1 commit into
mainfrom
docs/example-stock-anomalies
Jun 2, 2026
Merged

docs: add examples/stock_anomalies.py — fetch + scan worked example#64
copyleftdev merged 1 commit into
mainfrom
docs/example-stock-anomalies

Conversation

@copyleftdev

@copyleftdev copyleftdev commented Jun 2, 2026

Copy link
Copy Markdown
Owner

What

A committed, runnable example of using anomalyx on real data — and a clean demonstration of consuming the tq1 contract.

examples/stock_anomalies.py fetches a ticker's daily history from Yahoo Finance (yfinance), enriches it with daily-return % and intraday-range %, runs anomalyx 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
  • Single-corpus → point / multivariate / collective anomalies within one series.
  • --baseline TICKER → distributional drift of one ticker's behavior (volume/return/volatility) vs another (dist.ks/psi).
  • Extra flags pass straight through to 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.md and a README pointer.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation

    • Added examples section with detailed installation and usage instructions.
  • New Examples

    • Stock anomalies example demonstrating detection of trading anomalies in market data, including point and multivariate anomalies, with optional baseline comparisons for distributional analysis.

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>
@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces a new example script stock_anomalies.py that demonstrates consuming the anomalyx tool on real Yahoo Finance stock trading data, including data enrichment, tool invocation, result parsing, and human-readable reporting with optional baseline ticker comparison for distributional drift detection.

Changes

Stock Anomalies Example Implementation

Layer / File(s) Summary
Stock anomalies example script
examples/stock_anomalies.py
Implements fetch() to download OHLCV from yfinance and derive return/range metrics; anomalyx_scan() to locate the binary (via $ANOMALYX or default), run it with passthrough CLI args, and parse JSON envelope; describe_handle() to convert anomaly handles into human-readable dates; report() to format and print scan metadata and findings; and main() to orchestrate ticker/baseline arguments, temp workspace, data export, tool execution, and exit code mirroring.
Example documentation
examples/README.md, README.md, CHANGELOG.md
Introduces examples/README.md with installation, usage, and exit-code semantics; adds an "Examples" section to README.md highlighting the stock anomalies example; updates CHANGELOG.md with an Unreleased entry documenting the new example, its Yahoo Finance data source, anomaly/drift detection, and tq1 envelope parsing demonstration.

Sequence Diagram

sequenceDiagram
  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
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

A rabbit hops through financial trees,
With yfinance and anomalyx ease.
Trading days reveal their secrets strange,
Baselines drift, returns rearrange.
📊✨ New examples for all to see!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the primary change: adding a worked example script (stock_anomalies.py) that demonstrates fetching stock data and scanning with anomalyx. It is concise, specific, and directly reflects the main deliverable.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs/example-stock-anomalies

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
examples/stock_anomalies.py (1)

130-149: ⚡ Quick win

Use TemporaryDirectory to 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

📥 Commits

Reviewing files that changed from the base of the PR and between b173338 and 499d90c.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • README.md
  • examples/README.md
  • examples/stock_anomalies.py

Comment on lines +71 to +73
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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Suggested change
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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Suggested change
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.

@copyleftdev copyleftdev merged commit eaa1000 into main Jun 2, 2026
2 checks passed
@copyleftdev copyleftdev deleted the docs/example-stock-anomalies branch June 2, 2026 00:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant