Raxx · internal docs

internal · gated

Demo chain snapshot refresh

Script: scripts/demo/refresh_snapshots.py Epic: #482 — demo.raxx.app conversion flow Card: #484 — Static chain snapshot operator script


What this is

The demo proposal endpoint (POST /api/demo/propose) serves proposals from a static historical snapshot — it never calls a live options API at request time. This script refreshes those snapshots by fetching recent price bars from Alpaca and synthesising put-credit-spread candidates anchored to the most recent closing price.

Run it before any marketing campaign that drives traffic to demo.raxx.app, and on the normal weekly cadence.


Prerequisites

Python packages

The script requires only requests (already in requirements.txt). pyyaml is used to load scripts/demo/demo_ticker_config.yaml if present; the script falls back to built-in defaults if PyYAML is not installed.

pip install requests pyyaml

Alpaca credentials

The script reads credentials from environment variables. Set one of the following pairs before running:

export ALPACA_PAPER_API_KEY=<key>
export ALPACA_PAPER_API_SECRET=<secret>

or

export ALPACA_API_KEY=<key>
export ALPACA_API_SECRET=<secret>

Credentials live in the Infisical vault at /demo under the project raxx-api-prod. To pull them:

infisical secrets get ALPACA_PAPER_API_KEY --env=prod --path=/demo
infisical secrets get ALPACA_PAPER_API_SECRET --env=prod --path=/demo

If the vault folder does not yet exist, create it via the Infisical UI or POST /api/v1/folders before writing secrets (see feedback_vault_folder_must_exist.md).

Output directory

By default, snapshots are written to data/demo/ in the repo root. Override with:

export DEMO_SNAPSHOT_DIR=/path/to/output

The DEMO_SNAPSHOT_PATH env var consumed by the proposal endpoint should point at the latest symlink for a given ticker, e.g.:

export DEMO_SNAPSHOT_PATH=/path/to/data/demo/spy-chain-snapshot-latest.json

The snapshot file shape is a JSON object keyed by ticker symbol; the script writes one file per ticker. If you need a combined file, run the script and then concatenate with jq.


Running the script

Standard weekly refresh

python scripts/demo/refresh_snapshots.py

This fetches Alpaca bars for all tickers in scripts/demo/demo_ticker_config.yaml (default: SPX, SPY, QQQ) and writes versioned snapshot files to data/demo/. A latest symlink is updated for each ticker.

Before a marketing campaign

Run the standard refresh the day before the campaign starts to ensure the snapshot dates are recent. If you need to force-refresh even though today's snapshot already exists:

python scripts/demo/refresh_snapshots.py --force

Dry run (validate without writing)

python scripts/demo/refresh_snapshots.py --dry-run

The script fetches data and validates the output but does not write any files.

Single ticker

python scripts/demo/refresh_snapshots.py --ticker SPY

Custom output directory

python scripts/demo/refresh_snapshots.py --output-dir /tmp/demo-snapshots

Output file format

Each versioned file is named:

<ticker_lower>-chain-snapshot-<YYYY-MM-DD>.json

Example: spy-chain-snapshot-2026-05-12.json

A symlink spy-chain-snapshot-latest.json is kept up to date in the same directory.

The file content is a JSON array of put-credit-spread candidates. Each entry includes:

Field Type Description
structure_type string Always "put_credit_spread"
strikes [short, long] Strike prices as integers or floats
width number Spread width in underlying points
delta number Approximate short-put delta (negative)
theta number Approximate daily theta (positive)
entry_price number Net credit per share
premium_received number entry_price * 100
max_loss number (width - entry_price) * 100
breakeven number short_strike - entry_price
historical_win_rate number Historical win rate (0–1)
snapshot_date string YYYY-MM-DD of the snapshot
underlying_close number Closing price used to anchor strikes
data_fetched_at string UTC ISO-8601 timestamp of the fetch

File size

Each snapshot file is typically 2–5 KB per ticker (3 spread candidates × ~500 bytes each). Well within git's comfort range. If you expand the ticker set significantly, confirm the aggregate size stays under 1 MB per refresh cycle before committing to the repo.


Scheduled cadence


Troubleshooting

Symptom Likely cause Fix
ERROR: Alpaca credentials not found Env vars not set Export credentials (see Prerequisites)
HTTP 403 from Alpaca Invalid key/secret Verify credentials in vault; regenerate if needed
HTTP 422 from Alpaca Invalid ticker or date range Check demo_ticker_config.yaml; ensure data_feed is valid for your plan
HTTP 429 Rate limit hit Script retries once automatically; if still failing, wait a minute and re-run
Validation failed Alpaca returned unexpected data shape Check the bars response manually; open an issue if the Alpaca API shape changed
Zero bars returned No trading days in window Widen lookback_days in config; do not run on market holidays
Symlink errors on Windows NTFS symlink restrictions Script falls back to a file copy automatically

Connecting the snapshot to the proposal endpoint

Set the DEMO_SNAPSHOT_PATH environment variable on the Raptor Heroku app to the path of the combined snapshot JSON, or update the service to read per-ticker files. The current demo_proposal_service.load_snapshot_for_ticker() reads from a single combined file keyed by ticker symbol.

To produce a combined file from the per-ticker outputs:

python scripts/demo/refresh_snapshots.py
# Then combine with jq:
jq -s '{"SPX": .[0], "SPY": .[1], "QQQ": .[2]}' \
  data/demo/spx-chain-snapshot-latest.json \
  data/demo/spy-chain-snapshot-latest.json \
  data/demo/qqq-chain-snapshot-latest.json \
  > data/demo/combined-chain-snapshot-latest.json
# Then set on Heroku:
heroku config:set DEMO_SNAPSHOT_PATH=/app/data/demo/combined-chain-snapshot-latest.json \
  --app raxx-api-prod >/dev/null