5fb1d52026
Big-picture changes since b941223:
KOL pipeline (new) — Substack/podcast/blog RSS → AI ticker extraction →
on-chain wallet diff → talks-vs-trades divergence detection. Daily polls,
19 feeds, divergence emits Post + Telegram fan-out.
Telegram push (new) — walletless free tier + wallet-linked Pro upgrade,
in-bot preference commands (/trump /btc /funding /kol /conf /quiet),
signed-envelope API for dashboard. Disconnect-wallet keeps free
subscription.
BTC funding-rate reversal scanner (new) — hourly cron, 30d cumulative
funding threshold + mean-revert + 7d price confirm, emits via
/api/signals/ingest. BTC bottom-reversal scanner promoted to System 2.
WS broadcast rewrite — per-client send timeout + parallel fan-out
(asyncio.gather). Fixes "Binance WS no close frame" reconnect storms +
APScheduler 11-min job misses, both caused by one slow client stalling
the kline loop.
Error visibility — three silent-error sites (trumpstruth/truth_social
fetchers, funding_reversal scanner) now include exception type name so
httpx ConnectError-style empty-message errors stop logging blank lines.
Telegram bot loop now classifies ReadTimeout vs network vs unknown +
logger.exception for the unknown bucket.
Security hygiene — trumpsignal.db untracked from git (held subscriber
wallets + encrypted HL keys + 22 bot trades); .gitignore now blocks
*.db/.next/backups. CORS only allows FRONTEND_URL in production.
New ops scripts —
- scripts/preflight.py: env/DB/Telegram/AI auth verification gate
- scripts/backup_db.sh: cron-friendly daily DB backup (SQLite + Postgres)
- scripts/seed_kol_wallets.py: idempotent KOL on-chain wallet seeder
15 new Alembic migrations (007-021) covering convex strategy fields,
phase-1 safety, two-system frozen exits, invalidation prices, dynamic
SYS2 leverage, staged de-risk + pyramiding, peak gain tracking, risk
mode, auto-trade + grow flags, KOL module, KOL on-chain, KOL divergence,
Telegram bindings + walletless.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
96 lines
3.0 KiB
Python
96 lines
3.0 KiB
Python
from app.services.scanners.funding_reversal import evaluate_funding_reversal
|
|
from app.services.scanners.sma_reclaim import evaluate_sma_reclaim
|
|
from app.services.signal_categories import (
|
|
is_system_2,
|
|
is_supported_trading_source,
|
|
system2_display_name,
|
|
system2_min_confidence,
|
|
system2_size_multiplier,
|
|
)
|
|
from app.services.bot_engine import _confidence_floor_for, _should_apply_schedule
|
|
from app.services.tp_sl_monitor import register_trade, _watched
|
|
|
|
|
|
def _daily(close: float, volume: float = 100.0) -> dict:
|
|
return {"close": close, "volume": volume}
|
|
|
|
|
|
def test_sma_reclaim_no_longer_emits_short_breakdowns():
|
|
candles = [_daily(110.0) for _ in range(200)]
|
|
candles.extend(_daily(110.0) for _ in range(31))
|
|
candles.append(_daily(90.0, volume=200.0))
|
|
|
|
is_signal, debug = evaluate_sma_reclaim(candles)
|
|
|
|
assert is_signal is False
|
|
assert debug["reason"] == "shorts_disabled"
|
|
|
|
|
|
def test_funding_reversal_reports_boost_without_standalone_signal():
|
|
funding = [
|
|
{"time_ms": i * 3_600_000, "rate": -0.00005}
|
|
for i in range(24 * 29)
|
|
]
|
|
funding.extend(
|
|
{"time_ms": (24 * 29 + i) * 3_600_000, "rate": -0.00001}
|
|
for i in range(24)
|
|
)
|
|
candles = [_daily(100.0) for _ in range(8)]
|
|
candles[-1] = _daily(104.0)
|
|
|
|
is_signal, debug = evaluate_funding_reversal(funding, candles)
|
|
|
|
assert is_signal is True
|
|
assert debug["direction"] == "buy"
|
|
|
|
|
|
def test_bottom_reversal_source_routes_to_system_2():
|
|
assert is_system_2("btc_bottom_reversal")
|
|
|
|
|
|
def test_old_scanner_sources_no_longer_route_to_module_2():
|
|
for source in ("rsi_reversal", "sma_reclaim", "funding_reversal", "breakout", "vcp_breakout"):
|
|
assert not is_system_2(source)
|
|
|
|
|
|
def test_unknown_external_sources_are_not_supported_for_trading():
|
|
assert is_supported_trading_source("truth")
|
|
assert is_supported_trading_source("btc_bottom_reversal")
|
|
assert not is_supported_trading_source("manual")
|
|
assert not is_supported_trading_source("sma_reclaim")
|
|
|
|
|
|
def test_module_2_uses_own_confidence_floor_and_bypasses_schedule():
|
|
sys2_sub = {"_is_system_2": True, "min_confidence": 95}
|
|
sys1_sub = {"_is_system_2": False, "min_confidence": 95}
|
|
|
|
assert system2_display_name() == "Bitcoin Bottom"
|
|
assert _confidence_floor_for(sys2_sub) == system2_min_confidence()
|
|
assert _confidence_floor_for(sys1_sub) == 95
|
|
assert _should_apply_schedule(sys2_sub) is False
|
|
assert _should_apply_schedule(sys1_sub) is True
|
|
|
|
|
|
def test_register_trade_keeps_invalidation_price():
|
|
register_trade(
|
|
trade_id=99991,
|
|
wallet="0xabc",
|
|
api_key="key",
|
|
leverage=3,
|
|
asset="BTC",
|
|
side="long",
|
|
entry_price=100.0,
|
|
take_profit_pct=None,
|
|
stop_loss_pct=6.0,
|
|
trailing_stop_pct=5.0,
|
|
trailing_activate_at_pct=12.0,
|
|
invalidation="below_entry",
|
|
invalidation_price=92.5,
|
|
min_hold_until_ts=None,
|
|
)
|
|
try:
|
|
wt = _watched[99991]
|
|
assert wt.invalidation_price == 92.5
|
|
finally:
|
|
_watched.pop(99991, None)
|