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.
Open this figure at full size.
Derive the weights from fractional differencing
For this restricted variant, the fractional coefficients satisfy δ_1=d and
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
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.
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:
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.
| Construction | Weight rule | Tail treatment |
|---|---|---|
| GARCH response on a fixed shock path | Geometric recursion | Prior state |
| This FIGARCH(0,d,0) reference | Fractional coefficient recurrence | Explicit backcast mass |
| Normalized j^(−d) smoother | Chosen finite power law | Renormalized retained weights |
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.
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.
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"])
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
Rendered from the canonical Mermaid sources linked by this article.
FIGARCH — calculation-flow
FIGARCH — decision-boundary
ReferencesPrimary sources and evidence notesExpand the source trail, evidence role, and limitations behind the engineering choices.
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.
Full dependency-light reference implementations in both supported languages.
/** 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};
}
The embedded lab now expands to its full document height, keeping the article as the only scroll surface.
