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)