A public, paper-only BTC research experiment using TypeSafe Jev with Bybit spot and perpetual-futures market data. A public Cloudflare Worker serves the dashboard and schedules a private Stockholm-placed Worker, which collects data, requests Jev decisions, and records simulated outcomes. It does not place orders.
Public dashboard: https://lab.rokogrga.com/btc-jev
Cloudflare fallback: https://btc-jev-signal.roko-experiments.workers.dev
This repository owns the complete BTC–Jev project: its dashboard, market-data collection, Jev judgments, scoring, Cloudflare Worker, and D1 schema. The separate WebGrga/rg-lab repository owns the public project index and routes /btc-jev to this project's independent Netlify deployment.
This is a personal software experiment, not financial advice, investment research, or a trading service. It never places or prepares trades.
The Cloudflare Worker collects a neutral structured market State at 15-minute UTC boundaries. Ordinary code first checks freshness, estimated volatility, spread, costs, and active-position limits. It sends a bounded Jev decision request only for a candidate event; Jev may choose a fixed long-only spot paper policy or no trade. The simulator enters on the next eligible 1-minute candle and records fees, spread, slippage, exit reason, and source candles. No-candidate, blocked, no-trade, pending, skipped, and unpriceable records are kept distinct.
Earlier higher-or-lower forecasts remain in the historical direction diagnostics. The Cloudflare Worker does not currently create new scheduled direction-only forecasts, so those forecast scores are separate from current paper-trade results.
The historical direction-only experiment used a natural, non-overlapping schedule:
| Forecast | Issued | Target | Maximum primary trials per UTC day |
|---|---|---|---|
| 15 minutes | Every 15 minutes | Issue + 15 minutes | 96 |
| 1 hour | At the top of each hour | Issue + 1 hour | 24 |
| 4 hours | Every fourth UTC hour | Issue + 4 hours | 6 |
| UTC day close | At 00:00 UTC | Next 00:00 UTC | 1 |
An earlier version issued every horizon every 15 minutes. Those append-only records remain auditable, but overlapping 1-hour, 4-hour, and day-close forecasts are excluded from the primary dashboard scores unless their issue time matches the natural schedule.
Every State uses normalized UTC ISO 8601 timestamps and completed candles only.
| Measurement | Source and definition |
|---|---|
| Anchor and realized target | Cloudflare: Bybit Spot completed BTCUSDT 1-minute candle. Local runner: Binance Spot completed BTCUSDT 1-minute candle. |
| Returns | 1m, 5m, 15m, 1h, and 4h from the runtime's spot source |
| 15m / 1h / 4h indicators | RSI(14), MACD(12,26,9), ATR(14), SMA/EMA 20/50/200, price distances, bar returns, and volume ratios from completed spot candles |
| UTC session | Spot open, high, low, volume, return from open, and minutes to 00:00 UTC |
| Perpetual futures | Funding, mark/index price, open interest, and OI changes from Bybit USDT Perpetual |
| Liquidations | Short timestamped observation of Bybit's public BTCUSDT all-liquidation WebSocket |
| Order book | Cloudflare: Bybit Spot top-20 levels. Local runner: Binance Spot top-20 levels. Both expose bid/ask notionals, spread, and imbalance. |
The State always identifies snapshot.symbol, snapshot.quote_asset, and every source string. Monetary values are denominated in USDT and use stable _usdt field names.
All current candidate questions share one TypeSafe request. No-candidate scans make no TypeSafe request. The API key stays server-side.
The dashboard intentionally separates five concepts:
- Paper results show net P&L, expectancy, drawdown, exposure, uncertainty, and sample size separately from directional scores.
- Paper decisions show Jev's action, Choice distribution, independent Noul probability, code eligibility, simulated fills, and costs.
- Candidate log shows no-candidate and blocked scans as well as requests sent to Jev.
- Forecast diagnostics keep archived directional accuracy, Brier score, and log loss separate from trading results.
- Audit records include exact market State and source candles.
Small samples are descriptive only. Jev confidence describes concentration in the returned distribution, not guaranteed correctness.
- A public Cloudflare Worker serves the API and dashboard assets and owns the Cron Trigger.
- Cron invokes a separate private Worker through a Service Binding. The runner is placed in
aws:eu-north-1and owns exchange, Jev, D1 collection, paper-simulation, and settlement calls. - A project-specific Netlify site builds this repository's dashboard under
/btc-jev; the RG Lab edge route exposes it at the canonicallab.rokogrga.com/btc-jevaddress. - Cloudflare Static Assets also provides a fallback copy on
workers.dev. - Cloudflare D1 stores historical prediction batches and settlements, paper scans, proposals, and simulated positions.
- Only the private runner stores the
TYPESAFE_API_KEYsecret. - One Cron Trigger runs at minutes 1, 16, 31, and 46, allowing the just-completed Bybit candle to finalize before collection.
The production database is intentionally separate from data/*.jsonl. A new deployment starts a clean online history unless a local history import is deliberately approved and performed.
The private runner allows at most 97 TypeSafe requests per UTC day by default. Candidate gating usually uses fewer, and a no-candidate scan consumes no request. TYPESAFE_DAILY_REQUEST_LIMIT in wrangler.runner.jsonc can lower or raise that positive whole-number limit (maximum 10,000). Each scheduled boundary is claimed once in D1, and each TypeSafe request reserves one daily slot before it is sent. Reservations count conservatively, even if the provider request later fails. The runner disables SDK retries so one reservation cannot fan out into extra TypeSafe attempts. Local CLI runs are not covered by this Cloudflare-only limit.
The D1 control row provides a pause switch. Run either command from the repository with Wrangler authenticated to this Cloudflare account:
# Pause scheduled collection and settlement fetches
npx wrangler d1 execute btc-jev-signal --remote --command "UPDATE experiment_control SET paused = 1, updated_at_utc = strftime('%Y-%m-%dT%H:%M:%fZ','now') WHERE control_id = 'collection';"
# Resume
npx wrangler d1 execute btc-jev-signal --remote --command "UPDATE experiment_control SET paused = 0, updated_at_utc = strftime('%Y-%m-%dT%H:%M:%fZ','now') WHERE control_id = 'collection';"
# Inspect today's request count and the latest scheduled runs
npx wrangler d1 execute btc-jev-signal --remote --command "SELECT utc_day, COUNT(*) AS requests FROM typesafe_request_reservations WHERE utc_day = strftime('%Y-%m-%d','now') GROUP BY utc_day; SELECT boundary_utc, status, requests_used, request_limit, skipped_reason FROM scheduled_runs ORDER BY boundary_utc DESC LIMIT 20;"Apply the D1 migration before deploying the Worker so the guard tables and default control row exist. The D1 pause stops scheduled market collection, Jev calls, and settlements after the next handler checks it; scheduled Worker invocations still occur. To stop those invocations too, disable/remove the Cron Trigger in Cloudflare. These application controls reduce runaway usage but cannot enforce a Cloudflare billing cap, include the fixed Workers Paid plan fee, or cover other Cloudflare products and local CLI usage. Cloudflare budget alerts remain warning-only.
Requires Node.js 20.19 or newer and a Cloudflare account.
npm install
npx wrangler login
npm run cloudflare:migrate
npx wrangler secret put TYPESAFE_API_KEY --name btc-jev-signal-runner
npm run cloudflare:deployThe public scheduler is configured in wrangler.jsonc; the private, Stockholm-placed runner is configured in wrangler.runner.jsonc. The D1 schema is in migrations/.
Useful Cloudflare commands:
npm run cloudflare:check # Build and validate without publishing
npm run cloudflare:check:runner # Validate only the private runner
npm run cloudflare:dev # Local Worker/D1 development
npm run cloudflare:deploy # Build, publish runner, then publish public WorkerCopy .env.example to .env, add TYPESAFE_API_KEY, and run:
npm install
npm run experimentThe local runner uses the same natural cadence and writes append-only records to data/. Restarting is safe; existing boundary IDs are not duplicated and overdue forecasts are settled on startup.
Other useful commands:
npm run dashboard # Read-only dashboard at http://127.0.0.1:3000
npm run experiment:once # Immediate four-horizon diagnostic batch
npm run experiment:state # Inspect State without calling Jev
npm run settle # Settle due local forecasts
npm run report # Regenerate local report files
npm run check
npm testLocal mode writes:
data/predictions.jsonl: complete States and probability distributions.data/settlements.jsonl: target prices, realized returns, accuracy, Brier score, and log loss.data/report.jsonanddata/report.md: aggregate summaries.
Cloudflare stores equivalent JSON records in indexed D1 tables. Credentials are never written to State, dashboard responses, or prediction records.
- Market HTTP requests use bounded retries and timeouts.
- TypeSafe requests use SDK retries and respect rate-limit guidance.
- The Cloudflare Worker reserves each TypeSafe call against a daily D1 limit and disables automatic retries; the local runner retains its separate SDK retry policy.
- Optional derivatives, order-book, or liquidation failures remain explicit in State.
- Forecast and settlement IDs are unique, making repeated scheduled delivery safe.
- The public API is read-only and returns no secret or TypeSafe usage metadata.
- Dashboard and API responses include restrictive security headers.
This repository deploys only the BTC–Jev dashboard. netlify.toml builds web/dist, serves the app beneath /btc-jev, proxies /btc-jev/api/* to the Cloudflare Worker, and redirects the project deployment root to the canonical RG Lab address. The hub and its cross-project routing live in WebGrga/rg-lab.