Chart Patterns

All functions · nseries package

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 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, SwingHighEvent and 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>Setup reports 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>Neckline holds the level a close must break while the pattern is under test, and NaN on every other bar.
  • <Pattern>Stop holds the level whose break voids the pattern while it is under test, and NaN on every other bar.
  • <Pattern>Target holds 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 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.

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. The triple tops and bottoms (parked on demand, GLE-520; the names TripleTop and TripleBottom are reserved), 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))

Head and shoulders

Six views of one machine, three for each orientation; a head-and-shoulders pattern is under test while it is armed:

ViewValue on each bar
HeadShouldersTop1 on the bar whose close first breaks below an armed top’s neckline, +0 otherwise
HeadShouldersTopNecklinethe neckline N(t) while a top is armed, NaN otherwise
HeadShouldersTopTargetN(t) - H, the neckline less the head height, while a top is armed, NaN otherwise
HeadShouldersBottom1 on the bar whose close first breaks above an armed bottom’s neckline, +0 otherwise
HeadShouldersBottomNecklinethe neckline N(t) while a bottom is armed, NaN otherwise
HeadShouldersBottomTargetN(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. 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. BullFlag is 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 recipe c.GT(neck.Shift(1)) from bar 1 on (Shift pads bar 0 with 0, which the recipes below mask with setup.Shift(1)).
  • A stop and two pole views instead of a target. BullFlagStop is 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, so BullFlagPoleTop and BullFlagPoleBottom take 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.

ViewValue on each bar
Cup1 on the first bar of each run of cup bars and on each bar that moves the rim bar, +0 otherwise
CupLengththe length B5 - B0 + 1, from the cup’s first bar B0 to its rim bar B5, else +0
CupRimthe high at the rim bar, the level a breakout must clear, else NaN
CupLowthe 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.

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 breakNeckline equals neckline and breakTarget equals target bit for bit; on every other bar they repeat their previous values. For n >= 1 the event equals c.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 example ExampleSeries_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: height is 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; closeTarget is NaN before the first event bar.
  • Anchors: height is 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. closeTarget is 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 example ExampleSeries_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: held opens on each event bar while flat and closes hold bars later; reached also closes on the first later bar whose close is strictly below closeTarget. A break while a trade is open opens nothing, but it moves closeTarget to 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 example ExampleSeries_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) and c.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 examples ExampleSeries_HeadShouldersTop and ExampleSeries_HeadShouldersBottom.