InĀ [11]:
import pandas as pd
import numpy as np
import yfinance as yf
import random
from matplotlib import pyplot as plt
import seaborn as sns
import warnings
import secrets
import plotly as plotly
import requests
import plotly.express as px
from tqdm import tqdm
import plotly.graph_objects as go
pd.set_option('plotting.backend', 'matplotlib')
plotly.offline.init_notebook_mode()
#warnings.simplefilter(action='ignore', category=FutureWarning
import plotly.io as pio
plotly_template = pio.templates["plotly_dark"]
plt.style.use('dark_background')
#sas
PORTFOLIOS
InĀ [12]:
TICKERS=["SWDA.MI","EIMI.MI","EQAC.MI","IUSN.DE"]
Ticker_names=[x.split(".", 1)[0] for x in TICKERS]
NUMBER_OF_SIMULATIONS=1000
STARTING_CAPITAL = 10000
YEARS_OF_SIMULATION=20
InĀ [13]:
YEARS_OF_SIMULATION=YEARS_OF_SIMULATION+1
Market_Days=253
df_Tickers={x: yf.download(x)["Adj Close"].pct_change(1).dropna() for x in TICKERS}
[*********************100%%**********************] 1 of 1 completed [*********************100%%**********************] 1 of 1 completed [*********************100%%**********************] 1 of 1 completed [*********************100%%**********************] 1 of 1 completed
InĀ [14]:
df_simulations={x: np.zeros((YEARS_OF_SIMULATION,NUMBER_OF_SIMULATIONS)) for x in TICKERS}
for TICKER in TICKERS:
df_local=df_simulations[TICKER]
df_local[0, :] = STARTING_CAPITAL
df_simulations[TICKER]=df_local
for x in tqdm(range(0,NUMBER_OF_SIMULATIONS)):
_=np.array((df_Tickers[TICKER].iloc[:]))
for i in range(1,YEARS_OF_SIMULATION):
sample=np.random.choice(_,Market_Days)+1
annual_change=np.prod(sample)
df_simulations[TICKER][i,x]=annual_change*df_simulations[TICKER][i-1,x]
df_simulations={x: pd.DataFrame(df_simulations[x]) for x in TICKERS}
df_simulations_1= df_simulations
100%|āāāāāāāāāā| 1000/1000 [00:00<00:00, 1899.41it/s] 100%|āāāāāāāāāā| 1000/1000 [00:00<00:00, 1805.08it/s] 100%|āāāāāāāāāā| 1000/1000 [00:00<00:00, 1829.84it/s] 100%|āāāāāāāāāā| 1000/1000 [00:00<00:00, 1899.42it/s]
InĀ [14]:
InĀ [15]:
quantile={x: list(df_simulations[x].iloc[YEARS_OF_SIMULATION-1].quantile([0.1,0.9])) for x in TICKERS}
print(quantile)
df_simulations_purged={x: 40 for x in TICKERS}
print(df_simulations_purged)
for TICKER in TICKERS:
df_local=df_simulations[TICKER]
df_simulations_purged[TICKER]=df_local[df_local.columns[ df_local.max() < quantile[TICKER][1]]]
df_simulations_purged[TICKER]=df_simulations_purged[TICKER][df_simulations_purged[TICKER].columns[ df_simulations_purged[TICKER].max() > quantile[TICKER][0]]]
{'SWDA.MI': [35202.58510425184, 223850.9804366963], 'EIMI.MI': [7559.108925374785, 68063.485286416], 'EQAC.MI': [46362.75815510128, 958788.8953439298], 'IUSN.DE': [12918.631151283731, 117885.18290522239]} {'SWDA.MI': 40, 'EIMI.MI': 40, 'EQAC.MI': 40, 'IUSN.DE': 40}
InĀ [16]:
for TICKER in TICKERS:
print(df_simulations_purged[TICKER].shape)
(21, 820) (21, 870) (21, 825) (21, 853)
InĀ [17]:
#for TICKER in TICKERS:
# df_simulations_purged[TICKER].plot(figsize=(16,8), title=f"Simulation of {TICKER} {NUMBER_OF_SIMULATIONS} portfolios", legend=False)
InĀ [18]:
for TICKER in TICKERS:
df_simulations_purged[TICKER].iloc[YEARS_OF_SIMULATION-1].plot.density(figsize=(16,8),fontsize=14, xlim=(-10000,900000),label=TICKER ,legend= True)
InĀ [19]:
df_statistics= {x: 0 for x in TICKERS}
for TICKER in TICKERS:
top_25 =[]
low_25 =[]
median=[]
for i in range(0,YEARS_OF_SIMULATION):
top_25.append(df_simulations[TICKER].iloc[i].quantile(0.75))
low_25.append(df_simulations[TICKER].iloc[i].quantile(0.25))
median.append(df_simulations[TICKER].iloc[i].median())
columns=["top 25%","median","bottom 25%"]
df_statistics[TICKER]=pd.DataFrame(list(zip(top_25,median,low_25)),columns=columns)
InĀ [20]:
#ax= df_statistics.plot(legend=None,logy=False,fontsize=20,figsize=(25,15),linewidth=4,color="black",title=f"Simulation of {NUMBER_OF_SIMULATIONS} portfolios")
pd.set_option('plotting.backend', 'plotly')
for TICKER in TICKERS:
fig = df_statistics[TICKER].plot(width=1600, height=800,title=f"Simulation of {NUMBER_OF_SIMULATIONS} {TICKER} portfolios",labels= {"index": "Years ", "value":"Total Capital"} ,template='plotly_dark')
fig.add_trace(go.Scatter(x=list(range(0,YEARS_OF_SIMULATION)),y=np.array(df_statistics[TICKER]["top 25%"]),fill='tonexty',mode='lines', line_color='blue' , fillcolor ="red", showlegend=False, hoverinfo="skip"))
fig.add_trace(go.Scatter(x=list(range(0,YEARS_OF_SIMULATION)),y=np.array(df_statistics[TICKER]["median"]),fill='tonexty',mode='lines', line_color='orange', fillcolor="green", showlegend=False, hoverinfo="skip"))
fig.show()
InĀ [21]:
df_simulations_at_profit={x:0 for x in TICKERS}
for TICKER in TICKERS:
ls_simulations_at_profit=[]
for i in range(YEARS_OF_SIMULATION):
c=len([1 for i in list(df_simulations[TICKER].iloc[i])if i > STARTING_CAPITAL])
ls_simulations_at_profit.append(c/NUMBER_OF_SIMULATIONS*100)
pd.set_option('plotting.backend', 'plotly')
df_simulations_at_profit[TICKER] = pd.DataFrame(ls_simulations_at_profit,columns=["Profit %"])
#refactor this cell cod to be all in une fig
fig=go.Figure()
for TICKER in TICKERS:
fig.add_trace(go.Scatter(x=list(range(0,YEARS_OF_SIMULATION)),y=np.array(df_simulations_at_profit[TICKER]["Profit %"]),mode='lines', name=TICKER, hovertemplate='Chance to be in profit: %{y:.2f}%'+ '<br>Period length in years: %{x}',line=dict(width=2)))
fig.update_layout(width=1600, height=800)
fig.update_layout(
xaxis_title="Period length in years",
yaxis_title="Chance to be in profit",
title="Minimum investment horizon",
yaxis_ticksuffix = '%'
)
fig.show()
InĀ [22]:
for TICKER in TICKERS:
print(f"The median return of {TICKER} is {round((((np.median(np.array(df_simulations[TICKER].iloc[YEARS_OF_SIMULATION-1,:]))/STARTING_CAPITAL)**(1/YEARS_OF_SIMULATION))-1)*100,2)}%")
The median return of SWDA.MI is 10.92% The median return of EIMI.MI is 3.95% The median return of EQAC.MI is 15.41% The median return of IUSN.DE is 6.68%
InĀ [23]:
def drawdown(a):
a= a.pct_change().dropna()
acc_max = np.maximum.accumulate(a)
return (a - acc_max).min()
median_Drawdown={x : [] for x in TICKERS}
for TICKER in TICKERS:
for s in range(NUMBER_OF_SIMULATIONS-1):
series=df_simulations[TICKER].iloc[:,s]
d=drawdown(series)
median_Drawdown[TICKER].append(d)
print(f"The median drawdown of {TICKER} is {round(np.median(median_Drawdown[TICKER])*100,2)}%")
The median drawdown of SWDA.MI is -62.54% The median drawdown of EIMI.MI is -70.63% The median drawdown of EQAC.MI is -111.1% The median drawdown of IUSN.DE is -71.52%
InĀ [23]:
InĀ [24]:
#TICKERS=["AAPL", "DB"]
df_joined=yf.download(TICKERS)["Adj Close"].pct_change(1).dropna()
df_joined.corr()
[*********************100%%**********************] 4 of 4 completed
C:\Users\dadoi\AppData\Local\Temp\ipykernel_25212\867487608.py:2: FutureWarning: The default fill_method='pad' in DataFrame.pct_change is deprecated and will be removed in a future version. Call ffill before calling pct_change to retain current behavior and silence this warning.
Out[24]:
EIMI.MI | EQAC.MI | IUSN.DE | SWDA.MI | |
---|---|---|---|---|
EIMI.MI | 1.000000 | 0.431788 | 0.736255 | 0.758584 |
EQAC.MI | 0.431788 | 1.000000 | 0.507665 | 0.608290 |
IUSN.DE | 0.736255 | 0.507665 | 1.000000 | 0.900140 |
SWDA.MI | 0.758584 | 0.608290 | 0.900140 | 1.000000 |
InĀ [25]:
rows=[]
for TICKER in TICKERS:
for TICKER_1 in TICKERS:
Mak_Score=round(np.median((np.array(df_joined[TICKER_1][(df_joined[TICKER_1]>0) & (df_joined[TICKER]>0) ])+1e-10)/(np.array(df_joined[TICKER][(df_joined[TICKER_1]>0) & (df_joined[TICKER]>0) ])+1e-10)),2) - round(np.median((np.array(df_joined[TICKER_1][(df_joined[TICKER_1]<0) & (df_joined[TICKER]<0) ])+1e-10)/(np.array(df_joined[TICKER][(df_joined[TICKER_1]<0) & (df_joined[TICKER]<0) ])+1e-10)))
rows.append([TICKER,TICKER_1,Mak_Score])
df_Mak=pd.DataFrame(data=rows, columns=["tick1","tick2","Mak coefficent"])
df_Mak
Out[25]:
tick1 | tick2 | Mak coefficent | |
---|---|---|---|
0 | SWDA.MI | SWDA.MI | 0.00 |
1 | SWDA.MI | EIMI.MI | 0.13 |
2 | SWDA.MI | EQAC.MI | 0.44 |
3 | SWDA.MI | IUSN.DE | 0.11 |
4 | EIMI.MI | SWDA.MI | -0.12 |
5 | EIMI.MI | EIMI.MI | 0.00 |
6 | EIMI.MI | EQAC.MI | 0.21 |
7 | EIMI.MI | IUSN.DE | -0.05 |
8 | EQAC.MI | SWDA.MI | -0.31 |
9 | EQAC.MI | EIMI.MI | -0.17 |
10 | EQAC.MI | EQAC.MI | 0.00 |
11 | EQAC.MI | IUSN.DE | -0.19 |
12 | IUSN.DE | SWDA.MI | -0.10 |
13 | IUSN.DE | EIMI.MI | 0.06 |
14 | IUSN.DE | EQAC.MI | 0.23 |
15 | IUSN.DE | IUSN.DE | 0.00 |
/
PORTOFLIO OPTIMISATION
InĀ [26]:
DAYS_OF_CHANGE=30
df_simulations_yr_={x: np.zeros((YEARS_OF_SIMULATION,len(TICKERS))) for x in TICKERS}
df_joined=yf.download(TICKERS)["Adj Close"].pct_change(DAYS_OF_CHANGE).dropna()
df_joined=df_joined[TICKERS]
df_joined_3=df_joined
f_matrix= df_joined.to_numpy()
f_matrix
[*********************100%%**********************] 4 of 4 completed
C:\Users\dadoi\AppData\Local\Temp\ipykernel_25212\2718229752.py:5: FutureWarning: The default fill_method='pad' in DataFrame.pct_change is deprecated and will be removed in a future version. Call ffill before calling pct_change to retain current behavior and silence this warning.
Out[26]:
array([[-0.03919764, -0.02063385, -0.07577486, -0.062459 ], [-0.03104259, -0.02255005, -0.04746221, -0.05171655], [-0.02853079, -0.03532606, -0.06120931, -0.047214 ], ..., [ 0.0569086 , 0.02853618, 0.0759298 , 0.00302597], [ 0.05690965, 0.03023871, 0.08008968, -0.00710116], [ 0.06114712, 0.02434294, 0.08917774, -0.005356 ]])
InĀ [27]:
#maxe a list of al possible combinations without repetition of all natural numbers from 0 to 10 where the sum of the components is always 10
import numpy as np
def all_possible_combinations_Mak(n,k):
# Create a 3D grid of all possible combinations of [i, j, k]
grid = np.indices((n + 1 for _ in range(k)))
# Sum along the last axis to check if i + j + k == n
sum_grid = grid.sum(axis=0)
# Find indices where the sum equals n
valid_indices = np.argwhere(sum_grid == n)
valid_indices=np.divide(valid_indices,n)
return valid_indices
i=10
q=7000
a=all_possible_combinations_Mak(i,len(TICKERS))
while True:
if len(a)<q:
a=all_possible_combinations_Mak(i,len(TICKERS))
i=round(i*(np.log2(q/len(a))))
else:
break
InĀ [28]:
i=0
return_matrix=np.zeros((len(f_matrix),len(a)))
for row in tqdm(a):
j=0
for pair in f_matrix:
#return_matrix[j,i]=np.sum([pair[x]*row[x] for x in range(0,len(TICKERS))])
return_matrix[j,i]=np.dot(pair,row)
j=j+1
i=i+1
100%|āāāāāāāāāā| 18424/18424 [00:33<00:00, 549.16it/s]
InĀ [29]:
plane_of_returns=[]
i=0
j=0
Rf=0.04
print(Rf)
#return_matrix=np.flip(return_matrix)
for x in return_matrix.T:
median=np.median(x)*(Market_Days/DAYS_OF_CHANGE)
values=a[i]
mean=np.mean(x)*(Market_Days/DAYS_OF_CHANGE)
std=np.std(x)*(Market_Days/DAYS_OF_CHANGE)
sharpe_ratio=(mean-Rf)/std
plane_of_returns.append(np.append(values ,[median ,mean,std,sharpe_ratio]))
i=i+1
plane_of_returns=np.array(plane_of_returns)
df_plane=pd.DataFrame(plane_of_returns,columns=TICKERS+["Median","Mean","Std","Sharpe_ratio"])
df_plane
0.04
Out[29]:
SWDA.MI | EIMI.MI | EQAC.MI | IUSN.DE | Median | Mean | Std | Sharpe_ratio | |
---|---|---|---|---|---|---|---|---|
0 | 0.000000 | 0.000000 | 0.000000 | 1.000000 | 0.128205 | 0.091670 | 0.605354 | 0.085356 |
1 | 0.000000 | 0.000000 | 0.021739 | 0.978261 | 0.129839 | 0.093502 | 0.602191 | 0.088845 |
2 | 0.000000 | 0.000000 | 0.043478 | 0.956522 | 0.134460 | 0.095333 | 0.599172 | 0.092349 |
3 | 0.000000 | 0.000000 | 0.065217 | 0.934783 | 0.136246 | 0.097164 | 0.596299 | 0.095865 |
4 | 0.000000 | 0.000000 | 0.086957 | 0.913043 | 0.137404 | 0.098995 | 0.593575 | 0.099390 |
... | ... | ... | ... | ... | ... | ... | ... | ... |
18419 | 0.956522 | 0.043478 | 0.000000 | 0.000000 | 0.179834 | 0.119192 | 0.478014 | 0.165669 |
18420 | 0.978261 | 0.000000 | 0.000000 | 0.021739 | 0.187692 | 0.121377 | 0.484065 | 0.168112 |
18421 | 0.978261 | 0.000000 | 0.021739 | 0.000000 | 0.184503 | 0.123208 | 0.483351 | 0.172149 |
18422 | 0.978261 | 0.021739 | 0.000000 | 0.000000 | 0.183067 | 0.120615 | 0.480060 | 0.167926 |
18423 | 1.000000 | 0.000000 | 0.000000 | 0.000000 | 0.187043 | 0.122037 | 0.482196 | 0.170133 |
18424 rows Ć 8 columns
āSharpe Ratio=Ļpā RpāāRfāā
InĀ [30]:
hover=""
i=0
for TICKER in TICKERS:
if i %2 == 0:
hover=hover+'<b>'+TICKER+': %{customdata['+str(i)+']:.2f}%</b>'
else:
hover=hover+'<br><b>'+TICKER+': %{customdata['+str(i)+']:.2f}%</b></br>'
i=i+1
hover=hover+'<br><b>Standard Deviation: %{x:.3f}</b></br> '+ '<b>Mean: %{y:.3f}%</b>'+ '<br><b>Sharpe Ratio %{text:.4f}</b></br>'
fig=go.Figure()
fig.add_trace(go.Scatter(x=df_plane["Std"], y=df_plane["Mean"]*100, mode='markers',marker=dict(size=10,color=df_plane["Sharpe_ratio"],colorscale='Viridis',showscale=True),hovertemplate =hover
,customdata=np.stack((df_plane[TICKER]*100 for TICKER in TICKERS), axis=-1),text=df_plane["Sharpe_ratio"]))
fig.update_layout(width=1600, height=800)
fig.update_layout(
hoverlabel=dict(
bgcolor="white",
font_size=16
),
)
fig.add_hline(y=Rf*100, line_width=3, line_dash="dash", line_color="red")
#labbel the axis and add a title and to mean add the % sign
fig.update_layout(
xaxis_title="Standard Deviation",
yaxis_title="Mean Return %",
title="Efficient Frontier",
yaxis_ticksuffix = '%'
)
#MAx sharpe ratio, max mean and min std points
max_sharpe_ratio=df_plane.iloc[[df_plane["Mean"].idxmax(),df_plane["Std"].idxmin(),df_plane["Sharpe_ratio"].idxmax()]]
fig.add_trace(go.Scatter(x=max_sharpe_ratio["Std"], y=max_sharpe_ratio["Mean"]*100, mode='markers',marker=dict(size=20,color=["red","blue","green"]),hovertemplate= hover, customdata=np.stack((max_sharpe_ratio[TICKER]*100 for TICKER in TICKERS), axis=-1) ,text=max_sharpe_ratio["Sharpe_ratio"]))
fig.add_annotation(x=max_sharpe_ratio["Std"].iloc[0], y=max_sharpe_ratio["Mean"].iloc[0]*100*1.005, text="Max Mean ", showarrow=False, yshift=10)
fig.add_annotation(x=max_sharpe_ratio["Std"].iloc[1], y=max_sharpe_ratio["Mean"].iloc[1]*100*1.005, text="Min Std", showarrow=False, yshift=10)
fig.add_annotation(x=max_sharpe_ratio["Std"].iloc[2], y=max_sharpe_ratio["Mean"].iloc[2]*100*1.005, text="Max Sharpe Ratio", showarrow=False, yshift=10)
fig.add_trace(go.Scatter(x=[0.483350], y=[0.117129*100], marker=dict(size=20,color="violet"),))
fig.add_annotation(x=np.min(df_plane["Std"]), y=Rf*100, text="Risk Free Rate", showarrow=False, yshift=10)
fig.show()
C:\Users\dadoi\anaconda3\Lib\site-packages\IPython\core\interactiveshell.py:3466: FutureWarning: arrays to stack must be passed as a "sequence" type such as list or tuple. Support for non-sequence iterables such as generators is deprecated as of NumPy 1.16 and will raise an error in the future. C:\Users\dadoi\anaconda3\Lib\site-packages\IPython\core\interactiveshell.py:3466: FutureWarning: arrays to stack must be passed as a "sequence" type such as list or tuple. Support for non-sequence iterables such as generators is deprecated as of NumPy 1.16 and will raise an error in the future.
InĀ [31]:
!jupyter nbconvert --to html Portfolio1.ipynb --HTMLExporter.theme=dark