Appearance
Infrastructure
Two pieces of hosted infrastructure support this project, both on a single free Cloudflare account:
| Need | Service | Free tier | Cost at our scale |
|---|---|---|---|
| Host this private research site | Cloudflare Pages + Cloudflare Access | Unlimited bandwidth/sites; Access gives 50 seats | $0 |
| Cache fetched market data | Cloudflare R2 | 10 GB storage, $0 egress — always | $0 |
Why Cloudflare
- GitHub Pages doesn't fit — this repo is private, and private Pages needs a paid GitHub plan with only coarse access control.
- Netlify / Vercel host private-repo static sites free, but put password protection behind a paid plan. Cloudflare Access is free for 50 seats.
- R2's zero egress fee (on free and paid tiers) is the deciding factor for data: CI and agents re-read the same bars constantly, and S3/B2 charge for every read. R2 never does. It's also S3-compatible, so it drops into the existing pandas/parquet code with no new concepts.
Sources: R2 pricing · Pages limits · Cloudflare Access.
Hosting the site (Cloudflare Pages)
The Deploy docs workflow builds docs/ and publishes docs/.vitepress/dist to a Pages project on every push to main that touches the site. The build still fails on dead internal links, so a broken link blocks the deploy.
One-time setup (no manual project creation — the workflow creates the vibe-trader-two Pages project on its first run):
- Create an API token. My Profile → API Tokens → Create Token → template "Edit Cloudflare Workers" (or a custom token with Account → Cloudflare Pages → Edit; the Edit permission is what lets the workflow create the project). Copy the token and your Account ID (right sidebar of any dashboard page).
- Add the repo secrets (Settings → Secrets and variables → Actions):
CLOUDFLARE_API_TOKENCLOUDFLARE_ACCOUNT_ID
- Deploy once. Merge to
main(any change underdocs/triggers the workflow) or run it manually from Actions → Deploy docs → Run workflow. The first run creates the project and publishes tovibe-trader-two.pages.dev. - Lock it down with Access. After that first deploy, Zero Trust → Access → Applications → Add a self-hosted application for the Pages domain (
vibe-trader-two.pages.devand any custom domain). Add an Allow policy scoped to your email(s) or an email domain. Viewers then authenticate with a one-time PIN (or SSO) before the site loads.
The site deploys at the Pages project root, so leave base unset in docs/.vitepress/config.ts — the /vibe-trader-two/ base mentioned there is only for a GitHub Pages deploy, which we are not using.
Caching market data (Cloudflare R2)
vibe_trader.data.storage resolves each parquet key to a local path or an s3:// URI on R2, decided purely by environment. Nothing is hard-coded to the cloud: with VIBE_DATA_BUCKET unset, ingest and backtests read/write strategies/data/raw/ exactly as before, so a fresh checkout and the offline smoke test need no account.
One-time setup:
- Create the bucket. Cloudflare dashboard → R2 → Create bucket, e.g.
vibe-data. - Create an R2 API token. R2 → Manage R2 API Tokens → Create → Object Read & Write. Note the Access Key ID, Secret Access Key, and the S3 endpoint (
https://<account id>.r2.cloudflarestorage.com— the account id is your Cloudflare account id, the same one used for Pages). - Configure locally (in
strategies/.env, gitignored):shThenVIBE_DATA_BUCKET=vibe-data R2_ACCESS_KEY_ID=<access key id> R2_SECRET_ACCESS_KEY=<secret access key> R2_ENDPOINT=https://<account id>.r2.cloudflarestorage.com # (or set R2_ACCOUNT_ID=<account id> instead of R2_ENDPOINT) DATABENTO_API_KEY=db-...uv sync --extra r2(installss3fs, only needed for R2). - Configure CI for the
Refresh market dataworkflow. The workflow maps these GitHub secret names to the env vars the code reads (see itsenv:block):- Secrets:
DATABENTO_API_KEY,CLOUDFLARE_R2_ENDPOINT,CLOUDFLARE_R2_ACCESS_ID,CLOUDFLARE_R2_ACCESS_KEY - Variable:
VIBE_DATA_BUCKET
- Secrets:
The repo is deprecated, so the weekly schedule trigger (previously Wednesday 00:30 UTC, after Tuesday's CME settlement) has been removed; the workflow now only runs on demand via Run workflow:
<symbol>_{1d,1h}.parquet— Databento bars (needs the secrets above), extended incrementally: each run fetches only the bars since the last stored one (plus a small overlap) and merges, so the weekly Databento spend is days of data, not the full window. The 10-year backfill (launch date for the young micros) happens only on a first run or an explicit--fullreset — and because refreshes append, the stored history accumulates past the cap over time (the cap bounds fetch cost, not storage). Note this also means backtest windows lengthen as the cache grows.{vix,vix9d,vix3m,vvix,skew}_1d.parquet— Cboe volatility indices and<symbol>_cot_1w.parquet— CFTC COT TFF positioning: full refreshes (keyless, free, single-request feeds).
Every Databento merge is validated in-process before the write, and a final step re-validates the stored daily + hourly bars (vibe_trader.data.validate) so a corrupt upload fails the run visibly.
Reading the cache from an agent session
Any script with the four R2 env vars set reads the shared cache — no re-fetch:
python
from vibe_trader.data.databento_ingest import load_bars_df
df = load_bars_df("MES") # pulls s3://vibe-data/mes_1d.parquet off R2
from vibe_trader.features import feature_frame
feats = feature_frame("MES") # bars + VIX regime + COT z-scores, alignedFor ad-hoc analysis, DuckDB queries the parquet directly over R2's S3 API:
python
import duckdb
con = duckdb.connect()
con.execute("""
CREATE SECRET r2 (
TYPE r2,
KEY_ID '…', SECRET '…',
ACCOUNT_ID '…'
);
""")
con.sql("SELECT * FROM read_parquet('r2://vibe-data/mes_1d.parquet') LIMIT 5").show()Cost summary
Everything here sits inside free tiers at this project's scale (a few small parquet files, one low-traffic private site). R2 storage beyond 10 GB is $0.015/GB/month with no egress charge; the first paid dollar is a long way off.