btc_dominance_anchor
btc_dominance_anchor is a master switch wired to one number: what share of all the money in crypto is sitting in Bitcoin. Every five minutes we ask CoinGecko what percentage of the total crypto market capitalisation Bitcoin accounts for, and you pick a condition off it — "only when dominance is above 55%", "only when it's below 50%". While your condition holds the bot trades; while it doesn't, the bot places no trades at all that cycle, in any market. It gates the whole bot, not just crypto markets.
- Bots using it
- 0 (none live, none out-of-sample, none ever)
- Data source
- CoinGecko /api/v3/global
- Refresh
- Every 5 minutes (*/5 * * * *)
- Rows stored
- 1,611 readings 7 days — the rest is deleted
- Backtest-replayable
- No — see catch #3
- Anchor module
- quants/btc_dominance_anchor.py
The idea behind this signal is a good one, and it is genuinely the sort of thing most people betting on a crypto market never pull up. Bitcoin dominance is the standard read on rotation: when Bitcoin's share of the pot is rising, money is concentrating in the safest thing in crypto; when it's falling, money is fanning out into everything else — what the market calls "alt season". One free HTTP request gets you the number.
The problem is the settings. Every configuration this site ships or suggests for this signal is a constant. The builder's default is "below 50%", which was open 0.00% of the time across every reading we hold. The builder's one-click recipe is "below 50%". The starter bot ships "above 50%", which was open 100.00% of the time. The module's own documented examples are "above 55" (100.00%) and "below 50" (0.00%). Not one of them can change state. A switch that cannot change state is not a switch.
And here the usual excuse — "our window is short, maybe we just caught a quiet week" — doesn't hold, which is what makes this page different from its sibling on the same API call. There is a free, keyless feed carrying four years of daily Bitcoin dominance, and we checked it against our own rows: it agrees to within 0.044 percentage points. So we can say precisely what our seven days can't. Dominance has been below 50% — for 32.3% of the last four years. The threshold isn't silly. It just hasn't been reachable since 2 June 2024, 800 days ago, and nothing in the product says so.
Where the data comes from
One free HTTP request, no key and no account: api.coingecko.com/api/v3/global. Fetched live from the production box while writing this page it answered HTTP 200 in 0.09 seconds and reported Bitcoin at 56.4669% of a $2.28 trillion total, measured across 18,374 listed coins. The signal reads exactly one field out of that response — market_cap_percentage.btc — and rounds it to four decimal places.
predictive/crypto_poller.py makes that call on cron */5 * * * * — every five minutes, 288 times a day — and writes one row with symbol='BTC' into crypto_dominance in predictive.db. The same single response also yields the Ethereum share, the combined stablecoin share and the whole market's 24-hour move, so three signals in our library ride one request: this one, crypto_momentum_anchor and stablecoin_share_anchor. All three have no bot using them.
As of the queries behind this page the table held 1,611 Bitcoin readings covering 4–11 August 2026 — mean 56.6185%, range 56.3388% to 56.8290%, standard deviation 0.102. That is the entire memory of this signal. The poller deletes anything older than seven days on every run.
There is one more source worth naming, because it is the reason this page can be fair rather than merely damning. api.bitcoin-data.com/v1/bitcoin-dominance serves 1,300 daily dominance readings back to 11 August 2022, free and keyless, and returned HTTP 200 from the same box. We are not using it in production and this page makes no recommendation that we should — but we did check it: over the days where it overlaps our own CoinGecko rows it differs by a mean of 0.044 and a maximum of 0.083 percentage points, which is close enough to describe the same series. Every four-year figure below comes from it, and every seven-day figure comes from our own table.
What it means for your bot
This is a global gate, not a market filter. It runs before your bot looks at anything. If dominance isn't where you asked, quants/engine.py returns an empty candidate list and your bot places no trades that cycle — not in crypto markets, in any markets. A weather bot with a dominance gate on it is a weather bot that stops trading when Bitcoin's market share moves. That is occasionally what someone wants, and much more often a surprise.
What the number is actually telling you is where money is concentrated inside crypto, not whether crypto is going up. Dominance can rise in a crash — it usually does, because alts fall faster than Bitcoin — and fall in a rally. If you want "is crypto up today", that is crypto_momentum_anchor. If you want "is Bitcoin above $X", that is crypto_anchor. This one answers a composition question, and it is the only signal in the library that does.
It fails closed, and the check is written properly: if the newest row is more than 90 minutes old the gate returns False and your bot stands down rather than trading on a stale number. That sounds like a low bar until you read the other pages in this dictionary.
The catch
Eight of them, worst first. The first two are the ones that matter; the rest are the kind of thing you only find by reading the code.
1. The builder's default setting has not been able to open since June 2024 — and every setting we ship is a constant
Open the builder, click "BTC dominance", change nothing, and you get {"below": 50}. That is also the entire content of the one-click recipe "Alt-season rotation". The starter bot "Bitcoin-Dominance Watch" ships the mirror image, {"above": 50}, and its own blurb tells you to level up by flipping it to below 50%. The module's documented examples are above 55 and below 50. Measured against all 1,611 readings we hold:
| Setting | Where it appears | Open |
|---|---|---|
| {"below": 50} | builder default · recipe · starter's "level up" | 0.00% |
| {"above": 50} | starter bot as shipped | 100.00% |
| {"above": 55} | docstring example | 100.00% |
| {"above": 56.5} | nowhere — the only setting that discriminates | 86.03% |
| {"above": 57} | nowhere | 0.00% |
Every published setting is either permanently open or permanently shut. The whole week lives inside a 0.49 percentage-point band around 56.6%, and the only threshold anywhere near it that actually changes state is one nobody suggests.
Now the fair half, which is why this is a calibration failure and not a stupidity. Over the four years we can see, below 50 was true on 420 of 1,300 days — 32.3% of the time, and above 50 on 67.7%. Those are real gates with real duty cycles; whoever chose 50 chose a number that genuinely split the world for most of this series' history. What has happened is that the world moved and the defaults didn't. Yearly means run 37.68% (2022) → 46.48% (2023) → 51.93% (2024) → 58.44% (2025) → 56.88% (2026). The last reading below 50 was 2 June 2024. A beginner accepting the builder's default today gets a bot that has been unable to trade for 800 days, and nothing in the interface mentions it.
2. We keep seven days of a number that moves on a four-year arc
Dominance travelled from 35.95% to 63.14% over this series — 27 percentage points. Our retained window covers 0.49 of them. That is not a sampling accident, it is the shape of the variable: this is a slow regime read, and we are evaluating it every five minutes against a memory that is shorter than the time it takes to move anywhere.
The arithmetic of the default makes the point precisely. From today's 56.47%, reaching below 50 requires a fall of 6.47 points. The median week in four years covers a 0.73-point range; the most violent week covers 4.67. The single largest 90-day fall in the entire series is −7.05 points (23 Jun → 21 Sep 2025) — so reaching the builder's default from here would take the worst quarter on record, and would clear it by 0.58 points. Not impossible. Not something to leave as a default with no warning either.
Our week was not unusually quiet. That is worth stating because it is the excuse this signal doesn't get: its 0.49-point range sits at the 23.6th percentile of all four-year weekly ranges — on the calm side, unremarkably so. Even a perfectly typical week would have left every shipped setting exactly as pinned as it was.
3. The backtester can't replay it — and here the usual excuse is refuted
btc_dominance_anchor appears nowhere in quants/backtest.py — not in the supported list, not even in _UNSUPPORTED_ENTRY, the list that documents what can't be replayed. It is caught only by the catch-all, which flags any unknown entry key as unsupported_rules. The consequence is the one to remember: a backtest of a bot with this gate runs without the gate and still returns a curve. You are shown the performance of the bot minus the one rule you were testing.
The standing justification in the code is that these anchors "need a forward-captured series we don't have retroactively". For this signal that is not true. Four years of daily dominance came back HTTP 200 from a free keyless endpoint, in one request, from the production box — and it matches our own rows to 0.044 points. A daily-resolution replay of this gate is available for the cost of reading it. That is a gap in our work, not a gap in the world.
4. "Dominance" is not one number — the vendors disagree by more than half our week's range
Bitcoin's dominance is a fraction, and nobody agrees on the denominator. Asked at the same instant, three times in a row: CoinGecko says 56.4669% across 18,374 listed coins; CoinPaprika says 56.1500% across 12,256. The gap is 0.32 percentage points — 65% of the entire range our retained week covers, and roughly three standard deviations of it.
This matters more than it sounds. The one threshold band that discriminates at all in our data is around 56.5. A gate set there would be open 86% of the time on our vendor's number and would have a materially different duty cycle on someone else's, with no market having moved. When you tell a friend "dominance is 56.5%", you are quoting a listings policy as much as a market.
5. A ninth of the denominator is stablecoins, which by construction cannot rotate anywhere
Measured from our own crypto_dominance rows, stablecoins are 11.16% of total crypto market cap on average across the week (11.21% at the latest reading). They sit in the denominator of every dominance figure quoted on this page. They are also, definitionally, not participating in the risk rotation the signal is supposed to detect — a dollar parked in USDT is not "in altcoins".
Take them out and the same instant reads 63.60% instead of 56.47%. Across the whole week that definition runs 63.53–63.93%. The practical consequence, on identical data: {"above": 60} is open 0.00% of the time as we report it and 100.00% of the time with stablecoins removed. Neither is wrong; they answer different questions. We offer only the first and don't say which one it is.
6. Blind for roughly a quarter of the clock, by design
Over the retained window we stored 1,611 readings against 2,187 expected five-minute cycles — 73.7% coverage. The dominance write lives in its own try/except inside the poller, deliberately, so a failure there can never break the core coin-price polling. That design is sound and it has a price: this signal's row is the first thing dropped whenever the database is contended.
The poller's log across its whole life carries 890 "dominance step failed" lines, 421 "dominance fetch failed", 4,494 database is locked errors, 112 HTTP 429 rate limits and 299 timeouts. (The log has no timestamps, so those are lifetime counts and can't be attributed to our window; the 73.7% coverage figure can.) This is the same shared-predictive.db write contention documented on the crypto_momentum_anchor, energy_anchor, tropical_anchor and sentiment_anchor pages.
For this particular signal the damage is mild, and it would be unfair not to say so: dominance barely moves between five-minute samples, so a missed cycle costs almost nothing. The gaps only matter because of what they do to the record, and the record is already being deleted.
7. The prune keeps a day more than it claims
The poller runs DELETE FROM crypto_dominance WHERE fetched_at < datetime('now','-7 days'). The stored timestamps are ISO-8601 (2026-08-04T00:10:02+00:00) and SQLite's datetime() returns 2026-08-04 00:10:02 — the comparison is textual, and 'T' sorts after a space, so nothing on the cut-off date is ever deleted. Real retention is seven days plus a partial eighth: our window is 182.2 hours, not 168. It is the same string-vs-timestamp mistake that breaks the freshness checks on finance_anchor and tropical_anchor — here it is harmless, and it is worth knowing the pattern is in the codebase in several places. Not fixed: this loop doesn't edit pollers.
8. Three configs that validate and can never open, and a band whose English contradicts itself
validate_config checks that your number is between 0 and 100 and nothing else. So {"above": 100} validates and can never be true. {"below": 0} validates and can never be true. {"above": 60, "below": 50} validates and is unsatisfiable — and renders in the builder as "only when BTC dominance > 60% (money rotating into Bitcoin) and BTC dominance < 50% (alt-season — money rotating into altcoins)", which describes two opposite regimes at once without noticing. This is the same family of bug as the VIX example on finance_anchor.
Worse for anyone using it properly: the genuinely useful shape here is a band, and the English mangles it. {"above": 50, "below": 60} — "somewhere in the normal range" — is a sensible rule, and it reads back as "money rotating into Bitcoin and alt-season — money rotating into altcoins". The rule is correct; only the description is wrong. Ignore the preview.
The case for the defence
Four things here are genuinely right, and the criticism above would be dishonest without them.
- The freshness check is correctly written. It parses fetched_at into a real Python datetime and compares actual instants — it does not have the string-comparison bug that defeats the same check on finance_anchor and tropical_anchor. It also fails closed on an unparseable timestamp rather than guessing.
- It has never had to fail closed. The threshold is 90 minutes; the largest gap between readings in the entire retained window is 35 minutes, and there are zero gaps over 90. Coverage is poor in aggregate but never sustained-poor. (Any longer outage before 4 August was deleted along with everything else.)
- A gate that rarely switches is not automatically broken. This is a regime filter, and regimes are supposed to persist — "open for months, shut for months" is the correct behaviour for this kind of signal, and 32.3% / 67.7% over four years is a perfectly respectable duty cycle. The defect is not that the gate is steady; it is that at the settings we ship it cannot change at all, and that our own records are too short to tell the difference between "correctly waiting" and "broken".
- The signal itself is real and cheap. Rotation between Bitcoin and everything else is a genuine market phenomenon, the number is free, keyless and arrives in 0.09 seconds, and it rides a request we were making anyway. Nothing about the idea is wrong. It is the defaults, the retention and the replay that let it down.
The real record
There isn't one. No bot has ever used this signal — not live, not out-of-sample, not archived, not a draft. A search of all 76 strategy configs on the box (17 live, 19 out-of-sample, 36 archived, 4 drafts) and all 88 saved strategy versions returns zero matches for btc_dominance_anchor. There are no trades, no fills, no evaluations and no profit or loss to report, and this page will not manufacture any.
The anchor module has sat unmodified since 15 June 2026 — 57 days of a number nothing reads, written to disk 288 times a day and deleted a week later. Its two siblings on the same /global call have no bot either. That is the honest state of this tier of the library, and it is the reason these pages exist.
What we can say is what a bot would have experienced. Shipped as the starter bot has it, the gate would have been open every cycle of every day — no filtering at all. Shipped as the builder defaults it, the bot would not have placed a single trade, and would have looked identical from the outside to a bot whose poller had died.
How to actually use it
Set it near where the series actually is, not at a round number. 50 and 55 are folklore thresholds from a different regime. Today the number lives around 56.5; a gate within a point of that is the only kind that will change state this year. Look at the reading before you pick the level — the builder shows it.
A level is a weak way to ask this question. What you almost certainly mean by "alt season" is a direction — dominance falling — not a level. We don't offer that: there is no change-over-N-days or percentile form of this anchor, only above/below. Until there is, the honest version of the rotation trade is not expressible here, and a level gate is a rough proxy for it. Saying that plainly is better than pretending the level is the thing.
Remember it gates the whole bot. If your markets aren't crypto, this signal will still silence them. That is rarely what people intend when they add it.
Don't read a quiet run as a verdict. A backtest containing this signal ran without it. A live bot that never trades might be a shut gate, a rate-limited poller or a locked database, and from the outside those look identical — check the newest row in crypto_dominance before concluding anything about the market.
Where to go next
crypto_momentum_anchor is the closest relative — same API call, same poller, same isolated try/except, same seven-day prune, same zero bots. Read them together: that one is a fast variable whose thresholds are too far out, this one is a slow variable whose thresholds are in the wrong decade. The failure looks identical from the builder and has opposite causes. sentiment_anchor is the instructive contrast on retention: another global crypto gate whose threshold is never met, but it keeps 8.5 years of history, which is why that page can prove a rare setting is merely rare. finance_anchor is where the validate-anything bug does real damage, on a signal that has a bot attached.
stablecoin_share_anchor — the third signal on this same request, and the one that reads the denominator discussed in catch #5 — has no profile page yet.
Written 11 August 2026. Every number on this page was read from production that day — the live host at /var/www/tinycorp.ai, not a local mirror: the output of quants/signal_registry.py, the source of quants/btc_dominance_anchor.py, predictive/crypto_poller.py, quants/engine.py, quants/backtest.py, static/builder-signals.js and static/starters.js, the live crontab, the poller's log at /var/log/crypto-poller.log, read-only queries against crypto_dominance in predictive.db and all 76 strategy configs and 88 strategy versions in quants.db, and the CoinGecko, CoinPaprika and bitcoin-data.com endpoints fetched directly from that same host. Duty-cycle and distribution figures are a single consistent snapshot taken at 14:25 UTC; the table grows by one row every five minutes and is pruned daily, so counts move. The four-year figures are daily readings from a third-party feed that agrees with our own to 0.044 percentage points — they are cited as corroboration, not as a production data source. No performance figures appear on this page because no bot has ever used this signal. All signals →