Methodology

This page describes the public methodology of the RiskState engine: what inputs drive each decision, how they combine into max_size_fraction, and how the output is anchored for audit. It is not a full specification — specific coefficients, rule thresholds and regime-dependent calibration are deliberately out of scope.

If you want to see the engine produce live output, use the visualizer for a side-by-side read of every input and every cap. Every field below corresponds to a field in the API response.

Current production state (v1.3.0, score_v3)

The engine is in a deliberate two-layer state during the score_v3 freeze window (frozen until 2026-10-22):

  • max_size_fraction is bound by the legacy 4-cap chain (rules, DeFi, macro, cycle) × quality × volatility.
  • direction_bias is driven by the structural / tactical policy combiner (new in PR2).
  • composite remains in the response for backwards compatibility and still powers the tactical_state label, rules-engine inputs and AI-prompt context.
  • structural_score, tactical_score and policy_permissions are the new semantic layer — exposed today, candidates to drive sizing in the next scoring version (earliest 2026-10-22).
  • policy_level is an informational summary for human reading. The binding constraints are the exposure_policy fields.

The sections below describe the sizing motor (caps), the direction motor (combiner) and the legacy scoring layer (composite) in that order.

Core formula

Every response is anchored on a single computation:

max_exposure = min(rules_cap, defi_cap, macro_cap, cycle_cap) × quality_factor × vol_mult

Four independent caps are computed from four independent signal families. The most restrictive one wins — this is the binding constraint surfaced in binding_constraint.source. That base cap is then multiplied by a quality factor (data integrity × signal agreement) and a volatility multiplier. The result is the max_size_fraction field.

Three properties follow from this shape:

  • No cap can rescue another. If DeFi health is bad, no amount of bullish composite score can unblock exposure.
  • Data quality binds last. Missing core signals collapse quality_factor and therefore max_exposure, regardless of the individual caps.
  • Volatility scales, never inverts. vol_mult can shrink size in HIGH/EXTREME regimes but does not flip direction.

The four caps

Each cap is a number in [0, 1]. Lower means more restrictive.

rules_cap

Measures how many rules (from the rules engine) are firing and at what severity. The rules engine is a deterministic evaluator over 80+ conditions spanning price action, derivatives, on-chain, macro and cross-asset context. Each fired rule contributes to a count; counts pass through a bounded smoothing function to produce the cap.

What moves it: CRITICAL, DANGER and WARNING rules firing. Examples: RSI at extremes, funding rate percentile at extremes, macro risk-off with high coupling, liquidation cascades, Fed liquidity tightening.

defi_cap

Measures the safety of your on-chain collateral position, when a wallet is provided. Derived from the gap between the current Health Factor and the protocol's liquidation threshold, plus the distance from current LTV to max LTV.

What moves it: Health Factor trending down, LTV rising, collateral price dropping, liquidation price approaching. When no wallet is provided, defi_cap = 1.0 (neutral — not a penalty).

macro_cap

Measures how hostile the macro environment is to risk assets. A 2-dimensional cap computed from macro regime × coupling.

What moves it: Macro regime shifting from RISK-ON → NEUTRAL → RISK-OFF. Coupling increasing (BTC/SPX or ETH/NASDAQ correlation rising toward 1). Yield curve inverting. Real rates rising. Fed balance sheet contracting. DXY strengthening.

cycle_cap

Measures where BTC (or ETH) is in its market cycle. Computed from phase classification + MVRV percentile (BTC) or ETH/BTC ratio + altseason context (ETH).

What moves it: BTC cycle phase (BOTTOM / EARLY / MID / LATE / EUPHORIA / CORRECTION / POST-PEAK / LATE-CORRECTION). ETH phase (DEPRESSED / VALUE_ZONE / RECOVERY / EXTENDED / DISTRIBUTION). In structurally negative regimes, cycle_cap is further reduced by a structural downgrade mechanism (ETH only, v2).

cycle_cap vs structural_score — why both exist

Both read cycle information, but bind different outputs:

  • cycle_cap is a hard sizing cap in the legacy 4-cap chain. One of the four min-inputs to max_size_fraction. Today, this is the path that actually limits exposure.
  • structural_score is a slow-moving 0–100 score that feeds the policy combiner. Its output binds direction_bias, not size. It is broader than cycle_cap — it also incorporates Supply, Demand and Macro subfamilies, not just the cycle phase.

They overlap on inputs (both consume the cycle phase) but serve different outputs (size vs direction). Retiring cycle_cap in favour of a structural-score-derived sizing path is a scheduled migration for the next scoring version (earliest 2026-10-22).

Policy engine (5 levels)

After the core formula produces max_size_fraction, that number is classified into one of 5 discrete policy levels. Levels are informational — the binding numbers are max_size_fraction, max_leverage, allowed_actions and blocked_actions. Levels exist so that humans and agents have a shared vocabulary for "how restrictive is the state."

max_size_fractionLevelLabelPosture
< 0.151BLOCK SurvivalReduce, hedge only
0.15 – 0.352BLOCK DefensiveWait, hedge, small scalps only
0.35 – 0.603CAUTIOUS PreservationDCA, R:R > 2:1 only
0.60 – 0.804GREEN SelectiveLong/short with confirmation
≥ 0.805GREEN ExpansionTrend follow, moderate leverage

At each level, a fixed allow-list / block-list of action tokens is applied to exposure_policy.allowed_actions and exposure_policy.blocked_actions. The full action enum is in the API reference.

Structural vs tactical (v1.3.0)

From API v1.3.0 onward, two parallel scores feed a policy combiner that binds direction_bias and exposes a risk_permission_score. These are the direction motor; the composite (below) is the legacy layer kept for backwards compatibility.

                ┌─────────────────────┐
     slow →     │  structural_score   │ ─┐
 (weeks-months) │  0–100              │  │
                └─────────────────────┘  │
                                         ▼
                                ┌──────────────────┐
                                │ policy combiner  │  →  risk_permission_score
                                │ regime-weighted  │  →  direction_bias
                                └──────────────────┘  →  direction_layer
                                         ▲
                ┌─────────────────────┐  │
     fast  →    │  tactical_score     │ ─┘
   (24-72h)    │  0–100              │
                └─────────────────────┘

Three directional fields, one taxonomy

API responses carry three fields whose names are adjacent but whose roles are distinct:

FieldSourceRole
tactical_stateLegacy label from compositeHuman-readable tilt label (BULLISH / LEAN BULL / NEUTRAL / LEAN BEAR / BEARISH). Kept for backwards compatibility.
tactical_scoreNew layer (score_v3)0–100 score with label STRONG_BULL / BULL / NEUTRAL / BEAR / STRONG_BEAR. The tactical input to the combiner.
direction_biasPolicy combinerBinding directional output (LONG_PREFERRED / NEUTRAL / SHORT_PREFERRED). The field agents should consume.

direction_bias is the operational output. tactical_score is the signal feeding the combiner. tactical_state is a legacy label that still populates for existing integrators.

Structural score

Slow-moving fundamentals. 4 subfamilies, each 0–100, combined with fixed weights (not published). Cycle carries the largest share, followed jointly by Supply and Demand, with Macro contributing the smallest share.

SubfamilyWhat it measures
CyclePhase + MVRV percentile / ETH-BTC phase + halving age
SupplyBTC: supply in profit + LTH supply. ETH: staking ratio + issuance regime.
DemandETF flows + netflow trend + institutional accumulation
MacroReal rate + liquidity regime + macro regime

Structural labels: WEAK / FRAGILE / NEUTRAL / STABILIZING / STRONG. Data-quality governance caps the label at NEUTRAL when input quality is below a minimum threshold, to avoid false confidence.

Tactical score

Fast-moving positioning. 6 components, each 0–100, combined with fixed weights (not published). Positioning Pressure carries the largest share, followed by Momentum and Volume/CVD, then Derivatives extremity, and finally L/S velocity and Whale Pressure.

ComponentWhat it measures
Positioning Pressure (PPS)Squeeze setup: crowding × funding × basis × OI z-score
MomentumRSI zone + 24h change + 1h velocity
Volume / CVDCVD acceleration + volume z-score + buy-ratio
Derivatives extremityFunding percentile + OI z-score direction
L/S velocityRegression slope of L/S ratio history
Whale PressureMulti-signal whale score (proxy-based)

Tactical labels: STRONG_BEAR / BEAR / NEUTRAL / BULL / STRONG_BULL.

Policy combiner

Combines structural and tactical using regime-dependent weights. The specific weights per regime are not published, but the posture per regime is:

Market regimePosture
PANICStructural dominates — tactical carries minimal influence on direction.
EUPHORIAStructural dominates — tactical carries minimal influence on direction.
SQUEEZEStructural and tactical contribute roughly equally — confluence required.
TREND / RANGEStructural anchors with material tactical input — the default blend.

The combiner produces:

  • risk_permission_score — weighted blend, 0–100
  • direction_bias — tactical-driven (LONG if tactical is in the upper band, SHORT if in the lower band, NEUTRAL otherwise), with structural veto at extremes (very weak structural blocks LONG; very strong structural blocks SHORT)
  • direction_layer — audit trail: tactical / structural_veto / structural_downgrade / legacy_composite
  • reason_codes — machine-parseable tokens explaining the decision

Exact regime-dependent weights, veto thresholds and band cut-offs are not published.

Composite scoring (legacy layer)

The composite is the 0–100 weighted score that pre-dated the structural / tactical split. It is kept for backwards compatibility in v1.3.0 and still powers:

  • The tactical_state label returned in every API response (5-bucket tilt from composite value).
  • Some rules in the rules engine that trigger on directional composite tilt.
  • AI-prompt context for the separate AI analysis features in the dashboard.

It does not drive direction_bias or policy_permissions in v1.3.0 — those come from the combiner above. A future scoring version may retire composite in favour of the structural / tactical split; no migration date is committed.

Each subscore takes raw inputs (e.g. RSI, funding, OI change, ETF flows, DXY, MVRV, etc.), normalises them to [0, 100] with a monotone transform, and contributes to the weighted sum.

BTC subscores

The composite is built from these 8 subscores. Each one is weighted — exact weights are not published, but Momentum and On-chain carry the most weight, followed by Derivatives, Exchange flow and Volume, with Macro, Positioning and ETF contributing smaller amounts.

SubscorePrimary inputs
MomentumRSI 4h, OBV, short-term price structure
On-chainMVRV, NUPL, Exchange netflow
DerivativesFunding rate, OI change, L/S ratio
Exchange flowExchange reserves, netflow direction
VolumeVolume z-score, buy/sell taker ratio
MacroDXY, 10Y yield, SPX correlation, real rates
PositioningPositioning Pressure Score (PPS) — derived from squeeze detector
ETFDaily + weekly spot ETF flow, streak

ETH composite

ETH mirrors the BTC structure with two differences:

  • "On-chain" is replaced by "ETH Structural", a 4-subfamily score over Network Fundamentals, Supply/Monetary, Relative Strength (ETH/BTC, ETH DOM, altseason) and Demand/Institutional. This replaces MVRV/NUPL, which are BTC-native and not directly transferable.
  • ETH Structural can trigger a structural downgrade that reduces cycle_cap when hard and soft signals combine to a minimum threshold.

Base weights and runtime-normalisation are equivalent between BTC and ETH — the substitution is one-for-one on the On-chain / ETH Structural slot.

Market regimes

The 5-state unified regime classifier is evaluated in priority order, so earlier states dominate later ones.

PriorityStateApproximate condition
1PANICExtreme fear + negative momentum + stressed derivatives + strong downside tape
2EUPHORIAExtreme greed + elevated on-chain valuation + stretched positioning
3SQUEEZEDirectional squeeze detector at high confidence (funding percentile + OI z-score + L/S crowding)
4TRENDTrend Strength score above a directional threshold, confirmed by MA stack + expansion
5RANGEDefault — none of the above dominate

Specific numeric thresholds per state (fear/greed percentiles, Trend Strength cut-offs, squeeze confidence minimums) are not published. They are inspectable live in the visualizer for any current market state.

Auditability: policy_hash

Every API response includes a policy_hash — a SHA-256 hex string over a deterministic serialization of the policy inputs. Given the same inputs (including reference_time), the hash is bit-for-bit identical across requests. This enables:

  • Non-repudiation — an agent can commit a decision to an auditable log with the hash of the policy it acted on.
  • Reproducibility — re-running the same request with reference_time pinned returns a hash that must match.
  • Integrity verification — any upstream data drift changes the hash. Same hash ⇒ same inputs.

Hashed fields

The hash covers:

GroupFields
Versionapi_version, scoring_version
Timets (from reference_time)
PricesBTC price, ETH price
IndicatorsRSI BTC, RSI ETH, MVRV, NUPL, funding BTC, funding ETH, L/S ratio, CVD, netflow, OI, ETF flows
PositioningFunding percentile (BTC/ETH), OI z-score (BTC/ETH), squeeze direction, PPS score
MacroRegime, coupling, DXY
VolatilityRegime + score
CyclePhase, MVRV percentile, boost flag
CompositeOverall score
StructuralOverall score
TacticalOverall score
Permissionsrisk_permission_score, direction_bias, regime context
PolicyLevel, max_size_fraction, binding constraint source

Fields are serialized with fixed ordering and fixed numeric precision before hashing. The exact serialization format is not published, but the contract is: same inputs → same hash, every time.

Versioning & scoring freeze

Predictable stability is a hard requirement for audit and for downstream agents: a field you consume today must mean the same thing next month.

  • Current scoring version: score_v3
  • Current API version: 1.3.0
  • Frozen until: 2026-10-22

RiskState applies a semi-annual structural freeze — a minimum of 6 months between scoring versions. Between freezes, only additive, backwards-compatible changes are allowed: new fields, new rules, new response keys. Scoring-breaking changes — renaming/removing fields, modifying normalisation ranges, changing subscore weights, rebalancing the policy combiner — only land at the freeze boundary and require a new scoring_version identifier.

The scoring_version field is returned on every response and is a component of policy_hash. Agents should pin their integration against a known scoring_version and re-validate behaviour when the version changes.

Live inspection

Every concept on this page maps one-to-one to a visible output in the production engine.

  • Visualizer — read-only view of the full live state: all 4 caps, composite subscores, structural and tactical components, market regime, policy level. BYOK optional.
  • API Reference — response schema, every field documented, action enums and risk flags.
  • MCP Server — one-tool agent integration (get_risk_policy).