Skip to main content

Overview

The ank-risk module provides tick-level and day-bucketed risk tracking for backtests and agent-based models. Key features:
  • Tick-level metrics in e18 units (engine-native)
  • Day-bucketed metrics in USD (f64) for analytics
  • Protocol-specific valuation helpers (Aave, Uniswap V3)
  • Rich risk metrics: health factor, LTV, drawdown, Sharpe, Sortino, APY

Risk Accumulators

RiskAccumulator

Tick-level accumulator of portfolio risk metrics in e18 space. Call update() once per tick with current net equity and health factor. When the backtest ends, call finalize() to obtain a summary snapshot.

Fields

n
u64
Number of update observations (ticks).
min_hf_bps
u64
Minimum observed health factor (basis points).
max_hf_bps
u64
Maximum observed health factor (basis points).
max_drawdown_e18
u128
Maximum drawdown in e18 units, tracked over the run.
last_drawdown_e18
u128
Last computed drawdown in e18 units.

Methods

update
fn(&mut self, ts: u64, net_value_e18: u128, hf_bps: u64) -> u128
Update the accumulator with the current net value (e18) and HF (bps).Parameters:
  • ts: Timestamp (seconds or milliseconds; ms are auto-normalized)
  • net_value_e18: Net equity in 1e18 units
  • hf_bps: Health factor in basis points (higher is safer)
Returns: Current drawdown in e18 after the update
finalize
fn(&self) -> RiskSummary
Finalize and produce a RiskSummary.If timestamps were observed, annualization uses the actual horizon; otherwise, it falls back to TICKS_PER_YEAR (31,536,000 = 365 days × 86,400 seconds).

RiskAccumulatorUsd

Day-bucketed risk accumulator in USD (f64) for human-scale analytics. Feed it tick-level net equity (e18) each call; it will maintain a rolling USD value, track drawdown, and close out daily bars.

Fields

min_hf_bps
u64
Minimum observed health factor (bps).
max_hf_bps
u64
Maximum observed health factor (bps).
last_drawdown_usd
f64
Last computed drawdown in USD.
max_drawdown_usd
f64
Maximum drawdown in USD.

Methods

update
fn(&mut self, ts: u64, net_value_e18: u128, hf_bps: u64) -> f64
Ingest a tick at ts with current net value (e18) and HF (bps).Returns: Current USD drawdown after the update
finalize
fn(self) -> RiskSummaryUsd
Finalize and produce a daily (USD) RiskSummaryUsd.Uses daily log returns for volatility and Sharpe/Sortino, and a timestamp-based horizon for APR/APY when available.

Summary Types

RiskSummary

Compact summary produced by RiskAccumulator::finalize(). Numeric identifiers and e18 amounts are wrapped in string-serialized types (U64S, U128S, I128S) so JSON/TS never lose precision.

Fields

ticks
U64S
Number of ticks observed.
min_hf_bps
U64S
Minimum observed health factor (bps).
max_hf_bps
U64S
Maximum observed health factor (bps).
max_drawdown_e18
U128S
Maximum drawdown in e18.
vol_annual
f64
Annualized volatility (from per-tick simple returns).
sharpe
f64
Annualized Sharpe ratio (risk-free rate = 0).
sortino
f64
Annualized Sortino ratio (risk-free rate = 0).
terminal_net_e18
U128S
Terminal net equity (e18).
initial_net_e18
U128S
Initial net equity (e18).
total_pnl_e18
I128S
Total PnL over the run (e18, signed).
total_return
f64
Total return (terminal / initial − 1).
apr
f64
Time-based APR (linearized).
apy
f64
Time-based APY (compounded).
wins
U64S
Number of up ticks.
losses
U64S
Number of down ticks.
win_rate
f64
wins / (wins + losses).

RiskSummaryUsd

Human-scale (USD, f64) summary of risk over day buckets. Produced by RiskAccumulatorUsd::finalize().

Fields

days
u64
Number of distinct days observed.
ticks
u64
Number of ticks observed.
min_hf_bps
u64
Minimum observed health factor (bps).
max_hf_bps
u64
Maximum observed health factor (bps).
max_drawdown_usd
f64
Maximum drawdown on the USD series.
vol_annual
f64
Annualized volatility from daily log returns.
sharpe
f64
Annualized Sharpe (rf = 0) from daily log returns.
sortino
f64
Annualized Sortino (rf = 0) from daily log returns.
initial_net_usd
f64
Initial USD equity.
terminal_net_usd
f64
Terminal USD equity.
total_pnl_usd
f64
Terminal − Initial (USD).
total_return
f64
Total return (terminal/initial − 1).
apr
f64
APR (linearized, time-based).
apy
f64
APY (compounded, time-based).
wins
u64
Number of days with positive daily return.
losses
u64
Number of days with negative daily return.
win_rate
f64
wins / (wins + losses).

RiskSnapshot

One-tick snapshot of wallet/health state (all e18 except *_bps).
ts
u64
Timestamp for this snapshot (seconds or ms; ms are auto-normalized).
wallet_value_e18
u128
Total wallet value in e18 (sum of assets).
deposit_value_e18
u128
Collateral/deposits value in e18.
debt_value_e18
u128
Debt value in e18.
net_value_e18
u128
Net equity in e18.
ltv_bps
u64
Loan-to-value in basis points.
hf_bps
u64
Health factor in basis points (higher is safer).

Protocol Valuation Helpers

compute_aave_values_from_views

Compute Aave-style valuation metrics from protocol views. Signature:
pub fn compute_aave_values_from_views(
    aave_user: &serde_json::Value,
    aave_mkt: &serde_json::Value,
) -> (u128, u128, u64, u64)
Returns: (deposit_value_e18, debt_value_e18, ltv_bps, hf_bps) Expected view shapes:
  • aave_market["reserves"][symbol] contains:
    • "price_e18" (string or number)
    • "liq_threshold_bps" (number)
  • aave_user["deposits"][symbol] and ["debts"][symbol] are amounts (string/number)

compute_uniswap_values_from_views

Compute Uniswap V3-style valuation metrics from views. Signature:
pub fn compute_uniswap_values_from_views(
    uniswap_user: &serde_json::Value,
    uniswap_market: &serde_json::Value,
) -> (u128, u128, u64, u64)
Returns: (total_liquidity_value_e18, fees_earned_e18, utilization_bps, position_health_bps) Expected view shapes:
  • uniswap_market:
    • "sqrt_price_x96" (string)
    • optionally "token0_price_e18", "token1_price_e18" overrides
  • uniswap_user["positions"] is an array of:
    • tick_lower / tick_upper (i32)
    • liquidity (string)
    • fees_owed_0 / fees_owed_1 (string)

Usage Examples

Basic risk tracking

use ank_risk::RiskAccumulator;

let mut risk = RiskAccumulator::default();

// Simulate a backtest
for tick in 0..1000 {
    let ts = 1640000000 + tick * 60; // 1-minute intervals
    let net_value_e18 = 10_000_000_000_000_000_000_000u128; // $10k starting
    let hf_bps = 15_000; // 150% health factor
    
    let drawdown = risk.update(ts, net_value_e18, hf_bps);
}

// Get summary
let summary = risk.finalize();
println!("Sharpe: {:.2}", summary.sharpe);
println!("Max Drawdown: {} e18", summary.max_drawdown_e18.0);
println!("APY: {:.2}%", summary.apy * 100.0);

Day-bucketed USD tracking

use ank_risk::RiskAccumulatorUsd;

let mut risk_usd = RiskAccumulatorUsd::default();

// Feed tick-level data
for tick in 0..10000 {
    let ts = 1640000000 + tick * 60;
    let net_value_e18 = 10_000_000_000_000_000_000_000u128;
    let hf_bps = 15_000;
    
    risk_usd.update(ts, net_value_e18, hf_bps);
}

// Get daily summary
let summary = risk_usd.finalize();
println!("Days: {}", summary.days);
println!("Max Drawdown: ${:.2}", summary.max_drawdown_usd);
println!("Win Rate: {:.2}%", summary.win_rate * 100.0);

Aave position valuation

use ank_risk::compute_aave_values_from_views;
use serde_json::json;

let aave_market = json!({
    "reserves": {
        "WETH": {
            "price_e18": "2000_000_000_000_000_000_000",
            "liq_threshold_bps": 8000
        },
        "USDC": {
            "price_e18": "1_000_000_000_000_000_000",
            "liq_threshold_bps": 8500
        }
    }
});

let aave_user = json!({
    "deposits": {
        "WETH": "5_000_000_000_000_000_000" // 5 WETH
    },
    "debts": {
        "USDC": "6000_000_000_000_000_000_000" // $6000 USDC
    }
});

let (deposit_val, debt_val, ltv_bps, hf_bps) = 
    compute_aave_values_from_views(&aave_user, &aave_market);

println!("Deposit Value: ${:.2}", deposit_val as f64 / 1e18);
println!("Debt Value: ${:.2}", debt_val as f64 / 1e18);
println!("LTV: {:.2}%", ltv_bps as f64 / 100.0);
println!("Health Factor: {:.2}", hf_bps as f64 / 10000.0);

Risk Metrics Explained

Health Factor (HF)

Measures how close a position is to liquidation. Expressed in basis points (bps):
  • 10,000 bps = 1.0 (liquidation threshold)
  • 15,000 bps = 1.5 (healthy)
  • > 20,000 bps = 2.0+ (very safe)
Formula: HF = (CollateralValue × LiquidationThreshold) / DebtValue

Loan-to-Value (LTV)

Ratio of debt to collateral, expressed in basis points:
  • 5,000 bps = 50% LTV
  • 7,500 bps = 75% LTV
  • 10,000 bps = 100% LTV (fully leveraged)
Formula: LTV = (DebtValue / CollateralValue) × 10,000

Drawdown

Peak-to-trough decline in portfolio value: Formula: Drawdown = RunningPeak - CurrentValue

Sharpe Ratio

Risk-adjusted return metric (higher is better): Formula: Sharpe = AnnualizedReturn / AnnualizedVolatility

Sortino Ratio

Like Sharpe, but only penalizes downside volatility: Formula: Sortino = AnnualizedReturn / DownsideDeviation

APR vs APY

  • APR (Annual Percentage Rate): Linearized return
    • APR = TotalReturn / Years
  • APY (Annual Percentage Yield): Compounded return
    • APY = exp(ln(TerminalValue / InitialValue) / Years) - 1

CSV Output Format

Risk metrics can be exported to CSV for analysis. Typical format:
ts,wallet_value_e18,deposit_value_e18,debt_value_e18,net_value_e18,ltv_bps,hf_bps
1640000000,10000000000000000000000,8000000000000000000000,2000000000000000000000,8000000000000000000000,2500,32000
1640003600,10100000000000000000000,8100000000000000000000,2000000000000000000000,8100000000000000000000,2469,32400
Field descriptions:
  • All *_e18 fields are 1e18-scaled integers
  • ltv_bps and hf_bps are basis points (÷ 10,000 for decimal)
  • ts is Unix timestamp (seconds)

Build docs developers (and LLMs) love