Refusal-first гейт (модули 11–12) живёт в Rates Desk. Но под ним стоит ещё один,
более общий слой — spa_core/risk/policy.py. Это глобальный RiskPolicy, через который
обязана пройти любая позиция всего портфеля. Композиция: RatePolicy approve — необходимое, но
не достаточное условие; глобальная RiskPolicy всё ещё должна одобрить (логика AND,
никогда OR — см. compose_under_global_policy).
Жёсткие лимиты (policy.py)
| Параметр | Значение | Поле в коде |
|---|---|---|
| Concentration T1 | ≤ 40% | max_concentration_t1 = 0.40 |
| Concentration T2 | ≤ 20% | max_concentration_t2 = 0.20 |
| Single protocol | ≤ 40% | max_single_protocol = 0.40 |
| T2 суммарно | ≤ 50% | max_total_t2_allocation = 0.50 (ADR-019) |
| Min cash buffer | ≥ 5% | min_cash_pct = 0.05 |
| Min TVL на пул | ≥ $5M | min_tvl_usd = 5_000_000 |
| APY-band (новый вход) | 1% … 30% | min/max_apy_for_new_position |
| Base chain суммарно | ≤ 20% | BASE_CHAIN_CAP = 0.20 (ADR-025) |
Почему LLM запрещён в risk-слое
Одно из FORBIDDEN-правил деска (CLAUDE.md #5): LLM запрещён в
risk / execution / monitoring. Причина честная: нейросеть недетерминирована, не
воспроизводима байт-в-байт и может быть «уговорена» красивым нарративом. Гейт, решающий судьбу
капитала, обязан быть проверяемым — чтобы любой скептик мог переиграть решение и
получить тот же результат (это связывает RiskPolicy с proof-of-risk, модуль 17).
fail-CLOSED
Политика отказывает по умолчанию: при любом сомнении, нехватке данных или малформ-входе вердикт — не входить, а не «наверное, ок». Это та же философия, что в kill-switch (модуль 14) и в refusal-гейте: безопасный отказ всегда дешевле небезопасного входа.