Patterns and chart types
All functions · nseries package
Sections: Chart Patterns · Candlesticks · Heikin-Ashi · Point and Figure · Kagi · DeMark Indicators · Elliott Wave
Chart Patterns
Chart patterns that do not repaint
Every method here is causal: the output on bar t uses bars 0 to t only, so nothing repaints, and where a published form looks ahead or repaints, the method departs from it and its section says where. The patterns are found in three ways.
- On confirmed swings. The double bottoms and double tops, the triple tops and triple bottoms, the head-and-shoulders top and bottom and the rounding bottom and top are built on the causal swing state of the “Swing State” category (
SwingLowEvent,SwingHighEventand their value and age views). A swing is known only once its whole pivot window is, n bars after its pivot bar, and such a pattern is reported only once its last pivot is confirmed, so nothing repaints: a breakout in the n bars before that confirmation is not read. - By a window scan. The bull and bear flags are Katsanos’s published rules read over fixed and data-dependent windows that end at the current bar, with no swing state: every quantity is known on the setup bar, and the levels are reported on setup bars only.
- By a per-bar grid. The cup with or without handle is Siligardos’s five-by-five grid on the logarithms of the prices, anchored on the latest highest high of the last n bars and evaluated on every bar as if it were the last, with no swing state: it reports his identification bar, not a breakout.
The published tests of these patterns mostly used pivots whose confirmation they did not wait for, or patterns picked or checked by hand, and measured outcomes to a hindsight high or low; the mechanical tests that the sections cite carry the caveats stated there. None is a test of the methods as built here, and the methods make no claim of profitability. Each section below gives its pattern’s rules, views, evidence and what is not built, with the recipes as go blocks under “Recipes” at the end of the section, except the head and shoulders’, which are under “Compositions” after the function list.
The views of one pattern take the same arguments, and each runs the pattern’s whole machine. The views of the patterns on swings and of the flags use these words, each defined once here; the rounding patterns’ width and extreme views and the cup’s length, rim and low views are defined in their sections:
<Pattern>reports 1 on the signal bar, the bar whose close first breaks the pattern’s neckline, and 0 otherwise. For the flags it is the bar whose high (for a bear flag, whose low) reaches the neckline of the setup bar before it: the fill of a stop order.<Pattern>Setupreports 1 on the arming bar, the first bar on which the whole pattern is known (for a pattern on swings, the bar that confirms its last pivot), and 0 otherwise. For the flags it is every bar on which the rules hold, the bar that places the next bar’s stop order.- A pattern is under test from its arming bar through the bar whose close signals or voids it. It stops being under test earlier when it expires, or when a newer pattern of the same kind arms and replaces it.
<Pattern>Necklineholds the level a close must break while the pattern is under test, and NaN on every other bar.<Pattern>Stopholds the level whose break voids the pattern while it is under test, and NaN on every other bar.<Pattern>Targetholds the pattern’s measured target, for a pattern whose target is not a recipe over its other views.
The flags are not under test: they report their levels on setup bars only, NaN on every other bar, because their stop order lives one bar and is placed again on each setup bar, and nothing voids a flag. Their <Pattern>Stop is the flag’s extreme low (for a bear flag, its extreme high), the basis of Katsanos’s protective stop, not a level whose break voids the pattern.
A pattern ships <Pattern>Stop only where its source defines one. The double bottoms and double tops have a Stop (the lower bottom or the higher peak, whose break voids the pattern) and no Target: their levels stay constant while a pattern is under test, so the measure rule is a recipe over them. The head-and-shoulders top and bottom have a Target and no Stop: their neckline slopes, so the head height that the measure rule needs is held by no other view, and neither of their sources defines a stop. The triple tops and triple bottoms, like the head and shoulders, have a Target and no Stop: their source defines no stop, and the outer extreme the height needs is held by no other view. The rounding bottom and top have neither: they are decided on the bar that regains the swing high’s level, so no pattern is under test, and their source’s stops are conditions on an open trade. The cup has none of the four (Setup, Neckline, Stop and Target): Cup marks Siligardos’s identification bar, not a neckline break, so no pattern is under test; the level a breakout must clear is CupRim, and the initial stop of his 2017 test, 0.7 of the cup’s percentage height below the identification bar’s high, is a recipe over that high and CupLow. The levels are NaN rather than 0 where no pattern is under test (for the flags, on every bar that is not a setup bar; for the rounding patterns, off the event bar; for the cup, where no cup holds), because when a pattern arms depends on the data; the events, the widths and the cup’s length read +0 there.
PointAndFigureSignal (category “Point and Figure”) is a different “double top”: Vomund’s breakout of a column of Xs above the previous column of Xs on a point-and-figure chart, with no swings, band or height.
Double bottoms and double tops
DoubleBottom, DoubleBottomSetup, DoubleBottomNeckline and DoubleBottomStop pair each confirmed swing low with the previous one, and DoubleTop and its three views pair swing highs. They follow Thomas Bulkowski’s computer model of 1998, with the time limit of Tsinaslanidis and Zapranis (2016): the two bottoms lie within a band of each other (bandMinFraction to bandMaxFraction of the lower bottom, signed and inclusive), no low between them is below the lower bottom, and the highest high strictly between them, the neckline, is at least minHeightFraction of the lower bottom above it. The pattern arms when the second bottom is confirmed, signals on the first close strictly above the neckline on or after the arming bar, is void on a close strictly below the lower bottom, and expires on bar b2 + (b2 - b1), where b1 and b2 are the bars of the two bottoms; a newer qualifying pair replaces it. The double top mirrors this with its own ratios, over the higher peak. DoubleBottom’s and DoubleTop’s docs carry the full rules. A third bottom that pairs with the second under these rules makes a double bottom of the last two bottoms; TripleBottom reads all three (the next section), and on a bar where both arm the double’s neckline is at or below the triple’s.
The measure rule. Bulkowski, following Edwards and Magee, adds the pattern’s height (the neckline less the lower bottom) to the neckline for a double bottom, and subtracts its height (the higher peak less the neckline) from the neckline for a double top. The one expression neck.Add(neck.Sub(stop)).ValueWhen(sig) does both, since neck.Sub(stop) is negative for a top. Bulkowski (2016) found that the full height was reached by 68% of more than 2,700 double bottoms in bull markets, and shrinks the height by such a rate for a closer target; that rate is a statistic, not a parameter, and a multiple k of the height is the recipe neck.Add(neck.Sub(stop).Mul(k)).ValueWhen(sig).
Gates. Bulkowski’s two weeks to six months between the bottoms, his falling 60-bar regression slope into a double bottom (rising, over 40 bars, into a double top), the yearly low of the ugly double bottom and a bull-market filter are not parameters but gates: every property of the two bottoms can be read on the arming bar, and setup.BarsSince() finds the arming bar again on the signal bar. A gate g read on the arming bar filters the signals as sig.Mul(g.ShiftVariable(setup.BarsSince()).GT(0)). The GT(0) is needed: before the first setup BarsSince is NaN, so the shifted gate is NaN there, and 0 times NaN is NaN. A gate filters signals only: a pair that fails it still arms and replaces a pending pattern, which a rule built into the machine would not do.
The ugly double bottom (Bulkowski, 2016) is a second bottom 5% to 20% above a first bottom that sets the yearly low, on minor lows five bars either side: c.DoubleBottom(h, l, 5, 0.05, 0.20, 0) gated by the yearly low at the first bottom. Bulkowski would trade the 10% to 20% band with a target of twice the height; his example’s bottoms of 57.55 and 64.91 under a top of 67.21 give a target of 86.53.
Evidence. Bulkowski’s 1998 patterns were found by computer and then checked by hand, and his outcome, the “ultimate high” (defined in his later tests as the highest high before a 20% drop), is known only in hindsight; his 2019 tests used 11-bar minor lows confirmed before entry, with further checks he did not publish. Kosinski (2018) tested mechanically, without commissions or other costs, with a pivot rule he did not publish. The long-side rule of Tsinaslanidis and Zapranis stopped out of 98.5% to 98.7% of their double bottoms on real prices and 99.8% to 99.9% on simulated random paths, which says more about the rule than about the pattern. None of these tests is a test of the methods as built here.
Not built. Triple patterns as a mode of these methods (the triple tops and triple bottoms have their own section, next), volume and candle filters, a target view, trade statistics, and a variant in which a pair that fails a gate leaves the pending pattern alone.
Recipes
The views and the measure rule (a, b and m are the three fractions, for example Bulkowski’s -0.04, 0.04 and 0.10):
sig := c.DoubleBottom(h, l, n, a, b, m)
setup := c.DoubleBottomSetup(h, l, n, a, b, m)
neck := c.DoubleBottomNeckline(h, l, n, a, b, m)
stop := c.DoubleBottomStop(h, l, n, a, b, m)
height := neck.Sub(stop) // C - Lm while a pattern is under test
target := neck.Add(neck.Sub(stop)).ValueWhen(sig) // the measure rule, latched on the signal bar
age := setup.BarsSince() // bars since the arming bar
pending := neck.EQ(neck) // 1 while a pattern is under test, for a buy-stop above neck
second := l.SwingLowValue(n).ValueWhen(setup) // the second bottom: Bulkowski's 2019 stop is a close below it
Gates read on the arming bar, where w, the age of the previous swing low, is t - b1:
w := l.SwingLowAge(n, 1) // t - b1 on the arming bar
width := w.Sub(float64(n)) // b2 - b1
gate := width.GTE(10).Mul(width.LTE(126)) // two weeks to six months apart
falling := c.Slope(60).ShiftVariable(w).LT(0) // a falling 60-bar slope at b1
gated := sig.Mul(gate.Mul(falling).ShiftVariable(setup.BarsSince()).GT(0)) // both gates, read on the arming bar
Slope reads 0 until its window is full (from bar 59 for 60 bars, bar 39 for 40), so a slope gate fails every pattern whose first bottom or peak is earlier than that.
The ugly double bottom, its yearly-low gate and its target at twice the height:
ugly := c.DoubleBottom(h, l, 5, 0.05, 0.20, 0)
uglySetup := c.DoubleBottomSetup(h, l, 5, 0.05, 0.20, 0)
uglyNeck := c.DoubleBottomNeckline(h, l, 5, 0.05, 0.20, 0)
uglyStop := c.DoubleBottomStop(h, l, 5, 0.05, 0.20, 0)
yearly := l.Lowest(252).ShiftVariable(l.SwingLowAge(5, 1)).EQ(l.SwingLowValue(5, 1)) // the yearly low at b1
buy := ugly.Mul(yearly.ShiftVariable(uglySetup.BarsSince()).GT(0))
uglyTarget := uglyNeck.Add(uglyNeck.Sub(uglyStop).Mul(2)).ValueWhen(buy)A bull-market filter is read on the signal bar itself, without the shift. Kosinski traded only when the S&P 500 was bullish and did not say how he judged it; one such filter, with spy the index’s closes right-aligned with the stock:
bull := spy.Average(100).GT(spy.Average(400)) // the index's 100-bar average above its 400-bar average
bullSig := sig.Mul(bull)The double top with Bulkowski’s 1998 top parameters, its measure rule and a rising trend into the first peak:
top := c.DoubleTop(h, l, n, -0.02, 0.02, 0.10)
topSetup := c.DoubleTopSetup(h, l, n, -0.02, 0.02, 0.10)
topNeck := c.DoubleTopNeckline(h, l, n, -0.02, 0.02, 0.10)
topStop := c.DoubleTopStop(h, l, n, -0.02, 0.02, 0.10)
topTarget := topNeck.Add(topNeck.Sub(topStop)).ValueWhen(top) // V - (Pm - V)
rising := c.Slope(40).ShiftVariable(h.SwingHighAge(n, 1)).GT(0) // a rising 40-bar slope at the first peak
topGated := top.Mul(rising.ShiftVariable(topSetup.BarsSince()).GT(0))Triple tops and triple bottoms
TripleTop, TripleTopSetup, TripleTopNeckline and TripleTopTarget read the three latest consecutive confirmed swing highs, and TripleBottom and its three views the three latest swing lows. They follow the rules of Tsinaslanidis and Zapranis (2016), chapter 5, the only published mechanical rules for the pattern, on confirmed swings. For a top with peaks P1, P2 and P3 on bars b1 < b2 < b3, and T1 and T2 the lowest lows strictly between b1 and b2 and between b2 and b3 (they need not be confirmed swing lows):
- the band: (P3 - P1)/max(P1, P3) lies between
bandMinFractionandbandMaxFraction, signed and inclusive; - no high strictly between b1 and b3, P2 included, is above max(P1, P3): a second peak above both outer peaks is a head and shoulders;
- the troughs: T1 > 0, T2 >= T1, and (T2 - T1)/T1 <=
maxSpreadFraction; - the symmetry: 2*(b2-b1) < 5*(b3-b2) and 2*(b3-b2) < 5*(b2-b1), their ratio of 2.5 in whole bars.
A top arms on bar b3 + n, the bar that confirms its third peak, and the newest qualifying triple replaces a pending one. It signals on the first close strictly below T1, a horizontal support, on or after the arming bar and before bar b3 + (b3 - b1); nothing voids it. The bottom mirrors this on the swing lows with the peaks P1 and P2 between them, the band over the lower outer bottom, and the spread (P1 - P2)/P2 over the second peak, as they print it. The ratios are not symmetric under negation, so a triple bottom is not a triple top of negated prices. Their 4% band and spread are c.TripleTop(h, l, n, -0.04, 0.04, 0.04); their own form, on the closes with the pivot half-window w = 15, is c.TripleTop(c, c, 15, -0.04, 0.04, 0.04). TripleTop’s and TripleBottom’s docs carry the full rules.
Departures from the published rules. The band divides by the higher outer peak, where they divide by the lower (the doubles’ rule); the between rule reads every high of the outer span, where they test P2 alone, so a spike above both outer peaks that is not itself a swing fails here; the troughs are the lowest lows of the spans, where they read the rolling-window troughs (the two coincide when that trough is the span’s minimum); and the break is read from the arming bar, where they search from b3 + 1, before the third peak can be confirmed. Their trend condition, P1 > P0 and T1 > T0, is a gate below, not a rule.
Relation to the double patterns and the head and shoulders. Both families arm only on bars on which SwingHighEvent confirms a swing high (SwingLowEvent a swing low, for the bottoms), and the doubles read a third peak whose pair qualifies as a double top on the last two peaks: a triple top’s T1 and T2 are the necklines of the double tops of its pairs (P1, P2) and (P2, P3). DoubleTop reads the last two peaks of the same swings with its own band and decline, so neither method’s setups contain the other’s: the triple bands P1 against P3 and needs no decline, the double bands P2 against P3 and needs one. On a bar where both arm, the triple’s neckline T1 is at or below the double’s T2, and if both stay pending the double signals no later than the triple. A head and shoulders needs a second peak above both outer peaks, which the triple’s between rule excludes, so TripleTop and HeadShouldersTop do not arm on the same three peaks. The bottoms mirror all of this: where both arm, the triple bottom’s neckline P1 is at or above the double bottom’s P2.
Targets. TripleTopTarget is the target of their Table 5.3, T1 - H with the height H = max(P1, P3) - T1, each subtraction rounded on its own; TripleBottomTarget is P1 + H with H = P1 - min(T1, T3), which can overflow to +Inf. Their text measures from the break price B instead (B - H, B + H), which is c.Sub(neck).Add(target) on the signal bar. The levels are constant while a pattern is under test, so target.ValueWhen(sig) latches the target of each break.
Evidence. Tsinaslanidis and Zapranis found 19 triple tops in NASDAQ stocks and 74 in NYSE stocks from 1990 to 2010, on pivots found over the whole history and without costs; 21.05% and 24.32% reached the target within the time limit, against 15.94% and 14.27% on simulated geometric Brownian motion paths. Samples this small decide little, and their patterns are not the ones found here. Their 12 and 67 triple bottoms stopped out 91.67% and 97.01% of the time (100% and 99.9% on the simulated paths), the same defect as their double bottoms’ long-side rule, so nothing is claimed for the bottoms. The witnesses in the docs are synthetic: no source prints a bar series with a triple.
The point-and-figure triple top, a column of Xs rising above two earlier columns at one level, is a different object; PointAndFigureSignal (category “Point and Figure”) builds only the double top and double bottom of that chart.
Not built. A stop view: their source defines none (the exits of their trading rule, closes back through the neckline and a percentage loss, are conditions on an open trade), so the names TripleTopStop and TripleBottomStop, once reserved, are released, and a stop a later source defines would take that suffix; a sloping support through T1 and T2; their trend condition as a rule; a height, volume or candle filter; and trade statistics.
Recipes
The views, the latched target, their text’s target from the break and the height (a, b and p are the three fractions, for example their -0.04, 0.04 and 0.04):
sig := c.TripleTop(h, l, n, a, b, p)
setup := c.TripleTopSetup(h, l, n, a, b, p)
neck := c.TripleTopNeckline(h, l, n, a, b, p)
target := c.TripleTopTarget(h, l, n, a, b, p)
latched := target.ValueWhen(sig) // the target of each break
fromBreak := c.Sub(neck).Add(target) // B - H on the signal bar, to rounding
height := neck.Sub(target) // H, to rounding, while a pattern is under test
age := setup.BarsSince() // bars since the arming bar
Gates read on the arming bar, where a1 and a2 are the ages of the second and first peaks: their trend condition (eq. 5.15), a rising 40-bar slope at the first peak and a height of at least 10% of the neckline. A gate filters signals only: a triple that fails it still arms and replaces a pending one.
a1 := h.SwingHighAge(n, 1) // t - b2 on the arming bar
a2 := h.SwingHighAge(n, 2) // t - b1
k := a1.Sub(float64(n)) // b3 - b2
V := l.Shift(n+1).LowestVariable(k.Sub(1), 1, len(l)) // the lowest low between two swing highs
T1 := V.ShiftVariable(k) // the first trough
T0 := V.ShiftVariable(a2.Sub(float64(n))) // the trough before the first peak
uptrend := h.SwingHighValue(n, 2).GT(h.SwingHighValue(n, 3)).Mul(T1.GT(T0)) // P1 > P0 and T1 > T0
rising := c.Slope(40).ShiftVariable(a2).GT(0) // a rising 40-bar slope at b1
deep := neck.Sub(target).Div(neck).GTE(0.10) // H at least 10% of the neckline
gated := sig.Mul(uptrend.Mul(deep).ShiftVariable(setup.BarsSince()).GT(0)) // both gates, read on the arming bar
A void as Bulkowski’s double patterns have it, a close above the higher outer peak, is a recipe over these views: the signal of a pattern with no such close since its arming bar.
ext := h.SwingHighValue(n, 2).Maximum(h.SwingHighValue(n)).ValueWhen(setup) // max(P1, P3) of the latest triple
clean := sig.Mul(c.GT(ext).SumSince(setup).EQ(0)) // no close above it since arming
The triple bottom, its target and its void:
bsig := c.TripleBottom(h, l, n, a, b, p)
bsetup := c.TripleBottomSetup(h, l, n, a, b, p)
bneck := c.TripleBottomNeckline(h, l, n, a, b, p)
btarget := c.TripleBottomTarget(h, l, n, a, b, p)
blatched := btarget.ValueWhen(bsig) // P1 + H, latched on the signal bar
bext := l.SwingLowValue(n, 2).Minimum(l.SwingLowValue(n)).ValueWhen(bsetup) // min(T1, T3) of the latest triple
bclean := bsig.Mul(c.LT(bext).SumSince(bsetup).EQ(0)) // no close below it since arming
Head and shoulders
Six views of one machine, three for each orientation; a head-and-shoulders pattern is under test while it is armed:
| View | Value on each bar |
|---|---|
HeadShouldersTop | 1 on the bar whose close first breaks below an armed top’s neckline, +0 otherwise |
HeadShouldersTopNeckline | the neckline N(t) while a top is armed, NaN otherwise |
HeadShouldersTopTarget | N(t) - H, the neckline less the head height, while a top is armed, NaN otherwise |
HeadShouldersBottom | 1 on the bar whose close first breaks above an armed bottom’s neckline, +0 otherwise |
HeadShouldersBottomNeckline | the neckline N(t) while a bottom is armed, NaN otherwise |
HeadShouldersBottomTarget | N(t) + H, the neckline plus the head height, while a bottom is armed, NaN otherwise |
Each is called as c.HeadShouldersTop(h, l, n), with the closes c as the receiver, the series h and l whose confirmed swings form the pattern (usually the highs and the lows) and the pivot half-window n. The swing highs of h and the swing lows of l are merged by pivot bar, the pattern’s own kind first when a bar holds both (the high for a top, the low for a bottom). On each bar that confirms a swing high, the last five merged swings are read as P1, T1, P2, T2, P3 at bars tP1 < tT1 < tP2 < tT2 < tP3, and they form a top when their kinds alternate, their bars strictly increase, their values are finite and the conditions of Osler and Chang (1995) and Lucke (2003), as Tsinaslanidis and Zapranis (2016) print and code them, hold:
- the head: P2 > P1 and P2 > P3;
- the balance: P1 >= 0.5*(P3+T2) and P3 >= 0.5*(P1+T1), inclusive, each sum rounded before it is halved;
- the symmetry: 2*(tP2-tP1) < 5*(tP3-tP2) and 2*(tP3-tP2) < 5*(tP2-tP1), their ratio of 2.5 in whole bars.
The trend before the pattern is not tested, as in their code. Three peaks whose middle one is not above both others can make a triple top instead (TripleTop, the section above). A top arms on bar tP3 + n, the bar that confirms its right shoulder, and the newest qualifying pattern replaces any that is still armed. The neckline runs through the two troughs, N(t) = T1 + b*(t-tT1) with b = (T2-T1)/(tT2-tT1), and the head height is H = P2 - N(tP2). The top reports the first armed bar whose close is strictly below N(t), and is disarmed then, or without an event on its deadline 2*tP3 - tP1. The bottom is the mirror image on the swing lows (T1, P1, T2, P2, T3, the neckline through the two peaks, H = N(tT2) - T2): c.HeadShouldersBottom(h, l, n) equals c.Mul(-1).HeadShouldersTop(l.Mul(-1), h.Mul(-1), n) bit for bit, and its levels are the top’s negated, with a zero level written as +0. The close-only form c.HeadShouldersTop(c, c, w) runs the detector on the rolling-window extrema of Tsinaslanidis and Zapranis, with their w as the half-window (a window of 2w + 1 bars). HeadShouldersTop and HeadShouldersBottom carry the full rules.
Look-ahead in the published tests. Tsinaslanidis and Zapranis find the extrema over the whole history and search the break from tP3 + 1, before the right shoulder can be confirmed. Here the break is a level test from the arming bar tP3 + n: a close below the neckline before that bar is not seen, and a close still below it on that bar reports the event there.
Targets. The target views measure from the neckline on each armed bar. Table 5.3 of Tsinaslanidis and Zapranis measures from the close B of the breaking bar instead, B - H for a top and B + H for a bottom, which is c.Sub(neckline).Add(target) on the event bar; their published code measures from the neckline at an interpolated crossing between the last two closes.
Evidence. Mixed. Osler and Chang (1995) set out the criteria on daily dollar exchange rates from 1973 to 1994, and Chang and Osler (1999) found the rule profitable in the mark and the yen but not in four other currencies, and dominated by simpler moving-average rules; Lucke (2003) found returns on five currencies that were not significantly positive; Osler (1998) found it unprofitable in 100 US stocks. Tsinaslanidis and Zapranis report their detector finding a top in about 22% and a bottom in about 23% of its runs on simulated geometric Brownian motion paths. No trading edge is demonstrated.
No stop is defined by the sources; the usual stop above the right shoulder or the head needs P3 or P2, which these views do not expose. For a bottom, the usual stop below the right shoulder or the head needs T3 or T2. The trading rule of Tsinaslanidis and Zapranis closes a position at a percentage loss, a parameter of the trade rather than a level the pattern sets.
Not built. Siligardos’s algorithmic head and shoulders (2013), a window search on the closes with about twenty constants (parked on demand; the name HeadShouldersSiligardos is reserved); a stop view; balance and symmetry constants as parameters; swings found by a threshold rather than a bar count; invalidation rules; the trend condition and its reversal and continuation classes. The recipes (the latched levels, the head height, Table 5.3’s target, the timed exit and the close-only form) are under “Compositions” below.
Bull and bear flags
BullFlag, BullFlagSetup, BullFlagNeckline, BullFlagStop, BullFlagPoleTop and BullFlagPoleBottom are the entry rules of Markos Katsanos’s intraday flag strategy (2014), translated literally, and the six BearFlag views are their exact mirror on negated prices. A bull flag is a steep rise, the pole, followed by a short and shallow drift lower, the flag; the entry is a buy stop for the next bar at the flag’s highest close. In brief, on bar t: the pole top is the latest highest close of the maxFlagBars bars ending two bars back, at least four bars before t; the flag after it drifts lower (least-squares slopes of its closes below 0) and is shallow (the top less its lowest close is below maxFlagDepthATR times the ATR); the pole bottom, the latest lowest close of the pole window, lies at or before the top and the pole is taller than minPoleHeightATR times the ATR; the pole bottom is above the lowest low of the last 70 bars; and the ATR has risen by more than 5 percent since the pole bottom. The ATR is c.ATR(h, l, c, 40), a simple 40-bar average of the true range, as Katsanos’s AvgTrueRange(40) is, not Wilder’s smoothing. BullFlag’s doc carries the rules in full, with their windows, ties and strictness, and BearFlag’s the mirrored rules.
The flags differ from the patterns on confirmed swings in four ways:
- A window scan, with no swing state. Every rule reads fixed and data-dependent windows ending at the current bar, so every quantity is known on the setup bar and nothing repaints, as published.
- Levels on setup bars only. Katsanos’s order lives one bar and is placed again on each setup bar, so consecutive setup bars are common; the four level views are NaN on every other bar, and
neck.ValueWhen(setup)carries a level forward. - The event is the next bar’s stop fill on the high.
BullFlagis 1 on the bar whose high reaches the previous setup bar’s neckline (BearFlag: whose low reaches it), inclusive, as a stop order fills: the published rule, and an exception to the package’s strict thresholds. A close beyond the level is the recipec.GT(neck.Shift(1))from bar 1 on (Shiftpads bar 0 with 0, which the recipes below mask withsetup.Shift(1)). - A stop and two pole views instead of a target.
BullFlagStopis the flag’s lowest low, behind Katsanos’s protective stop three ATR below it (his code defines it, so the flags ship a Stop), not a level that voids the pattern. Both published targets need the pole’s top and bottom, soBullFlagPoleTopandBullFlagPoleBottomtake the place of a target view.
Settings. Katsanos’s settings are c.BullFlag(h, l, 15, 23, 2.5, 5.5) for Nasdaq stocks on 15-minute bars, a flag depth of 2.5 ATR and a pole of 3.5 ATR for index futures, and 2 and 4 for forex, with the target’s k at 1.2, 0.9 and 0.6 (his text puts k from 0.7 for YM and ES to 1.1 for TF and NQ); the stock entry values were not fitted, while the index-futures and forex values were tuned on those markets. 15 and 23 bars are the 90th percentiles of flag and pole duration in his intraday sample, and 5.5 sits just under the 10th percentile of pole height, 5.6 ATR. The 40-bar ATR, the 70 bars and the 5 percent rise are fixed, as printed.
Targets and stops. The measured move of Edwards and Magee adds the pole, top less bottom, to the neckline; Katsanos’s target scales the neckline, which stands in for his entry price, by one plus k times the pole over the pole bottom (his code adds 0.0001 to the pole bottom, which the recipe drops). Both are read on a setup bar, for the order placed there. His protective stop is three ATR below the flag low and is placed, for the next bar, only once the position is more than five bars old (his BSE > BSEMIN, with BSEMIN 5); the delay, the flat-position test, the 50-bar wait after an exit and his other exits are trade state and are not built: a position built from these entries would feed back into them.
Evidence. Katsanos’s in-sample test on five Nasdaq stocks over the first half of 2014, on 15-minute bars, made 54 trades, 65 percent profitable on average across the five stocks, with an average profit factor of 5.19 and no costs stated: the entry thresholds came from flag statistics of the same half-year, the exits were fitted on one stock, TSLA, and the Nasdaq 100 rose over the period. His earlier test of bear flags (2006) on 2,000 stocks over ten years was profitable only in 1998 and from March 2001 to March 2003, and he withheld the system, so the bear views are a mirror of the bull rules, not a published system. On one driftless random walk of 20,000 15-minute-like bars the stock settings give 49 distinct setup episodes (setups more than 50 bars after the previous one), 2.45 per 1,000 bars (1.6 to 2.6 over eight such walks), against about 3.35 trades per 1,000 bars in his test (54 trades on five stocks over the half-year’s 124 sessions of 26 15-minute bars, 16,120 bars): noise alone sets up flags at a comparable rate. No profitability is claimed.
Not built. Katsanos’s earlier flag system (2005), whose zigzag anchor looks ahead; the flags, pennants and wedges of Tsinaslanidis and Zapranis on pivots; Bulkowski’s high and tight flag; an uptrend test stronger than the 70-bar condition; volume rules; and the exits, the flat-position test and the 50-bar wait, which are trade state. The names Pennant, Wedge and HighTightFlag are reserved.
Recipes
The views, the targets and the stops with the stock settings (c, h and l are the closes, highs and lows):
sig := c.BullFlag(h, l, 15, 23, 2.5, 5.5)
setup := c.BullFlagSetup(h, l, 15, 23, 2.5, 5.5)
neck := c.BullFlagNeckline(h, l, 15, 23, 2.5, 5.5)
stop := c.BullFlagStop(h, l, 15, 23, 2.5, 5.5)
top := c.BullFlagPoleTop(h, l, 15, 23, 2.5, 5.5)
bottom := c.BullFlagPoleBottom(h, l, 15, 23, 2.5, 5.5)
atr := c.ATR(h, l, c, 40) // Katsanos's AvgTrueRange(40), a simple average
move := neck.Add(top.Sub(bottom)) // Edwards and Magee's measured move, on setup bars
target := neck.Mul(top.Sub(bottom).Div(bottom).Mul(1.2).Add(1)) // Katsanos's target with k = 1.2, on setup bars
held := target.Shift(1).ValueWhen(sig) // the target of the order that filled, from its fill
protect := stop.Sub(atr.Mul(3)) // his protective stop, three ATR below the flag low
poleATR := top.Sub(bottom).Div(atr) // the pole in ATR, the unit of his statistics
last := neck.ValueWhen(setup) // the latest setup's level, carried forward
closeBreak := c.GT(neck.Shift(1)).Mul(setup.Shift(1)) // a close above the level instead of the high
On the worked shape of BullFlag’s Example (a pole from 100 to 124, a flag from 123 down to 121 and back to 121.5, every high and low a point from its close, an ATR of 2.45 on the setup bars) the first setup bar, bar 89, has the neckline 122, the measured move 146, Katsanos’s target 157.136 and the pole about 9.8 ATR.
The bear flag, its measured move down and its stops:
bearSig := c.BearFlag(h, l, 15, 23, 2.5, 5.5)
bearSetup := c.BearFlagSetup(h, l, 15, 23, 2.5, 5.5)
bearNeck := c.BearFlagNeckline(h, l, 15, 23, 2.5, 5.5)
bearStop := c.BearFlagStop(h, l, 15, 23, 2.5, 5.5)
peak := c.BearFlagPoleTop(h, l, 15, 23, 2.5, 5.5)
trough := c.BearFlagPoleBottom(h, l, 15, 23, 2.5, 5.5)
bearMove := bearNeck.Sub(peak.Sub(trough)) // the measured move down, on setup bars
bearProtect := bearStop.Add(c.ATR(h, l, c, 40).Mul(3)) // the mirrored stop, three ATR above the flag high
bearHeld := bearMove.Shift(1).ValueWhen(bearSig) // the move of the order that filled, from its fill
bearCloseBreak := c.LT(bearNeck.Shift(1)).Mul(bearSetup.Shift(1)) // a close below the level instead of the low
The rounding bottom and top
RoundingBottom, RoundingBottomWidth and RoundingBottomLow find the rounding bottom, or saucer, of Tsinaslanidis and Zapranis (2016), Technical Analysis for Algorithmic Pattern Recognition, chapter 6, as their Matlab function RBottoms computes it, evaluated bar by bar on the closes (the receiver). A strict swing high P1 of the closes, confirmed n bars later where c.SwingHighEvent(n) reads 1, sets a level, and the first later bar P2 whose close is at least that level completes a candidate of width W = P2 - P1 + 1. The closes from P1 to P2 are scaled so that the depth is half the width, a circle is drawn through the scaled ends and the scaled minimum, and the candidate is a rounding bottom when W > minWidth and the share of its closes inside a band about the circle’s lower half, of boundFraction times the radius on each side, edges included, is at least minFitFraction. Every close between P1 and P2 is below the level, so P2 comes after the swing high is confirmed: the pattern is decided on the bar that regains the level, from confirmed data only, and nothing repaints. RoundingTop, RoundingTopWidth and RoundingTopHigh are the same detector on the negated closes, bit for bit: a swing low, the first later close at most its level, and the highest close. The book prints only the bottom’s code and calls the top a mirror process.
The cup with or without handle (its section below) is the other rounded base here, found differently: Cup anchors on the latest highest high of the last n bars and the last higher high before it, reads the highs, lows and closes against a grid on their logarithms, can be identified while the right side is still rising and is reported again on each bar that moves the rim bar, and its views give a length, a rim and a low. The rounding bottom anchors on a confirmed swing high of the closes, reads the closes alone against a band about a circle, is decided once, on the bar that regains the swing high’s level, and its views give a width and the lowest close. The cup has no mirror image here.
The event views read 1 on the bar that completes an accepted pattern, the width views W there, and the level views the lowest (for the top, the highest) close from P1 to P2 there, copied from the first bar at the extreme; elsewhere the events and widths read 0 and the levels NaN. Each swing high is evaluated once, on its own P2, and consumed there whether or not its pattern is accepted. One close can regain several swing highs at once, since the pending levels fall from the oldest to the newest; the bar then reports one event, for the widest accepted pattern, and a wider pattern that fails does not hide a narrower one that fits. A count of the accepted patterns or the best fit would need other views, and none is built. In the vocabulary above the event is the signal bar and the swing high’s level plays the neckline, reached at >= rather than broken (the methods follow the code); a rounding pattern has no Setup and no Neckline view, because it is decided on the bar that first reaches the level and is not under test on any bar.
Text and code. Where the book’s text and its code differ, the methods follow the code: P2’s close is at least the level (>=) where the text says the price “overcomes” it, and a width of exactly minWidth is rejected (W > minWidth) where the text asks for at least 15 days at a threshold of 15. The defaults of the book’s tables are c.RoundingBottom(10, 15, 0.3, 0.9); its IBM example used a bound of 0.2.
Departures. RBottoms scans a whole series once and lists every accepted pattern; the methods decide each pattern on its own P2 from bars 0 to P2 and report the widest one a bar. The band’s arcs are computed in the stable form Ky - sqrt(r^2 - dx^2) rather than as the root of the printed quadratic: the two are equal in exact arithmetic, and the quadratic’s form differs in the last bits on a few per cent of the arcs, so it can decide a close that lies on an edge the other way. The scaling is MathWorks’s documented formula for mapminmax, evaluated left to right (the book does not print the toolbox’s code). Matlab’s max and min skip NaN, so the published functions score a segment that holds one; here a NaN close suppresses every swing high whose window holds it and regains nothing, and a segment that holds a NaN, +Inf or -Inf is rejected, its swing high consumed all the same.
The bound. The band is loose at the tables’ bound of 0.3. A 40-bar V from 100 down to 62 and back, and a 40-bar flat-bottomed box with four-bar walls, both fit perfectly there (40 closes of 40 inside), as a parabola does. At the IBM example’s bound of 0.2 the V fits 0.5 and the box 0.85, so both fail at a fit of 0.9, while the parabola still fits 1. A pattern found at the defaults is therefore not necessarily round, and a bound of 0.2 separates the shapes better.
No Stop view. The authors define no level whose break voids the pattern: their stops are conditions on an open trade, at most tn consecutive closes back below the swing high’s level (tn = 2 in their tables) and a loss of tsl (4%) from the entry, and their time limit is the pattern’s width, W bars after the event. These belong to a trade (the “Trade State” section), so they are recipes below, and no RoundingBottomStop ships.
Evidence. The authors ran the patterns on the components of NYSE and NASDAQ indices and on simulated GBM paths, with the time limit and the stops above, and compared the frequencies of the three outcomes (target reached, time limit, stop), before costs: NYSE rounding bottoms did better than the simulations, while the rounding tops and the NASDAQ bottoms were mixed, so one cell of the four was clearly positive, and only that cell’s average return was positive. The methods make no claim of profitability.
Cost and slices. A pattern reads the bars from P1 - n to P2, and its width has no cap, as in RBottoms: each regained swing high wider than minWidth costs O(W) once, so the worst case is quadratic, a staircase decline with a swing high on every step, all regained by one bar, when every pattern is rejected. A slice that starts later can lose a wide pattern and report a narrower one on the same bar; no fixed amount of history makes a slice agree with the full run.
Not built. A view of the number of patterns completed on a bar or of the best fit, the book’s middle circle and its plots, the W-bar holding window of its first signal series, a stop method and a cap on the width (callers can slice).
Recipes
The views, the book’s depth (6.5) and its price target, the level of the tn stop and the time limit (b and f are the bound and the fit):
ev := c.RoundingBottom(n, minWidth, b, f)
w := c.RoundingBottomWidth(n, minWidth, b, f)
low := c.RoundingBottomLow(n, minWidth, b, f)
depth := c.Sub(low).Div(low) // (Pmax - Pmin)/Pmin on the event bar, where c is the close at P2
target := c.Add(c.Sub(low)) // Pmax + depth*Pmin in exact arithmetic
level := c.ShiftVariable(w.Sub(1)).ValueWhen(ev) // the close at P1, the level of the tn stop
limit := w.ValueWhen(ev) // the time limit in bars
lastLow := low.ValueWhen(ev) // the last pattern's low
On a sine bowl, the closes 95 to 99 and then 100 - 20 sin(pi i/40) for i = 0 to 40 with the last close 100.5, at n = 3 and the other defaults, the bar that regains 100 reports W = 41 and the low 80, so the depth is 0.25625 and the target 121. The depths and the loss test below divide by a close, and Div gives 0 where the divisor is 0, so these recipes assume positive closes.
The top’s depth and target, where the close at P2 is the lower end of the pattern:
top := c.RoundingTop(n, minWidth, b, f)
high := c.RoundingTopHigh(n, minWidth, b, f)
topDepth := high.Sub(c).Div(c) // (Pmax - Pmin)/Pmin, Pmin the close at P2
topTarget := c.Sub(high.Sub(c)) // Pmin - depth*Pmin
On the same bowl turned over, 200 less each close, the top’s event bar reports the high 120, the depth 20.5/99.5 and the target 79.
The authors’ trade, with the stop pattern of the “Trade State” section (close test, close fill): long on the event bar’s close for W bars, out earlier when the close reaches the target (at or above it; the book says both “reaches” and “exceeds”), when its loss from the entry reaches 4%, or on the second consecutive close below the swing high’s level (a NaN close is not below it, so it ends a run rather than extending it: the recipe counts the closes since the last one that is not below the level). The loss test is LTE(-0.04): the authors’ “maximum loss accepted” can be read as strict, and the inclusive edge holds in exact arithmetic only, so a close exactly 4% below the entry can fall either side after rounding (an entry of 100.5 and a close of 96.48 give a return of -0.039999999999999925, which does not cut the trade). A pattern completed while the trade is open restarts the time limit and replaces the level and the target, and the trade keeps its entry; a trade cut by a stop stays flat until the next entry of the base position.
pos := c.Position(ev, ev.BarsSince().GTE(limit)) // long from the event bar for W bars
entry := c.EntryValue(pos) // the close at P2 while the trade is open
loss := c.Div(entry).Sub(1).LTE(-0.04) // tsl: a 4% loss, inclusive up to rounding
breach := c.LT(level).EQ(0).BarsSince().GTE(2) // tn: two consecutive closes below level
reached := c.GTE(target.ValueWhen(ev)) // the price target
hit := loss.Add(breach).Add(reached).GT(0).Mul(pos.BarsSinceEntry().GT(0)) // any of the three, after the entry bar
cut := hit.SumSince(pos.Entries()).GT(0)
trade := pos.Mul(cut.Mul(-1).Add(1))c.TradeResult(trade) posts each trade’s result on its exit bar.
The cup with or without handle
Cup, CupLength, CupRim and CupLow are four views of one scan: the cup with or without handle of Giorgos Siligardos (2006), a five-by-five grid on the natural logarithms of the prices, anchored on the latest highest high and the last higher high before it, with boxes that must be empty of closes and boxes that must hold lows. The receiver holds the closes, and h and l are its highs and lows; n is the rim lookback, minCup the length a cup must exceed and maxCup the longest cup looked for, all in bars. The cup uses no swings, and its views do not follow the vocabulary of the patterns on swings above: Cup marks the bar on which the grid identifies a cup, not the break of a neckline, and the cup has no setup, neckline or stop view. Siligardos’s published MetaStock indicator evaluates the grid once, at the chart’s last bar, and freezes its anchors over the whole chart, so a historical chart repaints; the methods evaluate the grid on every bar t as if t were the last bar, from bars 0 to t only, so nothing repaints. Cup’s doc carries the full rules.
| View | Value on each bar |
|---|---|
Cup | 1 on the first bar of each run of cup bars and on each bar that moves the rim bar, +0 otherwise |
CupLength | the length B5 - B0 + 1, from the cup’s first bar B0 to its rim bar B5, else +0 |
CupRim | the high at the rim bar, the level a breakout must clear, else NaN |
CupLow | the lowest low from B0 to the current bar, handle included, else NaN |
Cup reports Siligardos’s identification bar, not a breakout. A high at or above the high at the rim bar makes its own bar the new rim bar and re-measures a longer cup with no handle, which may or may not be identified again. A breakout bar, a close above the rim, normally brings such a high, so on that bar the cup is measured afresh rather than confirmed. For the same reason Cup also reports 1 on each bar inside a run of cup bars on which the rim bar moves: a high at or above the rim that is still below the left rim moves the rim bar, which on a rising right side happens on each new high that keeps a cup. The rim bar also moves when it drops out of the last n bars, to the latest bar at the highest high of the new window, if that bar heads a cup. On the package tests’ random series (cup-shaped bowls, walks and coarse prices) rim moves outnumber run starts about three to one. The recipe length.GT(0).GT(length.GT(0).Shift(1)) below gives one event per run.
The rounding bottom of Tsinaslanidis and Zapranis (the section above) is the other rounded base here: it anchors on a confirmed swing high of the closes and is decided once, on the bar whose close regains that high, where the cup is anchored on its rim and identified bar by bar while its right side rises. On the sine bowl of RoundingBottom’s Example, with highs and lows 1% either side of the closes, c.Cup(h, l, 3, 15, len(c)) reports 1 on every bar from 33 to 44, as the rim bar moves with each new high, and c.RoundingBottom(3, 15, 0.3, 0.9) reports once, on bar 45.
Where the methods follow the listing. Siligardos’s text and his MetaStock listing differ in four places, and the methods follow the listing: a cup must be longer than minCup bars (cupperiod > 30), where the text asks for at least 30; the handle’s closes, not its lows, must stay above the level L2; the lowest low P0 runs from the cup’s first bar to the current bar, handle included, where the text stops at the rim bar; and the low boxes’ thresholds are written L1 - bh/3 and L2 - bh/3, as the listing prints them, where the text describes boxes two thirds of a grid box high (the same in real arithmetic, an ulp apart on some prices). The levels are taken on the logarithms; the rim bar, the left rim and the extremes of each box are found on the prices themselves.
Parameters. Siligardos used n = 20 and a length above 30 bars and set no maximum, so c.Cup(h, l, 20, 30, len(c)) reproduces his unbounded scan. maxCup bounds the cost, O(n + maxCup) a bar, and the reach: cups longer than maxCup are not found, and no default is suggested for it. Siligardos (2017) tested a slight modification of the algorithm that he did not print; the shortest cup it found lasted 20 bars, which minCup = 19 allows.
Evidence. Siligardos (2006) showed the pattern on chart examples only (Alvarion, Invitrogen, Goodyear and Scientific Atlanta), as a screening aid for large baskets of securities rather than a source of buy signals, and the algorithm ignores volume. Siligardos (2017) tested his modified algorithm on the daily charts of S&P 500 stocks from 1982 to 2014 and found an edge that vanished after 2000; the test left out commissions and spreads (his second part names them among the frictions a backtest cannot simulate precisely; exits were taken at the exit bar’s low, for slippage), had no benchmark, and did not say whether stocks that left the index were included, so survivorship is probable. The methods make no claim of profitability.
Not built. Siligardos’s 2011 semi-cup, which identifies a cup’s left half early (parked on demand; the name SemiCup is reserved); his 2017 modification and its rule that keeps only “distinct” cups, which needs hindsight across cups; volume rules; the display curve and the coloured bars, which back-date the pattern.
Recipes
The views, one event per run, the handle’s length, the height and the breakout (n, minCup and maxCup as in the calls):
cup := c.Cup(h, l, n, minCup, maxCup)
length := c.CupLength(h, l, n, minCup, maxCup)
rim := c.CupRim(h, l, n, minCup, maxCup)
low := c.CupLow(h, l, n, minCup, maxCup)
perRun := length.GT(0).GT(length.GT(0).Shift(1)) // 1 on the first bar of each run of cup bars
handle := h.IndexOfHighestS(n).Mul(-1).Add(float64(n - 1)) // bars from the rim bar to this bar
height := rim.Div(low).Sub(1).Mul(100) // the height in percent, his exploration column
breakout := c.GT(rim.Shift(1)).Mul(length.Shift(1).GT(0)) // a close above the rim of the previous bar's cup
IndexOfHighestS counts the window from its oldest bar, so the handle is (n-1) less it. The breakout’s mask is needed because Shift pads bar 0 with 0, where the bare comparison would read 1 for any positive close.
Siligardos’s 2017 entry at the high of the identification bar H, and his initial stop 0.7 times the cup’s percentage height below it, H(1 - 0.7(H - L)/H) = 0.3H + 0.7L with L the cup’s lowest low (158 for his H = 200 and L = 140):
hi := h.ValueWhen(cup) // H, the high of the latest identification bar
lo := low.ValueWhen(cup) // L, the cup's lowest low on that bar
stop := hi.Mul(0.3).Add(lo.Mul(0.7)) // the initial stop
H is the high of the identification bar itself, which is the rim only when that bar is the rim bar (with a handle it is lower), as in Siligardos’s definition. Since Cup also reports rim moves, hi and lo move to each new rim bar; perRun in place of cup keeps the first bar of the run. The stop then trailed the advance; trailing it is trade state, built from the since-entry views of the “Trade State” section (HighestSinceEntry and its kin).
BearFlag
func (s Series) BearFlag(h, l Series, maxFlagBars, maxPoleBars int, maxFlagDepthATR, minPoleHeightATR float64) Series
BearFlag reports 1 on the bar whose low reaches the sell-stop level of the bear flag set up on the previous bar, and 0 otherwise.
The bear views are the exact mirror of the bull views: every bear view equals the bull view of s.Mul(-1) with l.Mul(-1) as the highs and h.Mul(-1) as the lows, with the same parameters, the events unchanged, the levels negated back and the two pole views exchanged, bit for bit, signed zeros included. The receiver holds the closes, and h and l are its highs and lows. ATR is s.ATR(h, l, s, 40), which the negation leaves unchanged. In the mirrored rules of Katsanos (2014), bar t is a setup bar when every close, high and low of its read window is finite and: p is the latest bar holding the lowest close of bars t-maxFlagBars-1 to t-2, x1 = t - 2 - p is at least 2 and BOT is its close; the slope of the closes of bars t-x1 to t-1 is above 0 and HF - BOT < maxFlagDepthATRATR[t], with HF the highest close of bars t-x1 to t; q is the latest bar holding the highest close of bars t-maxPoleBars-x1 to t, PEAK is its close, t - q > x1 + 1 and PEAK - BOT > minPoleHeightATRATR[t]; the slope of the closes of bars t-x1+1 to t or of bars t-x1 to t-2 is above 0; PEAK is below the highest high of bars t-69 to t; and (ATR[t]/ATR[q] - 1)*100 > 5. Every comparison is strict as printed and every tie goes to the latest bar, as on the bull side, which BullFlag documents in full.
The order is a sell stop for the next bar at the lowest close of bars t-x1+1 to t, the level that BearFlagNeckline holds. BearFlag is 1 on bar t when bar t-1 is a setup bar and l[t] <= that level, an inclusive fill as published: from bar 1 on the view equals l.LTE(neck.Shift(1)) with neck the neckline view, and bar 0 is 0. A NaN low does not fill; a -Inf low does. The close-based alternative is the recipe s.LT(neck.Shift(1)) from bar 1 on (Shift pads bar 0 with 0, so a negative close there reads 1). The parameters, their units and the settings are those of BullFlag: s.BearFlag(h, l, 15, 23, 2.5, 5.5) is the stock setting. The mirrored protective stop is three ATR above the flag high of BearFlagStop, and the measured move subtracts the pole, the pole top less the pole bottom, from the neckline.
Katsanos published no bear rules with the 2014 strategy. His earlier test of bear flags (2006) on 2,000 stocks over ten years was profitable only in 1998 and from March 2001 to March 2003, and he withheld the system, so the bear views are a mirror of the bull rules, not a published system. No profitability is claimed.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. If maxFlagBars < 3, maxPoleBars < 1, or maxFlagDepthATR or minPoleHeightATR is NaN or infinite, the result is all +0. If maxFlagBars or maxPoleBars exceeds len(s), or the first valid bar lies beyond the data, nothing is set up and the result is all +0. Prices are not validated. Bar t can be a setup bar only if every close, high and low of bars t-W+1 to t is finite, with W = max(70, maxPoleBars + maxFlagBars + 40) and the window clipped at bar 0; a NaN or an infinity there gives no setup on bar t and changes nothing else. The breakout reads l[t] as it is.
Warm-up: no bar before max(69, maxPoleBars + maxFlagBars + 38) is a setup bar, so no breakout comes before the bar after it, and +0 is the no-event value.
Causal: the output on bar t uses bars 0 to t only, and every prefix of the inputs gives the first values of the result bit for bit; a slice of the history can move a comparison at its threshold, since the ATR is a compensated running sum. Cost: O(maxPoleBars + maxFlagBars + 70) a bar after the ATR. Pure: the inputs are not modified and the result is a new Series. See the manual’s “Chart Patterns” section for the recipes.
BearFlagNeckline
func (s Series) BearFlagNeckline(h, l Series, maxFlagBars, maxPoleBars int, maxFlagDepthATR, minPoleHeightATR float64) Series
BearFlagNeckline holds the lowest close of the flag on each setup bar, the level of the next bar’s sell stop, and NaN otherwise.
On each setup bar t of BearFlagSetup the value is the close of the latest bar holding the lowest close of bars t-x1+1 to t, with x1 the flag length, copied verbatim: a tie goes to the latest bar, and a -0 stays -0. Off setup bars the value is NaN rather than 0. The receiver holds the closes, and h and l are its highs and lows. The view is the negation of BullFlagNeckline of s.Mul(-1) with l.Mul(-1) as the highs and h.Mul(-1) as the lows. The level is the sell stop placed for the next bar: with neck this view and setup the setup view, BearFlag equals l.LTE(neck.Shift(1)) from bar 1 on, and neck.ValueWhen(setup) carries the level forward. When each high of the flag is at least its close the level is at most the stop of BearFlagStop. The measured move is the level less the pole of BearFlagPoleTop and BearFlagPoleBottom.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. If maxFlagBars < 3, maxPoleBars < 1, or maxFlagDepthATR or minPoleHeightATR is NaN or infinite, the result is all +0. If maxFlagBars or maxPoleBars exceeds len(s) nothing is set up and the result is all NaN. Bar t can be a setup bar only if every close, high and low of bars t-W+1 to t is finite, with W = max(70, maxPoleBars + maxFlagBars + 40) and the window clipped at bar 0.
Warm-up: no bar before max(69, maxPoleBars + maxFlagBars + 38) is a setup bar, so the value is NaN there.
Causal: the output on bar t uses bars 0 to t only. Pure: the inputs are not modified and the result is a new Series. See the manual’s “Chart Patterns” section for the recipes.
BearFlagPoleBottom
func (s Series) BearFlagPoleBottom(h, l Series, maxFlagBars, maxPoleBars int, maxFlagDepthATR, minPoleHeightATR float64) Series
BearFlagPoleBottom holds the close at the bottom of the pole on each setup bar, and NaN otherwise.
On each setup bar t of BearFlagSetup the value is BOT, the close of bar p, the latest bar holding the lowest close of bars t-maxFlagBars-1 to t-2, where the flag starts, copied verbatim. Off setup bars the value is NaN rather than 0. The receiver holds the closes, and h and l are its highs and lows. The view is the negation of BullFlagPoleTop of s.Mul(-1) with l.Mul(-1) as the highs and h.Mul(-1) as the lows. The setup bar is x1 + 2 bars after p, with x1 = t - 2 - p the flag length, and on every setup bar the value is strictly below the pole top of BearFlagPoleTop.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. If maxFlagBars < 3, maxPoleBars < 1, or maxFlagDepthATR or minPoleHeightATR is NaN or infinite, the result is all +0. If maxFlagBars or maxPoleBars exceeds len(s) nothing is set up and the result is all NaN. Bar t can be a setup bar only if every close, high and low of bars t-W+1 to t is finite, with W = max(70, maxPoleBars + maxFlagBars + 40) and the window clipped at bar 0.
Warm-up: no bar before max(69, maxPoleBars + maxFlagBars + 38) is a setup bar, so the value is NaN there.
Causal: the output on bar t uses bars 0 to t only. Pure: the inputs are not modified and the result is a new Series. See the manual’s “Chart Patterns” section for the recipes.
BearFlagPoleTop
func (s Series) BearFlagPoleTop(h, l Series, maxFlagBars, maxPoleBars int, maxFlagDepthATR, minPoleHeightATR float64) Series
BearFlagPoleTop holds the highest close of the pole window on each setup bar, and NaN otherwise.
On each setup bar t of BearFlagSetup the value is PEAK, the close of bar q, the latest bar holding the highest close of bars t-maxPoleBars-x1 to t, with x1 the flag length, where the falling pole starts, copied verbatim. Off setup bars the value is NaN rather than 0. The receiver holds the closes, and h and l are its highs and lows. The view is the negation of BullFlagPoleBottom of s.Mul(-1) with l.Mul(-1) as the highs and h.Mul(-1) as the lows. Bar q is at or before the bottom of the pole, at most maxPoleBars - 2 bars before it, and on every setup bar the value is strictly above the pole bottom of BearFlagPoleBottom. The measured move subtracts the pole, this level less the pole bottom, from the neckline of BearFlagNeckline.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. If maxFlagBars < 3, maxPoleBars < 1, or maxFlagDepthATR or minPoleHeightATR is NaN or infinite, the result is all +0. If maxFlagBars or maxPoleBars exceeds len(s) nothing is set up and the result is all NaN. Bar t can be a setup bar only if every close, high and low of bars t-W+1 to t is finite, with W = max(70, maxPoleBars + maxFlagBars + 40) and the window clipped at bar 0.
Warm-up: no bar before max(69, maxPoleBars + maxFlagBars + 38) is a setup bar, so the value is NaN there.
Causal: the output on bar t uses bars 0 to t only. Pure: the inputs are not modified and the result is a new Series. See the manual’s “Chart Patterns” section for the recipes.
BearFlagSetup
func (s Series) BearFlagSetup(h, l Series, maxFlagBars, maxPoleBars int, maxFlagDepthATR, minPoleHeightATR float64) Series
BearFlagSetup reports 1 on each bar that completes the mirror image of Katsanos’s bull-flag entry conditions, and 0 otherwise.
A setup bar is the bar on which the mirrored strategy of Katsanos (2014) places a sell stop for the next bar at the flag’s lowest close; consecutive setup bars renew the order. The view equals BullFlagSetup of s.Mul(-1) with l.Mul(-1) as the highs and h.Mul(-1) as the lows, bit for bit: BearFlag gives the mirrored rules in brief and BullFlag the published ones in full. The receiver holds the closes, and h and l are its highs and lows. The flat-position test and the 50-bar wait after an exit are trade state and are left out. Katsanos’s test of bear flags (2006) on 2,000 stocks over ten years was profitable only in 1998 and from March 2001 to March 2003 and he withheld the system, so the setups are a mirror of the bull rules, not a published system. No profitability is claimed.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. If maxFlagBars < 3, maxPoleBars < 1, or maxFlagDepthATR or minPoleHeightATR is NaN or infinite, the result is all +0. If maxFlagBars or maxPoleBars exceeds len(s) nothing is set up and the result is all +0. Bar t can be a setup bar only if every close, high and low of bars t-W+1 to t is finite, with W = max(70, maxPoleBars + maxFlagBars + 40) and the window clipped at bar 0; a NaN or an infinity there gives no setup on bar t and changes nothing else.
Warm-up: no bar before max(69, maxPoleBars + maxFlagBars + 38) is a setup bar, and +0 is the no-event value.
Causal: the output on bar t uses bars 0 to t only, and every prefix of the inputs gives the first values of the result bit for bit. Pure: the inputs are not modified and the result is a new Series. See the manual’s “Chart Patterns” section for the recipes.
BearFlagStop
func (s Series) BearFlagStop(h, l Series, maxFlagBars, maxPoleBars int, maxFlagDepthATR, minPoleHeightATR float64) Series
BearFlagStop holds the highest high of the flag on each setup bar, and NaN otherwise.
On each setup bar t of BearFlagSetup the value is the high of the latest bar holding the highest high of bars t-x1 to t, the x1 + 1 bars of the flag with x1 the flag length, copied verbatim: a tie goes to the latest bar, and a -0 stays -0. Off setup bars the value is NaN rather than 0. The receiver holds the closes, and h and l are its highs and lows. The view is the negation of BullFlagStop of s.Mul(-1) with l.Mul(-1) as the highs and h.Mul(-1) as the lows: the flag high behind the mirrored protective stop, three ATR above it and placed only once the position is more than five bars old, with the delay trade state. Prices are not validated, so the level is at least the neckline of BearFlagNeckline only when each high of the flag is at least its close.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. If maxFlagBars < 3, maxPoleBars < 1, or maxFlagDepthATR or minPoleHeightATR is NaN or infinite, the result is all +0. If maxFlagBars or maxPoleBars exceeds len(s) nothing is set up and the result is all NaN. Bar t can be a setup bar only if every close, high and low of bars t-W+1 to t is finite, with W = max(70, maxPoleBars + maxFlagBars + 40) and the window clipped at bar 0.
Warm-up: no bar before max(69, maxPoleBars + maxFlagBars + 38) is a setup bar, so the value is NaN there.
Causal: the output on bar t uses bars 0 to t only. Pure: the inputs are not modified and the result is a new Series. See the manual’s “Chart Patterns” section for the recipes.
BullFlag
func (s Series) BullFlag(h, l Series, maxFlagBars, maxPoleBars int, maxFlagDepthATR, minPoleHeightATR float64) Series
BullFlag reports 1 on the bar whose high reaches the buy-stop level of the bull flag set up on the previous bar, and 0 otherwise.
A bull flag is a steep rise, the pole, followed by a short and shallow drift lower, the flag; the entry is a buy stop at the flag’s highest close, filled when the high reaches it. The rules are the entry conditions of the intraday strategy of Katsanos (2014), translated literally: a scan of fixed and data-dependent windows ending at the current bar, with no swing state, so that every quantity is known on the setup bar and nothing repaints. They find what the published code finds, not hand-picked textbook flags. The receiver holds the closes, and h and l are its highs and lows. ATR is s.ATR(h, l, s, 40), the simple 40-bar average of the true range, not Wilder’s smoothing.
Bar t is a setup bar when every close, high and low of its read window is finite and all six rules hold, each comparison strict as printed and each extreme taken on the latest bar holding it. Rule 1: p is the latest bar holding the highest close of bars t-maxFlagBars-1 to t-2, x1 = t - 2 - p is at least 2, and TOP is the close of bar p. Rule 2: the slope of the closes of bars t-x1 to t-1 is below 0, and TOP - LF < maxFlagDepthATRATR[t], where LF is the lowest close of bars t-x1 to t (bar p+1 is left out, as printed). Rule 3: q is the latest bar holding the lowest close of bars t-maxPoleBars-x1 to t, BOTTOM is its close, t - q > x1 + 1 (q is at or before p) and TOP - BOTTOM > minPoleHeightATRATR[t]. Rule 4: the slope of the closes of bars t-x1+1 to t is below 0, or the slope of the closes of bars t-x1 to t-2 is below 0 (with x1 = 2 that window has one close, its slope is NaN and it does not pass). Rule 5: BOTTOM is above the lowest low of bars t-69 to t, Katsanos’s Condition 3, kept although it is nearly vacuous; no uptrend is tested. Rule 6: (ATR[t]/ATR[q] - 1)*100 > 5, the rise of the ATR from the pole bottom to the setup bar. Each slope is the fitted least-squares slope of its window, as Slope gives it. The setup bar is x1 + 2 bars after the top of the pole, so 4 to maxFlagBars + 1 bars after it, and the pole bottom is at most maxPoleBars - 2 bars before the top.
The order is a buy stop for the next bar at the highest close of bars t-x1+1 to t, the level that BullFlagNeckline holds. BullFlag is 1 on bar t when bar t-1 is a setup bar and h[t] >= that level: a stop order fills when the high reaches it, so the fill is inclusive, the published rule and an exception to the package’s strict thresholds. From bar 1 on the view equals h.GTE(neck.Shift(1)) with neck the neckline view, and bar 0 is 0. A NaN high does not fill; a +Inf high does. The close-based alternative is the recipe s.GT(neck.Shift(1)) from bar 1 on (Shift pads bar 0 with 0, so a positive close there reads 1), or s.GT(neck.Shift(1)).Mul(setup.Shift(1)) with setup the setup view on every bar. The levels are not latched: the order lives one bar and is placed again on each setup bar, so consecutive setup bars and consecutive breakouts are common.
maxFlagBars and maxPoleBars count bars, and maxFlagDepthATR and minPoleHeightATR are multiples of the ATR. Katsanos’s settings are s.BullFlag(h, l, 15, 23, 2.5, 5.5) for Nasdaq stocks on 15-minute bars, a depth of 2.5 and a pole of 3.5 for index futures, and 2 and 4 for forex, these two tuned on those markets where the stock entry values were not fitted; 15 and 23 bars are the 90th percentiles of flag and pole duration in his sample, and 5.5 sits just under the 10th percentile of pole height, 5.6 ATR. The 40-bar ATR, the 70 bars of rule 5 and the 5 percent rise are fixed, as printed. Any finite multiple is valid, zero and negative included. His exits, the profit target, the flat-position test and the 50-bar wait after an exit are trade state and are left out. With neck, top and bottom the neckline and pole views on a setup bar, the measured move of Edwards and Magee is neck.Add(top.Sub(bottom)) and Katsanos’s target, with the neckline standing in for his entry price and his +.0001 guard dropped, is neck.Mul(top.Sub(bottom).Div(bottom).Mul(1.2).Add(1)), with k = 1.2 for stocks, 0.9 for index futures and 0.6 for forex; neck.ValueWhen(setup) carries either forward. His in-sample test on five Nasdaq stocks over the first half of 2014 made 54 trades, 65 percent profitable on average across the five stocks, with an average profit factor of 5.19, no costs stated and the exits fitted on one stock. No profitability is claimed.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. If maxFlagBars < 3, maxPoleBars < 1, or maxFlagDepthATR or minPoleHeightATR is NaN or infinite, the result is all +0. If maxFlagBars or maxPoleBars exceeds len(s), or the first valid bar lies beyond the data, nothing is set up and the result is all +0. Prices are not validated. Bar t can be a setup bar only if every close, high and low of bars t-W+1 to t is finite, with W = max(70, maxPoleBars + maxFlagBars + 40) (78 with the defaults) and the window clipped at bar 0; W is the deepest bar the rules read, and a NaN or an infinity there gives no setup on bar t and changes nothing else. The breakout reads h[t] as it is.
Warm-up: no bar before max(69, maxPoleBars + maxFlagBars + 38) is a setup bar (76 with the defaults), so no breakout comes before the bar after it, and +0 is the no-event value. Katsanos’s 150 bars are what he had his platform load (its maximum bars back), more than the entry rules read.
Causal: the output on bar t uses bars 0 to t only, and every prefix of the inputs gives the first values of the result bit for bit. The rules are window-local, but the ATR is a compensated running sum whose last bits depend on where the series starts, so a slice of the history can move a comparison that sits at its threshold. Cost: O(maxPoleBars + maxFlagBars + 70) a bar after the ATR. Pure: the inputs are not modified and the result is a new Series. See the manual’s “Chart Patterns” section for the recipes.
BullFlagNeckline
func (s Series) BullFlagNeckline(h, l Series, maxFlagBars, maxPoleBars int, maxFlagDepthATR, minPoleHeightATR float64) Series
BullFlagNeckline holds the highest close of the flag on each setup bar, the level of the next bar’s buy stop, and NaN otherwise.
On each setup bar t of BullFlagSetup the value is the close of the latest bar holding the highest close of bars t-x1+1 to t, with x1 the flag length of Katsanos (2014), copied verbatim: a tie goes to the latest bar, and a -0 stays -0. Off setup bars the value is NaN rather than 0, so that a level of 0 is still a level. The receiver holds the closes, and h and l are its highs and lows. The level is the buy stop placed for the next bar: with neck this view and setup the setup view, BullFlag equals h.GTE(neck.Shift(1)) from bar 1 on, and neck.ValueWhen(setup) carries the level forward to the bars after the setup; the view itself does not latch it, since the order lives one bar. When each low of the flag is at most its close the level is at least the stop of BullFlagStop. With top and bottom the pole views, the measured move is neck.Add(top.Sub(bottom)) and Katsanos’s target, with this level standing in for his entry price, is neck.Mul(top.Sub(bottom).Div(bottom).Mul(1.2).Add(1)); on the package’s worked example with the stock settings they are 146 and 157.136.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. If maxFlagBars < 3, maxPoleBars < 1, or maxFlagDepthATR or minPoleHeightATR is NaN or infinite, the result is all +0. If maxFlagBars or maxPoleBars exceeds len(s) nothing is set up and the result is all NaN. Bar t can be a setup bar only if every close, high and low of bars t-W+1 to t is finite, with W = max(70, maxPoleBars + maxFlagBars + 40) and the window clipped at bar 0.
Warm-up: no bar before max(69, maxPoleBars + maxFlagBars + 38) is a setup bar, so the value is NaN there.
Causal: the output on bar t uses bars 0 to t only. Pure: the inputs are not modified and the result is a new Series. See the manual’s “Chart Patterns” section for the recipes.
BullFlagPoleBottom
func (s Series) BullFlagPoleBottom(h, l Series, maxFlagBars, maxPoleBars int, maxFlagDepthATR, minPoleHeightATR float64) Series
BullFlagPoleBottom holds the lowest close of the pole window on each setup bar, and NaN otherwise.
On each setup bar t of BullFlagSetup the value is BOTTOM, the close of bar q, the latest bar holding the lowest close of bars t-maxPoleBars-x1 to t in the rules of Katsanos (2014), with x1 the flag length, copied verbatim. Off setup bars the value is NaN rather than 0. The receiver holds the closes, and h and l are its highs and lows. Bar q is at or before the top of the pole, at most maxPoleBars - 2 bars before it, and on every setup bar the value is strictly below the pole top of BullFlagPoleTop. The targets of BullFlagNeckline use it: the measured move adds the pole, top less bottom, to the neckline, and Katsanos’s target scales the neckline by one plus k times the pole divided by this level; the pole in ATR is top.Sub(bottom).Div(s.ATR(h, l, s, 40)).
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. If maxFlagBars < 3, maxPoleBars < 1, or maxFlagDepthATR or minPoleHeightATR is NaN or infinite, the result is all +0. If maxFlagBars or maxPoleBars exceeds len(s) nothing is set up and the result is all NaN. Bar t can be a setup bar only if every close, high and low of bars t-W+1 to t is finite, with W = max(70, maxPoleBars + maxFlagBars + 40) and the window clipped at bar 0.
Warm-up: no bar before max(69, maxPoleBars + maxFlagBars + 38) is a setup bar, so the value is NaN there.
Causal: the output on bar t uses bars 0 to t only. Pure: the inputs are not modified and the result is a new Series. See the manual’s “Chart Patterns” section for the recipes.
BullFlagPoleTop
func (s Series) BullFlagPoleTop(h, l Series, maxFlagBars, maxPoleBars int, maxFlagDepthATR, minPoleHeightATR float64) Series
BullFlagPoleTop holds the close at the top of the pole on each setup bar, and NaN otherwise.
On each setup bar t of BullFlagSetup the value is TOP, the close of bar p, the latest bar holding the highest close of bars t-maxFlagBars-1 to t-2 in the rules of Katsanos (2014), copied verbatim. Off setup bars the value is NaN rather than 0. The receiver holds the closes, and h and l are its highs and lows. The setup bar is x1 + 2 bars after p, with x1 = t - 2 - p the flag length, so 4 to maxFlagBars + 1 bars after it. On every setup bar the pole bottom of BullFlagPoleBottom is strictly below this level. With neck, top and bottom the neckline and pole views, the measured move of Edwards and Magee is neck.Add(top.Sub(bottom)), Katsanos’s target, with the neckline standing in for his entry price, is neck.Mul(top.Sub(bottom).Div(bottom).Mul(1.2).Add(1)) with k = 1.2 for stocks, and the pole in ATR, the unit of his pole statistics, is top.Sub(bottom).Div(s.ATR(h, l, s, 40)).
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. If maxFlagBars < 3, maxPoleBars < 1, or maxFlagDepthATR or minPoleHeightATR is NaN or infinite, the result is all +0. If maxFlagBars or maxPoleBars exceeds len(s) nothing is set up and the result is all NaN. Bar t can be a setup bar only if every close, high and low of bars t-W+1 to t is finite, with W = max(70, maxPoleBars + maxFlagBars + 40) and the window clipped at bar 0.
Warm-up: no bar before max(69, maxPoleBars + maxFlagBars + 38) is a setup bar, so the value is NaN there.
Causal: the output on bar t uses bars 0 to t only. Pure: the inputs are not modified and the result is a new Series. See the manual’s “Chart Patterns” section for the recipes.
BullFlagSetup
func (s Series) BullFlagSetup(h, l Series, maxFlagBars, maxPoleBars int, maxFlagDepthATR, minPoleHeightATR float64) Series
BullFlagSetup reports 1 on each bar that completes Katsanos’s bull-flag entry conditions, and 0 otherwise.
A setup bar is the bar on which the strategy of Katsanos (2014) places a buy stop for the next bar at the flag’s highest close; consecutive setup bars renew the order. The receiver holds the closes, and h and l are its highs and lows. In brief, with ATR = s.ATR(h, l, s, 40): p is the latest bar holding the highest close of bars t-maxFlagBars-1 to t-2 and x1 = t - 2 - p is at least 2; the slope of the closes of bars t-x1 to t-1 is below 0 and the close of bar p less the lowest close of bars t-x1 to t is below maxFlagDepthATRATR[t]; q, the latest bar holding the lowest close of bars t-maxPoleBars-x1 to t, satisfies t - q > x1 + 1, and the close of bar p less the close of bar q is above minPoleHeightATRATR[t]; the slope of the closes of bars t-x1+1 to t or that of bars t-x1 to t-2 is below 0; the close of bar q is above the lowest low of bars t-69 to t; and (ATR[t]/ATR[q] - 1)*100 > 5. BullFlag gives the rules in full, with their ties and their strictness. The flat-position test and the 50-bar wait after an exit are trade state and are left out, so the view reports every setup, including those a system already in a position would not take.
On finite data whose 40-bar ATR is above 0 from bar 39 the view equals, from the first valid bar on, an OR over the flag lengths k = 2 to maxFlagBars - 1 of one composition per k of IndexOfHighestS, Slope, Highest, Lowest, IndexOfLowestS and ShiftVariable. Let F = maxFlagBars, P = maxPoleBars, d = maxFlagDepthATR, m = minPoleHeightATR, atr = s.ATR(h, l, s, 40) and x1 = s.IndexOfHighestS(F).Mul(-1).Add(float64(F-1)).Shift(2). For each k from 2 to F-1, let w = P + k + 1, top = s.Highest(k + 1).Shift(2), y23 = s.IndexOfLowestS(w).Mul(-1).Add(float64(w - 1)) and bottom = s.Lowest(w), and multiply x1.EQ(float64(k)) by s.Slope(k).Shift(1).LT(0), top.Sub(s.Lowest(k + 1)).LT(atr.Mul(d)), top.Sub(bottom).GT(atr.Mul(m)), y23.GT(float64(k + 1)), s.Slope(k).LT(0).Add(s.Slope(k - 1).Shift(2).LT(0)).GT(0), bottom.Sub(l.Lowest(70)).GT(0) and atr.Div(atr.ShiftVariable(y23)).Sub(1).Mul(100).GT(5). Summing those terms over k and applying sum.GT(0) gives this view. The ATR must be above 0 because Div gives 0 for a zero divisor, where the rule’s IEEE quotient makes (ATR[t]/ATR[q] - 1)*100 +Inf, which passes. The method is needed because the flag length, the pole bottom and ATR[q] change from bar to bar: the kernel costs one pass where the composition costs about a dozen Series per k. Katsanos’s own example is PCLN, set up on 14 January 2014 at 14:30.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. If maxFlagBars < 3, maxPoleBars < 1, or maxFlagDepthATR or minPoleHeightATR is NaN or infinite, the result is all +0. If maxFlagBars or maxPoleBars exceeds len(s) nothing is set up and the result is all +0. Bar t can be a setup bar only if every close, high and low of bars t-W+1 to t is finite, with W = max(70, maxPoleBars + maxFlagBars + 40) and the window clipped at bar 0; a NaN or an infinity there gives no setup on bar t and changes nothing else.
Warm-up: no bar before max(69, maxPoleBars + maxFlagBars + 38) is a setup bar, and +0 is the no-event value.
Causal: the output on bar t uses bars 0 to t only, and every prefix of the inputs gives the first values of the result bit for bit. Pure: the inputs are not modified and the result is a new Series. See the manual’s “Chart Patterns” section for the recipes.
BullFlagStop
func (s Series) BullFlagStop(h, l Series, maxFlagBars, maxPoleBars int, maxFlagDepthATR, minPoleHeightATR float64) Series
BullFlagStop holds the lowest low of the flag on each setup bar, and NaN otherwise.
On each setup bar t of BullFlagSetup the value is the low of the latest bar holding the lowest low of bars t-x1 to t, the x1 + 1 bars of the flag with x1 the flag length of Katsanos (2014), copied verbatim: a tie goes to the latest bar, and a -0 stays -0. Off setup bars the value is NaN rather than 0. The receiver holds the closes, and h and l are its highs and lows. This is the flag low behind Katsanos’s protective stop, which sits three ATR below it and is placed, for the next bar, only once the position is more than five bars old (his BSE > BSEMIN, with BSEMIN 5): with stop this view, that level is stop.Sub(s.ATR(h, l, s, 40).Mul(3)), and the delay is trade state. Prices are not validated, so the level is at most the neckline of BullFlagNeckline only when each low of the flag is at most its close.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. If maxFlagBars < 3, maxPoleBars < 1, or maxFlagDepthATR or minPoleHeightATR is NaN or infinite, the result is all +0. If maxFlagBars or maxPoleBars exceeds len(s) nothing is set up and the result is all NaN. Bar t can be a setup bar only if every close, high and low of bars t-W+1 to t is finite, with W = max(70, maxPoleBars + maxFlagBars + 40) and the window clipped at bar 0.
Warm-up: no bar before max(69, maxPoleBars + maxFlagBars + 38) is a setup bar, so the value is NaN there.
Causal: the output on bar t uses bars 0 to t only. Pure: the inputs are not modified and the result is a new Series. See the manual’s “Chart Patterns” section for the recipes.
Cup
func (s Series) Cup(h, l Series, n, minCup, maxCup int) Series
Cup reports 1 on each bar on which Siligardos’s five-by-five log grid identifies a cup, with or without handle, that it did not report on the previous bar or whose rim bar has changed, and 0 otherwise.
The receiver holds the closes, and h and l are its highs and lows. The pattern is the cup of Siligardos (2006): a five-by-five grid on the natural logarithm of the prices, anchored on the two rims, with boxes that must be empty of closes and boxes that must hold lows. His published MetaStock indicator evaluates the grid once, at the last bar of the chart, and freezes its anchors over the whole chart, so a historical chart repaints; here the grid is evaluated on every bar t as if t were the last bar, from bars 0 to t only, so nothing repaints, and no swings or pivots are involved. n is the rim lookback, minCup the length a cup must exceed and maxCup the longest cup looked for, all in bars.
On bar t the rim bar B5 is the latest bar of the last n bars whose high equals their highest high, so the handle, bars B5 to t, is 0 to n-1 bars long. The left rim is the latest bar before B5 whose high is strictly above the high at B5, found by a scan of at most maxCup bars back; a bar before B5 whose high equals the high at B5 is passed, and without a left rim within reach there is no cup. The cup’s first bar B0 is the bar after the left rim, its length is B5 - B0 + 1, and a cup needs length > minCup, the test of the listing (the text asks for at least 30 bars). P1 is ln of the high at B5 and P0 is ln of the lowest low from B0 to t, handle included, as in the listing (the text stops at B5). With bh = |P1 - P0|/5 the levels are L1 = P0 + bh, L2 = P0 + 2bh and L3 = P0 + 3bh, and the nodes are B1 = B5 - 4length/5, B2 = B5 - 3length/5, B3 = B5 - 2*length/5 and B4 = B5 - length/5, the divisions rounded down. Between two nodes means the open interval, the bars strictly between them, which is empty when the nodes are fewer than two bars apart.
Three rules then decide. No close between B1 and B4 has ln at or above L3 and no close between B2 and B3 has ln at or above L2: a close exactly on the line is inside the box and breaks the rule. Some low between B1 and B2 and some low between B3 and B4 are strictly below L1 - bh/3, or some low between B0 and B1 and some low between B4 and B5 are strictly below L2 - bh/3, the thresholds in the form of the listing. Every close from B5 to t is strictly above L2: the handle is tested on closes, not lows, as the listing does (the text says lows). The rim bar, the left rim and the extremes of each box are found on the prices; only the levels are in logarithms, taken with the package’s subnormal-safe ln. On amd64 the logarithm of the math package, which ln wraps, is assembly and on arm64 it is Go code, and the two differ in the last bit on some arguments, so a close or low within an ulp of a level can be decided differently on the two.
The 1 marks Siligardos’s identification bar, not a breakout. It is reported on the first bar of each run of bars on which a cup holds, and again on each bar inside a run on which the rim bar changes: a high at or above the high at B5 that is still below the left rim moves B5 to its bar (an equal high moves it too) and re-measures the cup, which on a rising right side happens on each new high that keeps a cup. The rim bar also moves when it drops out of the last n bars, to the latest bar at the highest high of the new window, if that bar heads a cup. A breakout bar normally brings a high at or above the rim, and the longer cup with no handle that it re-measures may or may not be identified again. The recipe length.GT(0).GT(length.GT(0).Shift(1)) gives one event per run; see the manual’s “Chart Patterns” section for it and the other recipes.
Siligardos’s defaults are n = 20 and minCup = 30, and he set no maximum, so c.Cup(h, l, 20, 30, len(c)) reproduces his unbounded scan; a smaller maxCup bounds the cost and the reach, and no default is suggested for it. Siligardos (2017) tested a slight modification of the algorithm, which he did not print; the shortest cup it found lasted 20 bars, so minCup = 19 allows cups of that length.
A bar whose high, low or close is not finite and positive is invalid. A cup on bar t needs every bar from the left rim to t valid: an invalid bar among the last n bars, or met by the scan for the left rim, gives no cup on bar t, and nothing is latched, so later bars recover once the bad bar is out of reach.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. If n < 1 or minCup < 0 the result is all +0. Any maxCup is valid, and with maxCup <= minCup no cup can be found. Warm-up: a cup needs a left rim before the last n bars, length > minCup and a length of at least 8 bars for its low boxes, so the first possible 1 is on bar max(minCup + 1, n, 8) and bars 0 to max(minCup, n-1, 7) read 0. Causal and window-local: the output on bar t uses bars 0 to t only, and at most bars t - n + 1 - maxCup to t together with the evaluation of bar t-1, so a slice equals the full run on every bar with n + maxCup bars of history before it.
Siligardos (2006) showed the pattern on chart examples only, as a screening aid that ignores volume. Siligardos (2017) tested his modified algorithm on S&P 500 stocks from 1982 to 2014 and found an edge that vanished after 2000, without commissions or a benchmark. No profitability is claimed.
CupLength, CupRim and CupLow are the other views of the same scan: the length, the high at the rim bar and the lowest low. Each bar costs O(n + maxCup). Pure: a new Series is returned and no input is modified, including when the receiver and an argument are the same slice.
CupLength
func (s Series) CupLength(h, l Series, n, minCup, maxCup int) Series
CupLength holds the number of bars from the cup’s first bar to its rim bar on every bar on which a cup holds, and 0 otherwise.
The receiver holds the closes, and h and l are its highs and lows. The length is B5 - B0 + 1, from the cup’s first bar B0, the bar after the left rim, to its rim bar B5, the latest of the last n bars at their highest high, and a reported length satisfies minCup < length <= maxCup; it is Siligardos’s own output, 0 when no cup holds. The same scan as Cup decides whether a cup holds on a bar, and Cup documents its rules; CupRim and CupLow are the other views of it. The handle’s length, the bars from B5 to the current bar, is (n-1) - h.IndexOfHighestS(n), because IndexOfHighestS counts the position of the latest highest high from the oldest bar of the window rather than the bars since it.
The bounds only mask: a bound changes neither the rim bar nor the left rim the scan finds, so with free := c.CupLength(h, l, n, 0, len(c)) the result equals free.Mul(free.GT(minCup)).Mul(free.LTE(maxCup)) on every bar, for any minCup >= 0 and any maxCup.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. If n < 1 or minCup < 0 the result is all +0. Warm-up: the first possible non-zero value is on bar max(minCup + 1, n, 8), and bars 0 to max(minCup, n-1, 7) read 0. Causal and window-local: the output on bar t uses bars 0 to t only, and at most bars t - n + 1 - maxCup to t, so a slice equals the full run on every bar with n + maxCup bars of history before it. See the manual’s “Chart Patterns” section for the recipes. Each bar costs O(n + maxCup). Pure: a new Series is returned and no input is modified, including when the receiver and an argument are the same slice.
CupLow
func (s Series) CupLow(h, l Series, n, minCup, maxCup int) Series
CupLow holds the lowest low from the first bar of the cup under way to the current bar, and NaN when none holds.
The receiver holds the closes, and h and l are its highs and lows. The value is the lowest low from the cup’s first bar B0, the bar after the left rim, to the current bar, handle included, copied verbatim: the low behind P0, the floor of Siligardos’s grid, taken over the whole period as his listing does (his text stops at the rim bar). On a bar on which a cup holds it equals l.LowestVariable(p, 1, len(l)) with p = length + (n-1) - h.IndexOfHighestS(n), the number of bars from B0 to the current bar, where length is the CupLength view. The same scan as Cup decides whether a cup holds, and Cup documents its rules; CupLength and CupRim are the other views of it. Siligardos (2017) placed his stop 0.7 times the cup’s height below the high of the identification bar: with cup the Cup view, hi := h.ValueWhen(cup) and lo := low.ValueWhen(cup), the level is hi.Mul(0.3).Add(lo.Mul(0.7)), which is 158 for his example of a high of 200 and a low of 140.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. If n < 1 or minCup < 0 the result is all +0. Warm-up: NaN rather than 0 wherever no cup holds, because the start depends on the data; the first possible value is on bar max(minCup + 1, n, 8). Causal and window-local: the output on bar t uses bars 0 to t only, and at most bars t - n + 1 - maxCup to t, so a slice equals the full run on every bar with n + maxCup bars of history before it. See the manual’s “Chart Patterns” section for the recipes. Each bar costs O(n + maxCup). Pure: a new Series is returned and no input is modified, including when the receiver and an argument are the same slice.
CupRim
func (s Series) CupRim(h, l Series, n, minCup, maxCup int) Series
CupRim holds the high at the rim bar of the cup under way, the level a breakout must clear, and NaN when none holds.
The receiver holds the closes, and h and l are its highs and lows. The value is the high at the rim bar B5, copied verbatim; B5 is the latest of the last n bars at their highest high, so on a bar on which a cup holds the value equals h.Highest(n). The same scan as Cup decides whether a cup holds, and Cup documents its rules; CupLength and CupLow are the other views of it. The cup’s height as a percentage, Siligardos’s exploration column, is rim.Div(low).Sub(1).Mul(100) with rim this view and low the CupLow view. A breakout of the cup that held on the previous bar is c.GT(rim.Shift(1)).Mul(length.Shift(1).GT(0)) with length the CupLength view: the mask is there because Shift pads bar 0 with 0, where the bare comparison would read 1 for any positive close. The breakout bar normally brings a high at or above the rim, which moves the rim bar to it and re-measures a longer cup with no handle, so on the breakout bar itself this view holds the new rim if a cup is identified again and NaN otherwise.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. If n < 1 or minCup < 0 the result is all +0. Warm-up: NaN rather than 0 wherever no cup holds, because the start depends on the data; the first possible value is on bar max(minCup + 1, n, 8). Causal and window-local: the output on bar t uses bars 0 to t only, and at most bars t - n + 1 - maxCup to t, so a slice equals the full run on every bar with n + maxCup bars of history before it. See the manual’s “Chart Patterns” section for the recipes. Each bar costs O(n + maxCup). Pure: a new Series is returned and no input is modified, including when the receiver and an argument are the same slice.
DoubleBottom
func (s Series) DoubleBottom(h, l Series, n int, bandMinFraction, bandMaxFraction, minHeightFraction float64) Series
DoubleBottom reports 1 on the bar whose close first exceeds the neckline of a pending double bottom, and 0 otherwise.
The receiver holds the closes, and h and l are its highs and lows. The pivots are l.SwingLowEvent(n): a low must be strictly below every other low in its complete 2n+1 bar window. It is confirmed n bars later, so nothing repaints. A tie, including +0 against -0, or any NaN in the window rejects the pivot. Infinities are ordered values in the swing test. Only l supplies the swings. Each confirmed swing low pairs with the immediately previous swing low; the first swing pairs with nothing. Their pivot bars b1 and b2 satisfy b2 - b1 > n.
A pair requires finite h and l on every bar from b1 to b2 inclusive, including the bottom bars’ highs. Write L1 = l[b1], L2 = l[b2] and Lm = min(L1, L2); require Lm > 0. The signed ratio (L2 - L1)/Lm must lie between bandMinFraction and bandMaxFraction, inclusive. Every low strictly between the bottoms must be >= Lm; an equal low passes. The neckline C is the highest high strictly between the bottoms; the bottom bars’ highs are left out. C need not be a confirmed swing high. Require (C - Lm)/Lm >= minHeightFraction, inclusive. Closes do not enter these checks. Highs and lows after b2 cannot alter the pair’s fixed levels, though they can affect later swings.
A qualifying pair arms on t = b2 + n and replaces any pending pattern: the newest wins. A non-qualifying pair leaves a pending pattern unchanged, while its second swing becomes the previous swing for the next pair. Each bar first expires a pending pattern, then arms a qualifying pair, then tests the current close. Following Tsinaslanidis and Zapranis (2016), a pattern survives only while t - b2 < b2 - b1; its expiry bar is b2 + (b2 - b1). A close strictly above C signals and ends the pattern; otherwise a close strictly below Lm voids it. Equality keeps it pending. A NaN close does neither. +Inf signals and -Inf voids. The arming bar’s own close is tested. This is a level test, not a fresh cross, and closes before arming are not read. The levels are under test from arming through the resolving bar, excluding the expiry bar. With valid parameters and equal lengths, the signal equals s.GT(neck), where neck is DoubleBottomNeckline.
The fractions are fractions of price, as for DirectionalChange: 0.04 is 4%. Bulkowski (1998) uses a band of -0.04 to 0.04 and minHeightFraction 0.10. The classical band is -0.03 to 0.03, with height 0.20, or 0.15 for patterns close together. Bulkowski (2019) uses 11-bar minor lows, n = 5, and a band of -0.015 to 0.015, with confirmation by a close above the pattern and a buy-stop just above it as the preferred entry. Bulkowski (2016) defines an ugly double bottom with the second bottom 5% to 20% above a first bottom that sets the yearly low: use s.DoubleBottom(h, l, 5, 0.05, 0.20, 0) with a yearly-low gate.
LUV’s bottoms 21.375 and 20.625 with neckline 23.5 give height 2.875 and target 26.375. VLSI’s lower bottom 10.375 and neckline 20.375 give target 30.375. The measure rule from Edwards and Magee is neck.Add(neck.Sub(stop)).ValueWhen(sig). A multiple of the height uses neck.Add(neck.Sub(stop).Mul(m)).ValueWhen(sig). See the manual’s “Chart Patterns” section for the measure rule and gates. A gate filters signals only: sig.Mul(g.ShiftVariable(setup.BarsSince()).GT(0)) reads g on the arming bar. GT(0) prevents a missing lag before the first setup from producing NaN events. A pair failing the gate still arms and replaces a pending pattern. Separation, trend and yearly-low constraints are gates. With w := l.SwingLowAge(n, 1), the yearly-low gate is l.Lowest(252).ShiftVariable(w).EQ(l.SwingLowValue(n, 1)), read on the arming bar.
Published tests mainly use hand-picked or unconfirmed pivots and hindsight outcomes, without costs. No profitability is claimed. PointAndFigureSignal uses point and figure rules; this method uses confirmed bar-count swings.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. For n < 1, non-finite band bounds, bandMinFraction > bandMaxFraction, or a non-finite or negative minHeightFraction, the result is all +0. Negative band bounds, equal bounds and minHeightFraction of -0 are valid. A valid n > (len(s)-1)/2 confirms no swing and gives all +0. Empty inputs return an empty, non-nil Series. Warm-up: bars 0 to 3n are +0; the first possible arming bar is 3n + 1.
This view is path-dependent: the output on bar t uses bars 0 to t only. Every prefix agrees bit for bit. For s[k:] with h and l sliced alike, a pattern in the full run whose first bottom is before bar k + n is lost: its events are +0 and its levels NaN there; nothing else changes. The cost is O(len(s)) whatever n, with consecutive pair spans scanned once. Pure: inputs are unchanged, results have fresh storage, repeated calls are deterministic, and concurrent calls on shared inputs are race-free.
DoubleBottomNeckline
func (s Series) DoubleBottomNeckline(h, l Series, n int, bandMinFraction, bandMaxFraction, minHeightFraction float64) Series
DoubleBottomNeckline holds the highest high between the two bottoms of the double bottom under test, and NaN when none is under test.
The receiver holds the closes, and h and l are its highs and lows. For the pair qualified by DoubleBottom, C is the highest high strictly between the bottom bars; their own highs are excluded. C is copied verbatim while the pattern is under test, from its arming bar through the bar whose close signals or voids it. It is absent on the expiry bar and until another pair arms. Replacement takes effect before the current close is tested, so the new neckline appears on its own arming bar.
With valid parameters and equal lengths, the signal is s.GT(neck), and s.LT(stop) identifies void bars. On every other bar under test, stop <= s[t] <= neck, or the close is NaN. Closes before arming are not read. neck.ValueWhen(sig) carries the last signal’s neckline forward. With valid parameters, neck.EQ(neck) is a pending flag for a buy-stop: it is 1 exactly on bars under test, including the resolving bar. The offset above the neckline is the caller’s choice. Height is neck.Sub(stop); the measure-rule target is neck.Add(neck.Sub(stop)).ValueWhen(sig). See the manual’s “Chart Patterns” section for the target and gate recipes.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. For n < 1, non-finite band bounds, bandMinFraction > bandMaxFraction, or a non-finite or negative minHeightFraction, the result is all +0. A minHeightFraction of -0 is valid. A valid n > (len(s)-1)/2 confirms no swing and gives all NaN. Empty inputs return an empty, non-nil Series. Warm-up: with valid parameters, the first possible arming bar is 3n + 1; the level is NaN rather than 0 until a pattern arms and wherever none is under test.
This view is path-dependent: the output on bar t uses bars 0 to t only. Every prefix agrees bit for bit. For s[k:] with h and l sliced alike, a pattern in the full run whose first bottom is before bar k + n is lost: its events are +0 and its levels NaN there; nothing else changes. The cost is O(len(s)) whatever n. Pure: inputs are unchanged, results have fresh storage, repeated calls are deterministic, and concurrent calls on shared inputs are race-free.
DoubleBottomSetup
func (s Series) DoubleBottomSetup(h, l Series, n int, bandMinFraction, bandMaxFraction, minHeightFraction float64) Series
DoubleBottomSetup reports 1 on the bar that confirms a swing low pairing with the previous swing low into a double bottom, and 0 otherwise.
The receiver holds the closes, and h and l are its highs and lows. DoubleBottom defines the qualification and lifetime rules. A qualifying pair arms on t = b2 + n, including when that bar’s own close signals or voids the pattern. The newest qualifying pair replaces a pending one; a non-qualifying pair leaves it alone.
With finite h and l, write ev := l.SwingLowEvent(n), L2 := l.SwingLowValue(n), L1 := l.SwingLowValue(n, 1), and w := l.SwingLowAge(n, 1).Sub(float64(n)). On a confirmation bar w = b2 - b1. Let Lm := L2.Minimum(L1), C := h.Shift(n+1).HighestVariable(w.Sub(1), 1, len(h)), and lo := l.Shift(n+1).LowestVariable(w.Sub(1), 1, len(l)). With r := L2.Sub(L1).Div(Lm), height := C.Sub(Lm).Div(Lm), a := bandMinFraction, b := bandMaxFraction and m := minHeightFraction, the setup equals ev.Mul(r.GTE(a)).Mul(r.LTE(b)).Mul(lo.GTE(Lm)).Mul(height.GTE(m)).Mul(Lm.GT(0)). The last factor rejects non-positive extremes. The method also rejects any non-finite h or l from b1 to b2 inclusive, including the bottom bars.
setup.BarsSince() gives the age since arming, including on a signal bar. A separation gate g := w.LTE(126), or w.GTE(10), reads b2 - b1 on the arming bar. Apply it as sig.Mul(g.ShiftVariable(setup.BarsSince()).GT(0)); GT(0) keeps events before the first setup at +0. A gate filters signals only; a pair that fails it still arms and replaces a pending pattern. See the manual’s “Chart Patterns” section for these recipes.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. For n < 1, non-finite band bounds, bandMinFraction > bandMaxFraction, or a non-finite or negative minHeightFraction, the result is all +0. A minHeightFraction of -0 is valid. A valid n > (len(s)-1)/2 confirms no swing and gives all +0. Empty inputs return an empty, non-nil Series. Warm-up: bars 0 to 3n are +0; the first possible arming bar is 3n + 1.
This view is path-dependent: the output on bar t uses bars 0 to t only. Every prefix agrees bit for bit. For s[k:] with h and l sliced alike, a pattern in the full run whose first bottom is before bar k + n is lost: its events are +0 and its levels NaN there; nothing else changes. The cost is O(len(s)) whatever n. Pure: inputs are unchanged, results have fresh storage, repeated calls are deterministic, and concurrent calls on shared inputs are race-free.
DoubleBottomStop
func (s Series) DoubleBottomStop(h, l Series, n int, bandMinFraction, bandMaxFraction, minHeightFraction float64) Series
DoubleBottomStop holds the lower bottom of the double bottom under test, and NaN when none is under test.
The receiver holds the closes, and h and l are its highs and lows. DoubleBottom defines qualification and lifetime. The stop is Lm = min(L1, L2), copied verbatim. With valid parameters it appears on precisely the bars on which DoubleBottomNeckline is not NaN: from arming through the bar whose close signals or voids the pattern, excluding the expiry bar. A replacement supplies its own stop before the current close is tested.
With valid parameters and equal lengths, s.LT(stop) identifies the void bars. Equality at the stop keeps the pattern pending, and a NaN close does neither signal nor void. Protective-stop offsets are the caller’s choice, including Bulkowski’s 3/16 and Kosinski’s few cents. Bulkowski (2019) uses a close below the second bottom; that alternative level is l.SwingLowValue(n).ValueWhen(setup). This machine voids on a close below the lower bottom. Height is neck.Sub(stop). See the manual’s “Chart Patterns” section for the target and gate recipes.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. For n < 1, non-finite band bounds, bandMinFraction > bandMaxFraction, or a non-finite or negative minHeightFraction, the result is all +0. A minHeightFraction of -0 is valid. A valid n > (len(s)-1)/2 confirms no swing and gives all NaN. Empty inputs return an empty, non-nil Series. Warm-up: with valid parameters, the first possible arming bar is 3n + 1; the level is NaN rather than 0 until a pattern arms and wherever none is under test.
This view is path-dependent: the output on bar t uses bars 0 to t only. Every prefix agrees bit for bit. For s[k:] with h and l sliced alike, a pattern in the full run whose first bottom is before bar k + n is lost: its events are +0 and its levels NaN there; nothing else changes. The cost is O(len(s)) whatever n. Pure: inputs are unchanged, results have fresh storage, repeated calls are deterministic, and concurrent calls on shared inputs are race-free.
DoubleTop
func (s Series) DoubleTop(h, l Series, n int, bandMinFraction, bandMaxFraction, minHeightFraction float64) Series
DoubleTop reports 1 on the bar whose close first falls below the neckline of a pending double top, and 0 otherwise.
The receiver holds the closes, and h and l are its highs and lows. The pivots are h.SwingHighEvent(n): a high must be strictly above every other high in its complete 2n+1 bar window. It is confirmed n bars later, so nothing repaints. A tie, including +0 against -0, or any NaN in the window rejects the pivot. Infinities are ordered values in the swing test. Only h supplies the swings. Each confirmed swing high pairs with the immediately previous swing high; the first swing pairs with nothing. Their pivot bars b1 and b2 satisfy b2 - b1 > n.
A pair requires finite h and l on every bar from b1 to b2 inclusive, including the peak bars’ lows. Write P1 = h[b1], P2 = h[b2] and Pm = max(P1, P2); require Pm > 0. Either individual peak may be zero or negative. The signed ratio (P2 - P1)/Pm must lie between bandMinFraction and bandMaxFraction, inclusive. Every high strictly between the peaks must be <= Pm; an equal high passes. The neckline V is the lowest low strictly between the peaks, with -0 taken over +0; the peak bars’ lows are left out. V need not be a confirmed swing low. Require (Pm - V)/Pm >= minHeightFraction, inclusive. V may be negative, zero or positive, and its sign of zero is preserved. These ratios are not those of a double bottom of negated data. Closes do not enter the pair checks. Highs and lows after b2 cannot alter its fixed levels, though they can affect later swings.
A qualifying pair arms on t = b2 + n and replaces any pending pattern: the newest wins. A non-qualifying pair leaves a pending pattern unchanged, while its second swing becomes the previous swing for the next pair. Each bar first expires a pending pattern, then arms a qualifying pair, then tests the current close. Following Tsinaslanidis and Zapranis (2016), a pattern survives only while t - b2 < b2 - b1; its expiry bar is b2 + (b2 - b1). A close strictly below V signals and ends the pattern; otherwise a close strictly above Pm voids it. Equality keeps it pending. A NaN close does neither. -Inf signals and +Inf voids. The arming bar’s own close is tested. This is a level test, not a fresh cross, and closes before arming are not read. The levels are under test from arming through the resolving bar, excluding the expiry bar. With valid parameters and equal lengths, the signal equals s.LT(neck), where neck is DoubleTopNeckline. The top and bottom machines are independent.
The fractions are fractions of price, as for DirectionalChange: 0.02 is 2%. Bulkowski (1998) uses a band of -0.02 to 0.02 and a decline of at least 0.10 from the tallest peak. ABTC’s peak 27.25 and valley 21 give a decline of 6.25, about 23%. The classical band is -0.03 to 0.03, with decline 0.20, or 0.15 for patterns close together. Tsinaslanidis and Zapranis (2016) use 0.04 and 0.10, with their band over the lower peak and their decline from the first peak, where this method uses Pm for both.
MYL’s higher peak 24.5 and neckline 18.375 give height 6.125 and target 12.25. ADBE’s higher peak 44.125 and neckline 38.75 give height 5.375 and target 33.375; its decline to 38.5 confirms. The measure rule from Edwards and Magee is neck.Add(neck.Sub(stop)).ValueWhen(sig); the negative difference subtracts the height. Height is stop.Sub(neck). See the manual’s “Chart Patterns” section for the measure rule and gates. A rising trend into the first peak uses g := s.Slope(40).ShiftVariable(h.SwingHighAge(n, 1)).GT(0), read on the arming bar. Apply sig.Mul(g.ShiftVariable(setup.BarsSince()).GT(0)) to filter the signal. Slope(40) reads 0 before bar 39, so this gate fails a pattern whose first peak is earlier than that. GT(0) prevents missing lags before the first setup from producing NaN events. A gate filters signals only; a pair failing it still arms and replaces a pending pattern.
Published tests mainly use hand-picked or unconfirmed pivots and hindsight outcomes, without costs. No profitability is claimed. PointAndFigureSignal uses point and figure rules; this method uses confirmed bar-count swings.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. For n < 1, non-finite band bounds, bandMinFraction > bandMaxFraction, or a non-finite or negative minHeightFraction, the result is all +0. Negative band bounds, equal bounds and minHeightFraction of -0 are valid. A valid n > (len(s)-1)/2 confirms no swing and gives all +0. Empty inputs return an empty, non-nil Series. Warm-up: bars 0 to 3n are +0; the first possible arming bar is 3n + 1.
This view is path-dependent: the output on bar t uses bars 0 to t only. Every prefix agrees bit for bit. For s[k:] with h and l sliced alike, a pattern in the full run whose first peak is before bar k + n is lost: its events are +0 and its levels NaN there; nothing else changes. The cost is O(len(s)) whatever n, with consecutive pair spans scanned once. Pure: inputs are unchanged, results have fresh storage, repeated calls are deterministic, and concurrent calls on shared inputs are race-free.
DoubleTopNeckline
func (s Series) DoubleTopNeckline(h, l Series, n int, bandMinFraction, bandMaxFraction, minHeightFraction float64) Series
DoubleTopNeckline holds the lowest low between the two peaks of the double top under test, and NaN when none is under test.
The receiver holds the closes, and h and l are its highs and lows. For the pair qualified by DoubleTop, V is the lowest low strictly between the peak bars, with -0 taken over +0; their own lows are excluded. V is copied verbatim, including its sign of zero, while the pattern is under test: from its arming bar through the bar whose close signals or voids it. It is absent on the expiry bar and until another pair arms. Replacement takes effect before the current close is tested, so the new neckline appears on its own arming bar.
With valid parameters and equal lengths, the signal is s.LT(neck), and s.GT(stop) identifies void bars. On every other bar under test, neck <= s[t] <= stop, or the close is NaN. Closes before arming are not read. neck.ValueWhen(sig) carries the last signal’s neckline forward. Height is stop.Sub(neck); the measure-rule target is neck.Add(neck.Sub(stop)).ValueWhen(sig). See the manual’s “Chart Patterns” section for the target and gate recipes.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. For n < 1, non-finite band bounds, bandMinFraction > bandMaxFraction, or a non-finite or negative minHeightFraction, the result is all +0. A minHeightFraction of -0 is valid. A valid n > (len(s)-1)/2 confirms no swing and gives all NaN. Empty inputs return an empty, non-nil Series. Warm-up: with valid parameters, the first possible arming bar is 3n + 1; the level is NaN rather than 0 until a pattern arms and wherever none is under test.
This view is path-dependent: the output on bar t uses bars 0 to t only. Every prefix agrees bit for bit. For s[k:] with h and l sliced alike, a pattern in the full run whose first peak is before bar k + n is lost: its events are +0 and its levels NaN there; nothing else changes. The cost is O(len(s)) whatever n. Pure: inputs are unchanged, results have fresh storage, repeated calls are deterministic, and concurrent calls on shared inputs are race-free.
DoubleTopSetup
func (s Series) DoubleTopSetup(h, l Series, n int, bandMinFraction, bandMaxFraction, minHeightFraction float64) Series
DoubleTopSetup reports 1 on the bar that confirms a swing high pairing with the previous swing high into a double top, and 0 otherwise.
The receiver holds the closes, and h and l are its highs and lows. DoubleTop defines the qualification and lifetime rules. A qualifying pair arms on t = b2 + n, including when that bar’s own close signals or voids the pattern. The newest qualifying pair replaces a pending one; a non-qualifying pair leaves it alone.
With finite h and l, write ev := h.SwingHighEvent(n), P2 := h.SwingHighValue(n), P1 := h.SwingHighValue(n, 1), and w := h.SwingHighAge(n, 1).Sub(float64(n)). On a confirmation bar w = b2 - b1. Let Pm := P2.Maximum(P1), V := l.Shift(n+1).LowestVariable(w.Sub(1), 1, len(l)), and hi := h.Shift(n+1).HighestVariable(w.Sub(1), 1, len(h)). With r := P2.Sub(P1).Div(Pm), dec := Pm.Sub(V).Div(Pm), a := bandMinFraction, b := bandMaxFraction and m := minHeightFraction, the setup equals ev.Mul(r.GTE(a)).Mul(r.LTE(b)).Mul(hi.LTE(Pm)).Mul(dec.GTE(m)).Mul(Pm.GT(0)). The last factor rejects non-positive extremes. The method also rejects any non-finite h or l from b1 to b2 inclusive, including the peak bars.
setup.BarsSince() gives the age since arming, including on a signal bar. A separation gate g := w.LTE(126), or w.GTE(10), reads b2 - b1 on the arming bar. Apply it as sig.Mul(g.ShiftVariable(setup.BarsSince()).GT(0)); GT(0) keeps events before the first setup at +0. A gate filters signals only; a pair that fails it still arms and replaces a pending pattern. See the manual’s “Chart Patterns” section for these recipes.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. For n < 1, non-finite band bounds, bandMinFraction > bandMaxFraction, or a non-finite or negative minHeightFraction, the result is all +0. A minHeightFraction of -0 is valid. A valid n > (len(s)-1)/2 confirms no swing and gives all +0. Empty inputs return an empty, non-nil Series. Warm-up: bars 0 to 3n are +0; the first possible arming bar is 3n + 1.
This view is path-dependent: the output on bar t uses bars 0 to t only. Every prefix agrees bit for bit. For s[k:] with h and l sliced alike, a pattern in the full run whose first peak is before bar k + n is lost: its events are +0 and its levels NaN there; nothing else changes. The cost is O(len(s)) whatever n. Pure: inputs are unchanged, results have fresh storage, repeated calls are deterministic, and concurrent calls on shared inputs are race-free.
DoubleTopStop
func (s Series) DoubleTopStop(h, l Series, n int, bandMinFraction, bandMaxFraction, minHeightFraction float64) Series
DoubleTopStop holds the higher peak of the double top under test, and NaN when none is under test.
The receiver holds the closes, and h and l are its highs and lows. DoubleTop defines qualification and lifetime. The stop is Pm = max(P1, P2), copied verbatim. With valid parameters it appears on precisely the bars on which DoubleTopNeckline is not NaN: from arming through the bar whose close signals or voids the pattern, excluding the expiry bar. A replacement supplies its own stop before the current close is tested.
With valid parameters and equal lengths, s.GT(stop) identifies the void bars. Equality at the stop keeps the pattern pending, and a NaN close does neither signal nor void. Protective-stop offsets are the caller’s choice. Height is stop.Sub(neck). See the manual’s “Chart Patterns” section for the target and gate recipes.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. For n < 1, non-finite band bounds, bandMinFraction > bandMaxFraction, or a non-finite or negative minHeightFraction, the result is all +0. A minHeightFraction of -0 is valid. A valid n > (len(s)-1)/2 confirms no swing and gives all NaN. Empty inputs return an empty, non-nil Series. Warm-up: with valid parameters, the first possible arming bar is 3n + 1; the level is NaN rather than 0 until a pattern arms and wherever none is under test.
This view is path-dependent: the output on bar t uses bars 0 to t only. Every prefix agrees bit for bit. For s[k:] with h and l sliced alike, a pattern in the full run whose first peak is before bar k + n is lost: its events are +0 and its levels NaN there; nothing else changes. The cost is O(len(s)) whatever n. Pure: inputs are unchanged, results have fresh storage, repeated calls are deterministic, and concurrent calls on shared inputs are race-free.
HeadShouldersBottom
func (s Series) HeadShouldersBottom(h, l Series, n int) Series
HeadShouldersBottom reports 1 on the bar whose close first breaks above the neckline of a confirmed head-and-shoulders bottom, and 0 otherwise.
The receiver s holds the closes (c in the recipes below), which are tested against the neckline; h and l hold the series whose confirmed swings form the pattern, usually the highs and the lows; n is the pivot half-window of the swings. The swing lows of l are the strict bar-count pivots that l.SwingLowEvent(n) confirms and the swing highs of h those of h.SwingHighEvent(n): a pivot is strictly beyond every other value of its window of n bars on each side, a tie or a NaN anywhere in the window suppresses the swing, +Inf and -Inf are ordered values, and a swing is confirmed n bars after its pivot. The close-only form c.HeadShouldersBottom(c, c, n) runs the detector on the closes alone, where the swings are exactly the rolling-window extrema of Tsinaslanidis and Zapranis (Technical Analysis for Algorithmic Pattern Recognition, 2016: the RW function of their chapter 2, which their head-and-shoulders code of chapter 5 calls) with their w = n, a window of 2n + 1 bars, dated n bars later.
The confirmed swings are merged in order of their pivot bars, the low first when a bar holds both. Whenever a swing low is confirmed, the last five merged swings are read as T1, P1, T2, P2, T3 at bars tT1 < tP1 < tT2 < tP2 < tT3, and they form a bottom when their kinds alternate low, high, low, high, low, the bars strictly increase (so no two of the five share a bar), the five values are finite, and the mirrored conditions of Osler and Chang (1995) and Lucke (2003), as Tsinaslanidis and Zapranis print and code them, hold: the head T2 < T1 and T2 < T3, the corrected form of their equation 5.8, whose printed form carries a misprint; the balance T1 <= 0.5*(T3+P2) and T3 <= 0.5*(T1+P1), inclusive, with each sum rounded before it is halved, so that a sum that overflows to -Inf fails and one that overflows to +Inf passes; and the symmetry 2*(tT2-tT1) < 5*(tT3-tT2) and 2*(tT3-tT2) < 5*(tT2-tT1) in whole bars, their ratio of 2.5. The trend before the pattern is not tested, as in their code. A qualifying pattern is armed on bar tT3 + n, the bar that confirms its right shoulder, and replaces any pattern still armed: the newest wins, where their code records overlapping patterns separately. A later swing that does not qualify leaves the armed pattern alone.
The neckline runs through the two peaks: N(t) = P1 + b*(t-tP1) with b = (P2-P1)/(tP2-tP1), the product rounded on its own and then added to P1. The head height is H = N(tT2) - T2, without the abs() of their code, so it is negative where the head trough lies above the neckline. On every bar, in this order, the swings confirmed on the bar are merged, a qualifying pattern is armed, an armed pattern whose deadline 2*tT3 - tT1 has arrived is disarmed without an event (the deadline is strict, where their code includes that bar), and then the close is tested: a close strictly above N(t) reports 1 and disarms the pattern. The test is a level test, not a fresh cross: a close above the neckline between tT3 and the arming bar is not seen, and a close still above it on tT3 + n reports the event on that bar. Their code searches the break from tT3 + 1, which is look-ahead, and their reported tests found the swings over the whole history. No other rule invalidates an armed pattern. A NaN close neither breaks nor disarms; a NaN in h or l suppresses the swings whose windows hold it and does not touch an armed pattern, whose values are stored; a pattern with an infinite swing value does not qualify.
HeadShouldersBottomNeckline and HeadShouldersBottomTarget hold N(t) and N(t) + H on every armed bar, so with neckline = c.HeadShouldersBottomNeckline(h, l, n) the event equals c.GT(neckline) for n >= 1 (n < 1 makes every view +0), and with event = c.HeadShouldersBottom(h, l, n) the neckline of each break is neckline.ValueWhen(event). On a bar that confirms a swing low, a bottom arms exactly when T3, T2 and T1 read from l.SwingLowValue(n), l.SwingLowValue(n, 1) and l.SwingLowValue(n, 2), P2 and P1 from h.SwingHighValue(n) and h.SwingHighValue(n, 1), and their bars from the matching ages, with h.SwingHighAge(n, 2) NaN or older than T1, pass the conditions above; on such a bar the level views hold N(t) and N(t) + H from these values. The bottom is the mirror image of the top: c.Mul(-1).HeadShouldersTop(l.Mul(-1), h.Mul(-1), n) equals c.HeadShouldersBottom(h, l, n) bit for bit, with the closes negated and the roles of h and l exchanged.
No pattern can arm before bar 4*n + 2, so the result is +0 on earlier bars, and it contains no NaN. Unequal lengths of s, h and l return Series{}, checked before n; equal empty inputs return a non-nil empty Series; n < 1 returns all +0. The result is window-local with a data-dependent span: a pattern depends only on the bars from tT1 - n to its last bar, so a slice of the inputs reproduces every pattern whose first window it holds and no other. One pass, O(len(s)) time.
Evidence: mixed. Osler and Chang (1995) set out the criteria on daily dollar exchange rates from 1973 to 1994, Chang and Osler (1999) found the rule profitable in the mark and the yen but not in four other currencies, Lucke (2003) found returns on five currencies that were not significantly positive, Osler (1998) found it unprofitable in US stocks, and Tsinaslanidis and Zapranis report the detector finding a bottom in about 23% of its runs on simulated geometric Brownian motion paths; the published tests include look-ahead. No trading edge is demonstrated. No stop is defined by the sources; the usual stop below the right shoulder or the head needs T3 or T2, which these views do not expose. Recipes built on the HeadShoulders methods are listed under “Compositions” in the “Chart Patterns” category.
HeadShouldersBottomNeckline
func (s Series) HeadShouldersBottomNeckline(h, l Series, n int) Series
HeadShouldersBottomNeckline holds the neckline of the armed head-and-shoulders bottom at each bar, and NaN when none is armed.
On every bar where a bottom is armed the value is N(t) = P1 + b*(t-tP1) with b = (P2-P1)/(tP2-tP1), the line through the two peaks of the pattern, the product rounded on its own and then added to P1; a zero level is written as +0. The value is NaN on every other bar. A neckline that overflows is +Inf or -Inf, not NaN, since the five swing values are finite and the bar differences positive. The rules are those of HeadShouldersBottom: a qualifying pattern is armed on bar tT3 + n, the bar that confirms its right shoulder, the level is written on every armed bar up to and including the bar of the event, the pattern expires without an event on its deadline 2*tT3 - tT1, and a newer qualifying pattern replaces the armed one on its arming bar, so the newest wins. The close-only form is c.HeadShouldersBottomNeckline(c, c, n).
With neckline = c.HeadShouldersBottomNeckline(h, l, n), event = c.HeadShouldersBottom(h, l, n) and target = c.HeadShouldersBottomTarget(h, l, n): the event equals c.GT(neckline) for n >= 1, since the level is extended to every armed bar and the pattern is disarmed after its break; neckline.ValueWhen(event) latches the neckline of each break; the target is NaN wherever the neckline is NaN, and the two NaN footprints coincide wherever no level overflows; and target.Sub(neckline) is the head height H = N(tT2) - T2 to rounding, since the target is N(t) + H rounded and can differ from it in the last bits of N(t). Neither level is -0 on any bar. The bottom is the mirror image of the top: the neckline equals c.Mul(-1).HeadShouldersTopNeckline(l.Mul(-1), h.Mul(-1), n).Mul(-1).Add(0) bit for bit, the final Add(0) turning the negated zero levels back to +0.
No pattern can arm before bar 4*n + 2, so the value is NaN on earlier bars. Unequal lengths of s, h and l return Series{}, checked before n; equal empty inputs return a non-nil empty Series; n < 1 returns all +0. The result is window-local with a data-dependent span: a pattern depends only on the bars from tT1 - n to its last bar, so a slice of the inputs reproduces every pattern whose first window it holds and no other. One pass, O(len(s)) time. Recipes built on the HeadShoulders methods are listed under “Compositions” in the “Chart Patterns” category.
HeadShouldersBottomTarget
func (s Series) HeadShouldersBottomTarget(h, l Series, n int) Series
HeadShouldersBottomTarget holds the neckline plus the head height while a head-and-shoulders bottom is armed, and NaN otherwise.
On every bar where a bottom is armed the value is N(t) + H: the neckline N(t) = P1 + b*(t-tP1) of HeadShouldersBottomNeckline, the product rounded on its own and then added to P1, plus the head height H = N(tT2) - T2, the neckline’s excess over the head at its own bar; a zero level is written as +0. The value is NaN on every other bar. A target that overflows is +Inf or -Inf, and it is NaN on an armed bar only where N(t) and H overflow to opposite infinities, so that the sum cancels; the formula is kept as it stands, with no clamp or repair. The rules are those of HeadShouldersBottom: a qualifying pattern is armed on bar tT3 + n, the bar that confirms its right shoulder, the level is written on every armed bar up to and including the bar of the event, the pattern expires without an event on its deadline 2*tT3 - tT1, and a newer qualifying pattern replaces the armed one on its arming bar, so the newest wins. The close-only form is c.HeadShouldersBottomTarget(c, c, n).
With target = c.HeadShouldersBottomTarget(h, l, n), neckline = c.HeadShouldersBottomNeckline(h, l, n) and event = c.HeadShouldersBottom(h, l, n): target.ValueWhen(event) latches the target of each break; c.Sub(neckline).Add(target) on the event bar is the close-measured target of Table 5.3 of Tsinaslanidis and Zapranis, where their published code measures from the neckline at an interpolated crossing; the target is NaN wherever the neckline is NaN, and the two NaN footprints coincide wherever no level overflows; and target.Sub(neckline) is H to rounding, since the target is N(t) + H rounded and can differ from it in the last bits of N(t), by an amount that varies along an armed run. Neither level is -0 on any bar. The bottom is the mirror image of the top: the target equals c.Mul(-1).HeadShouldersTopTarget(l.Mul(-1), h.Mul(-1), n).Mul(-1).Add(0) bit for bit, the final Add(0) turning the negated zero levels back to +0.
No pattern can arm before bar 4*n + 2, so the value is NaN on earlier bars. Unequal lengths of s, h and l return Series{}, checked before n; equal empty inputs return a non-nil empty Series; n < 1 returns all +0. The result is window-local with a data-dependent span: a pattern depends only on the bars from tT1 - n to its last bar, so a slice of the inputs reproduces every pattern whose first window it holds and no other. One pass, O(len(s)) time. Recipes built on the HeadShoulders methods are listed under “Compositions” in the “Chart Patterns” category.
HeadShouldersTop
func (s Series) HeadShouldersTop(h, l Series, n int) Series
HeadShouldersTop reports 1 on the bar whose close first breaks below the neckline of a confirmed head-and-shoulders top, and 0 otherwise.
The receiver s holds the closes (c in the recipes below), which are tested against the neckline; h and l hold the series whose confirmed swings form the pattern, usually the highs and the lows; n is the pivot half-window of the swings. The swing highs of h are the strict bar-count pivots that h.SwingHighEvent(n) confirms and the swing lows of l those of l.SwingLowEvent(n): a pivot is strictly beyond every other value of its window of n bars on each side, a tie or a NaN anywhere in the window suppresses the swing, +Inf and -Inf are ordered values, and a swing is confirmed n bars after its pivot. The close-only form c.HeadShouldersTop(c, c, n) runs the detector on the closes alone, where the swings are exactly the rolling-window extrema of Tsinaslanidis and Zapranis (Technical Analysis for Algorithmic Pattern Recognition, 2016: the RW function of their chapter 2, which their head-and-shoulders code of chapter 5 calls) with their w = n, a window of 2n + 1 bars, dated n bars later.
The confirmed swings are merged in order of their pivot bars, the high first when a bar holds both. Whenever a swing high is confirmed, the last five merged swings are read as P1, T1, P2, T2, P3 at bars tP1 < tT1 < tP2 < tT2 < tP3, and they form a top when their kinds alternate high, low, high, low, high, the bars strictly increase (so no two of the five share a bar), the five values are finite, and the conditions of Osler and Chang (1995) and Lucke (2003), as Tsinaslanidis and Zapranis print and code them, hold: the head P2 > P1 and P2 > P3; the balance P1 >= 0.5*(P3+T2) and P3 >= 0.5*(P1+T1), inclusive, with each sum rounded before it is halved, so that a sum that overflows to +Inf fails and one that overflows to -Inf passes; and the symmetry 2*(tP2-tP1) < 5*(tP3-tP2) and 2*(tP3-tP2) < 5*(tP2-tP1) in whole bars, their ratio of 2.5. The trend before the pattern is not tested, as in their code. A qualifying pattern is armed on bar tP3 + n, the bar that confirms its right shoulder, and replaces any pattern still armed: the newest wins, where their code records overlapping patterns separately. A later swing that does not qualify leaves the armed pattern alone.
The neckline runs through the two troughs: N(t) = T1 + b*(t-tT1) with b = (T2-T1)/(tT2-tT1), the product rounded on its own and then added to T1. The head height is H = P2 - N(tP2). On every bar, in this order, the swings confirmed on the bar are merged, a qualifying pattern is armed, an armed pattern whose deadline 2*tP3 - tP1 has arrived is disarmed without an event (the deadline is strict, where their code includes that bar), and then the close is tested: a close strictly below N(t) reports 1 and disarms the pattern. The test is a level test, not a fresh cross: a close below the neckline between tP3 and the arming bar is not seen, and a close still below it on tP3 + n reports the event on that bar. Their code searches the break from tP3 + 1, which is look-ahead, and their reported tests found the swings over the whole history. No other rule invalidates an armed pattern. A NaN close neither breaks nor disarms; a NaN in h or l suppresses the swings whose windows hold it and does not touch an armed pattern, whose values are stored; a pattern with an infinite swing value does not qualify.
HeadShouldersTopNeckline and HeadShouldersTopTarget hold N(t) and N(t) - H on every armed bar, so with neckline = c.HeadShouldersTopNeckline(h, l, n) the event equals c.LT(neckline) for n >= 1 (n < 1 makes every view +0), and with event = c.HeadShouldersTop(h, l, n) the neckline of each break is neckline.ValueWhen(event). On a bar that confirms a swing high, a top arms exactly when P3, P2 and P1 read from h.SwingHighValue(n), h.SwingHighValue(n, 1) and h.SwingHighValue(n, 2), T2 and T1 from l.SwingLowValue(n) and l.SwingLowValue(n, 1), and their bars from the matching ages, with l.SwingLowAge(n, 2) NaN or older than P1, pass the conditions above; on such a bar the level views hold N(t) and N(t) - H from these values. The mirror image is HeadShouldersBottom: c.Mul(-1).HeadShouldersTop(l.Mul(-1), h.Mul(-1), n) equals c.HeadShouldersBottom(h, l, n) bit for bit.
No pattern can arm before bar 4*n + 2, so the result is +0 on earlier bars, and it contains no NaN. Unequal lengths of s, h and l return Series{}, checked before n; equal empty inputs return a non-nil empty Series; n < 1 returns all +0. The result is window-local with a data-dependent span: a pattern depends only on the bars from tP1 - n to its last bar, so a slice of the inputs reproduces every pattern whose first window it holds and no other. One pass, O(len(s)) time.
Evidence: mixed. Osler and Chang (1995) set out the criteria on daily dollar exchange rates from 1973 to 1994, Chang and Osler (1999) found the rule profitable in the mark and the yen but not in four other currencies, Lucke (2003) found returns on five currencies that were not significantly positive, Osler (1998) found it unprofitable in US stocks, and Tsinaslanidis and Zapranis report the detector finding a top in about 22% of its runs on simulated geometric Brownian motion paths; the published tests include look-ahead. No trading edge is demonstrated. No stop is defined by the sources; the usual stop above the right shoulder or the head needs P3 or P2, which these views do not expose. Recipes built on the HeadShoulders methods are listed under “Compositions” in the “Chart Patterns” category.
HeadShouldersTopNeckline
func (s Series) HeadShouldersTopNeckline(h, l Series, n int) Series
HeadShouldersTopNeckline holds the neckline of the armed head-and-shoulders top at each bar, and NaN when none is armed.
On every bar where a top is armed the value is N(t) = T1 + b*(t-tT1) with b = (T2-T1)/(tT2-tT1), the line through the two troughs of the pattern, the product rounded on its own and then added to T1; a zero level is written as +0. The value is NaN on every other bar. A neckline that overflows is +Inf or -Inf, not NaN, since the five swing values are finite and the bar differences positive. The rules are those of HeadShouldersTop: a qualifying pattern is armed on bar tP3 + n, the bar that confirms its right shoulder, the level is written on every armed bar up to and including the bar of the event, the pattern expires without an event on its deadline 2*tP3 - tP1, and a newer qualifying pattern replaces the armed one on its arming bar, so the newest wins. The close-only form is c.HeadShouldersTopNeckline(c, c, n).
With neckline = c.HeadShouldersTopNeckline(h, l, n), event = c.HeadShouldersTop(h, l, n) and target = c.HeadShouldersTopTarget(h, l, n): the event equals c.LT(neckline) for n >= 1, since the level is extended to every armed bar and the pattern is disarmed after its break; neckline.ValueWhen(event) latches the neckline of each break; the target is NaN wherever the neckline is NaN, and the two NaN footprints coincide wherever no level overflows; and neckline.Sub(target) is the head height H = P2 - N(tP2) to rounding, since the target is N(t) - H rounded and can differ from it in the last bits of N(t). Neither level is -0 on any bar.
No pattern can arm before bar 4*n + 2, so the value is NaN on earlier bars. Unequal lengths of s, h and l return Series{}, checked before n; equal empty inputs return a non-nil empty Series; n < 1 returns all +0. The result is window-local with a data-dependent span: a pattern depends only on the bars from tP1 - n to its last bar, so a slice of the inputs reproduces every pattern whose first window it holds and no other. One pass, O(len(s)) time. Recipes built on the HeadShoulders methods are listed under “Compositions” in the “Chart Patterns” category.
HeadShouldersTopTarget
func (s Series) HeadShouldersTopTarget(h, l Series, n int) Series
HeadShouldersTopTarget holds the neckline less the head height while a head-and-shoulders top is armed, and NaN otherwise.
On every bar where a top is armed the value is N(t) - H: the neckline N(t) = T1 + b*(t-tT1) of HeadShouldersTopNeckline, the product rounded on its own and then added to T1, less the head height H = P2 - N(tP2), the head’s excess over the neckline at its own bar; a zero level is written as +0. The value is NaN on every other bar. A target that overflows is +Inf or -Inf, and it is NaN on an armed bar only where N(t) and H overflow to the same infinity, so that the difference cancels; the formula is kept as it stands, with no clamp or repair. The rules are those of HeadShouldersTop: a qualifying pattern is armed on bar tP3 + n, the bar that confirms its right shoulder, the level is written on every armed bar up to and including the bar of the event, the pattern expires without an event on its deadline 2*tP3 - tP1, and a newer qualifying pattern replaces the armed one on its arming bar, so the newest wins. The close-only form is c.HeadShouldersTopTarget(c, c, n).
With target = c.HeadShouldersTopTarget(h, l, n), neckline = c.HeadShouldersTopNeckline(h, l, n) and event = c.HeadShouldersTop(h, l, n): target.ValueWhen(event) latches the target of each break; c.Sub(neckline).Add(target) on the event bar is the close-measured target of Table 5.3 of Tsinaslanidis and Zapranis, where their published code measures from the neckline at an interpolated crossing; the target is NaN wherever the neckline is NaN, and the two NaN footprints coincide wherever no level overflows; and neckline.Sub(target) is H to rounding, since the target is N(t) - H rounded and can differ from it in the last bits of N(t), by an amount that varies along an armed run. Neither level is -0 on any bar.
No pattern can arm before bar 4*n + 2, so the value is NaN on earlier bars. Unequal lengths of s, h and l return Series{}, checked before n; equal empty inputs return a non-nil empty Series; n < 1 returns all +0. The result is window-local with a data-dependent span: a pattern depends only on the bars from tP1 - n to its last bar, so a slice of the inputs reproduces every pattern whose first window it holds and no other. One pass, O(len(s)) time. Recipes built on the HeadShoulders methods are listed under “Compositions” in the “Chart Patterns” category.
RoundingBottom
func (s Series) RoundingBottom(n, minWidth int, boundFraction, minFitFraction float64) Series
RoundingBottom reports 1 on the bar whose close first regains a confirmed swing high of the closes after a segment that fits Tsinaslanidis and Zapranis’s semicircle band, and 0 otherwise.
This is the rounding bottom, or saucer, of Tsinaslanidis and Zapranis (2016), Technical Analysis for Algorithmic Pattern Recognition, chapter 6, as their Matlab function RBottoms and its sub-function RBcircles compute it, evaluated bar by bar. The receiver holds the closes.
P1 is a strict swing high of the closes with half-window n: its close is above every close in the n bars either side (a tie is no swing high), and it is confirmed n bars later, where c.SwingHighEvent(n) reads 1 (their rolling window with w = n). P2 is the first later bar whose close is at least the close at P1, as the code’s >= has it, where the text says the price overcomes the level. Every close between P1 and P2 is below the close at P1 or NaN, so P2 comes at least n + 1 bars after P1, the pattern is decided on P2 from confirmed data only, and nothing repaints.
The width is W = P2 - P1 + 1, and the pattern needs W > minWidth, as the code tests it, where the text asks for at least 15 days at a threshold of 15: a width of exactly 15 is rejected.
The closes from P1 to P2 are scaled to (W/2 - 1)(y - min)/(max - min) + 1, evaluated left to right, so that the lowest close maps to 1 and the close at P2, the highest, to W/2 in exact arithmetic: the depth is half the width. An isosceles triangle joins A and C, at the offsets 0 and W - 1 from P1 and the mean height of the scaled ends, to B, midway between them at height 1. The circle through the three has the radius R = a/(2 sin A), with a the side BC and A the angle at A, and its centre at offset (W - 1)/2 and height Ky = 1 + R.
The band lies between the lower semicircles of radius (1 - boundFraction)R and (1 + boundFraction)R about that centre: at offset dx from the centre the semicircle of radius r is at Ky - sqrt(r^2 - dx^2), and at Ky when r^2 < dx^2. The smaller circle gives the upper edge and the larger the lower edge; a scaled close on either edge is inside (the edges are inclusive), and the pattern is accepted when inside/W >= minFitFraction, also inclusive. The arcs are written in this stable form rather than as the root of the printed quadratic, which differs from it only in rounding, and the middle circle the book draws is not used.
Each swing high is evaluated once, on its own P2, and is consumed there whether or not its pattern is accepted: a later close at or above its level does not revive it. A close can regain several swing highs at once; the bar then reports one event, for the widest accepted pattern, and a wider regained pattern that fails does not hide a narrower one that fits.
Missing data: a NaN close suppresses every swing high whose window of n bars either side holds it. A NaN close regains nothing. Infinite closes are ordered values for the swing highs and the regain, and a segment from P1 to P2 that holds a NaN, +Inf or -Inf is rejected, its swing high consumed all the same.
Defaults: the book’s empirical tables use w = 10, a width threshold of 15, a bound of 0.3 and a fit of 0.9, that is c.RoundingBottom(10, 15, 0.3, 0.9); the IBM example uses a bound of 0.2. n and minWidth are bars, and boundFraction and minFitFraction fractions: 0.3 is a band of 30% of R on each side of the circle, 0.9 is nine closes in ten.
The band is loose at the default bound: a V and a flat-bottomed box with four-bar walls, 40 bars wide, both fit perfectly at 0.3 (all 40 closes inside), as a parabola does. At the IBM example’s bound of 0.2 the V fits 0.5 and the box 0.85, so both fail at a fit of 0.9, while the parabola still fits 1. So a pattern found at the defaults is not necessarily round, and a bound of 0.2 separates the shapes better.
If n < 1, boundFraction is NaN, negative or +Inf, or minFitFraction is NaN or infinite, the result is all +0. Every minWidth is valid, and below 3 it sets no threshold, since W >= n + 2 >= 3; a boundFraction of 0 or -0 counts only the closes exactly on the circle, bounds of 1 or more apply as written, and an n above (len(c) - 1)/2 finds no swing high. An empty receiver gives an empty result.
Warm-up: P1 >= n, P2 >= P1 + n + 1 and W > minWidth, so no event comes before bar max(2n + 1, n + minWidth), and the bars before it read 0. Causal: the output on bar t uses bars 0 to t only, and every prefix of the input gives the first values of the full result. A pattern reads the bars from P1 - n to P2, a span that depends on the data, so a slice that starts later can lose a wide pattern and report a narrower one on the same bar; no fixed amount of history makes a slice agree with the full run.
Cost: the swing highs cost O(len(c)), and each regained swing high wider than minWidth costs O(W) once. The width has no cap, as in RBottoms, so the worst case is quadratic: a staircase decline with a swing high on every step, all regained by one bar, when every pattern is rejected.
Evidence: the authors compared how often the patterns reached their target, ran out of time or were stopped on NYSE and NASDAQ components with the same frequencies on simulated GBM paths: NYSE rounding bottoms did better than the simulations, while rounding tops and NASDAQ bottoms were mixed, all before costs. No profitability is claimed.
RoundingBottomWidth and RoundingBottomLow give the width and the lowest close of the same pattern, and RoundingTop is the mirror image; the depth, the target, the stop level and the time limit are recipes in the manual’s “Chart Patterns” section. Pure: the receiver is not modified, the result is a new Series, and concurrent calls on one receiver are safe.
RoundingBottomLow
func (s Series) RoundingBottomLow(n, minWidth int, boundFraction, minFitFraction float64) Series
RoundingBottomLow holds the lowest close of the rounding bottom completed on this bar, and NaN otherwise.
The receiver holds the closes. On the event bar of RoundingBottom with the same arguments the result is the lowest close from P1 to P2 of the widest accepted pattern, copied verbatim from the first bar at the minimum, so that of a -0 and a +0 the one that comes first is reported. On every other bar it is NaN rather than 0, since 0 can be a close; low.ValueWhen(event) holds the last pattern’s low. On the event bar the value equals c.LowestVariable(width, 1, len(c)), with width from RoundingBottomWidth (as values: LowestVariable takes -0 for a tie of zeros).
Depth and target, the book’s (6.5) and its price target: on the event bar the close is that at P2, the higher end of the pattern, so with low this result the depth is c.Sub(low).Div(low) and the target c.Add(c.Sub(low)); the book writes the target as Pmax + depth*Pmin, the same in exact arithmetic.
If n < 1, boundFraction is NaN, negative or +Inf, or minFitFraction is NaN or infinite, the result is all +0. Warm-up: no low comes before bar max(2n + 1, n + minWidth), and the bars before it read NaN. Causal: the output on bar t uses bars 0 to t only. Recipes are in the manual’s “Chart Patterns” section. Pure: the receiver is not modified and the result is a new Series.
RoundingBottomWidth
func (s Series) RoundingBottomWidth(n, minWidth int, boundFraction, minFitFraction float64) Series
RoundingBottomWidth holds the width in bars of the rounding bottom completed on this bar, and 0 otherwise.
The receiver holds the closes. The pattern is that of RoundingBottom with the same arguments: on its event bar the result is W = P2 - P1 + 1, from the swing high P1 to the bar P2 whose close first regains it, for the widest accepted pattern completed on that bar, and W > minWidth. So RoundingBottom is 1 exactly where the width is above 0.
The swing high is on bar t - W + 1, and c.ShiftVariable(width.Sub(1)) reads its close on the event bar: the level of the authors’ stop rule (their tn), to latch with ValueWhen. W is also the authors’ time limit for a trade, in bars. With k the number of swing highs confirmed on bars P1 + n + 1 to t, the width is c.SwingHighAge(n, k) + 1 on the event bar, and c.SwingHighValue(n, k) is the close at P1.
The width threshold only masks: with free := c.RoundingBottomWidth(n, 0, boundFraction, minFitFraction), the result equals free.Mul(free.GT(minWidth)) for every minWidth: a pattern too narrow for the threshold makes every narrower one too narrow as well. The fit threshold is not a mask: raising it can turn the widest pattern down and leave a narrower one.
If n < 1, boundFraction is NaN, negative or +Inf, or minFitFraction is NaN or infinite, the result is all +0. Warm-up: no width comes before bar max(2n + 1, n + minWidth), and the bars before it read 0. Causal: the output on bar t uses bars 0 to t only. The time limit and the stop level are recipes in the manual’s “Chart Patterns” section. Pure: the receiver is not modified and the result is a new Series.
RoundingTop
func (s Series) RoundingTop(n, minWidth int, boundFraction, minFitFraction float64) Series
RoundingTop reports 1 on the bar whose close first falls back to a confirmed swing low of the closes after a segment that fits the mirror image of Tsinaslanidis and Zapranis’s semicircle band, and 0 otherwise.
The receiver holds the closes. The rounding top is the rounding bottom of Tsinaslanidis and Zapranis (2016) run on the negated closes, bit for bit: the result equals c.Mul(-1).RoundingBottom(n, minWidth, boundFraction, minFitFraction). The book prints only the bottom’s code and calls the top a mirror process. In the top’s own terms P1 is a strict swing low of the closes, confirmed n bars later where c.SwingLowEvent(n) reads 1, and P2 is the first later bar whose close is at most the close at P1. The width W = P2 - P1 + 1 must exceed minWidth, the negated closes from P1 to P2 are scaled and fitted to the band as RoundingBottom describes, each swing low is consumed on its P2 whether or not its pattern is accepted, and a bar that falls back to several swing lows reports the widest accepted pattern once. A NaN close falls back to nothing, and a segment that holds a NaN or an infinity is rejected.
Defaults: c.RoundingTop(10, 15, 0.3, 0.9), from the book’s empirical tables, with the same caveat on the bound: at 0.3 the band also admits an inverted V and an inverted flat-topped box, which a bound of 0.2 rejects. In the authors’ test against simulated GBM paths, rounding tops gave mixed results, before costs. No profitability is claimed.
If n < 1, boundFraction is NaN, negative or +Inf, or minFitFraction is NaN or infinite, the result is all +0. Warm-up: no event comes before bar max(2n + 1, n + minWidth), and the bars before it read 0. Causal: the output on bar t uses bars 0 to t only, and nothing repaints. RoundingTopWidth and RoundingTopHigh give the width and the highest close of the same pattern; the depth and the target are recipes in the manual’s “Chart Patterns” section. Pure: the receiver is not modified and the result is a new Series.
RoundingTopHigh
func (s Series) RoundingTopHigh(n, minWidth int, boundFraction, minFitFraction float64) Series
RoundingTopHigh holds the highest close of the rounding top completed on this bar, and NaN otherwise.
The receiver holds the closes. On the event bar of RoundingTop with the same arguments the result is the highest close from P1 to P2 of the widest accepted rounding top, copied verbatim from the first bar at the maximum, so that of a +0 and a -0 the one that comes first is reported. On every other bar it is NaN rather than 0, since 0 can be a close. For valid arguments the result equals c.Mul(-1).RoundingBottomLow(n, minWidth, boundFraction, minFitFraction).Mul(-1) bit for bit, NaN where NaN.
Depth and target: on the event bar the close is that at P2, the lower end of the pattern, so with high this result the depth is high.Sub(c).Div(c) and the target c.Sub(high.Sub(c)), the book’s Pmin - depth*Pmin for a top.
If n < 1, boundFraction is NaN, negative or +Inf, or minFitFraction is NaN or infinite, the result is all +0. Warm-up: no high comes before bar max(2n + 1, n + minWidth), and the bars before it read NaN. Causal: the output on bar t uses bars 0 to t only. Recipes are in the manual’s “Chart Patterns” section. Pure: the receiver is not modified and the result is a new Series.
RoundingTopWidth
func (s Series) RoundingTopWidth(n, minWidth int, boundFraction, minFitFraction float64) Series
RoundingTopWidth holds the width in bars of the rounding top completed on this bar, and 0 otherwise.
The receiver holds the closes. On the event bar of RoundingTop with the same arguments the result is the width W = P2 - P1 + 1 of the widest accepted rounding top completed there, from the confirmed swing low P1 to the bar P2 whose close first falls back to it, and W > minWidth; RoundingTop is 1 exactly where the width is above 0. It equals c.Mul(-1).RoundingBottomWidth(n, minWidth, boundFraction, minFitFraction) bit for bit, and the threshold only masks, as for RoundingBottomWidth. c.ShiftVariable(width.Sub(1)) reads the close at P1 on the event bar, the level of the authors’ stop rule, and W is the authors’ time limit.
If n < 1, boundFraction is NaN, negative or +Inf, or minFitFraction is NaN or infinite, the result is all +0. Warm-up: no width comes before bar max(2n + 1, n + minWidth), and the bars before it read 0. Causal: the output on bar t uses bars 0 to t only. The time limit and the stop level are recipes in the manual’s “Chart Patterns” section. Pure: the receiver is not modified and the result is a new Series.
TripleBottom
func (s Series) TripleBottom(h, l Series, n int, bandMinFraction, bandMaxFraction, maxSpreadFraction float64) Series
TripleBottom reports 1 on the bar whose close first exceeds the neckline of a pending triple bottom, and 0 otherwise.
The receiver holds the closes, and h and l are its highs and lows. The swings are those of l.SwingLowEvent(n): a swing low is a bar whose low is strictly below every other low of the n bars on each side, confirmed n bars later, when its whole window is known, so nothing repaints. On the bar t = b3 + n that confirms the swing low at bar b3, the two swing lows confirmed before it, at bars b1 < b2 < b3, complete the triple (b2 - b1 > n and b3 - b2 > n). With T1, T2 and T3 the three lows, ext = min(T1, T3), P1 the highest high strictly between b1 and b2 and P2 the highest high strictly between b2 and b3 (the bottom bars’ own highs are left out, and the peaks need not be confirmed swing highs), the triple qualifies when every high and low from b1 to b3 is finite; ext > 0; the band r = (T3 - T1)/ext satisfies bandMinFraction <= r <= bandMaxFraction, both bounds inclusive, signed; every low strictly between b1 and b3, T2 included, is at least ext (an equal low passes); P2 > 0, P2 <= P1 and the spread (P1 - P2)/P2 is at most maxSpreadFraction, inclusive, over the second peak as their eq. 5.22 prints it, not over the neckline; and 2*(b2-b1) < 5*(b3-b2) and 2*(b3-b2) < 5*(b2-b1), both strict, in whole bars. The fractions are fractions of price, not percentages: 0.04 is 4%. The ratios are not symmetric under negation, so a triple bottom is not a triple top of negated data, and the two machines are independent: this view reads only the swings of l, and TripleTop only those of h.
A qualifying triple arms on its confirmation bar t = b3 + n and becomes the pending pattern, replacing any pending one (the newest wins); a swing low whose triple does not qualify changes nothing, though it is a bottom of later triples. On each bar, in this order: a pending pattern whose age reaches its own span, t - b3 >= b3 - b1, expires, so a signal needs t - b3 < b3 - b1; a qualifying triple arms; then the close is tested against the neckline P1, the first peak: a close strictly above P1 is the signal, and the pattern ends; otherwise it stays pending. There is no void: no close ends a pattern without a signal, and only the expiry and a replacement do. A NaN close does not signal; a +Inf close does, a -Inf close does not. The test is a level, not a fresh cross, and closes before the arming bar are not read: a close above P1 between the third bottom and its confirmation is not a signal, and the pattern signals on the arming bar only if the close is still above P1 there, or later on the first close above P1. The arming bar is itself tested, so a setup bar can also be a signal bar. On every bar the signal equals s.GT(neck), with neck the neckline of TripleBottomNeckline.
Source: Tsinaslanidis and Zapranis (2016), the triple bottom of their chapter 5, with the departures of TripleTop mirrored: the band over the outer extreme ext, which for the bottoms coincides with their base; the between rule over every low strictly between b1 and b3, where they test T2 alone; the peaks as the highest highs of the two spans, not their rolling-window peaks; and the break read from the arming bar. Their trend condition (eq. 5.16) is a gate; the recipes are in the manual’s “Chart Patterns” section. Their close-only form with w = 15 is c.TripleBottom(c, c, 15, -0.04, 0.04, 0.04), and their band and peak spread of 4% are a bandMinFraction of -0.04, a bandMaxFraction of 0.04 and a maxSpreadFraction of 0.04. The resistance is horizontal at P1, the target is that of TripleBottomTarget, and the source defines no stop, so there is no stop view. Witness (synthetic, n = 2): bottoms 100, 101 and 102 on bars 5, 13 and 21 under peaks 110 and 108 on bars 9 and 17 arm on bar 23 with neckline 110, height 10 and target 120, and signal on bar 26 with a close of 112.
Relations: an inverse head and shoulders needs T2 below both outer bottoms, so HeadShouldersBottom and this view do not arm on the same three swing lows. Where TripleBottomSetup and DoubleBottomSetup arm on the same bar, the neckline of the triple is at or above that of the double bottom of DoubleBottom, which is P2, and neither set of setups contains the other. The point-and-figure triple bottom, which PointAndFigureSignal does not build, is a boxed construction, not this one.
Evidence: none for the bottoms. Tsinaslanidis and Zapranis report 12 triple bottoms in NASDAQ series and 67 in NYSE series, daily closes from 1990 to 2010, with look-ahead pivots and no costs, but their long-side rule was stopped out 91.67% and 97.01% of the time on real paths and 100% and 99.9% on simulated ones, the defect of their double bottoms, so those rows say nothing about the pattern, let alone about this method. No profitability is claimed.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. When n < 1, bandMinFraction or bandMaxFraction is NaN or infinite, bandMinFraction > bandMaxFraction, or maxSpreadFraction is NaN, infinite or negative, the result is all +0. A huge n is valid and confirms no swing. Prices are not validated: a non-positive ext or P2 simply fails its rule. A NaN low suppresses the swing lows whose window holds it, and a triple whose span from b1 to b3 holds a NaN or an infinity in h or l does not qualify; a NaN or an infinity after the third bottom changes nothing for that pattern, except through later swings.
Warm-up: no arming bar is before bar 4n + 2 (the first bottom is at bar n or later and the swings are more than n bars apart), so the result is +0 on bars 0 to 4n + 1; +0 is the true “no event” value.
Causal: the output on bar t uses bars 0 to t only, and every prefix of the inputs gives the first values of the result. The view is path-dependent: a triple reads the latest swing low and the two before it, so a slice of the inputs from bar k on loses the patterns whose first bottom is before bar k + n and changes nothing else. Cost: O(len(s)) whatever n. Pure: the inputs are not modified, aliased arguments included, and the result is a new Series that shares no storage with them.
TripleBottomNeckline
func (s Series) TripleBottomNeckline(h, l Series, n int, bandMinFraction, bandMaxFraction, maxSpreadFraction float64) Series
TripleBottomNeckline holds the first peak of the triple bottom under test, the highest high between its first two bottoms, and NaN when none is under test.
The receiver holds the closes, and h and l are its highs and lows. A triple bottom, armed by the rules of TripleBottom, is under test from its arming bar t = b3 + n through the bar whose close signals it; it is not under test on the bar on which it expires (t - b3 >= b3 - b1, checked before that bar’s close) or on any later bar, and once a newer qualifying triple replaces it the newer pattern is under test from its own arming bar on. The value is P1, the highest high strictly between the first two bottoms, copied verbatim and constant along each run under test: the resistance is horizontal at P1, not a line through P1 and P2. On a bar under test the close is at or below the neckline, or NaN, except on the signal bar, so the signal of TripleBottom equals s.GT(neck) on every bar, with neck this view; neck.ValueWhen(sig) latches the neckline of each break, with sig that signal, and neck.EQ(neck) is 1 exactly on the bars under test. Where TripleBottomSetup and DoubleBottomSetup arm on the same bar this neckline is at or above DoubleBottomNeckline, the highest high between the last two bottoms, P2. The recipes are in the manual’s “Chart Patterns” section.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. When n < 1, bandMinFraction or bandMaxFraction is NaN or infinite, bandMinFraction > bandMaxFraction, or maxSpreadFraction is NaN, infinite or negative, the result is all +0, not NaN. A huge n is valid, confirms no swing and gives all NaN.
Warm-up: NaN rather than 0 until the first pattern arms, on bar 4n + 2 at the earliest, because the start depends on the data; NaN again on every later bar with no pattern under test, the same bars on which TripleBottomTarget is NaN.
Causal: the output on bar t uses bars 0 to t only. The view is path-dependent, with the slice rule of TripleBottom: a slice from bar k on reads NaN where the lost pattern, whose first bottom is before bar k + n, was under test. Cost: O(len(s)) whatever n. Pure: the inputs are not modified and the result is a new Series.
TripleBottomSetup
func (s Series) TripleBottomSetup(h, l Series, n int, bandMinFraction, bandMaxFraction, maxSpreadFraction float64) Series
TripleBottomSetup reports 1 on the bar that confirms a swing low forming a triple bottom with the two swing lows before it, and 0 otherwise.
The receiver holds the closes, and h and l are its highs and lows. The arming bar of a triple bottom is the bar t = b3 + n on which l.SwingLowEvent(n) confirms the third bottom, at bar b3, and its triple with the two swing lows confirmed before it qualifies under the rules of TripleBottom. The bar arms even when its own close signals there, so a setup bar can also be a signal bar, and a qualifying triple replaces any pending pattern, the newest winning. A swing low whose triple does not qualify arms nothing.
When every high and low is finite the view is a recipe over the swing views, with a, b and p the three fractions: ev := l.SwingLowEvent(n); T3, T2, T1 := l.SwingLowValue(n), l.SwingLowValue(n, 1), l.SwingLowValue(n, 2); k := l.SwingLowAge(n, 1).Sub(float64(n)), the bars from b2 to b3, and d := l.SwingLowAge(n, 2).Sub(float64(n)).Sub(k), the bars from b1 to b2; V := l.Shift(n+1).LowestVariable(k.Sub(1), 1, len(l)) and U := h.Shift(n+1).HighestVariable(k.Sub(1), 1, len(h)) cover the bars strictly between the last two bottoms; P2, P1 := U, U.ShiftVariable(k); lo := V.ShiftVariable(k).Minimum(T2).Minimum(V); ext := T1.Minimum(T3); r := T3.Sub(T1).Div(ext); sp := P1.Sub(P2).Div(P2); sym := d.Mul(2).LT(k.Mul(5)).Mul(k.Mul(2).LT(d.Mul(5))); and the setup is ev multiplied, with Mul, by r.GTE(a), r.LTE(b), lo.GTE(ext), P2.LTE(P1), sp.LTE(p), sym, ext.GT(0) and P2.GT(0). The method adds the finite-span rule and runs in one pass.
Relations: DoubleBottomSetup also arms only on bars where l.SwingLowEvent(n) is 1; where both arm on the same bar the neckline of the triple is at or above the neckline of the double, and neither set of setups contains the other. Age: setup.BarsSince() counts the bars since the arming bar; on a signal bar it reads the age of the signalled pattern. Gate: a filter series g, read on the arming bar, gates the signal sig of TripleBottom as sig.Mul(g.ShiftVariable(setup.BarsSince()).GT(0)); the GT(0) is needed because before the first setup setup.BarsSince() is NaN, the shifted gate is NaN there and 0 times NaN is NaN. A gate filters signals only: a triple that fails it still arms and replaces a pending pattern. The gates (their trend condition, a slope at the first bottom, a height) are recipes in the manual’s “Chart Patterns” section.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. When n < 1, bandMinFraction or bandMaxFraction is NaN or infinite, bandMinFraction > bandMaxFraction, or maxSpreadFraction is NaN, infinite or negative, the result is all +0. A huge n is valid and confirms no swing. A triple whose span from b1 to b3 holds a NaN or an infinity in h or l does not qualify.
Warm-up: no arming bar is before bar 4n + 2, so the result is +0 on bars 0 to 4n + 1.
Causal: the output on bar t uses bars 0 to t only. The view is path-dependent, with the slice rule of TripleBottom: a slice from bar k on loses the setups whose first bottom is before bar k + n and changes nothing else. Cost: O(len(s)) whatever n. Pure: the inputs are not modified and the result is a new Series.
TripleBottomTarget
func (s Series) TripleBottomTarget(h, l Series, n int, bandMinFraction, bandMaxFraction, maxSpreadFraction float64) Series
TripleBottomTarget holds the neckline plus the height of the triple bottom under test, and NaN when none is under test.
The receiver holds the closes, and h and l are its highs and lows. The value is P1 + (P1 - ext), with P1 the neckline of TripleBottomNeckline and ext = min(T1, T3) the lower outer bottom of the triple bottom under test, armed by the rules of TripleBottom: the height H = P1 - ext is rounded first and then P1 + H, two roundings in that order, which is the price target PT = P1 + H of Table 5.3 of Tsinaslanidis and Zapranis (2016). It is held on exactly the bars on which TripleBottomNeckline holds the neckline, constant along each run under test, and NaN elsewhere. The target is not -0; it can be +Inf when the sum overflows, and is kept as it stands. target.ValueWhen(sig) latches the target of each break, with sig the signal of TripleBottom; their text’s target from the break price, B + H, is c.Sub(neck).Add(target) on the signal bar, equal to the close plus the height to rounding, and the height itself is target.Sub(neck), H to rounding. The height needs the outer extreme, which no other view holds, so the target is a view; the source defines no stop, so there is no stop view, and a void in Bulkowski’s style is a recipe in the manual’s “Chart Patterns” section.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. When n < 1, bandMinFraction or bandMaxFraction is NaN or infinite, bandMinFraction > bandMaxFraction, or maxSpreadFraction is NaN, infinite or negative, the result is all +0, not NaN. A huge n is valid, confirms no swing and gives all NaN.
Warm-up: NaN rather than 0 until the first pattern arms, on bar 4n + 2 at the earliest, because the start depends on the data; NaN again on every later bar with no pattern under test, the same bars on which TripleBottomNeckline is NaN.
Causal: the output on bar t uses bars 0 to t only. The view is path-dependent, with the slice rule of TripleBottom: a slice from bar k on reads NaN where the lost pattern, whose first bottom is before bar k + n, was under test. Cost: O(len(s)) whatever n. Pure: the inputs are not modified and the result is a new Series.
TripleTop
func (s Series) TripleTop(h, l Series, n int, bandMinFraction, bandMaxFraction, maxSpreadFraction float64) Series
TripleTop reports 1 on the bar whose close first falls below the neckline of a pending triple top, and 0 otherwise.
The receiver holds the closes, and h and l are its highs and lows. The swings are those of h.SwingHighEvent(n): a swing high is a bar whose high is strictly above every other high of the n bars on each side, confirmed n bars later, when its whole window is known, so nothing repaints. On the bar t = b3 + n that confirms the swing high at bar b3, the two swing highs confirmed before it, at bars b1 < b2 < b3, complete the triple (b2 - b1 > n and b3 - b2 > n). With P1, P2 and P3 the three peaks, ext = max(P1, P3), T1 the lowest low strictly between b1 and b2 and T2 the lowest low strictly between b2 and b3 (the peak bars’ own lows are left out, and the troughs need not be confirmed swing lows), the triple qualifies when every high and low from b1 to b3 is finite; ext > 0; the band r = (P3 - P1)/ext satisfies bandMinFraction <= r <= bandMaxFraction, both bounds inclusive, signed (r < 0 when the third peak is the lower outer peak); every high strictly between b1 and b3, P2 included, is at most ext (an equal high passes); T1 > 0, T2 >= T1 and the spread (T2 - T1)/T1 is at most maxSpreadFraction, inclusive; and 2*(b2-b1) < 5*(b3-b2) and 2*(b3-b2) < 5*(b2-b1), both strict, in whole bars (the published ratio 2.5 is a constant, not a parameter). The fractions are fractions of price, not percentages: 0.04 is 4%. Each ratio is one subtraction and one division.
A qualifying triple arms on its confirmation bar t = b3 + n and becomes the pending pattern, replacing any pending one (the newest wins); a swing high whose triple does not qualify changes nothing, though it is a peak of later triples. On each bar, in this order: a pending pattern whose age reaches its own span, t - b3 >= b3 - b1, expires, so a signal needs t - b3 < b3 - b1; a qualifying triple arms; then the close is tested against the neckline T1, the first trough: a close strictly below T1 is the signal, and the pattern ends; otherwise it stays pending. There is no void: no close ends a pattern without a signal, and only the expiry and a replacement do. A NaN close does not signal; a -Inf close does, a +Inf close does not. The test is a level, not a fresh cross, and closes before the arming bar are not read: a close below T1 between the third peak and its confirmation is not a signal, and the pattern signals on the arming bar only if the close is still below T1 there, or later on the first close below T1. The arming bar is itself tested, so a setup bar can also be a signal bar. On every bar the signal equals s.LT(neck), with neck the neckline of TripleTopNeckline.
Source: Tsinaslanidis and Zapranis (2016), the triple top of their chapter 5, with four departures: the band is taken over the higher outer peak, max(P1, P3), where they divide by the lower one; the between rule reads every high strictly between b1 and b3, where they test P2 alone; the troughs are the lowest lows of the two spans, not their rolling-window troughs; and the break is read from the arming bar, not from the bar after the third peak, which comes before the peak can be confirmed. Their trend condition (eq. 5.15) is a gate, not a rule; the recipes are in the manual’s “Chart Patterns” section. Their close-only form with w = 15 is c.TripleTop(c, c, 15, -0.04, 0.04, 0.04), whose pivots are their rolling-window peaks dated 15 bars later. Their band and trough spread of 4% are a bandMinFraction of -0.04, a bandMaxFraction of 0.04 and a maxSpreadFraction of 0.04. The support is horizontal at T1, the target is that of TripleTopTarget, and the source defines no stop, so there is no stop view. Witness (synthetic, n = 2): peaks 100, 101 and 102 on bars 5, 13 and 21 over troughs 90 and 92 on bars 9 and 17 arm on bar 23 with neckline 90, height 12 and target 78, signal on bar 27 and expire on bar 37 if no close falls below 90 before it.
Relations: a head and shoulders needs P2 above both outer peaks, so HeadShouldersTop and this view do not arm on the same three swing highs. Where TripleTopSetup and DoubleTopSetup arm on the same bar, the neckline of the triple is at or below that of the double top of DoubleTop, which is T2, and neither set of setups contains the other. The point-and-figure triple top, which PointAndFigureSignal does not build, is a boxed construction, not this one.
Evidence: Tsinaslanidis and Zapranis report 19 triple tops in NASDAQ series and 74 in NYSE series, daily closes from 1990 to 2010, with look-ahead pivots and no costs, reaching the target within the time limit in 21.05% and 24.32% of cases against 15.94% and 14.27% on simulated paths. The samples are small and their rules differ from these views, so the figures describe their study and not this method. No profitability is claimed.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. When n < 1, bandMinFraction or bandMaxFraction is NaN or infinite, bandMinFraction > bandMaxFraction, or maxSpreadFraction is NaN, infinite or negative, the result is all +0. A huge n is valid and confirms no swing. Prices are not validated: a non-positive ext or T1 simply fails its rule. A NaN high suppresses the swing highs whose window holds it, and a triple whose span from b1 to b3 holds a NaN or an infinity in h or l does not qualify; a NaN or an infinity after the third peak changes nothing for that pattern, except through later swings.
Warm-up: no arming bar is before bar 4n + 2 (the first peak is at bar n or later and the swings are more than n bars apart), so the result is +0 on bars 0 to 4n + 1; +0 is the true “no event” value.
Causal: the output on bar t uses bars 0 to t only, and every prefix of the inputs gives the first values of the result. The view is path-dependent: a triple reads the latest swing high and the two before it, so a slice of the inputs from bar k on loses the patterns whose first peak is before bar k + n and changes nothing else. Cost: O(len(s)) whatever n. Pure: the inputs are not modified, aliased arguments included, and the result is a new Series that shares no storage with them.
TripleTopNeckline
func (s Series) TripleTopNeckline(h, l Series, n int, bandMinFraction, bandMaxFraction, maxSpreadFraction float64) Series
TripleTopNeckline holds the first trough of the triple top under test, the lowest low between its first two peaks, and NaN when none is under test.
The receiver holds the closes, and h and l are its highs and lows. A triple top, armed by the rules of TripleTop, is under test from its arming bar t = b3 + n through the bar whose close signals it; it is not under test on the bar on which it expires (t - b3 >= b3 - b1, checked before that bar’s close) or on any later bar, and once a newer qualifying triple replaces it the newer pattern is under test from its own arming bar on. The value is T1, the lowest low strictly between the first two peaks, copied verbatim and constant along each run under test: the support is horizontal at T1, not a line through T1 and T2. On a bar under test the close is at or above the neckline, or NaN, except on the signal bar, so the signal of TripleTop equals s.LT(neck) on every bar, with neck this view; neck.ValueWhen(sig) latches the neckline of each break, with sig that signal, and neck.EQ(neck) is 1 exactly on the bars under test. Where TripleTopSetup and DoubleTopSetup arm on the same bar this neckline is at or below DoubleTopNeckline, the lowest low between the last two peaks, T2. The recipes are in the manual’s “Chart Patterns” section.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. When n < 1, bandMinFraction or bandMaxFraction is NaN or infinite, bandMinFraction > bandMaxFraction, or maxSpreadFraction is NaN, infinite or negative, the result is all +0, not NaN. A huge n is valid, confirms no swing and gives all NaN.
Warm-up: NaN rather than 0 until the first pattern arms, on bar 4n + 2 at the earliest, because the start depends on the data; NaN again on every later bar with no pattern under test, the same bars on which TripleTopTarget is NaN.
Causal: the output on bar t uses bars 0 to t only. The view is path-dependent, with the slice rule of TripleTop: a slice from bar k on reads NaN where the lost pattern, whose first peak is before bar k + n, was under test. Cost: O(len(s)) whatever n. Pure: the inputs are not modified and the result is a new Series.
TripleTopSetup
func (s Series) TripleTopSetup(h, l Series, n int, bandMinFraction, bandMaxFraction, maxSpreadFraction float64) Series
TripleTopSetup reports 1 on the bar that confirms a swing high forming a triple top with the two swing highs before it, and 0 otherwise.
The receiver holds the closes, and h and l are its highs and lows. The arming bar of a triple top is the bar t = b3 + n on which h.SwingHighEvent(n) confirms the third peak, at bar b3, and its triple with the two swing highs confirmed before it qualifies under the rules of TripleTop. The bar arms even when its own close signals there, so a setup bar can also be a signal bar, and a qualifying triple replaces any pending pattern, the newest winning. A swing high whose triple does not qualify arms nothing.
When every high and low is finite the view is a recipe over the swing views, with a, b and p the three fractions: ev := h.SwingHighEvent(n); P3, P2, P1 := h.SwingHighValue(n), h.SwingHighValue(n, 1), h.SwingHighValue(n, 2); k := h.SwingHighAge(n, 1).Sub(float64(n)), the bars from b2 to b3, and d := h.SwingHighAge(n, 2).Sub(float64(n)).Sub(k), the bars from b1 to b2; V := l.Shift(n+1).LowestVariable(k.Sub(1), 1, len(l)) and U := h.Shift(n+1).HighestVariable(k.Sub(1), 1, len(h)) cover the bars strictly between the last two peaks; T2, T1 := V, V.ShiftVariable(k); hi := U.ShiftVariable(k).Maximum(P2).Maximum(U); ext := P1.Maximum(P3); r := P3.Sub(P1).Div(ext); sp := T2.Sub(T1).Div(T1); sym := d.Mul(2).LT(k.Mul(5)).Mul(k.Mul(2).LT(d.Mul(5))); and the setup is ev multiplied, with Mul, by r.GTE(a), r.LTE(b), hi.LTE(ext), T2.GTE(T1), sp.LTE(p), sym, ext.GT(0) and T1.GT(0). The method adds the finite-span rule and runs in one pass.
Relations: DoubleTopSetup also arms only on bars where h.SwingHighEvent(n) is 1; where both arm on the same bar the neckline of the triple is at or below the neckline of the double, and neither set of setups contains the other. Age: setup.BarsSince() counts the bars since the arming bar; on a signal bar it reads the age of the signalled pattern. Gate: a filter series g, read on the arming bar, gates the signal sig of TripleTop as sig.Mul(g.ShiftVariable(setup.BarsSince()).GT(0)); the GT(0) is needed because before the first setup setup.BarsSince() is NaN, the shifted gate is NaN there and 0 times NaN is NaN. A gate filters signals only: a triple that fails it still arms and replaces a pending pattern. The gates (their trend condition, a slope at the first peak, a height) are recipes in the manual’s “Chart Patterns” section.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. When n < 1, bandMinFraction or bandMaxFraction is NaN or infinite, bandMinFraction > bandMaxFraction, or maxSpreadFraction is NaN, infinite or negative, the result is all +0. A huge n is valid and confirms no swing. A triple whose span from b1 to b3 holds a NaN or an infinity in h or l does not qualify.
Warm-up: no arming bar is before bar 4n + 2, so the result is +0 on bars 0 to 4n + 1.
Causal: the output on bar t uses bars 0 to t only. The view is path-dependent, with the slice rule of TripleTop: a slice from bar k on loses the setups whose first peak is before bar k + n and changes nothing else. Cost: O(len(s)) whatever n. Pure: the inputs are not modified and the result is a new Series.
TripleTopTarget
func (s Series) TripleTopTarget(h, l Series, n int, bandMinFraction, bandMaxFraction, maxSpreadFraction float64) Series
TripleTopTarget holds the neckline less the height of the triple top under test, and NaN when none is under test.
The receiver holds the closes, and h and l are its highs and lows. The value is T1 - (ext - T1), with T1 the neckline of TripleTopNeckline and ext = max(P1, P3) the higher outer peak of the triple top under test, armed by the rules of TripleTop: the height H = ext - T1 is rounded first and then T1 - H, two roundings in that order, which is the price target PT = T1 - H of Table 5.3 of Tsinaslanidis and Zapranis (2016). It is held on exactly the bars on which TripleTopNeckline holds the neckline, constant along each run under test, and NaN elsewhere. The target is not -0 and, when no low exceeds its high, finite and between -ext and T1. target.ValueWhen(sig) latches the target of each break, with sig the signal of TripleTop; their text’s target from the break price, B - H, is c.Sub(neck).Add(target) on the signal bar, equal to the close less the height to rounding, and the height itself is neck.Sub(target), H to rounding. The height needs the outer extreme, which no other view holds, so the target is a view; the source defines no stop, so there is no stop view, and a void in Bulkowski’s style is a recipe in the manual’s “Chart Patterns” section.
When len(h) or len(l) differs from len(s) the result is Series{}, checked first. When n < 1, bandMinFraction or bandMaxFraction is NaN or infinite, bandMinFraction > bandMaxFraction, or maxSpreadFraction is NaN, infinite or negative, the result is all +0, not NaN. A huge n is valid, confirms no swing and gives all NaN.
Warm-up: NaN rather than 0 until the first pattern arms, on bar 4n + 2 at the earliest, because the start depends on the data; NaN again on every later bar with no pattern under test, the same bars on which TripleTopNeckline is NaN.
Causal: the output on bar t uses bars 0 to t only. The view is path-dependent, with the slice rule of TripleTop: a slice from bar k on reads NaN where the lost pattern, whose first peak is before bar k + n, was under test. Cost: O(len(s)) whatever n. Pure: the inputs are not modified and the result is a new Series.
Compositions
Head-and-shoulders recipes
The recipes below read the three views of one top, with c the closes, h and l the series whose swings form the pattern and n the pivot half-window; event, neckline and target stand for the three views. The bottom’s recipes are the same with HeadShouldersBottom and its views, with target.Sub(neckline) for the height and a close above the target for the exit in (c).
event := c.HeadShouldersTop(h, l, n)
neckline := c.HeadShouldersTopNeckline(h, l, n)
target := c.HeadShouldersTopTarget(h, l, n)(a) The levels of each break, latched
The level views are extended over every armed bar and are NaN after the break; ValueWhen holds the values of the break bar until the next break:
breakNeckline := neckline.ValueWhen(event) // the neckline on the bar of the last break
breakTarget := target.ValueWhen(event) // the target on that bar
- First valid index: the first event bar; both are NaN before it.
- Anchors: on an event bar
breakNecklineequalsnecklineandbreakTargetequalstargetbit for bit; on every other bar they repeat their previous values. For n >= 1 the event equalsc.LT(neckline)bit for bit (for a bottom,c.GT(neckline)), since the level is extended to every armed bar and the pattern is disarmed after its break; n < 1 makes every view +0, so a close below 0 would read 1 there. - Pinned by
TestRecipeHeadShoulders; runnable exampleExampleSeries_HeadShouldersTop.
(b) The head height and the close-measured target of Table 5.3
The head height H is the difference of the two levels on any armed bar; Table 5.3 of Tsinaslanidis and Zapranis measures the target from the close B of the breaking bar, B - H for a top (B + H for a bottom):
height := neckline.Sub(target) // H on every armed bar, to rounding
closeTarget := c.Sub(neckline).Add(target).ValueWhen(event) // B - H, latched on the break
- First valid index:
heightis finite on every armed bar where no level overflows (from bar 4*n + 2 at the earliest), +Inf, -Inf or NaN on an armed bar where one does, and NaN elsewhere;closeTargetis NaN before the first event bar. - Anchors:
heightis H only to rounding, since the target is N(t) - H rounded: it is within 2^-52 * (|N(t)| + 2|H|) of H on every armed bar where no level is infinite, and can vary along an armed run in the last bits.closeTargetis B - H to rounding in the same way. Their published code measures from the neckline at an interpolated crossing between the last two closes instead. - Pinned by
TestRecipeHeadShoulders; runnable exampleExampleSeries_HeadShouldersTopTarget.
(c) A timed exit
Tsinaslanidis and Zapranis’s simplest rule takes the position on the break (short after a top, long after a bottom) and holds it for a predefined period; their case-sensitive rule also closes it once the move passes the target. With Position the trade is a latch on the event (category “Trade State”):
held := c.Position(event, nil, hold) // 1 while the trade opened on a break is open, hold bars
reached := c.Position(event, c.LT(closeTarget), hold) // closed earlier by a close below Table 5.3's target
- First valid index: 0; both are +0 before the first event bar.
- Anchors:
heldopens on each event bar while flat and closes hold bars later;reachedalso closes on the first later bar whose close is strictly belowcloseTarget. A break while a trade is open opens nothing, but it movescloseTargetto the newer pattern’s. Their time limit is the pattern’s own width, tP3 - tP1 bars after the break (Table 5.3; their code uses tT2 - tT1), which these views do not expose, so a fixed hold stands in for it; their exit after n closes back above the neckline or an sl% loss is not built. - Pinned by
TestRecipeHeadShoulders; runnable exampleExampleSeries_HeadShouldersTop_timedExit.
(d) The close-only form
Tsinaslanidis and Zapranis run the detector on the closes alone, with the extrema of their rolling window of half-width w (2w + 1 bars):
closeOnly := c.HeadShouldersTop(c, c, w)- First valid index: as for the views; no pattern arms before bar 4*w + 2.
- Anchors: the swings are exactly their rolling-window extrema (strict on both sides), dated w bars later:
c.SwingHighEvent(w)andc.SwingLowEvent(w). A close cannot be both a swing high and a swing low, so no bar is shared. With w = 7, the window of their Table 5.2 for this pattern, the ADM points of their Table 5.1, joined by straight lines and followed by a decline to 20.4 on bar 335, arm a top on bar 303 that breaks on bar 334. - Pinned by
TestRecipeHeadShoulders; runnable examplesExampleSeries_HeadShouldersTopandExampleSeries_HeadShouldersBottom.
Candlesticks
Candle anatomy
RealBody, UpperShadow, LowerShadow and CandleAverage take the close c as the receiver and the open o, high h and low l as arguments: the columns of one bar table, so each must have the receiver’s length (otherwise the result is Series{}), and they are not right-aligned. The methods follow TA-Lib 0.6.4’s candle macros and default candle settings (ta_utility.h and ta_global.c, the same in substance from TA-Lib 0.4.0 to its main branch): RealBody is TA_REALBODY, |c - o|; UpperShadow and LowerShadow are TA_UPPERSHADOW and TA_LOWERSHADOW, written with Go’s built-in max and min; and CandleAverage is TA_CANDLEAVERAGE, factor times the mean of a measure over the n bars before each bar, evaluated as factor * (S/n) / d, where S is the window’s sum and d is 2 for the sum of the shadows and 1 otherwise. Maximum and Minimum, under “Math”, give the body’s top and bottom, c.Maximum(o) and c.Minimum(o). The columns can be any bar table of equal lengths, the views of the “Heikin-Ashi” category included: with haO and haC from HeikinAshiOpen and HeikinAshiClose, haC.RealBody(haO) is haC.Sub(haO).Abs(), the size of Valcu’s haDiffCO, bit for bit, and the kit’s colour haC.GTE(haO) counts a tie as white where Valcu’s white body, haC.GT(haO), does not.
TA-Lib’s patterns keep a running total of each measure, adding the new bar and subtracting the oldest. CandleAverage uses Average’s rolling sum instead: for n >= 1 it equals R.Average(n).Shift(1).Mul(factor).Div(d).SetN(n, math.NaN()) bit for bit, with R the measure. TA-Lib’s thresholds and these agree to rounding where no partial sum overflows, and bit for bit where the window sums are exact, as on prices in quarters (the primer of TA-Lib’s regression tests, which fixes every threshold its test file prints, uses whole numbers; some of its scenario bars use tenths). The rolling sum is window-local: a NaN or infinite measure affects only the windows that hold it, while TA-Lib’s running total stays NaN for the rest of the call.
TA-Lib’s candle settings
A TA-Lib candlestick pattern compares a candle’s parts with thresholds, its candle settings. The threshold of a setting (kind, n, factor) is c.CandleAverage(o, h, l, kind, n, factor). TA-Lib’s eleven defaults, with TA-Lib’s own comments, are:
| Setting | kind | n | factor | TA-Lib’s comment |
|---|---|---|---|---|
| BodyLong | CandleRealBody | 10 | 1 | longer than the average of the 10 previous candles’ real body |
| BodyVeryLong | CandleRealBody | 10 | 3 | longer than 3 times the average of the 10 previous candles’ real body |
| BodyShort | CandleRealBody | 10 | 1 | shorter than the average of the 10 previous candles’ real bodies |
| BodyDoji | CandleHighLow | 10 | 0.1 | shorter than 10% the average of the 10 previous candles’ high-low range |
| ShadowLong | CandleRealBody | 0 | 1 | longer than the real body |
| ShadowVeryLong | CandleRealBody | 0 | 2 | longer than 2 times the real body |
| ShadowShort | CandleShadows | 10 | 1 | shorter than half the average of the 10 previous candles’ sum of shadows |
| ShadowVeryShort | CandleHighLow | 10 | 0.1 | shorter than 10% the average of the 10 previous candles’ high-low range |
| Near | CandleHighLow | 5 | 0.2 | <= 20% of the average of the 5 previous candles’ high-low range |
| Far | CandleHighLow | 5 | 0.6 | >= 60% of the average of the 5 previous candles’ high-low range |
| Equal | CandleHighLow | 5 | 0.05 | <= 5% of the average of the 5 previous candles’ high-low range |
The comparison is not part of a setting: each pattern writes its own. TA-Lib’s doji is a real body no larger than BodyDoji, inclusive, c.RealBody(o).LTE(c.CandleAverage(o, h, l, CandleHighLow, 10, 0.1)): 1 on a doji and 0 elsewhere, including the ten warm-up bars. On the primer of TA-Lib’s regression tests, twelve bars with bodies of 2 and ranges of 10, the thresholds at the next bar are those its test file prints: 2 for BodyLong and BodyShort, 1 for BodyDoji and ShadowVeryShort, 0.5 for Equal and 2 for Near.
TA-Lib’s core patterns
Thirteen methods are TA-Lib 0.6.4’s core candlestick functions with its default candle settings. Each takes the close c as the receiver and the open o, high h and low l as arguments, c.CandleHammer(o, h, l), compares the parts of one to four candles with the thresholds of the settings above, each c.CandleAverage(o, h, l, kind, n, factor) bit for bit, and returns TA-Lib’s integer output divided by 100 at every bar: 1 or -1 on a pattern, 0.8 or -0.8 on the one-end ties of the engulfing pattern and the harami, and +0 on every other bar, NaN on none. CandleDoji’s 1 means that a doji is present, not that it is bullish. The dark cloud cover and the two stars take a penetration, as TA-Lib’s functions do: a fraction of the first candle’s real body, which the caller passes (TA-Lib’s defaults are 0.5 and 0.3).
| Method | TA-Lib | Values | Lookback | Penetration |
|---|---|---|---|---|
| CandleDoji | CDLDOJI | 1 | 10 | none |
| CandleEngulfing | CDLENGULFING | 1, -1, 0.8, -0.8 | 2 | none |
| CandleHammer | CDLHAMMER | 1 | 11 | none |
| CandleHangingMan | CDLHANGINGMAN | -1 | 11 | none |
| CandleInvertedHammer | CDLINVERTEDHAMMER | 1 | 11 | none |
| CandleShootingStar | CDLSHOOTINGSTAR | -1 | 11 | none |
| CandleHarami | CDLHARAMI | 1, -1, 0.8, -0.8 | 11 | none |
| CandlePiercing | CDLPIERCING | 1 | 11 | fixed at 0.5 |
| CandleDarkCloudCover | CDLDARKCLOUDCOVER | -1 | 11 | 0.5 |
| CandleMorningStar | CDLMORNINGSTAR | 1 | 12 | 0.3 |
| CandleEveningStar | CDLEVENINGSTAR | -1 | 12 | 0.3 |
| CandleThreeWhiteSoldiers | CDL3WHITESOLDIERS | 1 | 12 | none |
| CandleThreeBlackCrows | CDL3BLACKCROWS | -1 | 13 | none |
The lookback is TA-Lib’s at its default settings: bars 0 to L-1 are 0 whatever the data, so the first bar that can fire is TA-Lib’s, and a table of L bars or fewer is all 0. For the engulfing pattern, the hammer, the hanging man, the inverted hammer, the shooting star and the crows, every value the decision reads already exists at bar L-1, and that bar is 0 even where TA-Lib’s condition holds. A penetration that is NaN, +Inf, -Inf or negative gives zeros of the receiver’s length; every other value is valid, 0 and values above 1 included, and the lengths are checked first: the open, high and low must each have the receiver’s length, otherwise the result is Series{}.
Bar t of each pattern reads these prices and thresholds, a threshold at bar k being the setting’s value at index k, whose window is the ten bars before k (five for Near and Far; ShadowLong is the body of bar k itself):
- CandleDoji: o and c at bar t, and BodyDoji at bar t;
- CandleEngulfing: o and c at bars t-1 and t, and no threshold;
- CandleHammer and CandleHangingMan: o, h, l and c at bar t, l at bar t-1 (the hammer) or h at bar t-1 (the hanging man), BodyShort, ShadowLong and ShadowVeryShort at bar t, and Near at bar t-1;
- CandleInvertedHammer and CandleShootingStar: o, h, l and c at bar t, o and c at bar t-1, and BodyShort, ShadowLong and ShadowVeryShort at bar t;
- CandleHarami: o and c at bars t-1 and t, BodyLong at bar t-1 and BodyShort at bar t;
- CandlePiercing: o, l and c at bar t-1, o and c at bar t, and BodyLong at bars t-1 and t;
- CandleDarkCloudCover: o, h and c at bar t-1, o and c at bar t, and BodyLong at bar t-1;
- CandleMorningStar and CandleEveningStar: o and c at bars t-2 to t, BodyLong at bar t-2, and BodyShort at bars t-1 and t;
- CandleThreeWhiteSoldiers: o, h and c at bars t-2 to t, ShadowVeryShort at bars t-2 to t, Near and Far at bars t-2 and t-1, and BodyShort at bar t;
- CandleThreeBlackCrows: o, h and c at bar t-3, o, l and c at bars t-2 to t, and ShadowVeryShort at bars t-2 to t.
So the engulfing pattern, the harami and the two stars read only the open and the close, since their thresholds are body settings. The thresholds agree with TA-Lib’s running totals to rounding, and exactly where the window sums are exact, as on prices in quarters, so a decision can differ from TA-Lib’s only where a compared value sits on a threshold that the two sums round differently. After ten bars with opens, lows and closes of 0 and ranges summing to 87.5, a body of 0.875 is a doji here, where the rolling sum gives BodyDoji 0.875 exactly, and not in TA-Lib, whose running total gives 0.8749999999999999.
- Missing values. A bar whose decision reads a NaN, +Inf or -Inf, among the prices listed or in the window of a listed threshold, is 0, whatever the pattern’s other conditions give, and every other bar keeps its value: on flat bars, a NaN high at bar 19 makes CandleDoji 0 on bars 20 to 29, whose BodyDoji windows hold it, and 1 again from bar 30. TA-Lib differs twice. It compares infinities as numbers, so an infinite close is a white candle that engulfs anything and an infinite range makes a threshold of +Inf that every body is below, a doji. And its running totals, once they hold a NaN, keep it to the end of the call (an infinite price makes the total infinite, and a NaN when it leaves the window; a total that overflowed on finite measures stays infinite), so that every later bar of a pattern that reads the setting is 0. A window of huge finite measures can overflow to +Inf too; that threshold is then not finite, and the bar is 0.
- The white tie. A candle with c = o counts as white, as in TA-Lib. The second of three white soldiers may be a doji, the crows’ white candle may be one, and a doji first candle can be engulfed by a black candle and not by a white one. Otherwise the engulfing pattern is its own mirror on reflected prices (o, h, l and c negated, the high and the low swapped), the harami is its own mirror, and the morning and evening stars are each other’s, bit for bit, since every candle whose colour the stars test has a body above a threshold. The other pairs are not exact mirrors.
- One-end ties. When the two real bodies match at one end, CandleEngulfing and CandleHarami give 0.8 or -0.8, TA-Lib’s 80. TA-Lib before 0.6.0, the 0.4 line that many wrappers shipped, has no such tie and gives 0 on those bars. Bodies equal at both ends give 0 in the engulfing pattern.
- TA-Lib 0.8.1. The penetration lines are written
c + B*pandc - B*p, with B the first body and the product rounded before the addition or subtraction, as in TA-Lib 0.6.4. TA-Lib 0.8.1 writes the morning star’s line as a fused multiply-add,fma(B, p, c), which can move it by one ulp: on TA-Lib’s morning-star scenario at penetration 0.296, a third close of 103.552 lies above the 0.6.4 line, 103.55199999999999, and gives 1 here and in 0.6.4, while the fused line is 103.552 itself and gives 0. It writes the piercing pattern’s midpoint asfma(B, 0.5, c), but the product by 0.5 is exact unless the body is subnormal, so the two agree on practical data.
No pattern tests the trend; the recipes below add context, and the sign and the full patterns are compositions. For a result x:
sign := x.GT(0).Sub(x.LT(0))
full := x.Abs().EQ(1)
masked := x.MaskNonFinite(o, h, l, c)The sign folds the ties into 1 and -1, and full marks the full patterns only. masked is NaN where a price of the pattern’s own bar is not finite, and only there: a bar that is 0 because an earlier bar or a threshold’s window holds the missing value keeps its 0.
Nison’s hammer has a lower shadow at least twice the body (strict here, as TA-Lib’s comparisons are; see “The hammer’s shadow”), and Sung’s closes below its 20-day average:
nison := c.CandleHammer(o, h, l).Mul(c.LowerShadow(o, l).GT(c.RealBody(o).Mul(2)))
sung := c.CandleHammer(o, h, l).Mul(c.LT(c.Average(20)))nison is bit for bit TA-Lib’s hammer with ShadowVeryLong in place of ShadowLong, since a lower shadow above twice a body that is not negative is above the body. sung is 0 on bars 0 to 18, where Average passes the closes through, and his hanging man closes above the average. Hellal and Meyers’s ten-bar trend rule (under “Evidence”) is applied the same way, as a series of 0 and 1 that multiplies the result; their engulfing pattern also asks a bullish engulfing bar’s high to exceed the prior high and a bearish one’s low to undercut the prior low, which TA-Lib does not test.
Ties and missing values
- Colour. A candle is white when
c.GTE(o)is 1 and black when it is 0. A tie, c = o, counts as white, as in TA-Lib’sTA_CANDLECOLOR; Nison calls such a candle a doji, with no colour. A NaN close or open reads as black, since a comparison with NaN gives 0:c.GTE(o).MaskNonFinite(c, o)keeps it missing. - Body ends.
UpperShadow,LowerShadow,MaximumandMinimumuse the built-inmaxandmin, which give NaN when either price is NaN, and +0 (max) or -0 (min) from the two signed zeros. TA-Lib’s macros choose the body’s end withc >= o, which is false when either price is NaN: its upper shadow then reads the open, so it is finite when only the close is NaN, and its lower shadow reads the close, so it is finite when only the open is NaN. On a close of -0 with an open of +0 they take the close as the top and the open as the bottom. On other prices the two agree. - Warm-up.
CandleAverageis NaN on its first n bars, and on every bar when n is at least the length, not the package’s +0: a +0 threshold would pass every<=test, while a comparison with NaN gives 0. - A setting read k bars back.
Shift(k)pads +0 at the start, so mask the warm-up again:c.CandleAverage(o, h, l, kind, n, factor).Shift(k).SetN(n+k, math.NaN()). When n can be nearmath.MaxInt, n+k overflows andSetNthen masks nothing;SetN(min(n, len(c))+k, math.NaN())masks the same bars.
Evidence
The evidence for candlestick patterns is weak. Marshall, Young and Rose (2006) found no value in them on the stocks of the Dow Jones Industrial Average (1992-2002). Hellal and Meyers tested the engulfing pattern with a ten-bar trend rule on 14 currency pairs and 21 ETFs and stocks (2000-2011): over 1,740 patterns, the best holding period gave +0.052% per trade before costs, indistinguishable from zero. Sung found no bullish edge after hammers: prices generally headed down after the umbrella candles he studied, and more so after hammers.
No TA-Lib pattern tests the trend, although Nison defines the hammer and the engulfing pattern by the trend they end; the sources’ trend filters are recipes. Sung’s hammer closes below its 20-day average, c.LT(c.Average(20)) (0 on bars 0 to 18, where Average passes the closes through), and his hanging man above it; Hellal and Meyers compare the highest high of the pattern’s three bars (the lowest low, for the bearish case) with the highest high and the lowest low of the previous ten bars.
Recipes
The parts of a candle that have no method of their own are compositions:
- the range,
h.Sub(l). Upper shadow plus lower shadow plus real body, added in that order, equals it exactly on well-formed bars with 0 < l and h <= 2l, and on prices in quarters, and to rounding otherwise; - the colour,
c.GTE(o), above; - the signed body,
c.Sub(o), whose n-bar average equalsc.QStick(o, c, n)to rounding; - the body’s top and bottom,
c.Maximum(o)andc.Minimum(o). The arithmetic form of the top,o.Add(c).Add(o.Sub(c).Abs()).Div(2), is not exact.
Gaps
TA-Lib’s four gap macros compare a bar with the bar before it, strictly. Shift pads +0, so without the mask bar 0 would gap up on positive prices:
bodyGapUp := c.Minimum(o).GT(c.Maximum(o).Shift(1)).SetN(1, 0)
bodyGapDown := c.Maximum(o).LT(c.Minimum(o).Shift(1)).SetN(1, 0)
gapUp := l.GT(h.Shift(1)).SetN(1, 0)
gapDown := h.LT(l.Shift(1)).SetN(1, 0)The first two are the real-body gaps; the last two are the candle gaps, Nison’s rising and falling windows. A comparison with NaN gives 0, so a missing price reads as no gap.
The hammer’s shadow
Nison’s hammer has a lower shadow “at least two to three times the height of the real body”; TA-Lib’s hammer asks for a lower shadow longer than the body, its ShadowLong setting. The strict two-times test is the ShadowVeryLong setting, which equals c.RealBody(o).Mul(2) bit for bit (Nison’s “at least” would be GTE; TA-Lib’s comparisons are strict):
twice := c.LowerShadow(o, l).GT(c.RealBody(o).Mul(2))
taLib := c.LowerShadow(o, l).GT(c.CandleAverage(o, h, l, CandleRealBody, 0, 1))Cline’s candle signature
Cline cuts each bar into k = 5 segments of the larger of its range and the mean range of the ten bars before it, and rounds each part half away from zero (Excel’s ROUND, which Round matches), so a bar is coded by three digits from 0 to 5 on a well-formed bar, its signature HO:HC:OL:
avg := c.CandleAverage(o, h, l, CandleHighLow, 10, 1)
D := h.Sub(l).Maximum(avg)
ho := h.Sub(o).Mul(5).Div(D).Round()
hc := h.Sub(c).Mul(5).Div(D).Round()
ol := o.Sub(l).Mul(5).Div(D).Round()
code := ho.Mul(36).Add(hc.Mul(6)).Add(ol)The first valid bar is 10: before it the average is NaN, and so is every part. code packs the signature into one number in base k + 1 = 6. On Cline’s twelve monthly SPY bars of 2013, the recipe reproduces all twelve printed signatures from his printed averages, and from its own averages those of November and December, 3:0:1 and 2:0:2. A flat bar after ten flat bars has D = 0, where Div gives 0, so its signature is 0:0:0.
Ruggiero’s graded long body
Ruggiero grades a body between 0, at zeroCof times the average body, and 1, at oneCof times it, linearly in between; his tests use 15 bars, oneCof = 2 and zeroCof = 1:
body := c.RealBody(o)
avg := body.Average(15)
z, one := avg.Mul(zeroCof), avg.Mul(oneCof)
scale := one.Sub(z)
long := body.Sub(z).Div(scale).Clamp(0, 1).SetN(14, math.NaN())
long = long.If(func(_ float64, i int) bool { return scale[i] == 0 }, math.NaN(), long)His average includes the current bar, as TradeStation’s does, unlike TA-Lib’s settings; bars 0 to 13, where Average passes the bodies through, are masked. The numerator is body - z, as his text and figure describe; the printed code subtracts the one level, which would give 0 below it. Where the scale is 0, his code substitutes 99.99 and Div gives 0, which would read as “not long”, so the recipe masks those bars to NaN. With oneCof below zeroCof (0.3 and 1, his small body) the same lines grade a small body; his small-body code is not printed. His clause that relaxes the test after a long bar is left out: as printed it relaxes nothing clearly.
CandleAverage
func (s Series) CandleAverage(o, h, l Series, kind CandleRange, n int, factor float64) Series
CandleAverage returns TA-Lib’s candle-setting threshold: factor times the mean of the chosen measure over the n bars before each bar, the bar’s own measure when n is 0, halved for the shadows, and NaN on the first n bars.
The receiver c is the close. CandleRealBody measures |c[t] - o[t]|, CandleHighLow measures h[t] - l[t], and CandleShadows measures (h[t] - max(o[t], c[t])) + (min(o[t], c[t]) - l[t]), using the built-in max and min. The divisor d is 2 for CandleShadows and 1 for the other kinds. Prices are unchecked, so shadows and ranges may be negative. Only the columns needed by the chosen measure affect its values.
For n >= 1, bar t uses the n bars t-n to t-1, excluding the current bar. With S the sum over that window, the TA_CANDLEAVERAGE order is factor * (S/n) / d: first form the mean, then multiply by factor, then divide by d. The mean is the value Average computes for the same measure at bar t-1. The result is R.Average(n).Shift(1).Mul(factor).Div(d).SetN(n, math.NaN()) bit for bit, where R is the chosen measure. For n = 0, the result is R.Mul(factor).Div(d) bit for bit, evaluated as (factor * R[t]) / d on every bar.
TA-Lib uses a running total: on finite data without overflow it agrees to rounding, and bit for bit when sums are exact. A NaN in that running total persists. Here missing data is window-local: a NaN or infinity affects only windows containing that measure, and later windows recover. A window containing NaN or both signs of infinity gives NaN; otherwise an infinity keeps its sign before scaling. A zero factor times an infinity gives NaN. Partial sums of finite measures can overflow, giving +Inf, -Inf or NaN according to the summation grouping, even when the exact window sum is finite. Multiplication by factor can also overflow before division by d. A slice that starts later gives the same values from its bar n on only to rounding, and bit for bit when the window sums are exact, because the grouping of the rolling sum depends on where it started; where a partial sum overflows, one start can give a finite value and another an infinity.
Bars before n are NaN, including every bar when n equals or exceeds the length. This departs from the package’s +0 warm-up: a +0 threshold can pass a <= test before an average is available, whereas comparisons with NaN return 0. To read a setting k bars back, use Shift(k), then SetN(n+k, math.NaN()) to mask the +0 padding as well as the original warm-up. When n can be near math.MaxInt, n+k overflows and SetN then masks nothing; SetN(min(n, len(c))+k, math.NaN()) masks the same bars without overflow.
TA-Lib 0.6.4’s eleven default candle settings, as (kind, n, factor), are BodyLong (CandleRealBody, 10, 1), BodyVeryLong (CandleRealBody, 10, 3), BodyShort (CandleRealBody, 10, 1), BodyDoji (CandleHighLow, 10, 0.1), ShadowLong (CandleRealBody, 0, 1), ShadowVeryLong (CandleRealBody, 0, 2), ShadowShort (CandleShadows, 10, 1), ShadowVeryShort (CandleHighLow, 10, 0.1), Near (CandleHighLow, 5, 0.2), Far (CandleHighLow, 5, 0.6) and Equal (CandleHighLow, 5, 0.05). Each pattern supplies its own comparison operator. For instance, c.RealBody(o).LTE(c.CandleAverage(o, h, l, CandleHighLow, 10, 0.1)) is TA-Lib’s inclusive doji rule, giving 1 on a doji and 0 elsewhere, including the warm-up bars. The thirteen TA-Lib core patterns of the Candlesticks category read every threshold they compare with from this method at these defaults, bit for bit.
The open o, high h and low l must each have the receiver’s length, whichever kind is chosen, otherwise the result is Series{}. These columns form one bar table and are not right-aligned. Lengths are checked before parameters. A negative n, an unknown kind, or a NaN or infinite factor returns fresh +0 values of the receiver’s length. Every finite factor is accepted, including 0, -0, negative factors and subnormals. Every non-negative n is valid. Empty or nil inputs of equal length give an empty, non-nil result for any parameters. The method takes O(len(s)) time, returns fresh storage and does not modify its inputs.
CandleDarkCloudCover
func (s Series) CandleDarkCloudCover(o, h, l Series, penetration float64) Series
CandleDarkCloudCover is -1 on TA-Lib’s dark cloud cover (CDLDARKCLOUDCOVER), a black candle that opens above the high of a long white candle and closes inside its body, more than penetration times the body below its close, and 0 otherwise.
The receiver is the close, written c below, so a caller writes c.CandleDarkCloudCover(o, h, l, 0.5). Bar t is -1 when bar t-1 is white, c >= o, with a real body |c - o| above BodyLong at bar t-1, bar t is black, c < o, o[t] > h[t-1], c[t] > o[t-1] and c[t] < c[t-1] - penetration times the first body, the product rounded before the subtraction as in TA-Lib 0.6.4; otherwise it is 0. Every test is strict, and there is no size test on bar t. The setting BodyLong (CandleRealBody, 10, 1) is the mean body of bars t-11 to t-2.
The penetration is a fraction of the first candle’s real body. TA-Lib’s default is 0.5. A penetration of 0 asks only for a close below the first close; values above 1 are valid, although in exact arithmetic the pattern cannot fire at 1 or more, since the close would then have to be below the first open while above it; the rounded line can lie a few ulps above the first open at a penetration of 1 or within rounding of it, and a close between the two then fires, as in TA-Lib 0.6.4. A negative, NaN or infinite penetration gives zeros of the receiver’s length. Lengths are checked before penetration.
Each threshold is c.CandleAverage(o, h, l, kind, n, factor) at TA-Lib’s default setting, bit for bit; it agrees with TA-Lib’s running total to rounding, and exactly where the window sums are exact, as on prices in quarters. So the method equals TA-Lib 0.6.4, its integer output divided by 100, except where a compared value sits on a tie that the two sums round differently.
It reads o, h and c at bar t-1, o and c at bar t, and BodyLong at bar t-1. Bars before TA-Lib’s lookback, the first 11, are 0.
CandlePiercing is the bullish counterpart but not an exact mirror: its fraction is fixed at 0.5, it asks for a long second body, and it compares the open with the prior low. TA-Lib does not test the trend: a dark cloud cover is a top reversal only after an advance, which the caller supplies.
A bar whose decision reads a NaN, +Inf or -Inf, among its prices or through the window of a threshold, is 0, and later bars recover once the value has left the windows. TA-Lib compares infinities as numbers and keeps a NaN running total to the end of the call. A caller who wants missing bars to stay missing can mask the result with MaskNonFinite. A table that starts later gives, from its own lookback on, the same values, bit for bit where the window sums are exact and otherwise to rounding.
The open o, high h and low l must each have the receiver’s length, otherwise the result is Series{}. They are the columns of one bar table, so they are not right-aligned. The cost is O(len(s)); the result is fresh storage and the inputs are not modified.
CandleDoji
func (s Series) CandleDoji(o, h, l Series) Series
CandleDoji is 1 on a doji, a real body no larger than a tenth of the mean range of the previous ten bars (TA-Lib CDLDOJI), and 0 otherwise.
The receiver is the close, written c below, so a caller writes c.CandleDoji(o, h, l). Bar t is 1 when its real body |c - o| is at most BodyDoji at bar t, an inclusive test, and 0 otherwise. The setting BodyDoji (CandleHighLow, 10, 0.1) is a tenth of the mean of h - l over bars t-10 to t-1. The 1 means that a doji is present, not that it is bullish: a doji marks indecision, and its meaning depends on the trend before it; TA-Lib does not test the trend, so the caller supplies that context. Ten or more flat bars give a BodyDoji of 0, and every zero body from bar 10 on is then a doji, since the test is inclusive.
Each threshold is c.CandleAverage(o, h, l, kind, n, factor) at TA-Lib’s default setting, bit for bit; it agrees with TA-Lib’s running total to rounding, and exactly where the window sums are exact, as on prices in quarters. So the method equals TA-Lib 0.6.4, its integer output divided by 100, except where a body sits on a tie that the two sums round differently. On any table without an infinite threshold the result is c.RealBody(o).LTE(c.CandleAverage(o, h, l, CandleHighLow, 10, 0.1)) bit for bit, NaN included, since LTE gives 0 where either operand is NaN; where a window holds an infinite range the two differ, LTE giving 1 for a finite body and this method 0.
It reads o and c at bar t and BodyDoji at bar t. Bars before TA-Lib’s lookback, the first 10, are 0.
A bar whose decision reads a NaN, +Inf or -Inf, among its prices or through the window of a threshold, is 0, and later bars recover once the value has left the windows. TA-Lib compares infinities as numbers and keeps a NaN running total to the end of the call. A caller who wants missing bars to stay missing can mask the result with MaskNonFinite. A table that starts later gives, from its own lookback on, the same values, bit for bit where the window sums are exact and otherwise to rounding.
The open o, high h and low l must each have the receiver’s length, otherwise the result is Series{}. They are the columns of one bar table, so they are not right-aligned. The cost is O(len(s)); the result is fresh storage and the inputs are not modified.
CandleEngulfing
func (s Series) CandleEngulfing(o, h, l Series) Series
CandleEngulfing is 1 for a bullish and -1 for a bearish engulfing pattern, 0.8 or -0.8 when the two real bodies match at one end (TA-Lib CDLENGULFING), and 0 otherwise.
The receiver is the close, written c below, so a caller writes c.CandleEngulfing(o, h, l). A candle is white when c >= o, a tie counting as white as in TA-Lib, and black otherwise. Bar t is a bullish engulfing when it is white, bar t-1 is black, and either c[t] >= o[t-1] with o[t] < c[t-1] or c[t] > o[t-1] with o[t] <= c[t-1]; it is a bearish one when it is black, bar t-1 is white, and either o[t] >= c[t-1] with c[t] < o[t-1] or o[t] > c[t-1] with c[t] <= o[t-1]. The value is 1 or -1 when o[t] != c[t-1] and c[t] != o[t-1], and 0.8 or -0.8 when the bodies match at one end, TA-Lib’s 80 divided by 100. Bodies equal at both ends satisfy neither arm and give 0, as does any other bar. TA-Lib before 0.6.0 had no one-end tie and gave 0 on those bars. For a result x, x.GT(0).Sub(x.LT(0)) is its sign, the ties folded into 1 and -1, and x.Abs().EQ(1) marks the full patterns only.
The first candle may be a doji: with c = o it is white in TA-Lib’s convention, so it can be engulfed by a black candle and not by a white one. On every other bar the pattern is its own mirror on reflected prices, o, h, l and c negated with the high and the low swapped, bit for bit. TA-Lib does not test the trend: a bullish engulfing is a reversal signal only after a decline and a bearish one only after an advance, which the caller supplies.
It reads o and c at bars t-1 and t, and no threshold. Bars before TA-Lib’s lookback, the first 2, are 0. On finite prices the method equals TA-Lib 0.6.4, its integer output divided by 100, bit for bit.
A bar whose decision reads a NaN, +Inf or -Inf, among its prices or through the window of a threshold, is 0, and later bars recover once the value has left the windows. TA-Lib compares infinities as numbers, so there an infinite close is a white candle that can engulf. A caller who wants missing bars to stay missing can mask the result with MaskNonFinite. A table that starts later gives the same values from its own lookback on, bit for bit.
The open o, high h and low l must each have the receiver’s length, otherwise the result is Series{}. They are the columns of one bar table, so they are not right-aligned, and the high and the low are checked but not read. The cost is O(len(s)); the result is fresh storage and the inputs are not modified.
CandleEveningStar
func (s Series) CandleEveningStar(o, h, l Series, penetration float64) Series
CandleEveningStar is -1 on TA-Lib’s evening star (CDLEVENINGSTAR), a long white candle, a short body gapping up and a black candle closing more than penetration times the first body below the first close, and 0 otherwise.
The receiver is the close, written c below, so a caller writes c.CandleEveningStar(o, h, l, 0.3). Bar t is -1 when bar t-2 is white, c >= o, with a real body |c - o| above BodyLong at bar t-2, bar t-1 has a body at most BodyShort at bar t-1 and gaps up, min(o, c) at bar t-1 above max(o, c) at bar t-2, and bar t is black, c < o, with a body above BodyShort at bar t, longer than short, and c[t] < c[t-2] - penetration times the first body, the product rounded before the subtraction as in TA-Lib 0.6.4; otherwise it is 0. Only the star’s body test is inclusive. The settings are BodyLong (CandleRealBody, 10, 1) and BodyShort (CandleRealBody, 10, 1), each the mean body of the ten bars before the bar it is read at, so the two series are equal.
The penetration is a fraction of the first candle’s real body. TA-Lib’s default is 0.3. A penetration of 0 asks only for a close below the first close; values above 1 are valid and ask for a close below the first open, since there is no lower test. A negative, NaN or infinite penetration gives zeros of the receiver’s length. Lengths are checked before penetration.
Each threshold is c.CandleAverage(o, h, l, kind, n, factor) at TA-Lib’s default setting, bit for bit; it agrees with TA-Lib’s running total to rounding, and exactly where the window sums are exact, as on prices in quarters. So the method equals TA-Lib 0.6.4, its integer output divided by 100, except where a compared value sits on a tie that the two sums round differently.
It reads o and c at bars t-2 to t, BodyLong at bar t-2, and BodyShort at bars t-1 and t. Bars before TA-Lib’s lookback, the first 12, are 0.
CandleMorningStar is the exact mirror: on reflected prices, o, h, l and c negated with the high and the low swapped, the negated CandleMorningStar is this method bit for bit at every penetration, as in TA-Lib 0.6.4. TA-Lib 0.8.1 fuses the morning star’s line into a multiply-add and not this one’s, so there the two stars are not exact mirrors; this method and the line it mirrors keep separate products, as 0.6.4 has them. TA-Lib does not test the trend: an evening star is a top reversal only after an advance, which the caller supplies.
A bar whose decision reads a NaN, +Inf or -Inf, among its prices or through the window of a threshold, is 0, and later bars recover once the value has left the windows. TA-Lib compares infinities as numbers and keeps a NaN running total to the end of the call. A caller who wants missing bars to stay missing can mask the result with MaskNonFinite. A table that starts later gives, from its own lookback on, the same values, bit for bit where the window sums are exact and otherwise to rounding.
The open o, high h and low l must each have the receiver’s length, otherwise the result is Series{}. They are the columns of one bar table, so they are not right-aligned. The cost is O(len(s)); the result is fresh storage and the inputs are not modified.
CandleHammer
func (s Series) CandleHammer(o, h, l Series) Series
CandleHammer is 1 on TA-Lib’s hammer (CDLHAMMER), a small real body near or below the prior bar’s low with a long lower shadow and a very short upper shadow, and 0 otherwise.
The receiver is the close, written c below, so a caller writes c.CandleHammer(o, h, l). Bar t is 1 when its real body |c - o| is below BodyShort at bar t, its lower shadow min(o, c) - l is above ShadowLong at bar t, its upper shadow h - max(o, c) is below ShadowVeryShort at bar t, and min(o, c) <= l[t-1] + Near at bar t-1, the body ending at or below a band above the prior low; otherwise it is 0. The three size tests are strict and the band is inclusive. The settings are BodyShort (CandleRealBody, 10, 1), the mean body of bars t-10 to t-1, ShadowLong (CandleRealBody, 0, 1), the body of bar t itself, so the lower shadow must be longer than the body where Nison asks for at least twice it, ShadowVeryShort (CandleHighLow, 10, 0.1), a tenth of the mean range of bars t-10 to t-1, and Near (CandleHighLow, 5, 0.2), a fifth of the mean range of bars t-6 to t-2. A caller who wants Nison’s stricter shadow can multiply the result by c.LowerShadow(o, l).GT(c.RealBody(o).Mul(2)).
Each threshold is c.CandleAverage(o, h, l, kind, n, factor) at TA-Lib’s default setting, bit for bit; it agrees with TA-Lib’s running total to rounding, and exactly where the window sums are exact, as on prices in quarters. So the method equals TA-Lib 0.6.4, its integer output divided by 100, except where a compared value sits on a tie that the two sums round differently.
It reads o, h, l and c at bar t, l at bar t-1, BodyShort, ShadowLong and ShadowVeryShort at bar t, and Near at bar t-1. Bars before TA-Lib’s lookback, the first 11, are 0.
CandleHangingMan tests the same bar against the prior high, so the two share their first three tests and fire together only when the prior range h[t-1] - l[t-1] is at most twice Near at bar t-1, to rounding. TA-Lib does not test the trend: a hammer is a bottom reversal only after a decline, which the caller supplies, for instance as c.CandleHammer(o, h, l).Mul(c.LT(c.Average(20))) after Sung.
A bar whose decision reads a NaN, +Inf or -Inf, among its prices or through the window of a threshold, is 0, and later bars recover once the value has left the windows. TA-Lib compares infinities as numbers and keeps a NaN running total to the end of the call. A caller who wants missing bars to stay missing can mask the result with MaskNonFinite. A table that starts later gives, from its own lookback on, the same values, bit for bit where the window sums are exact and otherwise to rounding.
The open o, high h and low l must each have the receiver’s length, otherwise the result is Series{}. They are the columns of one bar table, so they are not right-aligned. The cost is O(len(s)); the result is fresh storage and the inputs are not modified.
CandleHangingMan
func (s Series) CandleHangingMan(o, h, l Series) Series
CandleHangingMan is -1 on TA-Lib’s hanging man (CDLHANGINGMAN), a small real body near or above the prior bar’s high with a long lower shadow and a very short upper shadow, and 0 otherwise.
The receiver is the close, written c below, so a caller writes c.CandleHangingMan(o, h, l). Bar t is -1 when its real body |c - o| is below BodyShort at bar t, its lower shadow min(o, c) - l is above ShadowLong at bar t, its upper shadow h - max(o, c) is below ShadowVeryShort at bar t, and min(o, c) >= h[t-1] - Near at bar t-1, the body ending at or above a band below the prior high; otherwise it is 0. The three size tests are strict and the band is inclusive. The settings are BodyShort (CandleRealBody, 10, 1), the mean body of bars t-10 to t-1, ShadowLong (CandleRealBody, 0, 1), the body of bar t itself, so the lower shadow must be longer than the body where Nison asks for at least twice it, ShadowVeryShort (CandleHighLow, 10, 0.1), a tenth of the mean range of bars t-10 to t-1, and Near (CandleHighLow, 5, 0.2), a fifth of the mean range of bars t-6 to t-2.
Each threshold is c.CandleAverage(o, h, l, kind, n, factor) at TA-Lib’s default setting, bit for bit; it agrees with TA-Lib’s running total to rounding, and exactly where the window sums are exact, as on prices in quarters. So the method equals TA-Lib 0.6.4, its integer output divided by 100, except where a compared value sits on a tie that the two sums round differently.
It reads o, h, l and c at bar t, h at bar t-1, BodyShort, ShadowLong and ShadowVeryShort at bar t, and Near at bar t-1. Bars before TA-Lib’s lookback, the first 11, are 0.
CandleHammer tests the same bar against the prior low, so the two share their first three tests and fire together only when the prior range h[t-1] - l[t-1] is at most twice Near at bar t-1, to rounding. TA-Lib does not test the trend: a hanging man is a top reversal only after an advance, which the caller supplies.
A bar whose decision reads a NaN, +Inf or -Inf, among its prices or through the window of a threshold, is 0, and later bars recover once the value has left the windows. TA-Lib compares infinities as numbers and keeps a NaN running total to the end of the call. A caller who wants missing bars to stay missing can mask the result with MaskNonFinite. A table that starts later gives, from its own lookback on, the same values, bit for bit where the window sums are exact and otherwise to rounding.
The open o, high h and low l must each have the receiver’s length, otherwise the result is Series{}. They are the columns of one bar table, so they are not right-aligned. The cost is O(len(s)); the result is fresh storage and the inputs are not modified.
CandleHarami
func (s Series) CandleHarami(o, h, l Series) Series
CandleHarami is 1 after a long black candle and -1 after a long white one when a short real body lies inside it, 0.8 or -0.8 when the bodies match at one end (TA-Lib CDLHARAMI), and 0 otherwise.
The receiver is the close, written c below, so a caller writes c.CandleHarami(o, h, l). Bar t is tested when the real body |c - o| of bar t-1 is above BodyLong at bar t-1, a strict test, and the body of bar t is at most BodyShort at bar t, an inclusive one. The value is then 1 or -1 when max(o, c) at bar t is below max(o, c) at bar t-1 and min(o, c) at bar t is above min(o, c) at bar t-1, the second body strictly inside the first, and 0.8 or -0.8, TA-Lib’s 80 divided by 100, when the two inclusive tests hold instead, the bodies matching at one end; otherwise, and on every other bar, it is 0. The sign is against the first candle’s colour: negative after a white candle, c >= o at bar t-1, and positive after a black one. The colour of the second candle is not tested, so it may be a doji; the first cannot be, since its body exceeds a threshold that is not negative, so the white tie does not arise. TA-Lib before 0.6.0 had no one-end tie and gave 0 on those bars. For a result x, x.GT(0).Sub(x.LT(0)) is its sign, the ties folded into 1 and -1, and x.Abs().EQ(1) marks the full patterns only. The settings are BodyLong (CandleRealBody, 10, 1) and BodyShort (CandleRealBody, 10, 1), each the mean body of the ten bars before the bar it is read at, so the two series are equal.
Each threshold is c.CandleAverage(o, h, l, kind, n, factor) at TA-Lib’s default setting, bit for bit; it agrees with TA-Lib’s running total to rounding, and exactly where the window sums are exact, as on prices in quarters. So the method equals TA-Lib 0.6.4, its integer output divided by 100, except where a compared value sits on a tie that the two sums round differently.
It reads o and c at bars t-1 and t, BodyLong at bar t-1 and BodyShort at bar t. Bars before TA-Lib’s lookback, the first 11, are 0.
The pattern is its own mirror on reflected prices, o, h, l and c negated with the high and the low swapped, bit for bit. TA-Lib does not test the trend: a harami is a reversal signal only against the trend before it, a bullish one after a decline and a bearish one after an advance, which the caller supplies.
A bar whose decision reads a NaN, +Inf or -Inf, among its prices or through the window of a threshold, is 0, and later bars recover once the value has left the windows. TA-Lib compares infinities as numbers and keeps a NaN running total to the end of the call. A caller who wants missing bars to stay missing can mask the result with MaskNonFinite. A table that starts later gives, from its own lookback on, the same values, bit for bit where the window sums are exact and otherwise to rounding.
The open o, high h and low l must each have the receiver’s length, otherwise the result is Series{}. They are the columns of one bar table, so they are not right-aligned. The cost is O(len(s)); the result is fresh storage and the inputs are not modified.
CandleInvertedHammer
func (s Series) CandleInvertedHammer(o, h, l Series) Series
CandleInvertedHammer is 1 on TA-Lib’s inverted hammer (CDLINVERTEDHAMMER), a small real body gapping down from the prior body with a long upper shadow and a very short lower shadow, and 0 otherwise.
The receiver is the close, written c below, so a caller writes c.CandleInvertedHammer(o, h, l). Bar t is 1 when its real body |c - o| is below BodyShort at bar t, its upper shadow h - max(o, c) is above ShadowLong at bar t, its lower shadow min(o, c) - l is below ShadowVeryShort at bar t, and its body gaps down from the prior body, max(o, c) at bar t below min(o, c) at bar t-1; otherwise it is 0. Every test is strict. The settings are BodyShort (CandleRealBody, 10, 1), the mean body of bars t-10 to t-1, ShadowLong (CandleRealBody, 0, 1), the body of bar t itself, so the upper shadow must be longer than the body, and ShadowVeryShort (CandleHighLow, 10, 0.1), a tenth of the mean range of bars t-10 to t-1.
Each threshold is c.CandleAverage(o, h, l, kind, n, factor) at TA-Lib’s default setting, bit for bit; it agrees with TA-Lib’s running total to rounding, and exactly where the window sums are exact, as on prices in quarters. So the method equals TA-Lib 0.6.4, its integer output divided by 100, except where a compared value sits on a tie that the two sums round differently.
It reads o, h, l and c at bar t, o and c at bar t-1, and BodyShort, ShadowLong and ShadowVeryShort at bar t. Bars before TA-Lib’s lookback, the first 11, are 0.
CandleShootingStar is the same shape gapping up, and the two do not fire on the same bar, since their gaps are opposite and strict. TA-Lib does not test the trend: an inverted hammer is a bottom reversal only after a decline, which the caller supplies.
A bar whose decision reads a NaN, +Inf or -Inf, among its prices or through the window of a threshold, is 0, and later bars recover once the value has left the windows. TA-Lib compares infinities as numbers and keeps a NaN running total to the end of the call. A caller who wants missing bars to stay missing can mask the result with MaskNonFinite. A table that starts later gives, from its own lookback on, the same values, bit for bit where the window sums are exact and otherwise to rounding.
The open o, high h and low l must each have the receiver’s length, otherwise the result is Series{}. They are the columns of one bar table, so they are not right-aligned. The cost is O(len(s)); the result is fresh storage and the inputs are not modified.
CandleMorningStar
func (s Series) CandleMorningStar(o, h, l Series, penetration float64) Series
CandleMorningStar is 1 on TA-Lib’s morning star (CDLMORNINGSTAR), a long black candle, a short body gapping down and a white candle closing more than penetration times the first body above the first close, and 0 otherwise.
The receiver is the close, written c below, so a caller writes c.CandleMorningStar(o, h, l, 0.3). Bar t is 1 when bar t-2 is black, c < o, with a real body |c - o| above BodyLong at bar t-2, bar t-1 has a body at most BodyShort at bar t-1 and gaps down, max(o, c) at bar t-1 below min(o, c) at bar t-2, and bar t is white, c >= o, with a body above BodyShort at bar t, longer than short, and c[t] > c[t-2] + penetration times the first body, the product rounded before the addition as in TA-Lib 0.6.4; otherwise it is 0. Only the star’s body test is inclusive. The settings are BodyLong (CandleRealBody, 10, 1) and BodyShort (CandleRealBody, 10, 1), each the mean body of the ten bars before the bar it is read at, so the two series are equal.
The penetration is a fraction of the first candle’s real body. TA-Lib’s default is 0.3. A penetration of 0 asks only for a close above the first close; values above 1 are valid and ask for a close above the first open, since there is no upper test. A negative, NaN or infinite penetration gives zeros of the receiver’s length. Lengths are checked before penetration.
Each threshold is c.CandleAverage(o, h, l, kind, n, factor) at TA-Lib’s default setting, bit for bit; it agrees with TA-Lib’s running total to rounding, and exactly where the window sums are exact, as on prices in quarters. So the method equals TA-Lib 0.6.4, its integer output divided by 100, except where a compared value sits on a tie that the two sums round differently.
It reads o and c at bars t-2 to t, BodyLong at bar t-2, and BodyShort at bars t-1 and t. Bars before TA-Lib’s lookback, the first 12, are 0.
CandleEveningStar is the exact mirror: on reflected prices, o, h, l and c negated with the high and the low swapped, the negated CandleEveningStar is this method bit for bit at every penetration, as in TA-Lib 0.6.4. TA-Lib 0.8.1 writes this method’s line as a fused multiply-add, which can move it by one ulp, so a close exactly on the 0.6.4 line can differ there, and the two stars are then not exact mirrors; this method keeps the rounding of 0.6.4. TA-Lib does not test the trend: a morning star is a bottom reversal only after a decline, which the caller supplies.
A bar whose decision reads a NaN, +Inf or -Inf, among its prices or through the window of a threshold, is 0, and later bars recover once the value has left the windows. TA-Lib compares infinities as numbers and keeps a NaN running total to the end of the call. A caller who wants missing bars to stay missing can mask the result with MaskNonFinite. A table that starts later gives, from its own lookback on, the same values, bit for bit where the window sums are exact and otherwise to rounding.
The open o, high h and low l must each have the receiver’s length, otherwise the result is Series{}. They are the columns of one bar table, so they are not right-aligned. The cost is O(len(s)); the result is fresh storage and the inputs are not modified.
CandlePiercing
func (s Series) CandlePiercing(o, h, l Series) Series
CandlePiercing is 1 on TA-Lib’s piercing pattern (CDLPIERCING), a long white candle that opens below the low of a long black candle and closes inside its body, above the midpoint, and 0 otherwise.
The receiver is the close, written c below, so a caller writes c.CandlePiercing(o, h, l). Bar t is 1 when bar t-1 is black, c < o, with a real body |c - o| above BodyLong at bar t-1, bar t is white, c >= o, with a body above BodyLong at bar t, o[t] < l[t-1], c[t] < o[t-1] and c[t] > c[t-1] + 0.5 times the first body, the product rounded before the addition as in TA-Lib 0.6.4; otherwise it is 0. Every test is strict. The fraction of the first body is fixed at 0.5, as in TA-Lib, which has no penetration parameter here, and the second body must be long too. The setting BodyLong (CandleRealBody, 10, 1) is the mean body of the ten bars before the bar it is read at. TA-Lib 0.8.1 writes the midpoint line as a fused multiply-add; the product by 0.5 is exact unless the first body is subnormal, so the two agree on all practical data.
Each threshold is c.CandleAverage(o, h, l, kind, n, factor) at TA-Lib’s default setting, bit for bit; it agrees with TA-Lib’s running total to rounding, and exactly where the window sums are exact, as on prices in quarters. So the method equals TA-Lib 0.6.4, its integer output divided by 100, except where a compared value sits on a tie that the two sums round differently.
It reads o, l and c at bar t-1, o and c at bar t, and BodyLong at bars t-1 and t. Bars before TA-Lib’s lookback, the first 11, are 0.
CandleDarkCloudCover is the bearish counterpart but not an exact mirror: it takes a penetration, has no size test on its second candle, and compares the open with the prior high. TA-Lib does not test the trend: a piercing pattern is a bottom reversal only after a decline, which the caller supplies.
A bar whose decision reads a NaN, +Inf or -Inf, among its prices or through the window of a threshold, is 0, and later bars recover once the value has left the windows. TA-Lib compares infinities as numbers and keeps a NaN running total to the end of the call. A caller who wants missing bars to stay missing can mask the result with MaskNonFinite. A table that starts later gives, from its own lookback on, the same values, bit for bit where the window sums are exact and otherwise to rounding.
The open o, high h and low l must each have the receiver’s length, otherwise the result is Series{}. They are the columns of one bar table, so they are not right-aligned. The cost is O(len(s)); the result is fresh storage and the inputs are not modified.
CandleShootingStar
func (s Series) CandleShootingStar(o, h, l Series) Series
CandleShootingStar is -1 on TA-Lib’s shooting star (CDLSHOOTINGSTAR), a small real body gapping up from the prior body with a long upper shadow and a very short lower shadow, and 0 otherwise.
The receiver is the close, written c below, so a caller writes c.CandleShootingStar(o, h, l). Bar t is -1 when its real body |c - o| is below BodyShort at bar t, its upper shadow h - max(o, c) is above ShadowLong at bar t, its lower shadow min(o, c) - l is below ShadowVeryShort at bar t, and its body gaps up from the prior body, min(o, c) at bar t above max(o, c) at bar t-1; otherwise it is 0. Every test is strict. The settings are BodyShort (CandleRealBody, 10, 1), the mean body of bars t-10 to t-1, ShadowLong (CandleRealBody, 0, 1), the body of bar t itself, so the upper shadow must be longer than the body, and ShadowVeryShort (CandleHighLow, 10, 0.1), a tenth of the mean range of bars t-10 to t-1.
Each threshold is c.CandleAverage(o, h, l, kind, n, factor) at TA-Lib’s default setting, bit for bit; it agrees with TA-Lib’s running total to rounding, and exactly where the window sums are exact, as on prices in quarters. So the method equals TA-Lib 0.6.4, its integer output divided by 100, except where a compared value sits on a tie that the two sums round differently.
It reads o, h, l and c at bar t, o and c at bar t-1, and BodyShort, ShadowLong and ShadowVeryShort at bar t. Bars before TA-Lib’s lookback, the first 11, are 0.
CandleInvertedHammer is the same shape gapping down, and the two do not fire on the same bar, since their gaps are opposite and strict. TA-Lib does not test the trend: a shooting star is a top reversal only after an advance, which the caller supplies.
A bar whose decision reads a NaN, +Inf or -Inf, among its prices or through the window of a threshold, is 0, and later bars recover once the value has left the windows. TA-Lib compares infinities as numbers and keeps a NaN running total to the end of the call. A caller who wants missing bars to stay missing can mask the result with MaskNonFinite. A table that starts later gives, from its own lookback on, the same values, bit for bit where the window sums are exact and otherwise to rounding.
The open o, high h and low l must each have the receiver’s length, otherwise the result is Series{}. They are the columns of one bar table, so they are not right-aligned. The cost is O(len(s)); the result is fresh storage and the inputs are not modified.
CandleThreeBlackCrows
func (s Series) CandleThreeBlackCrows(o, h, l Series) Series
CandleThreeBlackCrows is -1 on TA-Lib’s three black crows (CDL3BLACKCROWS), a white candle and then three falling black candles with very short lower shadows, each opening inside the prior body, and 0 otherwise.
The receiver is the close, written c below, so a caller writes c.CandleThreeBlackCrows(o, h, l). Bar t is -1 when bar t-3 is a white candle, c >= o, a doji counting as white, bars t-2, t-1 and t are each black, c < o, with a lower shadow min(o, c) - l below ShadowVeryShort at their own bar, each crow opens inside the prior crow’s body, o[t-1] < o[t-2] with o[t-1] > c[t-2] and o[t] < o[t-1] with o[t] > c[t-1], the first crow closes below the white candle’s high, h[t-3] > c[t-2], and the closes fall, c[t-2] > c[t-1] > c[t]; otherwise it is 0. Every test is strict. There is no body-size, Near or Far test, unlike the soldiers. The setting ShadowVeryShort (CandleHighLow, 10, 0.1) is a tenth of the mean range of the ten bars before the bar it is read at.
Each threshold is c.CandleAverage(o, h, l, kind, n, factor) at TA-Lib’s default setting, bit for bit; it agrees with TA-Lib’s running total to rounding, and exactly where the window sums are exact, as on prices in quarters. So the method equals TA-Lib 0.6.4, its integer output divided by 100, except where a compared value sits on a tie that the two sums round differently.
It reads o, h and c at bar t-3, o, l and c at bars t-2 to t, and ShadowVeryShort at bars t-2 to t. Bars before TA-Lib’s lookback, the first 13, are 0.
CandleThreeWhiteSoldiers is the bullish counterpart but not an exact mirror: it has Near, Far and body-size tests and no test of the candle before the three. TA-Lib does not test the trend: three black crows are a top reversal only after an advance, which the caller supplies.
A bar whose decision reads a NaN, +Inf or -Inf, among its prices or through the window of a threshold, is 0, and later bars recover once the value has left the windows. TA-Lib compares infinities as numbers and keeps a NaN running total to the end of the call. A caller who wants missing bars to stay missing can mask the result with MaskNonFinite. A table that starts later gives, from its own lookback on, the same values, bit for bit where the window sums are exact and otherwise to rounding.
The open o, high h and low l must each have the receiver’s length, otherwise the result is Series{}. They are the columns of one bar table, so they are not right-aligned. The cost is O(len(s)); the result is fresh storage and the inputs are not modified.
CandleThreeWhiteSoldiers
func (s Series) CandleThreeWhiteSoldiers(o, h, l Series) Series
CandleThreeWhiteSoldiers is 1 on TA-Lib’s three white soldiers (CDL3WHITESOLDIERS), three white candles with rising opens and closes and very short upper shadows, each opening inside or near the prior body, and 0 otherwise.
The receiver is the close, written c below, so a caller writes c.CandleThreeWhiteSoldiers(o, h, l). Bar t is 1 when bars t-2, t-1 and t are each white, c >= o, with an upper shadow h - max(o, c) below ShadowVeryShort at their own bar, the closes rise, c[t] > c[t-1] > c[t-2], the opens rise, o[t-1] > o[t-2] and o[t] > o[t-1], each open is inside or near the prior body, o[t-1] <= c[t-2] + Near at bar t-2 and o[t] <= c[t-1] + Near at bar t-1, no body is far shorter than the one before it, |c - o| at bar t-1 above |c - o| at bar t-2 less Far at bar t-2 and |c - o| at bar t above |c - o| at bar t-1 less Far at bar t-1, and the third body is above BodyShort at bar t; otherwise it is 0. The Near bands are inclusive and every other test is strict. Only the third body is tested against BodyShort, as in TA-Lib’s code, whose comment asks for all three; a doji soldier, c = o, counts as white. The settings are ShadowVeryShort (CandleHighLow, 10, 0.1), a tenth of the mean range of the ten bars before the bar it is read at, Near (CandleHighLow, 5, 0.2) and Far (CandleHighLow, 5, 0.6), a fifth and six tenths of the mean range of the five bars before, and BodyShort (CandleRealBody, 10, 1), the mean body of bars t-10 to t-1.
Each threshold is c.CandleAverage(o, h, l, kind, n, factor) at TA-Lib’s default setting, bit for bit; it agrees with TA-Lib’s running total to rounding, and exactly where the window sums are exact, as on prices in quarters. So the method equals TA-Lib 0.6.4, its integer output divided by 100, except where a compared value sits on a tie that the two sums round differently.
It reads o, h and c at bars t-2 to t, ShadowVeryShort at bars t-2 to t, Near and Far at bars t-2 and t-1, and BodyShort at bar t. Bars before TA-Lib’s lookback, the first 12, are 0.
CandleThreeBlackCrows is the bearish counterpart but not an exact mirror: it asks for a white candle before the three and has no Near, Far or body-size test. TA-Lib does not test the trend: three white soldiers are a bottom reversal only after a decline, which the caller supplies.
A bar whose decision reads a NaN, +Inf or -Inf, among its prices or through the window of a threshold, is 0, and later bars recover once the value has left the windows. TA-Lib compares infinities as numbers and keeps a NaN running total to the end of the call. A caller who wants missing bars to stay missing can mask the result with MaskNonFinite. A table that starts later gives, from its own lookback on, the same values, bit for bit where the window sums are exact and otherwise to rounding.
The open o, high h and low l must each have the receiver’s length, otherwise the result is Series{}. They are the columns of one bar table, so they are not right-aligned. The cost is O(len(s)); the result is fresh storage and the inputs are not modified.
LowerShadow
func (s Series) LowerShadow(o, l Series) Series
LowerShadow returns each candle’s lower shadow, the lesser of the open o and the receiver’s close less the low l.
With close c as the receiver, each bar is min(o[t], c[t]) - l[t], using built-in min. The body bottom is c.Minimum(o), so c.Minimum(o).Sub(l) gives the same result bit for bit. NaN in any input gives NaN, and infinities follow IEEE subtraction; +Inf minus +Inf is NaN. The built-in selects -0 from +0 and -0, and the subtraction preserves IEEE signed zeros.
TA-Lib’s TA_LOWERSHADOW uses c >= o to choose the body’s bottom. That selection differs when the open is NaN, where it reads the close, and for a close of -0 with an open of +0. Prices are unchecked; a low above the body gives a negative shadow.
Upper shadow plus lower shadow plus real body, in that order, equals h.Sub(l) exactly on well-formed bars with 0 < l and h <= 2l, and on prices in quarters; it agrees to rounding otherwise.
The open and low must each have the receiver’s length, otherwise the result is Series{}. These are columns of one bar table and are not right-aligned. Empty or nil inputs of matching length give an empty, non-nil result. There is no warm-up: the first valid index is 0. The method takes one pass, returns fresh storage and does not modify its inputs.
RealBody
func (s Series) RealBody(o Series) Series
RealBody returns the size of each candle’s real body, the absolute difference between the receiver’s close and the open o.
With close c as the receiver, the value at bar t is |c[t] - o[t]|, TA-Lib’s TA_REALBODY, and equals c.Sub(o).Abs() bit for bit. A tie of finite values gives +0, including either order of +0 and -0, because the absolute value clears the sign. A NaN in either input gives NaN. Infinities follow IEEE arithmetic: an infinite difference gives +Inf, and subtracting the same infinity gives NaN.
The signed body is c.Sub(o); its n-bar average agrees with QStick(o, c, n) to rounding. The candle’s colour is c.GTE(o): 1 for white and 0 for black, with a tie counting as white.
Upper shadow plus lower shadow plus real body, in that order, equals h.Sub(l) exactly on well-formed bars with 0 < l and h <= 2l, and on prices in quarters; it agrees to rounding otherwise.
The open must have the receiver’s length, otherwise the result is Series{}. These are columns of one bar table and are not right-aligned. Empty or nil inputs of matching length give an empty, non-nil result. There is no warm-up: the first valid index is 0. The method takes one pass, returns fresh storage and does not modify its inputs.
UpperShadow
func (s Series) UpperShadow(o, h Series) Series
UpperShadow returns each candle’s upper shadow, the high h less the greater of the open o and the receiver’s close.
With close c as the receiver, each bar is h[t] - max(o[t], c[t]), using built-in max. The body top is c.Maximum(o), so h.Sub(c.Maximum(o)) gives the same result bit for bit. NaN in any input gives NaN, and infinities follow IEEE subtraction; +Inf minus +Inf is NaN. The built-in selects +0 from +0 and -0, and the subtraction preserves IEEE signed zeros.
TA-Lib’s TA_UPPERSHADOW uses c >= o to choose the body’s top. That selection differs when the close is NaN, where it reads the open, and for a close of -0 with an open of +0. Prices are unchecked; a high below the body gives a negative shadow.
Upper shadow plus lower shadow plus real body, in that order, equals h.Sub(l) exactly on well-formed bars with 0 < l and h <= 2l, and on prices in quarters; it agrees to rounding otherwise.
The open and high must each have the receiver’s length, otherwise the result is Series{}. These are columns of one bar table and are not right-aligned. Empty or nil inputs of matching length give an empty, non-nil result. There is no warm-up: the first valid index is 0. The method takes one pass, returns fresh storage and does not modify its inputs.
Compositions
Andrea Unger’s indecision filter allows entries on a day after an indecisive one, a day whose body, the distance from open to close (RealBody), was less than a fraction of its range, and his weekly factor applies the same test to the five sessions before. Recipe values (the daily and weekly factors) are NaN before their first valid index (SetN(F, math.NaN())), and the gates read 0 there, closed: every shifted series a pad can satisfy is masked, or the gate is. Prices are positive and finite and the bars are the caller’s daily bars, one a session (Unger trades 15- and 30-minute bars, which the intraday variant of the first section reaches only through a session flag); series are paired by position, so the caller aligns them by timestamp first. Both recipes read the open o. The gates place no order: Unger’s breakout stops, session windows and exits on the close belong to the caller’s model. An exact decimal tie of a body with a fraction of a range is decided by binary rounding, first of the price differences: the page pins the division-free comparison for the gates and Div for the factors, the two agree for a fraction that is a power of two, and the goldens run on prices in sixty-fourths, where ties are exact. Unger’s backtests are over data we do not have: evidence, not a reference.
Indecision gate and daily factor (Unger)
Andrea Unger (“Filters On The Test Bench”, Stocks & Commodities V40:12, November 2022, pp. 12-15) calls the market “indecisive” “If at the end of the session the distance between start and finish is less than 50% of the whole excursion”, and his Figure 2 listing tests the prior session division-free and strictly, absvalue(opens(1)-closes(1)) < myfactor*(highs(1)-lows(1)), so a flat session (H = L) is not indecisive. In “The Daily Factor: A Volatility Filter” (Stocks & Commodities V41:7, June 2023, pp. 26-31) he divides, “DF = Body / Range”, and assigns the DF only if (highS(1) - lowS(1)) <> 0, so the last DF is kept over a flat session. t6IndecisionGate returns the 2022 gate gate, the canonical form, the prior session’s daily factor df with the 2023 carry rule and the 2023 gate gate5275.
gate := c.RealBody(o).LT(h.Sub(l).Mul(f)).Shift(1)
dfc := c.RealBody(o).Div(h.Sub(l)).ValueWhen(h.GT(l))
df := dfc.Shift(1).SetN(1, math.NaN())
gate5275 := dfc.LT(f).Shift(1)- Inputs: daily opens, highs, lows and closes; f the fraction of the range: 0.5 for his “pattern 1”, 0.35 in 2023 (“which maximizes profits and shows stable results in its neighborhood”) and 0.4 to 0.9 in his later articles. An f above 1 disables
gate(his default is 2) except after a flat bar, which still closes it; at f = 1 the strict<still blocks the day after a session whose DF is exactly 1, its open and close at the extremes. - First valid index: 1 for all three:
gateandgate5275read 0, closed, on bar 0 (theShiftpad) anddfis NaN there; before the first bar with a rangedfis NaN andgate5275reads 0, andgatealso reads 0 after a flat bar or a bar with a NaN.gateis window-local (one prior bar); the carry makesdfandgate5275latch over any run of flat bars. - Anchors:
gateequalsc.RealBody(o).LT(c.CandleAverage(o, h, l, CandleHighLow, 0, f)).Shift(1)bit for bit (with n = 0CandleAverageis f times the bar’s own range) and a loop of Figure 2’s pattern line;dfcequals a loop of the 2023 listing from the first bar with a range. |BOP| = DF for h >= l: TA-Lib’s Balance of Power,(Close - Open)/(High - Low)and 0 for a range at or below 0, has no method here, andc.Sub(o).Div(h.Sub(l)).Abs()equalsc.RealBody(o).Div(h.Sub(l))bit for bit. The seven-bar witness (o 5, 5, 5, 4.5, 4.25, 4.25, 4; h 5, 5, 5.5, 5, 4.5, 4.25, 4.5; l 5, 5, 4.5, 4.25, 4, 4.25, 3.5; c 5, 5, 5.25, 4.25, 4.25, 4.25, 4.25; bars 0, 1 and 5 flat; f 0.5) gives the canonicalgate[0 0 0 1 1 1 0], the 2023 gate [0 0 0 1 1 1 1] anddf[NaN NaN NaN 0.25 0.3333333333333333 0 0]. For f a power of two the division and division-free forms agree; at decimal ties for f such as 0.35 or 0.7 they can differ (O 1.00, H 4.00, L 1.00, C 2.05 at 0.35: the DF 0.3499999999999999 is below 0.35, while the product test 1.0499999999999998 < 1.0499999999999998 fails). - Traps: the bare-
Divgate,c.RealBody(o).Div(h.Sub(l)).LT(0.5).Shift(1), reads a flat session’s DF of 0 as maximum indecision and opens: [0 1 1 1 1 1 1] on the seven bars, a reading no Unger listing has. The dossier’sdf.Shift(1).LT(f)compares the +0 pad and opens bar 0, and its “Unger keeps the previous DF” holds from 2023 on: the 2022 listing is division-free and keeps nothing. HisVar: DF(0)starts at 0, so his 2023 gate is open before the first session with a range, wheregate5275is closed (a start-of-data difference). NoBalanceOfPoweris shipped (the signed ratioc.Sub(o).Div(h.Sub(l))is the QStick compositions’ balance of power), andCandleDojiis another test, inclusive and against the average range of earlier bars. It isc.RealBody(o).LTE(c.CandleAverage(o, h, l, CandleHighLow, 10, 0.1)), soc.CandleDoji(o, h, l).Shift(1)isgatewith f = 0.1,LTEforLTand the mean range of the ten bars before for the bar’s own range. - Variant: intraday bars from the caller’s session-start flag
sf(1 on each session’s first bar), in one line: withev := sf.SetN(1, 0)the prior session’s close, high, low and open arec.Shift(1).ValueWhen(ev),h.HighestSince(sf).Shift(1).ValueWhen(ev),l.LowestSince(sf).Shift(1).ValueWhen(ev)ando.ValueWhen(sf).Shift(1).ValueWhen(ev), and the gate is the canonical comparison of them, equal to a loop that aggregates the sessions (402 bars in 58 sessions). - Missing piece: the session-start flag itself, which needs timestamps: the missing piece “holding a daily value on intraday bars” of the Average Directional Index page
t6-directional-index.md(“ADX environments and the rising-ADX gate”), whose step “applies the last completed session’s value to the next session’s bars” the variant writes out. NoPriorPeriod*method is shipped, and the open composes as above. - Evidence: the 2022 article’s four futures on 15-minute bars from 2010 (DAX, crude oil, gold and the E-mini S&P 500), each with a random series as a control, costs not stated: f 0.5 against 2 (off) cut the trades by about half and raised the average trade on all four markets (DAX 48.36 to 63.96, gold 41.93 to 88.47), and on the random series it lowered the DAX’s and crude’s but raised gold’s and the E-mini’s (32.33 to 38.68), though the text says of the E-mini that the filter “has no effect on randomized data”. The 2023 article (crude oil on 30-minute bars from 2010, a 40-bar channel, no stop) chose 0.35 from its Figure 5 sweep, in sample (net profit is higher at 0.65), and its level-0 row has no trade, as the strict
<requires. Evidence, not a reference. - Pinned by
TestRecipeT6IndecisionGate; runnable exampleExampleSeries_RealBody_indecisionGate.
Weekly factor (Unger)
Unger’s “The Weekly Factor Pattern” (Stocks & Commodities V41:9, August 2023, pp. 14-17) “looks at the trading range in a trading week (that is, the highest high and the lowest low of the last five sessions) and then compares that to the open of the session five days ago and the close of the session one day ago”; his strategy listing (p. 15) tests AbsValue(OpenS(5) - CloseS(1)) < RangeFilter * (maxlist(highS(1), ..., highS(5)) - minlist(lowS(1), ..., lowS(5))), strict and division-free. t6WeeklyFactor returns the factor wfac, the five-session body over the five-session range of the bars before t, and the gate wgate.
wbody := c.Shift(1).RealBody(o.Shift(5))
wrange := h.Highest(5).Sub(l.Lowest(5)).Shift(1)
wfac := wbody.Div(wrange).SetN(5, math.NaN())
wgate := wbody.LT(wrange.Mul(wf)).SetN(5, 0)- Inputs: daily opens, highs, lows and closes, one bar a session (his strategy runs on 15-minute bars); wf 0.7 (“This value of 0.7 means that the body should be less than 70% of the total weekly range”), 0.85 in a later article (Stocks & Commodities V44:7, July 2026); his Figure 1’s 50% is a schematic label.
- First valid index: 5 for both:
wfacis NaN andwgatereads 0 on bars 0 to 4, whereo.Shift(5)is the pad and the windows are still expanding. TheSetN(5, 0)ofwgateis needed: on bars 1 to 4 the padded open and the expanding range can pass the test (highs 10, lows 1, opens and closes 2 fire there unmasked), which a constructed case pins. - Anchors: the body is |O[t-5] - C[t-1]| and the range the highest high less the lowest low of bars t-5 to t-1, the current bar excluded;
wgateequals a loop of the strategy line bit for bit from bar 5, and the dossier’s rangeh.Shift(1).Highest(5).Sub(l.Shift(1).Lowest(5))gives the same gate after the mask (the two ranges differ only on bars 1 to 4, where the pad entersLowest). A five-session window with H = L everywhere reads closed, andwfacis 0 there. The strict<is pinned by a constructed case: a range of 20 ticks with a body of 14 ties the gate at 0.7. - Traps: the p. 14 text expression,
AbsValue(OpenS(5) - CloseS(1)) < RangeFilter * maxlist(...) - minlist(...), lacks the brackets the strategy line has, so it reads wf times the highest high less the lowest low; the recipe takes the bracketed line. The dossier’swf.LT(0.7)on the quotient reads a flat five-session window (a factor of 0, byDiv’s zero rule) as open, unlike the listing’s division-free test. - Evidence: his Figure 4 (three metals on 15-minute bars, RangeFilter swept from 0.1 to 1) has 9,872 trades at 1 and 7,904 at 0.7 (“about 2,000 trades (20%) are filtered out”), whose average trades Figures 3 and 6 give as 20.56 and 30.03; net profit peaks at 0.8 (253,750) while the text picks 0.7 for its drawdown, in sample, and the RangeFilter 0 row has no trade, as the strict
<requires. Evidence, not a reference. - Pinned by
TestRecipeT6WeeklyFactor.
Heikin-Ashi
The candles and the oscillators
Heikin-ashi candles (Valcu) redraw each bar from the bar’s open, high, low and close and the previous heikin-ashi candle: the close is the bar’s average price, the open is the midpoint of the previous heikin-ashi body, and the high and low take in the new open and close. Vervoort’s HACO and HACOLT read the candles, zero-lag TEMAs and his printed candle conditions into a state. The six methods of this category take the opens, highs, lows and closes of one table of bars as (o, h, l, c) after an unused receiver, as GarmanKlassVolatility(o, h, l, c, n) does; callers write c.HACO(o, h, l, c, 34).
| Method | On each bar of the span |
|---|---|
HeikinAshiOpen | the open: o[t0] on the first bar, then the midpoint of the previous heikin-ashi open and close |
HeikinAshiClose | the close: (((o + h) + l) + c) / 4 |
HeikinAshiHigh | the high: Go’s max of the bar’s high, the heikin-ashi open and the heikin-ashi close |
HeikinAshiLow | the low: Go’s min of the bar’s low, the heikin-ashi open and the heikin-ashi close |
HACO | Vervoort’s latch: 1 from an up switch, 0 from a down switch, 0 before the first |
HACOLT | Vervoort’s long-term position: +1 long, 0 flat, -1 short, 0 before the first switch |
They share these rules:
- One table of bars. o, h, l and c must have equal lengths; otherwise every method returns an empty series,
Series{}, before it reads its parameters. The bars are not right-aligned (OHLC columns are one table, not two series), and the receiver is not read. - The span. t0 is the first bar at which all four inputs are finite, and the span runs from t0 to the bar before the next bar with a NaN, +Inf or -Inf in any input. Every output is NaN outside the span, after it too, even where the inputs are finite again. On the span each method computes as if the series started at t0: there is no warm-up, and the first valid index is t0.
- The seed. The open starts at the real open of t0, as Valcu’s spreadsheet does. pandas-ta and many charting ports start it at
(o[0] + c[0]) / 2; the two opens differ by(o[0] - c[0]) / 2^(t+1)at bar t, which halves each bar and rounds away once it is below half a unit in the last place: on random walks whose daily moves are about a hundredth of the price, the opens agree bit for bit from bar 46 on in the median, and from bar 60 on at the latest in 3,000 such walks. - Bars as given. A malformed bar (an open or close outside the range, or a high below the low) is computed as it is, and overflow follows IEEE arithmetic without ending the span.
- The oscillators call the package’s
TEMAon the span, whose simple-average seeds may differ from MetaStock’s (the sources do not document MetaStock’s), and their switches in the first 6(n-1) bars or so depend on the seeding and on where the slice starts. Each is a latch that remembers its last switch.
Sources. Valcu, “Using The Heikin-Ashi Technique”, Technical Analysis of Stocks & Commodities, February 2004 (the candles, haDiffCO and its average, the white and black bodies); Vervoort, “Trading With The Heikin-Ashi Candlestick Oscillator”, Technical Analysis of Stocks & Commodities, December 2008 (HACO); Vervoort, “Long-Term Trading Using Exchange Traded Funds”, Technical Analysis of Stocks & Commodities, July 2012 (HACOLT); Vervoort, “The Quest For Reliable Crossovers”, Technical Analysis of Stocks & Commodities, May 2008 (the zero-lag TEMA crossover). The recipes under “Compositions” below build Valcu’s indicators, Vervoort’s four-point close, smoothed heikin-ashi and the crossover from the views.
HACO
func (s Series) HACO(o Series, h Series, l Series, c Series, n int) Series
HACO returns Vervoort’s heikin-ashi candlestick oscillator, a latch that turns 1 when his up leg takes over from his down leg and 0 on the reverse, from zero-lag TEMAs of length n of the median price and of his four-point heikin-ashi close, with his printed candle conditions.
The receiver is not used. The inputs o, h, l and c must have equal lengths; otherwise the result is an empty series, Series{}. Empty inputs also return a non-nil empty series. Lengths are checked before parameters. If n < 1 the result is len(o) zeros, including at bars with non-finite inputs. Any positive n is valid, including lengths greater than the span. The default is 34 bars, using a single length for both legs as Vervoort recommends.
With valid parameters, let t0 be the first bar at which all four inputs are finite. Values before t0 are NaN. The first later bar with a non-finite input and every later bar are NaN, even if finite inputs resume. If there is no finite starting bar, all values are NaN. The candles, averages and latch run on the span as if the series started there. There is no warm-up: the first valid index is t0. A malformed bar is computed as it is; overflow follows IEEE arithmetic and does not end the span.
Valcu’s open is seeded with o[t0] and recursively fed by ohlc4 = (((o + h) + l) + c) / 4. Vervoort’s four-point close haCV is (((ohlc4 + haOpen) + max(h, haOpen)) + min(l, haOpen)) / 4, in that printed order. Its green test is haCV >= haOpen, including ties, and its red test is haCV < haOpen. For either haCV or the median price (h + l) / 2, set t = x.TEMA(n); its zero-lag average is t.Add(t.Sub(t.TEMA(n))). The difference d subtracts the zero-lag four-point close from the zero-lag median and is compared with zero.
Each leg starts with Alert over 2 bars: a green candle now or previously keeps the up leg, while a red candle now or previously keeps the down leg. The up leg also keeps when d >= 0, and the down leg when d < 0. The up keepall is ku OR (previous ku AND c >= o) OR c >= previous c; the down keepall is kd OR (previous kd AND c < o) OR c < previous c. This is the printed grouping: an up close or down close can keep its leg on its own. The article’s step 2 instead groups both price tests behind the previous keeping flag; that alternative is not used.
A small body satisfies abs(c - o) < (h - l) times 0.35. The up leg utr holds if its keepall holds, or if the previous up keepall holds, the body is small and h >= previous l. The down leg dtr holds if its keepall holds, or if the previous down keepall holds, the body is small and l <= previous h. Every condition that reads the previous bar is false at the first bar.
An up switch upw requires utr and a transition of dtr from true to false; a down switch dnw requires dtr and a transition of utr from true to false. The latch is upw.Sub(dnw).Hysteresis(0, 0).GT(0): it is 0 before the first switch, becomes 1 on an up switch, becomes 0 on a down switch and otherwise holds. The output is 1 or +0.
The package’s TEMA uses three XAverage stages, each passing through its first n-1 inputs and taking an n-bar simple-average seed before the exponential recursion. MetaStock’s seeding is not documented in the sources and may differ, and switches in the first 6(n-1) bars or so depend on the seeding. The latch remembers its last switch, so runs from different history starts can retain different states until they have both passed the same switch.
His crossover recipe uses zl(h.Add(l).Add(c).Div(3)).CrossesAboveS(zl(haCV)) with length 55, where zl is the zero-lag TEMA above and haCV is his four-point close. It comes from Vervoort, “The Quest For Reliable Crossovers”, Technical Analysis of Stocks & Commodities, May 2008.
The work is O(1) per bar, with a fixed number of series allocations and O(1) state for the legs and latch. Invalid parameters allocate only the result. Inputs are unchanged.
Source: Vervoort, “Trading With The Heikin-Ashi Candlestick Oscillator”, Technical Analysis of Stocks & Commodities, December 2008.
HACOLT
func (s Series) HACOLT(o Series, h Series, l Series, c Series, n int, candleSize float64, ltAverage int) Series
HACOLT returns Vervoort’s long-term heikin-ashi candlestick oscillator as +1 (long), 0 (flat) or -1 (short), for his 100, 50 and 0.
The receiver is not used. The inputs o, h, l and c must have equal lengths; otherwise the result is an empty series, Series{}. Empty inputs also return a non-nil empty series. Lengths are checked before parameters. If n < 1, ltAverage < 1, or candleSize is negative or not finite, the result is len(o) zeros, including at bars with non-finite inputs. Both +0 and -0 are valid candle sizes and make the body test false. Any positive n and ltAverage are valid, including lengths greater than the span.
The parameters follow the printed order: n is the common TEMA length in bars, default 55; candleSize is the body threshold as a fraction of the bar’s range, default 1.1; ltAverage is the long-term exponential-average length in bars, default 60. The calculation is bar-agnostic; Vervoort tested weekly bars.
With valid parameters, let t0 be the first bar at which all four inputs are finite. Values before t0 are NaN. The first later bar with a non-finite input and every later bar are NaN, even if finite inputs resume. If there is no finite starting bar, all values are NaN. The candles, averages and states run on the span as if the series started there. There is no warm-up: the first valid index is t0. A malformed bar is computed as it is; overflow follows IEEE arithmetic and does not end the span.
HACOLT uses HACO’s Valcu open, Vervoort four-point close, zero-lag TEMA difference and down leg, with candleSize in place of 0.35. The up leg additionally keeps when c >= haCV or, after the first bar, h > previous h or l > previous l. All previous-bar conditions are false at the first bar.
On well-formed bars with a positive normal range and no overflowing differences, candleSize 1.1 “has no influence” in Vervoort’s words: the body test holds, just as for larger values. Flat bars fail the strict body test at every candleSize. Smaller values can end positions sooner; malformed bars and subnormal ranges can distinguish 1.1 from larger settings.
R is the 0/1 latch formed from the modified legs and starts at 0. The long-term latch LTR starts at 1 and stays there until the first up or down switch; the output is 0 before the first switch. Thereafter R = 1 sets LTR = 1. With R = 0, LTSell is the strict comparison c < c.XAverage(ltAverage) on the span; if it holds, LTR becomes 0, otherwise LTR holds. The output is +1 when R = 1, 0 when R = 0 and LTR = 1, and -1 when R = 0 and LTR = 0, mapping his 100/50/0 to +1/0/-1.
A short opens only with R = 0 and a close below its average and persists until R returns to 1, even if the close recovers above its average. After a long closes, the output stays flat until a close below the average while R remains 0.
As with HACO, TEMA’s simple-average seeds may differ from MetaStock’s, which the sources do not document: switches in the first 6(n-1) bars or so depend on the seeding. Changing where history starts can also change the latches’ later states.
The work is O(1) per bar, with a fixed number of series allocations and O(1) state for the legs and latches. Invalid parameters allocate only the result. Inputs are unchanged.
Source: Vervoort, “Long-Term Trading Using Exchange Traded Funds”, Technical Analysis of Stocks & Commodities, July 2012.
HeikinAshiClose
func (s Series) HeikinAshiClose(o Series, h Series, l Series, c Series) Series
HeikinAshiClose returns the heikin-ashi close, the average of the bar’s open, high, low and close.
The receiver is not used. The inputs o, h, l and c must have equal lengths; otherwise the result is an empty series, Series{}. Empty inputs also return a non-nil empty series.
Let t0 be the first bar at which all four inputs are finite. Values before t0 are NaN. The first later bar with a non-finite input and every later bar are NaN, even if finite inputs resume. If there is no finite starting bar, all values are NaN. There is no warm-up: the first valid index is t0.
The value is (((o + h) + l) + c) / 4, adding in that order before dividing, and equals o.SAverage(h, l, c) on equal-length finite inputs. Its arithmetic does not depend on earlier bars, but the output shares the open’s span. A malformed bar is computed as it is. Overflow follows IEEE arithmetic and does not end the span.
It takes O(1) work per bar in one pass and allocates only the result. Inputs are unchanged.
HeikinAshiHigh
func (s Series) HeikinAshiHigh(o Series, h Series, l Series, c Series) Series
HeikinAshiHigh returns the greatest of the bar’s high, its heikin-ashi open and its heikin-ashi close.
The receiver is not used. The inputs o, h, l and c must have equal lengths; otherwise the result is an empty series, Series{}. Empty inputs also return a non-nil empty series.
Let t0 be the first bar at which all four inputs are finite. Values before t0 are NaN. The first later bar with a non-finite input and every later bar are NaN, even if finite inputs resume. If there is no finite starting bar, all values are NaN. There is no warm-up: the first valid index is t0.
The value is max(h, haOpen, haClose), using Go’s built-in max, which propagates NaN and prefers +0 over -0. A malformed bar is computed as it is. Overflow follows IEEE arithmetic and does not end the span. On well-formed bars without overflow, the close lies within the real range, so the formula reduces to max(h, haOpen). On those bars Vervoort’s four-point close is haC.SAverage(haO, haH, haL), where the names denote the four views. The high depends on the start of history through the recursive open.
It takes O(1) work per bar in one pass and allocates only the result. Inputs are unchanged.
HeikinAshiLow
func (s Series) HeikinAshiLow(o Series, h Series, l Series, c Series) Series
HeikinAshiLow returns the least of the bar’s low, its heikin-ashi open and its heikin-ashi close.
The receiver is not used. The inputs o, h, l and c must have equal lengths; otherwise the result is an empty series, Series{}. Empty inputs also return a non-nil empty series.
Let t0 be the first bar at which all four inputs are finite. Values before t0 are NaN. The first later bar with a non-finite input and every later bar are NaN, even if finite inputs resume. If there is no finite starting bar, all values are NaN. There is no warm-up: the first valid index is t0.
The value is min(l, haOpen, haClose), using Go’s built-in min, which propagates NaN and prefers -0 over +0. A malformed bar is computed as it is. Overflow follows IEEE arithmetic and does not end the span. On well-formed bars without overflow, the close lies within the real range, so the formula reduces to min(l, haOpen). The low depends on the start of history through the recursive open.
It takes O(1) work per bar in one pass and allocates only the result. Inputs are unchanged.
HeikinAshiOpen
func (s Series) HeikinAshiOpen(o Series, h Series, l Series, c Series) Series
HeikinAshiOpen returns the heikin-ashi open of each bar, the midpoint of the previous heikin-ashi body, seeded with the real open of the first finite bar (Valcu 2004).
The receiver is not used. The inputs o, h, l and c must have equal lengths; otherwise the result is an empty series, Series{}. Empty inputs also return a non-nil empty series.
Let t0 be the first bar at which all four inputs are finite. Values before t0 are NaN. The first later bar with a non-finite input and every later bar are NaN, even if finite inputs resume. If there is no finite starting bar, all values are NaN. On the span, the seed is o[t0], copied verbatim as in Valcu’s spreadsheet, and each later value is (haOpen[t-1] + haClose[t-1]) / 2. There is no warm-up: the first valid index is t0. A malformed bar is computed as it is; overflow follows IEEE arithmetic and does not end the span.
This is a recursive exponential average with alpha 1/2, lagged one bar. With t0 = 0 and non-empty finite inputs of moderate magnitude, without overflow or subnormal rounding, it equals Series{o[0]}.Append(haC[:len(o)-1]).XAverageAlpha(0.5), where haC is the close view. The alternative haC.XAverage(3).Shift(1) has different starting values because of its simple-average seed and Shift’s first zero, then agrees after those seed effects round away.
For any view X, X(o, h, l, c)[t0:] equals X(o[t0:], h[t0:], l[t0:], c[t0:]) bit for bit, with NaNs matched. The open depends on where its history starts, so moving the start of a sliding slice can change it. For a series starting at bar 0, pandas-ta instead seeds with (o[0] + c[0]) / 2. The Valcu open minus that open is (o[0] - c[0]) / 2^(t+1) at bar t in exact arithmetic. This difference halves each bar and rounds away once it is below half a unit in the last place of the open: on random walks whose daily moves are about a hundredth of the price, the two opens agree bit for bit from bar 46 on in the median, and from bar 60 on at the latest in 3,000 such walks.
Tomorrow’s open is known at today’s close. Valcu’s white body is the strict test haC.GT(haO); a tie is filled, whereas Vervoort’s green test includes a tie. Valcu’s haDiffCO is haC.Sub(haO), and Mov-haDiffCO is haC.Sub(haO).Average(3). Smoothed heikin-ashi averages the inputs first, then applies the candle views.
It takes O(1) work per bar in one pass and allocates only the result. Inputs are unchanged.
Source: Valcu, “Using The Heikin-Ashi Technique”, Technical Analysis of Stocks & Commodities, February 2004.
Compositions
Heikin-ashi recipes
The recipes below read the four views of one call each, haO, haC, haH and haL:
haO, haC := c.HeikinAshiOpen(o, h, l, c), c.HeikinAshiClose(o, h, l, c)
haH, haL := c.HeikinAshiHigh(o, h, l, c), c.HeikinAshiLow(o, h, l, c)(a) Valcu’s haDiffCO, Mov-haDiffCO and the body colour
Valcu reads the trend from the heikin-ashi body: haDiffCO is the close less the open, Mov-haDiffCO its average over p bars (his MetaStock input runs from 3 to 50, default 3), and a white body is a close strictly above the open.
diff := haC.Sub(haO)
movDiff := diff.Average(3)
white := haC.GT(haO)- Colours: Valcu’s white body is strict, so a tie (
haC == haO) is filled, not white; Vervoort’s green (HACO’s colour test, on his four-point close) includes the tie.diff.GT(0)equalswhitebit for bit. The candle kit of the “Candlesticks” category reads the four views as it reads real columns:haC.RealBody(haO)isdiff.Abs()bit for bit, the size of haDiffCO, while the kit’s colourhaC.GTE(haO)counts the tie as white, wherewhitedoes not. - First valid index: t0 for
diffandwhite; t0 + 2 formovDiff, sinceAveragepasses the input through before its first full window (bars 0 and 1 with t0 = 0) and a window that holds a NaN bar is NaN. - Anchor:
haC.QStick(haO, haC, 3)is Mov-haDiffCO in exact arithmetic, a difference of averages rather than an average of differences, so the two agree to rounding; on the real candles,c.QStick(o, c, p)is Chande’s Qstick. - Pinned by
TestRecipeHeikinAshi; runnable exampleExampleSeries_HeikinAshiClose_haDiffCO.
(b) Vervoort’s four-point close
Vervoort’s HACO and HACOLT use his own heikin-ashi close, the average of the four heikin-ashi prices, in their colour tests and zero-lag averages (the open’s recursion keeps Valcu’s close):
haCV := haC.SAverage(haO, haH, haL)- Anchor: on well-formed bars (
l <= o, c <= h) Valcu’s close lies within the bar’s range, sohaHismax(h, haO)andhaLismin(l, haO), andhaCVequals the four-point close insideHACOandHACOLT,(((ohlc4 + haO) + max(h, haO)) + min(l, haO)) / 4, bit for bit. On a malformed bar the two can differ. - First valid index: t0.
- Pinned by
TestRecipeHeikinAshi; runnable exampleExampleSeries_HeikinAshiHigh_vervoortClose.
(c) Smoothed heikin-ashi
Smoothed heikin-ashi averages the inputs first and draws the candles from the averages. Mustapha uses TradingView’s smoothed heikin-ashi with its default setting “10 and 10”, which is read here as two lengths: one for the averages of the inputs and one for a second average of the candles afterwards (his article prints no formula):
so, sh, sl, sc := o.XAverage(10), h.XAverage(10), l.XAverage(10), c.XAverage(10)
sO, sC := c.HeikinAshiOpen(so, sh, sl, sc), c.HeikinAshiClose(so, sh, sl, sc)
sO2 := sO.XAverage(10)- Anchor: with a length of 1,
XAverage(1)computesx*1 + E*0, which is the input in value (a -0 can become +0), so on finite prices other than -0 the smoothed candles are the candles bit for bit. - First valid index:
XAverage(10)passes bars 0 to 8 through and seeds with a 10-bar simple average at bar 9, so the smoothed candles start from averages at bar 9, and the second average passes nine more bars through.XAveragedoes not recover from a NaN: a missing bar k is passed through as NaN at bar k and makes every average NaN from bar max(k, 9) on, so the smoothed candles end at bar k, or at bar 9 when the missing bar is bar 0 (bars 1 to 8 are then candles of the raw inputs). Start the slice after any leading missing bars. - Pinned by
TestRecipeHeikinAshi; runnable exampleExampleSeries_HeikinAshiOpen_smoothed.
(d) The zero-lag TEMA crossover (Vervoort, May 2008)
Vervoort’s crossover buys where the zero-lag TEMA of the typical price crosses above the zero-lag TEMA of his four-point close, at length 55. The zero-lag TEMA is his printed TMA1 + (TMA1 - TMA2):
zl := func(x Series) Series {
t := x.TEMA(n)
return t.Add(t.Sub(t.TEMA(n)))
}
cross := zl(h.Add(l).Add(c).Div(3)).CrossesAboveS(zl(haCV))- Anchor:
zlis the zero-lag average insideHACOandHACOLT, bit for bit, andzl(h.Add(l).Div(2))lesszl(haCV)is their difference d on well-formed bars. - First valid index: 1 for
CrossesAboveS; the TEMAs pass their inputs through on bars 0 to n-2, and switches and crosses in the first 6(n-1) bars or so depend on the seeding, so mask them, if wanted, withcross.SetN(6*(n-1), 0).TEMAdoes not recover from a NaN either. - Pinned by
TestRecipeHeikinAshi; runnable exampleExampleSeries_HACO_crossover.
Point and Figure
The chart and its six views
A point-and-figure chart records price moves of at least one box as columns of Xs (rising) and Os (falling) on a grid of boxes; time enters only through the order of the bars, and a column changes only when the price reaches a new box or reverses by reversal boxes. The methods of this category draw the chart bar by bar from highs s and lows low with the high-low method, on an absolute grid of box in the receiver’s units, and return six views of it. Each view is one pass of the same machine, with the same arguments (low, box, reversal):
| Method | On each bar | Before the first column |
|---|---|---|
PointAndFigureDirection | +1 in a column of Xs, -1 in a column of Os | 0 |
PointAndFigureExtreme | the level price of the current column’s top (Xs) or bottom (Os) | NaN |
PointAndFigureBoxes | the boxes the bar marks: +n Xs, -n Os, 0 when none | 0 |
PointAndFigureSignal | +1 from a double-top breakout, -1 from a double-bottom breakout, held | 0 |
PointAndFigureResistance | the top of the most recently completed column of Xs | NaN |
PointAndFigureSupport | the bottom of the most recently completed column of Os | NaN |
PointAndFigureDirection’s doc carries the whole machine; the others summarise it. In short:
- The grid is anchored at 0: level k has the price kbox, the float64 product rounded once. A high is read at its X level, the largest k with kbox <= high, and a low at its O level, the smallest k with k*box >= low; a price on a level is on it. The levels come from the products, not from
floor(x/box), whose float64 quotient misreads in both directions (at box 0.1 it reads 4.3 one level low and 1.7 one level high). - The high-low rule (StockCharts’ ChartSchool; Mike Moody in Edwards, Magee and Bassetti, Technical Analysis of Stock Trends, 11th edition, 2019, Appendix C): a column extends first, else reverses. In a column of Xs a higher X level of the high extends the column and the low is ignored; otherwise an O level of the low at least
reversalboxes below the top completes the column and starts one of Os. Columns of Os mirror this. A bar does not both extend and reverse, makes at most one reversal and can mark several boxes. - The first column waits until the range of the usable bars spans at least
reversalboxes and exactly one end of the range moved on the bar; an outside bar, which moves both, waits. The range before the first column is not a column, so the first double-top or double-bottom signal needs at least three columns. - Missing bars: a bar whose high or low is NaN, +Inf, -Inf or at least 2^52 boxes from 0 changes nothing (the hold class); a low above its high is used as given.
Units and boxes. The box is a fixed scalar in the receiver’s units. Close-only charts, logarithmic boxes, a grid anchored at a price, a box frozen from the ATR and the renko chart (the close-only chart with a reversal of 2 boxes) are recipes over these methods, listed under “Compositions” below. Not built: Brown’s touch rule (a high that only touches the top of a column of Xs keeps the column, even when the low would reverse it), a box re-read at each new box, the traditional box-size tiers, the triple top and the other multi-column patterns, count targets and the bullish percent of a group.
Related machines. DirectionalChangePoints is the same latch without a grid, reversing on an exact move of reversal from the column’s extreme rather than on whole boxes: on whole-number closes c below 2^52 in magnitude and a whole r >= 1, c.PointAndFigureDirection(c, 1, r) equals c.DirectionalChangePoints(r - 0.5) on every bar. The kagi line is that machine on closes, and its thickness state KagiYang (the “Kagi” category) is the same breakout latch as PointAndFigureSignal on the same shoulders and waists, without waiting for a completed column: c.KagiYang(r - 0.5) equals c.PointAndFigureSignal(c, 1, r) on every bar where the signal is not 0. The double top of PointAndFigureSignal is Vomund’s point-and-figure pattern, not the bar-chart DoubleTop of the “Chart Patterns” category, two confirmed swing highs within a band.
Sources. StockCharts, “Introduction to Point & Figure Charts”, ChartSchool (the construction example that ExampleSeries_PointAndFigureDirection replays); Moody, as above; Luis Ballesca Loyo, “Logarithmic point and figure charts”, Technical Analysis of Stocks & Commodities, January 1989 (logarithmic boxes); William G. S. Brown, “Logarithmic Point & Figure Charting”, Technical Analysis of Stocks & Commodities, July 1995 (the touch rule and the WSAU chart); David Vomund, “Classic Point And Figure”, Technical Analysis of Stocks & Commodities, December 2000 (the double-top and double-bottom signals and the box-size tiers).
PointAndFigureBoxes
func (s Series) PointAndFigureBoxes(low Series, box float64, reversal int) Series
PointAndFigureBoxes returns the boxes marked on each bar, +n for n Xs, -n for n Os and 0 when none, a reversal bar carrying the whole new column.
PointAndFigureDirection defines the grid anchored at 0 with level prices k*box rounded once in the receiver’s units and the rule for highs s and lows low: a column extends first, else reverses, and the first column waits for the reversal span and exactly one range extreme to move after the first usable bar.
An extension marks the difference between the new and previous extreme in whole levels, positive for Xs and negative for Os. A reversal marks all boxes from one beyond the completed extreme through the new extreme, so its magnitude is at least reversal. On the first column’s first bar, hiK and loK are the highest X level of the highs and lowest O level of the lows in the range so far, including that bar; the result is hiK - loK for Xs or its negative for Os. It is +0 on all other bars, including missing bars and every bar before the first column.
Let dir, e and b denote PointAndFigureDirection, PointAndFigureExtreme and PointAndFigureBoxes. After the first column’s first bar, b equals e.Div(box).Round().Delta() while levels have magnitude below 2^51. The first column’s first bar includes its whole hiK - loK span. The column height is b.Abs().SumSince(dir.Delta()), at least reversal on every bar of a column, including its first bar.
With c as closes, close mode is c.PointAndFigureBoxes(c, box, reversal).
Valid parameters satisfy 0 < box < +Inf and reversal >= 1; invalid parameters return len(s) zeros, all +0. Subnormal boxes and the largest finite box are valid. A reversal above 2^53 is valid but cannot start a column. Unequal lengths, checked first, return a non-nil empty Series. Equal empty inputs return a non-nil empty Series regardless of parameters.
A bar is missing if either price x fails math.Abs(x)/box < 2^52, including NaN, +Inf and -Inf. This is the hold class: state is unchanged and the bar marks +0 boxes. Deleting missing bars preserves the values on remaining bars bit for bit; a low above its high is used as given.
There is no warm-up: the first valid index is 0. The data-dependent start leaves +0 before the first column, and bar 0 is always undetermined, detected by dir.EQ(0). The calculation is causal and not start-invariant: a later start may choose other columns; after active runs share a direction and extreme, their boxes agree from the next bar. Prefixes and repeated calls agree bit for bit, and later bars do not revise earlier values.
The calculation is one pass, O(1) per bar regardless of the number of boxes, allocates only the result and does not change either input, including when both are the same slice.
Recipes built on the PointAndFigure methods are listed under “Compositions” in the “Point and Figure” category.
PointAndFigureDirection
func (s Series) PointAndFigureDirection(low Series, box float64, reversal int) Series
PointAndFigureDirection returns the point-and-figure column state of the highs s and lows low on an absolute grid of box in the receiver’s units with a reversal of reversal boxes: +1 in a column of Xs, -1 in a column of Os and 0 before the first column.
The grid is anchored at 0. Level k has the price kbox, with the float64 product rounded once, and level 0 has price +0. For a usable price x, its X level is the largest k with kbox <= x and its O level is the smallest k with k*box >= x; these comparisons use the rounded products. A price on a level reaches that level. Membership is set by products, not by floor(x/box): at box 0.1, 4.3 is level 43 because the rounded product of 43 and 0.1 is 4.3, but the float64 quotient is 42.99999999999999. A quotient can also over-read: 1.7 divided by 0.1 rounds to 17, while the rounded price of level 17 is above 1.7, so its X level is 16. Levels are exact whole numbers. Reaching exactly reversal boxes reverses, with no threshold tie because this is grid membership.
The high-low rule follows StockCharts, “Introduction to Point & Figure Charts”, ChartSchool, and Mike Moody in Edwards, Magee and Bassetti, Technical Analysis of Stock Trends, 11th edition, 2019, Appendix C. Bars are read from oldest to newest. A column extends first, else reverses. In Xs, a high’s X level above the extreme extends it and the low is ignored; otherwise the low’s O level at least reversal boxes below the extreme completes the X column and starts Os. In Os, a low’s O level below the extreme extends it and the high is ignored; otherwise the high’s X level at least reversal boxes above the extreme completes the O column and starts Xs. A bar does not both extend and reverse, and makes at most one reversal, although it can mark several boxes.
Before the first column, hiK is the highest X level of the highs and loK the lowest O level of the lows seen on usable bars. The first usable bar records both and waits. Later, update each extreme if it moves outwards; start only when hiK - loK >= reversal and exactly one moved. A higher hiK starts Xs at hiK; a lower loK starts Os at loK. If both moved, as on an outside bar, or neither moved, wait. The first column marks hiK - loK boxes, from one box beyond the opposite extreme through its own extreme, with positive Xs and negative Os; the range before the first column is not a column and supplies neither resistance nor support.
Each reversal records the completed X top as PointAndFigureResistance or the completed O bottom as PointAndFigureSupport, with NaN until that kind completes. After each usable update, PointAndFigureSignal becomes +1 when Xs exceed the known completed X top, or -1 when Os fall below the known completed O bottom; otherwise it holds, starting at +0. These comparisons are strict comparisons of levels, so touching a former extreme is not a breakout and the first signal needs at least three columns. PointAndFigureExtreme gives the current level price, NaN before the first column, and PointAndFigureBoxes gives the signed marks, +0 when no boxes are marked. Known level prices are finite and level 0 is +0.
The parameters require 0 < box < +Inf and reversal >= 1; otherwise every view returns len(s) zeros, all +0. Subnormal boxes and the largest finite box are valid, as is a one-box reversal; 3 is the usual reversal. A reversal above 2^53 is valid but unreachable, so the first column does not start. Unequal lengths, checked first, return a non-nil empty Series. Equal empty inputs also return a non-nil empty Series, regardless of parameters.
A price x is usable only when math.Abs(x)/box < 2^52; NaN, +Inf, -Inf and prices at or beyond that bound are missing. If either high or low is missing, the whole bar is in the hold class: it changes no state, all state views repeat their prior value and PointAndFigureBoxes is +0. Deleting missing bars preserves the outputs on the remaining bars bit for bit. No ordering check is made: a low above its high is used as given. Negative prices, +0 and -0 are ordinary values within the usable bound.
There is no warm-up: the first valid index is 0. The data-dependent undetermined state is detected by dir.EQ(0), where dir is PointAndFigureDirection; bar 0 is always undetermined and the first column needs at least two usable bars. After a column starts, its direction stays +1 or -1. The machine is causal and deterministic: prefixes and repeated calls agree bit for bit, and later bars do not revise earlier values. It is not start-invariant. Starting later keeps the same grid but can choose different columns. Once active runs share a direction and extreme, those states agree thereafter and their boxes agree from the next bar. Resistance agrees after the next X column completes, Support after the next O column completes, and Signal from the first later non-zero breakout input once those inputs agree.
Here h and l are highs and lows and c is closes. Close mode is c.PointAndFigureDirection(c, box, reversal). A logarithmic box with fraction p uses h.Log().PointAndFigureDirection(l.Log(), math.Log1p(p), reversal); level views then contain log prices and .Exp() maps them back to prices. Non-positive prices have missing logs. Ballesca Loyo, “Logarithmic point and figure charts”, Technical Analysis of Stocks & Commodities, January 1989, uses a log10 box of 0.01, which is p = 10^0.01 - 1, 2.33% per box and 7.15% per three-box reversal, and a box of 0.023026 in the natural-log units of the recipe; his log10 box of 0.005, p = 10^0.005 - 1, is 1.16% per box and 3.51% per three-box reversal.
A volatility box is a frozen scalar: choose box := c.ATRWilder(h, l, c, 14)[k], then run on h[k:] and l[k:]. Earlier bars would otherwise use a box computed from later bars. Traditional tiers use 0.50 from 5 to 20 and 1 from 20 to 100; Vomund doubles the recommended sizes, to 2 from 20 to 100 and 8 from 100 to 500, in “Classic Point And Figure”, Technical Analysis of Stocks & Commodities, December 2000. Tier selection and a box re-read at each new box are not built; the box stays fixed during a call.
Brown’s touch rule is not built. Brown, “Logarithmic Point & Figure Charting”, Technical Analysis of Stocks & Commodities, July 1995, holds a column when its extreme is touched even without another box. Here only an extension wins over a reversal: in StockCharts’ final bar the high 15.90 touches 15 but does not reach 16, so the low 12.00 reverses a three-box chart. On Brown’s WSAU replay this rule reverses to Xs in May and Os in June where his Os hold and then extend; the rules agree from July on.
DirectionalChangePoints is the corresponding machine without a grid. On closes with logarithmic fraction p, the direction is close to c.DirectionalChange((1+p)^reversal - 1), but quantisation to whole boxes and measuring the relative threshold from an exact extreme can make them differ.
With dir as the direction, dir.Delta().Clamp(-1, 1) marks +1 at X starts and -1 at O starts, including the first column. The direction changes dir.Delta().EQ(2) and dir.Delta().EQ(-2) mark X and O reversals. Any non-zero, non-NaN direction change is an event; dir.Delta().Abs().GT(0).CumSum() counts columns from the slice’s start, and dir.StateAge().Mul(dir.Abs()) counts bars in the column from 1, with 0 before the first column. Missing bars count towards that age because the state holds.
Each view makes one pass with O(1) per bar regardless of the boxes marked, allocates only the result and leaves both inputs unchanged, including aliased close inputs. Results have independent storage and concurrent calls on shared inputs are safe. Verification covers the cited constructions and independent references, with mirror and power-of-two scale identities checked bit for bit.
Recipes built on the PointAndFigure methods are listed under “Compositions” in the “Point and Figure” category.
PointAndFigureExtreme
func (s Series) PointAndFigureExtreme(low Series, box float64, reversal int) Series
PointAndFigureExtreme returns the grid level of the current column’s extreme, the top of a column of Xs or the bottom of a column of Os, in the receiver’s units, and NaN before the first column.
PointAndFigureDirection defines the grid anchored at 0 with level prices k*box rounded once in the receiver’s units and the rule for highs s and lows low: a column extends first, else reverses, and the first column waits for the reversal span and exactly one range extreme to move after the first usable bar.
At each bar in Xs the result is the top’s level price and in Os it is the bottom’s level price, with level 0 as +0. The result is NaN before the first column and only there for valid parameters; after a column starts it is finite on every bar. On data without missing bars, Xs use the X level of h.HighestSince(dir.Delta()), at most that highest high, and Os use the O level of l.LowestSince(dir.Delta()), at least that lowest low, where h and l are highs and lows and dir is PointAndFigureDirection.
With c as closes, close mode is c.PointAndFigureExtreme(c, box, reversal). For a logarithmic box of fraction p, h.Log().PointAndFigureExtreme(l.Log(), math.Log1p(p), reversal).Exp() maps level k to (1+p)^k up to rounding. The level itself is in log units; rounding of the base is magnified by the magnitude of k.
Valid parameters satisfy 0 < box < +Inf and reversal >= 1; invalid parameters return len(s) zeros, all +0. Subnormal boxes and the largest finite box are valid. A reversal above 2^53 is valid but cannot start a column. Unequal lengths, checked first, return a non-nil empty Series. Equal empty inputs return a non-nil empty Series regardless of parameters.
A bar is missing if either price x fails math.Abs(x)/box < 2^52, including NaN, +Inf and -Inf. This is the hold class: the whole bar changes no state and the prior extreme is repeated, or NaN if no column has started. Deleting missing bars preserves values on the remaining bars bit for bit; a low above its high is used as given.
There is no warm-up: the first valid index is 0. NaN is the data-dependent state before the first column, including bar 0; dir.EQ(0) detects it. This causal latch is not start-invariant: starting later can choose different columns. Once active runs share a direction and extreme, their extremes agree thereafter. Prefixes and repeated calls agree bit for bit, and later bars do not revise earlier values.
The calculation is one pass, O(1) per bar regardless of the number of boxes, allocates only the result and does not change either input, including when both are the same slice.
Recipes built on the PointAndFigure methods are listed under “Compositions” in the “Point and Figure” category.
PointAndFigureResistance
func (s Series) PointAndFigureResistance(low Series, box float64, reversal int) Series
PointAndFigureResistance returns the top of the most recently completed column of Xs, held until the next completes, and NaN until one has.
PointAndFigureDirection defines the grid anchored at 0 with level prices k*box rounded once in the receiver’s units and the rule for highs s and lows low: a column extends first, else reverses, and the first column waits for the reversal span and exactly one range extreme to move after the first usable bar.
An X column completes only on its reversal to Os. That bar posts the former X extreme as its level price k*box rounded once, in the receiver’s units, and subsequent bars hold it until another X column completes. It is NaN until that first completion; known values are finite, with level 0 as +0. In particular, the range before the first column is not a column and does not set resistance.
With e as PointAndFigureExtreme and dir as PointAndFigureDirection, the value is e.Shift(1).ValueWhen(dir.Delta().EQ(-2)), including NaN before the first event. PointAndFigureSignal sets +1 when a later X column rises strictly above this completed top, comparing levels.
With c as closes, close mode is c.PointAndFigureResistance(c, box, reversal).
Valid parameters satisfy 0 < box < +Inf and reversal >= 1; invalid parameters return len(s) zeros, all +0. Subnormal boxes and the largest finite box are valid. A reversal above 2^53 is valid but cannot start a column. Unequal lengths, checked first, return a non-nil empty Series. Equal empty inputs return a non-nil empty Series regardless of parameters.
A bar is missing if either price x fails math.Abs(x)/box < 2^52, including NaN, +Inf and -Inf. This is the hold class: the whole bar changes no state and repeats the prior resistance, or NaN if no X column has completed. Deleting missing bars preserves values on the remaining bars bit for bit; a low above its high is used as given.
There is no warm-up: the first valid index is 0. NaN represents an undetermined completed top; bar 0 has no column, as dir.EQ(0) detects. The calculation is causal and not start-invariant. Once two active runs share a direction and extreme, their resistance agrees after the next X column completes. Prefixes and repeated calls agree bit for bit, and later bars do not revise earlier values.
The calculation is one pass, O(1) per bar regardless of the number of boxes, allocates only the result and does not change either input, including when both are the same slice.
Recipes built on the PointAndFigure methods are listed under “Compositions” in the “Point and Figure” category.
PointAndFigureSignal
func (s Series) PointAndFigureSignal(low Series, box float64, reversal int) Series
PointAndFigureSignal returns Vomund’s double-top and double-bottom breakout latch: +1 from a column of Xs that rises above the previous column of Xs, -1 from a column of Os that falls below the previous column of Os, held until the opposite breakout, and 0 before the first.
PointAndFigureDirection defines the grid anchored at 0 with level prices k*box rounded once in the receiver’s units and the rule for highs s and lows low: a column extends first, else reverses, and the first column waits for the reversal span and exactly one range extreme to move after the first usable bar.
After each usable bar’s update, Xs set +1 when their extreme level exceeds the level of the completed X top reported by PointAndFigureResistance; otherwise Os set -1 when their extreme level is below the level of the completed O bottom reported by PointAndFigureSupport. A missing prior top or bottom cannot trigger a breakout. The comparisons use levels and are strict: reaching an old top or bottom does not signal. All other bars hold the latch, with +0 before the first breakout, including before the first column. A breakout needs at least three columns: one of the same kind to complete, an intervening opposite column and a later column that passes the completed extreme.
Let dir, e, res and sup be PointAndFigureDirection, PointAndFigureExtreme, PointAndFigureResistance and PointAndFigureSupport. With levels below 2^51 in magnitude, the equivalent expression comparing prices is dir.EQ(1).Mul(e.GT(res)).Sub(dir.EQ(-1).Mul(e.LT(sup))).Hysteresis(0, 0). GT and LT with NaN are false; Hysteresis holds the last non-zero decision and starts at 0.
This is the double-top and double-bottom latch from Vomund, “Classic Point And Figure”, Technical Analysis of Stocks & Commodities, December 2000. The triple top, bullish catapult, bullish signal reversal, count targets and the bullish percent of a group are not built. These rules alone demonstrate no trading edge. DoubleTop and DoubleBottom are a different pattern: the bar-chart double top and double bottom, two confirmed swings within a band, confirmed by a close beyond the neckline between them (the manual’s “Chart Patterns” section). TripleTop and TripleBottom are the bar-chart triple top and triple bottom on three confirmed swings, not this chart’s triple top (the same section).
With c as closes, close mode is c.PointAndFigureSignal(c, box, reversal).
Valid parameters satisfy 0 < box < +Inf and reversal >= 1; invalid parameters return len(s) zeros, all +0. Subnormal boxes and the largest finite box are valid. A reversal above 2^53 is valid but cannot start a column. Unequal lengths, checked first, return a non-nil empty Series. Equal empty inputs return a non-nil empty Series regardless of parameters.
A bar is missing if either price x fails math.Abs(x)/box < 2^52, including NaN, +Inf and -Inf. This is the hold class: the whole bar changes no state and repeats the prior signal, or +0 if no breakout has occurred. Deleting missing bars preserves values on the remaining bars bit for bit; a low above its high is used as given.
There is no warm-up: the first valid index is 0. Bar 0 and every bar before the first column are undetermined, detected by dir.EQ(0); the signal can remain +0 after a column starts. The latch is causal and not start-invariant: a later start may change columns and recorded levels. Once two runs have the same breakout inputs, their signals agree from the first non-zero input. Prefixes and repeated calls agree bit for bit, and later bars do not revise earlier values.
The calculation is one pass, O(1) per bar regardless of the number of boxes, allocates only the result and does not change either input, including when both are the same slice.
Recipes built on the PointAndFigure methods are listed under “Compositions” in the “Point and Figure” category.
PointAndFigureSupport
func (s Series) PointAndFigureSupport(low Series, box float64, reversal int) Series
PointAndFigureSupport returns the bottom of the most recently completed column of Os, held until the next completes, and NaN until one has.
PointAndFigureDirection defines the grid anchored at 0 with level prices k*box rounded once in the receiver’s units and the rule for highs s and lows low: a column extends first, else reverses, and the first column waits for the reversal span and exactly one range extreme to move after the first usable bar.
An O column completes only on its reversal to Xs. That bar posts the former O extreme as its level price k*box rounded once, in the receiver’s units, and subsequent bars hold it until another O column completes. It is NaN until that first completion; known values are finite, with level 0 as +0. In particular, the range before the first column is not a column and does not set support.
With e as PointAndFigureExtreme and dir as PointAndFigureDirection, the value is e.Shift(1).ValueWhen(dir.Delta().EQ(2)), including NaN before the first event. PointAndFigureSignal sets -1 when a later O column falls strictly below this completed bottom, comparing levels.
With c as closes, close mode is c.PointAndFigureSupport(c, box, reversal).
Valid parameters satisfy 0 < box < +Inf and reversal >= 1; invalid parameters return len(s) zeros, all +0. Subnormal boxes and the largest finite box are valid. A reversal above 2^53 is valid but cannot start a column. Unequal lengths, checked first, return a non-nil empty Series. Equal empty inputs return a non-nil empty Series regardless of parameters.
A bar is missing if either price x fails math.Abs(x)/box < 2^52, including NaN, +Inf and -Inf. This is the hold class: the whole bar changes no state and repeats the prior support, or NaN if no O column has completed. Deleting missing bars preserves values on the remaining bars bit for bit; a low above its high is used as given.
There is no warm-up: the first valid index is 0. NaN represents an undetermined completed bottom; bar 0 has no column, as dir.EQ(0) detects. The calculation is causal and not start-invariant. Once two active runs share a direction and extreme, their support agrees after the next O column completes. Prefixes and repeated calls agree bit for bit, and later bars do not revise earlier values.
The calculation is one pass, O(1) per bar regardless of the number of boxes, allocates only the result and does not change either input, including when both are the same slice.
Recipes built on the PointAndFigure methods are listed under “Compositions” in the “Point and Figure” category.
Compositions
Point-and-figure recipes
The six point-and-figure views take highs h as the receiver and lows l as the first argument, a fixed box in the receiver’s units and a reversal in whole boxes. The recipes below change what the box and the inputs are, or read column facts from the views; dir, ext and b are the direction, the extreme and the boxes of one call. The direction, the boxes and every count below are whole numbers, and the extremes are level prices, so every identity marked (E) below holds bit for bit.
(a) Close-only charts
Pass the closes as both series:
dir := c.PointAndFigureDirection(c, box, 3)
ext := c.PointAndFigureExtreme(c, box, 3)- First valid index: 0; the chart is undetermined (direction 0, extreme NaN) on bar 0 and until the highest X level and the lowest O level of the closes are at least
reversallevels apart, anddir.EQ(0)detects those bars. - Anchors: the call with the same slice twice equals the call with a copy of
cas the lows, and the inputs are not changed (E). Closes usually draw fewer columns than highs and lows, because a close moves less than the bar’s range, although a wide outside bar can delay the high-low chart where the closes start: on the 200 AAPL bars of 2023 in the package’s test data, at box 1 and reversal 3, close mode draws 24 columns and the high-low chart 52. - Pinned by
TestRecipePointAndFigure; runnable exampleExampleSeries_PointAndFigureDirection_closeMode.
(b) Logarithmic boxes and a grid anchored at a price
Every box the same fraction p of price (Ballesca Loyo): run the methods on log prices with the box math.Log1p(p), and map a level back to a price with Exp.
lb := math.Log1p(p)
dir := h.Log().PointAndFigureDirection(l.Log(), lb, 3)
level := h.Log().PointAndFigureExtreme(l.Log(), lb, 3).Exp()
// Brown's grid, anchored at the price a instead of 1:
dirA := h.Mul(1/a).Log().PointAndFigureDirection(l.Mul(1/a).Log(), lb, 3)
levelA := h.Mul(1/a).Log().PointAndFigureExtreme(l.Mul(1/a).Log(), lb, 3).Exp().Mul(a)- First valid index: 0, as for the views. A price at or below 0 has a NaN or -Inf log and is a missing bar.
- Accuracy: the level views hold k times
math.Log1p(p)rounded once, in log units;.Exp()gives (1+p)^k up to the rounding of the base, magnified |k| times, within a relative (|k|+1)*2^-50. - Loyo’s boxes: his log10 box of 0.01 is p = 10^0.01 - 1, 2.33% a box and 7.15% a three-box reversal, and a box of 0.023026 in the natural-log units of this recipe; his log10 box of 0.005 is p = 10^0.005 - 1, 1.16% and 3.51%. Read back from the extremes of a rising series, his log10 ladders (2238.7, 2290.9, … 2884.0 at levels 335 to 346 for 0.01) and ln ladders match his Figures 4 and 5 to 0.1.
- Brown’s WSAU chart (monthly highs and lows, February to December 1994, a 1% box, reversal 3): with a = 21.874035 the extremes of the anchored recipe, rounded to three decimals, are Brown’s printed box values 25.649, 28.902, 22.314, 25.144, 25.906, 27.499, 22.762, 25.144 and 22.093 from April on. Brown’s touch rule keeps his column of Os in May and extends it in June, where the high-low rule reverses twice; from July on the two charts agree.
- Pinned by
TestRecipePointAndFigure; runnable examplesExampleSeries_PointAndFigureDirection_logBoxandExampleSeries_PointAndFigureExtreme_brown.
(c) A box frozen from the ATR
A volatility box is a scalar read once, at the first bar where it is known, and the chart starts there:
k := 14 // the first valid index of ATRWilder(h, l, c, 14)
box := c.ATRWilder(h, l, c, 14)[k]
dir := h[k:].PointAndFigureDirection(l[k:], box, 3)
// The same values, aligned with the full series (bars before k are missing bars):
dirFull := h.SetN(k, math.NaN()).PointAndFigureDirection(l.SetN(k, math.NaN()), box, 3)- First valid index: bar k of the series (bar 0 of the slice); the bars before k read 0 and NaN in the aligned form.
- Why frozen and sliced: a box read at a later bar and applied from bar 0 would let the bars before it see a box computed from later bars. A box re-read at each new box (Durenard) is not built.
- Anchors:
dirFull[k:]equalsdiron every view, because a missing bar changes nothing and deleting the missing bars gives the same values on the others (E). On the AAPL bars of 2023 the box is 2.9221428571428567, and the frozen chart draws 7 columns, the first on bar 16 of the slice. - Pinned by
TestRecipePointAndFigure; runnable exampleExampleSeries_PointAndFigureDirection_atrBox.
(d) Column starts, count, height and age
dir := h.PointAndFigureDirection(l, box, 3)
b := h.PointAndFigureBoxes(l, box, 3)
starts := dir.Delta().Clamp(-1, 1) // +1 where Xs start, -1 where Os start, the first column included
toXs, toOs := dir.Delta().EQ(2), dir.Delta().EQ(-2) // the reversals alone
count := dir.Delta().Abs().GT(0).CumSum() // columns started so far
height := b.Abs().SumSince(dir.Delta()) // boxes in the current column
age := dir.StateAge().Mul(dir.Abs()) // bars in the current column, from 1
- First valid index: 0 for all six. Before the first column
starts,toXs,toOs,countandageare 0 andheightis NaN (SumSince is NaN before its first event). - Events:
dir.Delta()is itself an event series for the event operators (any non-zero value that is not NaN is an event), soSumSince,HighestSinceandLowestSinceoverdir.Delta()restart on each column’s first bar, anddir.Delta().BarsSince()isageless one from the first column on. - Anchors:
|b| >= reversalon every bar wherestartsis not 0, andheightis at leastreversalon every bar of a column (E).countis cumulative from the first bar of the series, so it drifts with the slice origin; compare counts only between runs on the same slice.agecounts missing bars, on which the column holds. On StockCharts’ six-day example (box 1, reversal 3)startsis [0, -1, 0, 1, 0, -1],count[0, 1, 1, 2, 2, 3],height[NaN, 3, 4, 3, 4, 3] andage[0, 1, 2, 1, 2, 1]. - Pinned by
TestRecipePointAndFigure; runnable exampleExampleSeries_PointAndFigureBoxes_columns.
Renko recipes
A renko chart on closes draws a brick of a fixed size b each time a close reaches the level one brick beyond the last brick, and turns only when a close reaches the level one brick beyond the last brick’s opposite edge: two bricks below the top of an up brick, or two above the bottom of a down brick. That is a close-only point-and-figure chart with a box of b and a reversal of 2 boxes, so the three renko series are recipes over the point-and-figure views, with the closes as both series:
dir := c.PointAndFigureDirection(c, b, 2) // the brick colour: +1 up, -1 down
level := c.PointAndFigureExtreme(c, b, 2) // the top of the last up brick, the bottom of the last down brick
d := dir.Delta() // +2 or -2 on a bar where the bricks turn
// The signed bricks drawn on the bar: the boxes, one fewer on a bar where the bricks turn.
bricks := c.PointAndFigureBoxes(c, b, 2).Sub(d.EQ(2)).Add(d.EQ(-2))- First valid index: 0. Before the first brick
dirandbricksare 0 (not NaN) andlevelis NaN, anddir.EQ(0)detects those bars. - Bricks: on a turn, the new point-and-figure column counts as its first box the level one box from the old extreme (the bottom of the last up brick on a turn down), where the renko chart’s first brick in the new direction only starts. So on a bar where
dis +2 or -2,bricksis one brick smaller in size thanPointAndFigureBoxes, and on every other bar from the first brick on the two are equal (E). The bricks of a bar are signed: +n draws n up bricks and -n draws n down bricks. - The start: the grid is absolute (the multiples of
b), so the levels do not depend on where the series starts. The engine’s first column waits until the closes span two boxes, from the highest floor level to the lowest ceiling level, and marks the whole span. Chart packages start at the first close and draw the first brick as soon as a close covers one whole brick of the grid beyond it, so a chart can show a brick whiledir.EQ(0)is 1; from the engine’s first brick on, a standard renko loop on the same grid and the recipe agree bar for bar on the direction and the level, and on the bricks from the next bar. On 2,000 random walks of 300 closes at five brick sizes they agree on all 590,475 bars after the engine’s first brick, and on its first brick’s bar in all 2,000 runs (direction and level), while on 2,209 earlier bars the loop already shows a brick. - Sources: Duddella (“Trading Renko Charts”, Technical Analysis of Stocks & Commodities, August 2007), rows 6 to 10 of his Figure 3 at a brick of 1, after a two-close prefix [535.5, 533.0] that puts the chart in the same falling state: on the closes 531.8, 534.2, 531.6, 532.5 and 530.2,
diris [-1, 1, -1, -1, -1],level[532, 534, 532, 532, 531] andbricks[-1, 1, -1, 0, -1]. His caveat that a turn needs “three times the box size” is a misprint against his own Figure 3, where the top of brick D is at 534 and the bottom of brick E at 532, two bricks, and his rows 2 and 4 draw a brick on less than one brick’s move, so only rows 6 to 10 are a fixture. His five-point bricks: a close 16 points above the top of the last up brick draws 3 bricks ([1000, 990, 1000, 1016] at 5), and one 9 points below the bottom of the last down brick draws 1 ([1000, 1010, 1000, 991]). Williams (Trade Stocks and Commodities with the Insiders, 2005, chapter 14, p. 174): at five-unit bricks a 20-point rally draws four ([100, 90, 100, 120] at 5). StockCharts’ ChartSchool (“Renko Charts”): at 10-point bricks a rise from 1840 to 1849 draws nothing and 1850 one brick ([1840, 1820, 1840, 1849, 1850] at 10 givesbricks[0, -2, 1, 0, 1]); its ATR brick is read when the chart is drawn, so the chart repaints. - A two-brick turn: on [1840, 1820, 1840, 1850, 1831, 1830] at 10,
bricksis [0, -2, 1, 1, 0, -1] wherePointAndFigureBoxesis [0, -2, 2, 1, 0, -2]: the close 1830 turns the chart down from the top 1850 and draws one brick, from 1840 to 1830. - Highs and lows: a renko chart on each bar’s range is the same three calls with the highs as the receiver and the lows as the first argument,
h.PointAndFigureDirection(l, b, 2)and so on. - Not built: Vervoort’s modified renko (“Exploring Charting Techniques, Part 1”, Technical Analysis of Stocks & Commodities, July 2014). His code keeps the standard thresholds; the open at the previous brick’s close and the wicks are drawing details of bricks in event time, which a Series aligned with the bars cannot carry; and his restart at each session is the recipe run on each session’s slice, with session keys the caller supplies, on the absolute grid rather than his grid anchored at the session’s first price. A brick re-read from the ATR at each new brick is not built either; a brick read once from the ATR is recipe (c) of the point-and-figure recipes, unchanged with a reversal of 2.
- Pinned by
TestRecipeRenko; runnable examplesExampleSeries_PointAndFigureDirection_renkoandExampleSeries_PointAndFigureBoxes_renkoBricks.
The OXXO state and its two column counts below read 0 before the first break, as latches do; the bands and centre lines that feed its variants are masked with NaN before their first valid index, where the methods pass the input through, and a NaN holds the latch. Prices are positive and finite and lengths are in bars; the column count starts at the first bar of the slice.
OXXO: the latched breakout and its columns (Chande)
Tushar Chande (“Charting Breakouts With Convertible Point & Figure Charts”, Stocks & Commodities V39:12, November 2021, pp. 9-16) replaces point-and-figure reversals “at trailing stops from the highest high and lowest low close” with “a price channel or a price band built around some moving average”, which “automatically guarantees that the reversal lines do not cross each other”, and advances one column for each reversal. t6OXXO latches the strict breaks of his “standard 20-day price channel … plotted with a one-day delay” with Breakout(n, 1).Hysteresis(0, 0): +1 in a rising column, -1 in a falling one, 0 before the first break; cols and age are the column views of this category’s recipe over PointAndFigureDirection. Chande’s fourth defect of point and figure, that “the reversal levels often cross over”, belongs to his simplified rule of reversals from a trailing close channel, not to this category’s engine, whose one reversal level is measured from the column’s extreme; what OXXO removes is the box size and the missing time axis.
st := c.Breakout(n, 1).Hysteresis(0, 0)
cols := st.Delta().Abs().GT(0).CumSum()
age := st.StateAge().Mul(st.Abs())- Inputs: closes; the channel length
n(Chande’s 20). - First valid index: n,
Breakout’s n + lag - 1 with lag 1;st,colsandageread 0 before the first break.colscounts columns from the slice’s first bar, so compare differences between runs on one slice, andagecounts the bars in the column from 1, so 0 means only that no column has started. - Anchors:
stequals a loop of Chande’s rule (a close above the highest of the previous n closes starts or continues a rising column, one below their lowest a falling one, anything else holds) and, withb := c.Breakout(n, 1),c.PositionSigned(b.EQ(1), b.EQ(-1), b.EQ(-1), b.EQ(1)), bit for bit, since b is not +1 and -1 on one bar;colsandageequal their loops; a constant series stays 0 and a strictly rising one is one rising column from bar n. - Traps: a latch over +-1 flags needs
Hysteresis(0, 0):Hysteresis(1, -1)decides only on values strictly beyond 1 and -1, so on flags it does not move. - Variant: the band form, with a centre line
cland widthswlandws:c.GT(cl.Add(wl)).Sub(c.LT(cl.Sub(ws))).Hysteresis(0, 0). Chande’s examples are a 50-day simple average plus and minus 3%, written asw := cl.Mul(0.03)withcl.Add(w)andcl.Sub(w)(cl.Mul(1.03)differs in the last bit), andc.Bollinger(20, 1)andc.Bollinger(20, -1)as the two levels; the caller masks the centre’s warm-up with NaN. - Variant: the trading form
c.PositionSigned(up, dn, dn, up)reverses where both flags fire on one bar (highs and lows beyond the bands, for example), where the latch holds. - Evidence: the article has no test, no table and no code; its figures plot the S&P 500’s closes, and the evidence for a channel stop and reverse is the literature on channel rules (Szakmary, Shen and Sharma, 2010). Evidence, not a reference.
- Pinned by
TestRecipeT6OXXO; runnable exampleExampleSeries_Breakout_oxxo.
Kagi
KagiYang
func (s Series) KagiYang(reversal float64) Series
KagiYang returns the thickness state of a kagi line drawn on the receiver with a reversal amount in the receiver’s units: +1 for a yang line, from a rising line that passes the latest shoulder, -1 for a yin line, from a falling line that passes the latest waist, held between such breaks, and 0 before the first line.
The line is the machine of DirectionalChangePoints(reversal), followed step for step, with reversal as both the up and the down threshold. The first valid bar sets a running high LH and a running low LL to its value and draws no line; until the first line both extremes are tracked, and after it each line tracks only its own extreme, LH while rising and LL while falling. On each later valid bar, with p its value, the machine extends first, setting LH = p when the line is not falling and p >= LH, and LL = p when it is not rising and p <= LL, a tie moving the extreme to the latest bar. It then runs the down test (LH - p) > reversal, which turns a rising line down or draws a first falling line, and otherwise the up test (p - LL) > reversal, which turns a falling line up or draws a first rising line; a rising line runs only the down test and a falling line only the up test. Each test is one subtraction compared with the reversal, not a level such as LH - reversal, which can round differently. On a down turn the top LH that the line turned from is posted as the latest shoulder S and LL restarts at p; on an up turn the bottom LL is posted as the latest waist W and LH restarts at p. So the line’s direction is DirectionalChangePoints(reversal) on every bar, and each shoulder or waist is the extreme that DirectionalChangePoints confirms on that bar.
The first line sets the state to its direction: a first rising line is yang and a first falling line is yin. After each valid bar’s update, and on a turning bar after the turn, the state becomes +1 when the line is rising, a shoulder has been posted and p > S, becomes -1 when the line is falling, a waist has been posted and p < W, and is held otherwise. So a falling line stays yang until it passes below the latest waist, and a rising line stays yin until it passes above the latest shoulder; a turn whose own bar is already beyond the latest waist or shoulder changes the state on that bar. Both comparisons are strict: a rising line that only reaches the shoulder, or a falling line that only reaches the waist, keeps its thickness. The latch compares p, the bar’s value, rather than the line’s extreme (LH while rising, LL while falling), and the two give the same state: the extreme changes only on a bar whose value it takes, and S and W do not change within one line, so the extreme passes S or W on exactly the bar where p first does. Each comparison is with the latest shoulder or waist, not the highest shoulder, the lowest waist or the first.
The reversal is crossed only when it is exceeded: a move equal to the reversal does not turn the line, so [100, 101] at reversal 1 draws no line. For a source whose rule turns on a move equal to the amount, call the method with math.Nextafter(reversal, math.Inf(-1)), the next float64 below a positive amount. Gopalakrishnan’s worked example applies her inclusive rule with a reversal of 3 to the closes 52, 55, 53, 50, 49, 55, 54, 58, 53, 54, 50, 51, 45, 48, 47, 40, 41, 45, 46 and 50 (sessions 1 to 20), and KagiYang(math.Nextafter(3, math.Inf(-1))) reproduces her Figure 3: 0 on session 1, yang on sessions 2 and 3, yin on sessions 4 to 7, yang on sessions 8 to 12, yin on sessions 13 to 19 and yang on session 20, with shoulders 55, 58 and 48 (posted on sessions 4, 9 and 16) and waists 52, 49, 45 and 40 (posted on sessions 2, 6, 14 and 18). On session 4 the turn down from 55 to 50 already passes below the waist 52 and makes the line yin on its own bar. Her text places the change from yin to yang in session 6, but session 6 closes at 55, equal to the shoulder 55, and her own rule asks the line to extend beyond 55; her Figure 3 thickens the line only above 55, which it reaches on session 8 (58), and the strict latch follows the figure. The strict call KagiYang(3), the house rule, gives 0, 0, 0, -1, -1, -1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1 by session: it draws no line on session 2 (a move of exactly 3), turns down on session 4 from the shoulder 55, and does not turn up on session 14 (45 to 48 is exactly 3), so it posts no shoulder at 48, its shoulders are 55 and 58, and the rise to 50 on session 20 stays yin.
The reversal is in receiver units, as in DirectionalChangePoints: points on prices, log units on a log series. The unit follows the method family, points here and a fraction in DirectionalChange. Gopalakrishnan prefers a percentage reversal: the line of a percentage kagi is DirectionalChange(p/100), and a thickness state on a fraction reversal is not built.
The reversal is valid when 0 <= reversal < +Inf. NaN (any payload) fails every comparison and is not valid; +Inf, -Inf and every negative value are not valid either, while +0, -0 (which compares equal to 0) and every positive finite value are valid. A reversal of 0 is not degenerate: any move against the line turns it, and a constant series draws no line. An invalid reversal gives len(s) zeros, and an empty or nil receiver gives an empty, non-nil result whatever the reversal.
There is no warm-up: the first valid index is 0 and the minimum length is 1. Bar 0 reads 0, as no line can start on the first valid bar. The 0 before the first line is the undetermined state of a data-dependent start, as in DirectionalChangePoints, not a warm-up: it is not reached again once a line is drawn, and c.DirectionalChangePoints(reversal).EQ(0) detects exactly the undetermined bars. The state is +1, -1 or +0 on every bar and is not NaN.
A bar whose value is NaN (any payload), +Inf or -Inf is missing: it updates nothing (no extension, no turn and no latch) and repeats the previous bar’s output, 0 at bar 0 and before the first line. This is the hold class of DirectionalChangePoints, where +Inf and -Inf are missing, not ordered values. Deleting every missing bar gives the same output on the remaining bars, bit for bit, and a leading run of missing bars reads 0 until the first valid bar starts the machine. Zero, -0 and negative values are ordinary values. A difference of two finite values of opposite signs near math.MaxFloat64 can overflow to +Inf, which exceeds every valid reversal (all are below +Inf) and turns the line.
The start departs from Gopalakrishnan’s. She draws the first line from the first close, the base, once a close is at least the reversal away from it, and makes the base the first waist of a first line up or the first shoulder of a first line down. This method starts from the running extremes instead, so that its line is that of DirectionalChangePoints on every bar; the first line’s waist or shoulder is then the running low or high it turned from. The two starts differ only when price wanders within the reversal of the base and then leaves on the other side: on 10, 11.5, 9, 7.5, 10.5 at reversal 2 this method gives 0, 0, -1, -1, -1 (the line turns down from 11.5 at bar 2, a shoulder that 10.5 does not pass), where the base start gives 0, 0, 0, -1, +1 (a first line down from the base 10 at bar 3, whose shoulder 10 the rise to 10.5 passes). On her worked example the two starts agree at her inclusive reversal; at the strict 3 a base start draws its first line on session 8, while this method turns down on session 4.
The state is a latch that remembers its line, shoulder and waist, so it is not start-invariant: a run started later can differ from the full run until it re-synchronises. The method is causal and deterministic: the output at bar t reads only the receiver up to bar t, every prefix of the receiver gives the first values of the full result bit for bit, nothing is revised, and repeated calls give the same bits.
The line’s direction is c.DirectionalChangePoints(reversal), +1 rising, -1 falling and 0 before the first line, and its turns are c.DirectionalChangePoints(reversal).Delta().Clamp(-1, 1), +1 on an up turn and -1 on a down turn, the first line’s included. The latest shoulder and waist are recipes: the extreme each turn confirms is read back verbatim as c.ShiftVariable(c.DirectionalChangePointsAge(reversal)), and ValueWhen holds it from the down turns as the latest shoulder and from the up turns as the latest waist, NaN before the first of each kind. The state is 0 on exactly the bars where DirectionalChangePoints(reversal).EQ(0) is 1, and it changes only to the line’s direction. Not built: a separate direction method (the direction is DirectionalChangePoints), shoulder and waist views (the recipes above), a fraction reversal, a reversal that changes from bar to bar (whose line DirectionalChangePointsScaled draws without the latch), a kagi on highs and lows, and Gopalakrishnan’s multi-level breaks and patterns, which her article does not specify precisely enough to build.
Sources: Gopalakrishnan, “Kagi Charts”, Technical Analysis of Stocks & Commodities, April 2000, after Nison, Beyond Candlesticks (1994), drawing the line on closes only, as this method does; and StockCharts’ ChartSchool, “Kagi Charts”, which describes the same breaks with an inclusive reversal. The evidence is a worked example and annotated charts with no test, and no trading edge is demonstrated.
Cost: one pass, O(1) per bar. The method allocates only the result, does not modify the receiver and is safe for concurrent use.
Verification: the method agrees bit for bit with two independent reference implementations. Negating the receiver negates the state, and on whole-number or quarter-step data, where every difference is exact, adding a whole constant or scaling the receiver and the reversal by the same power of two leaves it unchanged. On whole-number closes c below 2^52 in magnitude and a whole r >= 1, c.KagiYang(r - 0.5) equals c.PointAndFigureSignal(c, 1, r) on every bar where that signal is not 0, since the signal is the same latch on the same shoulders and waists but waits for a completed column of each kind, and c.PointAndFigureDirection(c, 1, r) equals c.DirectionalChangePoints(r - 0.5) on every bar. The bound is the engine’s: it treats a close 2^52 boxes or more from 0 as missing, while this method reads every finite close.
Recipes built on KagiYang are listed under “Compositions” in the “Kagi” category.
Compositions
Kagi recipes
KagiYang returns the thickness state of a kagi line: +1 yang, -1 yin, 0 before the first line. The line itself, its turns, its shoulders and waists and its breaks are recipes over the DirectionalChangePoints methods, whose machine KagiYang follows step for step with one reversal amount as both thresholds, and a percentage line is DirectionalChange, the fraction family of the same machine. c is the closes and R the reversal amount in the receiver’s units. Directions, turns, states, breaks and ages are whole numbers and the shoulders and waists are closes copied verbatim, so every identity marked (E) below holds bit for bit. W1 is Gopalakrishnan’s example (Figure 1 of her article, sessions 1 to 20): 52, 55, 53, 50, 49, 55, 54, 58, 53, 54, 50, 51, 45, 48, 47, 40, 41, 45, 46 and 50.
(a) The line and the inclusive source
R := math.Nextafter(3, math.Inf(-1)) // an inclusive source; plain 3 is the strict house rule
dir := c.DirectionalChangePoints(R) // +1 while the line rises, -1 while it falls, 0 before the first line
turns := dir.Delta().Clamp(-1, 1) // +1 on an up turn, -1 on a down turn, the first line included
yang := c.KagiYang(R) // +1 yang, -1 yin, 0 before the first line
- First valid index: 0.
dir,turnsandyangare 0 on bar 0 and until the first line, anddir.EQ(0)detects those bars:yang.EQ(0)equalsdir.EQ(0)on every input (E). - Tie rule: a reversal is crossed only when it is exceeded. Gopalakrishnan’s rule turns the line on a move equal to the amount, so her reversal of 3 is
math.Nextafter(3, math.Inf(-1)); a plain 3 is the strict house rule. - Anchors: on W1 at the inclusive 3,
diris [0, 1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, 1, 1] andyangis [0, 1, 1, -1, -1, -1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1], her Figure 3 by session. At the strict 3,diris [0, 0, 0, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1] andyangis [0, 0, 0, -1, -1, -1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1]: no line on session 2 (a move of exactly 3) and no turn on session 14 (45 to 48), so the rise to 50 on session 20 stays yin.turnsis +1 or -1 exactly on the bars wheredirchanges, and on every bar whereyangchanges it changes todir(E). - Pinned by
TestRecipeKagi; runnable exampleExampleSeries_KagiYang.
(b) The latest shoulder and waist
ref := c.ShiftVariable(c.DirectionalChangePointsAge(R)) // the extreme each turn confirms, copied verbatim
shoulder := ref.ValueWhen(turns.EQ(-1)) // the top the latest falling line turned from
waist := ref.ValueWhen(turns.EQ(1)) // the bottom the latest rising line turned from
- First valid index: 0.
shoulderis NaN until the first down turn andwaistuntil the first up turn. - Anchors:
shoulderandwaistare the latest shoulder and waist thatKagiYangcompares the closes with, on every input, missing bars included (E). On W1 at the inclusive 3 the shoulders are 55, 58 and 48 (posted on sessions 4, 9 and 16) and the waists 52, 49, 45 and 40 (sessions 2, 6, 14 and 18); at the strict 3 the shoulders are 55 and 58 (sessions 4 and 9) and the waists 49 and 40 (sessions 6 and 18). - Not the extension:
c.Sub(c.DirectionalChangePointsExtension(R))is guaranteed to read the same extremes only where both of its subtractions are exact: each close within a factor of two of the extreme it is measured from (Sterbenz’s lemma), or prices on a grid of quarter steps. Elsewhere it can miss by an ulp: on [0, 0.1, -0.4, 0.1] at 0.01 it reads the shoulder 0.1 as 0.09999999999999998, and the composition (c) built on it makes the last bar yang, where the close only touches the shoulder andKagiYanggives -1. - Pinned by
TestRecipeKagi; runnable exampleExampleSeries_KagiYang_shoulderWaist.
(c) The state as a composition
first := turns.Mul(turns.Abs().CumSum().EQ(1)) // the first line's turn, and 0 elsewhere
state := dir.EQ(1).Mul(c.GT(shoulder)).Sub(dir.EQ(-1).Mul(c.LT(waist))).Add(first).Hysteresis(0, 0)- First valid index: 0, as for
KagiYang. - Anchors:
stateequalsc.KagiYang(R)on every input without +Inf or -Inf, NaN bars included (E): the first line sets the state to its direction, a rising line’s close above the latest shoulder makes it +1, a falling line’s close below the latest waist makes it -1, andHysteresis(0, 0)holds it otherwise. On an infinite bar the two differ, becauseGTandLTread an infinity as an ordered value whereKagiYangholds a missing bar: on [10, 13, 11, 9, 11, +Inf] at 1 the composition reads +1 on the last bar andKagiYangholds -1. The composition is a check thatKagiYangis the latch it says, not a replacement: it allocates a Series at every step. - Pinned by
TestRecipeKagi.
(d) Breaks and ages
breaks := yang.Delta() // +2 yin to yang, -2 yang to yin, +1 or -1 at the first line
buy, sell := breaks.EQ(2), breaks.EQ(-2) // "Buy on the yang, Sell on the yin"
since := breaks.BarsSince() // bars since the state last changed
age := c.DirectionalChangePointsAge(R) // bars since the top or bottom the current line turned from
- First valid index: 0.
breaks,buy,sellandageare 0 before the first line, andsinceis NaN. - Anchors:
breaksis +1 or -1 only on the first line’s bar and +2 or -2 only on a bar where the state changes after it (E). From the first line on,c[t-age[t]]is the latest shoulder on a falling line’s bar t and the latest waist on a rising line’s (E). On W1 at the inclusive 3,breaksis [0, 1, 0, -2, 0, 0, 0, 2, 0, 0, 0, 0, -2, 0, 0, 0, 0, 0, 0, 2]: the buys of Gopalakrishnan’s rule “Buy on the yang, Sell on the yin” fall on sessions 8 and 20 and its sells on sessions 4 and 13. - Pinned by
TestRecipeKagi; runnable exampleExampleSeries_KagiYang.
(e) A percentage reversal
Gopalakrishnan prefers a reversal amount in percent. The line of a percentage kagi with a reversal of q percent is c.DirectionalChange(q/100): the same machine with ratio tests, whose thresholds are fractions (the units ruling of GLE-254). A constant expression such as 0.7/100 is folded exactly to the float64 nearest 0.007, while a run-time q/100 rounds q first: q = 0.7 gives 0.006999999999999999 (see DirectionalChange’s doc). No thickness state on a fraction reversal is built: KagiYang takes the reversal in receiver units only.
- Pinned by
TestRecipeKagi.
DeMark Indicators
TD Sequential’s setup phase
“TD Sequential”, “TD Setup”, “TD Price Flip”, “TDST” and the other TD names are trademarks of DeMark Analytics LLC (formerly Market Studies LLC), used here only to name the indicators; the methods’ names are descriptive (DeMarkSetup and so on).
These methods are the first half of Tom DeMark’s TD Sequential: the price flip, the setup count, setup perfection and the TDST levels, built on the true high and true low (TrueHigh and TrueLow, under “True Range”). The rulebook is Jason Perl’s DeMark Indicators (Bloomberg Press, 2008), chapter 1, with the Bloomberg default settings: a setup of at least 9 bars, a look-back of 4 bars and the close as the setup price, with the setup option “Include Prior Four Bars” off; TDST on, with its options “Extend the 2nd Most Recent TDST Level” and “Open Restriction” off. The three options are not built. DeMark’s 1994 rulebook (The New Science of Technical Analysis, as Davies applied it in 1995), with a non-strict flip and an “intersection” test in place of perfection, is a different indicator and is not built.
DeMarkPriceFlipmarks the flips: a bearish flip is a close above the close four bars earlier followed at once by a close below the close four bars earlier, and a bullish flip is the mirror. Both comparisons are strict.DeMarkSetupis the signed setup count. A buy setup starts on a bearish flip, its bar 1, and runs while each close is strictly below the close four bars earlier; it reads -1, -2 and so on. A sell setup mirrors it with +1, +2 and so on. Bar 9 completes a setup, and the count runs on past 9 until the run ends.DeMarkSetupPerfectedreports whether the most recently completed setup is perfected: -1 for a buy, +1 for a sell, and 0 while perfection is deferred.DeMarkSetupResistanceandDeMarkSetupSupportare the TDST levels: the highest true high of bars 1 to 9 of the most recently completed buy setup, and the lowest true low of bars 1 to 9 of the most recently completed sell setup, each held until the next completion of its side.
The setup count is the whole state of the setup phase: the three views read it, and each runs it again. A tie or a comparison with NaN ends a run, and +Inf and -Inf are ordinary values. The flip, the count and the perfection read 0 before their first event; the levels read NaN until the first completed setup of their side. Each method’s doc gives its rules in full. Under a slice of history the count settles at the first flip inside the slice and the levels at the first completion of their side, so give the methods a few hundred bars.
Evidence. Perl writes that he has not backtested the signals (“I have not felt the need to backtest the signals”, p. 48), and the book gives no trade statistics. Coles (2011) and Davies (1995) give single charts or hand-picked trades. The sources offer chart examples only; no backtest supports these signals.
The second phase. TD Sequential’s countdown, with its qualifiers, cancellation, recycling and the R of an 18-bar setup, TD Aggressive Sequential, TD Combo and the risk levels read the count of DeMarkSetup; they follow the setup recipes below.
Setup recipes
The blocks share their names: c, h and l are the closes, highs and lows, k comes from the first block and res and sup from the second.
Completion and display. Each setup completes once, on its bar 9, and bar 13 is the first bar that can complete one. Charting systems show bars 1 to 9 only.
k := c.DeMarkSetup()
buy9 := k.EQ(-9) // 1 on bar 9 of each buy setup, its completion bar
sell9 := k.EQ(9) // 1 on bar 9 of each sell setup
display := k.Clamp(-9, 9) // the count as charting systems show it
Trend bias (Perl’s two scenarios, pp. 6-8). In Scenario I, consolidation or reversal, no close up to and including bar 9 of the active setup is beyond the TDST level of the most recently completed setup in the opposite direction: the market lacks the momentum to break out of the range, and Perl expects a reversal or at least a consolidation lasting a minimum of one to four bars. In Scenario II, confirmed trend extension, a close beyond that level shows the momentum for a sustained break. Only closes count: a break within a bar does not.
res := c.DeMarkSetupResistance(h)
sup := c.DeMarkSetupSupport(l)
bias := c.GT(res).Sub(c.LT(sup)) // +1 above resistance, -1 below support, 0 otherwise
A comparison with a NaN level is false, so a side with no completed setup yet adds nothing, and a NaN close reads 0. The levels come from different setups and can cross: a close above the resistance and below the support at once reads 0.
Setup trade filter (Perl, pp. 13 and 16). Perl trades a completed buy setup only when it is perfected and none of its nine bars has closed below TDST support, and a sell setup only when it is perfected and none has closed above TDST resistance: Scenario I on the setup’s own bars.
p := c.DeMarkSetupPerfected(h, l)
buyClean := c.LT(sup).Sum(9).EQ(0).ValueWhen(k.EQ(-9)).EQ(1) // no close of the buy setup's nine bars below support
sellClean := c.GT(res).Sum(9).EQ(0).ValueWhen(k.EQ(9)).EQ(1) // no close of the sell setup's nine bars above resistance
buyOK := p.EQ(-1).Mul(buyClean) // 1 while the latest completed setup is a perfected buy setup with clean closes
sellOK := p.EQ(1).Mul(sellClean) // 1 while it is a perfected sell setup with clean closes
The support does not change during a buy setup’s nine bars, since no sell setup completes inside it (and the resistance does not change during a sell setup’s), so the sum compares all nine closes with the level that held at the completion. Before the first completed sell setup the support is NaN and no close is below it. Perl’s other conditions, a bar 9 close near the opposite level, the optional close of bar 9 beyond the close of bar 8, and his test of the distance from the bar 9 close to the setup’s own TDST level (the resistance that a completed buy setup has just set, the support that a sell setup has just set; pp. 14 and 17) against the distance to the risk level, are the caller’s.
Setup risk level (Perl, pp. 14 and 17). For a buy setup, take the bar of its nine with the lowest true low and subtract that bar’s true range from its true low; for a sell setup, add the true range of the bar with the highest true high to its true high. Among equal extremes the latest bar is taken. IndexOfLowestS(9) is the position of the latest lowest value in the nine-bar window, 0 for the oldest bar, so 8 minus it is the lag back to that bar:
tl := l.TrueLow(c)
tr := c.TrueRange(h, l, c)
lag := tl.IndexOfLowestS(9).Mul(-1).Add(8) // bars back to the lowest true low of the nine
buyRisk := tl.ShiftVariable(lag).Sub(tr.ShiftVariable(lag)).ValueWhen(k.EQ(-9))
th := h.TrueHigh(c)
lagH := th.IndexOfHighestS(9).Mul(-1).Add(8) // bars back to the highest true high of the nine
sellRisk := th.ShiftVariable(lagH).Add(tr.ShiftVariable(lagH)).ValueWhen(k.EQ(9))Each level is NaN until the first completed setup of its side and is held until the next. The true range of a bar is its true high minus its true low (h.TrueHigh(c).Sub(l.TrueLow(c)) equals c.TrueRange(h, l, c)). The TDST levels’ NaN rule does not carry over: IndexOfLowestS compares with <=, so a NaN true low on the oldest of the nine bars is kept as the lowest and gives a NaN risk level, while a NaN true low on a later bar is skipped; IndexOfHighestS does the same with >=. If a missing bar should leave the level undefined, test the nine bars at the completion with tl.Lowest(9) (th.Highest(9)), which is NaN for a window that holds a NaN. DeMarkSetupRiskLevel is the method: one series holding the level of the most recently completed setup of either side, NaN when any of its nine bars has a NaN true low or true high. On NaN-free data it equals buyRisk on the bars where k is -9 and sellRisk where k is 9.
TD Sequential’s countdown phase and TD Combo
These methods are the second half of TD Sequential, with TD Aggressive Sequential, TD Combo and the TD risk levels, from Perl’s chapters 1 and 2 with the Bloomberg default settings (pp. 57-58 for TD Sequential, p. 67 for TD Combo):
- Countdown: 13 bars with a look-back of 2 bars; the termination count and the countdown price are the close; the risk level is taken over all the bars of the countdown; the termination qualifier is on, and the “8 vs 5 Qualifier” (p. 20) and “Show Countdown Bars After Minimum” are off.
- Recycle: “Reverse Setup Cancel” and “Recycle Confirm” are on; setups within setups are enabled, with “Use Maximum Setup” and “Extreme Setup Closes Within True Range”; the recycle count is 18, the recycle setup rule “Recycle All” and the recycle multiplier 1.000 to 1.618.
- TD Combo uses the same countdown and recycle settings, and its panel has no termination qualifier. Its option “Use ‘Equal’ for Comparison: All Bars” is a platform option and is not built: the methods follow Perl’s text, whose last two Combo conditions are strict.
The methods:
DeMarkCountdownis TD Sequential’s countdown. It starts on the bar that completes a setup, which can be countdown bar 1, and counts a buy bar when the close is at or below the low two bars earlier (a sell bar when the close is at or above the high two bars earlier), not necessarily on consecutive bars. Countdown bar 13 also needs a low at or below the close of countdown bar 8 (a high at or above it); a bar that fails only that test is deferred, Perl’s “+”, and the count stays at 12. The count reads -n on bar n of a buy countdown and +n on bar n of a sell countdown, holds between countdown bars, posts the 13 once and reads 0 when no countdown is developing.DeMarkCountdownAggressiveis TD Aggressive Sequential: a buy bar needs a low at or below the low two bars earlier (a sell bar a high at or above the high two bars earlier), with the same bar-8 qualifier.DeMarkCombois TD Combo in Perl’s strict Version I. It counts from bar 1 of the completed setup, evaluated on the completion bar, which can read up to 9, and a buy bar needs a close at or below the low two bars earlier, a low at or below the previous bar’s low, and a close below both the previous countdown bar’s close (a test skipped on countdown bar 1) and the previous bar’s close (the mirror for a sell).DeMarkComboRelaxedis Version II, whose bars 11 to 13 need only a close below the previous countdown bar’s close (above it for a sell). Neither has a bar-13 qualifier.DeMarkCountdownRiskLevel,DeMarkCountdownAggressiveRiskLevel,DeMarkComboRiskLevelandDeMarkComboRelaxedRiskLevelpost the TD risk level of each completed countdown on its 13. Over every bar from countdown bar 1 to bar 13, numbered or not, it is the true low of the bar with the lowest true low less that bar’s true range for a buy, and the true high of the bar with the highest true high plus its true range for a sell, the latest of equal extremes.DeMarkSetupRiskLeveldoes the same over the nine bars of each completed setup. Each holds its level until the next completion on either side, and a NaN true low or true high in the window makes the level NaN.
One countdown at a time. One countdown develops at a time, on either side. A completed setup of the opposite side cancels it and starts its own countdown, and a true low strictly above the anchor setup’s TDST resistance (a true high strictly below its support) cancels it; a NaN level cancels nothing. When a setup of the same side completes while it develops, the countdown continues if the new setup’s closes lie within the anchor run’s range and its own extreme, the lowest true low for a buy or the highest true high for a sell, does not pass the anchor’s (Qualifier II, tested first); otherwise it restarts on the new setup if the new setup’s range is at least the anchor’s and below 1.618 times it (Qualifier I), and continues if not. A setup run that reaches bar 18 starts a countdown afresh on it, whether or not one is developing (Perl’s R). Bloomberg shows only the countdown closest to completion and lets a second one count in the background (Perl, p. 51); those background countdowns are not modelled. DeMarkCountdown’s doc gives the rulebook in full.
Minimum length. Perl gives at least twenty-two price bars for a TD Sequential signal and only thirteen for TD Combo (p. 59) without saying how he counts them. Counted from the bar before setup bar 1 to countdown bar 13, when countdown bar 1 is setup bar 9, and from setup bar 1 to countdown bar 13 for TD Combo, they match the methods: the earliest 13 falls on bar 25 of a series in DeMarkCountdown and DeMarkCountdownAggressive, and on bar 17 in the two Combos.
Evidence. As for the setup phase, the sources give chart examples only; no backtest supports the countdown signals.
Not built. DeMark’s open-based termination count (p. 26), the validation of a TD Sequential 9-13-9 count (p. 29), the four-step valid break of a risk level (p. 28; Perl prefers an intrabar stop), TD Reference Close (p. 28) and TD Aggressive Combo, which the book names but does not define. The entries are recipes, below.
Countdown recipes
The blocks share their names: c, h and l are the closes, highs and lows, cd and buy13 come from the first block.
Completion and the deferred bars. The 13 is posted once, on its bar. A deferred bar, Perl’s “+”, is a bar on which the count was already 12, still is, and the bar passes the countdown comparison: only the bar-8 qualifier failed. For DeMarkCountdownAggressive the comparisons are l.LTE(l.Shift(2)) and h.GTE(h.Shift(2)).
cd := c.DeMarkCountdown(h, l)
buy13 := cd.EQ(-13) // 1 on the bar where a buy countdown completes
sell13 := cd.EQ(13) // 1 on the bar where a sell countdown completes
buyDeferred := cd.EQ(-12).Mul(cd.StateAge().GT(1)).Mul(c.LTE(l.Shift(2)))
sellDeferred := cd.EQ(12).Mul(cd.StateAge().GT(1)).Mul(c.GTE(h.Shift(2)))Recycle count 18 (Perl’s R, p. 22). The countdown restarts on the bar where a setup reaches its bar 18. Perl’s chart prints R where the countdown’s 13 would have been; a series cannot rewrite a later bar, so the restart and this flag stand for it.
rBuy := c.DeMarkSetup().EQ(-18) // 1 where a buy setup reaches bar 18
rSell := c.DeMarkSetup().EQ(18) // 1 where a sell setup reaches bar 18
The risk level of one side. The risk methods hold the level of the latest 13 on either side; the levels of one side are the method sampled on that side’s 13s.
risk := c.DeMarkCountdownRiskLevel(h, l)
buyRisk := risk.ValueWhen(cd.EQ(-13)) // the level of the latest completed buy countdown
sellRisk := risk.ValueWhen(cd.EQ(13)) // the level of the latest completed sell countdown
Entries (Perl, p. 24). The aggressive entry buys on the close of the 13 bar, buy13 itself. The conservative entry waits, after the 13, for the first bullish price flip; the flip on the 13’s own bar does not count, and the next 13 starts the wait again. Any further condition, such as an opposite setup in between, is the caller’s.
up := c.DeMarkPriceFlip().EQ(1).Mul(buy13.EQ(0)) // the bullish flips, less any on a buy 13's own bar
buyEntry := up.Mul(up.SumSince(buy13).EQ(1)) // the first bullish flip after the latest buy 13
Sell entries mirror these with sell13 and the bearish flips, c.DeMarkPriceFlip().EQ(-1).
DeMarkCombo
func (s Series) DeMarkCombo(h, l Series) Series
DeMarkCombo is DeMark’s TD Combo countdown, Perl’s strict Version I, counted from bar 1 of the completed setup and posted from its bar 9 on.
The receiver s holds the closes, written c below, h the highs and l the lows of the same bars. When a countdown is anchored on a setup completing on bar t, afresh or by a recycle, the setup’s bars 1 to 9 (bars t-8 to t) are evaluated in order on that bar and bar t posts the number of them that pass, up to 9; no earlier bar is rewritten (Jason Perl’s DeMark Indicators (2008), p. 60, with the Bloomberg defaults). A completion that leaves a developing countdown in place is tested once, as any other bar. From then on each bar is tested once. A buy countdown counts bar t when c[t] <= l[t-2], l[t] <= l[t-1], c[t] is below the close of the previous countdown bar and c[t] < c[t-1]; a sell countdown when c[t] >= h[t-2], h[t] >= h[t-1], c[t] is above the close of the previous countdown bar and c[t] > c[t-1]. The first two conditions are inclusive and the last two strict, as Perl writes them (pp. 60-61); the comparison with the previous countdown bar is skipped on countdown bar 1. There is no bar-13 qualifier. Bloomberg’s panel option “Use ‘Equal’ for Comparison: All Bars” is a platform option and is not built. The count is held between countdown bars and the 13 is posted once, on its bar; the completed countdowns are cd.EQ(-13) and cd.EQ(13) with cd := c.DeMarkCombo(h, l). One countdown develops at a time, with the cancellations, Qualifier II, Qualifier I and the restart at bar 18 of DeMarkCountdown; a recycle on a completed setup evaluates that setup’s nine bars retrospectively, while a restart at bar 18 counts from that bar on, not retrospectively. Perl’s minimum for a TD Combo signal is 13 bars, setup bar 1 to countdown bar 13, so the earliest 13 falls on bar 17. The setup count is that of DeMarkSetup; the manual’s “DeMark Indicators” section holds the trademark attribution, the recipes and the note that nothing here is a backtest.
NaN, +Inf and -Inf are not errors and are not skipped: a comparison with NaN is false, so a NaN close, high or low fails every test that reads it, a NaN close ends the setup run through it, and +Inf and -Inf compare as ordinary values. The count is not NaN on any input and every 0 is +0. Warm-up: bars 0 to 12 read 0, since no setup completes before bar 13; bar 13 can read up to 9 and bar 17 is the earliest 13. Under a slice of history the count is latched on the setups inside the slice: the slice reads 0 before its first completed setup and agrees with the full history from that bar on when the full history has no countdown developing there after that bar’s cancellations, so give a few hundred bars. Pure: the inputs are not modified, even when the receiver and the arguments are one slice, and the result is a new Series. The result is Series{} when len(h) or len(l) differs from len(s), and an empty Series for an empty receiver.
DeMarkComboRelaxed
func (s Series) DeMarkComboRelaxed(h, l Series) Series
DeMarkComboRelaxed is the TD Combo countdown in Perl’s less strict Version II, whose bars 11 to 13 need only successively lower (higher) closes.
The receiver s holds the closes, written c below, h the highs and l the lows of the same bars. Countdown bars 1 to 10 are counted as in DeMarkCombo: a completed setup’s bars 1 to 9 are evaluated in order on its completion bar, which posts up to 9 while no earlier bar is rewritten, and each later bar is tested once, a buy bar needing c[t] <= l[t-2], l[t] <= l[t-1], a close below the close of the previous countdown bar and c[t] < c[t-1], a sell bar the mirror, with the comparison with the previous countdown bar skipped on countdown bar 1. Countdown bars 11 to 13 need only a close strictly below the close of the previous countdown bar for a buy, strictly above it for a sell, Perl’s “the closes need only be successively lower”, as in Jason Perl’s DeMark Indicators (2008), pp. 61 and 65, with the Bloomberg defaults. There is no bar-13 qualifier, and Bloomberg’s “Use ‘Equal’ for Comparison: All Bars” is not built. One countdown develops at a time, with the cancellations, Qualifier II, Qualifier I and the restart at bar 18 of DeMarkCountdown (a restart at bar 18 counts from that bar on, not retrospectively), and the 13 is posted once, on its bar: the completed countdowns are cd.EQ(-13) and cd.EQ(13) with cd := c.DeMarkComboRelaxed(h, l). So c.DeMarkCombo(h, l) equals c.DeMarkComboRelaxed(h, l) on every bar before the first 11 of either. The setup count is that of DeMarkSetup; the manual’s “DeMark Indicators” section holds the trademark attribution, the recipes and the note that nothing here is a backtest.
NaN, +Inf and -Inf are not errors and are not skipped: a comparison with NaN is false, so a NaN close, high or low fails every test that reads it, a NaN close ends the setup run through it, and +Inf and -Inf compare as ordinary values. The count is not NaN on any input and every 0 is +0. Warm-up: bars 0 to 12 read 0, since no setup completes before bar 13; bar 13 can read up to 9 and bar 17 is the earliest 13. Under a slice of history the count is latched on the setups inside the slice: the slice reads 0 before its first completed setup and agrees with the full history from that bar on when the full history has no countdown developing there after that bar’s cancellations, so give a few hundred bars. Pure: the inputs are not modified, even when the receiver and the arguments are one slice, and the result is a new Series. The result is Series{} when len(h) or len(l) differs from len(s), and an empty Series for an empty receiver.
DeMarkComboRelaxedRiskLevel
func (s Series) DeMarkComboRelaxedRiskLevel(h, l Series) Series
DeMarkComboRelaxedRiskLevel is the TD risk level of the most recently completed TD Combo countdown in Version II: the true low of its lowest-true-low bar less that bar’s true range for a buy, the mirror for a sell, held until the next.
The receiver s holds the closes, written c below, h the highs and l the lows of the same bars. The countdown is that of DeMarkComboRelaxed, cd := c.DeMarkComboRelaxed(h, l), run by the same engine under the same rules, those of Jason Perl’s DeMark Indicators (2008) with the Bloomberg defaults (p. 62 and Figure 2.2). When the countdown posts its 13 on bar t, its window is every bar from countdown bar 1 to bar t inclusive, numbered or not; countdown bar 1 can be a bar of the setup, before the completion bar that anchored the countdown, and the window starts there. For a buy countdown the level is tl - (th - tl) on the bar of the window with the lowest true low, the latest of equal lows, where tl is that bar’s true low, the lesser of its low and the close of the bar before, and th its true high, the greater of its high and the close of the bar before; for a sell countdown it is th + (th - tl) on the bar with the highest true high, the latest of equal highs. A NaN true low or true high on any bar of the window makes the level NaN; +Inf and -Inf follow the arithmetic. The level is posted on the 13 bar and held until the next 13 on either side, so with risk := c.DeMarkComboRelaxedRiskLevel(h, l) the result equals risk.ValueWhen(cd.Abs().EQ(13)) on every bar, a NaN matching a NaN, and the buy levels alone are risk.ValueWhen(cd.EQ(-13)). A level posted on a buy 13 that is not NaN is at most that bar’s low, and one posted on a sell 13 at least its high; the manual’s “DeMark Indicators” section holds the trademark attribution, the recipes and the note that nothing here is a backtest.
Warm-up: NaN until the first completed countdown, which cannot fall before bar 17 (NaN rather than 0, since 0 is a price). Under a slice of history the level is NaN before the slice’s first 13 and equals the full history’s from the first 13 at or after the bar from which the counts agree, the slice’s first completed setup when the full history has no countdown developing there after that bar’s cancellations: the setups inside the slice decide, so give a few hundred bars. Pure: the inputs are not modified, even when the receiver and the arguments are one slice, and the result is a new Series. The result is Series{} when len(h) or len(l) differs from len(s), and an empty Series for an empty receiver.
DeMarkComboRiskLevel
func (s Series) DeMarkComboRiskLevel(h, l Series) Series
DeMarkComboRiskLevel is the TD risk level of the most recently completed TD Combo countdown in Version I: the true low of its lowest-true-low bar less that bar’s true range for a buy, the mirror for a sell, held until the next.
The receiver s holds the closes, written c below, h the highs and l the lows of the same bars. The countdown is that of DeMarkCombo, cd := c.DeMarkCombo(h, l), run by the same engine under the same rules, those of Jason Perl’s DeMark Indicators (2008) with the Bloomberg defaults (p. 62 and Figure 2.2). When the countdown posts its 13 on bar t, its window is every bar from countdown bar 1 to bar t inclusive, numbered or not; countdown bar 1 can be a bar of the setup, before the completion bar that anchored the countdown, and the window starts there. For a buy countdown the level is tl - (th - tl) on the bar of the window with the lowest true low, the latest of equal lows, where tl is that bar’s true low, the lesser of its low and the close of the bar before, and th its true high, the greater of its high and the close of the bar before; for a sell countdown it is th + (th - tl) on the bar with the highest true high, the latest of equal highs. A NaN true low or true high on any bar of the window makes the level NaN; +Inf and -Inf follow the arithmetic. The level is posted on the 13 bar and held until the next 13 on either side, so with risk := c.DeMarkComboRiskLevel(h, l) the result equals risk.ValueWhen(cd.Abs().EQ(13)) on every bar, a NaN matching a NaN, and the buy levels alone are risk.ValueWhen(cd.EQ(-13)). A level posted on a buy 13 that is not NaN is at most that bar’s low, and one posted on a sell 13 at least its high; the manual’s “DeMark Indicators” section holds the trademark attribution, the recipes and the note that nothing here is a backtest.
Warm-up: NaN until the first completed countdown, which cannot fall before bar 17 (NaN rather than 0, since 0 is a price). Under a slice of history the level is NaN before the slice’s first 13 and equals the full history’s from the first 13 at or after the bar from which the counts agree, the slice’s first completed setup when the full history has no countdown developing there after that bar’s cancellations: the setups inside the slice decide, so give a few hundred bars. Pure: the inputs are not modified, even when the receiver and the arguments are one slice, and the result is a new Series. The result is Series{} when len(h) or len(l) differs from len(s), and an empty Series for an empty receiver.
DeMarkCountdown
func (s Series) DeMarkCountdown(h, l Series) Series
DeMarkCountdown is DeMark’s TD Sequential countdown on the receiver’s closes: -k on bar k of a buy countdown, +k on bar k of a sell countdown, held between countdown bars and 0 when none is developing.
The receiver s holds the closes, h the highs and l the lows of the same bars; below, the closes are written c, the caller’s view, as in cd := c.DeMarkCountdown(h, l). The rules are those of Jason Perl’s DeMark Indicators (2008) with the Bloomberg default settings, run on the setup count of DeMarkSetup; the manual’s “DeMark Indicators” section holds the trademark attribution, the recipes and the note that nothing here is a backtest.
A countdown starts on the bar that completes a setup, bar 9 of a setup run, and that bar can be countdown bar 1: a buy countdown counts bar t when c[t] <= l[t-2] and a sell countdown when c[t] >= h[t-2], both inclusive (Perl, p. 18). Until a bar passes, bar 1 is postponed and the count reads 0. Countdown bars need not be consecutive: a bar that fails leaves the count where it is, so every bar of a developing countdown reads its latest count. On the bar that would be countdown bar 13 the termination qualifier (on by default, p. 19) also requires a low at or below the close of countdown bar 8 for a buy, a high at or above it for a sell; a bar that passes the comparison but fails the qualifier is a deferred bar, Perl’s “+”, on which the count stays at 12 and the following bars are tested again. The deferred buy bars are cd.EQ(-12).Mul(cd.StateAge().GT(1)).Mul(c.LTE(l.Shift(2))) and the deferred sell bars the mirror with cd.EQ(12) and c.GTE(h.Shift(2)). The 13 is posted once, on its bar: the next bar reads 0 unless a countdown is anchored there, so the completed buy countdowns are cd.EQ(-13) and the completed sell countdowns cd.EQ(13).
One countdown develops at a time, on either side (Perl, p. 21). It is cancelled when a setup of the opposite side completes, which starts a countdown of that side on the same bar, or when the bar’s true low rises strictly above the anchor setup’s TDST resistance for a buy (the highest TrueHigh of the setup’s bars 1 to 9, the value DeMarkSetupResistance posted on its bar 9) or its true high falls strictly below the TDST support for a sell (the lowest TrueLow of its bars 1 to 9, the value of DeMarkSetupSupport). The level is the anchor setup’s own and does not move with a later setup that leaves the countdown in place. A NaN level cancels nothing, and nor does a NaN true low or true high. After a cancellation the next completed setup, or bar 18 of a run, starts a countdown.
When a setup of the same side completes while a countdown develops, Qualifier II is tested first: the new setup is contained, and the countdown continues as it is, when every close of the new setup’s bars 1 to 9 lies within the anchor run’s extremes (its lowest true low to its highest true high over the anchor run’s whole extension, bars beyond 9 included for as long as the run lasted) and, for a buy, its lowest true low is at or above the anchor’s, for a sell its highest true high at or below the anchor’s, every comparison inclusive (pp. 21-22). Otherwise Qualifier I compares the ranges: with oldR the anchor run’s range and newR the new setup’s range over its bars 1 to 9, the countdown is recycled, restarting on the new setup, when oldR <= newR and newR is below 1.618 times oldR; outside that band the countdown continues as it is. A NaN range recycles nothing. A recycle on the bar where the old countdown would have posted its 13 discards that 13. When a setup run reaches bar 18 (Perl’s R, p. 22) the countdown restarts on that run, whatever was developing and whether anything was, with the level of the run’s bars 1 to 9; those bars are c.DeMarkSetup().EQ(-18) and c.DeMarkSetup().EQ(18).
Bloomberg’s background countdowns (so there is no “closest to completion” rule), the open-based termination count, the 8 versus 5 qualifier, “Include Prior Four Bars” and “Show Countdown Bars After Minimum” are not built: the background countdowns have no stated start rule, and the rest are off by default or are not Perl’s preference.
NaN, +Inf and -Inf are not errors and are not skipped: a comparison with NaN is false, so a NaN close, high or low fails every test that reads it, a NaN close ends the setup run through it, and +Inf and -Inf compare as ordinary values. The count is not NaN on any input and every 0 is +0. Warm-up: bars 0 to 12 read 0, since no setup completes before bar 13, and the earliest 13 falls on bar 25. Under a slice of history the count is latched on the setups inside the slice: the slice reads 0 before its first completed setup and agrees with the full history from that bar on when the full history has no countdown developing there after that bar’s cancellations, so give a few hundred bars. Pure: the inputs are not modified, even when the receiver and the arguments are one slice, and the result is a new Series. The result is Series{} when len(h) or len(l) differs from len(s), and an empty Series for an empty receiver.
DeMarkCountdownAggressive
func (s Series) DeMarkCountdownAggressive(h, l Series) Series
DeMarkCountdownAggressive is the TD Aggressive Sequential countdown, which counts a buy bar on a low at or below the low two bars earlier and a sell bar on a high at or above the high two bars earlier.
The receiver s holds the closes, written c below, h the highs and l the lows of the same bars. A buy countdown counts bar t when l[t] <= l[t-2] and a sell countdown when h[t] >= h[t-2], both inclusive, in place of the close comparisons of DeMarkCountdown (Jason Perl’s DeMark Indicators (2008), p. 58, with the Bloomberg defaults). Everything else is as in DeMarkCountdown: the start on the completion bar of a setup, with a postponed bar 1 until a bar passes; the count held between countdown bars; one countdown at a time, cancelled by an opposite completion or a strict break of the anchor setup’s own TDST level, where a NaN level cancels nothing; Qualifier II before Qualifier I, with the band from the anchor’s range to 1.618 times it; the restart at bar 18; the 13 posted once; and the termination qualifier, which is unchanged: countdown bar 13 also needs a low at or below the close of countdown bar 8 for a buy, a high at or above it for a sell, and a bar that passes the comparison but not the qualifier is deferred, with the count staying at 12. The deferred buy bars are cd.EQ(-12).Mul(cd.StateAge().GT(1)).Mul(l.LTE(l.Shift(2))) and the deferred sell bars the mirror with cd.EQ(12) and h.GTE(h.Shift(2)), with cd := c.DeMarkCountdownAggressive(h, l); the completed countdowns are cd.EQ(-13) and cd.EQ(13). The setup count is that of DeMarkSetup; the manual’s “DeMark Indicators” section holds the trademark attribution, the recipes and the note that nothing here is a backtest.
On bars with l <= c <= h and no NaN, a bar that passes the close comparison of DeMarkCountdown passes this one, so the absolute count here is at least that of DeMarkCountdown on every bar before the first 13 of either. From then on the two can differ either way: a TD Sequential 13 can be posted while this countdown is deferred at 12 by the bar-8 qualifier, which qualifies Perl’s remark that TD Aggressive Sequential signals first.
NaN, +Inf and -Inf are not errors and are not skipped: a comparison with NaN is false, so a NaN high or low fails every test that reads it, a NaN close ends the setup run through it (and fails the bar-8 qualifier when it is the close of countdown bar 8), and +Inf and -Inf compare as ordinary values. The count is not NaN on any input and every 0 is +0. Warm-up: bars 0 to 12 read 0, since no setup completes before bar 13, and the earliest 13 falls on bar 25. Under a slice of history the count is latched on the setups inside the slice: the slice reads 0 before its first completed setup and agrees with the full history from that bar on when the full history has no countdown developing there after that bar’s cancellations, so give a few hundred bars. Pure: the inputs are not modified, even when the receiver and the arguments are one slice, and the result is a new Series. The result is Series{} when len(h) or len(l) differs from len(s), and an empty Series for an empty receiver.
DeMarkCountdownAggressiveRiskLevel
func (s Series) DeMarkCountdownAggressiveRiskLevel(h, l Series) Series
DeMarkCountdownAggressiveRiskLevel is the TD risk level of the most recently completed TD Aggressive Sequential countdown: the true low of its lowest-true-low bar less that bar’s true range for a buy, the mirror for a sell, held until the next.
The receiver s holds the closes, written c below, h the highs and l the lows of the same bars. The countdown is that of DeMarkCountdownAggressive, cd := c.DeMarkCountdownAggressive(h, l), run by the same engine under the same rules, those of Jason Perl’s DeMark Indicators (2008) with the Bloomberg defaults; the window and the formulas are those of the TD Sequential level (p. 26 and Figure 1.20), applied to the aggressive countdown. When the countdown posts its 13 on bar t, its window is every bar from countdown bar 1 to bar t inclusive, numbered or not. For a buy countdown the level is tl - (th - tl) on the bar of the window with the lowest true low, the latest of equal lows, where tl is that bar’s true low, the lesser of its low and the close of the bar before, and th its true high, the greater of its high and the close of the bar before; for a sell countdown it is th + (th - tl) on the bar with the highest true high, the latest of equal highs. A NaN true low or true high on any bar of the window makes the level NaN; +Inf and -Inf follow the arithmetic. The level is posted on the 13 bar and held until the next 13 on either side, so with risk := c.DeMarkCountdownAggressiveRiskLevel(h, l) the result equals risk.ValueWhen(cd.Abs().EQ(13)) on every bar, a NaN matching a NaN, and the buy levels alone are risk.ValueWhen(cd.EQ(-13)). A level posted on a buy 13 that is not NaN is at most that bar’s low, and one posted on a sell 13 at least its high; the manual’s “DeMark Indicators” section holds the trademark attribution, the recipes and the note that nothing here is a backtest.
Warm-up: NaN until the first completed countdown, which cannot fall before bar 25 (NaN rather than 0, since 0 is a price). Under a slice of history the level is NaN before the slice’s first 13 and equals the full history’s from the first 13 at or after the bar from which the counts agree, the slice’s first completed setup when the full history has no countdown developing there after that bar’s cancellations: the setups inside the slice decide, so give a few hundred bars. Pure: the inputs are not modified, even when the receiver and the arguments are one slice, and the result is a new Series. The result is Series{} when len(h) or len(l) differs from len(s), and an empty Series for an empty receiver.
DeMarkCountdownRiskLevel
func (s Series) DeMarkCountdownRiskLevel(h, l Series) Series
DeMarkCountdownRiskLevel is the TD risk level of the most recently completed TD Sequential countdown: the true low of its lowest-true-low bar less that bar’s true range for a buy, the mirror for a sell, held until the next.
The receiver s holds the closes, written c below, h the highs and l the lows of the same bars. The countdown is that of DeMarkCountdown, cd := c.DeMarkCountdown(h, l), run by the same engine under the same rules, those of Jason Perl’s DeMark Indicators (2008) with the Bloomberg defaults (p. 26 and Figure 1.20). When the countdown posts its 13 on bar t, its window is every bar from countdown bar 1 to bar t inclusive, numbered or not. For a buy countdown the level is tl - (th - tl) on the bar of the window with the lowest true low, the latest of equal lows, where tl is that bar’s true low, the lesser of its low and the close of the bar before, and th its true high, the greater of its high and the close of the bar before; for a sell countdown it is th + (th - tl) on the bar with the highest true high, the latest of equal highs. A NaN true low or true high on any bar of the window makes the level NaN; +Inf and -Inf follow the arithmetic. The level is posted on the 13 bar and held until the next 13 on either side, so with risk := c.DeMarkCountdownRiskLevel(h, l) the result equals risk.ValueWhen(cd.Abs().EQ(13)) on every bar, a NaN matching a NaN, and the buy levels alone are risk.ValueWhen(cd.EQ(-13)). A level posted on a buy 13 that is not NaN is at most that bar’s low, and one posted on a sell 13 at least its high; the manual’s “DeMark Indicators” section holds the trademark attribution, the recipes and the note that nothing here is a backtest.
Warm-up: NaN until the first completed countdown, which cannot fall before bar 25 (NaN rather than 0, since 0 is a price). Under a slice of history the level is NaN before the slice’s first 13 and equals the full history’s from the first 13 at or after the bar from which the counts agree, the slice’s first completed setup when the full history has no countdown developing there after that bar’s cancellations: the setups inside the slice decide, so give a few hundred bars. Pure: the inputs are not modified, even when the receiver and the arguments are one slice, and the result is a new Series. The result is Series{} when len(h) or len(l) differs from len(s), and an empty Series for an empty receiver.
DeMarkPriceFlip
func (s Series) DeMarkPriceFlip() Series
DeMarkPriceFlip marks DeMark’s TD Price Flip on the receiver’s closes: +1 on a bullish flip, -1 on a bearish flip and 0 otherwise.
With c the closes, a bearish flip on bar t is c[t-1] > c[t-5] and c[t] < c[t-4]: a close above the close four bars earlier, immediately followed by a close below the close four bars earlier (Perl, p. 2). A bullish flip is the mirror, c[t-1] < c[t-5] and c[t] > c[t-4]. The comparisons are strict float64 comparisons: a tie, -0 against +0 included, gives no flip, a comparison with NaN is false, and the infinities are ordinary values. The output is -1, 0 or +1 on every bar and is not NaN on any input; a zero is +0.
The flip does not depend on any setup state. A bearish flip is bar 1 of a buy setup and a bullish flip is bar 1 of a sell setup, so the flip equals c.DeMarkSetup() exactly where the count is -1 or +1, and a flip against a running setup ends it: a bearish flip during a sell setup starts a buy setup on the same bar. With up := c.GT(c.Shift(4)) and dn := c.LT(c.Shift(4)), the result equals up.Mul(dn.Shift(1)).Sub(dn.Mul(up.Shift(1))).SetN(5, 0) bit for bit on every bar, and so does k.Abs().EQ(1).Mul(k).Add(0) for k := c.DeMarkSetup().
Warm-up: bars 0 to 4 read 0, since a flip needs bar t-5, and bar 5 is the first that can read -1 or +1. The flip on bar t reads bars t-5 to t only, so where a slice of history starts matters only for the slice’s first five bars: for d := c[m:], d.DeMarkPriceFlip() equals c.DeMarkPriceFlip()[m:] from bar 5 of the slice on, and bars 0 to 4 inside the slice read 0. An empty receiver gives an empty, non-nil Series.
The rules are those of Jason Perl’s DeMark Indicators (2008), with the Bloomberg default settings. See the manual’s “DeMark Indicators” section for the trademark attribution, the recipes and the absence of any backtest. Pure: the result is a new Series, the receiver is not modified, concurrent calls are safe, and the method allocates only the result.
DeMarkSetup
func (s Series) DeMarkSetup() Series
DeMarkSetup is DeMark’s TD Setup count on the receiver’s closes: -k on bar k of a buy setup, +k on bar k of a sell setup and 0 otherwise, counting past 9 until the run ends.
With c the closes, a buy setup starts on a bearish flip, c[t-1] > c[t-5] and c[t] < c[t-4] (see DeMarkPriceFlip), which is its bar 1 and reads -1, and continues while each close is strictly below the close four bars earlier, c[t] < c[t-4], reading -2, -3 and so on. A sell setup mirrors it: it starts on a bullish flip as +1 and continues while c[t] > c[t-4]. Any bar without the run’s comparison ends the run, whatever its length: a close equal to the close four bars earlier (-0 against +0 included), a comparison with NaN, or a close on the other side. That last close is itself a flip of the opposite sign and so bar 1 of the other setup, while a tie or a NaN comparison gives 0. From bar to bar the size of the count rises by exactly 1 or falls to 0 or 1, and the count is -1 or +1 exactly where DeMarkPriceFlip is -1 or +1.
A run of lower (higher) closes that begins without a flip, because the prior-bar comparison of its first bar tied, read a NaN or came before bar 5, reads 0 throughout, so a series rising from bar 0 reads 0 everywhere. The count does not stop at 9: it continues as long as the run persists (Perl, p. 23), and c.DeMarkSetup().Clamp(-9, 9) gives the display of charting systems, which show bars 1 to 9 only. A buy setup completes on its bar 9, where c.DeMarkSetup().EQ(-9) is 1, and a sell setup where c.DeMarkSetup().EQ(9) is 1; each setup completes once, and bar 13 is the first bar that can complete one (a flip on bar 5, and its bar 9 on bar 13).
The count is the age of a run whose first bar is a flip. With x := c.LT(c.Shift(4)).SetN(4, 0), a := x.StateAge(), g := c.Shift(1).GT(c.Shift(5)).SetN(5, 0).ValueWhen(a.EQ(1)) and buy := a.Mul(x).Mul(g), and their mirrors y := c.GT(c.Shift(4)).SetN(4, 0), b := y.StateAge(), gs := c.Shift(1).LT(c.Shift(5)).SetN(5, 0).ValueWhen(b.EQ(1)) and sell := b.Mul(y).Mul(gs), the result equals sell.Sub(buy) bit for bit on every bar, NaN, the infinities, signed zeros and ties included. Coles’s MetaStock test for a completed buy setup (2011) is, in this notation, x.Sum(9).EQ(9).Mul(c.Shift(9).GT(c.Shift(13))), and it equals c.DeMarkSetup().EQ(-9) from bar 13 on; its sell form, y.Sum(9).EQ(9).Mul(c.Shift(9).LT(c.Shift(13))), equals c.DeMarkSetup().EQ(9) from bar 13 on (before bar 13 the Shift padding can fire them). His printed listing maps price field 4, labelled C, to the low, a misprint. pandas-ta’s td_seq is a different count: it counts runs of strict c[t] < c[t-4] (and of c[t] > c[t-4]) with no flip and caps them at 13, so it also counts runs that this method reads as 0; on a buy run that began with a flip it equals the negated count while the run is at most 13 bars long.
A NaN close on bar p makes both comparisons false on bars p and p+4, and the prior-bar comparisons of the flips on bars p+1 and p+5 false; the infinities are ordinary values. The count is a whole number stored as float64, a zero is +0, and the output is not NaN on any input. Warm-up: bars 0 to 4 read 0 (the real no-setup state), bar 5 is the first that can read -1 or +1, and bar 13 the first that can complete a setup. Under a slice of history the count settles at the first flip inside the slice: for d := c[m:], d.DeMarkSetup() reads 0 before the first non-zero of d.DeMarkPriceFlip() and equals c.DeMarkSetup()[m:] from that bar on, so give a few hundred bars. An empty receiver gives an empty, non-nil Series.
DeMarkSetupPerfected, DeMarkSetupResistance and DeMarkSetupSupport read this count, and each runs it again, as do the countdowns and the TD risk levels of the manual’s “DeMark Indicators” section. The rules are those of Jason Perl’s DeMark Indicators (2008), with the Bloomberg default settings: a minimum of 9 bars, a look-back of 4 and the close as the setup price. See the manual’s “DeMark Indicators” section for the trademark attribution, the recipes and the absence of any backtest. Pure: the result is a new Series, the receiver is not modified, and the method allocates only the result.
DeMarkSetupPerfected
func (s Series) DeMarkSetupPerfected(h, l Series) Series
DeMarkSetupPerfected reports whether the most recently completed TD Setup is perfected: -1 for a buy, +1 for a sell, 0 while deferred or before the first completion.
The receiver is the closes c, h the highs and l the lows, and the setup count is c.DeMarkSetup(). When a buy setup completes on bar t (the count is -9, its bar 9), the reference is r = min(l[t-3], l[t-2]), the lesser of the lows of setup bars 6 and 7, and the setup is perfected on its bar 9 if l[t-1] <= r or l[t] <= r, the low of bar 8 or of bar 9 at or below it: the output becomes -1. Otherwise perfection is deferred: the output reads 0 and the first later bar with l[t] <= r perfects the setup. A sell setup mirrors this with the highs: r = max(h[t-3], h[t-2]), and h[t-1] >= r or h[t] >= r, or a later h[t] >= r, gives +1. Perl’s rule box (p. 11) asks for a low of bar 8 or 9, or a subsequent low, “less than, or equal to,” the lows of bars 6 and 7, so equal lows perfect: <= for buys and >= for sells.
The output belongs to the most recently completed setup on either side. Each completion replaces whatever was held or pending, even on a bar whose low (high) would satisfy the pending reference, and a setup deferred on its bar 9 reads 0 there even when the previous setup’s -1 or +1 was held; between completions the value is held. A deferral has no other expiry: a flip, a setup that starts but does not complete, or any number of bars leaves it pending until a later low (high) perfects it or the next completion on either side replaces it. Perl gives no limit, and his Figure 1.10 perfects a buy setup five bars after its bar 9.
A comparison with NaN is false. A NaN low on bar 6 or 7 makes r NaN, so that buy setup cannot be perfected; a NaN low on bar 8 leaves the test to bar 9 and a NaN low on bar 9 leaves it to bar 8, since the two bars are tested one by one; a NaN low on a later bar does not perfect it. Sells mirror this with the highs. The infinities are ordinary values, so a -Inf low on bar 6 is matched only by a -Inf low. A NaN close affects the count only. The output is -1, 0 or +1 on every bar and is not NaN on any input.
The perfecting bars are p.Mul(p.StateAge().EQ(1)) with p the output: non-zero exactly where p changes to -1 or +1, on or after bar 9 of a completed setup and before the next completion, with the sign of its side and at most one for each completed setup (none when a setup is perfected on its bar 9 while the previous setup’s equal value is held). Perfection is Perl’s first condition for trading a completed setup.
If h or l does not have the receiver’s length, the result is Series{}. Warm-up: 0 before the first completion, so bars 0 to 12 read 0. Under a slice d := c[m:] (h and l sliced alike) the output reads 0 before the first completed setup inside the slice and equals the full result from it on. An empty receiver gives an empty, non-nil Series.
The rules are those of Jason Perl’s DeMark Indicators (2008), with the Bloomberg default settings. See the manual’s “DeMark Indicators” section for the trademark attribution, the recipes and the absence of any backtest. Pure: the result is a new Series, no input is modified, even when h or l is the receiver, and the method allocates only the result.
DeMarkSetupResistance
func (s Series) DeMarkSetupResistance(h Series) Series
DeMarkSetupResistance is the TDST resistance level, the highest true high of bars 1 to 9 of the most recently completed buy setup, held until the next.
The receiver is the closes c and h the highs. When a buy setup completes on bar t, its bar 9, where c.DeMarkSetup() is -9, the level becomes the greatest TrueHigh of the setup’s bars 1 to 9, that is bars t-8 to t, where the true high of bar j is max(h[j], c[j-1]). The true high reaches back to the close before a bar that gapped down, so the level can be a close rather than a high. The level is held on every later bar until the next completed buy setup replaces it: it is held through sell setups and through the bars beyond 9 of the same run, which are ignored. Perl, p. 14: TDST resistance is “the true high of the most recently completed TD Buy Setup”. Bloomberg’s setup option “Include Prior Four Bars” and its TDST options “Extend the 2nd Most Recent TDST Level” and “Open Restriction”, all off by default, are not built.
The nine true highs are folded with Go’s builtin max: a NaN true high among them gives a NaN level until the next completed buy setup, even beside +Inf; +0 is taken over -0; and the infinities are otherwise ordinary values. The result equals h.TrueHigh(c).Highest(9).ValueWhen(c.DeMarkSetup().EQ(-9)) bit for bit on every bar, for equal-length inputs of any values.
Perl reads the level in two scenarios: a sell setup none of whose closes is above TDST resistance lacks the momentum to break out of the range, so a reversal or at least a consolidation is expected, while a close above it suggests that the uptrend continues; TDST support plays the same part for buy setups. With res := c.DeMarkSetupResistance(h) and sup := c.DeMarkSetupSupport(l), the trend bias c.GT(res).Sub(c.LT(sup)) is +1 on a close above resistance, -1 on a close below support and 0 otherwise (a comparison with a NaN level is false).
If h does not have the receiver’s length, the result is Series{}. An empty receiver gives an empty, non-nil Series. Warm-up: NaN until the first completed buy setup, NaN rather than 0 since the start depends on the data, so bars 0 to 12 read NaN. Under a slice d := c[m:] (h sliced alike) the level reads NaN before the first completed buy setup inside the slice and equals the full result from it on.
The rules are those of Jason Perl’s DeMark Indicators (2008), with the Bloomberg default settings. See the manual’s “DeMark Indicators” section for the trademark attribution, the recipes and the absence of any backtest. Pure: the result is a new Series, no input is modified, even when h is the receiver, and the method allocates only the result.
DeMarkSetupRiskLevel
func (s Series) DeMarkSetupRiskLevel(h, l Series) Series
DeMarkSetupRiskLevel is the TD risk level of the most recently completed TD Setup, from the extreme true low or true high of its bars 1 to 9, held until the next completion.
The receiver s holds the closes, written c below, h the highs and l the lows of the same bars; the setup count is that of DeMarkSetup, and no countdown is run. When a buy setup completes on bar t, where that count reads -9, the level is tl - (th - tl) on the bar of bars t-8 to t with the lowest true low, the latest of equal lows, where tl is that bar’s true low, the lesser of its low and the close of the bar before, and th its true high, the greater of its high and the close of the bar before; when a sell setup completes (the count reads 9) it is th + (th - tl) on the bar with the highest true high, the latest of equal highs. These are Perl’s stops for trading a completed setup, as in Jason Perl’s DeMark Indicators (2008), pp. 14 and 17, with the Bloomberg defaults. A NaN true low or true high on any of the nine bars makes the level NaN; +Inf and -Inf follow the arithmetic. The level is posted on the completion bar and held until the next completion on either side; the bars of a setup beyond 9 do not move it, so with k := c.DeMarkSetup() the result equals its own ValueWhen(k.Abs().EQ(9)) on every bar. On NaN-free data the level on a buy completion bar equals tl.ShiftVariable(lag).Sub(tr.ShiftVariable(lag)) with tl := l.TrueLow(c), tr := c.TrueRange(h, l, c) and lag := tl.IndexOfLowestS(9).Mul(-1).Add(8), and on a sell completion bar th.ShiftVariable(lagH).Add(tr.ShiftVariable(lagH)) with th := h.TrueHigh(c) and lagH := th.IndexOfHighestS(9).Mul(-1).Add(8); that ShiftVariable recipe can differ on NaN data, and the manual’s “DeMark Indicators” section holds the trademark attribution, the recipes and the note that nothing here is a backtest.
Warm-up: NaN until the first completed setup, which cannot fall before bar 13 (NaN rather than 0, since 0 is a price). Under a slice of history the level is NaN before the slice’s first completed setup and equals the full history’s from that bar on: the setups inside the slice decide. Pure: the inputs are not modified, even when the receiver and the arguments are one slice, and the result is a new Series. The result is Series{} when len(h) or len(l) differs from len(s), and an empty Series for an empty receiver.
DeMarkSetupSupport
func (s Series) DeMarkSetupSupport(l Series) Series
DeMarkSetupSupport is the TDST support level, the lowest true low of bars 1 to 9 of the most recently completed sell setup, held until the next.
The receiver is the closes c and l the lows. When a sell setup completes on bar t, its bar 9, where c.DeMarkSetup() is 9, the level becomes the least TrueLow of the setup’s bars 1 to 9, that is bars t-8 to t, where the true low of bar j is min(l[j], c[j-1]). The true low reaches back to the close before a bar that gapped up, so the level can be a close rather than a low. The level is held on every later bar until the next completed sell setup replaces it: it is held through buy setups and through the bars beyond 9 of the same run, which are ignored. Perl, p. 17: TDST support is “the true low of the most recently completed TD Sell Setup”. Bloomberg’s setup option “Include Prior Four Bars” and its TDST options “Extend the 2nd Most Recent TDST Level” and “Open Restriction”, all off by default, are not built.
The nine true lows are folded with Go’s builtin min: a NaN true low among them gives a NaN level until the next completed sell setup, even beside -Inf; -0 is taken over +0; and the infinities are otherwise ordinary values. The result equals l.TrueLow(c).Lowest(9).ValueWhen(c.DeMarkSetup().EQ(9)) bit for bit on every bar, for equal-length inputs of any values.
Perl reads the level in two scenarios: a buy setup none of whose closes is below TDST support lacks the momentum to break out of the range, so a reversal or at least a consolidation is expected, while a close below it suggests that the downtrend continues; TDST resistance plays the same part for sell setups. With res := c.DeMarkSetupResistance(h) and sup := c.DeMarkSetupSupport(l), the trend bias c.GT(res).Sub(c.LT(sup)) is +1 on a close above resistance, -1 on a close below support and 0 otherwise (a comparison with a NaN level is false).
If l does not have the receiver’s length, the result is Series{}. An empty receiver gives an empty, non-nil Series. Warm-up: NaN until the first completed sell setup, NaN rather than 0 since the start depends on the data, so bars 0 to 12 read NaN. Under a slice d := c[m:] (l sliced alike) the level reads NaN before the first completed sell setup inside the slice and equals the full result from it on.
The rules are those of Jason Perl’s DeMark Indicators (2008), with the Bloomberg default settings. See the manual’s “DeMark Indicators” section for the trademark attribution, the recipes and the absence of any backtest. Pure: the result is a new Series, no input is modified, even when l is the receiver, and the method allocates only the result.
Elliott Wave
ElliottTrend
func (s Series) ElliottTrend(l Series, n int, trigger float64) Series
ElliottTrend returns Ruggiero’s Elliott trend latch on the Elliott Wave oscillator: 0 until its first n-bar high (+1) or low (-1), then -1 when the oscillator falls below -trigger times a positive n-bar high and +1 when it rises above trigger times the magnitude of a negative n-bar low.
The receiver holds the highs and l the lows of the same bars, and osc is their Elliott Wave oscillator, the result of ElliottWaveOscillator: with h the receiver and mid = h.Add(l).Div(2), osc is mid.Average(5).Sub(mid.Average(35)) from bar 34 and +0 on bars 0 to 33. Lengths are checked before parameters: when len(l) differs from len(s), or the receiver is empty, the result is Series{}; otherwise, when n < 1, or trigger is negative or not finite, the result is all +0. There are no defaults and no upper bound on n or trigger; trigger -0 behaves as +0.
The latch is Table 13.2 of Murray Ruggiero’s Cybernetic Trading Strategies (1997). With x = osc[t] and the n-bar extremes hh = osc.Highest(n) and ll = osc.Lowest(n) over bars t-n+1 to t, NaN when the window holds a NaN, each bar from n + 33 on applies four rules in the order printed, each reading the state the rule before it left: if x == hh and the state is 0, it becomes +1; if x == ll and the state is 0, it becomes -1; if ll < 0, the state is -1 and x > float64(-trigger * ll), it becomes +1; if hh > 0, the state is +1 and x < float64(-trigger * hh), it becomes -1. The equality tests are exact equality, under which +0 equals -0, the other comparisons are strict, and every comparison with NaN is false.
Ruggiero’s prose says the trend stays up until the oscillator retraces the trigger percent of the n-bar high, and his narrative speaks of a retracement of 138 percent; the printed code is the definition. For trigger >= 0 no bar fires two rules, so the order matters only between the first two. A flat window, in which hh equals ll and x equals both, reads +1 in state 0 since the first rule comes first; with n = 1 every window is flat, so the latch turns +1 on bar 34 (or on the first later bar whose oscillator is not NaN) and does not flip again, and constant input reads +1 from bar n + 33. A flip up needs x > 0 and a flip down x < 0: the oscillator must cross zero and pass trigger times the opposite extreme, so trigger = 0 makes it a zero-cross latch gated by the sign of that extreme. After the first latch the state is +1 or -1 for good.
Warm-up: the result is +0 on bars 0 to n+32, and all +0 when n + 33 >= len(s); bar n + 33 is the first whose window of the oscillator starts at bar 34 or later. The start is fixed, not data-dependent, so the warm-up reads +0, the latch’s own state before its first bar, and not NaN; a caller who wants StateAge to count only evaluable bars masks it first: tr.SetN(n+33, math.NaN()).StateAge() with tr the result. The output is +1, -1 or +0 on every bar, and it is not -0.
A NaN high or low on a bar p at bar 34 or later makes the oscillator NaN on bars p to p+34 and the n-bar extremes NaN on bars p to p+n+33; every rule compares with hh or ll, so the latch holds its state on those bars and then resumes. Infinities are ordinary values: the extremes and thresholds can be infinite and compare as such, a product that overflows is an infinity, and with trigger = 0 an infinite extreme gives a NaN threshold, so that flip does not fire.
The latch is not up.Sub(dn).Hysteresis(0, 0), with up and dn the two flip conditions: that seeds from 0 on the first flip condition, where Ruggiero seeds only at an n-bar extreme, so the two can seed differently and stay apart. Nor is it HighestSince: the extremes are windowed, not taken since an event.
Ruggiero tested n from 20 to 180 and trigger from 0.5 to 1.0; he reports a run with n = 20 and trigger 0.66, and the inputs of his listing are 80 and 0.7.
The output on bar t uses bars 0 to t only, and nothing is redrawn. The latch is path-dependent: a run on the slice h[m:], l[m:] reads as the full run, bit for bit, from the first bar on which the slice’s trend flips between -1 and +1 at least one bar after its own bar n + 33, since both runs are then in the same state with the same inputs ahead; before it the runs can differ, and there is no fixed convergence bar. This needs the slice’s oscillator to equal the full run’s from that bar on, which can fail where a partial sum of Average overflows (see ElliottWaveOscillator).
The short side runs the same latch on negated, swapped inputs, -osc in place of osc: l.Mul(-1).ElliottTrend(h.Mul(-1), n, trigger).Mul(-1).Add(0) equals this result on every bar, except when the bar on which either run first latches has a flat window, which seeds +1 on both; the final Add(0) turns the -0 of the negated warm-up back into +0. See the manual’s “Elliott Wave” section for the recipes. The cost is O(len(s)) whatever n is, the n-bar extremes costing amortised O(1) a bar. The inputs are not modified, the result shares no storage with them, and concurrent calls are safe.
ElliottWaveCount
func (s Series) ElliottWaveCount(l Series, n int, trigger float64) Series
ElliottWaveCount returns Ruggiero’s mechanical 3-4-5 count of the up wave on the Elliott Wave oscillator: 3, 4 or 5 while ElliottTrend is up, -3 while it is down and 0 before the first down trend.
The receiver holds the highs and l the lows of the same bars, and the trend is ElliottTrend with the same n and trigger; h means the receiver. Lengths are checked before parameters: when len(l) differs from len(s), or the receiver is empty, the result is Series{}; otherwise, when n < 1, or trigger is negative or not finite, the result is all +0.
The count is Table 13.3 of Murray Ruggiero’s Cybernetic Trading Strategies (1997). With et the trend on bar t and et1 the trend on the bar before, x the oscillator, p the median price (h+l)/2 and top the greatest median price over bars t-4 to t, each bar from n + 33 on applies nine rules in the order printed, each reading the state the rule before it left. First, if et == 1, et1 == -1 and x > 0, it stores hiOsc = x and hiPrice = p and sets the count to 3. In wave 3 it raises hiOsc to x and hiPrice to p when they are exceeded, and if then x <= 0 and et == 1 it sets 4. In wave 4, if p == top and x >= 0, it sets 5 and stores hiOsc2 = x and hiPrice2 = p. In wave 5 it raises hiOsc2 and hiPrice2 when they are exceeded, and if then hiOsc2 > hiOsc, hiPrice2 > hiPrice and et == 1 it sets 3 with hiOsc = hiOsc2 and hiPrice = hiPrice2. Last, if et == -1, it sets -3. The comparisons are as printed: <= and >= in the fourth and fifth rules, exact equality in the fifth and strict elsewhere, and a comparison with NaN is false. Each stored peak is read only while the count is in the wave that set it, so the peaks’ starting values do not matter (the listing uses -999).
Two cascades on one bar are kept: from 3 to 4 to 5, when x is exactly 0 and the median price is at its 5-bar high, and from 4 to 5 to 3, on a wave-5 entry bar whose x and p already exceed both first peaks. A cascade from 3 to 4 to 5 to 3 on one bar cannot happen: the fifth rule would store a second oscillator peak hiOsc2 of 0 while the first peak, hiOsc, is still positive. The count is -3 on every bar on which the trend is -1, not only in a wave 5 as Ruggiero’s comment says, and 3, 4 or 5 on every bar on which it is +1 after the first bar with the trend at -1; a first latch of +1 leaves it at 0 until the trend has been -1. Since a flip up needs x > 0, every flip from -1 to +1 starts a wave 3, so the count reads 3 on every flip bar.
No source prints a down count. On negated, swapped inputs the same rules run on -osc and -mid: l.Mul(-1).ElliottWaveCount(h.Mul(-1), n, trigger) labels with 3, 4 and 5 the waves of a down move and marks an up trend with -3. The manual’s “Elliott Wave” section gives the recipe.
Warm-up: the result is +0 on bars 0 to n+32, and all +0 when n + 33 >= len(s), as for ElliottTrend. The output is one of -3, +0, 3, 4 and 5 on every bar; it is not -0 and not NaN.
NaN enters only through comparisons, which are false. While the window of the oscillator holds a NaN the trend holds its state, but the count does not simply hold: on a bar whose oscillator is NaN the rules that read x are false, yet a valid median price can still raise a stored price peak, and the eighth rule can then relabel a wave 5 as 3 on that bar. Where only the n-bar extremes are NaN, the count reads the oscillator as usual while the trend holds. Infinities are ordinary values, and a stored price peak of +Inf is not exceeded.
Ruggiero’s system, Table 13.5, buys when the count enters wave 3 or wave 5 and exits when the oscillator turns negative: the entries w.EQ(3).Mul(w.Shift(1).LTE(0)), w.EQ(5).Mul(w.Shift(1).EQ(4)) and w.EQ(3).Mul(w.Shift(1).EQ(5)) and the exit osc.LT(0), filled at the next bar’s open through Position, are recipes in the manual’s “Elliott Wave” section. With n = 20, trigger 0.66 and $50 deducted per trade for slippage and commissions, he reports for the D-Mark from February 1975 to March 1996 a net profit of $35,350 in 57 trades with 51% profitable, an average trade of $690.35 and a drawdown of -$10,237.50 for a profit factor of 2.10 (Table 13.6), and for the yen from August 1976 to March 1996 a net profit of $89,800 in 51 trades with 51% profitable, an average trade of $1,760.70 and a drawdown of -$5,975.00 for a profit factor of 4.16 (Table 13.7). Over 80 percent of the runs he tested with n from 20 to 180 and trigger from 0.5 to 1.0 were profitable. These are his results, on continuous futures contracts, and they are not reproduced here.
The output on bar t uses bars 0 to t only, and nothing is redrawn. The count is path-dependent: a run on the slice h[m:], l[m:] reads as the full run from the first bar with the trend at -1 at or after the bar from which the two trends agree, as stated for ElliottTrend and with its condition on the oscillator: there the last rule sets -3 in both runs, and no stored peak is read again before it is set anew; before that bar the runs can differ. The cost is O(len(s)) whatever n is. The inputs are not modified, the result shares no storage with them, and concurrent calls are safe.
ElliottWaveOscillator
func (s Series) ElliottWaveOscillator(l Series) Series
ElliottWaveOscillator returns Tom Joseph’s Elliott Wave oscillator, the 5-bar simple average of the median price (h+l)/2 minus its 35-bar simple average, with the receiver the highs and l the lows.
The receiver holds the highs and l the lows of the same bars, the layout of h.BasingPointLowValue(l, k), and the result is in the prices’ units. Both series must have the same length: when len(l) differs from len(s), or the receiver is empty, the result is Series{}. There are no parameters.
With h the receiver and mid = h.Add(l).Div(2), the result equals mid.Average(5).Sub(mid.Average(35)) bit for bit on bars 34 and later, bar 34 closing the first full 35-bar window. Warm-up: the result is +0 on bars 0 to 33, and all +0 for a series of 34 bars or fewer: Average passes its input through before a full window, so the plain difference would read a difference of prices there.
The oscillator is coded in Table 13.2 of Murray Ruggiero’s Cybernetic Trading Strategies (1997), whose chapter 13 attributes it to Tom Joseph and his Advanced GET software. The listing computes the oscillator only when the 35-bar average is not 0 and otherwise keeps the previous value; on positive prices the guard does not bind, so it is dropped and the difference is computed on every bar. The Awesome Oscillator of Bill Williams is the same difference with 34 bars in place of 35, mid.Average(5).Sub(mid.Average(34)) from bar 33; it is not a method but a recipe in the manual’s “Elliott Wave” section.
A NaN high or low on a bar p at bar 34 or later makes the oscillator NaN on bars p to p+34 (one on an earlier bar, on bars 34 to p+34). Infinities are ordinary values: the oscillator is NaN where both averages are infinities of the same sign, or where a window holds infinities of both signs, and an infinity where only the 35-bar one is, so a lone +Inf high on a bar p at bar 34 or later gives NaN on bars p to p+4 and -Inf on bars p+5 to p+34. The sum is rounded before it is halved, so a bar with both prices at math.MaxFloat64 has an infinite median price and acts as an infinite high.
The output on bar t uses bars 0 to t only, and the oscillator is not path-dependent: from bar 34 each value reads the last 35 bars. (The compensated sum of Average groups its partial sums by the window’s position since the start, so where a partial sum overflows, with prices near math.MaxFloat64, a run on a slice can read another value, -Inf where the full run reads NaN for example; elsewhere runs on slices gave the same bits on every input tested.) The cost is O(len(s)). The inputs are not modified and the result shares no storage with them, so concurrent calls on shared inputs are safe.
Compositions
Elliott Wave recipes
The recipes below read the three methods on the highs h and the lows l of the same bars, with n and trig the latch’s bar count and trigger. The methods come from Murray Ruggiero’s Cybernetic Trading Strategies (1997), chapter 13: the oscillator and the latch from Table 13.2 and the count from Table 13.3. Every recipe is pinned by TestRecipeT4ElliottWave, which checks each line of the Go blocks below against the test’s own copies and each statement against direct scans.
(a) Ruggiero’s system (Table 13.5)
Ruggiero’s trading system buys at the open when the count enters wave 3 from -3 (the listing tests a previous count of 0 or less), enters wave 5 from wave 4, or is relabelled from 5 to 3, and leaves the trade at the open when the oscillator is negative. His orders are placed on a bar’s close for the next bar’s open, so each flag is shifted by one bar before Position reads it, with the opens o as the receiver (Position reads only their length; with o as the receiver, EntryValue and the other trade views are anchored on the fill):
osc := h.ElliottWaveOscillator(l)
w := h.ElliottWaveCount(l, n, trig)
fromLow := w.EQ(3).Mul(w.Shift(1).LTE(0))
fromFour := w.EQ(5).Mul(w.Shift(1).EQ(4))
fromFive := w.EQ(3).Mul(w.Shift(1).EQ(5))
entry := fromLow.Add(fromFour).Add(fromFive)
exit := osc.LT(0)
pos := o.Position(entry.Shift(1), exit.Shift(1))- Entries: each flag is 0 or 1 and no two fire on one bar, so
entryis 0 or 1. The count goes from 0 only to 0 or -3, sofromLowfires exactly on the bars on which the trend flips from -1 to +1: every flip up starts a wave 3. A bar that runs through a cascade, from 3 to 4 to 5 or from 4 to 5 to 3, reads 5 after 3 or 3 after 4, so none of the three entries fires on it, as in the listing, which also reads only the previous bar’s count. A relabel from 5 to 3 can come on a bar whose oscillator is NaN;exitis 0 there. - Fills:
Shift(1)pads bar 0 with +0, which does not fire.Positionignores an entry while long (no pyramiding) and does not test the exit on the bar it enters. - First valid index: the count is +0 on bars 0 to n+32 and cannot read 3 on bar n + 33, so the first entry comes on bar n + 34 at the earliest, and the first trade opens on the bar after it.
- Parameters: the listing’s inputs are
Len(80), Trig(.7); the run Ruggiero reports uses n = 20 and trigger 0.66, and he tested n from 20 to 180 and trigger from 0.5 to 1.0, over 80 percent of those runs being profitable. - Results (not reproduced): with $50 deducted per trade for slippage and commissions, he reports for the D-Mark from February 1975 to March 1996 a net profit of $35,350 in 57 trades, 51% profitable, an average trade of $690.35, a drawdown of -$10,237.50 and a profit factor of 2.10 (Table 13.6), and for the yen from August 1976 to March 1996 $89,800 in 51 trades, 51% profitable, $1,760.70, -$5,975.00 and 4.16 (Table 13.7). They come from Genesis continuous futures contracts run in TradeStation and are not reproduced here.
- Pinned by
TestRecipeT4ElliottWave; runnable exampleExampleSeries_ElliottWaveCount_system.
(b) The down side
No source prints a down count: Ruggiero codes only the up sequence and adds “We could also trade the short side” (p. 193). Negation is exact (Mul(-1) and the package’s sums round symmetrically), so on the negated lows as the receiver and the negated highs as l the latch and the count run on the negated oscillator and median price, every comparison reversed when written on the original values:
dt := l.Mul(-1).ElliottTrend(h.Mul(-1), n, trig).Mul(-1).Add(0)
dw := l.Mul(-1).ElliottWaveCount(h.Mul(-1), n, trig)- The trend:
dtequalsh.ElliottTrend(l, n, trig)on every bar, except when the bar on which either run first latches has a flat window (the oscillator’s n-bar high equals its n-bar low): the first rule seeds +1 on both, so the mirror reads -1 where the original reads +1, and the two can stay apart. Flat windows occur at any n: a plateau of n + 34 bars or more at an integer median price gives an oscillator of exactly 0 over a whole window, and on constant inputdtreads -1 from bar n + 33 while the trend reads +1.Add(0)turns the -0 thatMul(-1)makes of the warm-up’s +0 back into +0. - The count:
dwlabels with 3, 4 and 5 the waves of a down move: it is -3 on every bar on whichdtis +1, and 3, 4 or 5 on every bar on whichdtis -1 oncedthas been +1; it is +0 before. The short side of (a) takes the three entries ofdwand the exitosc.GT(0), which is the mirrored oscillator’sLT(0). - First valid index: n + 33, as for the methods.
- Pinned by
TestRecipeT4ElliottWave; runnable exampleExampleSeries_ElliottWaveCount_down.
(c) The Awesome Oscillator
Bill Williams’s Awesome Oscillator is the same difference with 34 bars in place of 35 (TradingView’s built-in ta.sma(hl2, 5) - ta.sma(hl2, 34); pandas-ta’s ao with its default lengths 5 and 34):
mid := h.Add(l).Div(2)
ao := mid.Average(5).Sub(mid.Average(34)).SetN(33, 0)- Warm-up: +0 on bars 0 to 32, since
Averagepasses its input through before a full window; the first value is on bar 33. With 35 andSetN(34, 0)the same line isElliottWaveOscillatorbit for bit. - Missing data: a NaN high or low on bar p (p >= 33) makes it NaN on bars p to p+33.
- Anchor: a plain sum of each window gives the same values to rounding;
Average’s compensated sum fixes the last bits. - Pinned by
TestRecipeT4ElliottWave; runnable exampleExampleSeries_ElliottWaveOscillator_awesome.
(d) The age of the trend
StateAge counts the bars of each run of the trend. The warm-up’s +0 would be aged as a state, so it is masked to NaN first:
age := h.ElliottTrend(l, n, trig).SetN(n+33, math.NaN()).StateAge()- First valid index: n + 33; the age is NaN on bars 0 to n+32 and 1 on bar n + 33. The 0 between bar n + 33 and the first latch is the latch’s undetermined state and is aged as a run of its own.
- Missing data: the trend is not NaN (it holds its state through a NaN in the oscillator’s window), so the age keeps counting there.
- Pinned by
TestRecipeT4ElliottWave; runnable exampleExampleSeries_ElliottTrend_age.
Davey’s trigger reads an Awesome Oscillator of his own lengths with a close-location test. Values are NaN before their first valid index, and the flags read 0 there; they are decided at bar t’s close for a fill at the next bar’s open (flag.Shift(1)). The averages are simple, so no seed is chosen. Bill Williams’s standard Awesome Oscillator is recipe (c) of the Elliott Wave recipes above, whose warm-up reads +0 where this page masks with NaN. The Elder-ray under “MACD” reads the bar’s high and low against an average, and the body-over-range ratios under “QStick” are the close location’s relatives. Prices are positive and finite and lengths are in bars.
Awesome Oscillator reversal (Davey)
Kevin Davey (Entry and Exit Confessions of a Champion Trader, 2019, Entry #38, “Start With an Awesome Oscillator”) builds on Williams’s oscillator (“I do not use it quite the way he does”) with averages of 5 and 7 bars of the median price, and sells short the next bar “if condition1 and condition4”: “AO of aback bars is greater than AO of bback bars”, a high above the previous high, and “(close-low)/(high-low+.000001)<(1-fatr)”; the buy mirrors it with a lower low and a close above fatr of the range. t6AwesomeReversal returns his oscillator and the two flags, with his epsilon of 0.000001 added to the range.
mid := h.Add(l).Div(2)
ao := mid.Average(v1).Sub(mid.Average(v1+v3)).SetN(v1+v3-1, math.NaN())
cl := c.Sub(l).Div(h.Sub(l).Add(0.000001))
short := ao.Shift(aback).GT(ao.Shift(bback)).Mul(h.Delta(1).GT(0)).Mul(cl.LT(1-fatr)).SetN(v1+v3-1+max(aback, bback), 0)
long := ao.Shift(aback).LT(ao.Shift(bback)).Mul(l.Delta(1).LT(0)).Mul(cl.GT(fatr)).SetN(v1+v3-1+max(aback, bback), 0)- Inputs:
h,landc, the lengthsv1andv3, the lookbacksabackandbbackand the thresholdfatr(5, 2 and 0.5, with the lookbacks 1 and 2 as the example’s choice, since his printed 1 and 1 fire on no bar);aois in price units andlongandshortare 0 or 1. Arch-identical. - First valid index:
v1 + v3 - 1(6) foraoandv1 + v3 - 1 + max(aback, bback)(8 for 1 and 2) for the flags, which read +0 before it. - Anchors: with v3 = 29
aois Williams’s 5-bar less 34-bar oscillator of recipe (c) bit for bit (masked with NaN), and with v3 = 30 it is theElliottWaveOscillatorline from bar 34;c.Stochastic(h, l, 1)is 100 times the plain close locationc.Sub(l).Div(h.Sub(l))bit for bit. Equal lookbacks fire on no bar. - Traps: the printed defaults
aback(1)andbback(1)fire on no bar, and the listing’s comments (“Bullish divergence” on the short) are reversed against the use: the entry fades a rising oscillator on a bar that makes a new high and closes low. The epsilon is in price units: an exact midpoint close reads just below 0.5, so it fires a short that the plain ratio would not.h.GT(h.Shift(1))fires on bar 0, theShiftpad being +0, whereh.Delta(1).GT(0)does not. The finalSetN(..., 0)keeps theShiftpad’s +0 from meeting a valid oscillator when the lookbacks differ by more thanv1 + v3 - 1. A bar with h = l has c = l, soclreads 0, a close at the low: after a higher high such a flat bar can fireshort. Recipe (c)’s warm-up is +0 and this page’s NaN. - Variant: the unit-free close location of his plain-English text,
c.Sub(l).Div(h.Sub(l)), in place ofcl. - Evidence: Davey prints no results, chart or trade list for this entry, and gives it no exit. Evidence, not a reference.
- Pinned by
TestRecipeT6AwesomeReversal; runnable exampleExampleSeries_ElliottWaveOscillator_davey.