Skip to main content
Yanked

This release has been yanked by its maintainers, and will be ignored by installers, except when explicitly specified.
Consider using release 0.2.0 instead.

Stock Swing Attribution Agent

Detects unusual price moves, retrieves news across sources, and produces an evidence-backed explanation of why a stock moved — or states that no catalyst can be identified.

Specs: agent-plan.md (what and why) · data-sources.md (APIs) · CODEBASE-PLAN.md (module layout, schema, build order).

Using it

swing                                   # opens the prompt
> why is NVDA down?
> what happened to Tesla yesterday?
> why did MU jump on Aug 27?

One-shot forms: swing ask "why is NVDA down?" · swing why NVDA --date 2026-08-27.

  • Covers the 12 watchlist stocks: NVDA MRVL MU SNDK AVGO QCOM AAPL GOOGL NFLX TTWO TSLA SBUX.
  • Each question fetches fresh prices and news first. A normal day is answered in a second or two with no model call; an unusual move takes ~20s and uses one Gemini request (free tier: 20/day). Asking again reuses the stored answer.
  • Evidence: SEC filings, company press releases, Reuters and Bloomberg wire stories, WSJ / CNBC / MarketWatch / Dow Jones, analyst rating changes, and news about related companies (competitors, big customers, suppliers), each placed before or after the moment the move started.
  • When nothing published before the move explains it, it says so instead of guessing.
  • It refuses forecasts ("is NVDA a buy?").
  • Answers are only as good as the news it holds, so keep the collector running (below).

Everything else (annotate, placebo, metrics, daily, ...) is evaluation and operations tooling.


Runbook

Installing the swing command

Installed globally (editable, so code edits take effect immediately):

uv tool install --editable ".[data,ml,llm]" --python 3.12 --force
swing health          # per-source feed health
swing coverage        # what data do I have
swing dbinit          # apply schema, idempotent

The collector

tail -f data/collector.log            # watch it
swing health                          # per-source counts + broken-feed check
kill $(cat data/collector.pid)        # stop
nohup caffeinate -is swing collect --daemon > data/collector.nohup.log 2>&1 &
  echo $! > data/collector.pid        # start  (see the sleep caveat below)

Polls: EDGAR every 10 min (12 stocks + 21 related companies) · IR/press RSS every 10-15 min · Finnhub news every 6 h · analyst ratings hourly · normalize every 5 min · dead-feed check every 30 min.

Scheduled inside the collector (interface/schedule.py), so no cron is needed:

  • Weekdays 16:45 ET — refresh, detect the day's swings, explain up to 3 new big moves, alert on |z| ≥ 3.
  • Nightly 00:30 PT — 17 placebo cases toward Gate 4's 200 (about 12 nights); the job stops by itself at 200.

That is up to 20 of the 20 free Gemini requests a day: on a day the post-close run explains moves, unusual-move questions wait until the quota resets at midnight Pacific. Normal days and repeated questions cost nothing. Each job records its last run in data/schedule_*.last; delete a file to make that job run again today.

⚠️ Two persistence caveats — read both

1. This Mac sleeps after 1 minute idle (pmset reports sleep 1, powernap 0 on battery). time.monotonic() freezes across macOS sleep, so the collector stops polling entirely — observed directly: one poll, then 61 minutes with zero CPU and no polls. That is silent, permanent news loss.

The caffeinate -is wrapper above is the mitigation and is verified working, but it keeps the Mac awake and does not survive a lid close. The real fix is an always-on host — a $5 VPS or a Raspberry Pi running Postgres and the collector. Every other part of this system is a batch job that can run anywhere; only the collector must never stop.

2. The collector runs under nohup. It survives closing the terminal, but not a reboot or logout. A macOS LaunchAgent was tried and does not work from this location: ~/Desktop is TCC-protected, and a launchd background agent does not inherit Full Disk Access, so the Python interpreter blocks forever in _PyConfig_InitPathConfig → open() before any project code runs.

Two durable fixes, either one is a few minutes:

  1. Move the repo out of ~/Desktop (e.g. ~/projects/swing-agent), recreate the venv there, then install scripts/com.swingagent.collector.plist:
    sed "s|__REPO__|$PWD|g" scripts/com.swingagent.collector.plist \
      > ~/Library/LaunchAgents/com.swingagent.collector.plist
    launchctl load ~/Library/LaunchAgents/com.swingagent.collector.plist
    
  2. Grant Full Disk Access to the interpreter in System Settings → Privacy & Security → Full Disk Access, then load the plist.

Until then: after any reboot, restart the collector with the command above. Check --report shows growth every few days. A silently dead feed for three weeks is three weeks of unrecoverable data.

Database

docker compose up -d                  # Postgres 16 + pgvector on :5433
docker compose ps
swing dbinit && .venv/bin/alembic upgrade head   # schema + migrations, idempotent
docker exec -e PGPASSWORD=swing swing-db psql -U swing -d swing_agent

Layout

src/swing/
  cli.py, commands.py   the `swing` command and its subcommands
  interface/explain.py  one question -> prices, news, split, reason   (what `swing` runs)
  ingest/               collector, EDGAR, RSS, Finnhub, prices, normalize
  analysis/             market/sector split, swing detection, onset, clustering, ranking
  agent/                LangGraph attribution: Gemini call + code-level citation/abstention guards
  eval/                 annotation, placebo test, metrics
  store/                schema.sql, models, migrations
config/                 watchlist.yaml, sources.yaml, thresholds.yaml, prompts/

Design and findings: CODEBASE-PLAN.md. Package layout is src/swing/ so a globally installed swing does not put common or agent on the system as top-level import names.

License

MIT — see LICENSE.

Disclaimer

This is not investment advice. It is a research tool that reports what the news said before a price move. It does not predict prices, recommend trades, or know anything about your situation.

Two limits are worth stating plainly, because they are measured rather than guessed:

  • It finds a catalyst for roughly 70% of the moves it is asked about. The rest come back unexplained, and the run swing metrics prints is the real number, not a target.
  • unexplained means no catalyst was found in the sources collected, which is not the same as no catalyst existed. Coverage is bounded by which feeds have been running and for how long.

The agent is built to abstain rather than guess — in 38 tests where it was given deliberately unrelated evidence, it invented a cause 0 times — but an explanation it does give is still a starting point for your own reading, not a conclusion. Verify anything you act on against the linked source.

Release files for swing-agent 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for swing-agent 0.1.0
File Size Uploaded
swing_agent-0.1.0.tar.gz 184.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for swing-agent 0.1.0
File Interpreter ABI Platform
swing_agent-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 359.0 kB

Release files / swing_agent-0.1.0.tar.gz

Download URL swing_agent-0.1.0.tar.gz
Size 184.0 kB
Tags Source
SHA-256 checksum
How to use checksums
b66ce330ad901910281af858de0562b5c315b9487c17000278ca1de8bf3799f0
BLAKE2b-256 checksum
How to use checksums
feed4f5386d449cda92d4ae8782c36aff7185fb47508067e1d1027bdd6652ac9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release files / swing_agent-0.1.0-py3-none-any.whl

Download URL swing_agent-0.1.0-py3-none-any.whl
Size 175.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ed1c97c1a2cffcd119223c33c9b676aa3c5659734e24b527c976a4ee1251437c
BLAKE2b-256 checksum
How to use checksums
c17307a66ed270887664283ed6c76c352e8ec2815c101ddeff1930deae9b43d7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release history Release notifications | RSS feed

0.2.0

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page