fix: pre-launch hardening — HYPE price feed, KOL wallet cleanup, Telegram Trump alert, rate limiting, brittle test

Batch of the pre-launch audit campaign (BUG-01…14 plus three new features):

Pricing / TP-SL protection
- Add app/services/hl_price_feed.py: supplemental HL allMids poller for
  HL-native assets (HYPE, PURR) not listed on Binance. Pumps price_store +
  tp_sl_monitor.on_price_tick so bot trades on these assets keep full
  stop-loss / take-profit / trailing protection instead of max-hold only.
- Wire feed into main.py lifespan (startup task + graceful shutdown cancel).

Telegram
- Add format_trump_mention + PATH B in _dispatch: crypto-relevant Trump
  posts with no directional signal (relevant=True, signal=hold) now alert
  the public channel only (no per-subscriber noise).
- Rate limiter (slowapi) on the API; assorted bot/digest fixes.

KOL on-chain
- seed_kol_wallets.py: KOL_FEEDS coverage cross-check; reversibly deactivate
  orphaned wallets (handle not in KOL_FEEDS → can never produce divergence)
  so the scanner stops burning cycles on them.

Tests / misc
- Fix brittle test_macro_ahr999_uses_same_formula_as_scanner: mock now uses
  realistic ms timestamps so the in-progress-day drop fires, matching the
  fetcher's bar count (was 0.3179 vs 0.3178 off-by-one).
- Refresh stale notify_signal comment in truth_social.py.

Frontend reduce-action type fix lives in the sibling repo.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
k
2026-05-29 11:57:19 +08:00
parent 6471e44aac
commit d6c802ef26
40 changed files with 1833 additions and 209 deletions
+16 -12
View File
@@ -28,6 +28,7 @@ from datetime import datetime, timedelta, timezone
from typing import Any, Optional
import httpx
from app.services.bottom_indicators import ahr999 as compute_ahr999
logger = logging.getLogger(__name__)
@@ -119,17 +120,20 @@ def _parse_farside_latest_total(html: str) -> dict:
# ── 1. AHR999 ───────────────────────────────────────────────────────────────
# Formula: AHR999 = (price / 200d MA) × (price / age_fit_price)
# age_fit_price = 10 ** (5.84 * log10(days_since_2009_01_03) - 17.01)
# Below 0.45 historically marks accumulation zones; above 1.2 marks
# "expensive" regime that invalidates a bottom thesis.
_AHR999_GENESIS = datetime(2009, 1, 3, tzinfo=timezone.utc)
# IMPORTANT: this macro-panel AHR999 MUST stay formula-identical to the
# live BTC bottom scanner, otherwise the displayed value can disagree with the
# actual trigger logic. Reuse the canonical implementation from
# app.services.bottom_indicators instead of re-implementing it here.
@_none_on_fail("ahr999")
async def fetch_ahr999() -> dict:
"""Compute AHR999 from the last 200 daily BTC closes (Binance fapi)."""
"""Compute the SAME AHR999 used by the BTC bottom scanner.
Raw input source stays the same (Binance daily BTCUSDT closes); only the
formula source-of-truth is centralized so the UI cannot drift from the
trading logic.
"""
end_ms = int(datetime.now(timezone.utc).timestamp() * 1000)
start_ms = end_ms - 260 * 24 * 3600 * 1000 # extra buffer after dropping in-progress day
async with httpx.AsyncClient(timeout=DEFAULT_TIMEOUT) as c:
@@ -144,12 +148,12 @@ async def fetch_ahr999() -> dict:
if len(closes) < 200:
return {"value": None, "raw": {"error": "insufficient candles", "have": len(closes)}}
price = closes[-1]
ma200 = sum(closes[-200:]) / 200
days = (datetime.now(timezone.utc) - _AHR999_GENESIS).total_seconds() / 86400
ma200 = sum(closes[-200:]) / 200 # kept in raw for operator intuition only
days = (datetime.now(timezone.utc) - datetime(2009, 1, 3, tzinfo=timezone.utc)).total_seconds() / 86400
age_fit = 10 ** (5.84 * math.log10(days) - 17.01)
ahr = (price / ma200) * (price / age_fit)
ahr = compute_ahr999(closes)
if ahr is None:
return {"value": None, "raw": {"error": "ahr999 computation returned null"}}
return {
"value": round(ahr, 4),
"raw": {"price": price, "ma200": round(ma200, 2),