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

Funding rate analysis functions for perpetual futures.

Pure functions for analyzing funding rates, calculating annualized returns,
detecting spikes, comparing rates across exchanges, trend detection,
mean reversion signals, cross-exchange ranking, and delta-neutral
funding-carry candidate ranking.

Works with plain maps from exchange APIs (e.g., maps with `:funding_rate` and `:symbol` keys).

## Example

    rates = [
      %{symbol: "BTC/USDT:USDT", funding_rate: 0.0001},
      %{symbol: "BTC/USDT:USDT", funding_rate: 0.00015}
    ]

    ZenQuant.Funding.average(rates)
    # => 0.000125

    ZenQuant.Funding.annualize(0.0001)
    # => 0.1095  (10.95% APR)

## API Functions
| Function | Arity | Description | Param Kinds |
| --- | --- | --- | --- |
| `carry_candidates` | 2 | Rank cross-venue delta-neutral funding-carry candidates on a common horizon. | `venues: exchange_data` |
| `favorable?` | 2 | Check if current funding is favorable for a position direction. | `rate: exchange_data`, `direction: value` |
| `rank` | 2 | Rank funding rates across exchanges, sorted by rate. | `rates_by_exchange: exchange_data` |
| `mean_reversion_signal` | 2 | Generate mean reversion signal from funding rate history. | `rates: exchange_data` |
| `trend` | 2 | Detect funding rate trend direction using linear regression. | `rates: exchange_data` |
| `volatility` | 1 | Calculate funding rate volatility (standard deviation). | `rates: exchange_data` |
| `cumulative` | 1 | Calculate cumulative funding over a period. | `rates: exchange_data` |
| `compare` | 1 | Compare funding rates across exchanges, sorted by rate descending. | `rates: exchange_data` |
| `detect_spikes` | 2 | Detect funding rate spikes (abnormally high or low rates). | `rates: exchange_data` |
| `average` | 1 | Calculate average funding rate from a list of rates. | `rates: exchange_data` |
| `annualize` | 2 | Annualize a per-period funding rate to APR. | `rate: value`, `period_hours: value` |

# `carry_assumption`

```elixir
@type carry_assumption() ::
  :fees | :borrow_cost | :basis | :transfer_cost | :capital_requirement
```

Caller-supplied assumption required for each candidate leg

# `carry_candidate`

```elixir
@type carry_candidate() :: %{
  long_venue: String.t(),
  short_venue: String.t(),
  long_symbol: String.t(),
  short_symbol: String.t(),
  horizon_hours: pos_integer(),
  long_funding_interval_hours: number(),
  short_funding_interval_hours: number(),
  long_rate_horizon_hours: number(),
  short_rate_horizon_hours: number(),
  long_rate: float(),
  short_rate: float(),
  long_rate_normalized: float(),
  short_rate_normalized: float(),
  gross_carry: float(),
  costs: carry_costs(),
  costs_by_leg: %{long: carry_costs(), short: carry_costs()},
  capital_requirement: float() | nil,
  capital_requirement_by_leg: %{long: float() | nil, short: float() | nil},
  net_carry: float() | nil,
  status: carry_status(),
  missing_inputs: [{:long | :short, carry_assumption()}],
  required_assumptions: [carry_assumption()]
}
```

Delta-neutral funding-carry candidate between two venues

# `carry_costs`

```elixir
@type carry_costs() :: %{
  fees: float() | nil,
  borrow_cost: float() | nil,
  basis: float() | nil,
  transfer_cost: float() | nil
}
```

Estimated costs over the comparison horizon, as fractions of notional

# `carry_status`

```elixir
@type carry_status() :: :complete | :incomplete
```

Completeness label for a carry candidate's cost inputs

# `annualize`

```elixir
@spec annualize(number(), pos_integer()) :: float()
```

Annualize a per-period funding rate to APR.

## Parameters

  * `rate` - Per-period funding rate as decimal (e.g., 0.0001) (value)
  * `period_hours` - Hours per funding period (default: `8`, value)

## Returns

Annualized percentage rate (APR) (`float`)

### Example

```elixir
0.1095
```

```elixir
# descripex:contract
%{
  params: %{
    rate: %{
      description: "Per-period funding rate as decimal (e.g., 0.0001)",
      kind: :value
    },
    period_hours: %{
      default: 8,
      description: "Hours per funding period",
      kind: :value
    }
  },
  returns: %{type: :float, description: "Annualized percentage rate (APR)"},
  returns_example: 0.1095
}
```

# `average`

```elixir
@spec average([map()]) :: float() | nil
```

Calculate average funding rate from a list of rates.

## Parameters

  * `rates` - List of maps with :funding_rate key (exchange_data)

## Returns

Average funding rate, or nil if empty (`float`)

### Example

```elixir
0.1095
```

## Composes With

  * `annualize`

```elixir
# descripex:contract
%{
  params: %{
    rates: %{
      description: "List of maps with :funding_rate key",
      source: "fetch_funding_rates(symbol)",
      kind: :exchange_data
    }
  },
  returns: %{type: :float, description: "Average funding rate, or nil if empty"},
  returns_example: 0.1095,
  composes_with: [:annualize]
}
```

# `carry_candidates`

```elixir
@spec carry_candidates(
  [map()],
  keyword()
) :: [carry_candidate()]
```

Rank cross-venue delta-neutral funding-carry candidates.

Each venue must identify the same `:symbol` and supply both its funding cadence
(`:funding_interval_hours`) and the horizon represented by its quoted rate
(`:rate_horizon_hours`). Rates are normalized from their quoted horizon to the
shared `:horizon_hours`; settlement cadence is exposed separately.

Each venue also supplies its own `:fees`, `:borrow_cost`, `:basis`,
`:transfer_cost`, and `:capital_requirement` assumptions. For each compatible
cross-venue pair, the candidate is long the lower normalized rate and short
the higher (stable venue-name order on ties). Gross carry, per-leg assumptions,
aggregate estimated costs, and capital are exposed separately. Incomplete
inputs yield `status: :incomplete` and `net_carry: nil` — never a
profitable/arbitrage label from spread alone.

Cost assumptions are fractions of notional over the comparison horizon.
Positive `:basis` is treated as a cost and negative `:basis` as a credit.

# `compare`

```elixir
@spec compare([map()]) :: [%{symbol: String.t(), rate: float(), apr: float()}]
```

Compare funding rates across exchanges, sorted by rate descending.

Each rate map must include a positive `:funding_interval_hours` (caller-supplied
settlement cadence). APR is computed via `annualize/2` with that cadence.

**Missing cadence rule:** rates without a funding rate or without a positive
whole-hour `:funding_interval_hours` are omitted from the result. There is no
silent 8-hour default.

# `cumulative`

```elixir
@spec cumulative([map()]) :: float()
```

Calculate cumulative funding over a period.

## Parameters

  * `rates` - List of maps with :funding_rate key (exchange_data)

## Returns

Sum of all funding rates (`float`)

### Example

```elixir
0.1095
```

```elixir
# descripex:contract
%{
  params: %{
    rates: %{
      description: "List of maps with :funding_rate key",
      source: "fetch_funding_rates(symbol)",
      kind: :exchange_data
    }
  },
  returns: %{type: :float, description: "Sum of all funding rates"},
  returns_example: 0.1095
}
```

# `detect_spikes`

```elixir
@spec detect_spikes(
  [map()],
  keyword()
) :: [{:high | :low, map()}]
```

Detect funding rate spikes (abnormally high or low rates).

## Parameters

  * `rates` - List of maps with :funding_rate key (exchange_data)

## Options

  * `threshold` - Std devs for spike detection (default: `2.0`)

## Returns

List of \{:high | :low, rate_map\} tuples (`list`)

### Example

```elixir
[high: %{symbol: "BTC/USDT:USDT", funding_rate: 3.5e-4}]
```

```elixir
# descripex:contract
%{
  opts: %{
    threshold: %{
      default: 2.0,
      type: :float,
      description: "Std devs for spike detection"
    }
  },
  params: %{
    rates: %{
      description: "List of maps with :funding_rate key",
      source: "fetch_funding_rates(symbol)",
      kind: :exchange_data
    }
  },
  returns: %{
    type: :list,
    description: "List of {:high | :low, rate_map} tuples"
  },
  returns_example: [high: %{symbol: "BTC/USDT:USDT", funding_rate: 3.5e-4}]
}
```

# `favorable?`

```elixir
@spec favorable?(map(), :long | :short) :: boolean()
```

Check if current funding is favorable for a position direction.

## Parameters

  * `rate` - Map with :funding_rate key (exchange_data)
  * `direction` - Position direction (:long or :short) (value)

## Returns

true if you receive funding, false if you pay (`boolean`)

### Example

```elixir
true
```

```elixir
# descripex:contract
%{
  params: %{
    direction: %{
      description: "Position direction (:long or :short)",
      kind: :value
    },
    rate: %{
      description: "Map with :funding_rate key",
      source: "fetch_funding_rates(symbol)",
      kind: :exchange_data
    }
  },
  returns: %{
    type: :boolean,
    description: "true if you receive funding, false if you pay"
  },
  returns_example: true
}
```

# `mean_reversion_signal`

```elixir
@spec mean_reversion_signal(
  [map()],
  keyword()
) :: :overbought | :oversold | :neutral | nil
```

Generate mean reversion signal from funding rate history.

## Parameters

  * `rates` - Time-ordered list of maps with :funding_rate key (last = current) (exchange_data)

## Options

  * `threshold` - Z-score threshold for signal (default: `2.0`)
  * `lookback` - Baseline point count (nil = all)

## Returns

:overbought | :oversold | :neutral | nil (`atom`)

### Example

```elixir
:neutral
```

```elixir
# descripex:contract
%{
  opts: %{
    threshold: %{
      default: 2.0,
      type: :float,
      description: "Z-score threshold for signal"
    },
    lookback: %{
      default: nil,
      type: :integer,
      description: "Baseline point count (nil = all)"
    }
  },
  params: %{
    rates: %{
      description: "Time-ordered list of maps with :funding_rate key (last = current)",
      source: "fetch_funding_rates(symbol)",
      kind: :exchange_data
    }
  },
  returns: %{
    type: :atom,
    description: ":overbought | :oversold | :neutral | nil"
  },
  returns_example: :neutral
}
```

# `rank`

```elixir
@spec rank(
  %{optional(any()) =&gt; [map()]},
  keyword()
) :: [map()]
```

Rank funding rates across exchanges, sorted by rate.

## Parameters

  * `rates_by_exchange` - Map of %\{exchange_name => [rate_maps]\}. Each rate map needs :symbol, :funding_rate, and :funding_interval_hours (settlement cadence in hours; required). Rates missing a positive interval are omitted — no default 8h assumption. (exchange_data)

## Options

  * `direction` - Sort direction (:desc or :asc) (default: `:desc`)
  * `limit` - Max results (alias: :top)

## Returns

List of maps with :exchange, :symbol, :rate, :apr (`list`)

### Example

```elixir
[%{exchange: :binance, symbol: "BTC/USDT:USDT", rate: 0.0001, apr: 0.1095}]
```

```elixir
# descripex:contract
%{
  opts: %{
    limit: %{
      default: nil,
      type: :integer,
      description: "Max results (alias: :top)"
    },
    direction: %{
      default: :desc,
      type: :atom,
      description: "Sort direction (:desc or :asc)"
    }
  },
  params: %{
    rates_by_exchange: %{
      description: "Map of %{exchange_name => [rate_maps]}. Each rate map needs :symbol, :funding_rate, and :funding_interval_hours (settlement cadence in hours; required). Rates missing a positive interval are omitted — no default 8h assumption.",
      source: "multiple fetch_funding_rates calls",
      kind: :exchange_data
    }
  },
  returns: %{
    type: :list,
    description: "List of maps with :exchange, :symbol, :rate, :apr"
  },
  returns_example: [
    %{exchange: :binance, symbol: "BTC/USDT:USDT", rate: 0.0001, apr: 0.1095}
  ]
}
```

# `trend`

```elixir
@spec trend(
  [map()],
  keyword()
) :: :rising | :falling | :stable | nil
```

Detect funding rate trend direction using linear regression.

## Parameters

  * `rates` - Time-ordered list of maps with :funding_rate key (exchange_data)

## Options

  * `threshold` - Min absolute slope for trending (default: `0.0`)

## Returns

:rising | :falling | :stable | nil (`atom`)

### Example

```elixir
:rising
```

```elixir
# descripex:contract
%{
  opts: %{
    threshold: %{
      default: 0.0,
      type: :float,
      description: "Min absolute slope for trending"
    }
  },
  params: %{
    rates: %{
      description: "Time-ordered list of maps with :funding_rate key",
      source: "fetch_funding_rates(symbol)",
      kind: :exchange_data
    }
  },
  returns: %{type: :atom, description: ":rising | :falling | :stable | nil"},
  returns_example: :rising
}
```

# `volatility`

```elixir
@spec volatility([map()]) :: float() | nil
```

Calculate funding rate volatility (standard deviation).

## Parameters

  * `rates` - List of maps with :funding_rate key (exchange_data)

## Returns

Population std dev of rates, or nil if < 2 points (`float`)

### Example

```elixir
0.1095
```

```elixir
# descripex:contract
%{
  params: %{
    rates: %{
      description: "List of maps with :funding_rate key",
      source: "fetch_funding_rates(symbol)",
      kind: :exchange_data
    }
  },
  returns: %{
    type: :float,
    description: "Population std dev of rates, or nil if < 2 points"
  },
  returns_example: 0.1095
}
```

---

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