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

Descending Triangle

Install and import#

bash
npm install fintech-algorithms
ts
import { detectDescendingTriangle } from "fintech-algorithms/geometric-chart-patterns/continuation-structures/descending-triangle";

Signature#

detectDescendingTriangle(close, parameters)

Scans a close series for a descending triangle - a falling ceiling pressing down on a flat floor - and reports whether that shape is still forming, has broken out, or was rejected. The two boundaries are least-squares lines fitted through the confirmed swing highs and swing lows of a pivot window; the shape passes when the floor's relative slope is within flat_tolerance of zero, the ceiling's is at most -min_slope, and the channel narrows by at least min_convergence. A close beyond either boundary by break_buffer within max_confirmation_bars of the last pivot ends the search, and only a bearish break confirms.

Parameters#

NameTypeNotes
closenumber[]The closing price series in chronological order, oldest bar first. Every value must be a finite number strictly greater than zero.
exclusive_min: 0 · finite: true
nulls: reject
parametersPartial<Parameters>Overrides merged over DEFAULTS. The pivot scan uses pivot_left (2) and pivot_right (2) bars on each side and a window needs pivots_per_side (3) confirmed highs and the same number of lows; max_pattern_bars (44) caps the span from the first pivot to the last, max_confirmation_bars (4) caps how far past the last pivot a breakout is still accepted, and impulse_lookback (12) sets how far back the prior move is measured. The shape budget is flat_tolerance (0.00045), min_slope (0.00022), parallel_tolerance (0.00035), min_convergence (0.18), min_impulse (0.055) and break_buffer (0.001); the three slope figures are compared against a fitted slope divided by the window's mean pivot price, min_convergence against the fraction of channel width lost across the window, min_impulse against the return over impulse_lookback bars, and break_buffer against the fraction by which a close must clear a boundary. Any key outside this set is rejected.
optional

Returns#

{ pattern: string; state: string; reason: string; event: Detection | null; events: Detection[]; pivots: Pivot[]; parameters: Parameters }

An object carrying pattern, the overall state (searching, rejected, candidate or confirmed), the reason behind that state, the selected event, the events that reached confirmed, every alternating pivots entry found so far, and the resolved parameters. Each event holds pivot_indices, pivot_prices, pivot_types, candidate_available_index, confirmation_index, breakout_direction, expected_direction, subtype, upper_boundary, lower_boundary and a metrics map with upper_slope, lower_slope, upper_relative_slope, lower_relative_slope, upper_intercept, lower_intercept, width_start, width_end, convergence, parallel_gap, impulse, impulse_direction and formation_bars.

Errors#

  • When close is not an array, or is empty — throws Error
  • When any close value is not a finite number, or is not strictly positive — throws Error
  • When parameters carries a key that is not one of the twelve in DEFAULTS — throws Error
  • When pivot_left, pivot_right, pivots_per_side, max_pattern_bars, max_confirmation_bars or impulse_lookback is not a positive integer, or pivots_per_side is below 2 — throws Error
  • When flat_tolerance, min_slope, parallel_tolerance, min_convergence, min_impulse or break_buffer is negative or not finite, or min_convergence or break_buffer is 1 or more — throws Error
  • When fewer than pivots_per_side * 2 alternating pivots have been confirmed — returns state searching rather than throwing

Complexity: time O(n * (pivot_left + pivot_right)), space O(n).

Worked example#

executed This entry is a thin wrapper its test never calls directly, so it was invoked with the arguments its shared implementation received. Real output, not asserted against a published figure.

Input#

close
[140, 138, 136, 134, 132, 130]

Showing 6 of 96 elements.

Call#

detectDescendingTriangle(close, parameters)

Returns#

object with 7 fields: pattern, state, reason, event, events, pivots, parameters

{
  "pattern": "descending_triangle",
  "state": "confirmed",
  "reason": "breakout-confirmed",
  "event": {
    "pattern": "descending_triangle",
    "state": "confirmed",
    "reason": "breakout-confirmed",
    "pivot_indices": [16, 22, 28, 34, 40, 46],
    "pivot_prices": [108, 122, 108, 118, 108.1, 115],
    "pivot_types": ["low", "high", "low", "high", "low", "high"],
    "candidate_available_index": 48,
    "confirmation_index": 50,
    "breakout_direction": "bearish",
    "expected_direction": "bearish",
    "subtype": null,
    "upper_boundary": 113.66666666666667,
    "lower_boundary": 108.125,
    "metrics": {
      "upper_slope": -0.2916666666666667,
      "lower_slope": 0.00416666666666643,
      "upper_relative_slope": -0.0025769400677367106,
      "lower_relative_slope": 0.00003681342953909377,
      "upper_intercept": 128.25,
      "lower_intercept": 107.91666666666669,
      "width_start": 15.59999999999998,
      "width_end": 6.72499999999998,
      "convergence": 0.5689102564102572,
      "parallel_gap": 0.002613753497275804,
      "impulse": -0.18181818181818182,
      "impulse_direction": "bearish",
      "formation_bars": 30
    }
  },
  "events": [
    {
      "pattern": "descending_triangle",
      "state": "confirmed",
      "reason": "breakout-confirmed",
      "pivot_indices": [16, 22, 28, 34, 40, 46],
      "pivot_prices": [108, 122, 108, 118, 108.1, 115],
      "pivot_types": ["low", "high", "low", "high", "low", "high"],
      "candidate_available_index": 48,
      "confirmation_index": 50,
      "breakout_direction": "bearish",
      "expected_direction": "bearish",
      "subtype": null,
      "upper_boundary": 113.66666666666667,
      "lower_boundary": 108.125,
      "metrics": {
        "upper_slope": -0.2916666666666667,
        "lower_slope": 0.00416666666666643,
        "upper_relative_slope": -0.0025769400677367106,
        "lower_relative_slope": 0.00003681342953909377,
        "upper_intercept": 128.25,
        "lower_intercept": 107.91666666666669,
        "width_start": 15.59999999999998,
        "width_end": 6.72499999999998,
        "convergence": 0.5689102564102572,
        "parallel_gap": 0.002613753497275804,
        "impulse": -0.18181818181818182,
        "impulse_direction": "bearish",
        "formation_bars": 30
      }
    }
  ],
  "pivots": [
    {
      "kind": "low",
      "index": 16,
      "confirmation_index": 18,
      "price": 108
    },
    {
      "kind": "high",
      "index": 22,
      "confirmation_index": 24,
      "price": 122
    },
    {
      "kind": "low",
      "index": 28,
      "confirmation_index": 30,
      "price": 108
    }
  ],
  "parameters": {
    "pivot_left": 2,
    "pivot_right": 2,
    "pivots_per_side": 3,
    "max_pattern_bars": 44,
    "max_confirmation_bars": 4,
    "impulse_lookback": 12,
    "flat_tolerance": 0.00045,
    "min_slope": 0.00022,
    "parallel_tolerance": 0.00035,
    "min_convergence": 0.18,
    "min_impulse": 0.055,
    "break_buffer": 0.001
  }
}

Other exports#

This module also exports traceDescendingTriangle. 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#

Descending Triangle — algorithm anatomy
Descending Triangle — article hero
Descending Triangle — calculation ledger
Descending Triangle — causal timeline
Descending Triangle — failure boundary
Descending Triangle — method comparison

Calculation flow#

Reasoning Flow — Descending Triangle
flowchart LR
    A["Finalized close arrives"] --> B["Confirm strict pivots after right-window delay"]
    B --> C{"Six alternating pivots available?"}
    C -->|No| S["SEARCHING"]
    C -->|Yes| D["Fit upper and lower boundaries"]
    D --> E{"Topic geometry contract passes?"}
    E -->|No| R["REJECTED with reason"]
    E -->|Yes| K["CANDIDATE at latest pivot confirmation"]
    K --> F{"Later strict buffered crossing?"}
    F -->|Accepted side| G["CONFIRMED"]
    F -->|Opposite side or expired| R
    F -->|Not yet| K

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 Continuation Structures family#