Ascending Triangle
Install and import#
npm install fintech-algorithmsimport { detectAscendingTriangle } from "fintech-algorithms/geometric-chart-patterns/continuation-structures/ascending-triangle";Signature#
detectAscendingTriangle(close, parameters)Scans a close series for an ascending triangle - a flat ceiling sitting over a rising 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 ceiling's relative slope is within flat_tolerance of zero, the floor's is at least 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 bullish break confirms.
Parameters#
| Name | Type | Notes |
|---|---|---|
close | number[] | 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 |
parameters | Partial<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
searchingrather 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#
[90, 91.75, 93.5, 95.25, 97, 98.75]Showing 6 of 96 elements.
Call#
detectAscendingTriangle(close, parameters)Returns#
object with 7 fields: pattern, state, reason, event, events, pivots, parameters
{
"pattern": "ascending_triangle",
"state": "confirmed",
"reason": "breakout-confirmed",
"event": {
"pattern": "ascending_triangle",
"state": "confirmed",
"reason": "breakout-confirmed",
"pivot_indices": [16, 22, 28, 34, 40, 46],
"pivot_prices": [118, 108, 118.1, 112, 118, 115],
"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": 118.03333333333335,
"lower_boundary": 116.04166666666667,
"metrics": {
"upper_slope": 0,
"lower_slope": 0.2916666666666667,
"upper_relative_slope": 0,
"lower_relative_slope": 0.0025395443331882165,
"upper_intercept": 118.03333333333335,
"lower_intercept": 101.75,
"width_start": 11.616666666666674,
"width_end": 2.8666666666666742,
"convergence": 0.7532281205164988,
"parallel_gap": 0.0025395443331882165,
"impulse": 0.21649484536082475,
"impulse_direction": "bullish",
"formation_bars": 30
}
},
"events": [
{
"pattern": "ascending_triangle",
"state": "confirmed",
"reason": "breakout-confirmed",
"pivot_indices": [16, 22, 28, 34, 40, 46],
"pivot_prices": [118, 108, 118.1, 112, 118, 115],
"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": 118.03333333333335,
"lower_boundary": 116.04166666666667,
"metrics": {
"upper_slope": 0,
"lower_slope": 0.2916666666666667,
"upper_relative_slope": 0,
"lower_relative_slope": 0.0025395443331882165,
"upper_intercept": 118.03333333333335,
"lower_intercept": 101.75,
"width_start": 11.616666666666674,
"width_end": 2.8666666666666742,
"convergence": 0.7532281205164988,
"parallel_gap": 0.0025395443331882165,
"impulse": 0.21649484536082475,
"impulse_direction": "bullish",
"formation_bars": 30
}
}
],
"pivots": [
{
"kind": "high",
"index": 16,
"confirmation_index": 18,
"price": 118
},
{
"kind": "low",
"index": 22,
"confirmation_index": 24,
"price": 108
},
{
"kind": "high",
"index": 28,
"confirmation_index": 30,
"price": 118.1
}
],
"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
traceAscendingTriangle. 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#
Calculation flow#
Reasoning Flow — Ascending 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.
References#
- Foundations of Technical Analysis — Andrew W. Lo, Harry Mamaysky, and Jiang Wang
- SciPy `find_peaks` — SciPy project
- The Probability of Backtest Overfitting — David H. Bailey, Jonathan M. Borwein, Marcos López de Prado, and Qiji Jim Zhu
- Applicability decision