ZenQuant.Funding (zen_quant v0.2.0)

Copy Markdown View Source

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

FunctionArityDescriptionParam Kinds
carry_candidates2Rank cross-venue delta-neutral funding-carry candidates on a common horizon.venues: exchange_data
favorable?2Check if current funding is favorable for a position direction.rate: exchange_data, direction: value
rank2Rank funding rates across exchanges, sorted by rate.rates_by_exchange: exchange_data
mean_reversion_signal2Generate mean reversion signal from funding rate history.rates: exchange_data
trend2Detect funding rate trend direction using linear regression.rates: exchange_data
volatility1Calculate funding rate volatility (standard deviation).rates: exchange_data
cumulative1Calculate cumulative funding over a period.rates: exchange_data
compare1Compare funding rates across exchanges, sorted by rate descending.rates: exchange_data
detect_spikes2Detect funding rate spikes (abnormally high or low rates).rates: exchange_data
average1Calculate average funding rate from a list of rates.rates: exchange_data
annualize2Annualize a per-period funding rate to APR.rate: value, period_hours: value

Summary

Types

Caller-supplied assumption required for each candidate leg

Delta-neutral funding-carry candidate between two venues

Estimated costs over the comparison horizon, as fractions of notional

Completeness label for a carry candidate's cost inputs

Functions

Annualize a per-period funding rate to APR.

Calculate average funding rate from a list of rates.

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

Compare funding rates across exchanges, sorted by rate descending.

Calculate cumulative funding over a period.

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

Check if current funding is favorable for a position direction.

Generate mean reversion signal from funding rate history.

Rank funding rates across exchanges, sorted by rate.

Detect funding rate trend direction using linear regression.

Calculate funding rate volatility (standard deviation).

Types

carry_assumption()

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

Caller-supplied assumption required for each candidate leg

carry_candidate()

@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()

@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()

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

Completeness label for a carry candidate's cost inputs

Functions

annualize(rate, period_hours \\ 8)

@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

0.1095
# 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(rates)

@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

0.1095

Composes With

  • annualize
# 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(venues, opts \\ [])

@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(rates)

@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(rates)

@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

0.1095
# 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(rates, opts \\ [])

@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

[high: %{symbol: "BTC/USDT:USDT", funding_rate: 3.5e-4}]
# 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?(map, arg2)

@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

true
# 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(rates, opts \\ [])

@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

:neutral
# 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(rates_by_exchange, opts \\ [])

@spec rank(
  %{optional(any()) => [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

[%{exchange: :binance, symbol: "BTC/USDT:USDT", rate: 0.0001, apr: 0.1095}]
# 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(rates, opts \\ [])

@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

:rising
# 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(rates)

@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

0.1095
# 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
}