Beta — the slope of column's one-bar returns regressed on
benchmark's, over a trailing period-bar window:
r[i] = column[i] / column[i−1] − 1 (and the same for benchmark) ${output} =cov(r, rBenchmark) /var(rBenchmark)
Appends one column. 1 means the instrument moved one-for-one with the
benchmark over the window, 2 twice as hard, 0 unrelated, negative
inverse. undefined for the first period rows — a bar count of
returns, so it needs period + 1 prices and the first reading lands
on bar period, one later than a plain period-bar window study.
Pass PRICES; the returns are taken inside
This is the one study in the two-series family that transforms its inputs
before measuring, and it is TA-Lib's BETA that decides so: TA-Lib takes
two price arrays and differences them internally. A caller who has already
differenced would get the beta of the returns of the returns, so the
option is named column (a price) and not returns. The one-bar return
is percentChangeValues (v, 1) — the same ROC every rate-of-change
study in the package composes on, so "a one-bar return" means one thing
here. It is expressed as a percent (×100), which cancels: scaling both
return series by the same constant leaves cov/var unchanged.
Argument order is a trap, and TA-Lib's is the opposite of the reading
Measured: talib.BETA(a, b, n) returns cov(rA, rB) / var(rA) — the
slope of the second argument's returns on the first's. So the
TA-Lib call equivalent to beta(wide, { column: 'close', benchmark: 'spy' })
is talib.BETA(spy, close, period), benchmark first, and that is exactly
what the oracle generator asserts against (bar-for-bar at period 5 and
20). A caller who passes (stock, index) in that order gets the slope of
the index on the stock — on an affine pair, BETA(x, 2x+5) = 0.952 where
BETA(2x+5, x) = 1.050, measured — which is a different number, not a
different sign, so nothing about the output says the arguments were
swapped. Pond's option names remove the ambiguity: the benchmark is
always the denominator.
benchmark = 2 · column + 5 does NOT give a beta of 1
Correlation is invariant to an affine transform, so
correlation reads exactly +1 on that pair — but beta is a slope
of returns, and an affine transform is not return-preserving:
r_y = Δx / (x + 2.5) against r_x = Δx / x, so the ratio drifts with the
price level. Measured on a random walk near 100 at period 5:
BETA(x, 2x+5) ≈ 0.976, not 1. The exactly-1 pair is benchmark = k · column (a pure scale, no shift), which is return-preserving; both
cases are pinned by tests.
The comparison series is a COLUMN, not a second TimeSeries
Join the benchmark in first and name its column — see
correlation's docstring for the recipe and the reasoning
(alignment is align/joinMany's job, and a study that took a second
series would have to invent an alignment policy core already expresses).
A missing benchmark column throws.
Edges
The window is strict: all period returns of both columns must
exist (rollingBivariateValues), so an interior gap in either
column blanks period + 1 rows — the gap bar costs two returns — and
then recovers.
A flat benchmark window reads undefined, and TA-Lib reads 0. Its
return variance is zero, and so — exactly — is the covariance (the
kernel's change counter writes both as exact 0, even when the
benchmark froze mid-window), so this is a genuine 0/0 and needs no
guard here (see the kernel). Measured:
talib.BETA over a constant first input returns 0.0. Same deliberate
delta as correlation's, for the same reason: 0 would claim "no
relationship" from data that cannot support the claim.
A zero price is a missing return here, and 0 in TA-Lib. TA-Lib
substitutes a return of 0 when the previous price is zero;
percentChangeValues marks it missing (x/0 is not a percent
change), and the strict window then blanks the period rows that read
it. Note it is exactly one return that goes missing, and it is the
one after the zero — the return into a zero price is a legitimate
−100%. Unreachable on prices, reachable when column is another
study's output; measured on a ramp with one zeroed bar, TA-Lib emits
4.6e-07, -3.0e-05, -6.0e-05, -1.2e-02 across the four affected
bars where this study emits nothing.
Invariant to scaling either column, NOT to shifting either. Scaling
a price series scales its returns by exactly one, so beta does not move;
adding a constant changes every return (a +50 on a 100-price series
halves them) and beta moves with it. Both are pinned, the second as a
deliberate inequality — a test that asserted shift-invariance would pass
only on a study that had normalised by the wrong thing.
benchmark must differ from column (beta against itself is 1 by
construction), and period must be at least 2 — a single return has
zero variance.
Beta — the slope of
column's one-bar returns regressed onbenchmark's, over a trailingperiod-bar window:Appends one column.
1means the instrument moved one-for-one with the benchmark over the window,2twice as hard,0unrelated, negative inverse.undefinedfor the firstperiodrows — a bar count of returns, so it needsperiod + 1prices and the first reading lands on barperiod, one later than a plainperiod-bar window study.Pass PRICES; the returns are taken inside
This is the one study in the two-series family that transforms its inputs before measuring, and it is TA-Lib's
BETAthat decides so: TA-Lib takes two price arrays and differences them internally. A caller who has already differenced would get the beta of the returns of the returns, so the option is namedcolumn(a price) and notreturns. The one-bar return is percentChangeValues(v, 1)— the same ROC every rate-of-change study in the package composes on, so "a one-bar return" means one thing here. It is expressed as a percent (×100), which cancels: scaling both return series by the same constant leavescov/varunchanged.Argument order is a trap, and TA-Lib's is the opposite of the reading
Measured:
talib.BETA(a, b, n)returnscov(rA, rB) / var(rA)— the slope of the second argument's returns on the first's. So the TA-Lib call equivalent tobeta(wide, { column: 'close', benchmark: 'spy' })istalib.BETA(spy, close, period), benchmark first, and that is exactly what the oracle generator asserts against (bar-for-bar atperiod5 and 20). A caller who passes(stock, index)in that order gets the slope of the index on the stock — on an affine pair,BETA(x, 2x+5) = 0.952whereBETA(2x+5, x) = 1.050, measured — which is a different number, not a different sign, so nothing about the output says the arguments were swapped. Pond's option names remove the ambiguity: thebenchmarkis always the denominator.benchmark = 2 · column + 5does NOT give a beta of 1Correlation is invariant to an affine transform, so correlation reads exactly
+1on that pair — but beta is a slope of returns, and an affine transform is not return-preserving:r_y = Δx / (x + 2.5)againstr_x = Δx / x, so the ratio drifts with the price level. Measured on a random walk near 100 atperiod 5:BETA(x, 2x+5) ≈ 0.976, not1. The exactly-1pair isbenchmark = k · column(a pure scale, no shift), which is return-preserving; both cases are pinned by tests.The comparison series is a COLUMN, not a second
TimeSeriesJoin the benchmark in first and name its column — see correlation's docstring for the recipe and the reasoning (alignment is
align/joinMany's job, and a study that took a second series would have to invent an alignment policy core already expresses). A missingbenchmarkcolumn throws.Edges
periodreturns of both columns must exist (rollingBivariateValues), so an interior gap in either column blanksperiod + 1rows — the gap bar costs two returns — and then recovers.undefined, and TA-Lib reads0. Its return variance is zero, and so — exactly — is the covariance (the kernel's change counter writes both as exact0, even when the benchmark froze mid-window), so this is a genuine0/0and needs no guard here (see the kernel). Measured:talib.BETAover a constant first input returns0.0. Same deliberate delta as correlation's, for the same reason:0would claim "no relationship" from data that cannot support the claim.0in TA-Lib. TA-Lib substitutes a return of0when the previous price is zero; percentChangeValues marks it missing (x/0is not a percent change), and the strict window then blanks theperiodrows that read it. Note it is exactly one return that goes missing, and it is the one after the zero — the return into a zero price is a legitimate−100%. Unreachable on prices, reachable whencolumnis another study's output; measured on a ramp with one zeroed bar, TA-Lib emits4.6e-07,-3.0e-05,-6.0e-05,-1.2e-02across the four affected bars where this study emits nothing.+50on a 100-price series halves them) and beta moves with it. Both are pinned, the second as a deliberate inequality — a test that asserted shift-invariance would pass only on a study that had normalised by the wrong thing.benchmarkmust differ fromcolumn(beta against itself is1by construction), andperiodmust be at least 2 — a single return has zero variance.