Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import matplotlib.pyplot as plt
- DEGIRO_FIXED = 1 # Best-case Core Selection transaction cost (conditions apply)
- DEGIRO_FIXED_OTHER_DIR = 2 # Degiro cost for Core Selection transactions <1000 euro or different direction of first transaction
- TOB = 0.0012 # 0.12% Taks Op Beursverrichtingen - Tax on orders for ETF's outside of EU (e.g. IWDA/EMIM in Ireland)
- VRIJSTELLING = 10000 # First 10.000 euro untaxed
- SOLIDARITEITS_BIJDRAGE = 0.1 # 10%
- print(f"\n\nThe following constants are given:\n\tFixed price at buying: {DEGIRO_FIXED} euro\n\tFixed price at selling: {DEGIRO_FIXED_OTHER_DIR} euros")
- print(f"\tTOB: {100.0*TOB}%\n\tYearly Vrijstelling: {VRIJSTELLING} euros\n\tSolidariteitsbijdrage: {SOLIDARITEITS_BIJDRAGE*100}%")
- START_CAPITAL=200000
- MONTHLY_ADDITION=1000
- YEARLY_RETURN=5.0
- print(f"\n\nSimulated scenario: {START_CAPITAL} at start, {MONTHLY_ADDITION} added monthly, {YEARLY_RETURN}% yearly.")
- ### SIMULATION FUNCTIONS
- # The parameter=xxx is a default value, which can be overwritten when calling the function.
- def simulate(start_capital=200e3, monthly_add=1000, yearly_percent=7.0, sell_after_years=4, verbose=False, use_new_rules = True, rebuy_yearly=True):
- # We start the fund/stock price at 100.0 euro. This number is irrelevant, as allow fractional shares are allowed.
- start_stock_price = 100.0
- start_stock_amount = start_capital / start_stock_price
- stock_dict = {start_stock_price:start_stock_amount}
- monthly_return = (0.01*yearly_percent+1)**(1/12) - 1 # Yearly return -> Monthly return
- total_stock_amount = start_stock_amount # amount of shares of the fund/stock that are currently owned
- total_added_cost = 0 # Start out at 0 costs
- # total_current_price is 'actual money spent to buy these shares' (from the Bank's perspective, rebuying increases this!)
- # When rebuying, this increases, even though from the investor's perspective it does not.
- # Costs are considered separately, in the total_added_cost variable.
- total_current_price = start_capital
- total_current_value = start_stock_price * total_stock_amount
- month_id = 0 # Current month, starting at 0. Also used outside the loop, so defined here.
- current_stock_price = start_stock_price # This will be increased monthly using the monthly_return
- yearly_buy = [] # If performing the yearly rebuying strategy, the total value of the rebought stock is stored here
- for month_id in range(int(sell_after_years*12)):
- current_stock_price *= (1+monthly_return) # Increase stock price by monthly return
- stocks_to_add = monthly_add/current_stock_price # Calculate amount of new shares that can be bought
- total_added_cost += DEGIRO_FIXED + TOB*monthly_add # Add TOB and brokerage costs
- stock_dict[current_stock_price] = stocks_to_add # Add bought stocks to the stock dictionary
- total_stock_amount += stocks_to_add # Add to total amount of stocks
- total_current_value = total_stock_amount*current_stock_price # Increase total value accordingly
- total_current_price += stocks_to_add*current_stock_price # Increase total buying price
- if rebuy_yearly and (month_id % 12 == 11): # If the rebuying strategy is used, and 12 months have passed, perform the rebuying:
- if verbose:
- print(f"\n# Structuring action happening, month {month_id}. Current stock price: {round(current_stock_price,2)}.")
- print(f"Total sell value before rebuying: {round(total_current_value,2)}")
- print(f"Total buy value before rebuying: {round(total_current_price,2)}")
- print(f"Total extra cost before rebuying: {round(total_added_cost,2)}")
- profit_to_limit = 0 # This will be increased using the FIFO strategy, until the VRIJSTELLING value is reached
- # Here we make a low-to-high stocklist. As we have a monotonically rising stock price, this equals FIFO.
- # This must be updated to be first-to-last (maybe just no sorting?) when a more variable simulation is implemented
- ordered_stock_buy_prices = []
- for buy_price in stock_dict.keys():
- ordered_stock_buy_prices.append(buy_price)
- ordered_stock_buy_prices.sort()
- rebuy_sell_dict = {} # This will be used to store the stocks that need to be sold for the rebuy
- VRIJSTELLING_reached = False # Did we reach the VRIJSTELLING goal? Or do we have insufficient profit?
- for historical_stock_price in ordered_stock_buy_prices:
- # gains_on_this calculates the maximum profit we can achieve by selling all stock we bought at this historical price.
- gains_on_this = (current_stock_price*stock_dict[historical_stock_price]) - (stock_dict[historical_stock_price]*historical_stock_price)
- if profit_to_limit + gains_on_this > VRIJSTELLING:
- # If we sold all our stock at this historical price, we'd have more profit than the VRIJSTELLING.
- # So we only need to sell part of it.
- VRIJSTELLING_reached = True
- amount_to_sell = (VRIJSTELLING-profit_to_limit)/(current_stock_price-historical_stock_price)
- rebuy_sell_dict[historical_stock_price] = amount_to_sell
- gains_on_this = (current_stock_price-historical_stock_price)*amount_to_sell
- # print(f"Intending to sell {round(amount_to_sell,2)} @{round(historical_stock_price,2)}. Gains: {round(gains_on_this,2)}. Current value: {round(stock_price*stock_dict[historical_stock_price],2)}")
- profit_to_limit += gains_on_this
- # We should be ready, and need not consider other historical buys. Time to sell them!
- break
- else:
- # We'll not reach the VRIJSTELLING limit by selling all of our stock that we bought at this historical price
- # So we sell it all!
- rebuy_sell_dict[historical_stock_price] = stock_dict[historical_stock_price]
- # print(f"Intending to sell {round(stock_dict[historical_stock_price],2)} @{round(historical_stock_price,2)}. Gains: {round(gains_on_this,2)}. Current value: {round(stock_price*stock_dict[historical_stock_price],2)}")
- profit_to_limit += gains_on_this
- if not VRIJSTELLING_reached and verbose:
- # We didn't reach VRIJSTELLING amount of profit. Highly tax-efficient, for now!
- print(f"This rebuying session (Year {int(1+month_id/12)}) sold all stock!")
- ### Rebuy action
- ## Sell section
- #available_for_buying = Total value extracted by selling
- available_for_buying = 0
- for historical_stock_price in rebuy_sell_dict:
- sell_amount = rebuy_sell_dict[historical_stock_price]
- total_stock_amount -= sell_amount
- stock_dict[historical_stock_price] -= sell_amount
- total_current_price -= sell_amount*historical_stock_price # Deducted from total buying price
- available_for_buying += sell_amount*current_stock_price
- if verbose:
- print(f"Sold {round(sell_amount,2)} stocks @{round(current_stock_price,2)}, that were bought @{round(historical_stock_price,2)}.")
- if verbose:
- print(f"Year {int(month_id/12)}. Available for taxless selling (rebuying here): {int(available_for_buying)}")
- yearly_buy.append(available_for_buying)
- ## Buy section
- stocks_to_add = available_for_buying/current_stock_price
- # print(f"Buying {round(stocks_to_add,2)} stocks @{round(stock_price,2)}.")
- stock_dict[current_stock_price] += stocks_to_add
- total_stock_amount += stocks_to_add
- total_current_value = total_stock_amount*current_stock_price
- total_current_price += stocks_to_add*current_stock_price # Added to total buying price
- # Costs of rebuying: 1x buy, 1x sell (other direction higher price) + 2x TOB on the total value
- total_added_cost += DEGIRO_FIXED + DEGIRO_FIXED_OTHER_DIR + 2*TOB*available_for_buying
- if verbose:
- # print(f"stock_dict after buying: {stock_dict}")
- print(f"Total sell value after rebuying: {round(total_current_value,2)}")
- print(f"Total buy value after rebuying: {round(total_current_price,2)}")
- print(f"Total extra cost after rebuying: {round(total_added_cost,2)}")
- if verbose:
- print(f"Selling after {int(1+month_id/12)} years.")
- absolute_gains = total_current_value - total_current_price
- # print(f"Theoretical gains before taxes and brokerage costs: {round(absolute_gains,2)}")
- if use_new_rules and (absolute_gains > VRIJSTELLING):
- sol_bijdrage = SOLIDARITEITS_BIJDRAGE*(absolute_gains-VRIJSTELLING)
- else:
- sol_bijdrage = 0
- total_added_cost += DEGIRO_FIXED + TOB*total_current_value + sol_bijdrage
- value_after_costs = total_current_value - total_added_cost
- total_invested_money = ((monthly_add*sell_after_years*12)+start_capital)
- profit_investor_viewpoint = (value_after_costs-total_invested_money)
- if verbose:
- print(f"Total sell value: {round(total_current_value,2)}")
- print(f"Total 'buy' value (as government sees it): {round(total_current_price,2)}")
- print(f"Total 'buy' value (as investor sees it): {round(total_invested_money,2)}")
- print(f"Total added cost at sale (tax and brokerage): {round(total_added_cost,2)}")
- print(f"Hits bank account: {round(value_after_costs)}")
- return([value_after_costs, profit_investor_viewpoint, yearly_buy])
- ### This function stores a matplotlib plot as PDF
- def storefig(plt,plot_name):
- plt.grid(True)
- fig = plt.gcf()
- horizontal_size = 20
- fig.set_size_inches((horizontal_size,int((9/16)*horizontal_size)),forward=False)
- fig.savefig(plot_name+'.pdf',dpi=500)
- plt.clf()
- ### This function creates a graph using the given inputs
- def generate_points(values_matrix, plot_name, xlabel="Year", ylabel="Profit", title = "New Tax comparison (DEGIRO pricing core selection july 2025)", legend=["Current rules","Unstructured","Yearly rebuying (10k gain max)"], log_scale=False):
- plt.title(title)
- plt.xlabel(xlabel)
- plt.ylabel(ylabel)
- for [x_list, y_list] in values_matrix:
- plt.plot(x_list,y_list, 'o')
- if log_scale:
- plt.yscale('log',base=10)
- plt.legend(legend)
- plt.show()
- storefig(plt, plot_name)
- ### SIMULATIONS
- verbosity = False # Set to True to see the intermediary values
- #Used in graph titles
- sim_summary = f"Portfolio size @start: {START_CAPITAL}, monthly addition: {MONTHLY_ADDITION}, expected yearly return: {YEARLY_RETURN}"
- # This simulation compares the scenarios for a single selloff time, no graphs
- simulate_single_selloffpoint = False # Change to True to perform this simulation
- # This simulation makes a graph of the yearly amount of assets that needs to be rebought for an optimal strategy
- show_paymentsize_graph = False # Change to True to see graph of payment sizes
- # This simulation makes a graph where different expected yearly returns are compared.
- compare_returnrates_graph = False
- returnrates_to_compare = [5.0, 7.0]
- # This simulation makes a graph where different expected yearly returns are compared.
- compare_returnrates_absolute_returns_graph = False
- returnrates_to_compare = [7.0]
- # This simulation compares the scenarios for a single selloff time, no graphs
- if simulate_single_selloffpoint:
- sell_after_years = 15 # Change this value to see the impact on a time certain horizon
- print(f"The following scenarios sell everything after {sell_after_years} years.")
- print(f"\n### Current scenario:")
- [value_after_costs, profit_investor_viewpoint, yearly_buy] = simulate(START_CAPITAL, MONTHLY_ADDITION, YEARLY_RETURN, sell_after_years, use_new_rules=False, rebuy_yearly=False, verbose = verbosity)
- print(f"Value after costs: {int(value_after_costs)}, Profit: {int(profit_investor_viewpoint)}.\nYearly buys: {[int(x) for x in yearly_buy]}")
- print(f"\n### New tax, unchanged strategy scenario:")
- [value_after_costs, profit_investor_viewpoint, yearly_buy] = simulate(START_CAPITAL, MONTHLY_ADDITION, YEARLY_RETURN, sell_after_years, use_new_rules=True, rebuy_yearly=False, verbose = verbosity)
- print(f"Value after costs: {int(value_after_costs)}, Profit: {int(profit_investor_viewpoint)}.\nYearly buys: {[int(x) for x in yearly_buy]}")
- print(f"\n### New tax, rebuying strategy scenario:")
- [value_after_costs, profit_investor_viewpoint, yearly_buy] = simulate(START_CAPITAL, MONTHLY_ADDITION, YEARLY_RETURN, sell_after_years, use_new_rules=True, rebuy_yearly=True, verbose = verbosity)
- print(f"Value after costs: {int(value_after_costs)}, Profit: {int(profit_investor_viewpoint)}.\nYearly buys: {[int(x) for x in yearly_buy]}")
- # This simulation makes a graph of the yearly amount of assets that needs to be rebought for an optimal strategy
- if show_paymentsize_graph:
- max_years_to_simulate = 30
- [value_after_costs, profit_investor_viewpoint, yearly_buy] = simulate(START_CAPITAL, MONTHLY_ADDITION, YEARLY_RETURN, max_years_to_simulate, use_new_rules=True, rebuy_yearly=True, verbose = verbosity)
- print(f"Value after costs: {int(value_after_costs)}, Profit: {int(profit_investor_viewpoint)}.\nYearly buys: {[int(x) for x in yearly_buy]}")
- generate_points([[[year for year in range(max_years_to_simulate)], yearly_buy]],'PaymentSize', xlabel="Year",ylabel="Rebuy value",legend=['Yearly payment'],title=f"Optimal strategy yearly sell amount\n{sim_summary}")
- # This simulation makes a graph where different expected yearly returns are compared.
- if compare_returnrates_graph:
- sim_summary = f"Portfolio size @start: {START_CAPITAL}, monthly addition: {MONTHLY_ADDITION}"
- legend = []
- norebuy_matrix = []
- rebuy_matrix = []
- maxyear = 30
- for returnrate in returnrates_to_compare:
- norebuy_values = []
- rebuy_values = []
- for year_amount in range(1,maxyear):
- # Calculate and store profit for the 3 strategies, normalize to the 'old/current/pre-2026' strategy
- current_strat_profit = simulate(START_CAPITAL, MONTHLY_ADDITION, returnrate, sell_after_years=year_amount, use_new_rules=False, rebuy_yearly=False, verbose = verbosity)[1]
- norebuy_strat_profit = simulate(START_CAPITAL, MONTHLY_ADDITION, returnrate, sell_after_years=year_amount, use_new_rules=True, rebuy_yearly=False, verbose = verbosity)[1]
- rebuy_strat_profit = simulate(START_CAPITAL, MONTHLY_ADDITION, returnrate, sell_after_years=year_amount, use_new_rules=True, rebuy_yearly=True, verbose = verbosity)[1]
- norebuy_strat_normalized = 100.0*norebuy_strat_profit/current_strat_profit
- norebuy_values.append(norebuy_strat_normalized)
- rebuy_strat_normalized = 100.0*rebuy_strat_profit/current_strat_profit
- rebuy_values.append(rebuy_strat_normalized)
- norebuy_matrix.append(norebuy_values)
- rebuy_matrix.append(rebuy_values)
- legend.append(f"No Rebuy - {returnrate}%")
- legend.append(f"Rebuy - {returnrate}%")
- years = [year for year in range(1,maxyear)]
- datamatrix = []
- for rate_index in range(len(returnrates_to_compare)):
- datamatrix.append([years,norebuy_matrix[rate_index]])
- datamatrix.append([years,rebuy_matrix[rate_index]])
- generate_points(datamatrix, 'ReturnRateComparison', legend=legend, title=f"Return Rate comparison\n{sim_summary}", xlabel="Selloff Year",ylabel="Profit as % of pre-ruling")
- # This simulation makes a graph where different expected yearly returns are compared.
- if compare_returnrates_absolute_returns_graph:
- sim_summary = f"Portfolio size @start: {START_CAPITAL}, monthly addition: {MONTHLY_ADDITION}"
- legend = []
- currentstrat_matrix = []
- norebuy_matrix = []
- rebuy_matrix = []
- maxyear = 30
- for returnrate in returnrates_to_compare:
- currentstrat_values = []
- norebuy_values = []
- rebuy_values = []
- for year_amount in range(1,maxyear):
- # Calculate and store profit for the 3 strategies, normalize to the 'old/current/pre-2026' strategy
- current_strat_profit = simulate(START_CAPITAL, MONTHLY_ADDITION, returnrate, sell_after_years=year_amount, use_new_rules=False, rebuy_yearly=False, verbose = verbosity)[1]
- norebuy_strat_profit = simulate(START_CAPITAL, MONTHLY_ADDITION, returnrate, sell_after_years=year_amount, use_new_rules=True, rebuy_yearly=False, verbose = verbosity)[1]
- rebuy_strat_profit = simulate(START_CAPITAL, MONTHLY_ADDITION, returnrate, sell_after_years=year_amount, use_new_rules=True, rebuy_yearly=True, verbose = verbosity)[1]
- currentstrat_values.append(current_strat_profit)
- norebuy_values.append(norebuy_strat_profit)
- rebuy_values.append(rebuy_strat_profit)
- currentstrat_matrix.append(currentstrat_values)
- norebuy_matrix.append(norebuy_values)
- rebuy_matrix.append(rebuy_values)
- legend.append(f"Current Strat - {returnrate}%")
- legend.append(f"No Rebuy - {returnrate}%")
- legend.append(f"Rebuy - {returnrate}%")
- years = [year for year in range(1,maxyear)]
- datamatrix = []
- for rate_index in range(len(returnrates_to_compare)):
- datamatrix.append([years,currentstrat_matrix[rate_index]])
- datamatrix.append([years,norebuy_matrix[rate_index]])
- datamatrix.append([years,rebuy_matrix[rate_index]])
- generate_points(datamatrix, 'ReturnRateComparison-abs', legend=legend, title=f"Return Rate comparison absolute returns\n{sim_summary}", xlabel="Selloff Year",ylabel="Absolute Profit")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement