-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathportfolio_rebalancing.py
157 lines (132 loc) · 4.33 KB
/
portfolio_rebalancing.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
157
# =============================================================================
# Backtesting strategy - I : Monthly portfolio rebalancing
# Author : Daniel Bachs Lobo
# =============================================================================
import numpy as np
import pandas as pd
import yfinance as yf
import datetime as dt
import copy
import matplotlib.pyplot as plt
def CAGR(DF):
"function to calculate the Cumulative Annual Growth Rate of a trading strategy"
df = DF.copy()
df["cum_return"] = (1 + df["mon_ret"]).cumprod()
n = len(df) / 12
CAGR = (df["cum_return"].tolist()[-1]) ** (1 / n) - 1
return CAGR
def volatility(DF):
"function to calculate annualized volatility of a trading strategy"
df = DF.copy()
vol = df["mon_ret"].std() * np.sqrt(12)
return vol
def sharpe(DF, rf):
"function to calculate sharpe ratio ; rf is the risk free rate"
df = DF.copy()
sr = (CAGR(df) - rf) / volatility(df)
return sr
def max_dd(DF):
"function to calculate max drawdown"
df = DF.copy()
df["cum_return"] = (1 + df["mon_ret"]).cumprod()
df["cum_roll_max"] = df["cum_return"].cummax()
df["drawdown"] = df["cum_roll_max"] - df["cum_return"]
df["drawdown_pct"] = df["drawdown"] / df["cum_roll_max"]
max_dd = df["drawdown_pct"].max()
return max_dd
# Download historical data (monthly) for DJI constituent stocks
tickers = [
"MMM",
"AXP",
"T",
"BA",
"CAT",
"CSCO",
"KO",
"XOM",
"GE",
"GS",
"HD",
"IBM",
"INTC",
"JNJ",
"JPM",
"MCD",
"MRK",
"MSFT",
"NKE",
"PFE",
"PG",
"TRV",
"UNH",
"VZ",
"V",
"WMT",
"DIS",
]
ohlc_mon = {} # directory with ohlc value for each stock
start = dt.datetime.today() - dt.timedelta(3650)
end = dt.datetime.today()
# looping over tickers and creating a dataframe with close prices
for ticker in tickers:
ohlc_mon[ticker] = yf.download(ticker, start, end, interval="1mo")
ohlc_mon[ticker].dropna(inplace=True, how="all")
tickers = (
ohlc_mon.keys()
) # redefine tickers variable after removing any tickers with corrupted data
################################Backtesting####################################
# calculating monthly return for each stock and consolidating return info by stock in a separate dataframe
ohlc_dict = copy.deepcopy(ohlc_mon)
return_df = pd.DataFrame()
for ticker in tickers:
print("calculating monthly return for ", ticker)
ohlc_dict[ticker]["mon_ret"] = ohlc_dict[ticker]["Adj Close"].pct_change()
return_df[ticker] = ohlc_dict[ticker]["mon_ret"]
return_df.dropna(inplace=True)
# function to calculate portfolio return iteratively
def pflio(DF, m, x):
"""Returns cumulative portfolio return
DF = dataframe with monthly return info for all stocks
m = number of stock in the portfolio
x = number of underperforming stocks to be removed from portfolio monthly"""
df = DF.copy()
portfolio = []
monthly_ret = [0]
for i in range(len(df)):
if len(portfolio) > 0:
monthly_ret.append(df[portfolio].iloc[i, :].mean())
bad_stocks = (
df[portfolio]
.iloc[i, :]
.sort_values(ascending=True)[:x]
.index.values.tolist()
)
portfolio = [t for t in portfolio if t not in bad_stocks]
fill = m - len(portfolio)
new_picks = (
df.iloc[i, :].sort_values(ascending=False)[:fill].index.values.tolist()
)
portfolio = portfolio + new_picks
print(portfolio)
monthly_ret_df = pd.DataFrame(np.array(monthly_ret), columns=["mon_ret"])
return monthly_ret_df
# calculating overall strategy's KPIs
CAGR(pflio(return_df, 6, 3))
sharpe(pflio(return_df, 6, 3), 0.025)
max_dd(pflio(return_df, 6, 3))
# calculating KPIs for Index buy and hold strategy over the same period
DJI = yf.download(
"^DJI", dt.date.today() - dt.timedelta(3650), dt.date.today(), interval="1mo"
)
DJI["mon_ret"] = DJI["Adj Close"].pct_change().fillna(0)
CAGR(DJI)
sharpe(DJI, 0.025)
max_dd(DJI)
# visualization
fig, ax = plt.subplots()
plt.plot((1 + pflio(return_df, 6, 3)).cumprod())
plt.plot((1 + DJI["mon_ret"].reset_index(drop=True)).cumprod())
plt.title("Index Return vs Strategy Return")
plt.ylabel("cumulative return")
plt.xlabel("months")
ax.legend(["Strategy Return", "Index Return"])