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

Symmetrical Triangle

Install and import#

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

Signature#

detectSymmetricalTriangle(close, parameters)

Scans a close series for a symmetrical triangle - a falling ceiling and a rising floor closing on each other - and reports whether that shape is still forming, has broken out, or was rejected. The shape passes when the fitted ceiling's relative slope is at most -min_slope, the floor's is at least min_slope, and the channel narrows by at least min_convergence. The prior move over impulse_lookback bars sets the expected side only when its size reaches min_impulse; without that, a break through either boundary by break_buffer 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
[90, 92, 94, 96, 98, 100]

Showing 6 of 96 elements.

Call#

detectSymmetricalTriangle(close, parameters)

Returns#

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

{
  "pattern": "symmetrical_triangle",
  "state": "confirmed",
  "reason": "breakout-confirmed",
  "event": {
    "pattern": "symmetrical_triangle",
    "state": "confirmed",
    "reason": "breakout-confirmed",
    "pivot_indices": [16, 22, 28, 34, 40, 46],
    "pivot_prices": [122, 106, 119, 109, 117, 111],
    "pivot_types": ["high", "low", "high", "low", "high", "low"],
    "candidate_available_index": 48,
    "confirmation_index": 49,
    "breakout_direction": "bullish",
    "expected_direction": "bullish",
    "subtype": null,
    "upper_boundary": 114.95833333333333,
    "lower_boundary": 111.79166666666667,
    "metrics": {
      "upper_slope": -0.20833333333333334,
      "lower_slope": 0.20833333333333334,
      "upper_relative_slope": -0.0018274853801169592,
      "lower_relative_slope": 0.0018274853801169592,
      "upper_intercept": 125.16666666666666,
      "lower_intercept": 101.58333333333334,
      "width_start": 16.916666666666657,
      "width_end": 4.416666666666657,
      "convergence": 0.7389162561576359,
      "parallel_gap": 0.0036549707602339184,
      "impulse": 0.24489795918367346,
      "impulse_direction": "bullish",
      "formation_bars": 30
    }
  },
  "events": [
    {
      "pattern": "symmetrical_triangle",
      "state": "confirmed",
      "reason": "breakout-confirmed",
      "pivot_indices": [16, 22, 28, 34, 40, 46],
      "pivot_prices": [122, 106, 119, 109, 117, 111],
      "pivot_types": ["high", "low", "high", "low", "high", "low"],
      "candidate_available_index": 48,
      "confirmation_index": 49,
      "breakout_direction": "bullish",
      "expected_direction": "bullish",
      "subtype": null,
      "upper_boundary": 114.95833333333333,
      "lower_boundary": 111.79166666666667,
      "metrics": {
        "upper_slope": -0.20833333333333334,
        "lower_slope": 0.20833333333333334,
        "upper_relative_slope": -0.0018274853801169592,
        "lower_relative_slope": 0.0018274853801169592,
        "upper_intercept": 125.16666666666666,
        "lower_intercept": 101.58333333333334,
        "width_start": 16.916666666666657,
        "width_end": 4.416666666666657,
        "convergence": 0.7389162561576359,
        "parallel_gap": 0.0036549707602339184,
        "impulse": 0.24489795918367346,
        "impulse_direction": "bullish",
        "formation_bars": 30
      }
    }
  ],
  "pivots": [
    {
      "kind": "high",
      "index": 16,
      "confirmation_index": 18,
      "price": 122
    },
    {
      "kind": "low",
      "index": 22,
      "confirmation_index": 24,
      "price": 106
    },
    {
      "kind": "high",
      "index": 28,
      "confirmation_index": 30,
      "price": 119
    }
  ],
  "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 traceSymmetricalTriangle. 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#

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

Calculation flow#

Reasoning Flow — Symmetrical 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#