Library/Volatility and Covariance/Conditional Volatility/FIGARCH

D10-F03-A05 / Released engineering topic

FIGARCH: fractional weights are not a normalized power-law average

Keep retained fractional weights and omitted backcast mass separate.

Keep retained fractional weights and omitted backcast mass separate.D10 / D10-F03

For analysts and developers who know residuals from a mean model, conditional variance, and lagged information. The goal is to reproduce the mechanism, inspect its failure states, and decide what the output can legitimately tell you—not to fit or endorse a trading strategy.

Long memory is easy to imitate visually. Draw a slowly decaying curve, normalize its weights and call it fractional memory. That may make a useful smoother, but it is not enough to implement FIGARCH.

This topic fixes a narrower, mathematically explicit model: FIGARCH(0,d,0), evaluated through its ARCH-infinity weights with finite truncation and an explicit backcast for unobserved or omitted history. You will calculate those weights and see why normalizing the retained ones changes the model.

Calculated behavior: Keep retained fractional weights and omitted backcast mass separate. Actual synthetic reference outputs.

Open this figure at full size.

Derive the weights from fractional differencing

For this restricted variant, the fractional coefficients satisfy δ_1=d and

δj=j1djδj1,j2.\delta_j=\frac{j-1-d}{j}\delta_{j-1},\qquad j\ge2.

The FIGARCH reference describes the general ARCH-infinity representation and its recursive coefficients. Setting the ordinary ARCH and GARCH lag coefficients to zero gives the variant implemented here.

With 0<d<1, these weights are positive and sum to one over the infinite history. They are not j^(−d) divided by a finite normalization constant. Their tail follows the fractional-differencing construction; replacing it with a convenient power law changes both early lags and omitted mass.

The parameter d is dimensionless. Residual squares and the backcast have variance units. The positive intercept ω is also on the variance scale.

A two-weight calculation

Set d=0.4. Then δ_1=0.4 and δ_2=(1−0.4)/2×0.4=0.12. The first two weights sum to 0.52. The remaining 0.48 is not an error to remove by renormalization.

Let ω=0.000002, the backcast variance be0.0001, and the observed residuals be ε_0=−0.02 and ε_1=0.01. With truncation m=2, the state at t=2 is

h2=0.000002+0.4(0.01)2+0.12(0.02)2+0.48(0.0001)=0.000138.h_2=0.000002+0.4(0.01)^2+0.12(-0.02)^2+0.48(0.0001) =\mathbf{0.000138}.

The latest residual gets the first weight. Reversing the residual order while retaining the weights changes the answer. The fixture checks both coefficient values, the backcast contribution and the final state.

Independent numeric checkpoint for FIGARCH

Open this figure at full size.

Download the exact worked input and expected values.

What truncation means in this package

At state t, use the most recent min(t,m) observed lagged residuals. The remaining weight mass—including presample history when t<m and the omitted tail beyond m—is assigned to the declared backcast variance:

ht=ω+j=1min(t,m)δjϵtj2+(1j=1min(t,m)δj)b.h_t=\omega+\sum_{j=1}^{\min(t,m)}\delta_j\epsilon_{t-j}^2 +\left(1-\sum_{j=1}^{\min(t,m)}\delta_j\right)b.

This is an explicit approximation, not an observation of the missing infinite past. At the first state, no lagged residual is available, so h_0=ω+b. That initialization differs from simply labeling b as h_0 in GARCH; the contract makes the distinction visible.

Increasing m replaces some backcast contribution with actual older residual information. It does not rescale the newest weight δ_1. If your newest-shock coefficient changes when you change m at fixed d, you are likely normalizing the retained weights and implementing a different filter.

Do not borrow the GARCH long-run formula

The infinite weights sum to one. The usual finite-unconditional-variance calculation cannot divide by 1−Σδ_j, because that denominator is zero. A finite plotted sequence is not evidence of a finite stationary unconditional variance for the integrated model.

The backcast keeps the finite calculation reproducible; it does not resolve the theoretical moment issue. Likewise, the package does not estimate d, fit the general FIGARCH(p,d,q) model, select a truncation automatically or demonstrate that long memory is the right explanation for a real series. Structural breaks can also create persistent-looking patterns.

ConstructionWeight ruleTail treatment
GARCH response on a fixed shock pathGeometric recursionPrior state
This FIGARCH(0,d,0) referenceFractional coefficient recurrenceExplicit backcast mass
Normalized j^(−d) smootherChosen finite power lawRenormalized retained weights

Comparison of FIGARCH conventions, outcomes and limitations.

Open this figure at full size.

What the playground should make undeniable

Use the same 64-residual path while changing d and m independently. Inspect the weight table, retained mass and backcast contribution. Step adds one observed shock history item; it does not merely advance a sentence while leaving the calculation fixed.

At fixed d, changing m must preserve weights for lags present in both runs. At fixed m, changing d changes the recurrence itself. Compare a large old shock with the same shock near the current cutoff to see how its age changes its weight.

The invalid scenario sets d=1 and shows its rejection. The reference API also rejects noninteger truncation or invalid variance inputs. The quiet path still includes ω and the declared missing-history contribution; it is not automatically a zero-volatility state.

Four calculation stages: δ1 = d; Recur unnormalized fractional weights; Weight newest through oldest shocks; Assign unused mass to backcast

Open this figure at full size.

Open the standalone guided playground. The embedded playground and runnable code are available on this page. Download the 64-observation teaching input.

When this article is useful

Use it to distinguish an actual fractional recurrence from a visually similar long-memory smoother, to audit lag ordering, and to assess sensitivity to truncation and initialization. The direct computation is O(nm), appropriate for transparent teaching rather than very large production fits.

Before making a forecasting claim, fit an appropriate model on training data, investigate alternatives and compare chronological predictions. The controlled examples here establish a calculation and its approximation boundary. That is already valuable: you can identify exactly which weights and missing-history assumption produced the number.

Reproduce and inspect the calculation

The Python and TypeScript tabs contain standalone implementations, not imports into an unseen runtime. Both expose calculate(input_data). Feed the worked JSON's input object into that entry point. For the longer experiment, use the teaching-path JSON directly.

Python
import json
from pathlib import Path
from figarch import calculate

data = json.loads(Path("teaching-path.json").read_text())
result = calculate(data)
print(result["latest"])
TypeScript
import {calculate} from './figarch.ts';
const result = calculate(inputData); // inputData is the downloaded JSON object
console.log(result.latest);

Place the downloaded input beside your script and the standalone source on its import path. The Python reference uses the standard library; the TypeScript reference has no external runtime dependency. Shared tests include independent numeric anchors, valid boundaries, rejected inputs and cross-language output comparisons. They establish arithmetic, not forecasting performance.

Evidence and scope

This article uses authored synthetic calculations and primary technical references, reviewed 2026-09-10. Historical market examples are deferred until identity, adjustment basis, chronology and redistribution rights can be verified. No personal trading history or search-ranking superiority is asserted.

FIGARCH(0,d,0) only. Unnormalized fractional weights; finite truncation m, omitted/unobserved mass assigned to explicit backcast variance. 0<d<1, omega>0; not a finite unconditional-variance claim.

Continue the investigation

  • ARCH: compare its assumptions and information boundary before comparing the numbers.
  • HAR-RV: compare its assumptions and information boundary before comparing the numbers.

FIGARCH — calculation-flow

FIGARCH — decision-boundary

ReferencesPrimary sources and evidence notes

Expand the source trail, evidence role, and limitations behind the engineering choices.

Reviewed 2026-09-10. Primary technical documentation and papers; synthetic arithmetic is author-derived. This is a targeted source review, not a verified review of Google's top ten results and not a claim of ranking superiority.

  • S1: arch — FIGARCH model reference — accessed 2026-09-10. Rolling official documentation snapshot; exact package versions used for numerical comparisons are recorded in the repair numeric-evidence.json. Supports the definition and declared convention, not investment performance. Jurisdiction: not applicable to this mathematical reference.

Scope of evidence

FIGARCH(0,d,0) only. Unnormalized fractional weights; finite truncation m, omitted/unobserved mass assigned to explicit backcast variance. 0<d<1, omega>0; not a finite unconditional-variance claim.

Historical case: deferred. No public provider dataset, historical performance claim, or personal trading anecdote is used. Synthetic examples demonstrate arithmetic, not market efficacy. Sources are not copied as article prose.

Accessed: 2026-09-10.

Supports: estimator definition and the explicitly declared variants.

Limitations: technical documentation does not verify a real market feed, author experience, forecast efficacy or search-result superiority. Original-paper access limitations are recorded in the repair report.

figarch.ts
/** Standalone D10-F03-A05 reference. Generated from validated D10 v2 source. */
export class ContractError extends Error {}
type RecordValue=Record<string, any>;
type Matrix=number[][];
const sum=(x:number[]):number=>x.reduce((a,b)=>a+b,0);


function requireValue(ok: unknown, code: string, message: string): asserts ok {
  if (!ok) throw new ContractError(`${code}: ${message}`);
}

function finite(x: unknown, name: string): number {
  requireValue(typeof x === 'number' && Number.isFinite(x), 'NUMBER', `${name} must be a finite number`);
  return x;
}

function integer(x: unknown, name: string, minimum = 0, maximum = 10000): number {
  const v = finite(x, name);
  requireValue(Number.isInteger(v) && v >= minimum && v <= maximum, 'INTEGER', `${name} must be an integer in [${minimum}, ${maximum}]`);
  return v;
}

function param(p: RecordValue, key: string, fallback: number): number {
  return finite(Object.hasOwn(p, key) ? p[key] : fallback, key);
}

function option(p: RecordValue, key: string, fallback: any): any {
  return Object.hasOwn(p, key) ? p[key] : fallback;
}

function positive(p: RecordValue, key: string, fallback: number): number {
  const v = param(p, key, fallback);
  requireValue(v > 0, 'RANGE', `${key} must be positive`);
  return v;
}

function vector(value: unknown, name: string, minimum = 1): number[] {
  requireValue(Array.isArray(value) && value.length >= minimum, 'SHAPE', `${name} needs ${minimum} or more values`);
  return value.map((v, i) => finite(v, `${name}[${i}]`));
}

function seriesResult(series: (RecordValue | null)[], diagnostics: RecordValue): RecordValue {
  const at = series.findIndex(v => v !== null);
  return {series, latest: series.at(-1) ?? null, ready: series.length > 0 && series.at(-1) !== null,
    ready_at: at < 0 ? null : at, diagnostics};
}

function conditional(data: RecordValue, p: RecordValue, kind: string): RecordValue {
  const r = vector(data.returns, 'returns'), omega = param(p, 'omega', kind === 'egarch' ? -.5 : .000002);
  const initial = positive(p, 'initial_variance', .0001), series: (RecordValue | null)[] = r.map(() => null);
  if (kind !== 'egarch') requireValue(omega > 0, 'RANGE', 'omega must be positive');
  if (kind === 'arch') {
    const alphas = vector(option(p, 'alphas', [.15, .1, .05]), 'alphas');
    requireValue(Math.min(...alphas) >= 0 && sum(alphas) < 1, 'PERSISTENCE', 'ARCH coefficients need nonnegative sum below one');
    for (let i = alphas.length; i < r.length; i++) {
      const terms = alphas.map((a, j) => a * r[i - j - 1] ** 2);
      series[i] = {index: i, variance: omega + sum(terms), intercept: omega, lag_contributions: terms};
    }
  } else if (kind === 'figarch') {
    const d = param(p, 'd', .35), m = integer(option(p, 'truncation', 24), 'truncation', 2, 1000);
    const backcast = positive(p, 'backcast_variance', initial);
    requireValue(d > 0 && d < 1, 'RANGE', 'd must be in (0,1)');
    const weights = [d];
    for (let j = 2; j <= m; j++) weights.push((j - 1 - d) / j * weights.at(-1)!);
    for (let i = 0; i < r.length; i++) {
      const used = Math.min(i, m), terms = weights.slice(0, used).map((v, j) => v * r[i - j - 1] ** 2);
      const mass = sum(weights.slice(0, used)), tail = (1 - mass) * backcast;
      series[i] = {index: i, variance: omega + sum(terms) + tail, intercept: omega, weights, lag_contributions: terms, backcast_contribution: tail, memory_mass: mass};
    }
  } else {
    const alpha = param(p, 'alpha', .08), beta = param(p, 'beta', .9), gamma = param(p, 'gamma', kind === 'egarch' ? -.12 : .02);
    if (kind === 'egarch') requireValue(Math.abs(beta) < 1, 'PERSISTENCE', 'EGARCH requires abs(beta)<1 in this contract');
    else {
      requireValue(alpha >= 0 && beta >= 0 && (kind !== 'gjr_garch' || alpha + gamma >= 0), 'RANGE', 'invalid variance coefficients');
      requireValue(alpha + beta + (kind === 'gjr_garch' ? gamma / 2 : 0) < 1, 'PERSISTENCE', 'persistence must be below one');
    }
    series[0] = {index: 0, variance: initial, initialization: true};
    for (let i = 1; i < r.length; i++) {
      const previous = series[i - 1]!.variance, shock = r[i - 1];
      if (kind === 'egarch') {
        const z = shock / Math.sqrt(previous), carry = beta * Math.log(previous), magnitude = alpha * (Math.abs(z) - Math.sqrt(2 / Math.PI)), sign = gamma * z;
        const lv = omega + carry + magnitude + sign;
        requireValue(lv >= -700 && lv <= 700, 'NUMERIC', 'EGARCH exponent outside supported safe range');
        series[i] = {index: i, variance: Math.exp(lv), log_variance: lv, standardized_shock: z, intercept: omega, carry, magnitude_contribution: magnitude, sign_contribution: sign};
      } else {
        const news = alpha * shock * shock, threshold = kind === 'gjr_garch' && shock < 0 ? gamma * shock * shock : 0, carry = beta * previous;
        series[i] = {index: i, variance: omega + news + threshold + carry, intercept: omega, shock_contribution: news, threshold_contribution: threshold, carry};
      }
    }
  }
  return seriesResult(series, {causal: true, input_count: r.length, fitted_parameters: false, model: kind});
}
export function calculate(data: RecordValue): RecordValue {
  requireValue(data && typeof data === 'object' && !Array.isArray(data),'SHAPE','input must be an object');
  const p=Object.hasOwn(data,'parameters')?data.parameters:{};
  requireValue(p && typeof p === 'object' && !Array.isArray(p),'SHAPE','parameters must be an object');
  const result=conditional(data,p,"figarch");
  function check(v:any):void {
    if(typeof v==='number')requireValue(Number.isFinite(v),'NUMERIC','nonfinite computed output');
    else if(Array.isArray(v))v.forEach(check);
    else if(v && typeof v==='object')Object.values(v).forEach(check);
  }
  check(result);
  return {topic_id:"D10-F03-A05",title:"FIGARCH",parameters:p,...result};
}
Full-height labguided labOpen full screen
Written by

Fintech engineer building market-data and financial systems, and the author of every article, glossary record, and reference implementation on The Fintech Builder.