FIRE-simulator (Monte Carlo)

Hej!

Jag har tillsammans med Gemini snickrat ihop en Python kod för att simulera pensionskapital och FIRE som jag tänkte dela med mig av.

Många enkla kalkylatorer räknar linjärt (“börsen går upp 7% varje år”). Problemet är att verkligheten inte ser ut så. Om börsen kraschar 30% precis året efter att du slutat jobba (Sequence of Return Risk) spelar det ingen roll om snittet blir bra på 30 år – dina pengar kan hinna ta slut innan dess.

Det här verktyget använder Monte Carlo-simulering (kör 4 000 olika slumpmässiga “framtider”) för att stresstesta din strategi.

:rocket: Vad gör den här modellen?

Den simulerar en variabel uttagsstrategi med buffert (Floor-strategi). Istället för att bara ha pengar på hög, räknar modellen ut hur stor buffert du behöver baserat på din “smärtgräns” (den lägsta inkomst du kan leva på under krisår).

  1. Bra år: Du tar ut din önskade “lyx-nivå” direkt från avkastningen (ISK).

  2. Dåliga år: Om börsen backar, rör du inte fonderna. Istället lever du på din buffert (sparkonto) och drar ner utgifterna till din smärtgräns.

  3. Effekt: Detta gör att portföljen återhämtar sig snabbare efter en krasch, vilket ofta drastiskt ökar chansen att pengarna räcker livet ut.

:bar_chart: Vad får du för svar?

När du kör koden får du en rapport och grafer som visar 4 scenarier:

  • Scenario A (Ditt val): Hur det går med din valda uttagsnivå (t.ex. 4%).

  • Scenario B (Låg kostnad): Hur kapitalet växer om du alltid lever snålt (på din smärtgräns).

  • Scenario C (Bevara kapitalet): Vilken uttagsnivå (%) du bör ligga på om du vill att köpkraften ska vara intakt när angiven tidsperiod passerat.

  • Scenario D (Die With Zero): Hur mycket du kan unna dig om målet är att kontot ska vara tomt (0 kr) exakt när angiven tidsperiod passerat.

Den räknar även om allt till vad det motsvarar i bruttolön (baserat på skattesats som du angivit) så siffrorna blir greppbara.

:gear: Antaganden i koden (Marknadsdata)

Simuleringen räknar med nominell avkastning (inklusive volatilitet/risk) baserat på historiska schabloner, och justerar sedan allt för inflation och skatt.

  • Globalfond (MSCI World): 7,5% avkastning (Risk/Std: 15%).

    • OBS: Detta inkluderar enbart utvecklade marknader (USA, Europa etc), ej tillväxtmarknader.
  • Sverigefond: 9,5% avkastning (Risk/Std: 22%). Högre risk/reward.

  • Europafond: 7,5% avkastning (Risk/Std: 18%).

  • Tillväxtmarknader (EM): 8,5% avkastning (Risk/Std: 25%). Högst risk.

  • Inflation: 2,0%.

  • ISK-skatt & Avgifter: ca 1,2% totalt.

Vill du ändra dessa siffror? I koden letar du upp funktionen def calculate_portfolio_metrics. Där ligger variabeln stats = { ... } där du enkelt kan ändra mean (avkastning) och std (risk) för respektive region.

:laptop: Så här kör du koden (även om du inte kan programmera)

Du behöver inte installera något. Du kan köra detta direkt i webbläsaren via Google Colab (gratis).

  1. Gå till colab.research.google.com (logga in med Google-konto).

  2. Klicka på “New Notebook” (Ny anteckningsbok).

  3. I den tomma rutan, klistra in hela koden nedanför.

  4. Klicka på den lilla “Play-knappen” (pil) till vänster om koden.

  5. Skrolla ner lite. Programmet ställer frågor (t.ex. “Totalt kapital”).

  6. Tips: Där det står ett värde inom klamrar, t.ex. [Standard: 4.0], kan du bara trycka Enter för att välja det.

Här är koden.

#!/usr/bin/env python3
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
from typing import Optional, Tuple, List

# -------------------------------------------------------------------------
# 1. SKATTEBERÄKNING
# -------------------------------------------------------------------------
def calculate_gross_salary_from_net(target_net_monthly, local_tax_rate, state_tax_threshold):
    low = target_net_monthly
    high = target_net_monthly * 2.5 
    
    def calc_net(gross):
        basic_deduction = 1250 
        if gross < 20000: basic_deduction = 2000
        taxable_income = gross - basic_deduction
        muni_tax = taxable_income * local_tax_rate
        state_tax = 0
        if taxable_income > state_tax_threshold:
            state_tax = (taxable_income - state_tax_threshold) * 0.20
        job_deduction = 0
        if gross > 40000: job_deduction = 3500
        elif gross > 20000: job_deduction = 2500
        else: job_deduction = 1500
        total_tax = muni_tax + state_tax - job_deduction
        if total_tax < 0: total_tax = 0
        return gross - total_tax

    for _ in range(30):
        mid = (low + high) / 2
        net = calc_net(mid)
        if net < target_net_monthly:
            low = mid
        else:
            high = mid     
    return high

# -------------------------------------------------------------------------
# 2. PORTFÖLJ-LOGIK
# -------------------------------------------------------------------------
def calculate_portfolio_metrics(pct_global, pct_swe, pct_eur, pct_em):
    stats = {
        "Global":   {"mean": 0.075, "std": 0.15},
        "Sverige":  {"mean": 0.095, "std": 0.22},
        "Europa":   {"mean": 0.075, "std": 0.18},
        "Tillväxt": {"mean": 0.085, "std": 0.25}
    }
    w_g, w_s = pct_global / 100.0, pct_swe / 100.0
    w_e, w_em = pct_eur / 100.0, pct_em / 100.0
    
    port_mean = (w_g * stats["Global"]["mean"] + w_s * stats["Sverige"]["mean"] + 
                 w_e * stats["Europa"]["mean"] + w_em * stats["Tillväxt"]["mean"])
    port_std = (w_g * stats["Global"]["std"] + w_s * stats["Sverige"]["std"] + 
                w_e * stats["Europa"]["std"] + w_em * stats["Tillväxt"]["std"])
    return port_mean, port_std, stats

# -------------------------------------------------------------------------
# 3. HJÄLPFUNKTIONER
# -------------------------------------------------------------------------
def fmt_sek(amount):
    return f"{amount:,.0f}".replace(",", " ")

def get_user_input(prompt_text, default_value, value_type=float):
    if default_value >= 1000:
        display_default = f"{default_value:,.0f}".replace(",", " ")
    else:
        display_default = str(default_value)
    user_str = input(f"{prompt_text} [Standard: {display_default}]: ")
    if not user_str.strip(): return default_value
    try:
        clean_str = user_str.replace(" ", "").replace(",", ".")
        if value_type is int: return int(float(clean_str))
        return float(clean_str)
    except ValueError: return default_value

def get_mandatory_input(prompt_text, value_type=float):
    while True:
        user_str = input(f"{prompt_text}: ")
        if not user_str.strip():
            print("⚠️ Du måste ange ett värde.")
            continue
        try:
            clean_str = user_str.replace(" ", "").replace(",", ".")
            if value_type is int: return int(float(clean_str))
            return float(clean_str)
        except ValueError:
            print("⚠️ Felaktigt format. Försök igen.")

def get_yes_no(prompt_text, default="n"):
    user_str = input(f"{prompt_text} (j/n) [Standard: {default}]: ").strip().lower()
    if not user_str: return default == "j"
    return user_str.startswith("j") or user_str.startswith("y")

# -------------------------------------------------------------------------
# 4. SIMULERINGSLOGIK
# -------------------------------------------------------------------------
def simulate_strategy(
    n_sims, years, start_capital, withdraw_rate, min_withdraw_yearly_start,
    stock_mean, stock_std, management_fee,
    isk_tax, inflation, skip_threshold, buffer_years, seed,
    return_full_stats=False
):
    if seed is not None: np.random.seed(seed)
    
    buffer_size_needed = min_withdraw_yearly_start * buffer_years
    
    if start_capital <= buffer_size_needed:
        invested_capital = 0.0
        current_buffer_start = start_capital
    else:
        invested_capital = start_capital - buffer_size_needed
        current_buffer_start = buffer_size_needed

    all_paths_nominal = np.zeros((n_sims, years + 1))
    all_paths_nominal[:, 0] = start_capital
    
    skip_years_used_list = []
    max_skip_streak_list = []
    survival_years_list = [] 
    
    market_returns = np.random.normal(stock_mean, stock_std, (n_sims, years))
    
    for i in range(n_sims):
        curr_isk = invested_capital
        curr_buffer = current_buffer_start
        
        std_withdraw_amt = invested_capital * withdraw_rate
        min_withdraw_amt = min_withdraw_yearly_start
        
        skip_years_used = 0
        curr_streak = 0
        max_streak = 0
        money_lasted_years = years 
        
        for year in range(1, years + 1):
            if curr_isk <= 0 and curr_buffer <= 0:
                if money_lasted_years == years: money_lasted_years = year - 1
                all_paths_nominal[i, year] = 0
                continue

            m_ret = market_returns[i, year-1]
            net_ret = m_ret - management_fee
            
            # 1. Aktieutveckling + Skatt
            curr_isk *= (1 + net_ret)
            if curr_isk > 0: curr_isk -= (curr_isk * isk_tax)
            if curr_isk < 0: curr_isk = 0.0
            
            # 2. Buffertutveckling (Inflation)
            curr_buffer *= (1 + inflation)
            
            # 3. Beslut: ISK eller Buffert?
            actual_std_withdraw = max(std_withdraw_amt, min_withdraw_amt)

            if m_ret <= skip_threshold and curr_buffer >= min_withdraw_amt:
                # DÅLIGT ÅR -> Ta från Buffert
                curr_buffer -= min_withdraw_amt
                skip_years_used += 1
                curr_streak += 1
                max_streak = max(max_streak, curr_streak)
            else:
                # BRA ÅR -> Ta från ISK
                take_amount = actual_std_withdraw
                if curr_isk >= take_amount:
                    curr_isk -= take_amount
                else:
                    remaining = take_amount - curr_isk
                    curr_isk = 0.0
                    if curr_buffer >= remaining:
                        curr_buffer -= remaining
                    else:
                        curr_buffer = 0.0
                curr_streak = 0
            
            # 4. Indexera upp behovet inför nästa år
            std_withdraw_amt *= (1 + inflation)
            min_withdraw_amt *= (1 + inflation)
            
            total_now = curr_isk + curr_buffer
            all_paths_nominal[i, year] = total_now
            
            if total_now <= 0 and money_lasted_years == years:
                money_lasted_years = year

        skip_years_used_list.append(skip_years_used)
        max_skip_streak_list.append(max_streak)
        survival_years_list.append(money_lasted_years)

    final_caps = all_paths_nominal[:, -1]
    final_real = final_caps / ((1+inflation)**years)
    prob_preservation = float(np.mean(final_real >= start_capital))
    median_final_nominal = float(np.median(final_caps))
    
    if return_full_stats:
        median_nom_path = np.median(all_paths_nominal, axis=0)
        median_real_path = median_nom_path / np.array([(1+inflation)**y for y in range(years+1)])
        median_survival = float(np.median(survival_years_list))
        return (
            median_final_nominal, 
            float(np.median(final_real)), 
            prob_preservation,
            skip_years_used_list,
            max_skip_streak_list,
            median_nom_path,
            median_real_path,
            invested_capital,
            current_buffer_start,
            median_survival,
            all_paths_nominal
        )
    else:
        return median_final_nominal

# --- OPTIMERINGAR ---
def find_preservation_rate(n_sims, years, total_cap, min_withdraw, stock_mean, stock_std, fee, tax, inf, skip, buf_years, seed):
    low, high = 0.00, 0.15
    best = 0.0
    for _ in range(15):
        mid = (low+high)/2
        result = simulate_strategy(
            min(n_sims, 2000), years, total_cap, mid, min_withdraw,
            stock_mean, stock_std, fee, tax, inf, skip, buf_years, seed, True
        )
        if result[2] > 0.50: best = mid; low = mid
        else: high = mid
    return best

def find_depletion_rate(n_sims, years, total_cap, min_withdraw, stock_mean, stock_std, fee, tax, inf, skip, buf_years, seed):
    low, high = 0.00, 0.30
    best = 0.0
    for _ in range(15):
        mid = (low+high)/2
        final_cap = simulate_strategy(
            min(n_sims, 2000), years, total_cap, mid, min_withdraw,
            stock_mean, stock_std, fee, tax, inf, skip, buf_years, seed, False
        )
        if final_cap > 10000: best = mid; low = mid
        else: high = mid
    return best

def export_detailed_csv(all_paths_nominal, years, inflation):
    df_data = []
    inflation_factors = np.array([(1 + inflation) ** y for y in range(years + 1)])
    p10 = np.percentile(all_paths_nominal, 10, axis=0)
    p50 = np.percentile(all_paths_nominal, 50, axis=0)
    p90 = np.percentile(all_paths_nominal, 90, axis=0)
    
    for y in range(years + 1):
        row = {
            "År": y,
            "Pessimistiskt (10%) - Nominellt": p10[y],
            "Median (50%) - Nominellt": p50[y],
            "Optimistiskt (90%) - Nominellt": p90[y],
            "Pessimistiskt (10%) - Realt": p10[y] / inflation_factors[y],
            "Median (50%) - Realt": p50[y] / inflation_factors[y],
            "Optimistiskt (90%) - Realt": p90[y] / inflation_factors[y]
        }
        df_data.append(row)
    pd.DataFrame(df_data).to_csv("fire_detaljerad_data.csv", index=False, float_format='%.0f')
    print(f"\n✅ Detaljerad data sparad till 'fire_detaljerad_data.csv'")

def plot_four_scenarios(years, start_capital, res_A, res_B, res_C, res_D, rate_A, rate_C, rate_D, min_withdraw_monthly):
    path_nom_A, path_real_A = res_A[5], res_A[6]
    path_nom_B, path_real_B = res_B[5], res_B[6]
    path_nom_C, path_real_C = res_C[5], res_C[6]
    path_nom_D, path_real_D = res_D[5], res_D[6]

    x_years = np.arange(years + 1)
    fig, axs = plt.subplots(2, 2, figsize=(14, 10))
    fig.suptitle(f"Utfall av 4 strategier (Medianutveckling över {years} år)", fontsize=16, fontweight='bold')
    
    def currency_fmt(x, pos):
        if x >= 1_000_000: return f'{x/1_000_000:.1f} M'
        return f'{x/1_000:.0f} k'
    
    scenarios = [
        (axs[0, 0], "A: DITT VAL", path_nom_A, path_real_A, f"{rate_A*100:.1f}% uttag"),
        (axs[0, 1], "B: LÅG KOSTNAD (Smärtgräns)", path_nom_B, path_real_B, f"{fmt_sek(min_withdraw_monthly)} kr/mån"),
        (axs[1, 0], "C: BEVARA KAPITAL", path_nom_C, path_real_C, f"{rate_C*100:.1f}% uttag"),
        (axs[1, 1], "D: DIE WITH ZERO", path_nom_D, path_real_D, f"{rate_D*100:.1f}% uttag"),
    ]

    for ax, title, p_nom, p_real, subtitle in scenarios:
        width = 0.4
        ax.bar(x_years - width/2, p_nom, width, label='Nominellt (På kontot)', color='#4c72b0', alpha=0.9)
        ax.bar(x_years + width/2, p_real, width, label='Realt (Köpkraft)', color='#55a868', alpha=0.9)
        ax.axhline(y=start_capital, color='red', linestyle='--', linewidth=1.5, label='Startkapital')
        ax.set_title(title, fontsize=12, fontweight='bold')
        ax.text(0.05, 0.95, subtitle, transform=ax.transAxes, fontsize=10, verticalalignment='top', bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))
        ax.grid(axis='y', linestyle='--', alpha=0.3)
        ax.yaxis.set_major_formatter(mticker.FuncFormatter(currency_fmt))
        if ax == axs[0, 0]: ax.legend(loc='upper right', fontsize=9)

    plt.tight_layout(rect=[0, 0.03, 1, 0.95])
    plt.savefig('fire_scenarios.png', dpi=100)
    plt.show()

# -------------------------------------------------------------------------
# 7. MAIN
# -------------------------------------------------------------------------
def main():
    print("\n--- STEG 1: EKONOMI & STRATEGI ---")
    total_available_capital = get_mandatory_input("TOTALT KAPITAL (ISK + Kontanter) (kr)")
    min_monthly = get_user_input("Smärtgräns uttag under krisår (kr/mån)", 50000, int)
    min_withdraw_yearly = min_monthly * 12
    buffer_years = get_user_input("Hur många år ska bufferten räcka?", 4, int)
    withdraw_percent = get_user_input("Önskat uttag BRA år (% av investerat ISK)", 4.0)
    withdraw_rate = withdraw_percent / 100.0
    years = get_user_input("Tidsperiod (år)", 30, int)
    create_detailed_report = get_yes_no("Vill du spara en detaljerad Excel-fil (CSV) med utfall år för år?", "n")
    
    print("\n--- STEG 2: PORTFÖLJALLOKERING ---")
    print("Ange fördelning i % (Bör summera till 100).")
    pct_global = get_user_input("Globalfond (%)", 65.0)
    pct_swe = get_user_input("Sverigefond (%)", 25.0)
    pct_em = get_user_input("Tillväxtmarknader (%)", 10.0)
    pct_eur = get_user_input("Europafond (%)", 0.0)
    
    total_pct = pct_global + pct_swe + pct_em + pct_eur
    if abs(total_pct - 100.0) > 0.1:
        pct_global = (pct_global / total_pct) * 100
        pct_swe = (pct_swe / total_pct) * 100
        pct_em = (pct_em / total_pct) * 100
        pct_eur = (pct_eur / total_pct) * 100
        
    print("\n--- STEG 3: SKATTEINSTÄLLNINGAR ---")
    user_tax_rate_input = get_user_input("Kommunalskatt (%)", 32.21)
    user_state_threshold_input = get_user_input("Brytpunkt statlig skatt (kr/mån)", 53_590, int)
    local_tax_rate = user_tax_rate_input / 100.0
    state_tax_threshold = user_state_threshold_input

    stock_mean, stock_std, stats_data = calculate_portfolio_metrics(pct_global, pct_swe, pct_eur, pct_em)
    n_sims = 4000
    management_fee = 0.002  
    isk_tax    = 0.010      
    inflation  = 0.02       
    skip_threshold = -0.05  
    seed = 42

    print(f"\n🚀 Kör simuleringar för alla scenarier...")

    rate_preservation = find_preservation_rate(
        n_sims, years, total_available_capital, min_withdraw_yearly,
        stock_mean, stock_std, management_fee, isk_tax, inflation,
        skip_threshold, buffer_years, seed
    )
    rate_depletion = find_depletion_rate(
        n_sims, years, total_available_capital, min_withdraw_yearly,
        stock_mean, stock_std, management_fee, isk_tax, inflation,
        skip_threshold, buffer_years, seed
    )
    
    res_A = simulate_strategy(
        n_sims, years, total_available_capital, withdraw_rate, min_withdraw_yearly,
        stock_mean, stock_std, management_fee, isk_tax, inflation,
        skip_threshold, buffer_years, seed, return_full_stats=True
    )
    res_B = simulate_strategy(
        n_sims, years, total_available_capital, 0.0, min_withdraw_yearly,
        stock_mean, stock_std, management_fee, isk_tax, inflation,
        skip_threshold, buffer_years, seed, return_full_stats=True
    )
    res_C = simulate_strategy(
        n_sims, years, total_available_capital, rate_preservation, min_withdraw_yearly,
        stock_mean, stock_std, management_fee, isk_tax, inflation,
        skip_threshold, buffer_years, seed, return_full_stats=True
    )
    res_D = simulate_strategy(
        n_sims, years, total_available_capital, rate_depletion, min_withdraw_yearly,
        stock_mean, stock_std, management_fee, isk_tax, inflation,
        skip_threshold, buffer_years, seed, return_full_stats=True
    )

    if create_detailed_report:
        export_detailed_csv(res_A[10], years, inflation)

    invested_cap = res_A[7]
    buffer_cap = res_A[8]
    pct_invested = (invested_cap / total_available_capital) * 100 if total_available_capital > 0 else 0
    pct_buffer = (buffer_cap / total_available_capital) * 100 if total_available_capital > 0 else 0
    
    yr_A = invested_cap * withdraw_rate
    mo_A = yr_A / 12
    gross_A = calculate_gross_salary_from_net(mo_A, local_tax_rate, state_tax_threshold)
    surv_A = res_A[9]; txt_surv_A = f"{surv_A:.0f}" if surv_A < years else f"{years}+"
    
    yr_C = invested_cap * rate_preservation; mo_C = yr_C / 12
    gross_C = calculate_gross_salary_from_net(mo_C, local_tax_rate, state_tax_threshold)
    margin_C = mo_C - mo_A
    
    yr_D = invested_cap * rate_depletion; mo_D = yr_D / 12
    gross_D = calculate_gross_salary_from_net(mo_D, local_tax_rate, state_tax_threshold)

    pct_any_skip = float(np.mean(np.array(res_A[3]) > 0))
    avg_skipped = float(np.mean(res_A[3]))
    med_streak = float(np.median(res_A[4])) if res_A[4] else 0.0

    assumptions_str = ""
    for market, data in stats_data.items():
        assumptions_str += f"- {market}: {data['mean']*100:.1f}% avkastning / {data['std']*100:.1f}% risk\n"

    summary_text = (
        f"📊 RAPPORT: FIRE-SIMULERING (4 SCENARIER)\n"
        f"Resultat baserat på {n_sims} antal Monte Carlo-simuleringar över {years} år\n"
        f"==========================================================\n"
        f"TOTALT KAPITAL: {fmt_sek(total_available_capital)} kr\n"
        f"FÖRDELNING (Baserat på smärtgräns {fmt_sek(min_monthly)} kr/mån × {buffer_years} år):\n"
        f"  👉 Investeras på ISK:   {fmt_sek(invested_cap)} kr ({pct_invested:.1f}%)\n"
        f"  👉 Buffert (Sparkonto): {fmt_sek(buffer_cap)} kr ({pct_buffer:.1f}%)\n"
        f"==========================================================\n"
        f"Portfölj: Global {pct_global:.0f}% | Sve {pct_swe:.0f}% | Tillväxt {pct_em:.0f}% | Europa {pct_eur:.0f}%\n"
        f"Skattemodell: {user_tax_rate_input}% kommunal, {fmt_sek(state_tax_threshold)} kr brytpunkt\n"
        f"\n"
        f"ANTAGANDEN (Nominell avkastning & Standardavvikelse):\n"
        f"{assumptions_str}"
        f"\n"
        f"💰 EKONOMI & UTFALL (Dagens penningvärde)\n"
        f"-----------------------------------------\n"
        f"SCENARIO A: DITT VAL ({withdraw_percent}% av ISK)\n"
        f"  Uttag/mån:    {fmt_sek(mo_A)} kr (Motsvarar lön: {fmt_sek(gross_A)} kr)\n"
        f"  Uttag/år:     {fmt_sek(yr_A)} kr\n"
        f"  Kapitalet räcker: {txt_surv_A} år (i snitt)\n"
        f"  Median slutkapital: {fmt_sek(res_A[1])} kr (realt)\n"
        f"\n"
        f"SCENARIO B: LÅG KOSTNAD (Lever alltid på smärtgränsen)\n"
        f"  Uttag/mån:    {fmt_sek(min_monthly)} kr\n"
        f"  Median slutkapital: {fmt_sek(res_B[1])} kr (realt) <-- Maximerar tillväxt\n"
        f"\n"
        f"SCENARIO C: BEVARA KAPITALET (Säkert uttag)\n"
        f"  Max uttag %:  {rate_preservation*100:.2f}%\n"
        f"  Uttag/mån:    {fmt_sek(mo_C)} kr (Motsvarar lön: {fmt_sek(gross_C)} kr)\n"
        f"  Diff mot A:   {fmt_sek(margin_C)} kr/mån\n"
        f"  Median slutkapital: {fmt_sek(res_C[1])} kr (realt)\n"
        f"\n"
        f"SCENARIO D: NOLLA KONTOT ('Die with Zero')\n"
        f"  Max uttag %:  {rate_depletion*100:.2f}%\n"
        f"  Uttag/mån:    {fmt_sek(mo_D)} kr (Motsvarar lön: {fmt_sek(gross_D)} kr)\n"
        f"  Median slutkapital: {fmt_sek(res_D[1])} kr (realt)\n"
        f"\n"
        f"🧰 BUFFERTANVÄNDNING (För Scenario A)\n"
        f"-------------------------------\n"
        f"- Sannolikhet att behöva använda buffert: {pct_any_skip:.1%}\n"
        f"- Snitt antal år med buffertanvändning: {avg_skipped:.1f} år\n"
        f"\n"
        f"🔍 SÅ GICK DET TILL\n"
        f"Här är logiken som kördes {n_sims} gånger:\n"
        f"1. Marknadens nycker: Varje år slumpas en avkastning baserad på din portföljs risk.\n"
        f"2. Beslutsregeln (Golv-strategin):\n"
        f"   - Är årets avkastning sämre än {skip_threshold*100:.0f}%? -> Vi tar smärtgräns-beloppet från bufferten.\n"
        f"   - Är avkastningen ok? -> Vi tar ut standardbeloppet från ISK-portföljen.\n"
        f"3. Inflation & Skatt: Alla uttag räknas upp med {inflation*100}%/år. ISK-skatt dras automatiskt.\n"
        f"4. Utfall: Vi mäter hur många av de {n_sims} simulerade liven som lyckades.\n"
    )

    filename = "fire_simulator_summering.txt"
    with open(filename, "w", encoding="utf-8") as f:
        f.write(summary_text)

    print(f"\n✅ Resultat sparat till {filename}")
    print(summary_text)
    
    print("\nGenererar grafer för alla scenarier...")
    plot_four_scenarios(years, total_available_capital, res_A, res_B, res_C, res_D, withdraw_rate, rate_preservation, rate_depletion, min_monthly)

if __name__ == "__main__":
    main()
5 gillningar

Imponerande.
Dels fungerade alldeles utmärkt rent tekniskt - in i Gemini och kör.
Resultatet var lite i linje med vad jag förväntat mig och det kändes bra.
Sedan blir det för en som inte är insatt i detaljer, lite av Black Box dvs man vet vad man stoppar in och förstår vad som kommer ut men man undrar vad som sker inne i lådan.
Men som sagt var - jag är i alla fall imponerad.
Sedan kanske nördarna som vet mera om vad som sker inne lådan har annat att tillägga?

1 gillning

Kul att det fungerade! Jag har lagt till en sektion som förklarar lite hur algoritmen tänker varje år (hur den väljer mellan buffert och ISK, hur den hanterar inflation och skatt, etc.). och även en möjlighet att välja att skapa en mer detaljerad rapport kring utfallet. Testa att köra den nya koden och se om det reducerar den svarta boxen

En stor poängen med att kunna köra koden i Gemini att man sedan kan fortsätta med att ställa frågor och få hjälp med att tolka resultatet och därefter be modellen att räkna om med andra förutsättningar.

Skall med spänning testa den nya koden.

Har du testat att köra koden i https://colab.research.google.com? Gemini klarar väl inte av den interaktiva delen där du ombedds ge input? Jag använder sedan Gemini för att förfina eller felsöka koden.

Om du ber AIn generera det som en standalone-html-fil så kan du köra det direkt i canvasen, åtm hos ChatGPT. Då kan den bygga gui, visa grafer, göra det interaktivt osv. Dock får det ju då bli javascript i stället för python.

Kanske prova att ge AIn pythonkoden och be den porta det till en standalone html-fil med gui och se vad som händer?