feat(macro): Macro Vibes — 8-indicator daily snapshot + composite score
New backend pipeline: 8 free public macro signals fetched in parallel,
upserted once per calendar day, served via /api/macro/{snapshot,history}.
- AHR999 (computed from Binance 200d klines)
- Altcoin Season Index (CoinGecko top-50 30d)
- Fear & Greed (alternative.me)
- BTC dominance, ETH/BTC ratio
- Stablecoin supply (DeFiLlama)
- Spot BTC ETF net flow (Farside)
- BTC perp open interest (Binance fapi)
Each fetcher is independently @_none_on_fail decorated so one outage
can't take down the snapshot; scoring renormalises across whichever
indicators returned a value. Daily cron at 03:00 UTC; on startup a
fire-and-forget bootstrap fills today's row if missing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+33
-1
@@ -1,9 +1,10 @@
|
||||
from datetime import datetime, timezone
|
||||
from datetime import date, datetime, timezone
|
||||
from typing import List, Optional
|
||||
|
||||
from sqlalchemy import (
|
||||
BigInteger,
|
||||
Boolean,
|
||||
Date,
|
||||
DateTime,
|
||||
Float,
|
||||
ForeignKey,
|
||||
@@ -412,3 +413,34 @@ class TelegramBinding(Base):
|
||||
last_alert_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
|
||||
total_alerts_sent: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
total_alerts_failed: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
|
||||
|
||||
class MacroSnapshot(Base):
|
||||
"""Daily snapshot of all macro indicators surfaced on the BTC page.
|
||||
|
||||
One row per calendar date. Every indicator column is nullable — any single
|
||||
upstream API failing must not block the rest from being recorded.
|
||||
Composite score is computed at insert time by app/services/macro/scoring.py
|
||||
against whichever indicators successfully fetched (the formula degrades
|
||||
gracefully if a few are missing).
|
||||
"""
|
||||
__tablename__ = "macro_snapshots"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
snapshot_date: Mapped[date] = mapped_column(Date, nullable=False, unique=True, index=True)
|
||||
captured_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=utcnow)
|
||||
|
||||
ahr999: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
|
||||
altcoin_season_index: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
|
||||
fear_greed: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
||||
fear_greed_label: Mapped[Optional[str]] = mapped_column(String(32), nullable=True)
|
||||
btc_dominance_pct: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
|
||||
eth_btc_ratio: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
|
||||
stablecoin_supply_usd: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
|
||||
etf_flow_net_usd_1d: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
|
||||
btc_open_interest_usd: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
|
||||
|
||||
composite_score: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
|
||||
regime_label: Mapped[Optional[str]] = mapped_column(String(16), nullable=True)
|
||||
|
||||
raw_json: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
|
||||
Reference in New Issue
Block a user