Relative: Timeframes
All functions · nseries package
Compositions
Timeframe emulation by length
nseries has no timestamps. A higher-timeframe indicator is emulated on the base bars by multiplying every length by the number of base bars per higher bar: MACD(5*f, 5*sl), Stochastic(h, l, 5*n), r.XAverage(5*n) and so on, including every relative entry that takes lengths. No method is needed. The published conversion factors:
| From | To | Factor | Source |
|---|---|---|---|
| weekly | daily | ×5 (12 and 26 weeks are 60 and 130 days; a 14-week %K is 70 days, with the 3-bar slowing kept, not multiplied) | Vitali Apirine, Stocks & Commodities 35:13, 36:2, 36:10; Martin Pring, 26:12 (his intermediate group only) |
| weekly | daily | 20 days ≈ 4 weeks ≈ a 40% weekly exponential; 40 days ≈ 8 weeks ≈ 22% | Arthur Merrill, 8:9 |
| monthly | weekly | ×4.33 (9, 12, 18 and 24 months are 39, 52, 78 and 104 weeks) | Pring, 10:10, 18:4; Narcouzi, 19:8 |
| monthly | weekly | 14 months ≈ 59 weeks: the author’s own figure, 294 days divided by 5, not 14 × 4.33 = 60.6 | Meibuhr, 10:12 |
| monthly | daily | ×21 (1 and 12 months are 21 and 252 days; 14 months ≈ 294 days) | Schmidt, 40:2; Meibuhr, 10:12 |
| daily | hourly | 7 bars for an equity session; 23 for 60-minute gold futures (a 23-bar high as “roughly a daily rolling high”) | Carey, 13:5; D’Errico and Trombetta, 42:8 |
| daily | 30-minute | ×13 (a daily SuperSmoother(4) is about a 30-minute SuperSmoother(52); 26 for less lag) | John Ehlers, 34:1 |
The emulations are partial in places: Apirine’s weekly slow stochastic keeps its 3-bar slowing rather than making it 15; Pring’s daily Special K is exactly ×5 only in its intermediate group (its long group uses spans 195, 265, 390 and 530 where ×5 gives 195, 260, 390 and 520, and its short group is the daily short-term KST); Meibuhr’s 59 weeks is his own figure. Guppy’s two groups of daily averages stand for two timeframes, because his software “made it easier to measure weeks in days than it would be to shift to a weekly view” (16:2), and long horizons serve as regimes: Russell’s barometer, the daily close against a 30-week average, is a 150-bar average of daily closes, and a 200-day average is a popular overlay for Dow Theory’s primary trend (Bowman and Hartle, 8:9).
Fidelity. Over 2003 to 2007 on the Russell 2000, all eight centreline crossovers of Apirine’s ×5 daily MACD fell within six business days of the true weekly MACD’s (35:13, figure 8). The differences come from holidays (Pring: “not an exact simulation because some weeks only contain four trading days”), from phase (a rolling five-bar window is not a calendar week) and from aliasing. Emulation on base bars does not alias, and Ehlers notes that it allows a shorter-than-proportional length for less lag. Averages emulate best; range-based indicators include partial weeks in their windows.
History budget. Emulation multiplies the minimum overlaps: a 26-week average emulated as XAverage(130) needs 130 bars plus four to five times 130 of burn-in.
Weekly and daily MACD on daily bars (Apirine)
Apirine’s weekly MACD on daily bars is the ×5 MACD, and his relative daily MACD adds the daily MACD to it (Stocks & Commodities 35:13). In this and the next two recipes c, h and l are one instrument’s daily closes, highs and lows, of equal length.
weekly := c.MACD(60, 130).Window(-129)
relative := c.MACD(60, 130).Add(c.MACD(12, 26)).Window(-129)- First valid index: 129 for both, counted from the start of
c. Minimum length: 130 bars, plus four to five times 130 bars of burn-in before the 130-bar average forgets its seed. - Pinned by
TestRecipeT3TASCWeeklyDailyMACD; runnable exampleExampleSeries_MACD_weeklyOnDaily.
Weekly and daily PPO (Apirine)
Apirine’s percentage form of the same pair (Stocks & Commodities 36:2): the weekly PPO is the ×5 MACD as a percentage of the 130-day average, and the daily term is the 12- and 26-day MACD as a percentage of the same 130-day average, so the two terms share a denominator; the relative daily PPO is their sum. The PPO’s advantage over the MACD is that its readings can be compared across securities and over long periods in which the price has doubled or tripled.
e := c.XAverage(ws)
w := c.XAverage(wf).Sub(e).Div(e).Mul(100).Window(-(ws - 1))
d := c.XAverage(df).Sub(c.XAverage(ds)).Div(e).Mul(100).Window(-(ws - 1))
rel := w.Add(d)- First valid index:
ws - 1for both, counted from the start ofc(129 at Apirine’s 60, 130, 12 and 26). Minimum length:wsbars. - Parameters:
1 <= wf < ws,1 <= df < ds <= ws, so one trim ofws - 1bars covers every average. - Printed values: Apirine’s spreadsheet (36:2, figure 1; the fixture
tasc-apirine-36-2.csv) is reproduced from its printed closes and its printed first-row averages: its first weekly PPO is100 × 21.26/1215.39 = 1.749from the printed weekly MACD (1.748 from the printed averages, whose difference is 21.25), printed 1.75, and its last relative daily PPO, 2.592 from the printed averages and printed 2.59, is reproduced only with the 130-day denominator (dividing the daily term by the 26-day average gives 2.570). - Pinned by
TestRecipeT3TASCWeeklyDailyPPOandTestRecipeT3TASCApirinePrinted.
Weekly and daily stochastics (Apirine)
Apirine’s weekly and daily slow stochastics (Stocks & Commodities 36:10): a 3-bar simple average of the 70-bar and of the 14-bar stochastic, an average of ratios. The weekly one is a partial emulation: its %K length is ×5, but its 3-bar slowing is kept rather than made 15.
w := c.Stochastic(h, l, nw).Window(-(nw - 1)).Average(slowing).Window(-(slowing - 1))
d := c.Stochastic(h, l, nd).Window(-(nd - 1)).Average(slowing).Window(-(slowing - 1))- First valid index:
nw + slowing - 2andnd + slowing - 2, counted from the start of the instrument (71 and 15 at Apirine’s 70, 14 and 3). Minimum length:nw + slowing - 1andnd + slowing - 1bars. With aslowingof 1, leave out the last trim, whichWindow(0)would empty. - Domain: the existing
Stochastic, which gives 0 on a flat window, where the channel-position convention of this library gives 50; and an average of ratios, which the slowed stochastic that divides smoothed sums matches only at a slowing of 1. - Printed values: his spreadsheet (36:10, figure 1; the fixture
tasc-apirine-36-10.csv) is reproduced from its printed closes and printed 70- and 14-bar highs and lows, and its printed 3-bar averages from the printed stochastics. - Pinned by
TestRecipeT3TASCWeeklyDailyStochasticandTestRecipeT3TASCApirinePrinted.
Relationship lines on emulated bars
An emulated weekly relationship line on daily bars is the composition with multiplied lengths, on a common clock: for example the log ratio’s MACD with lengths 60 and 130 (the MACD of the log ratio, under “Relative: Ratio and Relative Strength”), or a 13-week RSMK with 3-week smoothing as RSMK(b, 65, 15).
a, b := s.Overlap(s2)
rsmk := a.RSMK(b, n, smooth).Window(-(n + smooth - 1))- First valid index:
n + smooth - 1, counted from the start of the overlap (79 for 65 and 15). Minimum overlap:n + smoothbars (80 for 65 and 15). - Pinned by
TestRecipeT3TASCEmulatedRSMK.
Per-leg readings across bar intervals
Legs on different bar intervals (a daily s against a weekly s2) can be compared only through per-leg readings, each leg composed on its own history with lengths that span the same calendar time and read at “now” with Value. The two-series methods themselves pair by position and cut both legs to the overlap, so even those that take a length for each leg do not see a leg’s history beyond the shorter leg’s length. RSMK and Stress take one length for both legs, so they cross bar intervals only through their per-leg forms. For RSMK, the log relative momentum of the two legs:
mom := 100 * (s.Log().Momentum(n1).Value() - s2.Log().Momentum(n2).Value())and, because the average is linear, its smoothed form 100*(s.Log().Momentum(n1).Window(-n1).XAverage(k1).Value() - s2.Log().Momentum(n2).Window(-n2).XAverage(k2).Value()) with calendar-matched k1 and k2. For Stress’s first stage, the per-leg channel-position divergence s2.ChannelPosition(n2, 1).Value() - s.ChannelPosition(n1, 1).Value() (of opposite sign to Stress’s difference). Anything that annualises states its bar interval: the same volatility annualised by the square root of bars per year differs materially between daily, weekly and monthly bars (Katsanos, Stocks & Commodities 44:3).
- Pinned by
TestRecipeT3TASCPerLegLogMomentum.
Which relative indicators are meaningful across timeframes
| Pairing class | Across timeframes | Rule |
|---|---|---|
| Per leg, with a length for each leg: the per-leg compositions (relative momentum, the per-leg PPO difference, the PMO spread, the channel-position divergence) and the divergences’ legs composed per leg (each leg’s band position, disparity or percentage slope) | Yes, with each leg on its own interval | Choose per-leg lookbacks that span the same calendar time and combine at “now” with Value. A smoothing tail pairs raw values bar by bar, so it needs a common clock. The two-series methods themselves pair by position; across intervals compose each leg on its own history and subtract at “now”. |
Per leg, but one length for both legs: RSMK, Stress (its first stage) | Only as per-leg compositions, or on a common clock | daily.RSMK(weekly, 90, 1) compares 90 days with 90 weeks; use the per-leg forms above. |
Paired window, and recursive entries on a relationship line: correlations, betas, regressions, lead-lag, channels on relationship lines, the oscillator and regimes on a ratio, Stress’s second stage | Only on a common clock | Never pair weekly with daily bars. Resample both legs to the higher interval, or emulate it on both legs’ base bars. Correlations differ by interval (Sharp found copper against the T-bond yield at 0.06 over 50 monthly averages and 0.37 over 100 daily closes, 8:4, so the span differs as well as the interval), so the interval is part of the parameters. |
| Pointwise: the ratio, the log ratio, the percent premium | On a common clock, or as a level against a mapped higher-timeframe level | A mapped weekly level against a daily one is a same-bar comparison with a stale value: meaningful for levels (a daily close against the prior week’s range), not for returns. |
| The same symbol on two timeframes | Yes: treat the mapped or emulated higher-timeframe line as s2 | Apirine’s relative daily MACD and PPO above; a daily close against a 30-week average. |
| Votes across timeframes | Yes, once each timeframe’s state is on the base bars | A count of agreeing states. |
Mapping a higher timeframe onto base bars without look-ahead
When a strategy computes on true higher-timeframe bars and maps them back onto its base bars, five rules keep the mapping causal:
- Completed bars only. A base bar may use a higher-timeframe bar only if that bar closed at or before the base bar’s close; with bars stamped at their close, that is
stamp <= epoch. Steckler’s rule is the published statement: the weekly %K that gates a daily entry within the week is “the preceding Friday’s” (Stocks & Commodities 18:8). - Developing bars only when rebuilt from base bars to date. A week-to-date value built from the base bars so far is legitimate. A completed higher bar’s value must never be written back onto the base bars inside its own period: that is the “repainting” error.
- Stamps. A weekly bar stamped at the week’s start would leak the whole week onto its first base bar. A week whose stamp falls after its last session (a Friday holiday) appears one base bar late, which is conservative.
- Boundary flags. First-bar-of-period flags are causal; last-bar flags read the next bar’s timestamp. Today’s first-bar flags need three corrections (below).
- Forward fill. A mapped series repeats each higher value for its period, so its base-bar changes are mostly zero, and paired-window statistics on it are biased (the Epps effect): compute them on the common higher clock instead.
Sampling-interval effects. Sharp correlates monthly averages rather than month-end closes (8:3, 8:4), which no algolang accessor offers. Ehlers recommends a two-bar average or a four-bar SuperSmoother before applying any other indicator (34:1); the same filters protect any later decimation. Overlapping k-bar returns on base bars inflate any significance computed on them (Schmidt’s daily-rolled monthly returns, 40:2): their effective sample is about 3n/(2k). A true monthly beta or correlation needs non-overlapping two-series returns, which only true monthly bars give.
The algolang accessors for higher timeframes
nseries stays timestamp-free and adds no timestamp API; higher-timeframe bars come from algolang. At the time of writing:
- A daily strategy can load true weekly bars with
rs.LoadSeries(engine.SeriesOptions{BarInterval: "weekly"})and read them withrs.Close(n). The engine advances the weekly series to its latest bar stamped at or before the primary’s bar, so it holds completed weeks only, provided weeks are stamped at their close. It holds at most2*maxBarsBackweeks, and that is an upper bound: the warm-up skip applies to the primary only, so with a daily primary and a weekly secondary that start together, the weekly series holds about 10 bars on the first evaluated bar at the defaultmaxBarsBackof 50. Check its length before relying on a per-leg lookback; a lookback longer than the history returns a warm-up zero without warning. CloseW,CloseMandCloseCompressed(and their siblings) are stubs that return 0.0 or an empty series; do not use them.CloseDgives completed days (withlookback = 1) or the developing day, and the split accessors (CloseSplitand siblings) completed or developing sub-day splits at clock times, but their cache is keyed without the data series, so they are safe for one data series only:CloseD(1, loc)afterCloseD(0, loc)returns data 0’s closes. Do not use them for a second data series until algolang keys the cache bydataN.IsFirstBarOfDayandIsFirstBarOfWeekare per-bar flags. Clear the flag on the data’s first bar, which they always flag; take week boundaries from a change of ISO week between consecutive bars rather than fromIsFirstBarOfWeek, which misses a boundary when a closure spans a week without a weekday decrease; and take month and year boundaries from a change of month or year inrs.Time, clearing the data’s first bar here too, since no first-bar month or year flag exists (IsLastBarOfMonthandIsLastBarOfYearread the next bar’s timestamp, asIsLastBarOf*all do).