Writing up a small fix, because its failure mode is worth recording: nothing in the chain ever threw an exception or logged a warning. The log said “collection started” on schedule, and not a single row reached the database.
What was happening
The opportunity scanner sweeps both the USDⓈ-margined and coin-margined markets, while funding-rate history is fetched through the USDⓈ-margined endpoint UsdFuturesApi.ExchangeData.GetFundingRatesAsync.
Ask that endpoint about a coin-margined contract — BTCUSD_PERP, say — and what comes back is an empty page with Success still set to true. No exception, no warning.
So every cycle repeated the same loop: the coin-margined contract was admitted as “a new pair with no funding history yet”, an API call was spent on it, nothing could be stored; and on the following cycle, still having no history, it was admitted as new all over again.
Nineteen _PERP contracts ran that loop in production for a long time. The row count for _PERP in the FundingRateTicks table was zero, while the log reported “collection started this cycle” punctually every 15 minutes.
It was also spending someone else’s budget
This is the actual reason it needed fixing.
Each cycle has a cold-start ceiling — the FundingRateMaxColdStartsPerTick setting — which is effectively a quota. With that quota occupied by a dozen-odd contracts that could never be collected, the pairs genuinely waiting to backfill history had to wait an extra cycle each time.
So the fix sits before the quota is taken, not at the point of storage. Blocking at storage would only save a write; blocking before the quota hands the slots back.

Why the test is on the symbol’s shape
Coin-margined symbols carry an underscore (BTCUSD_PERP), and that is the discriminator. The rule has to be pinned on both sides:
- Loosen it a notch and
ETHUSDC,AAVEUSDC,ADAUSDCand other USDC-margined contracts get caught too. Those collect perfectly well and have history in the database. Excluding them would not raise an error either — it would just render those coins as “no recent data” on the page. Another silent failure. - Tighten it a notch and coin-margined leaks back in, and the loop resumes.
The count of excluded symbols was folded into the existing “collection started this cycle” line rather than given its own entry. Coin-margined is a permanent state; a dedicated log line for it would reintroduce the same spam under a new name.
On the difference between USDC- and USDT-margined funding, we have the comparison separately: 33 of 34 paired contracts show the USDC leg higher.
What the database looks like after the fix
As of 2026-10-08 06:00 +08:00 the funding-rate store holds 1,029,262 settlement records across 534 symbols, with the earliest period reaching back to 1 January 2020.
Only two of those symbols — BTC and ETH — carry more than two years of continuous history, at 7,416 periods each; the overwhelming majority of the rest only began collection after August 2025. That is worth confirming before reading any “year-by-year trend”: drawing six years of direction from one year of history invents data, which is why the trend chart carries only those two symbols.
Why publish this kind of thing
Because this failure class is precisely the one that should be visible from outside.
Failures that throw announce themselves. Failures that do not — an endpoint returning empty with a success status, a retry loop that never persists, a log that reports the start and never the result — surface only when somebody reconciles. Whether a platform’s claimed history count can be trusted depends entirely on whether anybody is doing that.
It is the same reason we set out what can actually be checked: balance, trade-by-trade detail, positions, the return curve, subscription records and withdrawal progress are all available to the investor directly. Only what can be checked counts.
Related change
One more in the same batch: the next-period funding forecast now displays its measured hit rate inline. We published the scorecard for that previously — the results across 11,472 forecasts are in that article — and the figure now sits next to the forecast rather than requiring a trip to the write-up.
Sources: the failure description and the fix come from this project’s own repository commit history (2026-09-17, touching FundingRateSymbolScope, FundingRateCollectorJob and the corresponding unit tests). Database scale figures come from the backend period-by-period snapshot as of 2026-10-08 06:00 +08:00.
