Relative: Alignment
All functions · nseries package
Time alignment
Every two-series method pairs values by position only, right-aligned at the most recent bar. With m = min(len(s), len(s2)) and off = len(s) - m, bar off+j of the receiver is paired with bar len(s2)-m+j of the other series; the output has the receiver’s length and is zero before off. Overlap and the relative methods have always paired this way, and the older two-series methods now do too: Add, Sub, Mul and Div with a Series argument, the comparisons (Equal, GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual and their aliases), Ratio, SAverage, SpearmanRankCorrelation, CrossesAboveS, CrossesBelowS, If with a Series branch and the FloorTrader levels. QStick and SuperTrend inherit it through Sub and Add. An empty Series operand is the m = 0 case and gives zeros.
nseries has no timestamps, so position is all it can pair. It cannot see that two series keep different calendars (exchange holidays, half days, a session one market trades and the other does not), that one of them has missing bars, or that one is released on its own schedule, as FRED economic series and the CFTC’s weekly Commitments of Traders are. A bar that one input has and the other lacks shifts every earlier pairing by one bar, and the output still looks plausible. Line such series up in time, by their timestamps, before they reach nseries, so that equal positions refer to equal times.
For a slow or irregular series, compute paired-window statistics (correlations, betas, regression spreads, channels on a relationship line) on that series’ own release clock, sampling both legs at its releases. Do not compute them on base bars with the slow series carried forward: carrying a value forward makes most of its bar-to-bar changes zero, which biases every paired-window statistic towards zero (the Epps effect; RELATIVE-INDICATORS-SPEC.md §3.15, rule 5, and §3.4).
Sign conventions
In every relative method s is the base and s2 the comparison. The relationship lines and the relative-strength quantities are base against comparison, so on positive prices they rise when s outperforms s2: PriceRelative is s/s2, LogRatio is ln s - ln s2, and RSMK is the log return of s less that of s2; RegressionSpread and RegressionZScore are positive when s stands above its fit on s2, and DistanceScore when s has risen more than s2 since the start of its window; and the relative momentum recipe is the return of s less that of s2. The divergence family is the other way round, comparison less base, so a positive value means that s has lagged s2: IntermarketDisparity, IntermarketDisparityX, BollingerDivergence, ZScoreDivergence, IntermarketLRSDivergence and IntermarketLRSDivergenceCV take the leg of s2 (scaled or sign-adjusted where the method says so) less the leg of s; IntermarketRegressionDivergence takes the return of s that s2 predicts less its actual return; and the recipes for Katsanos’s relative rate of change and the per-leg channel-position divergence follow the same sign. Stress keeps Kaufman’s sign: its difference is the position of s less that of s2, so a high reading means that s is high in its own range relative to s2. The lead-lag entries take Shift’s sign: a positive lag of CrossCorrelation or LeadLag, or a positive LeadLagBias, means that s2 leads, and LeadLagCorrelation carries the sign of the correlation.
Overlap
func (s Series) Overlap(s2 Series) (Series, Series)
Overlap returns copies of the trailing bars that s and s2 have in common, right-aligned at the most recent bar, so both results have length min(len(s), len(s2)).
s is the base and s2 the comparison. The inputs are right-aligned at their most recent bar and only the trailing m = min(len(s), len(s2)) bars are paired: the results are fresh copies of s[len(s)-m:] and s2[len(s2)-m:], with separate storage, so writing to or appending to either result never changes s, s2 or the other result. An empty input gives two empty, non-nil Series. Values are copied bit for bit, NaN and ±Inf included.
The first valid index is 0 and the minimum overlap is 1: every returned bar is a paired input bar, and there is no warm-up.
Overlap is the alignment primitive of the relative indicators. Any single-series method, and the arithmetic methods with a Series argument, become correct two-series building blocks when they are applied to its results, whatever the lengths of s and s2. The composition rule is:
- compose on the results, a, b := s.Overlap(s2), which have equal lengths and pair bars from the most recent one;
- trim each stage’s warm-up with Window(-f), where f > 0 is that stage’s first valid index (the recipe named below tabulates f for common stages); a stage whose first valid index is 0 needs no trim, and Window(0) would return an empty Series;
- the first valid index of a chain of trimmed stages is the sum of the stages’ first valid indices, and the minimum overlap is one more;
- a binary stage (Add, Sub, Mul, Div with a Series argument, and the comparisons) right-aligns its operands at the most recent bar, so operands trimmed by different amounts are still paired correctly, but its result is zero on the receiver’s leading bars that have no partner, and a later stage reads those zeros as data; trim both operands by the same amount, or combine at full length and trim once.
For example, a.Div(b).XAverage(3).Window(-2).Highest(300) has first valid index 2 + 299 = 301 and minimum overlap 302, counted from the start of the overlap. The result is right-aligned at the most recent bar, so once the overlap has at least the minimum overlap (plus k for LookBack(k)), Value() and LookBack(k).Value() read valid bars; a shorter overlap still gives numbers, so check its length first. Written on s and s2 directly, s.Div(s2) pairs the right bars (it right-aligns, as every two-series method does), but its first valid index is counted from the start of s rather than from the start of the overlap, and when s is the longer input its result is zero before the overlap: a following stage with a long memory, such as XAverage(150), takes those zeros as data and carries them into valid bars. The equivalent idiom m := min(len(s), len(s2)); s.Right(m), s2.Right(m) gives aliasing views with the same values. The worked recipe is listed under “Compositions” in the “Relative: Alignment” category.
Overlap cannot repair temporal misalignment: bars are paired by position from the most recent bar, so a session one input has and the other lacks shifts every earlier pairing.
Verification: a Python reference and a Julia reference slice the three pairs of testdata/ref/inputs/mismatch.csv, and the Go results equal them bit for bit. For every pair of lengths the first result equals s.Right(len(s2)) and the second equals s2.Right(len(s)) bit for bit (Right is the existing method; Overlap copies where Right aliases), s.Overlap(s) returns two separate copies equal to s, and Overlap is idempotent.
Compositions
Composing a relative indicator over Overlap
Any single-series method becomes a correct two-series building block when it runs on the Overlap results. Trim each stage’s warm-up with Window(-f), where f is that stage’s first valid index (see the table below); a stage with f = 0 needs no trim, and Window(0) would return an empty series, so a parameterised trim such as Window(-(n-1)) must skip n = 1. The first valid index of the chain is the sum of the stages’ first valid indices, and the minimum overlap is one more. A binary stage (Add, Sub, Mul, Div with a Series argument, and the comparisons) right-aligns its operands at the most recent bar, so operands of different lengths are still paired correctly, but its result is zero on the receiver’s leading bars that have no partner, and a later stage reads those zeros as data. Trim both operands of a binary stage by the same amount, or combine them at full length and trim once.
a, b := s.Overlap(s2)
x := a.Div(b).XAverage(3).Window(-2).Highest(300)
valid := x.Window(-299) // empty unless the overlap has at least 302 bars
- First valid index: 2 + 299 = 301, counted from the start of the overlap.
xhas lengthm - 2for an overlap ofmbars, and its element 299 is the first valid one. Its earlier elements are maxima of shorter windows, not zeros, and an overlap shorter than 302 bars still gives numbers, so checklen(a) >= 302, or readvalid, before usingx.Value(). - Minimum overlap: 302 bars, which give exactly one valid bar.
- Anchors: the chain on the
Overlapresults equals, bit for bit, the same chain on inputs trimmed to the overlap withoutOverlap(tag E, exact; the composition property of RELATIVE-INDICATORS-SPEC.md §3.5), and each valid bar is the maximum of exactly the 300 smoothed ratios ending at that bar, its own included.Overlapitself equalsRightof the other input’s length on both results (E, through the existingRight). - Why the rule matters:
Divwith aSeriesargument once paired bars from the start of each series and padded the shorter one with its last value, sos.Div(s2).XAverage(3).Highest(300)on inputs of different lengths differed from the correct value at every valid bar of thewalk3fixtures (testdata/ref/inputs/walk3.csv).Divnow right-aligns, and on those fixtures the one-liner equals the recipe at every valid bar, but only becauseXAverage(3)forgets its seed within a few bars. Its result is as long ass, so its first valid index counts from the start ofs, not of the overlap; and whensis the longer input, its firstoffvalues are zeros that seed the next stage. A stage with a long memory carries them into valid bars:s.Div(s2).XAverage(150)differs froma.Div(b).XAverage(150)at all 351 valid bars on(w1, w2[100:]), by up to 19%. Composing overOverlapkeeps every stage on paired bars. In this chain theWindow(-2)trim happens to change no valid bar, becauseHighest’s window at bar 301 starts afterXAverage’s warm-up. The trim matters when a later stage reads warm-up values: inStdDev(n)followed byXAverage, the untrimmed leading zeros enterXAverage’s seed and change valid bars. - Pinned by
TestRecipeCompositionRule; runnable exampleExampleSeries_Overlap_compositionRule.
Div keeps its legacy semantics: a zero divisor gives 0, not NaN.
First valid index of common stages
| Stage | First valid index f | Before f |
|---|---|---|
StdDev(n), ZScore(n, v), R2(n), Slope(n), Standardise(n) | n - 1 | zeros |
Average(n), Bollinger(n, k) | n - 1 | the input, passed through |
XAverage(n) | n - 1 | the input, passed through; it then seeds with the average of the first n inputs, so run it only on trimmed input |
Highest(n), Lowest(n) | n - 1 | the maximum or minimum of the bars so far |
RateOfChange(k), Delta(k), Momentum(k) | k | zeros |
Add, Sub, Mul, Div | 0 | — |
Pinned by TestRecipeStageFirstValid.