risk-engine
Skill3 starsChanged 4 months agoMITView on GitHub
Advisory risk-vetting for trade Intents. Pure function `analysis.risk.vet()` evaluates position size, portfolio drawdown, per-tier exposure, daily trade budget, available funds, and per-pair cooldown — returns a RiskVerdict (APPROVED / CONCERN / SCALE / REJECT) the LLM narrates before asking the user to confirm execution. Not a hard gate; the execution skill confirm is the actual safety layer.
In this repository47 other files it gives its agents.
- AGENTS.mdAGENTS.md
- beadsSkill
- backtest-engineSkill
- backtest-pipelineSkill
- backtest-trend-minerSkill
- bug-scanSkill
- daily-trade-pickSkill
- execution-kraken-perpsSkill
- execution-kraken-spotSkill
- l3-conviction-scanSkill
- market-accumulationSkill
- market-basisSkill
- market-breadthSkill
- market-breakoutSkill
- market-emaSkill
- market-exhaustionSkill
- market-fibonacciSkill
- market-liquidity-sweepSkill
- market-macdSkill
- market-macroSkill
- market-moversSkill
- market-notesSkill
- market-overviewSkill
- market-rsiSkill
- market-snapshotSkill
- market-squeezeSkill
- market-s-rSkill
- market-stateSkill
- market-trend-qualitySkill
- market-trendSkill
- market-valuationSkill
- market-volatilitySkill
- market-volumeSkill
- market-watchlistSkill
- portfolio-mgmtSkill
- position-watchdogSkill
- run-all-l2Skill
- run-all-l3Skill
- run-watchlistSkill
- strategy-accumulation-swingSkill
- strategy-breakout-confirmSkill
- strategy-exhaustion-fadeSkill
- strategy-fitness-heatmapSkill
- strategy-funding-carrySkill
- strategy-liquidity-sweepSkill
- strategy-mean-reversionSkill
- strategy-trend-followSkill
The file
We split this skill into the rules it gives an agent
8 rules. About 0 are common, 2 are distinctive, and 6 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 108
When the intent's venue ends in -perps (or intent.leverage is set) the perps policy set is added to the spot set.
- Distinctive: line 124
No perps policy ever REJECTs on missing context.
- Very specific: line 156
NEVER forwarded to the venue: execution keeps them in the Intent and rejects any extras key that is neither a venue CLI flag nor declared …
- Very specific: line 227
Read the verdict JSON. Narrate the narrative_hint field and any REJECT/SCALE concerns.
- Distinctive: line 229
Ask the user: "Risk says X.
- Very specific: line 230
If yes, call `execution-kraken-spot submit or execution-kraken-perps submit --intent <intent> which prints the order summary and asks for …
- Very specific: line 247
Add spot policies to analysis/risk/spot.py and perps policies to analysis/risk/perps.py (perps policies must short-circuit on spot intents …
- Very specific: line 270
No changes to vet() or the contract.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
risk-engine
Advisory risk-vetting layer between analytics and execution. The LLM calls
this skill before asking the user to confirm an order. The verdict is
advisory, not a hard gate — the LLM can recommend an override based
on context the policies didn't see, but the human-in-the-loop at
execution-kraken-spot is the actual safety layer.
When NOT to use
- As a confirmation gate — the verdict is advisory (APPROVED/CONCERN/SCALE/REJECT), never a hard block. Do not tell the user "risk approved" as if execution is safe; the execution skill confirm is the real safety layer.
- Without a built Intent — risk-engine vets an
Intent(or direct flags); call it after an L3 strategy produced the idea, not before. - For analysis/sizing discovery — it only vets a proposed trade against policy caps; use market-* / strategy-* for finding the trade.
Why advisory not gate
The LLM is the agent brain (see ARCHITECTURE.md "Agent brain"). A deterministic REJECT here would prevent the LLM from exercising judgment the policies can't model (e.g. the user is closing a hedge, the policy only sees the long). So Risk.vet returns a recommendation; the LLM narrates it; the user decides; the execution skill enforces the human confirm.
Quick Start
# Vet an Intent from file (machine input, JSON for LLM tool-use)
uv run skills/risk-engine/scripts/run.py --intent intent.json --portfolio spot --json
# Vet direct flags (interactive use)
uv run skills/risk-engine/scripts/run.py \
--pair HYPEUSD --side buy --order-type limit --volume 1.5 --limit-price 60.15 \
--portfolio spot
# Market order — pass --reference-price so the funds/size/tier checks can
# cost it (a market intent carries no limit_price; the flag is the last
# fallback — see "Market orders" below)
uv run skills/risk-engine/scripts/run.py \
--pair PENDLEEUR --side buy --order-type market --volume 177.86 \
--reference-price 11.3755 --portfolio spot
# Without portfolio context — policies degrade to "no info" (CONCERN at worst)
uv run skills/risk-engine/scripts/run.py --intent intent.jsonLLM agent brain: for the per-status workflow when this skill returns a
RiskVerdict(APPROVED / CONCERN / SCALE / REJECT), seeLLM-ORCHESTRATION.md§2.
What the policies check
Eleven composable policies, each returns a RiskVerdictFragment. The
spot set (first six) runs on every intent; the perps set (last five)
runs only when the intent's venue ends in -perps or has a
leverage field. Each perps policy short-circuits on spot intents
so they can be safely mixed.
| Policy | Set | Trigger | Worst status | Notes |
position_size | spot | intent cost > max_position_pct of portfolio | SCALE / REJECT | SCALE shrinks to cap; REJECT only if 3× over |
portfolio_drawdown | spot | drawdown > max_drawdown_pct | CONCERN / REJECT | Defensive posture — explain before adding risk |
per_tier_exposure | spot | projected tier exposure > cap | SCALE | Tier resolved from watchlist metadata |
daily_budget | spot | trades today + 1 > budget | CONCERN / REJECT | Resets at midnight UTC |
insufficient_funds | spot | cash < cost (buy) / held < qty (sell) | REJECT | Hard reject — no narrative needed |
per_pair_cooldown | spot | same pair+side within cooldown_hours | CONCERN | Pure advisory; DCA plans legitimately trade often |
leverage_cap | perps | intent.leverage > tier cap (BTC/ETH/SOL=2x, alts=5x) | REJECT | Tier-aware cap from LEVERAGE_CAPS |
liquidation_distance | perps | liq price < liq_min_distance_pct from entry (30%) | REJECT | Linear approx: move = 1/lev + mm_rate; needs extras.reference_entry + ctx.maintenance_margin_rate |
stop_distance | perps | bracket stop < 2% or > 25% of entry | REJECT | Swing-mode bucket; needs extras.reference_entry |
funding_drag | perps | 3-day funding drag > funding_warn_pct of notional | CONCERN | Sign convention: positive = this trade pays; needs ctx.funding_rate_per_8h + extras.position_value |
duplicate_perps_position | perps | open position on same pair+side | REJECT | Pyramiding guard; needs ctx.open_perps_positions |
vet() aggregates fragments by worst-case status. SCALE suggestions are
combined by taking the smallest suggested volume (most conservative).
Context snapshot
The skill builds a RiskContext from:
| Source | Fields |
portfolio-mgmt (--portfolio) | positions (via compute_positions), base_ccy, daily trades; prices read from the SQLite price_cache table (get_cached_prices), refreshed by portfolio.db.refresh_prices() |
market-watchlist (--watchlist) | tier metadata per pair → tier exposure |
--drawdown-pct (or 0.0) | portfolio drawdown override |
kraken futures (--perps-account) | open perps positions, current funding rate — only when intent's venue ends in -perps |
Static MM_RATES (in analysis/providers/execution_kraken_perps.py) | first-tier maintenance margin rate for the intent's pair — only when intent is perps |
Price cache / --reference-price | ctx.reference_prices — bare-pair price for the intent's pair, so a market order (no limit_price) can still be costed by funds/size/tier checks |
| CLI overrides | --funding-rate-per-8h, --maintenance-margin-rate, --open-perps-positions, --reference-price — win over auto-fetch (testing, custom sourcing) |
| Built-in defaults | max_position_pct=25, max_drawdown_pct=20, daily_trade_budget=10, pair_cooldown_hours=4, perps thresholds liq_min_distance_pct=30, stop_min_distance_pct=2, stop_max_distance_pct=25, funding_warn_pct=1.0 |
Without --portfolio, policies degrade to "no info" → APPROVED with the
relevant CONCERN fragment (e.g. position_size emits CONCERN if no
portfolio total_value to size against). ctx.reference_prices is still
populated for buy intents — the market-order price lookup doesn't depend
on portfolio state (sell intents skip the lookup: no spot policy costs a
sell from a reference price).
Perps context
When the intent's venue ends in -perps (or intent.leverage is
set) the perps policy set is added to the spot set. Three
RiskContext fields are perps-specific:
| Field | Source | Notes |
open_perps_positions | kraken futures positions --output json (auto) or --open-perps-positions <json> (override) | List of {symbol, size} (positive=long, negative=short). None / empty means the duplicate-position policy is a no-op. |
funding_rate_per_8h | kraken futures historical-funding-rates <PF_SYMBOL> (auto, takes most-recent entry) or --funding-rate-per-8h (override) | Sign convention: positive = this trade pays. Longs pay when funding > 0, shorts pay when funding < 0. The auto-fetch flips the sign for shorts. |
maintenance_margin_rate | Static MM_RATES table (auto) or --maintenance-margin-rate (override) | First-tier MM (smallest notional, most conservative). Higher-notional-tier MM is a few percent looser — out of scope for the policy's lower bound. |
Without --perps-account AND without overrides, all three fields
default to None / empty. The perps policies degrade to no-info
paths: duplicate_perps_position is a no-op when there are no
positions; funding_drag emits a CONCERN asking the caller to load
the rate; liquidation_distance and stop_distance emit CONCERN
when their required inputs (mm rate / reference entry) are missing.
No perps policy ever REJECTs on missing context.
--perps-account only triggers the auto-fetch when the intent is
perps — for spot intents, the perps branch is fully skipped.
Price source
Prices come from portfolio-mgmt's price cache (the same data the rest of
the portfolio UI consumes). portfolio-mgmt prices refresh should run on
cron; risk-engine then reads the cache directly. For cache misses,
risk-engine falls back to a one-shot live fetch (fetch_spot_price) so
a cold cache doesn't regress a hot one. Pass --refresh-prices to force
a refresh before vetting (rare — usually only for ad-hoc verification).
Market orders: a market intent carries no limit_price, so the spot
policies cost it from a resolved reference price instead. Resolution order
(analysis/risk/_common.py::resolve_intent_notional, first
strictly-positive value wins): the intent's limit_price →
extras.reference_price → extras.est_notional / extras.position_value
(quote notional ÷ volume) → the held position's current_price →
ctx.reference_prices[bare_pair]. That last entry is what risk-engine
injects — from --reference-price, the price cache, or a one-shot live
spot fetch — so --reference-price supplies the LAST fallback and is
ignored whenever the intent itself carries a price hint or the pair is
already held. When no price can be resolved, insufficient_funds /
position_size / per_tier_exposure emit a CONCERN naming the missing
price — a market buy is never silently approved just because its cost is
unknown. Perps intents skip this lookup (perps policies degrade to
CONCERN on their own missing inputs).
The price-hint extras keys are risk-layer-only. reference_price,
est_notional and position_value are consumed by risk-engine and are
NEVER forwarded to the venue: execution keeps them in the Intent and
rejects any extras key that is neither a venue CLI flag nor declared in
analysis/providers/execution/base.py::NON_VENUE_INTENT_EXTRAS (same
rule, same key names, stated in execution-kraken-spot SKILL.md — the
two halves of this contract must always agree; see
ADR-0011).
Configuration
Risk-engine parameters (max_position_pct, max_drawdown_pct, daily_budget,
cooldown_hours, tier_caps) ship with sane defaults from RiskContext.
Override them per portfolio / per pair via a YAML config file. Resolution
matches the rest of the skill config loaders:
--config PATHCLI flag$MARKET_SKILLS_RISK_POLICIES_PATHenv varskills/risk-engine/data/policies.yaml(repo default — gitignored)
A missing or unreadable file is not an error — RiskContext defaults are
used. Malformed YAML or unknown top-level keys raise ValueError (CLI
exits with code 2 and a clean message).
Schema
# Top-level scalars — apply to all portfolios/pairs unless overridden below.
max_position_pct: 30 # -> ctx.max_position_pct
max_drawdown_pct: 15 # -> ctx.max_drawdown_pct
daily_budget: 8 # -> ctx.daily_trade_budget
cooldown_hours: 6 # -> ctx.pair_cooldown_hours
# Per-tier exposure caps (merged into ctx.tier_limits as max_pct).
tier_caps:
tier1: 40
tier2: 25
tier3: 10
# Per-portfolio overrides (case-insensitive name match).
portfolios:
spot:
max_position_pct: 25
cooldown_hours: 8
tier_caps:
tier1: 40
tier2: 25
defi:
max_position_pct: 40
cooldown_hours: 4
daily_budget: 15
# Per-pair overrides (case-insensitive bare-ticker match — <PRIVATE_PERP>-USD matches <PRIVATE_PERP>USD).
pairs:
<PRIVATE_PERP>USD:
max_position_pct: 5Precedence
For each scalar field, the value is resolved as:
class default → top-level → per-portfolio → per-pair (later wins).
This matches the spec at POLICIES_CONFIG.md.
LLM tool use
The skill is shaped as an LLM tool. Before asking the user to confirm an order, the LLM should:
- Build an Intent (or read one from an L3 strategy output).
- Call
risk-engine:- Spot:
risk-engine --intent <intent> --portfolio spot --json - Perps:
risk-engine --intent <intent> --portfolio spot --perps-account kraken-futures --json(the--perps-accountflag triggers auto-fetch of open positions + funding rate fromkraken futures; MM rate comes from the static table)
- Spot:
- Read the verdict JSON. Narrate the
narrative_hintfield and any REJECT/SCALE concerns. - Ask the user: "Risk says X. Should I proceed?"
- If yes, call ``execution-kraken-spot submit
orexecution-kraken-perps submit--intent <intent>which prints the order summary and asks for the user's explicity/N.
Example system-prompt fragment for the LLM:
Before executing any trade, vet the Intent with
risk-engine. For perps Intents, add--perps-account kraken-futuresso the perps policies (leverage cap, liquidation distance, stop distance, funding drag, duplicate position) can evaluate against live state. Surface any REJECT or SCALE verdicts to the user before asking for confirmation. Risk is advisory — you may recommend an override if the user explains context the policies didn't see. The actual safety gate is the execution skill confirm; never bypass it.
Adding a new policy
Each policy is a function (Intent, RiskContext) -> RiskVerdictFragment.
Add spot policies to analysis/risk/spot.py and perps policies to
analysis/risk/perps.py (perps policies must short-circuit on spot
intents via _is_perps_intent). To add e.g. a "concentration check"
(no single position > 50% of portfolio) as a spot policy:
# analysis/risk/spot.py
def concentration_policy(intent, ctx):
asset = f"kraken:{intent['pair']}"
held_pct = (ctx.positions.get(asset, {}).get("market_value", 0) / ctx.total_value) * 100
if ctx.total_value <= 0:
return _empty_fragment("concentration")
if held_pct > 50:
return RiskVerdictFragment(
policy="concentration",
status="CONCERN",
reason=f"{asset} would be {held_pct:.0f}% of portfolio after this trade",
)
return _empty_fragment("concentration")
SPOT_POLICIES.append(concentration_policy)No changes to vet() or the contract. Tests can isolate the new policy
by passing policies=[concentration_policy] to vet().
Verdict schema
{
"intent_id": "...",
"pair": "HYPEUSD",
"side": "buy",
"status": "SCALE",
"fragments": [
{"policy": "position_size", "status": "SCALE", "reason": "...",
"suggested_volume": 1.2, "detail": {"pct": 27.5, "max_pct": 25.0}},
{"policy": "insufficient_funds", "status": "APPROVED", "reason": "no objection"},
...
],
"concerns": ["per_pair_cooldown: already traded HYPEUSD buy within 4h cooldown"],
"suggested_volume": 1.2,
"narrative_hint": "Risk layer suggests reducing volume to 1.200000 HYPEUSD: ..."
}References
references/sentiment-vs-structure.md— judgment playbook for when extreme F&G is a valid contrarian signal versus a trailing indicator (structure already broken). Consult before overriding a risk verdict on sentiment grounds.
Files
skills/risk-engine/
├── SKILL.md # this file
├── lib.py # build_context, render_verdict (pure helpers)
├── scripts/
│ └── run.py # CLI wrapper
├── examples/
│ └── intent.example.json
└── references/
└── sentiment-vs-structure.md # judgment playbookDoes it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

