Simulering av allvädersportfölj

Jag tänkte ge mig i kast med att bygga något eget verktyg för att simulera allvädersportföljer. Jag vill inte lägga några pengar på det och vill ha full kontroll, så att använda något kommersiellt verktyg känns inte som en framkomlig väg för mig.

Min första tanke är att göra något enklare hack i Excel (motsv), Python eller liknande, för att sedan bygga på det vartefter med olika finesser. Om möjligt återanvända färdig kod/mallar.

Kanske finns det fler med samma intresse som vill vara med på resan, antingen som åskådare, som bollplank, eller att mer aktivt bidra till utvecklingen. En idé jag har är att, om intresset är tillräckligt, man kanske skulle kunna göra det till ett open source-projekt som fler är med och utvecklar och kan ta del av.

Tips och idéer kring detta emottages tacksamt. Jag tänkte använda tråden för diskussion, information om progress mm. map. detta hobbyprojekt.

5 gillningar

Steg 1. Hämta hem testdata.

Jag hittar historisk data för aktier (Storebrand Global All Countries), guld (XAU/SEK), och råvaror (EN4C) på Investing.com, vilket jag laddat ned. Dock verkar inte obligationsfonden som @Zino använder i grundportföljen (Captor Iris) finnas på Investing. Den finns på Yahoo Finance, men därifrån verkar det inte gå att ladda ned hur som helst.

Är det någon som vet om det funkar att ladda ned historisk data från Yahoo Finance om man har ett Yahoo-konto? Är det en betaltjänst? Finns det någon alternativ datakälla för Captor Iris?

Roligt initiativ, ska bli kul att följa!

Innan du uppfinner hjulet på nytt - har du laborerat i testfol.io (gratis) och portfoliovisualizer (gratis 14 dagar)? På PV har man möjlighet att ladda upp egna dataserier. Det största jag saknar på PV är möjligheten att sätta separata ombalanseringsspann för varje fond, man kan endast sätta ett värde för alla fonder.

Jag har själv laddat ned data via Avanzas API, se detta inlägg:

En annan bra källa jag använt för svenska fonder är Handelsbankens hemsida, där man gratis kan ladda ned historiska fondkurser:

1 gillning

Tack Zino!

Jag har inte labbat i testfol eller pf-visualizer, men ska absolut kika på det.

Jag försökte komma åt historiska fondkurser via Handelsbankens sida, men lyckades inte. Blir det lättare om man är kund där och inloggad, eller ska det funka ändå?

Om du gör det (och inte lägger det online), se om du kan göra det som statiska html/javascript-filer som man kan ta ner och köra lokalt direkt i sin webläsare. Då slipper man installera någonting eller köra någon exe, annat än att det körs javascript i webläsaren. Har man statiska filer så här så är det också lätt att bara hosta filerna på en statisk webhost, vilket man ibland kan göra helt gratis.

Jag har funderat i de banorna, men det har aldrig blivit av att jag byggt det.

1 gillning

Tack, det låter som ett intressant koncept att testa. :pray:

1 gillning

Det har inte blivit så mycket gjort hittills på detta, men ikväll fastnade jag lite vid datorn och började nörda ned mig så smått i portföljsimulering mha. ChatGPT, Python, Google Colab och Yahoo Finance. Inga genombrott än, men intressant att se vad det finns för potential med verktygen. :nerd_face:

Tillägg: Den där plotten ovan var kanske ett dåligt exempel. Men plotten nedan visar hur enkelt det är att ladda hem kurser från Yahoo Finance. Sen är det bara fantasin som sätter gränser för hur man vill manipulera datat.

2 gillningar

Jag har fördrivit tiden ytterligare några timmar med ChatGPT, Python, Google Colab och Yahoo Finance. Jag ville bl.a. plotta MSCI ACWI samt BTC, både utvecklingen procentuellt i USD och i SEK, samt dessutom USDSEK-utvecklingen i samma diagram.

Inte helt lätt att få till det när jag inte är van att använda Python. Standardversionen av ChatGPT är lite som att ha en full kodguru med som hjälpreda. Den kommer med en massa förslag på kod, men det är oftast något småfel. När den rättar ett fel, så skapar den två andra. Men bit för bit kommer jag väl in mer och mer i Python och blir bättre på att felsöka och rätta koden själv.

Av någon anledning lyckades vi inte få det att funka att hämta hem USDSEK från Yahoo Finance, så jag fick ägna några timmar åt att försöka hämta den kursen från Riksbankens API. Det var inte helt enkelt, men gick till slut. Nu är bara problemet att kombinera dessa plottar i en enda graf, men det får bli en senare uppgift. Är för trött nu.

2 gillningar

Python library yfinance är ganska bra för portfölj analys eftersom har alla data som man kan lada lätt med API med tickers. Den första är att hitta fonders ticker som vill simulera på yahoo finance av varje fond. För den enkel allvädersportfölj:

  • MSCI ACWI, 30%
  • Råvaror EN4C, 20%
  • iShares Physical Gold ETC, 30%
  • Captor Iris Bond, 20%

man kan simulera med tickers som:

# Tickers Python yfinance API
tickers = {
    'Amundi Prime All World': 'WEBJ.DE',   # Global index fond all countries, no ESG
     'RavarorEN4C': 'EN4C.DE',             # Xetra commodities
     'iShares Physical Gold': 'IGLN.L',      # iShares Physical Gold ETC 
    'CaptorIrisBond': 'AGGH.AS',          # Global obligations-ETF
   
}
2 gillningar

Jo, jag använder Yahoo Finance, specifikt Python-biblioteket yfinance. Tyvärr fick jag som sagt problem då jag skulle ladda hem USDSEK, så då fick jag försöka ta den via Riksbankens API istället. Efter mycket trial and error så lyckades jag till sist ladda hem USDSEK från Riksbanken.

import yfinance as yf
import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime, timedelta

# Datumintervall – senaste 365 dagar
end_date = datetime.today()
start_date = end_date - timedelta(days=365)

# Hämta data med auto_adjust=False för tydlighet
btc = yf.download("BTC-USD", start=start_date, end=end_date, auto_adjust=False)
acwi = yf.download("ACWI", start=start_date, end=end_date, auto_adjust=False)

Tillägg: Nu har jag även lyckats ladda hem USDSEK med yfinance, samt plotta USDSEK i samma graf som BTC-USC och MSCI ACWI. Det går lite lättare när man är någorlunda utvilad på morgonen, istället för att försöka sitta och trilskas till sent in på nätterna.

usdsek = yf.download("SEK=X", start=start_date, end=end_date, auto_adjust=False) # USD/SEK

Tillägg 2: Så där ja. Nu börjar det likna nåt.

Nästa grej är att jag vill beräkna volatiliteten för de fem olika kurvorna, samt göra en volatilitetsviktning mellan Bitcoin i SEK samt MSCI ACWI i SEK.

Tillägg 3: Nu även med volatilitet.

Nedan är Python-koden, för den som är intresserad av vad som händer “under huven”, eller för den som vill bygga vidare på koden.

källkod
import yfinance as yf
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from datetime import datetime, timedelta

# Datumintervall – senaste 365 dagar
end_date = datetime.today()
start_date = end_date - timedelta(days=365)


# Hämta data: BTC i USD, ACWI i USD, och USD/SEK-växelkurs
btc = yf.download("BTC-USD", start=start_date, end=end_date, auto_adjust=False)
print("BTC-USD loaded:", not btc.empty)

acwi = yf.download("ACWI", start=start_date, end=end_date, auto_adjust=False)
print("ACWI loaded:", not acwi.empty)

usdsek = yf.download("SEK=X", start=start_date, end=end_date, auto_adjust=False)  # USD/SEK
print("USD/SEK loaded:", not usdsek.empty)


# Säker extraktion av 'Close' även om vi får en DataFrame med MultiIndex (efter nya yfinance-versioner)
if isinstance(btc.columns, pd.MultiIndex):
    btc_close = btc.loc[:, ("Close", "BTC-USD")]
else:
    btc_close = btc["Close"]

if isinstance(acwi.columns, pd.MultiIndex):
    acwi_close = acwi.loc[:, ("Close", "ACWI")]
else:
    acwi_close = acwi["Close"]

if isinstance(usdsek.columns, pd.MultiIndex):
    usdsek_close = usdsek.loc[:, ("Close", "SEK=X")]
else:
    usdsek_close = usdsek["Close"]


# Byt namn på serierna
btc_close.name = "BTC"
acwi_close.name = "ACWI"
usdsek_close.name = "USDSEK"

# Kombinera dem i en gemensam DataFrame
df = pd.concat([btc_close, acwi_close, usdsek_close], axis=1)


# Ta bort alla rader där någon av dem saknas (t.ex. helgdagar eller stängd börs)
df.dropna(inplace=True)

# Kontroll om DataFrame blev tom efter sammanslagningen
if df.empty:
    raise ValueError("Inga gemensamma datum – datan kan vara tom eller sakna överlapp.")

# Indexera utvecklingen (börja från 100)
df["BTC_pct"] = (df["BTC"] / df["BTC"].iloc[0]) * 100
df["ACWI_pct"] = (df["ACWI"] / df["ACWI"].iloc[0]) * 100
df["USDSEK_pct"] = (df["USDSEK"] / df["USDSEK"].iloc[0]) * 100

# Omräkna BTC och ACWI till SEK
df["BTC_SEK"] = df["BTC"] * df["USDSEK"]
df["ACWI_SEK"] = df["ACWI"] * df["USDSEK"]

# Indexera även utveckling i SEK
df["BTC_SEK_pct"] = (df["BTC_SEK"] / df["BTC_SEK"].iloc[0]) * 100
df["ACWI_SEK_pct"] = (df["ACWI_SEK"] / df["ACWI_SEK"].iloc[0]) * 100


# Beräkna dagliga logaritmiska avkastningar
log_returns = np.log(df[["BTC", "ACWI", "USDSEK", "BTC_SEK", "ACWI_SEK"]] / df[["BTC", "ACWI", "USDSEK", "BTC_SEK", "ACWI_SEK"]].shift(1))

# Beräkna årlig volatilitet (standardavvikelse * sqrt(252))
volatility = log_returns.std() * np.sqrt(252)

# Konvertera till procent, t.ex. 0.25 → 25.0%
volatility_pct = (volatility * 100).round(2)


# Plot
plt.figure(figsize=(12, 6))
plt.plot(df.index, df["BTC_pct"], label="Bitcoin (BTC) i USD", color="orange")
plt.plot(df.index, df["ACWI_pct"], label="MSCI ACWI (ACWI) i USD", color="blue")
plt.plot(df.index, df["USDSEK_pct"], label="USD/SEK", color="green", linestyle="--")
plt.plot(df.index, df["BTC_SEK_pct"], label="Bitcoin i SEK", color="red", linestyle=":")
plt.plot(df.index, df["ACWI_SEK_pct"], label="MSCI ACWI i SEK", color="purple", linestyle=":")


plt.title("Indexerad utveckling och volatilitet senaste året")
plt.xlabel("Datum")
plt.ylabel("Indexerad utveckling (start = 100)")
plt.legend()
plt.grid(True)
plt.tight_layout()

# Lägg till textetiketter i slutet av varje kurva med volatilitet (%)
last_date = df.index[-1]
offset = 1  # vertikal justering

plt.text(last_date, df["BTC_pct"].iloc[-1] + offset, f"Vol: {volatility_pct['BTC']}%", color="orange")
plt.text(last_date, df["ACWI_pct"].iloc[-1] + offset, f"Vol: {volatility_pct['ACWI']}%", color="blue")
plt.text(last_date, df["USDSEK_pct"].iloc[-1] + offset, f"Vol: {volatility_pct['USDSEK']}%", color="green")
plt.text(last_date, df["BTC_SEK_pct"].iloc[-1] + offset, f"Vol: {volatility_pct['BTC_SEK']}%", color="red")
plt.text(last_date, df["ACWI_SEK_pct"].iloc[-1] + offset, f"Vol: {volatility_pct['ACWI_SEK']}%", color="purple")


plt.show()
3 gillningar

Saker man roar sig med en lördagkväll, i brist på bättre. :crazy_face:

1 gillning

Nu behöver du bara uppfinna tidsmaskinen :stuck_out_tongue_winking_eye:

1 gillning

Jag jobbar på det, men det senaste experimentet med den nya generationens fluxkondensator gick inte riktigt enligt plan. :nerd_face:

1 gillning

Vilken software vertyg är den? eller så har du implementerat det själv med Python

Nej nej, det är inte jag som har implementerat den. Portfolio Visualizer finns på nätet. En befintlig produkt. Jag bara labbar lite med den för att få lite grepp om ombalansering, samt vilken funktionalitet som erbjuds av befintliga produkter.

https://www.portfoliovisualizer.com/backtest-asset-class-allocation

1 gillning

Att det var med “region” i menyerna fick mig att misstänka att det inte var eget.

Ser ut som detta om man kollar på bilderna:
https://www.portfoliovisualizer.com/faq

https://www.portfoliovisualizer.com/

uppdatering: Jag var några sekunder sen :joy:

2 gillningar

Jag fick lite feeling och fastnade en stund vid datorn. Jag uppdaterade min tidigare kod med dataserier för en enkel allvädersportfölj.

Not: Tickers i diagrammet är de som @Zino använt i första inlägget i sin tråd om allvädersportföljer:

Jag har hämtat data från Yahoo Finance och där används andra tickers, se nedan.

WEBN=WEBG.DE
Captor Iris Bond (B)=0P0001H70E.ST
EN4C=EN4C.DE
GLDA=GOLD.MI

Python-koden
import yfinance as yf
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from datetime import datetime, timedelta

# Datumintervall – senaste 365 dagar
end_date = datetime.today()
start_date = end_date - timedelta(days=365)


# Hämta data: WEBG, EN4C, samt GOLD i EUR, Captor Iris i SEK, och EUR/SEK-växelkurs
webg = yf.download("WEBG.DE", start=start_date, end=end_date, auto_adjust=False)
print("WEBG loaded:", not webg.empty)

en4c = yf.download("EN4C.DE", start=start_date, end=end_date, auto_adjust=False)
print("EN4C loaded:", not en4c.empty)

gold = yf.download("GOLD.MI", start=start_date, end=end_date, auto_adjust=False)
print("GOLD loaded:", not gold.empty)

eursek = yf.download("EURSEK=X", start=start_date, end=end_date, auto_adjust=False)  # EUR/SEK
print("EUR/SEK loaded:", not eursek.empty)

captor_iris = yf.download("0P0001H70E.ST", start=start_date, end=end_date, auto_adjust=False)  # ISIN + .ST ibland funkar
print("Captor Iris loaded:", not captor_iris.empty)

# Debug
"""
# Testa: skriv ut första 5 rader av varje
print("WEBG.DE:\n", webg.head(), "\n")
print("EN4C.DE:\n", en4c.head(), "\n")
print("GOLD.MI:\n", gold.head(), "\n")
print("EURSEK=X:\n", eursek.head(), "\n")
print("Captor Iris (SEK):\n", captor_iris.head(), "\n")
"""

# Säker extraktion av 'Close' även om vi får en DataFrame med MultiIndex (efter nya yfinance-versioner)
def extract_close(df, name):
    if isinstance(df.columns, pd.MultiIndex):
        return df.loc[:, ("Close", name)]
    else:
        return df["Close"]


# Byt namn på serierna
webg_close = extract_close(webg, "WEBG.DE")
webg_close.name = "WEBG_EUR"

en4c_close = extract_close(en4c, "EN4C.DE")
en4c_close.name = "EN4C_EUR"

gold_close = extract_close(gold, "GOLD.MI")
gold_close.name = "GOLD_EUR"

eursek_close = extract_close(eursek, "EURSEK=X")
eursek_close.name = "EURSEK"

captor_close = extract_close(captor_iris, "0P0001H70E.ST")
captor_close.name = "Captor_SEK"

# Kombinera dem i en gemensam DataFrame
df = pd.concat([webg_close, en4c_close, gold_close, eursek_close, captor_close], axis=1)
df.dropna(inplace=True)

# Ta bort alla rader där någon av dem saknas (t.ex. helgdagar eller stängd börs)
df.dropna(inplace=True)

# Kontroll om DataFrame blev tom efter sammanslagningen
if df.empty:
    raise ValueError("Inga gemensamma datum – datan kan vara tom eller sakna överlapp.")

# Omvandla till SEK
df["WEBG_SEK"] = df["WEBG_EUR"] * df["EURSEK"]
df["EN4C_SEK"] = df["EN4C_EUR"] * df["EURSEK"]
df["GOLD_SEK"] = df["GOLD_EUR"] * df["EURSEK"]

# Indexera utvecklingen (börja från 100)
for col in ["WEBG_EUR", "WEBG_SEK", "EN4C_EUR", "EN4C_SEK", "GOLD_EUR", "GOLD_SEK", "Captor_SEK", "EURSEK"]:
    df[f"{col}_pct"] = (df[col] / df[col].iloc[0]) * 100

# Beräkna dagliga logaritmiska avkastningar
cols_for_vol = ["WEBG_EUR", "WEBG_SEK", "EN4C_EUR", "EN4C_SEK", "GOLD_EUR", "GOLD_SEK", "Captor_SEK", "EURSEK"]
log_returns = np.log(df[cols_for_vol] / df[cols_for_vol].shift(1))

# Beräkna årlig volatilitet (standardavvikelse * sqrt(252))
volatility = log_returns.std() * np.sqrt(252)

# Konvertera till procent, t.ex. 0.25 → 25.0%
volatility_pct = (volatility * 100).round(2)


# Plot
plt.figure(figsize=(14, 7))

plot_config = [
    ("WEBG_EUR_pct", "Amundi ACWI (WEBN) i EUR", "blue", ":"),
    ("WEBG_SEK_pct", "Amundi ACWI (WEBN) i SEK", "blue", "-"),
    ("EN4C_EUR_pct", "Commodities (EN4C) i EUR", "red", ":"),
    ("EN4C_SEK_pct", "Commodities (EN4C) i SEK", "red", "-"),
    ("GOLD_EUR_pct", "Guld ETC (GLDA) i EUR", "orange", ":"),
    ("GOLD_SEK_pct", "Guld ETC (GLDA) i SEK", "orange", "-"),
    ("Captor_SEK_pct", "Captor Iris Bond B i SEK", "green", "-"),
    ("EURSEK_pct", "EUR/SEK", "black", "--")
]

for col, label, color, style in plot_config:
    plt.plot(df.index, df[col], label=label, color=color, linestyle=style)

# Volatilitets-etiketter
last_date = df.index[-1]
for col, label, color, _ in plot_config:
    vol = volatility_pct.get(col.replace("_pct", ""), None)
    if vol is not None:
        plt.text(last_date, df[col].iloc[-1], f"Vol: {vol}%", color=color, fontsize=8)

plt.title("Enkel allvädersportfölj - Indexerad utveckling & volatilitet (senaste 12 mån)")
plt.xlabel("Datum")
plt.ylabel("Index (start = 100)")
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
2 gillningar

Jag funderar på hur en volatilitetsviktad portfölj (riskparitetsportfölj) utgående från tillgångarna i föregående inlägg skulle kunna se ut, med de värden jag fått fram för volatiliteten i SEK.

Med beräkningarna i programmet nedan, så blir vikterna i portföljen som följer.

Python-kod
# Riskparitetsvikter med invers volatilitet

# Steg 1: Definiera volatiliteten (i decimalform)
volatilitet = {
    "Aktier": 0.1813,
    "Räntebärande": 0.0673,
    "Guld": 0.1713,
    "Råvaror": 0.1782
}

# Steg 2: Räkna ut inversen av volatiliteten
invers_vol = {tillgång: 1/v for tillgång, v in volatilitet.items()}

# Steg 3: Summera alla inversa volatiliteter
sum_invers_vol = sum(invers_vol.values())

# Steg 4: Normalisera vikterna så att de summerar till 1
vikter = {tillgång: inv_v / sum_invers_vol for tillgång, inv_v in invers_vol.items()}

# Steg 5: Skriv ut resultatet i procent
print("Volatilitetsviktade portföljvikter (riskparitet):")
for tillgång, vikt in vikter.items():
    print(f"{tillgång}: {vikt*100:.2f}%")

Volatilitetsviktade portföljvikter (riskparitet)
Aktier: 17.33%
Räntebärande: 46.69%
Guld: 18.34%
Råvaror: 17.63%

Det blir en mycket hög siffra för räntebärande, som i det här fallet är Captor Iris-fonden. Captor Iris har ju, enligt mina beräkningar, bara ca. 1/3 av volatiliteten som de övriga tillgångarna har, så mot den bakgrunden är det förståeligt att dess vikt i portföljen behöver vara nästan tre ggr så stor som övriga tillgångar, om den ska bidra med samma risk.

Jag funderar på varför det skiljer så pass mycket från de siffror som Zino kommit fram till. Det jag kan tänka mig är dels att det beror på att jag bara beräknat volatilitet utgående från ett år. Jag tror Zino gjorde beräkningar över fem år för att få fram stabila volatilitetsvärden.

Vidare tänker jag mig att det kanske kan ha att göra med valutaomvandlingar. Jag har gjort beräkningarna för volatilitet med SEK-kurser för tillgångarna. Jag vet inte exakt hur Zino gjort. Jag kollar närmare.

Ok, nu tror jag att jag förstår. Zino har redan från början utgått från en lägre allokering (20%) av Captor Iris. Dessutom är Zinos värde för volatiliteten för Captor Iris (10%) högre än mitt (6,73%), samt att Zinos volatilitetsvärden för övriga tillgångar (15%) är lägre än mina (17-18%).

1 gillning

Jag testade igen att försöka få ut fondkurser från Handelsbankens sida och denna gång lyckades jag bättre. Kanske hade jag mer tur i dag. Eller så var jag för trött för att förstå mig på sökfunktionen förra gången jag försökte.

Jag var på jakt efter minst 5 års dagliga kursdata för fonden Captor Iris Bond A. Trots att Avanza anger fondens startdatum till 2019-06-19 så hade Handelsbanken kursdata ända från 2017-05-29. Märkligt! :thinking:

Tack för tipset @Zino!

1 gillning

Några uppdateringar… Jag har jobbat med att försöka få tag på data som sträcker sig minst fem år bakåt i tiden till min plot av komponenterna i en enkel allvädersportfölj. Jag fick byta ut alla ETF:er för att lyckas med detta. ACWI-ETF:en och guld-ETF:en borde vara likvärdiga med de som används i den enkla portfölj @Zino beskriver i sin tråd. EN4C har jag dock bytt ut mot SXRS, som följer BCOM, ett råvaruindex. Detta då data för EN4C inte fanns för fem år (jag tror inte ens EN4C har existerat i fem år).

Nedan är en plot för drygt 7 år. För tillfället är det SXRS som är begränsande då det gäller vilken historik jag kan få tag på bakåt i tiden. Volatiliteten för de olika serierna är beräknad som ett snitt över hela tidsperioden.

Det finns fortfarande några kosmetiska skavanker i koden, men här nedan är den i sin helhet för den som är intresserad. Jag kör Python-koden i Google Colab och jag har en Excelfil med historiska data för Captor Iris, som ligger på Google Drive och som används för de äldre data som inte finns att tillgå på Yahoo Finance.

kod
import yfinance as yf
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from google.colab import drive
from matplotlib.ticker import FuncFormatter


# === Välj y-skale-typ ===
# Alternativ: "index", "procent", "log"
y_axis_type = "log"


# End date (för Yahoo)
end_date = datetime.combine(datetime.today().date(), datetime.min.time())
print("End date (för Yahoo):", end_date)


# Använd ett tidigt datum för att hämta all tillgänglig historik
raw_start_date = datetime.today() - relativedelta(years=100)  # eller t.ex. 15 år
# Alternativ för en femårsperiod (tidigare kod)
# start_date = end_date - relativedelta(years=5)

# Hämta data: ACWI i USD, SXRS och EGLN i EUR, Captor Iris i SEK, samt EUR/SEK- och USD/SEK växelkurser
acwi = yf.download("ACWI", start=raw_start_date, end=end_date, auto_adjust=False)
print("ACWI loaded:", not acwi.empty)

sxrs = yf.download("SXRS.DE", start=raw_start_date, end=end_date, auto_adjust=False)
print("SXRS loaded:", not sxrs.empty)

egln = yf.download("EGLN.L", start=raw_start_date, end=end_date, auto_adjust=False)
print("EGLN loaded:", not egln.empty)


# Mount Google Drive (om du kör i Colab)
drive.mount('/content/drive')

# === 1. Läs historiska NAV-data från Excel ===
excel_path = "/content/drive/MyDrive/Colab Notebooks/Indata/captor_iris.xlsx"
captor_excel = pd.read_excel(excel_path, parse_dates=["Datum"])

# Konvertera NAV till float
captor_excel["NAV"] = (
    captor_excel["NAV"]
    .astype(str)
    .str.replace(",", ".")
    .str.replace(" ", "")
    .astype(float)
)

captor_excel.set_index("Datum", inplace=True)
captor_excel.sort_index(inplace=True)

# === 2. Bestäm sista datum i Excel-filen ===
last_excel_date = captor_excel.index.max()
print("\nExcel-data slutar:", last_excel_date.date())

# === 3. Hämta nyare data från Yahoo Finance (om det finns) ===
start_yahoo_date = last_excel_date + timedelta(days=1)

# Debug
print("End date (för Yahoo-download):", end_date)
print("Sista datum i Captor från Excel:", last_excel_date)
print("Startdatum för Yahoo-download:", start_yahoo_date)
# End debug

# Ladda data med download()
captor_yahoo = yf.download(
    "0P0001H70C.ST",
    start=start_yahoo_date,
    end=end_date + timedelta(days=1),  # en dag extra för att inkludera gårdag
    auto_adjust=False
)


if captor_yahoo.empty:
    print("Ingen ny data från Yahoo Finance.")                   # <– Körs om Yahoo är tom
    captor_combined = captor_excel.copy()                       # <– ingår i if-blocket
else:
    print("Yahoo Finance-data hämtad.")                         # <– Körs annars

    # === Säkert extrahera 'Close' som NAV från Yahoo ===
    if isinstance(captor_yahoo.columns, pd.MultiIndex):
        # Yahoo ger MultiIndex: ('Close', '0P0001H70C.ST') – vi väljer första nivån = "Close"
        captor_yahoo = captor_yahoo.loc[:, pd.IndexSlice["Close", :]]
    else:
        captor_yahoo = captor_yahoo[["Close"]]

    # Byt kolumnnamnet till "NAV"
    captor_yahoo.columns = ["NAV"]

    # Standardisera indexnamn och sortering
    captor_yahoo.index.name = "Datum"
    captor_yahoo.sort_index(inplace=True)

    # === 4. Slå ihop Excel- och Yahoo-data ===
    captor_combined = pd.concat([captor_excel, captor_yahoo])
    captor_combined = captor_combined[~captor_combined.index.duplicated(keep="last")]

# === 5. Filtrera enligt datumintervall ===
# === Gäller oavsett if/else ovan ===
captor_combined = captor_combined[
    (captor_combined.index >= raw_start_date) & 
    (captor_combined.index <= end_date)
]

# Kontroll
if captor_combined.empty:
    print("⚠️ Varning: captor_combined är tom efter datumfiltrering!")
else:
    print("✅ captor_combined klar – sista datum:", captor_combined.index.max())

# Debug
print("Excel slutar:", last_excel_date.date())
print("Hämtar från start:", start_yahoo_date, "till", end_date)
print("Yahoo-data:\n", captor_yahoo.tail())

print("Captor Iris (kombinerad) loaded:", not captor_combined.empty)


usdsek = yf.download("USDSEK=X", start=raw_start_date, end=end_date, auto_adjust=False)  # USD/SEK
print("USD/SEK loaded:", not usdsek.empty)

eursek = yf.download("EURSEK=X", start=raw_start_date, end=end_date, auto_adjust=False)  # EUR/SEK
print("EUR/SEK loaded:", not eursek.empty)

# Testa: skriv ut första 5 raderna av varje
print("ACWI (USD):\n", acwi.head(), "\n")
print("SXRS.DE (EUR):\n", sxrs.head(), "\n")
print("EGLN.L (EUR):\n", egln.head(), "\n")

print("Captor Yahoo data:\n", captor_yahoo.head(), "\n")
print("Captor Yahoo data:\n", captor_yahoo.tail(), "\n")
print("Captor Iris (SEK):\n", captor_combined.head(), "\n")
print("Captor Iris (SEK):\n", captor_combined.tail(), "\n")

print("USDSEK=X:\n", usdsek.head(), "\n")
print("EURSEK=X:\n", eursek.head(), "\n")


# Säker extraktion av 'Close' även om vi får en DataFrame med MultiIndex (efter nya yfinance-versioner)
def extract_close(df, name):
    if isinstance(df.columns, pd.MultiIndex):
        return df.loc[:, ("Close", name)]
    else:
        return df["Close"]


# Byt namn på serierna
acwi_close = extract_close(acwi, "ACWI")
acwi_close.name = "ACWI_USD"

sxrs_close = extract_close(sxrs, "SXRS.DE")
sxrs_close.name = "SXRS_EUR"

egln_close = extract_close(egln, "EGLN.L")
egln_close.name = "EGLN_EUR"

captor_close = captor_combined["NAV"]
captor_close.name = "Captor_SEK"

usdsek_close = extract_close(usdsek, "USDSEK=X")
usdsek_close.name = "USDSEK"

eursek_close = extract_close(eursek, "EURSEK=X")
eursek_close.name = "EURSEK"

start_dates = [
    acwi_close.first_valid_index(),
    sxrs_close.first_valid_index(),
    egln_close.first_valid_index(),
    captor_close.first_valid_index(),
    usdsek_close.first_valid_index(),
    eursek_close.first_valid_index()
]

start_date = max(start_dates)  # senaste gemensamma startdatum
print("Automatiskt valt gemensamt startdatum:", start_date, "\n")


acwi_close = acwi_close[(acwi_close.index >= start_date) & (acwi_close.index <= end_date)]
sxrs_close = sxrs_close[(sxrs_close.index >= start_date) & (sxrs_close.index <= end_date)]
egln_close = egln_close[(egln_close.index >= start_date) & (egln_close.index <= end_date)]
captor_close = captor_close[(captor_close.index >= start_date) & (captor_close.index <= end_date)]
usdsek_close = usdsek_close[(usdsek_close.index >= start_date) & (usdsek_close.index <= end_date)]
eursek_close = eursek_close[(eursek_close.index >= start_date) & (eursek_close.index <= end_date)]


# Kolla startdatum för varje individuell serie (innan sammanslagning)
for name, series in [
    ("ACWI", acwi_close),
    ("SXRS", sxrs_close),
    ("EGLN", egln_close),
    ("Captor", captor_close),
    ("USDSEK", usdsek_close),
    ("EURSEK", eursek_close)
]:
    print(f"{name} – start: {series.first_valid_index()}, slut: {series.last_valid_index()}, antal: {len(series)}")


# Kombinera dem i en gemensam DataFrame
df = pd.concat([acwi_close, sxrs_close, egln_close, captor_close, usdsek_close, eursek_close], axis=1)
df.dropna(inplace=True)

# Ta bort alla rader där någon av dem saknas (t.ex. helgdagar eller stängd börs)
df.dropna(inplace=True)

# Kontroll om DataFrame blev tom efter sammanslagningen
if df.empty:
    raise ValueError("Inga gemensamma datum – datan kan vara tom eller sakna överlapp.")

# Kontrollera upplösningen på data och hur långt bak i tiden data finns
print("Frekvens:\n", pd.infer_freq(df.index))
print("Antal datapunkter:", len(df))
print("Startdatum:", df.index.min())
print("Slutdatum:", df.index.max())

# Omvandla till SEK
df["ACWI_SEK"] = df["ACWI_USD"] * df["USDSEK"]
df["SXRS_SEK"] = df["SXRS_EUR"] * df["EURSEK"]
df["EGLN_SEK"] = df["EGLN_EUR"] * df["EURSEK"]


indexed_cols = []

for col in ["ACWI_USD", "ACWI_SEK", "SXRS_EUR", "SXRS_SEK", 
            "EGLN_EUR", "EGLN_SEK", "Captor_SEK", "USDSEK", "EURSEK"]:
    
    if y_axis_type == "procent":
        df[f"{col}_transformed"] = (df[col] / df[col].iloc[0] - 1) * 100
    else:  # "index" eller "log", indexera utvecklingen (börja från 100)
        df[f"{col}_transformed"] = (df[col] / df[col].iloc[0]) * 100

    indexed_cols.append((f"{col}_transformed", col))  # sparar både nya och ursprungliga namnet
    

# Beräkna dagliga logaritmiska avkastningar
cols_for_vol = ["ACWI_USD", "ACWI_SEK", "SXRS_EUR", "SXRS_SEK", "EGLN_EUR", "EGLN_SEK", "Captor_SEK", "USDSEK", "EURSEK"]
log_returns = np.log(df[cols_for_vol] / df[cols_for_vol].shift(1))

# Beräkna årlig volatilitet (standardavvikelse * sqrt(252))
volatility = log_returns.std() * np.sqrt(252)

# Konvertera till procent, t.ex. 0.25 → 25.0%
volatility_pct = (volatility * 100).round(2)


# Plot
plt.figure(figsize=(14, 7))

# Definiera stil för varje kolumn (kan anpassas!)
style_map = {
    "ACWI_USD": ("Aktier - iShares ACWI i USD", "blue", ":"),
    "ACWI_SEK": ("Aktier - iShares ACWI i SEK", "blue", "-"),
    "SXRS_EUR": ("Råvaror - iShares SXRS i EUR", "red", ":"),
    "SXRS_SEK": ("Råvaror - iShares SXRS i SEK", "red", "-"),
    "EGLN_EUR": ("Guld - iShares EGLN i EUR", "orange", ":"),
    "EGLN_SEK": ("Guld - iShares EGLN i SEK", "orange", "-"),
    "Captor_SEK": ("Obligationer - Captor Iris Bond A i SEK", "green", "-"),
    "USDSEK": ("USD/SEK", "purple", "--"),
    "EURSEK": ("EUR/SEK", "black", "--")
}

for transformed_col, original_col in indexed_cols:
    label, color, style = style_map.get(original_col, (original_col, "grey", "-"))
    vol = volatility_pct.get(original_col, None)
    if vol is not None:
        label += f" (Vol: {vol}%)"
    plt.plot(df.index, df[transformed_col], label=label, color=color, linestyle=style)


start_str = df.index.min().strftime("%Y-%m-%d")
end_str = df.index.max().strftime("%Y-%m-%d")

title_map = {
    "index": "Indexerad utveckling",
    "procent": "Procentuell utveckling",
    "log": "Indexerad utveckling (log-skala)"
}

plt.title(f"Allvädersportfölj – {title_map[y_axis_type]}\nPeriod: {start_str} till {end_str}")


plt.xlabel("Datum")
plt.legend(loc="upper left")
plt.grid(True)
plt.tight_layout()


if y_axis_type == "procent":
    # Procentformat på y-axeln
    def percent_formatter(x, pos):
        return f"{x:.0f}%"
    plt.gca().yaxis.set_major_formatter(FuncFormatter(percent_formatter))
    plt.ylabel("Procentuell utveckling (start = 0%)")
    plt.yscale("linear")

elif y_axis_type == "log":
    if (df[[col for col, _ in indexed_cols]] <= 0).any().any():
        raise ValueError("Log-skala kräver positiva värden – minst ett värde är ≤ 0.")
    plt.ylabel("Index (log-skala, start = 100)")
    plt.yscale("log")  # OBS: fungerar inte om några värden ≤ 0

else:  # "index"
    plt.ylabel("Index (start = 100)")
    plt.yscale("linear")


plt.show()

Tillägg: Nu även med log-skala på y-axeln (valbart i koden).

Tillägg 2: Procentuell y-skala, för de som är lite mer mainstream :upside_down_face: