Authentication
Every request needs the public anon key below, as both the
apikey header and a Bearer token. It is the same key Rotator's own frontend ships to every browser, so it is not a secret. It grants read access only: Row Level Security rejects any insert, update or delete, whatever key is used.
apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Ind5dnd5Y2F0Z2V4cGJ1Z3prZGZ3Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzU0ODcwNTAsImV4cCI6MjA5MTA2MzA1MH0.msUPOUjd1iZwuG6SxFlhr-0xYPt6fqTUy5iOYnf8Z20
Authorization: Bearer <same value as apikey>
Base URL:
https://wyvwycatgexpbugzkdfw.supabase.co/rest/v1/Start here: the scored universe
Every 15 minutes the scoring engine runs once over the whole universe and writes one run (a header row) and one item per asset. This is the core dataset; everything else on this page is derived from it or recorded alongside it.
Read the newest run that has items, not simply the newest run. The run header is written a moment before its items. A request that lands in that gap gets a run with zero items. Ask for the newest two runs and use the first one that returns items, or use the embedded form below and check that the item list is not empty.
GET /signal_runs
One row per scoring run. The newest is the current state of the market as the engine sees it.
curl "https://wyvwycatgexpbugzkdfw.supabase.co/rest/v1/signal_runs?select=id,as_of,engine_version,cycle_label,universe_size,eligible_count&order=as_of.desc&limit=2" \
-H "apikey: <anon key>" -H "Authorization: Bearer <anon key>"
| field | type | notes |
|---|---|---|
| id | bigint | run id; join key for signal_run_items.run_id |
| as_of | timestamptz | when the run was scored |
| engine_version | text | scores from different versions are not directly comparable; see the engine changelog |
| cycle_label | text | BTC market regime from the Mayer multiple: 'neutral', 'oversold' or 'stretched'. Zone thresholds depend on it. |
| universe_size / eligible_count | integer | assets scored / assets that passed every eligibility check |
| params | jsonb | every threshold and input the run used (zone thresholds, macro readings, Fear & Greed, rule tables), so a score can be reproduced |
| changes | jsonb | what moved since the run ~24h earlier: zone and class transitions, biggest score and rank changes |
| input_freshness | jsonb | age in ms of the price and macro inputs when the run was scored |
GET /signal_run_items
One row per asset per run: its score, its label, and the components behind them. About 290 rows per run. The jsonb columns are large, so select only the columns you need.
# the strongest eligible assets in run 1732
curl "https://wyvwycatgexpbugzkdfw.supabase.co/rest/v1/signal_run_items?run_id=eq.1732&eligible=is.true&select=coin_sym,score,strength,zone,candidate_class,p7,p30&order=strength.desc&limit=10" \
-H "apikey: <anon key>" -H "Authorization: Bearer <anon key>"
# the newest run and its items in one request
curl "https://wyvwycatgexpbugzkdfw.supabase.co/rest/v1/signal_runs?select=id,as_of,signal_run_items(coin_sym,score,zone)&order=as_of.desc&limit=1" \
-H "apikey: <anon key>" -H "Authorization: Bearer <anon key>"
| field | type | notes |
|---|---|---|
| run_id | bigint | signal_runs.id |
| coin_id | text | CoinGecko id for crypto; tokenized stocks are bstock_<TICKER>. Use this as the key, not the ticker: tickers are not unique. |
| coin_sym | text | display ticker |
| asset_type | text | 'crypto' or 'bstock' (Binance tokenized equity certificates; never eligible) |
| price / mcap | numeric | USD at scoring time |
| p7 / p14 / p30 | numeric | trailing return in percent |
| r7 / r14 / r30 | integer | rank of that return within the run, 1 = best; 0 = not ranked (incomplete data) |
| score | numeric | composite: momentum rank, macro-relative strength, tokenomics (supply and unlocks). Higher = stronger recent relative performance. Not bounded to 0–100; it can go negative. |
| effective_score | numeric | score after the insight cross-check; this is what sets the zone |
| zone | text | 'buy', 'neutral' or 'sell': a band of effective_score, not an instruction. 'buy' is the LOW end (has lagged), 'sell' the HIGH end (has run). Thresholds and hysteresis are in params.zoneRules. See "What the evidence says" below before using it. |
| strength | numeric | 0–100, high = relative strength (second model). Rank on this. |
| setup | numeric | 0–100 mean-reversion setup quality; null when there is no setup |
| eligible | boolean | false when any exclusion applies |
| exclusions | text[] | why not eligible: 'equity', 'delisted', 'illiquid', 'no_market_cap', 'incomplete_history', 'monitoring' (Binance monitoring tag), 'unlock_pending' (large token unlock within 30 days) |
| data_complete | boolean | false = inputs missing; treat the score as unreliable |
| candidate_class | text | mean-reversion classification, see the table below; null when not eligible |
| rsi / rsi_state | numeric / text | daily RSI(14); state 'oversold' (≤30), 'low' (≤45), 'neutral' (<60), 'elevated' (<70), 'overbought'; null when no candles |
| breakdown | jsonb | score components: layer1 (momentum), layer2 (macro-relative), layer3 (tokenomics), supplyPts, unlockPts, sizeAdj, … |
| candidate | jsonb | the numbers the class was reached from: p24, recentWeek, priorWeek, pace30, flags |
| insight | jsonb | technical read: label 'STRONG' / 'NEUTRAL' / 'WARN', points, and human-readable signals[] |
| positioning | jsonb | derivatives: funding, open interest and its 24h change, long/short and taker ratios; label 'NEUTRAL', 'CROWDED LONG', 'CROWDED SHORT' or 'UNWINDING'. Published at zero weight: it does not move the score. |
| unlock30d | numeric | percent of supply unlocking in the next 30 days. null means no published schedule, not "no unlock due"; roughly 30% of the universe has one. |
| candidate_class | meaning |
|---|---|
| CANDIDATE | in a pullback, not accelerating down, RSI agrees it is low |
| STRONG | no pullback; a strong asset, not a mean-reversion entry |
| EXTENDED | already made an extreme one-day move |
| COOLDOWN | large one-day gain, short of extreme |
| FALLING_KNIFE | decline accelerating, or deep with no sign of stabilising |
| NOT_OVERSOLD | in a pullback, but RSI does not support the setup |
| UNCONFIRMED | RSI is in use this run but missing for this asset |
Published calls
GET /rotation_snapshots
The daily "rotate out of A, into B" pairs, written once a day at 19:07 UTC from the latest run: the five highest scores paired with the five lowest eligible ones. Always filter on
source. The table also holds experiments that are recorded for grading and never published (see below); an unfiltered query mixes them in. Calls are graded at 30 days; the pre-graded record is at /track-record.html.curl "https://wyvwycatgexpbugzkdfw.supabase.co/rest/v1/rotation_snapshots?source=eq.sync-rotation-snapshot&order=snap_date.desc&limit=20" \
-H "apikey: <anon key>" -H "Authorization: Bearer <anon key>"
| field | type | notes |
|---|---|---|
| snap_date | date | the day the call was made |
| from_id / from_sym | text | the "rotate out of" asset |
| from_price / from_score | numeric | price and score at call time |
| to_id / to_sym | text | the "rotate into" asset |
| to_price / to_score | numeric | price and score at call time |
| source | text | sync-rotation-snapshot = the published calls. Unpublished experiments: -inverted (the same pairs reversed; retired 2026-09-23, 15 rows kept as history), -gold, -gold-30d, -gold-both (a hot asset paired with PAXG), -top20 (within the 20 largest by market cap, lowest scores into highest; from 2026-09-23). |
GET /signal_snapshots
Daily single-asset calls, 'bullish' or 'lagging', recorded since 2026-09-07. Each row carries the engine version that made it. The track record grades these against the market median over the same window, from 2026-09-11 on.
curl "https://wyvwycatgexpbugzkdfw.supabase.co/rest/v1/signal_snapshots?snap_date=gte.2026-09-11&order=snap_date.desc" \
-H "apikey: <anon key>" -H "Authorization: Bearer <anon key>"
| field | type | notes |
|---|---|---|
| snap_date | date | |
| coin_id / coin_sym / coin_name | text | |
| signal_type | text | 'bullish' or 'lagging' |
| signal_label | text | display label at call time, e.g. 'MOMENTUM', 'LAGGING', 'ROTATE IN' |
| score / zone | numeric / text | at call time |
| price / p24 / p7 / p30 / mcap | numeric | at call time |
| engine_version / source | text | which engine made the call; 'dashboard' or 'bot' |
Market backdrop: the TODAY indices
The readings on the TODAY section of the main page: central bank rates and government bond yields, the dollar index, metals, energy, and Bitcoin and DeFi network activity. Each comes from a named public source and is collected by the same sync that feeds the scores. They describe the market around crypto; they are context, not signals, and none of them is a forecast. One request returns all of them.
GET /market_cache?cache_key=eq.world_data
Every TODAY index in one row: the latest value, the date of that reading, its change over six windows, and the daily history the site draws its charts from. Refreshed once a day after the US close; the Treasury and central banks publish on their own calendars, so each item carries its own
date.curl "https://wyvwycatgexpbugzkdfw.supabase.co/rest/v1/market_cache?cache_key=eq.world_data&select=updated_at,data" \
-H "apikey: <anon key>" -H "Authorization: Bearer <anon key>"
# the response: data.items.<key> for each index, e.g. the Fed rate
# { "v": 3.88, "date": "2026-10-01", "lo": 3.75, "hi": 4, "c30": 0.25, "c365": -0.21, "s": [ … ], … }
| field (per item) | type | notes |
|---|---|---|
| v | number | latest value, in the unit listed below |
| date | text | the day of that reading (YYYY-MM-DD); for power the first day of its month |
| c7 / c30 / c90 / c182 / c365 / c1095 | number | change over 7 days, 30 days, 3 months, 6 months, 1 year, 3 years. Percent for prices and activity; points for rates and yields (4.00% → 4.40% is 0.40); dollars for crack. null when there is no reading that far back. |
| s | number[] | about one year of daily values, oldest first (monthly for power) |
| s3 | number[] | three years, thinned to about 150 points |
| n7 / n30 / n90 / n182 | integer | how many points of s fall inside that window; s.slice(-n30) is the 30-day line |
| lo / hi | number | fed only: the target range around the effective rate |
| last | object | central banks only: the latest rate decision, {from, to, date} |
| cn | object | oil only: Shanghai crude in dollars, with its own series and its gap to Brent and WTI |
| gwei | number | ethFees only: the current gas price |
| key | what it is | unit of v | source |
|---|---|---|---|
| fed / ecb / boj | US, euro area and Japan central bank policy rates | % | New York Fed, ECB, Bank of Japan |
| us3m / us2y / us10y | US Treasury yields, 3 months, 2 and 10 years | % | U.S. Treasury |
| jgb10y | Japan 10-year government bond yield | % | Japan Ministry of Finance |
| dxy | US dollar index | index | ICE via Yahoo Finance |
| gold / silver | futures, front month | $ per troy ounce | COMEX via Yahoo Finance |
| copper | futures, front month | $ per pound | COMEX via Yahoo Finance |
| aluminum | futures, front month | $ per tonne | COMEX via Yahoo Finance |
| oil / brent | WTI and Brent crude futures | $ per barrel | NYMEX via Yahoo Finance (CL=F, BZ=F) |
| crack | diesel crack spread: heating oil × 42 minus Brent, what refiners earn per barrel | $ per barrel | computed from heating oil (HO=F) and Brent (BZ=F), Yahoo Finance |
| gas | US natural gas futures | $ per MMBtu | NYMEX via Yahoo Finance |
| power | average US household electricity price, monthly, about six weeks behind | $ per kWh | U.S. Bureau of Labor Statistics |
| hash | Bitcoin hash rate | EH/s | Blockchain.com |
| addr | active Bitcoin addresses per day | count | Blockchain.com |
| tvl / stable | DeFi value locked across all chains; stablecoin supply | $ | DefiLlama |
| ethFees / solFees | fees users paid on Ethereum and Solana in a day | $ per day | DefiLlama |
| solDex | value traded on Solana's exchanges in a day | $ per day | DefiLlama |
GET /market_cache?cache_key=eq.network_data
The on-chain readings averaged over 7 days, which is what the TODAY tiles show as their headline value:
hashrateEh, addrCount, tvlUsd, stableUsd, each with its 7-day change (hashrateP7, addrP7, tvlP7, stableP7), 30-day changes in p30, and a month of daily values in series. Refreshed daily.curl "https://wyvwycatgexpbugzkdfw.supabase.co/rest/v1/market_cache?cache_key=eq.network_data&select=updated_at,data" \
-H "apikey: <anon key>" -H "Authorization: Bearer <anon key>"
GET /market_cache?cache_key=eq.fear_greed
The Crypto Fear & Greed Index:
{value, label, asOf, source}, 0 = extreme fear, 100 = extreme greed. One reading a day, from alternative.me.curl "https://wyvwycatgexpbugzkdfw.supabase.co/rest/v1/market_cache?cache_key=eq.fear_greed&select=data" \
-H "apikey: <anon key>" -H "Authorization: Bearer <anon key>"
Showing these on another site? A ready-made panel exists: today-embed.html shows every index with its source, in English or Macedonian, light or dark, and can be placed on any page with one
<iframe>. Please keep the visible link to Rotator and the source of each reading.
<!-- Rotator · TODAY. Options: lang=mk|en, theme=dark|light (default: the viewer's setting), period=7|30|365 -->
<iframe id="rotator-today" src="https://rotatortool.com/today-embed.html?lang=mk&period=30"
style="width:100%;height:1400px;border:0;" loading="lazy" title="Rotator · TODAY"></iframe>
<script>
// optional: size the frame to its content
window.addEventListener('message', function (e) {
if ((e.origin === 'https://rotatortool.com' || e.origin === 'https://rotatortool-official.github.io') && e.data && e.data.rotatorEmbedHeight)
document.getElementById('rotator-today').style.height = e.data.rotatorEmbedHeight + 'px';
});
</script>
Supporting data
GET /coin_events
Technical events detected once a day at 00:45 UTC: 'golden_cross', 'death_cross', 'rsi_overbought', 'rsi_oversold', 'futures_long_crowded'.
curl "https://wyvwycatgexpbugzkdfw.supabase.co/rest/v1/coin_events?select=base_asset,event_date,event_type,value,prev_value&order=event_date.desc&limit=50" \
-H "apikey: <anon key>" -H "Authorization: Bearer <anon key>"
GET /coin_technicals
Per Binance base asset: daily and weekly RSI(14), 60/125-day moving averages, cross state and how many days ago it crossed. Refreshed every 3 hours.
curl "https://wyvwycatgexpbugzkdfw.supabase.co/rest/v1/coin_technicals?base_asset=eq.BTC" \
-H "apikey: <anon key>" -H "Authorization: Bearer <anon key>"
GET /market_cycle
Real 200-day moving average and Mayer multiple (price ÷ MA200) for BTC, ETH, BNB, SOL, XRP and PAXG. Only BTC has calibrated bands: the 2.4× / 0.8× thresholds come from Bitcoin's own history. Treat the other five as raw ratios.
curl "https://wyvwycatgexpbugzkdfw.supabase.co/rest/v1/market_cycle?select=*" \
-H "apikey: <anon key>" -H "Authorization: Bearer <anon key>"
| field | type | notes |
|---|---|---|
| symbol | text | 'BTC','ETH','BNB','SOL','XRP','PAXG' |
| price / ma200 / mayer_multiple | numeric | ma200 is the average of actual daily closes |
| sample_size | integer | closes that went into ma200; a true 200-day average only when this is 200 |
| computed_at | timestamptz | synced once a day |
GET /unified_market_data
Price, 24h change and market cap for every tracked Binance bStock (tokenized equity certificate, not direct share ownership). Crypto prices are in signal_run_items.
curl "https://wyvwycatgexpbugzkdfw.supabase.co/rest/v1/unified_market_data?asset_type=eq.stock&select=*" \
-H "apikey: <anon key>" -H "Authorization: Bearer <anon key>"
What the evidence says
Measured on 771 days of history (2024-06 to 2026-08, about 115 assets a day), not on live results:
· The score ranks in the right direction. A higher score preceded better 30-day relative performance: rank correlation +0.053. The share of each score decile that beat the market median rose from 45.1% (lowest) to 51.8% (highest). It is a small edge, and it was measured on the history the engine was tuned on.
· So the 'buy' zone is not a prediction of outperformance. It marks the low end of the score, the assets that have lagged. The published rotation pairs (sell the five highest scores, buy the five lowest) had a positive 30-day spread on 46.8% of 3,855 pairs, below the ~50% of two assets picked at random. The pairs are published and graded as a test.
· No live accuracy claim yet. Live calls are graded at 30 days on the track record. It will take months of live data before it can confirm or contradict the figures above.
· The score ranks in the right direction. A higher score preceded better 30-day relative performance: rank correlation +0.053. The share of each score decile that beat the market median rose from 45.1% (lowest) to 51.8% (highest). It is a small edge, and it was measured on the history the engine was tuned on.
· So the 'buy' zone is not a prediction of outperformance. It marks the low end of the score, the assets that have lagged. The published rotation pairs (sell the five highest scores, buy the five lowest) had a positive 30-day spread on 46.8% of 3,855 pairs, below the ~50% of two assets picked at random. The pairs are published and graded as a test.
· No live accuracy claim yet. Live calls are graded at 30 days on the track record. It will take months of live data before it can confirm or contradict the figures above.
Rate limits & fair use
No key tiers and no published hard limit yet. This runs on the same infrastructure as the live site, so be reasonable: runs change every 15 minutes, so polling faster buys nothing; cache what you read; select only the columns you need; and do not bulk-scrape in parallel. If you need higher volume or an SLA, get in touch through the CONTACT link on the main site before building against this at scale.
Scope, stated plainly: nothing here is a security audit, a long-term valuation, a usage or TVL metric, or sentiment analysis. The scores measure 7–30 day price momentum, macro-relative strength and, for crypto, supply and unlock mechanics. Nothing else. This is not financial advice; nothing here, machine-readable or not, should be the sole basis for deploying capital.