Script 3 — Ex-ante benchmark
The rolling statistics over the ex-ante stocking index series, from which the ex-ante benchmark is derived.
The listing below is vm0047_get_ex_ante_PBt_v2.py: 68 lines, the script delivered earlier with the change of Table 19, marked v2 in the code. It is read from the file at render time, so the document and the script cannot drift apart.
⬇ Download vm0047_get_ex_ante_PBt_v2.py
#(c) Terra Global Capital CONFIDENTIAL
import pandas as pd
import numpy as np
from scipy.stats import linregress
import os # v2: paths relative to the update folder
def calculate_rolling_stats(df, window_size=10):
# Find columns that start with 'SI'
si_columns = [col for col in df.columns if col.startswith('SI')]
# Initialize dictionaries to store results
slopes = pd.DataFrame(index=df.index)
std_errs = pd.DataFrame(index=df.index)
p_vals = pd.DataFrame(index=df.index)
# For each SI column
for col in si_columns:
slopes[col] = np.nan
std_errs[col] = np.nan
p_vals[col] = np.nan
# For each row
for i in range(len(df)):
# Get start index for window
start_idx = max(0, i - window_size + 1)
# Get values for regression
y = df[col].iloc[start_idx:i+1]
x = np.arange(len(y))
# Only calculate if we have at least 2 points
if len(y) >= 2:
# Calculate regression
slope, intercept, r_value, p_value, std_err = linregress(x, y)
# v2: a constant series (the flat control) is a line with slope 0 known exactly: std_err 0, p 1;
# linregress returns NaN for both, which left z undefined (the delivered input avoided it by a
# typed control value at t = 1)
if np.ptp(y.values) == 0:
slope, std_err, p_value = 0.0, 0.0, 1.0
# Store results
slopes.loc[i, col] = slope
std_errs.loc[i, col] = std_err
p_vals.loc[i, col] = p_value
return slopes, std_errs, p_vals
def calculate_z_scores(slopes, std_errs, control_name='SI control'):
# find all columns other than control_name
z_scores = pd.DataFrame(index=slopes.index)
for col in slopes.columns:
if col != control_name:
z_scores[col + '_z'] = (slopes[col] - slopes[control_name]) / np.sqrt(std_errs[col]**2 + std_errs[control_name]**2)
return z_scores
# v2: input and output inside the update folder (the parent of PB Codes)
update_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
df = pd.read_csv(os.path.join(update_dir, 'Data_for_exante_v2.csv'))
slopes, std_errs, p_vals = calculate_rolling_stats(df)
z_scores = calculate_z_scores(slopes, std_errs)
# append slopes, std_errs, p_vals to df, after editing the column names
slopes.columns = [col + '_slope' for col in slopes.columns]
std_errs.columns = [col + '_std_err' for col in std_errs.columns]
p_vals.columns = [col + '_p_val' for col in p_vals.columns]
df = pd.concat([df, slopes, std_errs, p_vals, z_scores], axis=1)
# save as csv
df.to_csv(os.path.join(update_dir, 'run_outputs', 'ex_ante_for_PB2.csv'), index=False)