fintech-algorithms
Using a coding agent? Give it the skill: npx skills add IslamBaraka90/Fintech-Algorithms-Library What it does →

Cyclical and Commodity-Cycle Normalization

Install and import#

bash
npm install fintech-algorithms
ts
import { cyclicalAndCommodityCycleNormalization } from "fintech-algorithms/fundamental-analysis-and-valuation/sector-specific-equity-scoring/cyclical-and-commodity-cycle-normalization";

Signature#

cyclicalAndCommodityCycleNormalization(data)

Normalizes a commodity producer to mid-cycle by taking the median of its price, unit-cost and volume histories, rebuilding revenue and EBITDA from those medians less fixed costs, and scoring the resulting margin, leverage, free-cash-flow consistency and where the current price sits in its own history.

Parameters#

NameTypeNotes
dataRecordValueA plain object. framework must be the string cyclical-midcycle-teaching-v1. weights must be an object holding exactly normalized_margin, normalized_leverage, cash_consistency and cycle_balance. realized_price_history, unit_cost_history, volume_history and free_cash_flow_history must each be an array of at least seven finite numbers, and all four must have the same length; the price, cost and volume series must additionally be strictly positive throughout. fixed_costs and net_debt must be nonnegative and current_realized_price strictly positive.
min_observations: 7

Returns#

{ state: string; method: string; normalized_price: number; normalized_unit_cost: number; normalized_volume: number; normalized_revenue: number; normalized_ebitda: number; normalized_margin: number; normalized_net_leverage: number | null; positive_fcf_ratio: number; cycle_percentile: number; component_scores: Record<string, number>; weights: Record<string, number>; normalized_score: number; coverage_ratio: number; reason: string }

normalized_price, normalized_unit_cost and normalized_volume are the medians of their series. normalized_revenue is median price times median volume; normalized_ebitda is median price less median unit cost, times median volume, less fixed_costs; normalized_margin is EBITDA over revenue. normalized_net_leverage is net_debt over that EBITDA, or null when EBITDA is not positive, in which case the leverage component scores 0. positive_fcf_ratio is the share of the free-cash-flow history above zero and cycle_percentile the share of historical prices at or below current_realized_price. component_scores holds four band scores on a 0-100 scale and normalized_score is their weighted sum. state is nonpositive-midcycle-ebitda-review when EBITDA is zero or negative, otherwise the band of the score: strong-review-band at 75 or more, mixed-review-band at 50 or more, weak-review-band below that. method is cyclical-midcycle-normalization-v1 and coverage_ratio is 1.

Errors#

  • When data is not a plain object — throws TypeError
  • When framework is not a nonempty string, or a history entry is not a finite number — throws TypeError
  • When framework is not cyclical-midcycle-teaching-v1 — throws RangeError
  • When any of the four histories is not an array of at least 7 observations — throws RangeError
  • When the four histories do not all have the same length — throws RangeError
  • When any price, unit-cost or volume observation is zero or negative — throws RangeError
  • When weights does not hold exactly the four component names, or its values do not sum to 1 within 1e-9 — throws RangeError
  • When fixed_costs or net_debt is negative, or current_realized_price is not positive — throws RangeError

Complexity: time O(n log n), space O(n).

Worked example#

executed Captured by running this function on the input its own test provides. Real output of real code — but not asserted against a published figure.

Input#

data
{
  "framework": "cyclical-midcycle-teaching-v1",
  "realized_price_history": [72, 64, 58, 83, 91, 77],
  "unit_cost_history": [43, 44, 45, 47, 49, 46],
  "volume_history": [9.2, 9.4, 9, 9.7, 9.8, 9.6],
  "free_cash_flow_history": [52, 18, -8, 74, 96, 61],
  "fixed_costs": 105,
  "net_debt": 690,
  "current_realized_price": 74,
  "weights": {
    "normalized_margin": 0.32,
    "normalized_leverage": 0.28,
    "cash_consistency": 0.22,
    "cycle_balance": 0.18
  }
}

Call#

cyclicalAndCommodityCycleNormalization(data)

Returns#

object with 16 fields: state, method, normalized_price, normalized_unit_cost, normalized_volume, normalized_revenue, normalized_ebitda, normalized_margin, …

{
  "state": "mixed-review-band",
  "method": "cyclical-midcycle-normalization-v1",
  "normalized_price": 72,
  "normalized_unit_cost": 45,
  "normalized_volume": 9.4,
  "normalized_revenue": 676.8000000000001,
  "normalized_ebitda": 148.8,
  "normalized_margin": 0.21985815602836878,
  "normalized_net_leverage": 4.637096774193548,
  "positive_fcf_ratio": 0.7777777777777778,
  "cycle_percentile": 0.6666666666666666,
  "component_scores": {
    "normalized_margin": 73.28605200945627,
    "normalized_leverage": 0,
    "cash_consistency": 79.62962962962962,
    "cycle_balance": 83.33333333333333
  },
  "weights": {
    "normalized_margin": 0.32,
    "normalized_leverage": 0.28,
    "cash_consistency": 0.22,
    "cycle_balance": 0.18
  },
  "normalized_score": 55.97005516154452
}

Showing 14 of 16 fields.

Other exports#

This module also exports calculate, bankFundamentalScore, insuranceFundamentalScore, reitFundamentalScore, utilityFundamentalScore, earlyStageLiquidityAndRunwayScore, holdingCompanyLookThroughScore, sectorSpecificWeightCalibration, unsupportedScopeAndCoverageDecision. Every module additionally exports run as an alias of its primary function, and a meta object carrying its catalog id, domain, family, shape and article URL.

Diagrams#

Cyclical and Commodity-Cycle Normalization — article hero
Cyclical and Commodity-Cycle Normalization — component ledger
Cyclical and Commodity-Cycle Normalization — decision boundary
Cyclical and Commodity-Cycle Normalization — evidence clock
Cyclical and Commodity-Cycle Normalization — model anatomy
Cyclical and Commodity-Cycle Normalization — system map

How it works#

This page states the contract — how to call it correctly. The article explains the concept: why it works, and where it breaks.

Read the article →

References#

The rest of the Sector-Specific Equity Scoring family#