tradingmachine.assets.analysis.statistic_functions
¶
Statistic functions: rolling regressions, correlations and dispersion from TA-Lib.
Each method fetches the instrument's candles through prices, adds one or more TA-Lib columns and returns the candles. The class is inherited by tradingmachine.assets.instruments.Instrument, which supplies prices.
Typical usage example:
infosys = instruments.Instrument(exchange="nse", segment="equities", symbol="INFY") frame = infosys.linear_regression_slope(window=14, days=365) nifty = instruments.NonTradeableInstrument(exchange="nse", segment="equity_indices", symbol="NIFTY") frame = infosys.beta(nifty, window=60, days=730)
StatisticFunctions
¶
Bases: PriceAnalysis
Rolling statistics an instrument calculates from its candles with TA-Lib.
beta
¶
beta(benchmark, window=14, column='close', interval='day', from_date=None, to_date=None, days=None, adjusted=True)
Adds the rolling beta of the instrument against a benchmark, such as an index.
TA-Lib's beta works on the change from each candle to the next, so a beta of 1 means the instrument moved in step with the benchmark. Candles are matched by time, and a candle either side lacks is left out.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
benchmark
|
PriceAnalysis
|
The instrument to measure against, such as an tradingmachine.assets.instruments.NonTradeableInstrument for NIFTY, or any other object with a |
required |
window
|
int
|
The int number of candles in each calculation window. |
14
|
column
|
str
|
The str name of the candle column to use from both, such as |
'close'
|
interval
|
str
|
The str candle interval, such as |
'day'
|
from_date
|
date | str | None
|
The first day of the range as a datetime.date or a |
None
|
to_date
|
date | str | None
|
The last day of the range as a datetime.date or a |
None
|
days
|
int | None
|
The int number of days to count back from today, or None when from_date and to_date are given. |
None
|
adjusted
|
bool
|
A bool that is True for prices adjusted for splits and bonuses. |
True
|
Returns:
| Type | Description |
|---|---|
DataFrame | None
|
A pandas.DataFrame of the matched candles with a |
Raises:
| Type | Description |
|---|---|
UnifiedBrokerInterfaceError
|
UBI refused the request or could not be reached. |
Examples:
Print the last five values of Infosys's 20-day beta against NIFTY:
from tradingmachine.assets import equities
infosys = equities.Equity(exchange="nse", symbol="INFY")
nifty = equities.EquityIndex(exchange="nse", symbol="NIFTY")
frame = infosys.beta(benchmark=nifty, window=20, days=180)
print(frame.set_index("datetime")["beta_20"].tail())
Compare four shares by their 60-day beta against NIFTY:
from tradingmachine.assets import equities
nifty = equities.EquityIndex(exchange="nse", symbol="NIFTY")
symbols = [
"INFY",
"TCS",
"HDFCBANK",
"RELIANCE",
]
for symbol in symbols:
share = equities.Equity(exchange="nse", symbol=symbol)
frame = share.beta(benchmark=nifty, window=60, days=365)
print(f"{symbol}: beta {frame['beta_60'].iloc[-1]:.2f}")
Source code in src/tradingmachine/assets/analysis/statistic_functions.py
correlation_coefficient
¶
correlation_coefficient(benchmark, window=14, column='close', interval='day', from_date=None, to_date=None, days=None, adjusted=True)
Adds the rolling Pearson correlation of the instrument's returns with a benchmark's returns.
Returns, the fractional change from each candle to the next, are correlated rather than price levels, because two unrelated prices that both trend upwards would otherwise look strongly correlated. Candles are matched by time, and a candle either side lacks is left out.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
benchmark
|
PriceAnalysis
|
The instrument to compare with, such as an tradingmachine.assets.instruments.NonTradeableInstrument for NIFTY, or any other object with a |
required |
window
|
int
|
The int number of returns in each calculation window. |
14
|
column
|
str
|
The str name of the candle column to use from both, such as |
'close'
|
interval
|
str
|
The str candle interval, such as |
'day'
|
from_date
|
date | str | None
|
The first day of the range as a datetime.date or a |
None
|
to_date
|
date | str | None
|
The last day of the range as a datetime.date or a |
None
|
days
|
int | None
|
The int number of days to count back from today, or None when from_date and to_date are given. |
None
|
adjusted
|
bool
|
A bool that is True for prices adjusted for splits and bonuses. |
True
|
Returns:
| Type | Description |
|---|---|
DataFrame | None
|
A pandas.DataFrame of the matched candles with a |
Raises:
| Type | Description |
|---|---|
UnifiedBrokerInterfaceError
|
UBI refused the request or could not be reached. |
Examples:
Print the last five values of Infosys's 20-day correlation with NIFTY:
from tradingmachine.assets import equities
infosys = equities.Equity(exchange="nse", symbol="INFY")
nifty = equities.EquityIndex(exchange="nse", symbol="NIFTY")
frame = infosys.correlation_coefficient(
benchmark=nifty,
window=20,
days=180,
)
print(frame.set_index("datetime")["corr_20"].tail())
Average the 30-day correlation of TCS with Infosys over the last year, using a share as the benchmark:
from tradingmachine.assets import equities
tcs = equities.Equity(exchange="nse", symbol="TCS")
infosys = equities.Equity(exchange="nse", symbol="INFY")
frame = tcs.correlation_coefficient(
benchmark=infosys,
window=30,
days=365,
)
print(f"Average correlation: {frame['corr_30'].mean():.2f}")
Source code in src/tradingmachine/assets/analysis/statistic_functions.py
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | |
linear_regression
¶
linear_regression(window=14, column='close', interval='day', from_date=None, to_date=None, days=None, adjusted=True)
Adds the end value of a rolling linear regression line through one candle column.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
window
|
int
|
The int number of candles in each calculation window. |
14
|
column
|
str
|
The str name of the candle column to use, such as |
'close'
|
interval
|
str
|
The str candle interval, such as |
'day'
|
from_date
|
date | str | None
|
The first day of the range as a datetime.date or a |
None
|
to_date
|
date | str | None
|
The last day of the range as a datetime.date or a |
None
|
days
|
int | None
|
The int number of days to count back from today, or None when from_date and to_date are given. |
None
|
adjusted
|
bool
|
A bool that is True for prices adjusted for splits and bonuses. |
True
|
Returns:
| Type | Description |
|---|---|
DataFrame | None
|
A pandas.DataFrame of the candles with a |
Raises:
| Type | Description |
|---|---|
UnifiedBrokerInterfaceError
|
UBI refused the request or could not be reached. |
Examples:
Print the last five end values of Infosys's 14-day regression line:
from tradingmachine.assets import equities
infosys = equities.Equity(exchange="nse", symbol="INFY")
frame = infosys.linear_regression(days=90)
print(frame.set_index("datetime")["lin_regr_14"].tail())
Print how far NIFTY's close sits from its 20-day regression line:
from tradingmachine.assets import equities
nifty = equities.EquityIndex(exchange="nse", symbol="NIFTY")
frame = nifty.linear_regression(window=20, days=120)
gap = frame["close"].iloc[-1] - frame["lin_regr_20"].iloc[-1]
print(f"The close is {gap:.2f} points from the regression line")
Source code in src/tradingmachine/assets/analysis/statistic_functions.py
linear_regression_slope
¶
linear_regression_slope(window=14, column='close', interval='day', from_date=None, to_date=None, days=None, adjusted=True)
Adds the slope of a rolling linear regression line through one candle column.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
window
|
int
|
The int number of candles in each calculation window. |
14
|
column
|
str
|
The str name of the candle column to use, such as |
'close'
|
interval
|
str
|
The str candle interval, such as |
'day'
|
from_date
|
date | str | None
|
The first day of the range as a datetime.date or a |
None
|
to_date
|
date | str | None
|
The last day of the range as a datetime.date or a |
None
|
days
|
int | None
|
The int number of days to count back from today, or None when from_date and to_date are given. |
None
|
adjusted
|
bool
|
A bool that is True for prices adjusted for splits and bonuses. |
True
|
Returns:
| Type | Description |
|---|---|
DataFrame | None
|
A pandas.DataFrame of the candles with a |
Raises:
| Type | Description |
|---|---|
UnifiedBrokerInterfaceError
|
UBI refused the request or could not be reached. |
Examples:
Print the last five slopes of Infosys's 14-day regression line:
from tradingmachine.assets import equities
infosys = equities.Equity(exchange="nse", symbol="INFY")
frame = infosys.linear_regression_slope(days=90)
print(frame.set_index("datetime")["lin_regr_slope_14"].tail())
Say whether NIFTY's 50-day regression line is rising or falling:
from tradingmachine.assets import equities
nifty = equities.EquityIndex(exchange="nse", symbol="NIFTY")
frame = nifty.linear_regression_slope(window=50, days=180)
slope = frame["lin_regr_slope_50"].iloc[-1]
if slope > 0:
print(f"Rising by {slope:.2f} points a day")
else:
print(f"Falling by {-slope:.2f} points a day")
Source code in src/tradingmachine/assets/analysis/statistic_functions.py
linear_regression_intercept
¶
linear_regression_intercept(window=14, column='close', interval='day', from_date=None, to_date=None, days=None, adjusted=True)
Adds the intercept of a rolling linear regression line through one candle column.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
window
|
int
|
The int number of candles in each calculation window. |
14
|
column
|
str
|
The str name of the candle column to use, such as |
'close'
|
interval
|
str
|
The str candle interval, such as |
'day'
|
from_date
|
date | str | None
|
The first day of the range as a datetime.date or a |
None
|
to_date
|
date | str | None
|
The last day of the range as a datetime.date or a |
None
|
days
|
int | None
|
The int number of days to count back from today, or None when from_date and to_date are given. |
None
|
adjusted
|
bool
|
A bool that is True for prices adjusted for splits and bonuses. |
True
|
Returns:
| Type | Description |
|---|---|
DataFrame | None
|
A pandas.DataFrame of the candles with a |
Raises:
| Type | Description |
|---|---|
UnifiedBrokerInterfaceError
|
UBI refused the request or could not be reached. |
Examples:
Print the last five intercepts of Infosys's 14-day regression line:
from tradingmachine.assets import equities
infosys = equities.Equity(exchange="nse", symbol="INFY")
frame = infosys.linear_regression_intercept(days=90)
print(frame.set_index("datetime")["lin_regr_int_14"].tail())
Rebuild the line's end value from its intercept and slope and compare it with linear_regression:
from tradingmachine.assets import equities
infosys = equities.Equity(exchange="nse", symbol="INFY")
intercept_frame = infosys.linear_regression_intercept(days=90)
slope_frame = infosys.linear_regression_slope(days=90)
line_frame = infosys.linear_regression(days=90)
intercept = intercept_frame["lin_regr_int_14"].iloc[-1]
slope = slope_frame["lin_regr_slope_14"].iloc[-1]
print(f"Rebuilt end value {intercept + 13 * slope:.2f}")
print(f"linear_regression {line_frame['lin_regr_14'].iloc[-1]:.2f}")
Source code in src/tradingmachine/assets/analysis/statistic_functions.py
linear_regression_angle
¶
linear_regression_angle(window=14, column='close', interval='day', from_date=None, to_date=None, days=None, adjusted=True)
Adds the angle in degrees of a rolling linear regression line through one candle column.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
window
|
int
|
The int number of candles in each calculation window. |
14
|
column
|
str
|
The str name of the candle column to use, such as |
'close'
|
interval
|
str
|
The str candle interval, such as |
'day'
|
from_date
|
date | str | None
|
The first day of the range as a datetime.date or a |
None
|
to_date
|
date | str | None
|
The last day of the range as a datetime.date or a |
None
|
days
|
int | None
|
The int number of days to count back from today, or None when from_date and to_date are given. |
None
|
adjusted
|
bool
|
A bool that is True for prices adjusted for splits and bonuses. |
True
|
Returns:
| Type | Description |
|---|---|
DataFrame | None
|
A pandas.DataFrame of the candles with a |
Raises:
| Type | Description |
|---|---|
UnifiedBrokerInterfaceError
|
UBI refused the request or could not be reached. |
Examples:
Print the last five angles of Infosys's 14-day regression line:
from tradingmachine.assets import equities
infosys = equities.Equity(exchange="nse", symbol="INFY")
frame = infosys.linear_regression_angle(days=90)
print(frame.set_index("datetime")["lin_regr_angle_14"].tail())
Print the angle of NIFTY's 30-day regression line in degrees:
from tradingmachine.assets import equities
nifty = equities.EquityIndex(exchange="nse", symbol="NIFTY")
frame = nifty.linear_regression_angle(window=30, days=120)
print(f"{frame['lin_regr_angle_30'].iloc[-1]:.2f} degrees")
Source code in src/tradingmachine/assets/analysis/statistic_functions.py
standard_deviation
¶
standard_deviation(window=14, standard_deviations=1, column='close', interval='day', from_date=None, to_date=None, days=None, adjusted=True)
Adds the rolling standard deviation of one candle column.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
window
|
int
|
The int number of candles in each calculation window. |
14
|
standard_deviations
|
float
|
The float multiple of the standard deviation to report. |
1
|
column
|
str
|
The str name of the candle column to use, such as |
'close'
|
interval
|
str
|
The str candle interval, such as |
'day'
|
from_date
|
date | str | None
|
The first day of the range as a datetime.date or a |
None
|
to_date
|
date | str | None
|
The last day of the range as a datetime.date or a |
None
|
days
|
int | None
|
The int number of days to count back from today, or None when from_date and to_date are given. |
None
|
adjusted
|
bool
|
A bool that is True for prices adjusted for splits and bonuses. |
True
|
Returns:
| Type | Description |
|---|---|
DataFrame | None
|
A pandas.DataFrame of the candles with a |
Raises:
| Type | Description |
|---|---|
UnifiedBrokerInterfaceError
|
UBI refused the request or could not be reached. |
Examples:
Print the last five values of Infosys's 20-day standard deviation of the close:
from tradingmachine.assets import equities
infosys = equities.Equity(exchange="nse", symbol="INFY")
frame = infosys.standard_deviation(window=20, days=90)
print(frame.set_index("datetime")["std_dev_20"].tail())
Draw NIFTY's upper and lower bands two standard deviations from its 20-day average:
from tradingmachine.assets import equities
nifty = equities.EquityIndex(exchange="nse", symbol="NIFTY")
frame = nifty.standard_deviation(
window=20,
standard_deviations=2,
days=90,
)
middle = frame["close"].rolling(20).mean().iloc[-1]
width = frame["std_dev_20"].iloc[-1]
print(f"Upper {middle + width:.2f}, lower {middle - width:.2f}")
Source code in src/tradingmachine/assets/analysis/statistic_functions.py
variance
¶
variance(window=14, standard_deviations=1, column='close', interval='day', from_date=None, to_date=None, days=None, adjusted=True)
Adds the rolling variance of one candle column.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
window
|
int
|
The int number of candles in each calculation window. |
14
|
standard_deviations
|
float
|
The float multiple passed to TA-Lib, which its variance calculation does not use. |
1
|
column
|
str
|
The str name of the candle column to use, such as |
'close'
|
interval
|
str
|
The str candle interval, such as |
'day'
|
from_date
|
date | str | None
|
The first day of the range as a datetime.date or a |
None
|
to_date
|
date | str | None
|
The last day of the range as a datetime.date or a |
None
|
days
|
int | None
|
The int number of days to count back from today, or None when from_date and to_date are given. |
None
|
adjusted
|
bool
|
A bool that is True for prices adjusted for splits and bonuses. |
True
|
Returns:
| Type | Description |
|---|---|
DataFrame | None
|
A pandas.DataFrame of the candles with a |
Raises:
| Type | Description |
|---|---|
UnifiedBrokerInterfaceError
|
UBI refused the request or could not be reached. |
Examples:
Print the last five values of Infosys's 20-day variance of the close:
from tradingmachine.assets import equities
infosys = equities.Equity(exchange="nse", symbol="INFY")
frame = infosys.variance(window=20, days=90)
print(frame.set_index("datetime")["var_20"].tail())
Find the day in the first half of 2026 on which NIFTY's 10-day variance peaked:
from tradingmachine.assets import equities
nifty = equities.EquityIndex(exchange="nse", symbol="NIFTY")
frame = nifty.variance(
window=10,
from_date="2026-01-01",
to_date="2026-06-30",
)
peak_row = frame["var_10"].idxmax()
peak_day = frame.loc[peak_row, "datetime"].date()
print(peak_day, round(frame["var_10"].max(), 1))
Source code in src/tradingmachine/assets/analysis/statistic_functions.py
Example programs¶
Tabulate how closely NSE shares follow the NIFTY 50 index¶
Run it with .venv/bin/python examples/assets/analysis/statistic_functions/statistic_functions/market_sensitivity_table.py.
"""Tabulate how closely NSE shares follow the NIFTY 50 index.
The program reads a year of daily candles for each share and for the index, and prints each share's latest 60-day beta against NIFTY, its latest 60-day correlation with NIFTY's returns, and the average of that correlation over the year.
Typical usage example:
.venv/bin/python examples/assets/analysis/statistic_functions/statistic_functions/market_sensitivity_table.py
"""
from tradingmachine.assets import equities
class MarketSensitivityTable:
"""A table of each share's beta and correlation against the index.
Attributes:
benchmark: The tradingmachine.assets.equities.EquityIndex the shares are measured against.
symbols: A list of str NSE symbols of the shares that are measured.
window: The int number of candles in each beta and correlation window.
days: The int number of days of daily candles to read.
"""
def __init__(self, window: int = 60, days: int = 365):
"""Creates the table over five shares measured against NIFTY.
Args:
window: The int number of candles in each beta and correlation window.
days: The int number of days of daily candles to read.
Raises:
Nothing.
"""
self.benchmark = equities.EquityIndex(exchange="nse", symbol="NIFTY")
self.symbols = [
"INFY",
"TCS",
"HDFCBANK",
"RELIANCE",
"IDEA",
]
self.window = window
self.days = days
def describe(self, symbol: str) -> str:
"""Measures one share against the benchmark.
Args:
symbol: The str NSE symbol of the share.
Returns:
A str line with the share's latest beta, latest correlation and average correlation.
Raises:
tradingmachine.assets.exceptions.EquityError: UBI does not know the share.
tradingmachine.unified_broker_interface.exceptions.UnifiedBrokerInterfaceError: UBI could not be reached or refused the request.
"""
share = equities.Equity(exchange="nse", symbol=symbol)
beta_frame = share.beta(
benchmark=self.benchmark,
window=self.window,
days=self.days,
)
if beta_frame is None:
return f"{symbol:<10} no candles"
correlation_frame = share.correlation_coefficient(
benchmark=self.benchmark,
window=self.window,
days=self.days,
)
beta = beta_frame[f"beta_{self.window}"].iloc[-1]
correlation = correlation_frame[f"corr_{self.window}"]
return (
f"{symbol:<10} {beta:>6.2f} {correlation.iloc[-1]:>12.2f} "
f"{correlation.mean():>12.2f}"
)
def run(self) -> None:
"""Prints a header and one line per share.
Returns:
None.
Raises:
tradingmachine.assets.exceptions.EquityError: UBI does not know one of the shares.
tradingmachine.unified_broker_interface.exceptions.UnifiedBrokerInterfaceError: UBI could not be reached or refused the request.
"""
print(f"Measured against NIFTY over {self.window}-day windows:")
print(f"{'Symbol':<10} {'Beta':>6} {'Correlation':>12} {'Average':>12}")
for symbol in self.symbols:
print(self.describe(symbol))
if __name__ == "__main__":
MarketSensitivityTable().run()
Describe NIFTY's trend with a regression channel two standard deviations wide¶
Run it with .venv/bin/python examples/assets/analysis/statistic_functions/statistic_functions/regression_channel.py.
"""Describe NIFTY's trend with a regression channel two standard deviations wide.
The program reads half a year of NIFTY 50 daily candles, fits a 50-day rolling regression line through the close, and prints the line's latest value, slope, angle and intercept, a channel two standard deviations either side of the line, and where the latest close sits in that channel.
Typical usage example:
.venv/bin/python examples/assets/analysis/statistic_functions/statistic_functions/regression_channel.py
"""
from tradingmachine.assets import equities
class RegressionChannel:
"""A regression channel drawn around an index's close.
Attributes:
index: The tradingmachine.assets.equities.EquityIndex whose trend is described.
window: The int number of candles the regression line is fitted over.
days: The int number of days of daily candles to read.
"""
def __init__(self, window: int = 50, days: int = 180):
"""Creates the channel over the NIFTY 50 index.
Args:
window: The int number of candles the regression line is fitted over.
days: The int number of days of daily candles to read.
Raises:
Nothing.
"""
self.index = equities.EquityIndex(exchange="nse", symbol="NIFTY")
self.window = window
self.days = days
def latest(self, frame, column: str) -> float:
"""Gives the last value of one column.
Args:
frame: The pandas.DataFrame of candles with the column added.
column: The str name of the column.
Returns:
The float value in the column's last row.
Raises:
KeyError: The frame has no such column.
"""
return float(frame[column].iloc[-1])
def run(self) -> None:
"""Fits the line, draws the channel and prints the description.
Returns:
None.
Raises:
tradingmachine.unified_broker_interface.exceptions.UnifiedBrokerInterfaceError: UBI could not be reached or refused the request.
"""
line_frame = self.index.linear_regression(window=self.window, days=self.days)
if line_frame is None:
print("UBI has no NIFTY candles for the range.")
return
suffix = str(self.window)
line = self.latest(line_frame, "lin_regr_" + suffix)
slope = self.latest(
self.index.linear_regression_slope(window=self.window, days=self.days),
"lin_regr_slope_" + suffix,
)
angle = self.latest(
self.index.linear_regression_angle(window=self.window, days=self.days),
"lin_regr_angle_" + suffix,
)
intercept = self.latest(
self.index.linear_regression_intercept(
window=self.window,
days=self.days,
),
"lin_regr_int_" + suffix,
)
width = self.latest(
self.index.standard_deviation(
window=self.window,
standard_deviations=2,
days=self.days,
),
"std_dev_" + suffix,
)
close = self.latest(line_frame, "close")
print(f"Regression line {line:.2f}, intercept {intercept:.2f}")
print(f"Slope {slope:+.2f} points a day, angle {angle:+.2f} degrees")
print(f"Channel from {line - width:.2f} to {line + width:.2f}")
position = (close - (line - width)) / (2 * width)
print(f"Close {close:.2f} sits {position:.0%} of the way up the channel")
if __name__ == "__main__":
RegressionChannel().run()