Relative: Divergence

All functions · nseries package

BollingerDivergence

func (s Series) BollingerDivergence(s2 Series, n1, n2, smooth int) Series

BollingerDivergence returns Katsanos’s Bollinger band divergence, the percentage difference between the positions of s2 and s within their own two-standard-deviation bands, smoothed over smooth bars.

It is the Bollinger band divergence of Markos Katsanos, Intermarket Trading Strategies (Wiley, 2008), section 9.2, pages 123-124, with the MetaStock listing in Appendix A.1, pages 297-298. The band position of a series v over n bars, with mu = v.Average(n) and sd = v.StdDev(n), is 1 + (((v[j] - mu[j]) + 2sd[j]) / (4sd[j])), in exactly that order, which is the order of the listing’s 1+((C- Mov(C,D1,S)+2Stdev(C,D1))/(4Stdev(C,D1))): 1 on the lower band, 1.5 on the average and 2 on the upper band, and 1.5 on a flat leg, where sd is 0. With bs the position of s over n1 bars and bs2 that of s2 over n2 bars, the raw value at overlap bar j is the signed percentage change signedPctChange(bs2[j], bs[j]), which is (bs2/bs - 1)*100 when bs > 0 and (bs2/(-bs) + 1)*100 when bs < 0. For smooth > 1 the output is the exponential moving average XAverage(smooth) of the raw values from the first raw bar on, bit for bit, and zero while it warms up.

The sign is intermarket minus base: a positive value means that s2 sits higher in its band than s does in its own, so s has lagged s2, which is bullish for s if the gap closes by mean reversion.

s is the base and s2 the comparison; the inputs are right-aligned at their most recent bar, only the trailing min(len(s), len(s2)) bars are paired, the result has len(s), and bars before the overlap are zero.

The first valid index, counted from the start of the overlap, is w = max(n1, n2) - 1 for smooth <= 1 and w + smooth - 1, that is max(n1, n2) + smooth - 2, for smooth > 1, and the minimum overlap is one bar more: max(n1, n2) bars unsmoothed and max(n1, n2) + smooth - 1 bars smoothed, so Katsanos’s 40/40/3 needs 42 bars. Every output before the first valid index is zero. Any smooth <= 1 (zero, negative and math.MinInt included) is valid and gives the raw divergence. Invalid parameters give aligned zeros, a fresh all-zero series of len(s): an empty overlap, n1 < 2 or n2 < 2, n1 or n2 greater than the overlap, or a smooth > 1 that leaves fewer than smooth raw values. The checks never add parameters together, so every int from math.MinInt to math.MaxInt is safe, and an invalid one returns at once without allocating anything beyond the zero result.

Pairing class: per leg, each band position seeing only its own series and window, with the legs combined bar by bar and a recursive tail when smoothed. In exact arithmetic an unsmoothed value depends only on the two trailing windows, but the rolling variance behind StdDev depends on earlier history, so it is not start-invariant bit for bit, and the smoothed values depend on where the history starts. Valid data: any finite data whose deviations from their window mean lie between about 1e-154 and 1e154/sqrt(n), the range of the rolling variance behind StdDev (outside it the band position is NaN or a silent 1.5), since a band position is unchanged by adding a constant to its series or multiplying it by a positive one; within that range the hazard below lies in the window length, not in the data.

Non-finite values: unsmoothed, a NaN or an infinity at overlap index p of s gives NaN on [p, p+n1-1] and one in s2 gives NaN on [p, p+n2-1], each clipped to [w, m) with m the overlap length, and later bars recover once the value has left the window; smoothed, with p the index of the first NaN raw value, every output from max(p, w + smooth - 1) on is NaN for ever, because the recursion never recovers. A flat leg sits at mid-band, 1.5, so a flat s gives (bs2/1.5 - 1)*100 and two flat legs give exactly 0. A base position of exactly 0 (the zero base) gives NaN, as does a quotient that overflows.

In exact arithmetic B = 1.5 + Z/4, with Z the population z-score of the bar in its window, so the raw value is 100*(Zs2 - Zs)/|6 + Zs|. By Samuelson’s inequality |Z| <= sqrt(n-1), so for n1 <= 36 the base position is at least 1.5 - sqrt(n1-1)/4 > 0 and |raw| is at most 100*(sqrt(n2-1) + sqrt(n1-1))/(6 - sqrt(n1-1)), which is 531.2 at n1 = n2 = 20 and 14,099 at n1 = n2 = 36. From n1 = 37, which includes the default of 40 in Katsanos’s Appendix A.1, the base position can reach or cross 0: the published quantity then spikes as the base approaches 0 (the signed leg keeps its sign across the crossing, where Katsanos’s formula flips it), and a base of exactly 0 gives NaN. The method keeps the published quantity; for long windows use ZScoreDivergence, which has no such hazard. Katsanos’s signal levels lie between 10 and 30 either side of 0, and for closely related markets most readings lie within about 30 either side of 0; less related pairs exceed it more often.

The value of this composite lies in its published, verifiable code, not in a demonstrated edge: Katsanos’s Chapter 11 tests have 9 to 38 trades over 1995 to 2007, with the levels optimised per system.

Katsanos’s defaults are 40/40/3 (Appendix A.1), 20 bars in his Figure 9.2 (GLD against SLV) and 100 bars in the Appendix A.2 systems; the systems of his articles in Stocks & Commodities use smooth = 1 with 20 bars (“Trading The Loonie”, 33:13, December 2015) and 30 bars (“Trading The Aussie”, 27:2, 2009), below the hazard. Trading use: buy when the divergence peaks above +10 to +30 and declines, and sell when it troughs below -10 to -30 and rises (15 either side of 0 in Figure 9.2); the Loonie article’s signal code is HHV(DIV1,3) > 20 AND DIV1 < REF(DIV1,-1) for longs and its mirror for shorts, and the Aussie article uses 10 and -10. Katsanos writes that the formula “doesn’t work so well” for negatively correlated markets; use ZScoreDivergence for those.

Against several intermarkets, as in “Trading The Aussie”, take the maximum, minimum or sum of the divergences against each of them, composed over a three-way overlap:

m := min(len(aud), len(xau), len(crb))
a, x, c := aud.Right(m), xau.Right(m), crb.Right(m)
d2 := a.BollingerDivergence(x, 30, 30, 1)
d3 := a.BollingerDivergence(c, 30, 30, 1)
maxDiv := d2.GT(d3).If(func(v float64) bool { return v != 0 }, d2, d3)
minDiv := d2.LT(d3).If(func(v float64) bool { return v != 0 }, d2, d3)
sumDiv := d2.Add(d3) // Katsanos's DIV2 + DIV3 > 80

GT and LT are false when either value is NaN, so the maximum and the minimum then take d3, which is NaN where d3 is.

Deviations from Katsanos’s MetaStock code: the smoothing average is seeded with the simple average of its first smooth values, where MetaStock’s Mov(…, E) is understood to seed with the first value (a MetaStock forum formula; Katsanos does not state it), so the two converge after a few times smooth bars; the raw value is the signed percentage change of the positions (signedPctChange) in place of the listing’s DIVERG1:=(sec2BOL-sec1BOL)/SEC1BOL100, which it matches up to rounding while the base position is positive, and a zero base gives NaN; a flat leg sits at 1.5 by an exact zero-divisor rule, with no +.0001 guard, where the article listings add .0001 to the denominator 4Stdev, a constant in price units that changes the position’s distance from 1, B - 1, by a relative amount of about 0.0001/(4*sd); the standard deviations are population ones, MetaStock’s Stdev being taken to be population; and outputs before the first valid index are zero rather than undefined.

Verification: two independent references, a Python reference in exact rational arithmetic (with 140-digit decimals for square roots and the smoothing) and a Julia reference in BigFloat that also translates Katsanos’s MetaStock listings, agree with it; and there are exact identities with existing methods: on non-flat windows each leg equals v.Sub(v.Average(n)).Add(v.StdDev(n).Mul(2)).Div(v.StdDev(n).Mul(4)).Add(1) bit for bit, and s2 == s and s2 = s.Mul(2), each with n1 == n2, give exactly 0 wherever the base position is not 0.

IntermarketDisparity

func (s Series) IntermarketDisparity(s2 Series, n1, n2, smooth int, c float64) Series

IntermarketDisparity returns Katsanos’s intermarket disparity, c times the percentage distance of s2 from its n2-bar average minus that of s from its n1-bar average, smoothed over smooth bars.

It is the intermarket disparity of Markos Katsanos, Intermarket Trading Strategies (Wiley, 2008), section 9.3, pages 124-127, with the MetaStock listing in Appendix A.1, page 298. The disparity of a series v is its signed percentage distance from its n-bar simple moving average M = v.Average(n), the listing’s Mov(C,D1,S): signedPctChange(v[j], M[j]), which is (v/M - 1)*100 when M > 0 and (v/(-M) + 1)*100 when M < 0. With ds1 the disparity of s over n1 bars and ds2 that of s2 over n2 bars, the raw value at overlap bar j is float64(c*ds2[j]) - ds1[j], in exactly that form: the conversion rounds the product before the subtraction, so that the compiler cannot fuse the two into one multiply-add on arm64. For smooth > 1 the output is the exponential moving average XAverage(smooth) of the raw values from the first raw bar on, bit for bit, and zero while it warms up; Katsanos smooths with MetaStock’s Mov(…, E).

The sign is intermarket minus base: a positive value means that s stands lower against its average than s2, scaled by c, stands against its own, so s has lagged s2, which is bullish for s if the gap closes by mean reversion. The multiplier c orients the intermarket leg, +1 for a market that moves with s and -1 for one that moves against it, and any other finite non-zero value also scales it.

s is the base and s2 the comparison; the inputs are right-aligned at their most recent bar, only the trailing min(len(s), len(s2)) bars are paired, the result has len(s), and bars before the overlap are zero.

The first valid index, counted from the start of the overlap, is w = max(n1, n2) - 1 for smooth <= 1 and w + smooth - 1, that is max(n1, n2) + smooth - 2, for smooth > 1, and the minimum overlap is one bar more: max(n1, n2) bars unsmoothed and max(n1, n2) + smooth - 1 bars smoothed, so Katsanos’s 30/30/3 needs 32 bars. Every output before the first valid index is zero. Any smooth <= 1 (zero, negative and math.MinInt included) is valid and gives the raw disparity. Invalid parameters give aligned zeros, a fresh all-zero series of len(s): an empty overlap, n1 < 1 or n2 < 1, a c that is zero, NaN or infinite, n1 or n2 greater than the overlap, or a smooth > 1 that leaves fewer than smooth raw values. The checks never add parameters together, so every int from math.MinInt to math.MaxInt is safe, and an invalid one returns at once without allocating anything beyond the zero result.

Pairing class: per leg, each disparity seeing only its own series and window, with the legs combined bar by bar and a recursive tail when smoothed. In exact arithmetic an unsmoothed value depends only on the two trailing windows and so is start-invariant, though the rolling sums behind Average are not promised to be start-invariant bit for bit; the smoothed values depend on where the history starts and are not start-invariant. Valid data: levels whose n-bar average is not zero, such as prices and index values. Scaling either input by a positive factor leaves the value unchanged, shifting it does not, and a negative level takes the signed branch, which keeps the sign of the distance. A zero average gives NaN and an average near zero makes the percentage ill-conditioned, so for spreads, rates and back-adjusted futures, which can cross zero, prefer ZScoreDivergence.

Non-finite values: unsmoothed, a NaN or an infinity at overlap index p of s gives NaN on [p, p+n1-1] and one in s2 gives NaN on [p, p+n2-1], each clipped to [w, m) with m the overlap length, and later bars recover once the value has left the window; smoothed, with p the index of the first NaN raw value, every output from max(p, w + smooth - 1) on is NaN for ever, because the recursion never recovers. A zero average (the zero base), a non-finite value or a non-finite quotient gives its leg NaN; a product c*ds2 that overflows stands as an infinity, and the smoothed values from it on stay infinite or NaN. A flat window gives its leg 0 up to the rounding of the simple average (Average of equal values need not return the value exactly), a flat window of zeros gives NaN, and levels above about math.MaxFloat64/n overflow the rolling sum and give NaN.

The value of this composite lies in its published, verifiable code, not in a demonstrated edge: Katsanos’s Chapter 11 tests have 9 to 38 trades over 1995 to 2007, with the levels optimised per system.

Katsanos’s defaults are 30/30/3 with c = -1, for gold against the dollar index (Appendix A.1); his Chapter 11 optima are 15/15 against the Philadelphia Gold and Silver Mining Index (XAU), and n1 = 20 for gold with n2 = 15 for the dollar index. His yen system (Appendix A.8) takes exponential averages, which IntermarketDisparityX provides. Trading use, as in his Figure 9.3: buy when the value tops above about +3% and turns down, and sell when it bottoms below about -3% and turns up. Unlike Ruggiero’s sign rule, it can signal when both markets are on the same side of their averages.

Deviations from Katsanos’s MetaStock code: the smoothing average is seeded with the simple average of its first smooth values, where MetaStock’s Mov(…, E) is understood to seed with the first value (a MetaStock forum formula; Katsanos does not state it), so the two converge after a few times smooth bars; each leg is the signed percentage change of v over M (signedPctChange) in place of the subtract-first ((C - Mov)/Mov)*100, which it matches up to rounding on a positive average and which reverses the direction of the distance on a negative one, and a zero average gives NaN; c is a multiplier on the intermarket leg in place of the listing’s correlation-sign input; and outputs before the first valid index are zero rather than undefined.

Verification: two independent references, a Python reference in exact rational arithmetic (with 140-digit decimals for square roots and the smoothing) and a Julia reference in BigFloat that also translates Katsanos’s MetaStock listings, agree with it; and there are exact identities with existing methods: where the average is positive, each leg equals v.Div(v.Average(n)).Sub(1).Mul(100) bit for bit from index n-1 on, an all-ones s2 gives, unsmoothed, minus that leg of s, and s2 == s with n1 == n2 and c = 1 gives exactly 0.

IntermarketDisparityX

func (s Series) IntermarketDisparityX(s2 Series, n1, n2, smooth int, c float64) Series

IntermarketDisparityX returns IntermarketDisparity computed against exponential rather than simple moving averages, as in Katsanos’s yen system.

It follows the yen system of Markos Katsanos, Intermarket Trading Strategies (Wiley, 2008), Appendix A.8, whose listing measures each market’s disparity from its exponential moving average, Mov(C,10,E), in place of the simple average of the intermarket disparity of section 9.3, pages 124-127, and Appendix A.1, page 298. Each leg is the signed percentage distance signedPctChange(v[j], M[j]) with M = v.XAverage(n), which is (v/M - 1)*100 when M > 0 and (v/(-M) + 1)*100 when M < 0. With ds1 the leg of s over n1 bars and ds2 that of s2 over n2 bars, the raw value at overlap bar j is float64(c*ds2[j]) - ds1[j], in exactly that form: the conversion rounds the product before the subtraction, so that the compiler cannot fuse the two into one multiply-add on arm64. For smooth > 1 the output is the exponential moving average XAverage(smooth) of the raw values from the first raw bar on, bit for bit, and zero while it warms up.

The sign is intermarket minus base: a positive value means that s stands lower against its average than s2, scaled by c, stands against its own, so s has lagged s2, which is bullish for s if the gap closes by mean reversion. The multiplier c orients the intermarket leg, +1 for a market that moves with s and -1 for one that moves against it, and any other finite non-zero value also scales it.

s is the base and s2 the comparison; the inputs are right-aligned at their most recent bar, only the trailing min(len(s), len(s2)) bars are paired, the result has len(s), and bars before the overlap are zero.

The first valid index, counted from the start of the overlap, is w = max(n1, n2) - 1 for smooth <= 1 and w + smooth - 1, that is max(n1, n2) + smooth - 2, for smooth > 1, and the minimum overlap is one bar more: max(n1, n2) bars unsmoothed and max(n1, n2) + smooth - 1 bars smoothed, so the yen system’s 10/10/3 needs 12 bars; as the exponential averages remember their seeds, allow a few times max(n1, n2) bars more history before relying on the values. Every output before the first valid index is zero. Any smooth <= 1 (zero, negative and math.MinInt included) is valid and gives the raw disparity. Invalid parameters give aligned zeros, a fresh all-zero series of len(s): an empty overlap, n1 < 1 or n2 < 1, a c that is zero, NaN or infinite, n1 or n2 greater than the overlap, or a smooth > 1 that leaves fewer than smooth raw values. The checks never add parameters together, so every int from math.MinInt to math.MaxInt is safe, and an invalid one returns at once without allocating anything beyond the zero result.

Pairing class: per leg, each disparity seeing only its own series, with the legs combined bar by bar and a recursive tail when smoothed. The legs are recursive too, each exponential average running from the start of the overlap, so even the unsmoothed values depend on where the history starts and are not start-invariant. Valid data: levels whose exponential average is not zero, such as prices and index values. Scaling either input by a positive factor leaves the value unchanged, shifting it does not, and a negative level takes the signed branch, which keeps the sign of the distance. A zero average gives NaN and an average near zero makes the percentage ill-conditioned, so for spreads, rates and back-adjusted futures, which can cross zero, prefer ZScoreDivergence.

Non-finite values: a NaN or an infinity at overlap index p of either input gives NaN from max(p, w) on unsmoothed, and from max(p, w + smooth - 1) on smoothed, for ever, because an exponential average never recovers. A zero average (the zero base), a non-finite value or a non-finite quotient gives its leg NaN; a product c*ds2 that overflows stands as an infinity, and the smoothed values from it on stay infinite or NaN. A flat stretch draws its leg towards 0 as the average converges on the flat level.

The value of this composite lies in its published, verifiable code, not in a demonstrated edge: Katsanos’s Chapter 11 tests have 9 to 38 trades over 1995 to 2007, with the levels optimised per system.

Katsanos’s USD/JPY yen system (Appendix A.8) uses 10/10/3 with c = +1: its listing forms the difference DIS3 - DISY, the ten-year Treasury yield’s leg minus the yen’s, as here, and it buys when that divergence has risen above 4 within four bars and turns down, and sells when it has fallen below -4 and turns up. His yen futures listing negates the intermarket leg (-DIS3 - DISY, that is c = -1) and uses 2 and -2, and his regression system takes a length it does not print in place of 10. Unlike Ruggiero’s sign rule, the divergence can signal when both markets are on the same side of their averages.

Deviations from Katsanos’s MetaStock code: the leg averages are seeded with the simple average of their first n1 or n2 values and the smoothing average with that of its first smooth values, where MetaStock’s Mov(…, E) is understood to seed with the first value (a MetaStock forum formula; Katsanos does not state it), so each pair converges after a few times its length; each leg is the signed percentage change of v over M (signedPctChange) in place of the subtract-first ((C - Mov)/Mov)*100, which it matches up to rounding on a positive average and which reverses the direction of the distance on a negative one, and a zero average gives NaN; c is a multiplier on the intermarket leg in place of the listing’s plain difference, which is c = 1; and outputs before the first valid index are zero rather than undefined.

Verification: two independent references, a Python reference in exact rational arithmetic (with 140-digit decimals for square roots and the smoothing) and a Julia reference in BigFloat that also translates Katsanos’s MetaStock listings, agree with it; and there are exact identities with existing methods: where the average is positive, each leg equals v.Div(v.XAverage(n)).Sub(1).Mul(100) bit for bit from index n-1 on, an all-ones s2 gives, unsmoothed, minus that leg of s, and s2 == s with n1 == n2 and c = 1 gives exactly 0.

IntermarketLRSDivergence

func (s Series) IntermarketLRSDivergence(s2 Series, n1, n2, volLen, smooth int, c float64) Series

IntermarketLRSDivergence returns Katsanos’s linear-regression-slope divergence, the volatility-scaled percentage slope of s2 minus that of s, smoothed over smooth bars.

It is the linear-regression-slope divergence of Markos Katsanos, Intermarket Trading Strategies (Wiley, 2008), section 9.4, pages 127-128, in the form of his published code (Appendix A.1, page 298: “D3:=D2Stdev(C,200)/Stdev(P,200); LRSI:=LinRegSlope(P,D1)/Abs(Ref(P,-D1+1))100; LRS:=LinRegSlope(C,D1)/Abs(Ref(C,-D1+1))100; DIVERG:=(D3LRSI-LRS)100; Mov(DIVERG,3,E)”), with C the base s, P the comparison s2, D2 the coefficient c and D1 the slope length, here n1 for s and n2 for s2. On the overlap, psA and psB are the percentage slopes of s over n1 bars and of s2 over n2 bars (SlopePercent), and sdA and sdB are the population standard deviations of s and s2 over volLen bars (StdDev). The scale is Katsanos’s default, the plain ratio of standard deviations, q = (csdA)/sdB, which is the scale of every published result of the method, and the unsmoothed divergence is (float64(qpsB) - psA) 100, his (D3*LRSI-LRS)*100, in that Go operation order: the conversion rounds the product before the subtraction, so the two are never fused into one multiply-add. The result is that divergence smoothed by an exponential moving average of length smooth. His price-level-adjusted scale, which he recommends for"a high and a low priced security" but published no test of, is IntermarketLRSDivergenceCV.

s is the base and s2 the comparison; the inputs are right-aligned at their most recent bar, only the trailing min(len(s), len(s2)) bars are paired, the result has len(s), and bars before the overlap are zero.

The first valid index, counted from the start of the overlap, is w = max(n1, n2, volLen) - 1 for smooth <= 1 and w + smooth - 1 for smooth > 1, and the minimum overlap is one bar more; every earlier output is 0. With m the overlap length, the result is all zeros for an empty overlap, for n1, n2 or volLen below 2, for a c that is zero or not finite, for n1, n2 or volLen greater than m and, when smooth > 1, for smooth > m - w (tested by subtraction); any smooth <= 1 means unsmoothed, and every int parameter may be math.MaxInt or math.MinInt. For smooth > 1 the outputs from w + smooth - 1 on are exactly d[w:].XAverage(smooth)[smooth-1:], with d the unsmoothed result on the overlap.

Pairing is per leg: each percentage slope and each standard deviation is computed on its own series over its own window, and the legs are combined bar by bar. Unsmoothed outputs are start-invariant up to rounding: the slopes are window-local, but the standard deviations come from a rolling aggregate whose last bits depend on where the series starts; smoothed ones are not, because the exponential average starts at the first valid bar. Valid data: price levels, as a percentage slope needs a non-zero first bar in each window. Units: percent per bar times 100, or basis points per bar, centred on 0, with the sign intermarket minus base.

Before smoothing, a NaN or an infinity at overlap index p of s gives NaN on [p, p + max(n1, volLen) - 1], and one in s2 on [p, p + max(n2, volLen) - 1], each clipped to [w, m), and later values recover. A zero first bar of a slope window gives NaN at that window’s end only. A zero standard deviation of s2 (a flat volLen-bar window) gives 0 when both percentage slopes are finite. With smooth > 1, a NaN unsmoothed value at p makes every output from max(p, w + smooth - 1) on NaN, because the exponential recursion never recovers. A result beyond the binary64 range is whatever binary64 gives.

Katsanos’s defaults (Appendix A.1) are n1 = n2 = 15, volLen = 200, smooth = 3 and c = +1, so the minimum overlap is 202. His trading use, on page 128, is to buy when it peaks above a level between +10 and +40 and declines, and to sell when it troughs below a level between -10 and -40 and rises; Figure 9.4, gold against the CRB, uses +15 and -30. The output is multiplied by 100 twice, once in the percentage slope and once in the divergence, which matters only for absolute thresholds. His gold systems (Appendix A.2) run it against the XAU with 40/50 and c = +1 and against the dollar index with 30/40 and c = -1; in Table 11.1 the dollar index system made $282,257 in 21 trades with 90% winners and a profit factor of 35.39, and “the single LRS system stood out” (section 11.9, page 186).

Deviations from the MetaStock code: the smoothing average is seeded with the simple average of its first smooth values, where MetaStock’s Mov(…, E) is taken to seed with the first value (the MetaStock forum’s formula); every output before the first valid index is 0; and a zero standard deviation of s2 gives 0, where his e-mini listing guards the denominator with +.01 (“Stdev(SEC2,200)+.01”).

Verification: two independent references, one in Python in exact rational arithmetic with high-precision square roots and smoothing, and one in Julia that also translates his Appendix A.1 listing for both scales and his Appendix A.2 gold systems for the default scale, agree with it. There are exact identities: s2 == s with n1 == n2 and c = +1 gives 0 on every valid bar; s2 = s.Mul(2^k) with n1 == n2 and c = +1 gives, unsmoothed, exactly (float64(2^-k*PS) - PS)*100, with PS the SlopePercent(n1) of s, wherever the volLen-bar window of s is not flat, because the default scale depends on the relative price level; the legs psA and psB equal SlopePercent of the overlap views; and the smoothed tail equals XAverage of the unsmoothed values.

IntermarketLRSDivergenceCV

func (s Series) IntermarketLRSDivergenceCV(s2 Series, n1, n2, volLen, smooth int, c float64) Series

IntermarketLRSDivergenceCV returns IntermarketLRSDivergence with Katsanos’s price-level-adjusted scale, the ratio of the coefficients of variation of s and s2, for pairs at very different price levels.

It is the price-level-adjusted linear-regression-slope divergence of Markos Katsanos, Intermarket Trading Strategies (Wiley, 2008), section 9.4, pages 127-128 and Appendix A.1, page 298, which he recommends for “a high and a low priced security” but published no test of, so no published result or threshold applies to it. Everything is as in IntermarketLRSDivergence, with psA and psB the percentage slopes of s over n1 bars and of s2 over n2 bars (SlopePercent) and sdA and sdB the population standard deviations of s and s2 over volLen bars (StdDev), except the scale, which also uses maA and maB, the volLen-bar moving averages of s and s2 (Average): q = (((csdA)/sdB)maB)/maA, exactly in that Go operation order, his “D3:=D2Stdev(C,200)/Stdev(P,200)Mov(P,200,S)/Mov(S,200,S)” read from left to right, and not the algebraically equal c times the quotient of the two coefficients of variation. The line is printed with Mov(S,…), read here as Mov(C,…), the only dimensionally consistent reading, which makes the adjustment exactly the ratio of the coefficient of variation of s, sdA/maA, to that of s2, sdB/maB. The unsmoothed divergence is (float64(qpsB) - psA) 100, his “DIVERG:=(D3*LRSI-LRS)*100”, in that Go operation order: the conversion rounds the product before the subtraction, so the two are never fused into one multiply-add. The result is that divergence smoothed by an exponential moving average of length smooth.

s is the base and s2 the comparison; the inputs are right-aligned at their most recent bar, only the trailing min(len(s), len(s2)) bars are paired, the result has len(s), and bars before the overlap are zero.

The first valid index, counted from the start of the overlap, is w = max(n1, n2, volLen) - 1 for smooth <= 1 and w + smooth - 1 for smooth > 1, and the minimum overlap is one bar more; every earlier output is 0. With m the overlap length, the result is all zeros for an empty overlap, for n1, n2 or volLen below 2, for a c that is zero or not finite, for n1, n2 or volLen greater than m and, when smooth > 1, for smooth > m - w (tested by subtraction); any smooth <= 1 means unsmoothed, and every int parameter may be math.MaxInt or math.MinInt. For smooth > 1 the outputs from w + smooth - 1 on are exactly d[w:].XAverage(smooth)[smooth-1:], with d the unsmoothed result on the overlap.

Pairing is per leg: each percentage slope, standard deviation and moving average is computed on its own series over its own window, and the legs are combined bar by bar. Unsmoothed outputs are start-invariant up to rounding: the slopes are window-local, but the standard deviations and averages come from rolling aggregates whose last bits depend on where the series starts; smoothed ones are not, because the exponential average starts at the first valid bar. Valid data: positive price levels, since a coefficient of variation needs a positive mean. Units: percent per bar times 100, or basis points per bar, centred on 0, with the sign intermarket minus base.

Before smoothing, a NaN or an infinity at overlap index p of s gives NaN on [p, p + max(n1, volLen) - 1], and one in s2 on [p, p + max(n2, volLen) - 1], each clipped to [w, m), and later values recover. A zero first bar of a slope window gives NaN at that window’s end only, and a volLen-bar average of s or s2 at or below zero gives NaN. A zero standard deviation of s2 (a flat volLen-bar window) gives 0 when both percentage slopes are finite and both averages are positive. With smooth > 1, a NaN unsmoothed value at p makes every output from max(p, w + smooth - 1) on NaN, because the exponential recursion never recovers. A result beyond the binary64 range is whatever binary64 gives.

Katsanos’s defaults for the divergence (Appendix A.1) are n1 = n2 = 15, volLen = 200, smooth = 3 and c = +1, so the minimum overlap is 202, and his rule of trading its peaks and troughs (page 128) applies. His thresholds, from 10 to 40 on either side of 0, and his gold systems were all found with the default scale, so none of them carries over to this one. The output is multiplied by 100 twice, once in the percentage slope and once in the divergence, which matters only for absolute thresholds.

Deviations from the MetaStock code: the smoothing average is seeded with the simple average of its first smooth values, where MetaStock’s Mov(…, E) is taken to seed with the first value (the MetaStock forum’s formula); every output before the first valid index is 0; and a zero standard deviation of s2 gives 0, where his e-mini listing guards the denominator with +.01 (“Stdev(SEC2,200)+.01”).

Verification: two independent references, one in Python in exact rational arithmetic with high-precision square roots and smoothing, and one in Julia that also translates his Appendix A.1 listing for both scales and his Appendix A.2 gold systems for the default scale, agree with it. There are exact identities: s2 == s with n1 == n2 and c = +1 gives 0 on every valid bar; on positive data s2 = s.Mul(2^k) with n1 == n2 and c = +1 gives 0 as well, unlike the default scale, because the adjustment removes the price level; the legs psA and psB equal SlopePercent of the overlap views; and the smoothed tail equals XAverage of the unsmoothed values.

IntermarketRegressionDivergence

func (s Series) IntermarketRegressionDivergence(s2 Series, n, rocLen, smooth int) Series

IntermarketRegressionDivergence returns Katsanos’s regression divergence, the rocLen-bar return of s predicted from s2 by a rolling n-bar regression minus the actual return, smoothed over smooth bars.

It is the regression divergence of Markos Katsanos, Intermarket Trading Strategies (Wiley, 2008), section 9.5, page 128, equations 9.9 to 9.11, in the form of his published code (Appendix A.1, page 299: “RS1:=ROC(C,D2,%); RS2:=ROC(SEC2,D2,%); b:=Correl(RS1,RS2,D1,0)Stdev(RS1,D1)/Stdev(RS2,D1); a:=Mov(RS1,D1,S)-bMov(RS2,D1,S); PRED:=b*RS2+a; DIVERG:=(PRED-RS1); Mov(DIVERG,3,E)”), with C the base s, SEC2 the comparison s2, D1 the regression length n and D2 the return length rocLen. With k = rocLen, rs1 and rs2 are the k-bar signed percent returns of s and s2 on the overlap: (v/v0 - 1)*100 on a positive base v0, the expression of RateOfChange, and (v/(-v0) + 1)100 on a negative one. At each bar, beta and alpha are the least-squares slope and intercept of the regression of the n returns of s ending there on those of s2 (the values of Beta and Intercept, with the returns of s2 the regressor), and the unsmoothed divergence is (float64(betars2) + alpha) - rs1, predicted minus actual, his PRED-RS1, in that Go operation order: the conversion rounds the product before the addition, so the two are never fused into one multiply-add. His b, a correlation times a ratio of standard deviations, equals the least-squares slope only in exact arithmetic. The result is that divergence smoothed by an exponential moving average of length smooth.

s is the base and s2 the comparison; the inputs are right-aligned at their most recent bar, only the trailing min(len(s), len(s2)) bars are paired, the result has len(s), and bars before the overlap are zero.

The first valid index, counted from the start of the overlap, is w = k + n - 1 for smooth <= 1 and w + smooth - 1 = k + n + smooth - 2 for smooth > 1, and the minimum overlap is one bar more; every earlier output is 0. With m the overlap length, the result is all zeros for an empty overlap, for n < 3, for k < 1, for k + n > m (tested without forming the sum) and, when smooth > 1, for smooth > m - w (tested by subtraction); any smooth <= 1 means unsmoothed, and every int parameter may be math.MaxInt or math.MinInt. For smooth > 1 the outputs from w + smooth - 1 on are exactly d[w:].XAverage(smooth)[smooth-1:], with d the unsmoothed result on the overlap.

Pairing is by paired window: the returns are per leg, and each regression pairs the n returns of s and s2 that end at the same bar. Unsmoothed outputs are start-invariant; smoothed ones are not, because the exponential average starts at the first valid bar. Valid data: price levels, whose k-bar returns it regresses, because Katsanos holds the regression valid on price differences or yields only, not on raw prices (linearity and normality). Units: percentage points of k-bar return, centred on 0.

Before smoothing, a NaN or an infinity at overlap index p of either input makes returns p and p + k NaN, so the NaN outputs are [p, p+n-1] and [p+k, p+k+n-1], clipped to [w, m), and later outputs recover; a zero at p makes return p + k NaN, as its base is zero. A window holding a NaN return gives NaN, as its slope and intercept are both NaN. A flat window of returns of s2 gives beta = 0 and alpha the mean of the returns of s, so the unsmoothed result is that mean minus rs1. With smooth > 1, a NaN unsmoothed value at p makes every output from max(p, w + smooth - 1) on NaN, because the exponential recursion never recovers.

The book’s defaults (Appendix A.1) are n = 300, k = 15 and smooth = 3, so the minimum overlap is 317; his gold systems (Table 11.1) use n = 400 with k = 12 against the XAU and k = 10 against the dollar index. His trading use (Figure 9.5) is to buy when it peaks above about +3 and turns down, and to sell when it troughs below about -4 and turns up. Gold against the XAU with n = 400 and k = 12 made $621,084 in 31 trades with 81% winners and a profit factor of 28.91 (Table 11.1), and Katsanos stresses that the regression system needs no calculation or optimisation of the regression coefficients. Chained with an oscillator such as ChannelPosition(200, 3) applied from the divergence’s first valid index (Window(-413)), the first oscillator value of the gold and XAU system (400, ROC 12, smoothing 3, oscillator 200 and 3) is at overlap index 614, so it needs 615 bars; Katsanos’s 617 adds the five lengths with an 11-day ROC (11 + 400 + 3 + 200 + 3). The 2017 Nikkei system (Stocks & Commodities 35:8) uses n = 50, k = 3 and smooth = 1 on DXJ against FXY with ChannelPosition(50, 2); its EasyLanguage code zero-fills the first returns, so that its first windows hold zeros, which this method never does. The no-intercept listing (Appendix A.1, page 299, “PRED:=b*RS2”) keeps the with-intercept b and drops only a, so in exact arithmetic it is this method’s unsmoothed value minus Intercept on the return tails, then smoothed; it is not BetaOrigin. The gold-stock listing’s “DIVERG:=(PRED-RS)*10” subtracts the price ratio RS, a typo for RS1; neither the typo nor the *10 is reproduced. RegressionSpread on returns is its negation up to rounding, except where RegressionSpread’s perfect-fit guard returns 0. Overlapping k-bar returns are autocorrelated, so each regression is about as precise as 3n/(2k) independent pairs of returns.

Deviations from the MetaStock code: the smoothing average is seeded with the simple average of its first smooth values, where MetaStock’s Mov(…, E) is taken to seed with the first value (the MetaStock forum’s formula); every output before the first valid index is 0; and a negative base gives the signed return (v/(-v0) + 1)*100 rather than that of ROC.

Verification: two independent references, one in Python at 130 significant digits with every exact-zero and flatness decision made in rational arithmetic, and one in Julia that also translates the Appendix A.1 and Appendix A.2 listings, agree with it. There are exact identities: s2 = s and s2 = s.Mul(2) give 0 on every valid bar, with beta exactly 1 and alpha exactly 0 wherever the window’s returns are not flat; with smooth <= 1 and positive inputs, over the overlap views a and b, with ra := a.RateOfChange(k).Window(-k) and rb likewise, it equals ra.Beta(rb, n).Mul(rb).Add(ra.Intercept(rb, n)).Sub(ra) bit for bit from index n - 1 of that composition on, a composition through RateOfChange, Beta, Intercept, Mul, Add and Sub; and the smoothed tail equals XAverage of the unsmoothed values.

ZScoreDivergence

func (s Series) ZScoreDivergence(s2 Series, n, smooth int) Series

ZScoreDivergence returns Katsanos’s correlation-weighted z-score divergence of s2 minus s over n bars, smoothed by a smooth-bar exponential moving average.

It is the z-score divergence of Markos Katsanos, Intermarket Trading Strategies (Wiley, 2008), section 9.7, pages 130-131, with the MetaStock listing in Appendix A.2, page 312. On the overlap, za and zb are Standardise(n) of the overlap views of s and s2, each bar measured against the mean and population standard deviation of its own n-bar window, and r is the n-bar Pearson correlation of the pair, equal bit for bit to s.Correlation(s2, n) on the overlap. The raw value at overlap bar j is (zb[j]r[j]/math.Abs(r[j]) - za[j]) math.Abs(r[j]), the operation order of the published ZDIV:=(Z2*r/Abs(r)-Z1)*Abs(r), evaluated left to right as Go does, and 0 where r[j] == 0. For smooth > 1 the output is the exponential moving average XAverage(smooth) of the raw values from the first raw bar on, bit for bit, and zero while it warms up.

In exact arithmetic raw = r*Zs2 - |r|*Zs, with Zs and Zs2 the z-scores of s and s2: the code form, not the text form r*(Z2 - Z1) of page 130. For r < 0 the text form flips both legs, whereas the code sign-adjusts only the intermarket leg, as c does in the disparity methods; with s2 = s.Mul(-1), zb == -za bit for bit and r == -1, so the code form gives exactly 0 and the text form does not.

The sign is intermarket minus base: a positive value means that s stands lower in its window than s2, sign-adjusted by the correlation, stands in its own, so s has lagged s2, which is bullish for s if the gap closes by mean reversion.

s is the base and s2 the comparison; the inputs are right-aligned at their most recent bar, only the trailing min(len(s), len(s2)) bars are paired, the result has len(s), and bars before the overlap are zero.

The first valid index, counted from the start of the overlap, is n - 1 for smooth <= 1 and n + smooth - 2 for smooth > 1, and the minimum overlap is one bar more: n bars unsmoothed and n + smooth - 1 bars smoothed. That sets the history budget: Katsanos’s n = 500 with smooth = 3, against the dollar index, needs a minimum overlap of 502 bars. Every output before the first valid index is zero. Any smooth <= 1 (zero, negative and math.MinInt included) is valid and gives the raw divergence. Invalid parameters give aligned zeros, a fresh all-zero series of len(s): an empty overlap, n < 2, n greater than the overlap, or a smooth > 1 that leaves fewer than smooth raw values. The checks never add parameters together, so every int from math.MinInt to math.MaxInt is safe, and an invalid one returns at once without allocating anything beyond the zero result.

Pairing class: a paired window for r, which sees the same n bars of both series, and per leg for the z-scores, with a recursive tail when smoothed. It is not start-invariant: the z-scores use a rolling variance that depends on earlier history, and the smoothing is recursive. Valid data: any finite data whose deviations from their window mean lie between about 1e-154 and 1e154/sqrt(n), the range of the rolling variance behind Standardise (outside it the value is NaN or a silent 0, which reads as no divergence), since the value is unchanged by shifting either input or scaling it by a positive factor (a negative factor on s2 is absorbed by the correlation’s sign, and one on s negates the value), so it is the recommended divergence for spreads, rates and back-adjusted futures, and for long windows, where BollingerDivergence is ill-conditioned. The output is in standard-deviation units centred on 0, with |raw| <= 2*sqrt(n-1). With n = 2 both z-scores are plus or minus 1 and so is the correlation, so the value is 0 in exact arithmetic and rounding residue in binary64; use n >= 3.

Non-finite values: unsmoothed, a NaN or an infinity at overlap index p of either input gives NaN on [p, p+n-1], clipped to [n-1, m) with m the overlap length, and later bars recover once the value has left the window; smoothed, with p the index of the first NaN raw value, every output from max(p, n + smooth - 2) on is NaN for ever, because the recursion never recovers. A flat window in either series has r exactly 0 and gives 0, as does any other r == 0, so there is no zero-base NaN.

The value of this composite lies in its published, verifiable code, not in a demonstrated edge: Katsanos’s Chapter 11 tests have 9 to 38 trades over 1995 to 2007, with the levels optimised per system.

Katsanos’s defaults are n = 100 against the Philadelphia Gold and Silver Mining Index (XAU) and n = 500 against the dollar index, each with smooth = 3. On page 130 he suggests moving z-scores onto a scale centred on 50 (“adding 50 and multiplying by 10”); the method returns the unscaled value. Trading use: s2 can be a related market of either correlation sign; trade the extremes of a momentum oscillator of the divergence, as the Appendix A.2 systems do when they buy on the oscillator crossing below 80 (the XAU system) or 90 (the dollar index system) with ROC(SEC2,2) > 0. Katsanos (section 11.6) calls it “statistically unsound” because it assumes normality, yet in his Table 11.2 it beat every other single-intermarket test against the dollar index on net profit.

Deviations from Katsanos’s MetaStock code: the smoothing average is seeded with the simple average of its first smooth values, where MetaStock’s Mov(…, E) is understood to seed with the first value (a MetaStock forum formula; Katsanos does not state it), so the two converge after a few times smooth bars; the value is 0 where r == 0, where the listing would divide by Abs(r) = 0; the z-scores use population standard deviations, MetaStock’s Stdev being taken to be population; and outputs before the first valid index are zero rather than undefined.

Verification: two independent references, a Python reference in exact rational arithmetic (with 140-digit decimals for square roots and the smoothing) and a Julia reference in BigFloat that also translates Katsanos’s MetaStock listings, agree with it; and there are exact identities with existing methods: each z leg equals v.ZScore(n, v[i])[i] bit for bit, r equals s.Correlation(s2, n) on the overlap, and s2 = s, s.Mul(-1) and s.Mul(2) give exactly 0.