ASK vs. taxation on realisation

Change language: DK EN

The equity savings account (ASK) has a low tax rate of 17%, but is taxed mark-to-market. In principle, taxation on realisation with share tax of 27%/42% could give a higher return over a long period due to compound interest. Here it will be investigated how long this period actually is.

First, the return of the portfolios must be computed. Data for the S&P500 is used as the starting point (because this data is easily available). The data is retrieved from www.econ.yale.edu, 16-11-2020. The percentage increase of the ETF price is then the percentage increase of the S&P500 plus the percentage dividend. See SP500.csv for the processed data.

First, all the modules needed for the model are imported. dkfinance_modeller is from dkfinance_modeller

from typing import List, Tuple

import matplotlib.pyplot as plt
import numpy as np

import dkfinance_modeller.aktieskat.depotmodel as depotmodel
import dkfinance_modeller.aktieskat.kurtage as kurtage
import dkfinance_modeller.aktieskat.skat as skat
import dkfinance_modeller.aktieskat.vaerdipapirer as værdipapirer
import dkfinance_modeller.aktieskat.valuta as valuta
import dkfinance_modeller.utility.formler as formler

The portfolios are constructed with a 0.0% ongoing charge (ÅOP) to emulate the best scenario for taxation on realisation, i.e. investing directly in shares instead of buying ETFs and investment funds.

Now the portfolios of the model are defined.

def depoter(
    start_kapital: float
) -> Tuple[depotmodel.DepotModel, depotmodel.DepotModel, depotmodel.DepotModel]:
    """Definerer depoter.

    Args:
      start_kapital: Startkapital for depoterne.

    Returns:
      Depot med lagerbeskatning og to depoter med realisationsbeskatning.
    """
    etf1 = værdipapirer.ETF(kurs=100, åop=0.0 / 100, beskatningstype="lager")
    etf2 = værdipapirer.ETF(kurs=100, åop=0.0 / 100, beskatningstype="realisation")
    etf3 = værdipapirer.ETF(kurs=100, åop=0.0 / 100, beskatningstype="realisation")
    skatter1 = skat.Skat(beskatningstype="ask")
    skatter2 = skat.Skat(beskatningstype="aktie")
    skatter3 = skat.Skat(beskatningstype="ask")
    skatter3.skatteprocenter = [0.42]
    ask = depotmodel.DepotModel(
        kapital=start_kapital,
        kurtagefunktion=kurtage.saxo_kurtage_bygger(valuta="euro", valutakurs=7.44),
        skatteklasse=skatter1,
        minimumskøb=1000,
        ETFer=[etf1],
        ETF_fordeling=[1.0],
    )
    normal = depotmodel.DepotModel(
        kapital=start_kapital,
        kurtagefunktion=kurtage.saxo_kurtage_bygger(valuta="euro", valutakurs=7.44, underkonto=True),
        skatteklasse=skatter2,
        minimumskøb=1000,
        ETFer=[etf2],
        ETF_fordeling=[1.0],
        valutafunktion=valuta.saxo_underkonto_kurtage,
    )
    normal_worst_case = depotmodel.DepotModel(
        kapital=start_kapital,
        kurtagefunktion=kurtage.saxo_kurtage_bygger(valuta="euro", valutakurs=7.44, underkonto=True),
        skatteklasse=skatter3,
        minimumskøb=1000,
        ETFer=[etf3],
        ETF_fordeling=[1.0],
        valutafunktion=valuta.saxo_underkonto_kurtage,
    )
    return ask, normal, normal_worst_case

Here there are two different portfolios taxed on realisation. The first has 27%/42% taxation with the full progression limit; this is the best case for taxation on realisation, and relevant if this is the entire portfolio. The second always has 42% taxation and models a side portfolio that uses the entire progression limit. This is the worst case for taxation on realisation.

The model is now defined.

def kør_model(  # pylint: disable=R0914
    start_kapital: float
) -> Tuple[List[List[float]], List[List[float]], List[List[float]]]:
    """Kører modellen.

    Args:
      start_kapital: Startkapital for depoterne.

    Returns:
      Fraktiler for CAGR for forskellige depot typer.
    """
    data = np.genfromtxt("data/SP500.csv", delimiter=";")
    ask: List[List[float]] = [[], [], []]
    normal: List[List[float]] = [[], [], []]
    normal_worst_case: List[List[float]] = [[], [], []]
    for j, antal_år in enumerate([20, 30, 40]):
        for start in range(0, len(data) - 950 - antal_år * 12):
            askdepot, normaldepot, normaldepot_worst_case = depoter(start_kapital)
            for i in range(start + 950, start + 950 + 12 * antal_år):
                udbytteafkast = askdepot.ETFer[0].kurs * data[i + 1, 4]
                kursafkast = askdepot.ETFer[0].kurs * data[i + 1, 3]
                askdepot.afkast_månedlig([kursafkast], [udbytteafkast])
                normaldepot.afkast_månedlig([kursafkast], [udbytteafkast])
                normaldepot_worst_case.afkast_månedlig([kursafkast], [udbytteafkast])
                if i % 12 == 0:
                    normaldepot.skatteklasse.progressionsgrænse *= 1 + 0.02
            ask[j].append(askdepot.total_salgsværdi())
            normal[j].append(normaldepot.total_salgsværdi())
            normal_worst_case[j].append(normaldepot_worst_case.total_salgsværdi())

    ask_cagr = []
    normal_cagr = []
    normal_worst_case_cagr = []
    for år, depot1, depot2, depot3 in zip([20, 30, 40], ask, normal, normal_worst_case):
        ask_cagr.append(formler.CAGR(start_kapital, np.array(depot1), år))
        normal_cagr.append(formler.CAGR(start_kapital, np.array(depot2), år))
        normal_worst_case_cagr.append(formler.CAGR(start_kapital, np.array(depot3), år))
    ask_q = []
    normal_q = []
    normal_worst_case_q = []
    for cagr1, cagr2, cagr3 in zip(ask_cagr, normal_cagr, normal_worst_case_cagr):
        q1 = []
        q2 = []
        q3 = []
        for j in range(0, 1001):
            q1.append(np.quantile(cagr1, j * 0.001))
            q2.append(np.quantile(cagr2, j * 0.001))
            q3.append(np.quantile(cagr3, j * 0.001))
        ask_q.append(q1)
        normal_q.append(q2)
        normal_worst_case_q.append(q3)
    return ask_q, normal_q, normal_worst_case_q

The comparison between ASK and taxation on realisation is now run and plotted.

plt.rc("font", size=12)
plt.rc("axes", titlesize=12)
plt.rc("axes", labelsize=12)
plt.rc("xtick", labelsize=12)
plt.rc("ytick", labelsize=12)
plt.rc("legend", fontsize=12)
plt.rc("figure", titlesize=12)

ask_fraktil, mormal_fraktil, normal_worst_case_fraktil = kør_model(102300)
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(6, 8), sharex=True)
for depot, navn in zip([ask_fraktil, mormal_fraktil, normal_worst_case_fraktil], ["ASK", "27%/42%", "42%"]):
    for ax, serie in zip([ax1, ax2, ax3], depot):
        ax.plot(np.linspace(0.0, 1, 1001), serie, label=navn, linewidth=3)
ax1.legend()
for ax, år_inv in zip([ax1, ax2, ax3], [20, 30, 40]):
    ax.set_xticks(np.linspace(0.0, 1.0, 11))
    ax.grid(which="minor")
    ax.grid(which="major")
    ax.set_ylabel("CAGR")
    ax.set_title(f"{år_inv:1.0f} år investering")
ax3.set_xlabel("Fraktil")
plt.tight_layout()
plt.savefig("ask_vs_real.svg")

This gives the following plot.

The graph shows the CAGR after costs for the three different portfolios. Depending on how much of the progression limit is used up by other portfolios, taxation on realisation will lie between 27%/42% and 42%. Note that even after 40 years, ASK is an advantage compared to investing with taxation on realisation.

It is not surprising that ASK gives a better return even after long periods of 40+ years. This can be compared with what one gets from an average model.

The simple form for mark-to-market taxation is:

\[L = k\cdot\left(1+a\cdot\left(1-s\right)\right)^{y}\]

with \(k\) the starting capital, \(a\) the return per year, \(s\) the tax rate and \(y\) the number of years.

The simple form for taxation on realisation is:

\[R = k\cdot\left(1+a\right)^{y}-\left(k\cdot\left(1+a\right)^{y}-k\right)\cdot q\]

with \(q\) the tax rate.

These two can now be set equal to find at which year the switch occurs:

\[\begin{eqnarray} k\cdot\left(1+a\cdot\left(1-s\right)\right)^{y}&=&k\cdot\left(1+a\right)^{y}-\left(k\cdot\left(1+a\right)^{y}-k\right)\cdot q \\ k\cdot\left(1+a-a\cdot s\right)^{y}&=&k\cdot\left(\left(1+a\right)^{y}-\left(1+a\right)^{y}\cdot q+q\right) \\ \left(1+a-a\cdot s\right)^{y}&=&\left(1+a\right)^{y}\cdot\left(1-q\right)+q \end{eqnarray}\]

I believe the above can be regarded as a transcendental equation, so from here on it is only solved numerically.

Now define the equation so that it is solved approximately, and plot the solution:

def gennemsnit_ask_vs_real(cagr):
    """
    Regner forskellen i depotstørrelse mellem et lagerbeskattet,
    og et realisationsbeskattet depot.
    Dette er baseret på en gennemsnits model for afkast.

    Args:
      cagr: CAGR

    Returns:
      Forskellen mellem lagerbeskattet og realisationsbeskattet.
    """
    s = 0.17
    q = 0.42
    y = np.linspace(0, 200, 10000)
    return (1 + cagr - cagr * s) ** y - (1 + cagr) ** y * (1 - q) - q


As = []
years = []
for m in range(20, 201):
    As.append(m / 1000)
    f = gennemsnit_ask_vs_real(m / 1000)
    years.append(len(f[f - 10 ** -12 > 0]) / 10000 * 200)
fig, ax1 = plt.subplots(1, 1, figsize=(6, 4), sharex=True)
ax1.plot(As, years, linewidth=3)
ax1.grid(which="minor")
ax1.grid(which="major")
ax1.set_ylabel("År")
ax1.set_xlabel("CAGR før skat")
plt.tight_layout()
plt.savefig("ask_vs_real_gennemsnit.svg")

This gives the following plot.

It can be seen that for a CAGR (before tax) of 7.5%, it would take a little over 40 years before the compound interest from taxation on realisation would give an advantage. This supports the result from the complicated model, namely that ASK “always” pays off.

The full code can be found here: ask_vs_realisation.py

If you enjoyed this post you can donate a coffee , if you like :)