Elliott Wave

All functions · nseries package

ElliottTrend

func (s Series) ElliottTrend(l Series, n int, trigger float64) Series

ElliottTrend returns Ruggiero’s Elliott trend latch on the Elliott Wave oscillator: 0 until its first n-bar high (+1) or low (-1), then -1 when the oscillator falls below -trigger times a positive n-bar high and +1 when it rises above trigger times the magnitude of a negative n-bar low.

The receiver holds the highs and l the lows of the same bars, and osc is their Elliott Wave oscillator, the result of ElliottWaveOscillator: with h the receiver and mid = h.Add(l).Div(2), osc is mid.Average(5).Sub(mid.Average(35)) from bar 34 and +0 on bars 0 to 33. Lengths are checked before parameters: when len(l) differs from len(s), or the receiver is empty, the result is Series{}; otherwise, when n < 1, or trigger is negative or not finite, the result is all +0. There are no defaults and no upper bound on n or trigger; trigger -0 behaves as +0.

The latch is Table 13.2 of Murray Ruggiero’s Cybernetic Trading Strategies (1997). With x = osc[t] and the n-bar extremes hh = osc.Highest(n) and ll = osc.Lowest(n) over bars t-n+1 to t, NaN when the window holds a NaN, each bar from n + 33 on applies four rules in the order printed, each reading the state the rule before it left: if x == hh and the state is 0, it becomes +1; if x == ll and the state is 0, it becomes -1; if ll < 0, the state is -1 and x > float64(-trigger * ll), it becomes +1; if hh > 0, the state is +1 and x < float64(-trigger * hh), it becomes -1. The equality tests are exact equality, under which +0 equals -0, the other comparisons are strict, and every comparison with NaN is false.

Ruggiero’s prose says the trend stays up until the oscillator retraces the trigger percent of the n-bar high, and his narrative speaks of a retracement of 138 percent; the printed code is the definition. For trigger >= 0 no bar fires two rules, so the order matters only between the first two. A flat window, in which hh equals ll and x equals both, reads +1 in state 0 since the first rule comes first; with n = 1 every window is flat, so the latch turns +1 on bar 34 (or on the first later bar whose oscillator is not NaN) and does not flip again, and constant input reads +1 from bar n + 33. A flip up needs x > 0 and a flip down x < 0: the oscillator must cross zero and pass trigger times the opposite extreme, so trigger = 0 makes it a zero-cross latch gated by the sign of that extreme. After the first latch the state is +1 or -1 for good.

Warm-up: the result is +0 on bars 0 to n+32, and all +0 when n + 33 >= len(s); bar n + 33 is the first whose window of the oscillator starts at bar 34 or later. The start is fixed, not data-dependent, so the warm-up reads +0, the latch’s own state before its first bar, and not NaN; a caller who wants StateAge to count only evaluable bars masks it first: tr.SetN(n+33, math.NaN()).StateAge() with tr the result. The output is +1, -1 or +0 on every bar, and it is not -0.

A NaN high or low on a bar p at bar 34 or later makes the oscillator NaN on bars p to p+34 and the n-bar extremes NaN on bars p to p+n+33; every rule compares with hh or ll, so the latch holds its state on those bars and then resumes. Infinities are ordinary values: the extremes and thresholds can be infinite and compare as such, a product that overflows is an infinity, and with trigger = 0 an infinite extreme gives a NaN threshold, so that flip does not fire.

The latch is not up.Sub(dn).Hysteresis(0, 0), with up and dn the two flip conditions: that seeds from 0 on the first flip condition, where Ruggiero seeds only at an n-bar extreme, so the two can seed differently and stay apart. Nor is it HighestSince: the extremes are windowed, not taken since an event.

Ruggiero tested n from 20 to 180 and trigger from 0.5 to 1.0; he reports a run with n = 20 and trigger 0.66, and the inputs of his listing are 80 and 0.7.

The output on bar t uses bars 0 to t only, and nothing is redrawn. The latch is path-dependent: a run on the slice h[m:], l[m:] reads as the full run, bit for bit, from the first bar on which the slice’s trend flips between -1 and +1 at least one bar after its own bar n + 33, since both runs are then in the same state with the same inputs ahead; before it the runs can differ, and there is no fixed convergence bar. This needs the slice’s oscillator to equal the full run’s from that bar on, which can fail where a partial sum of Average overflows (see ElliottWaveOscillator).

The short side runs the same latch on negated, swapped inputs, -osc in place of osc: l.Mul(-1).ElliottTrend(h.Mul(-1), n, trigger).Mul(-1).Add(0) equals this result on every bar, except when the bar on which either run first latches has a flat window, which seeds +1 on both; the final Add(0) turns the -0 of the negated warm-up back into +0. See the manual’s “Elliott Wave” section for the recipes. The cost is O(len(s)) whatever n is, the n-bar extremes costing amortised O(1) a bar. The inputs are not modified, the result shares no storage with them, and concurrent calls are safe.

ElliottWaveCount

func (s Series) ElliottWaveCount(l Series, n int, trigger float64) Series

ElliottWaveCount returns Ruggiero’s mechanical 3-4-5 count of the up wave on the Elliott Wave oscillator: 3, 4 or 5 while ElliottTrend is up, -3 while it is down and 0 before the first down trend.

The receiver holds the highs and l the lows of the same bars, and the trend is ElliottTrend with the same n and trigger; h means the receiver. Lengths are checked before parameters: when len(l) differs from len(s), or the receiver is empty, the result is Series{}; otherwise, when n < 1, or trigger is negative or not finite, the result is all +0.

The count is Table 13.3 of Murray Ruggiero’s Cybernetic Trading Strategies (1997). With et the trend on bar t and et1 the trend on the bar before, x the oscillator, p the median price (h+l)/2 and top the greatest median price over bars t-4 to t, each bar from n + 33 on applies nine rules in the order printed, each reading the state the rule before it left. First, if et == 1, et1 == -1 and x > 0, it stores hiOsc = x and hiPrice = p and sets the count to 3. In wave 3 it raises hiOsc to x and hiPrice to p when they are exceeded, and if then x <= 0 and et == 1 it sets 4. In wave 4, if p == top and x >= 0, it sets 5 and stores hiOsc2 = x and hiPrice2 = p. In wave 5 it raises hiOsc2 and hiPrice2 when they are exceeded, and if then hiOsc2 > hiOsc, hiPrice2 > hiPrice and et == 1 it sets 3 with hiOsc = hiOsc2 and hiPrice = hiPrice2. Last, if et == -1, it sets -3. The comparisons are as printed: <= and >= in the fourth and fifth rules, exact equality in the fifth and strict elsewhere, and a comparison with NaN is false. Each stored peak is read only while the count is in the wave that set it, so the peaks’ starting values do not matter (the listing uses -999).

Two cascades on one bar are kept: from 3 to 4 to 5, when x is exactly 0 and the median price is at its 5-bar high, and from 4 to 5 to 3, on a wave-5 entry bar whose x and p already exceed both first peaks. A cascade from 3 to 4 to 5 to 3 on one bar cannot happen: the fifth rule would store a second oscillator peak hiOsc2 of 0 while the first peak, hiOsc, is still positive. The count is -3 on every bar on which the trend is -1, not only in a wave 5 as Ruggiero’s comment says, and 3, 4 or 5 on every bar on which it is +1 after the first bar with the trend at -1; a first latch of +1 leaves it at 0 until the trend has been -1. Since a flip up needs x > 0, every flip from -1 to +1 starts a wave 3, so the count reads 3 on every flip bar.

No source prints a down count. On negated, swapped inputs the same rules run on -osc and -mid: l.Mul(-1).ElliottWaveCount(h.Mul(-1), n, trigger) labels with 3, 4 and 5 the waves of a down move and marks an up trend with -3. The manual’s “Elliott Wave” section gives the recipe.

Warm-up: the result is +0 on bars 0 to n+32, and all +0 when n + 33 >= len(s), as for ElliottTrend. The output is one of -3, +0, 3, 4 and 5 on every bar; it is not -0 and not NaN.

NaN enters only through comparisons, which are false. While the window of the oscillator holds a NaN the trend holds its state, but the count does not simply hold: on a bar whose oscillator is NaN the rules that read x are false, yet a valid median price can still raise a stored price peak, and the eighth rule can then relabel a wave 5 as 3 on that bar. Where only the n-bar extremes are NaN, the count reads the oscillator as usual while the trend holds. Infinities are ordinary values, and a stored price peak of +Inf is not exceeded.

Ruggiero’s system, Table 13.5, buys when the count enters wave 3 or wave 5 and exits when the oscillator turns negative: the entries w.EQ(3).Mul(w.Shift(1).LTE(0)), w.EQ(5).Mul(w.Shift(1).EQ(4)) and w.EQ(3).Mul(w.Shift(1).EQ(5)) and the exit osc.LT(0), filled at the next bar’s open through Position, are recipes in the manual’s “Elliott Wave” section. With n = 20, trigger 0.66 and $50 deducted per trade for slippage and commissions, he reports for the D-Mark from February 1975 to March 1996 a net profit of $35,350 in 57 trades with 51% profitable, an average trade of $690.35 and a drawdown of -$10,237.50 for a profit factor of 2.10 (Table 13.6), and for the yen from August 1976 to March 1996 a net profit of $89,800 in 51 trades with 51% profitable, an average trade of $1,760.70 and a drawdown of -$5,975.00 for a profit factor of 4.16 (Table 13.7). Over 80 percent of the runs he tested with n from 20 to 180 and trigger from 0.5 to 1.0 were profitable. These are his results, on continuous futures contracts, and they are not reproduced here.

The output on bar t uses bars 0 to t only, and nothing is redrawn. The count is path-dependent: a run on the slice h[m:], l[m:] reads as the full run from the first bar with the trend at -1 at or after the bar from which the two trends agree, as stated for ElliottTrend and with its condition on the oscillator: there the last rule sets -3 in both runs, and no stored peak is read again before it is set anew; before that bar the runs can differ. The cost is O(len(s)) whatever n is. The inputs are not modified, the result shares no storage with them, and concurrent calls are safe.

ElliottWaveOscillator

func (s Series) ElliottWaveOscillator(l Series) Series

ElliottWaveOscillator returns Tom Joseph’s Elliott Wave oscillator, the 5-bar simple average of the median price (h+l)/2 minus its 35-bar simple average, with the receiver the highs and l the lows.

The receiver holds the highs and l the lows of the same bars, the layout of h.BasingPointLowValue(l, k), and the result is in the prices’ units. Both series must have the same length: when len(l) differs from len(s), or the receiver is empty, the result is Series{}. There are no parameters.

With h the receiver and mid = h.Add(l).Div(2), the result equals mid.Average(5).Sub(mid.Average(35)) bit for bit on bars 34 and later, bar 34 closing the first full 35-bar window. Warm-up: the result is +0 on bars 0 to 33, and all +0 for a series of 34 bars or fewer: Average passes its input through before a full window, so the plain difference would read a difference of prices there.

The oscillator is coded in Table 13.2 of Murray Ruggiero’s Cybernetic Trading Strategies (1997), whose chapter 13 attributes it to Tom Joseph and his Advanced GET software. The listing computes the oscillator only when the 35-bar average is not 0 and otherwise keeps the previous value; on positive prices the guard does not bind, so it is dropped and the difference is computed on every bar. The Awesome Oscillator of Bill Williams is the same difference with 34 bars in place of 35, mid.Average(5).Sub(mid.Average(34)) from bar 33; it is not a method but a recipe in the manual’s “Elliott Wave” section.

A NaN high or low on a bar p at bar 34 or later makes the oscillator NaN on bars p to p+34 (one on an earlier bar, on bars 34 to p+34). Infinities are ordinary values: the oscillator is NaN where both averages are infinities of the same sign, or where a window holds infinities of both signs, and an infinity where only the 35-bar one is, so a lone +Inf high on a bar p at bar 34 or later gives NaN on bars p to p+4 and -Inf on bars p+5 to p+34. The sum is rounded before it is halved, so a bar with both prices at math.MaxFloat64 has an infinite median price and acts as an infinite high.

The output on bar t uses bars 0 to t only, and the oscillator is not path-dependent: from bar 34 each value reads the last 35 bars. (The compensated sum of Average groups its partial sums by the window’s position since the start, so where a partial sum overflows, with prices near math.MaxFloat64, a run on a slice can read another value, -Inf where the full run reads NaN for example; elsewhere runs on slices gave the same bits on every input tested.) The cost is O(len(s)). The inputs are not modified and the result shares no storage with them, so concurrent calls on shared inputs are safe.

Compositions

Elliott Wave recipes

The recipes below read the three methods on the highs h and the lows l of the same bars, with n and trig the latch’s bar count and trigger. The methods come from Murray Ruggiero’s Cybernetic Trading Strategies (1997), chapter 13: the oscillator and the latch from Table 13.2 and the count from Table 13.3. Every recipe is pinned by TestRecipeT4ElliottWave, which checks each line of the Go blocks below against the test’s own copies and each statement against direct scans.

(a) Ruggiero’s system (Table 13.5)

Ruggiero’s trading system buys at the open when the count enters wave 3 from -3 (the listing tests a previous count of 0 or less), enters wave 5 from wave 4, or is relabelled from 5 to 3, and leaves the trade at the open when the oscillator is negative. His orders are placed on a bar’s close for the next bar’s open, so each flag is shifted by one bar before Position reads it, with the opens o as the receiver (Position reads only their length; with o as the receiver, EntryValue and the other trade views are anchored on the fill):

osc := h.ElliottWaveOscillator(l)
w := h.ElliottWaveCount(l, n, trig)
fromLow := w.EQ(3).Mul(w.Shift(1).LTE(0))
fromFour := w.EQ(5).Mul(w.Shift(1).EQ(4))
fromFive := w.EQ(3).Mul(w.Shift(1).EQ(5))
entry := fromLow.Add(fromFour).Add(fromFive)
exit := osc.LT(0)
pos := o.Position(entry.Shift(1), exit.Shift(1))
  • Entries: each flag is 0 or 1 and no two fire on one bar, so entry is 0 or 1. The count goes from 0 only to 0 or -3, so fromLow fires exactly on the bars on which the trend flips from -1 to +1: every flip up starts a wave 3. A bar that runs through a cascade, from 3 to 4 to 5 or from 4 to 5 to 3, reads 5 after 3 or 3 after 4, so none of the three entries fires on it, as in the listing, which also reads only the previous bar’s count. A relabel from 5 to 3 can come on a bar whose oscillator is NaN; exit is 0 there.
  • Fills: Shift(1) pads bar 0 with +0, which does not fire. Position ignores an entry while long (no pyramiding) and does not test the exit on the bar it enters.
  • First valid index: the count is +0 on bars 0 to n+32 and cannot read 3 on bar n + 33, so the first entry comes on bar n + 34 at the earliest, and the first trade opens on the bar after it.
  • Parameters: the listing’s inputs are Len(80), Trig(.7); the run Ruggiero reports uses n = 20 and trigger 0.66, and he tested n from 20 to 180 and trigger from 0.5 to 1.0, over 80 percent of those runs being profitable.
  • Results (not reproduced): with $50 deducted per trade for slippage and commissions, he reports for the D-Mark from February 1975 to March 1996 a net profit of $35,350 in 57 trades, 51% profitable, an average trade of $690.35, a drawdown of -$10,237.50 and a profit factor of 2.10 (Table 13.6), and for the yen from August 1976 to March 1996 $89,800 in 51 trades, 51% profitable, $1,760.70, -$5,975.00 and 4.16 (Table 13.7). They come from Genesis continuous futures contracts run in TradeStation and are not reproduced here.
  • Pinned by TestRecipeT4ElliottWave; runnable example ExampleSeries_ElliottWaveCount_system.

(b) The down side

No source prints a down count: Ruggiero codes only the up sequence and adds “We could also trade the short side” (p. 193). Negation is exact (Mul(-1) and the package’s sums round symmetrically), so on the negated lows as the receiver and the negated highs as l the latch and the count run on the negated oscillator and median price, every comparison reversed when written on the original values:

dt := l.Mul(-1).ElliottTrend(h.Mul(-1), n, trig).Mul(-1).Add(0)
dw := l.Mul(-1).ElliottWaveCount(h.Mul(-1), n, trig)
  • The trend: dt equals h.ElliottTrend(l, n, trig) on every bar, except when the bar on which either run first latches has a flat window (the oscillator’s n-bar high equals its n-bar low): the first rule seeds +1 on both, so the mirror reads -1 where the original reads +1, and the two can stay apart. Flat windows occur at any n: a plateau of n + 34 bars or more at an integer median price gives an oscillator of exactly 0 over a whole window, and on constant input dt reads -1 from bar n + 33 while the trend reads +1. Add(0) turns the -0 that Mul(-1) makes of the warm-up’s +0 back into +0.
  • The count: dw labels with 3, 4 and 5 the waves of a down move: it is -3 on every bar on which dt is +1, and 3, 4 or 5 on every bar on which dt is -1 once dt has been +1; it is +0 before. The short side of (a) takes the three entries of dw and the exit osc.GT(0), which is the mirrored oscillator’s LT(0).
  • First valid index: n + 33, as for the methods.
  • Pinned by TestRecipeT4ElliottWave; runnable example ExampleSeries_ElliottWaveCount_down.

(c) The Awesome Oscillator

Bill Williams’s Awesome Oscillator is the same difference with 34 bars in place of 35 (TradingView’s built-in ta.sma(hl2, 5) - ta.sma(hl2, 34); pandas-ta’s ao with its default lengths 5 and 34):

mid := h.Add(l).Div(2)
ao := mid.Average(5).Sub(mid.Average(34)).SetN(33, 0)
  • Warm-up: +0 on bars 0 to 32, since Average passes its input through before a full window; the first value is on bar 33. With 35 and SetN(34, 0) the same line is ElliottWaveOscillator bit for bit.
  • Missing data: a NaN high or low on bar p (p >= 33) makes it NaN on bars p to p+33.
  • Anchor: a plain sum of each window gives the same values to rounding; Average’s compensated sum fixes the last bits.
  • Pinned by TestRecipeT4ElliottWave; runnable example ExampleSeries_ElliottWaveOscillator_awesome.

(d) The age of the trend

StateAge counts the bars of each run of the trend. The warm-up’s +0 would be aged as a state, so it is masked to NaN first:

age := h.ElliottTrend(l, n, trig).SetN(n+33, math.NaN()).StateAge()
  • First valid index: n + 33; the age is NaN on bars 0 to n+32 and 1 on bar n + 33. The 0 between bar n + 33 and the first latch is the latch’s undetermined state and is aged as a run of its own.
  • Missing data: the trend is not NaN (it holds its state through a NaN in the oscillator’s window), so the age keeps counting there.
  • Pinned by TestRecipeT4ElliottWave; runnable example ExampleSeries_ElliottTrend_age.

Davey’s trigger reads an Awesome Oscillator of his own lengths with a close-location test. Values are NaN before their first valid index, and the flags read 0 there; they are decided at bar t’s close for a fill at the next bar’s open (flag.Shift(1)). The averages are simple, so no seed is chosen. Bill Williams’s standard Awesome Oscillator is recipe (c) of the Elliott Wave recipes above, whose warm-up reads +0 where this page masks with NaN. The Elder-ray under “MACD” reads the bar’s high and low against an average, and the body-over-range ratios under “QStick” are the close location’s relatives. Prices are positive and finite and lengths are in bars.

Awesome Oscillator reversal (Davey)

Kevin Davey (Entry and Exit Confessions of a Champion Trader, 2019, Entry #38, “Start With an Awesome Oscillator”) builds on Williams’s oscillator (“I do not use it quite the way he does”) with averages of 5 and 7 bars of the median price, and sells short the next bar “if condition1 and condition4”: “AO of aback bars is greater than AO of bback bars”, a high above the previous high, and “(close-low)/(high-low+.000001)<(1-fatr)”; the buy mirrors it with a lower low and a close above fatr of the range. t6AwesomeReversal returns his oscillator and the two flags, with his epsilon of 0.000001 added to the range.

mid := h.Add(l).Div(2)
ao := mid.Average(v1).Sub(mid.Average(v1+v3)).SetN(v1+v3-1, math.NaN())
cl := c.Sub(l).Div(h.Sub(l).Add(0.000001))
short := ao.Shift(aback).GT(ao.Shift(bback)).Mul(h.Delta(1).GT(0)).Mul(cl.LT(1-fatr)).SetN(v1+v3-1+max(aback, bback), 0)
long := ao.Shift(aback).LT(ao.Shift(bback)).Mul(l.Delta(1).LT(0)).Mul(cl.GT(fatr)).SetN(v1+v3-1+max(aback, bback), 0)
  • Inputs: h, l and c, the lengths v1 and v3, the lookbacks aback and bback and the threshold fatr (5, 2 and 0.5, with the lookbacks 1 and 2 as the example’s choice, since his printed 1 and 1 fire on no bar); ao is in price units and long and short are 0 or 1. Arch-identical.
  • First valid index: v1 + v3 - 1 (6) for ao and v1 + v3 - 1 + max(aback, bback) (8 for 1 and 2) for the flags, which read +0 before it.
  • Anchors: with v3 = 29 ao is Williams’s 5-bar less 34-bar oscillator of recipe (c) bit for bit (masked with NaN), and with v3 = 30 it is the ElliottWaveOscillator line from bar 34; c.Stochastic(h, l, 1) is 100 times the plain close location c.Sub(l).Div(h.Sub(l)) bit for bit. Equal lookbacks fire on no bar.
  • Traps: the printed defaults aback(1) and bback(1) fire on no bar, and the listing’s comments (“Bullish divergence” on the short) are reversed against the use: the entry fades a rising oscillator on a bar that makes a new high and closes low. The epsilon is in price units: an exact midpoint close reads just below 0.5, so it fires a short that the plain ratio would not. h.GT(h.Shift(1)) fires on bar 0, the Shift pad being +0, where h.Delta(1).GT(0) does not. The final SetN(..., 0) keeps the Shift pad’s +0 from meeting a valid oscillator when the lookbacks differ by more than v1 + v3 - 1. A bar with h = l has c = l, so cl reads 0, a close at the low: after a higher high such a flat bar can fire short. Recipe (c)’s warm-up is +0 and this page’s NaN.
  • Variant: the unit-free close location of his plain-English text, c.Sub(l).Div(h.Sub(l)), in place of cl.
  • Evidence: Davey prints no results, chart or trade list for this entry, and gives it no exit. Evidence, not a reference.
  • Pinned by TestRecipeT6AwesomeReversal; runnable example ExampleSeries_ElliottWaveOscillator_davey.