-
Notifications
You must be signed in to change notification settings - Fork 0
/
Functions.py
156 lines (128 loc) · 6.04 KB
/
Functions.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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
# Libraries
import numpy as np
import scipy.stats
import pandas as pd
# Computes the Sharpe Ratio rom a vector of returns
def sharpe_ratio(returns, risk_free_rate=0):
mean_return = np.mean(returns) - risk_free_rate
std_dev = np.std(returns, ddof=1)
sharpe_ratio = mean_return / std_dev
return sharpe_ratio
# Expected maximum Sharpe ratio
def expected_max_sharpe_ratio(mean_sharpe, var_sharpe, M):
gamma = 0.5772156649015328606 # Euler-Mascheroni constant
result = mean_sharpe + np.sqrt(var_sharpe) * ((1 - gamma) * scipy.stats.norm.ppf(1 - 1 / M) + gamma * scipy.stats.norm.ppf(1 - 1 / (M * np.exp(1))))
return result
# Computes the t-Statistic rom a vector of returns
def t_statistic(returns, risk_free_rate=0):
N = len(returns)
sr = sharpe_ratio(returns, risk_free_rate)
t_stat = sr * np.sqrt(N)
return t_stat
# Computes the multiple testing adjusted critical t-values by the Bonferroni method
def bonferroni_t_statistic(t_statistics, significance_level = 0.05):
num_tests = len(t_statistics)
adjusted_alpha = np.array([significance_level / num_tests for _ in range(num_tests)])
z_critical = scipy.stats.norm.ppf(1 - adjusted_alpha / 2)
results = pd.DataFrame({
'Test Number': np.arange(1, num_tests + 1),
't-Statistic': t_statistics,
'Necessary t-Statistic': z_critical,
'Necessary p-Value': adjusted_alpha,
'Success': (t_statistics > z_critical).astype(int)
})
return results
# Computes the multiple testing adjusted critical t-values by the Holm method
def holm_t_statistics(t_statistics, significance_level = 0.05):
num_tests = len(t_statistics)
sorted_indices = np.argsort(t_statistics)[::-1] # Sort in descending order, because we use t-Statistics
sorted_t_statistics = np.array(t_statistics)[sorted_indices]
adjusted_alpha = np.array([significance_level / (num_tests + 1 - k) for k in range(1, num_tests + 1)])
z_critical = scipy.stats.norm.ppf(1 - adjusted_alpha / 2)
results = pd.DataFrame({
'Test Number': sorted_indices + 1,
't-Statistic': sorted_t_statistics,
'Necessary t-Statistic': z_critical,
'Necessary p-Value': adjusted_alpha,
'Success': (sorted_t_statistics > z_critical).astype(int)
})
results = results.sort_values(by='Test Number').reset_index(drop=True)
return results
# Computes the multiple testing adjusted critical t-values by the BHY method
def bhy_t_statistics(t_statistics, significance_level = 0.05):
num_tests = len(t_statistics)
c_m = np.sum([1.0 / i for i in range(1, num_tests + 1)])
sorted_indices = np.argsort(t_statistics)[::-1] # Sort in descending order, because we use t-Statistics
sorted_t_statistics = np.array(t_statistics)[sorted_indices]
adjusted_alpha = np.array([k * significance_level / (num_tests * c_m) for k in range(1, num_tests + 1)])
z_critical = scipy.stats.norm.ppf(1 - adjusted_alpha / 2)
results = pd.DataFrame({
'Test Number': sorted_indices + 1,
't-Statistic': sorted_t_statistics,
'Necessary t-Statistic': z_critical,
'Necessary p-Value': adjusted_alpha,
'Success': (sorted_t_statistics > z_critical).astype(int)
})
results = results.sort_values(by='Test Number').reset_index(drop=True)
return results
# Bundles all functions to compute the adjusted critical t-values
def necessary_t_statistics(t_statistics, significance_level, method='bonferroni'):
if method == 'bonferroni':
return bonferroni_t_statistic(t_statistics, significance_level)
elif method == 'holm':
return holm_t_statistics(t_statistics, significance_level)
elif method == 'bhy':
return bhy_t_statistics(t_statistics, significance_level)
else:
raise ValueError("Method must be 'bonferroni', 'holm', or 'bhy'")
# Sharpe Ratio Haircut
def haircut_sharpe_ratio(returns, risk_free_rate, num_tests, k=1, method='bonferroni'):
N = len(returns)
t = t_statistic(returns, risk_free_rate)
p = 2 * scipy.stats.norm.sf(abs(t))
min_p_value = 1e-10
p = max(p, min_p_value)
if method == 'bonferroni':
p_adj = min(p * num_tests, 1)
elif method == 'holm':
p_adj = min(p * (num_tests + 1 - k), 1)
elif method == 'bhy':
c_m = np.sum([1.0 / i for i in range(1, num_tests + 1)])
p_adj = min(p * num_tests * c_m / k, 1)
else:
raise ValueError("Method must be 'bonferroni', 'holm', or 'bhy'")
t_adj = scipy.stats.norm.ppf(1 - p_adj / 2)
SR_adj = t_adj / np.sqrt(N)
return SR_adj
# Evaluate all strategies
def evaluate_strategies(returns_matrix, risk_free_rate=0):
num_strategies = returns_matrix.shape[1]
N = returns_matrix.shape[0]
original_sharpe_ratios = []
t_statistics = []
for i in range(num_strategies):
returns = returns_matrix[:, i]
sr = sharpe_ratio(returns, risk_free_rate)
t_stat = t_statistic(returns, risk_free_rate)
original_sharpe_ratios.append(sr)
t_statistics.append(t_stat)
# Sort indices based on t_statistics in descending order
sorted_indices = np.argsort(t_statistics)[::-1]
# Compute adjusted Sharpe Ratios
haircut_sharpe_ratios_bonferroni = []
haircut_sharpe_ratios_holm = []
haircut_sharpe_ratios_bhy = []
for k, idx in enumerate(sorted_indices):
returns = returns_matrix[:, idx]
haircut_sharpe_ratios_bonferroni.append(haircut_sharpe_ratio(returns, risk_free_rate, num_strategies, k=k+1, method='bonferroni'))
haircut_sharpe_ratios_holm.append(haircut_sharpe_ratio(returns, risk_free_rate, num_strategies, k=k+1, method='holm'))
haircut_sharpe_ratios_bhy.append(haircut_sharpe_ratio(returns, risk_free_rate, num_strategies, k=k+1, method='bhy'))
# Arrange the results in the sorted order
results = pd.DataFrame({
'Strategy': sorted_indices + 1,
'Original': np.array(original_sharpe_ratios)[sorted_indices],
'Bonferroni': haircut_sharpe_ratios_bonferroni,
'Holm': haircut_sharpe_ratios_holm,
'BHY': haircut_sharpe_ratios_bhy
})
return results