crypto_momentum_anchor
crypto_momentum_anchor is a master switch wired to one number: how much the entire crypto market is worth today versus yesterday. Every five minutes we ask CoinGecko for the total market capitalisation of all crypto and its 24-hour percentage change, and you pick a condition off it — "only when the market is up more than 2%", "only when it's down more than 5%". 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,595 readings 7 days — the rest is deleted
- Backtest-replayable
- No — see catch #5
- Anchor module
- quants/crypto_momentum_anchor.py
This is the first page in the dictionary about a signal no bot has ever used. We poll it 288 times a day anyway. That is worth saying plainly, and it is the honest headline for most of what follows in this tier — but it is not the interesting part of this one.
The interesting part is that this site recommends three settings for this signal, and all three of them were shut 100% of the time across every reading we hold. The builder's default is "up more than 2%". The builder's one-click recipe is "up more than 2%". The starter bot's own instructions tell you to level it up to "+3%" or flip it to "down more than 5%". Against 1,595 readings the count of times each of those would have let a trade through is zero, zero, zero.
And then the part that stops that from being a dunk: we cannot tell you whether that is normal, because we delete the data after seven days — and the seven days we happen to be holding are among the calmest 3% of the last year. Measured over the past 365 days, a move of more than +2% is roughly a one-day-in-sixteen event. The thresholds aren't absurd. Our evidence is just seven days long, and it is seven days of nothing happening.
Where the data comes from
One free endpoint, one field. api.coingecko.com/api/v3/global is public, keyless and needs no account. Read live from the production host at 11:30 UTC on 11 August 2026 it returned HTTP 200 and, in the field market_cap_change_percentage_24h_usd, the value −0.934% against a total_market_cap.usd of $2.28 trillion across 18,374 tracked coins on 1,507 exchanges. The newest row in our own table at that moment read −0.9343% — the same number, which is the check that the pipe is actually connected.
predictive/crypto_poller.py fetches it on cron */5 * * * * — 288 times a day — and writes it into crypto_prices in predictive.db as a synthetic row with symbol='total-market', sitting alongside the five real coin rows the poller keeps (bitcoin, ethereum, solana, ripple, dogecoin). The row reuses the table's existing columns: usd_24h_change_pct holds the percentage, and the column named usd — which for every other row holds a coin price — holds the entire market's capitalisation, currently about 2,281,958,293,205. That is a deliberate reuse noted in the poller's own comments, not an accident, but it does mean any query that treats crypto_prices.usd as "a price" will find a $2.28 trillion outlier in it.
The same single /global call also carries the market_cap_percentage map that feeds two other signals in this library — btc_dominance_anchor and stablecoin_share_anchor — so three signals cost one request. That is good design, and it is also why they fail together.
What it means for your bot
It is a whole-bot switch, not a filter. In quants/engine.py the check runs before any market is examined, and a failed check returns an empty candidate list for the entire strategy — the bot does not trade a smaller set of markets that cycle, it trades nothing. Put this on a bot that also trades weather or sports contracts and those markets are gated on the crypto tape too. The starter bot we ship scopes itself to crypto markets, which is the sane pairing, but nothing enforces that.
Practically, the number answers one question: is the whole asset class up or down today? Not "is Bitcoin above $100k" — that is crypto_anchor, a different signal on a different row of the same table. Not "are people fearful" — that is sentiment_anchor. This one is the cap-weighted daily move of everything.
The config takes above, below, or both, as a percentage between −100 and 100. Both together is an AND, which is more useful than it sounds: it is the only way to express a band — {"above": -1, "below": 1} means "only when the market is quiet", and that would have been open 87.15% of our week. The builder cannot build it, because its direction control is a single either/or dropdown; you would have to write the JSON.
The catch
Every signal has one. This one has several, and the first is the one that makes the rest hard to settle.
1. Every threshold this site recommends was shut all week — and we deleted the evidence that would tell you whether that's normal
Three places on this site suggest a setting for this signal. The builder's number field defaults to 2. The builder's one-click crypto recipe, "Risk-on momentum", is {"above": 2}. The starter bot "Crypto Momentum (Up-Day)" ships with {"above": 0} and tells you in its own blurb to "raise it to +3% for a stronger breakout filter, or flip it to a dip-buy that only fires when the market is down more than 5%". The module's docstring gives the same two examples, {"above": 2} and {"below": -5}.
Counted against all 1,595 readings we hold:
| Setting | Where it comes from | Open |
|---|---|---|
| {"above": 0} | the starter bot, as shipped | 1,198 / 1,595 · 75.11% |
| {"above": 2} | builder default + its recipe + docstring | 0 / 1,595 · 0.00% |
| {"above": 3} | the starter's "level up" advice | 0 / 1,595 · 0.00% |
| {"below": -5} | the starter's "level up" advice + docstring | 0 / 1,595 · 0.00% |
The full range of the week was −1.79% to +1.67%, standard deviation 0.64. Not one reading came within a third of a percentage point of the builder's default. Counting by day rather than by reading, so the units match a daily statistic: on 0 of the 8 calendar days in the table did the number ever touch +2% at any moment. The best any single day managed was +1.67%, on 4 August.
Now the part that stops this being a dunk. Over the last 365 days a day moving more than +2% happened 12.6% of the time — but that is measured on Bitcoin, which swings wider than the index this signal reads, so it flatters the threshold. Scaled by the 0.752 beta in catch #3, clearing +2% on the total is closer to 6.3% of days, about one day in sixteen. It is a real, recurring condition. The thresholds are defensible.
What is not defensible is that our own records cannot tell you any of that, because the poller deletes everything older than seven days, and the seven days it happens to be holding rank 12th of 359 seven-day windows by volatility — the calmest 3% of the year. A user reading their own bot's history would conclude the +2% gate is broken. It isn't. Our memory is.
The one recommendation that does not survive the same defence is the starter bot's suggested dip-buy. Scaled the same way, a −5% day on the total works out at roughly 0.5% of days — about one day in two hundred. Told to "flip it to a dip-buy", a user would be building a bot that expects to trade on one or two days a year, and nothing on the page says so.
2. It is 91.6% Bitcoin, and we sell it as something only we compute
Our own copy, in the builder and the starter both, calls this "a cap-weighted, broad-market momentum read most bettors never compute". Measured against our own table: across 1,578 timestamps where we stored both, the correlation between the total-market 24h change and Bitcoin's own 24h change is 0.957, an r² of 0.916. Nine-tenths of this signal's movement is explained by a number printed at the top of CoinGecko's homepage and every other crypto site on the internet.
We are not presenting that as a shock. It is close to what you would predict: Bitcoin is 56.6% of the index by weight, and most of the rest trades like Bitcoin, so a high correlation is arithmetic before it is evidence. The residual 8% is real but small. The finding isn't "we discovered it's Bitcoin" — it's that the number is a damped Bitcoin, and our copy sells it as a distinct broad-market factor.
Two claims are bundled in that sentence and they deserve separating. Distinct is the one the correlation refutes. "Most bettors never compute it" is probably fair as far as it goes — most people betting on prediction markets are not pulling CoinGecko's global endpoint — but the phrasing implies proprietary work, and this is one HTTP request to a free, keyless URL. The honest version is that most bettors never gate a bot on it, which is true and much smaller. We should fix that copy; this page is where we admit it first.
3. The number is systematically damped, so a threshold that feels right is about a third too demanding
Total market cap moves less than the coins in it. Our measured beta of the total-market reading against Bitcoin's is 0.752 — when Bitcoin moves 1%, the headline total moves about ¾ of a percent. Across the same week: Bitcoin's own 24h reading ranged ±2.2%, Ethereum's ±3.0%, Solana's −2.5% to +4.4%, Ripple's ±3.6% — and the total ranged only −1.79% to +1.67%. The aggregate is the calmest series on the board.
The main mechanical reason is in our own data: stablecoins are 11.16% of the total market cap on average across the week (we store this, for stablecoin_share_anchor). A ninth of "the crypto market" is pinned to a dollar and cannot move. Even if every non-stablecoin coin moved in perfect lockstep with Bitcoin, that ballast alone would cap the beta at 0.888; the rest of the gap is imperfect co-movement between coins.
The consequence is practical: if you have "Bitcoin up 2%" in your head and you type 2 into this field, you have asked for something meaningfully rarer than you think. To fire on the same days, scale your instinct down — multiply by about 0.75, so a "+2% Bitcoin day" is roughly {"above": 1.5} here. In our week even that was open on just 12 of the 1,595 readings.
This is also, we think, the honest explanation for catch #1. The recommended settings are round numbers that feel right for Bitcoin — 2%, 3%, 5% — pointed at an index that moves a quarter less than Bitcoin does. That is a better description of what went wrong than "the defaults never fire": nobody picked absurd numbers, they picked Bitcoin-shaped numbers for a quieter series, and then had no history to check them against.
4. The signal is blind for a quarter of the clock, and it is designed to be the first thing dropped
Over the window we hold, 2,151 five-minute cycles should have produced a reading. We have 1,595 — 74.2% coverage, blind 25.8% of the time. The gap splits two ways. On 252 cycles the poller finished and wrote all five coin rows but no total-market row — the reading was lost on its own. The remaining ~304 produced no rows at all: a run that crashed, or a cycle that never completed.
The reason is a design decision that is defensible and still bites. The /global fetch and its write sit inside their own try/except, explicitly so that a problem there "can never break the core price polling". It works — but it means that when predictive.db is contended, the coin prices survive and this signal's row is the one thrown overboard. Lifetime, the poller's log shows 869 dominance-step failures with database is locked (plus 2 with database or disk is full), and separately 1,042 runs that died outright on the same lock at the price insert, with a further 190 crashing elsewhere in the same job — 1,230 OperationalErrors in all.
This is the seventh job we have found on the same shared-predictive.db write contention, after resolution_buckets, sentiment_poller, eia_poller, tropical_poller, space_launch_poller and this poller's own price step. On top of that, the vendor side has its own losses: 250 read timeouts, 111 HTTP 429 "Too Many Requests", 31 HTTP 400 and 21 assorted gateway errors.
A fair challenge to catch #1 lives here: if the dropped cycles were the volatile ones, we deleted the evidence and then mocked the threshold. It is a real worry — vendors rate-limit hardest when everyone is hitting them, which is when the market is moving. We can rule it out for this particular week, because the five coin rows survive the cycles the total-market row doesn't. Bitcoin has 253 readings the momentum row is missing, and across all of them Bitcoin's own 24h change never exceeded +2.22% — which at the 0.752 beta in catch #3 implies a total of +1.67%, precisely the highest total we recorded. Nothing was hiding in the gaps. That check works only because a better-covered series sits in the same table; it is not a general defence.
Those 429s are worth a line of their own, because the poller's docstring asserts the opposite: "CoinGecko's free tier allows ~10-30 calls/min — we hit it every 5 min, so 12 calls/hour. Well under any limit." The arithmetic is wrong (we make two CoinGecko calls per cycle, so 24/hour, not 12) and the conclusion is wrong: we have been rate-limited 111 times. Nothing retries, and a rate-limited cycle simply has no reading.
5. The backtester cannot replay it — and it isn't even on the list of things that can't be replayed
A backtest that includes this signal is not testing this signal. The replay loop has no historical total-market series to look up, so it runs the strategy without the gate and still returns a curve. The result is flagged as unsupported_rules, but the curve is still a curve, and a curve is what people look at.
There is a small twist here worth knowing. quants/backtest.py keeps a hand- maintained list, _UNSUPPORTED_ENTRY, of anchors it can't replay — crypto_momentum_anchor is not in it; it was added to the library after that list was last curated and nobody went back. What saves it is that the real check was rewritten to flag every entry key not on the supported list, so a forgotten anchor gets caught anyway. The safety net did its job. The list, though, is now documentation that is quietly out of date.
Unlike energy_anchor, where the vendor happily serves decades of history we simply never ask for, the excuse here is half fair. CoinGecko does not give away the total-market-cap history: its /global/market_cap_chart endpoint returned HTTP 401 Unauthorized to us today for every window we tried. But the half that isn't fair is worse than an excuse — we have been generating this series ourselves, every five minutes, since 17 June, and deleting it. See catch #6.
6. We throw away exactly the history that would make the signal usable
The poller ends every run with a prune: delete everything in crypto_prices older than seven days. That is sensible for five coin prices nobody needs a year of. It is destructive for this signal, because the whole question a user has — "how often would my threshold actually fire?" — is a question about history, and seven days of it is not enough to answer anything. Our table holds eight calendar dates.
Two smaller notes on the same statement. It compares an ISO-8601 timestamp against SQLite's datetime() format as text, and because 'T' > ' ' no row from the cut-off day itself is ever removed — so real retention is seven days plus a partial eighth, which is why our window is 179 hours rather than 168. And a row costs roughly nothing: keeping a year of this one series would be about 105,000 rows.
Nothing on this page proposes changing the poller — that is outside what this loop is allowed to touch, and it is a live job. But it is the single change that would move this signal from undecidable to testable.
7. Two configs that validate and shouldn't, and one that works while the English lies about it
{"above": 2, "below": -5} passes validation and can never be satisfied — it asks for a market that is simultaneously up more than 2% and down more than 5%. It renders in the builder's plain-English preview as a sentence joined by "and", with no warning. This is the same class of defect as the {"^VIX", above: 20, below: 10} case on finance_anchor; the builder's own controls can't produce it, but the JSON path can.
More interesting is the config that does work. {"above": -1, "below": 1} is a genuinely useful "quiet market" band, and the gate handles it correctly. But to_english hard-codes a label onto each clause, so it reads: "only when the total crypto market cap's 24h move is above −1% (risk-on momentum) and the total crypto market cap's 24h move is below 1% (risk-off / capitulation)" — describing a flat, boring market as a rally and a crash at the same time. The rule is right; the sentence explaining it to the user is nonsense.
Also permitted: {"above": 100}. The validator's range is −100 to 100, so "only when all of crypto doubles in a day" is a legal setting.
8. "Momentum" is a 24-hour return, and market cap isn't price
Two smaller honesty points about the name and the number.
Momentum, in the research sense, is persistence measured over weeks or months. This is a single day's return. Calling it momentum is a marketing choice, and a one-day return is much closer to noise than to a trend — which matters, because "trade with the trend" is the reason the starter bot gives for using it.
Market cap is price × supply, across a changing list of coins. The denominator moves: 18,374 coins are tracked today, new listings enter, tokens unlock, stablecoins are minted and redeemed. So a move in this field is not purely a move in prices. We have not measured how large that contamination is on any given day and we are not going to guess at it — but it is a real difference between this number and "the price of crypto went up", and anyone gating money on it should know the difference exists.
The case for the defence
Four things about this signal are genuinely well built, and it would be dishonest to bury them under the above.
- Its freshness check is correct. It parses fetched_at into a real datetime in Python and compares actual timestamps. That sounds like a low bar until you know that finance_anchor and tropical_anchor both compare ISO strings against SQLite's format and silently accept stale data as fresh. This one does not have that bug.
- It has never needed 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, so the gate has always had a real number to answer with. (Any longer outage before 04 August was deleted along with everything else, so this covers the retained window only.)
- The poller's isolation design works as intended. Catch #4 is the cost of a decision that also has a benefit: coin prices have never been lost to a /global problem, and three signals share one HTTP request instead of three.
- The +2% threshold is not silly. Scaled to this index it is about a one-in-sixteen day over a normal year — infrequent, which is what a regime filter is for. Someone chose a sensible number. They just chose it for a signal whose own records can't corroborate it, on a field damped a quarter below the Bitcoin move most people picture. (The −5% suggestion is the one that doesn't survive this defence — see the end of catch #1.)
The real record
There isn't one. No bot has ever used this signal — not live, not out-of-sample, not archived. A search of all 76 strategy configs on the box (17 live, 19 out-of-sample, 36 archived, 4 drafts) returns zero matches for crypto_momentum_anchor. There are no trades, no fills, no evaluations and no profit or loss to report, and this page will not manufacture any.
That is the honest state of this tier. The row has been written every five minutes since 17 June 2026, when the field was added to the poller and this anchor was created on the same day — about 55 days of a number nothing reads. It is not free either: the /global call it rides on is 288 requests a day, it takes a share of the write contention in catch #4, and every row it writes is deleted a week later. The two other signals fed by that same call have no bot using them either.
What we can say is what a bot would have experienced, and the table in catch #1 is exactly that: as shipped, the starter bot's gate would have been open 75.11% of the time — which is to say it would have been an approximate coin-flip on whether the market was green, not a momentum filter. At the builder's default it would not have traded once.
How to actually use it
Translate your threshold before you type it. The number in your head is probably a Bitcoin number, and this index moves about a quarter less. Multiply it by roughly 0.75 to fire on the same days — "Bitcoin up 2%" is about 1.5 here. Type your Bitcoin number in unchanged and you will get something markedly rarer than you intended, because a ninth of the market is stablecoins that never move.
If you actually want Bitcoin, ask for Bitcoin. crypto_anchor reads the coin rows in the same table and gives you a clean price condition, with none of the market-cap contamination in catch #8. Use this signal when you specifically mean "the whole asset class", and be able to say why that matters for the contract you're betting on.
The band is the underused shape. {"above": -1, "below": 1} — "only when nothing is happening" — is the one configuration here that expresses something the other crypto signals can't, and it was open 87.15% of our week. Ignore the English preview; the rule itself is correct.
Don't read a quiet backtest as a verdict. A backtest containing this signal ran without it. A live run 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 reading in the table before you conclude anything about the market.
Where to go next
sentiment_anchor is the closest relative and the instructive contrast — the other global crypto gate, the other one whose threshold has never been met. But it keeps 8.5 years of daily history, which is why that page can tell you a rare setting is rare and this one cannot. Same problem, opposite retention policy. finance_anchor is the other every-five-minutes market-level gate, and the place to see the freshness bug this signal avoids. launch_anchor is the other switch that isn't a switch — there, stuck open on almost every day; here, stuck shut at the settings we recommend.
Its two siblings on the same /global call — btc_dominance_anchor and stablecoin_share_anchor — have no profile pages yet. They read composition rather than direction, and they share every availability catch on this page, because they arrive in the same request and die in the same try/except.
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/crypto_momentum_anchor.py, predictive/crypto_poller.py, quants/engine.py and quants/backtest.py, the live crontab, the poller's log at /var/log/crypto-poller.log, read-only queries against crypto_prices and crypto_dominance in predictive.db and all 76 strategy configs in quants.db, and CoinGecko's own endpoints fetched directly from that same host. The distribution and duty-cycle figures are a single consistent snapshot taken at 11:30 UTC; the table grows by one row every five minutes, so counts move. The yearly base rate is Bitcoin's market cap used as a stand-in for the total, because CoinGecko returns HTTP 401 for total-market history on the free tier — it is a proxy, labelled as one, and Bitcoin is 56.6% of the aggregate it stands in for. Because Bitcoin swings wider than that aggregate, the raw proxy overstates how often this signal clears a threshold; where a figure is described as "scaled", it has been adjusted by the measured 0.752 beta, and that adjustment is a model, not a measurement. The daily counts in catch #1 are counted per calendar day so that they compare like-for-like with a daily base rate. The validation behaviours in catch #7 were confirmed by running the real validate_config and to_english functions against synthetic configs — no production data was written. All TinyCorp bots trade simulated money. Nothing on this page is a projection.