fix: Volatility (24h) N/A — replace broken CryptoCompare source#66
Conversation
…inbase + correct formula CryptoCompare's free histohour endpoint now requires an API key (HTTP 401), so volatility_24h was always NULL in production, causing the dashboard Volatility (24h) card to render 'N/A' and volatility_state='Unknown'. - calculate_volatility now fetches hourly closes from CoinGecko (primary), falling back to Coinbase public candles (no API key required). - Fixed the realized-vol formula: previously annualized an already-windowed std (std * sqrt(24*365)), inflating values to ~50-148%; now uses std * sqrt(n) for the window, yielding a sane ~1-6% 24h realized vol that matches the get_volatility_state thresholds. - Added tests/test_regime_volatility.py covering primary, fallback, both-fail, and insufficient-data paths plus formula scale.
protostatis
left a comment
There was a problem hiding this comment.
Sky's Code Review
Replaces broken CryptoCompare hourly-price source (now requires API key, returns 401) with CoinGecko as primary and Coinbase as fallback for the volatility calculation. Also corrects the realized-vol formula from annualized (inflated ~50-148%) to window-scaled (~1-6%). Tests cover primary, fallback, both-fail, insufficient-data, and formula scale. Changes are clean and well-structured.
Verdict: Approve
Comments
- The formula fix from annualized (
std * sqrt(24*365)) to window-scaled (std * sqrt(n)) is correct and well-explained. The new values (~1-6% 24h realized vol) align with the existingget_volatility_statethresholds (<2 Low, <4 Moderate, <6 High), which would have been unreachable with the old annualized formula. - No security concerns: no secrets/tokens exposed, no user input flows to shell commands or URL construction unsafely, hardcoded constants for API endpoints.
- The PR description is thorough — root cause, data source evaluation (Binance rejected for geo-blocking, CryptoCompare needs paid key), and live verification all documented.
Reviewed by Sky — Unchained Sky engineering agent
Inline Comments (could not attach to lines)
crypto_sentiment_crawler/confounders/regime.py:155 — Both except Exception blocks in _fetch_hourly_closes silently swallow all errors (including TypeError, json.JSONDecodeError, etc.). For a fallback pattern this is acceptable, but consider logging at warning level (not debug) for the primary source failure since it indicates a real degradation that operators should notice in logs. The fallback can stay at debug.
crypto_sentiment_crawler/confounders/regime.py:165 — The CoinGecko free/demo API has aggressive rate limits (~10-30 req/min). Consider adding a brief comment noting this, since the fallback to Coinbase exists specifically for this reason. If this endpoint is called for multiple coins (BTC, ETH, SOL) in quick succession, rate-limiting is likely. No code change needed — the fallback handles it — but a comment would help future maintainers.
crypto_sentiment_crawler/confounders/regime.py:181 — Coinbase candles endpoint has a default limit of 300 candles and returns an empty array (not an error) when no data exists for the time range. The if candles: guard handles the empty case, but the code will silently return an empty list if Coinbase returns [], which then falls through to the 'Insufficient price data' error at the caller. This is correct behavior — just noting it for clarity.
crypto_sentiment_crawler/confounders/regime.py:199 — The days calculation max(1, (hours + 1) // 24 + 1) for hours=24 gives days=2, which requests ~48 hourly data points from CoinGecko. This is more than the 25 needed but harmless — the extra closes just extend the return series. Works correctly.
tests/test_regime_volatility.py:62 — Good use of deterministic oscillation (math.sin(i/3.0)) for reproducible test data. Consider adding a test for Coinbase returning an empty array [] (distinct from a 4xx/5xx error) to verify that path is also handled correctly.
Summary
The Volatility (24h) card on the dashboard shows N/A in production because
volatility_24his alwaysNULLin theconfounderstable.Root cause:
RegimeCollector.calculate_volatilitypulled hourly prices from CryptoCompare'shistohourendpoint, which now requires an API key (HTTP 401). The failed request was caught and stored asNULL, which flows toget_volatility_statereturning"Unknown"and the frontend renderingN/A.Changes
market_chart(hourly) as primary, with Coinbase public candles as fallback. Both verified reachable from the environment; Binance was rejected (geo-blocked, HTTP 451) and CryptoCompare needs a paid key.std * sqrt(24*365)), inflating values to ~50–148%; now usesstd * sqrt(n)for the window, yielding a sane ~1–6% 24h realized vol that matches the existingget_volatility_statethresholds (<2Low,<4Moderate,<6High).tests/test_regime_volatility.pycovering primary path, Coinbase fallback, both-fail, insufficient-data, and formula scale (5 tests).Verification
64 passed).Test plan
confounders.volatility_24hpopulates in prod DB🤖 Generated with OpenCode