# `ZenQuant.Volatility`
[🔗](https://github.com/ZenHive/zen_quant/blob/v0.8.1/lib/zen_quant/volatility.ex#L1)

Volatility calculations for trading systems.

Pure functions for calculating historical (realized) volatility,
comparing implied vs realized volatility, and computing volatility metrics.

## Example

    prices = [100, 102, 99, 103, 101, 105, 102]

    ZenQuant.Volatility.realized(prices)
    # => 0.023  (2.3% daily volatility)

    ZenQuant.Volatility.realized(prices, annualize: true)
    # => 0.365  (36.5% annualized)

## API Functions
| Function | Arity | Description | Param Kinds |
| --- | --- | --- | --- |
| `rolling` | 3 | Calculate rolling volatility over a price series. | `prices: value`, `window: value` |
| `yang_zhang` | 2 | Calculate Yang-Zhang volatility estimator from OHLC data. | `candles: exchange_data` |
| `garman_klass` | 2 | Calculate Garman-Klass volatility estimator from OHLC data. | `candles: exchange_data` |
| `elevated?` | 3 | Check if volatility regime is elevated. | `current_vol: value`, `baseline_vol: value` |
| `cone` | 2 | Calculate volatility cone for term structure analysis. | `prices: value`, `periods: value` |
| `iv_vs_rv` | 3 | Compare implied volatility to realized volatility. | `implied_vol: value`, `realized_vol: value` |
| `iv_rank` | 2 | Calculate IV rank (normalized position in min-max range). | `current_iv: value`, `historical_ivs: value` |
| `iv_percentile` | 2 | Calculate IV percentile relative to historical distribution. | `current_iv: value`, `historical_ivs: value` |
| `parkinson` | 2 | Calculate Parkinson volatility estimator from high-low ranges. | `candles: exchange_data` |
| `realized` | 2 | Calculate realized (historical) volatility from close-to-close returns. | `prices: value` |
| `normalize_candles` | 1 | Normalize positional OHLCV rows into candle maps. | `rows: exchange_data` |

# `candle`

```elixir
@type candle() :: %{
  optional(:timestamp) =&gt; number(),
  optional(:open) =&gt; number(),
  :high =&gt; number(),
  :low =&gt; number(),
  optional(:close) =&gt; number(),
  optional(:volume) =&gt; number()
}
```

OHLC candle for volatility estimation

# `iv_format`

```elixir
@type iv_format() :: :ratio | :premium | :premium_pct
```

IV comparison output format

# `ohlcv_row`

```elixir
@type ohlcv_row() :: [number()]
```

Positional OHLCV row returned by Bourse 0.6.x

# `volatility`

```elixir
@type volatility() :: float()
```

Volatility as decimal (e.g., 0.25 = 25%)

# `cone`

```elixir
@spec cone([number()], [pos_integer()]) :: %{
  required(pos_integer()) =&gt; volatility() | nil
}
```

Calculate volatility cone for term structure analysis.

## Parameters

  * `prices` - List of prices (chronological order) (value)
  * `periods` - List of lookback periods to calculate (value)

## Returns

Map of period => volatility value (or nil if insufficient data) (`map`)

### Example

```elixir
%{7 => 0.42, 30 => 0.36}
```

```elixir
# descripex:contract
%{
  params: %{
    periods: %{
      description: "List of lookback periods to calculate",
      kind: :value
    },
    prices: %{description: "List of prices (chronological order)", kind: :value}
  },
  returns: %{
    type: :map,
    description: "Map of period => volatility value (or nil if insufficient data)"
  },
  returns_example: %{7 => 0.42, 30 => 0.36}
}
```

# `elevated?`

```elixir
@spec elevated?(number(), number(), number()) :: boolean()
```

Check if volatility regime is elevated.

## Parameters

  * `current_vol` - Current volatility (value)
  * `baseline_vol` - Normal/baseline volatility (value)

## Options

  * `threshold` - Multiple of baseline considered elevated (default: `1.5`)

## Returns

true if current_vol > baseline_vol * threshold (`boolean`)

### Example

```elixir
true
```

```elixir
# descripex:contract
%{
  opts: %{
    threshold: %{
      default: 1.5,
      type: :number,
      description: "Multiple of baseline considered elevated"
    }
  },
  params: %{
    current_vol: %{description: "Current volatility", kind: :value},
    baseline_vol: %{description: "Normal/baseline volatility", kind: :value}
  },
  returns: %{
    type: :boolean,
    description: "true if current_vol > baseline_vol * threshold"
  },
  returns_example: true
}
```

# `garman_klass`

```elixir
@spec garman_klass(
  [candle()],
  keyword()
) :: volatility() | nil | {:error, :invalid_prices}
```

Calculate Garman-Klass volatility estimator from OHLC data.

## Parameters

  * `candles` - List of candles with :open, :high, :low, :close fields (exchange_data)

## Options

  * `annualize` - Whether to annualize (default: `false`)
  * `trading_days` - Trading days per year (default: `365`)

## Returns

Garman-Klass volatility, nil if insufficient data, or \{:error, :invalid_prices\} (`float`)

### Example

```elixir
0.1095
```

## Errors

  * `:invalid_prices`

```elixir
# descripex:contract
%{
  opts: %{
    annualize: %{
      default: false,
      type: :boolean,
      description: "Whether to annualize"
    },
    trading_days: %{
      default: 365,
      type: :integer,
      description: "Trading days per year"
    }
  },
  params: %{
    candles: %{
      description: "List of candles with :open, :high, :low, :close fields",
      source: "fetch_ohlcv(symbol)",
      kind: :exchange_data
    }
  },
  errors: [:invalid_prices],
  returns: %{
    type: :float,
    description: "Garman-Klass volatility, nil if insufficient data, or {:error, :invalid_prices}"
  },
  returns_example: 0.1095
}
```

# `iv_percentile`

```elixir
@spec iv_percentile(number(), [number()]) :: float() | nil
```

Calculate IV percentile relative to historical distribution.

## Parameters

  * `current_iv` - Current implied volatility (value)
  * `historical_ivs` - List of historical IV values (value)

## Returns

Percentile (0-100) of historical values below current, or nil if empty (`float`)

### Example

```elixir
0.1095
```

```elixir
# descripex:contract
%{
  params: %{
    current_iv: %{description: "Current implied volatility", kind: :value},
    historical_ivs: %{description: "List of historical IV values", kind: :value}
  },
  returns: %{
    type: :float,
    description: "Percentile (0-100) of historical values below current, or nil if empty"
  },
  returns_example: 0.1095
}
```

# `iv_rank`

```elixir
@spec iv_rank(number(), [number()]) :: float() | nil
```

Calculate IV rank (normalized position in min-max range).

## Parameters

  * `current_iv` - Current implied volatility (value)
  * `historical_ivs` - List of historical IV values (value)

## Returns

Rank (0-100) showing position in min-max range, or nil if insufficient data (`float`)

### Example

```elixir
0.1095
```

```elixir
# descripex:contract
%{
  params: %{
    current_iv: %{description: "Current implied volatility", kind: :value},
    historical_ivs: %{description: "List of historical IV values", kind: :value}
  },
  returns: %{
    type: :float,
    description: "Rank (0-100) showing position in min-max range, or nil if insufficient data"
  },
  returns_example: 0.1095
}
```

# `iv_vs_rv`

```elixir
@spec iv_vs_rv(volatility(), volatility(), iv_format()) :: float() | nil
```

Compare implied volatility to realized volatility.

## Parameters

  * `implied_vol` - Current implied volatility (value)
  * `realized_vol` - Realized volatility over same period (value)

## Options

  * `format` - :ratio (IV/RV), :premium (IV-RV), or :premium_pct ((IV-RV)/RV*100) (default: `:ratio`)

## Returns

Comparison result in specified format, or nil if RV is zero (`float`)

### Example

```elixir
0.1095
```

```elixir
# descripex:contract
%{
  opts: %{
    format: %{
      default: :ratio,
      type: :atom,
      description: ":ratio (IV/RV), :premium (IV-RV), or :premium_pct ((IV-RV)/RV*100)"
    }
  },
  params: %{
    implied_vol: %{description: "Current implied volatility", kind: :value},
    realized_vol: %{
      description: "Realized volatility over same period",
      kind: :value
    }
  },
  returns: %{
    type: :float,
    description: "Comparison result in specified format, or nil if RV is zero"
  },
  returns_example: 0.1095
}
```

# `normalize_candles`

```elixir
@spec normalize_candles([ohlcv_row()]) :: [candle()]
```

Normalize positional OHLCV rows into candle maps.

## Parameters

  * `rows` - List of [timestamp, open, high, low, close, volume] rows (exchange_data)

## Returns

List of candle maps with atom keys (`list`)

### Example

```elixir
[
  %{
    close: 103.0,
    high: 105.0,
    low: 98.0,
    open: 100.0,
    timestamp: 1700000000000,
    volume: 42.0
  }
]
```

```elixir
# descripex:contract
%{
  params: %{
    rows: %{
      description: "List of [timestamp, open, high, low, close, volume] rows",
      source: "fetch_ohlcv(symbol)",
      kind: :exchange_data
    }
  },
  returns: %{type: :list, description: "List of candle maps with atom keys"},
  returns_example: [
    %{
      close: 103.0,
      high: 105.0,
      low: 98.0,
      open: 100.0,
      timestamp: 1700000000000,
      volume: 42.0
    }
  ]
}
```

# `parkinson`

```elixir
@spec parkinson(
  [candle()],
  keyword()
) :: volatility() | nil | {:error, :invalid_prices}
```

Calculate Parkinson volatility estimator from high-low ranges.

## Parameters

  * `candles` - List of candles with :high and :low fields (exchange_data)

## Options

  * `annualize` - Whether to annualize (default: `false`)
  * `trading_days` - Trading days per year (default: `365`)

## Returns

Parkinson volatility estimate, nil if insufficient data, or \{:error, :invalid_prices\} (`float`)

### Example

```elixir
0.1095
```

## Errors

  * `:invalid_prices`

```elixir
# descripex:contract
%{
  opts: %{
    annualize: %{
      default: false,
      type: :boolean,
      description: "Whether to annualize"
    },
    trading_days: %{
      default: 365,
      type: :integer,
      description: "Trading days per year"
    }
  },
  params: %{
    candles: %{
      description: "List of candles with :high and :low fields",
      source: "fetch_ohlcv(symbol)",
      kind: :exchange_data
    }
  },
  errors: [:invalid_prices],
  returns: %{
    type: :float,
    description: "Parkinson volatility estimate, nil if insufficient data, or {:error, :invalid_prices}"
  },
  returns_example: 0.1095
}
```

# `realized`

```elixir
@spec realized(
  [number()],
  keyword()
) :: volatility() | nil | {:error, :invalid_prices}
```

Calculate realized (historical) volatility from close-to-close returns.

## Parameters

  * `prices` - List of prices (chronological order, oldest first) (value)

## Options

  * `annualize` - Whether to annualize the result (default: `false`)
  * `trading_days` - Trading days per year for annualization (default: `365`)

## Returns

Standard deviation of returns, nil if insufficient data, or \{:error, :invalid_prices\} (`float`)

### Example

```elixir
0.1095
```

## Errors

  * `:invalid_prices`

```elixir
# descripex:contract
%{
  opts: %{
    annualize: %{
      default: false,
      type: :boolean,
      description: "Whether to annualize the result"
    },
    trading_days: %{
      default: 365,
      type: :integer,
      description: "Trading days per year for annualization"
    }
  },
  params: %{
    prices: %{
      description: "List of prices (chronological order, oldest first)",
      kind: :value
    }
  },
  errors: [:invalid_prices],
  returns: %{
    type: :float,
    description: "Standard deviation of returns, nil if insufficient data, or {:error, :invalid_prices}"
  },
  returns_example: 0.1095
}
```

# `rolling`

```elixir
@spec rolling([number()], pos_integer(), keyword()) :: [volatility()]
```

Calculate rolling volatility over a price series.

## Parameters

  * `prices` - List of prices (chronological order) (value)
  * `window` - Rolling window size (> 2) (value)

## Options

  * `annualize` - Whether to annualize (default: `false`)
  * `trading_days` - Trading days per year (default: `365`)

## Returns

List of volatility values, empty if insufficient data (`list`)

### Example

```elixir
[0.38, 0.41]
```

```elixir
# descripex:contract
%{
  opts: %{
    annualize: %{
      default: false,
      type: :boolean,
      description: "Whether to annualize"
    },
    trading_days: %{
      default: 365,
      type: :integer,
      description: "Trading days per year"
    }
  },
  params: %{
    window: %{description: "Rolling window size (> 2)", kind: :value},
    prices: %{description: "List of prices (chronological order)", kind: :value}
  },
  returns: %{
    type: :list,
    description: "List of volatility values, empty if insufficient data"
  },
  returns_example: [0.38, 0.41]
}
```

# `yang_zhang`

```elixir
@spec yang_zhang(
  [candle()],
  keyword()
) :: volatility() | nil | {:error, :invalid_prices}
```

Calculate Yang-Zhang volatility estimator from OHLC data.

## Parameters

  * `candles` - List of consecutive candles with :open, :high, :low, :close fields (exchange_data)

## Options

  * `annualize` - Whether to annualize (default: `false`)
  * `trading_days` - Trading days per year (default: `365`)

## Returns

Yang-Zhang volatility, nil if insufficient data, or \{:error, :invalid_prices\} (`float`)

### Example

```elixir
0.1095
```

## Errors

  * `:invalid_prices`

```elixir
# descripex:contract
%{
  opts: %{
    annualize: %{
      default: false,
      type: :boolean,
      description: "Whether to annualize"
    },
    trading_days: %{
      default: 365,
      type: :integer,
      description: "Trading days per year"
    }
  },
  params: %{
    candles: %{
      description: "List of consecutive candles with :open, :high, :low, :close fields",
      source: "fetch_ohlcv(symbol)",
      kind: :exchange_data
    }
  },
  errors: [:invalid_prices],
  returns: %{
    type: :float,
    description: "Yang-Zhang volatility, nil if insufficient data, or {:error, :invalid_prices}"
  },
  returns_example: 0.1095
}
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
