Advertisement
Guest User

Solidariteitsbijdrage Simulator

a guest
Jul 2nd, 2025
66
0
6 days
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 17.86 KB | None | 0 0
  1. import matplotlib.pyplot as plt
  2.  
  3. DEGIRO_FIXED = 1 # Best-case Core Selection transaction cost (conditions apply)
  4. DEGIRO_FIXED_OTHER_DIR = 2 # Degiro cost for Core Selection transactions <1000 euro or different direction of first transaction
  5. TOB = 0.0012 # 0.12% Taks Op Beursverrichtingen - Tax on orders for ETF's outside of EU (e.g. IWDA/EMIM in Ireland)
  6. VRIJSTELLING = 10000 # First 10.000 euro untaxed
  7. SOLIDARITEITS_BIJDRAGE = 0.1 # 10%
  8.  
  9. 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")
  10. print(f"\tTOB: {100.0*TOB}%\n\tYearly Vrijstelling: {VRIJSTELLING} euros\n\tSolidariteitsbijdrage: {SOLIDARITEITS_BIJDRAGE*100}%")
  11.  
  12. START_CAPITAL=200000
  13. MONTHLY_ADDITION=1000
  14. YEARLY_RETURN=5.0
  15. print(f"\n\nSimulated scenario: {START_CAPITAL} at start, {MONTHLY_ADDITION} added monthly, {YEARLY_RETURN}% yearly.")
  16.  
  17. ### SIMULATION FUNCTIONS
  18.  
  19. # The parameter=xxx is a default value, which can be overwritten when calling the function.
  20. 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):
  21.     # We start the fund/stock price at 100.0 euro. This number is irrelevant, as allow fractional shares are allowed.
  22.     start_stock_price = 100.0
  23.     start_stock_amount = start_capital / start_stock_price
  24.     stock_dict = {start_stock_price:start_stock_amount}
  25.     monthly_return = (0.01*yearly_percent+1)**(1/12) - 1 # Yearly return -> Monthly return
  26.  
  27.     total_stock_amount = start_stock_amount # amount of shares of the fund/stock that are currently owned
  28.     total_added_cost = 0 # Start out at 0 costs
  29.     # total_current_price is 'actual money spent to buy these shares' (from the Bank's perspective, rebuying increases this!)
  30.     # When rebuying, this increases, even though from the investor's perspective it does not.
  31.     # Costs are considered separately, in the total_added_cost variable.
  32.     total_current_price = start_capital
  33.     total_current_value = start_stock_price * total_stock_amount
  34.  
  35.     month_id = 0 # Current month, starting at 0. Also used outside the loop, so defined here.
  36.     current_stock_price = start_stock_price # This will be increased monthly using the monthly_return
  37.     yearly_buy = [] # If performing the yearly rebuying strategy, the total value of the rebought stock is stored here
  38.  
  39.     for month_id in range(int(sell_after_years*12)):
  40.         current_stock_price *= (1+monthly_return) # Increase stock price by monthly return
  41.         stocks_to_add = monthly_add/current_stock_price # Calculate amount of new shares that can be bought
  42.         total_added_cost += DEGIRO_FIXED + TOB*monthly_add # Add TOB and brokerage costs
  43.         stock_dict[current_stock_price] = stocks_to_add # Add bought stocks to the stock dictionary
  44.         total_stock_amount += stocks_to_add # Add to total amount of stocks
  45.         total_current_value = total_stock_amount*current_stock_price # Increase total value accordingly
  46.         total_current_price += stocks_to_add*current_stock_price # Increase total buying price
  47.  
  48.         if rebuy_yearly and (month_id % 12 == 11): # If the rebuying strategy is used, and 12 months have passed, perform the rebuying:
  49.             if verbose:
  50.                 print(f"\n# Structuring action happening, month {month_id}. Current stock price: {round(current_stock_price,2)}.")
  51.                 print(f"Total sell value before rebuying: {round(total_current_value,2)}")
  52.                 print(f"Total buy value before rebuying: {round(total_current_price,2)}")
  53.                 print(f"Total extra cost before rebuying: {round(total_added_cost,2)}")
  54.             profit_to_limit = 0 # This will be increased using the FIFO strategy, until the VRIJSTELLING value is reached
  55.            
  56.             # Here we make a low-to-high stocklist. As we have a monotonically rising stock price, this equals FIFO.
  57.             # This must be updated to be first-to-last (maybe just no sorting?) when a more variable simulation is implemented
  58.             ordered_stock_buy_prices = []
  59.             for buy_price in stock_dict.keys():
  60.                 ordered_stock_buy_prices.append(buy_price)
  61.             ordered_stock_buy_prices.sort()
  62.  
  63.             rebuy_sell_dict = {} # This will be used to store the stocks that need to be sold for the rebuy
  64.             VRIJSTELLING_reached = False # Did we reach the VRIJSTELLING goal? Or do we have insufficient profit?
  65.             for historical_stock_price in ordered_stock_buy_prices:
  66.                 # gains_on_this calculates the maximum profit we can achieve by selling all stock we bought at this historical price.
  67.                 gains_on_this = (current_stock_price*stock_dict[historical_stock_price]) - (stock_dict[historical_stock_price]*historical_stock_price)
  68.                 if profit_to_limit + gains_on_this > VRIJSTELLING:
  69.                     # If we sold all our stock at this historical price, we'd have more profit than the VRIJSTELLING.
  70.                     # So we only need to sell part of it.
  71.                     VRIJSTELLING_reached = True
  72.                     amount_to_sell = (VRIJSTELLING-profit_to_limit)/(current_stock_price-historical_stock_price)
  73.                     rebuy_sell_dict[historical_stock_price] = amount_to_sell
  74.                     gains_on_this = (current_stock_price-historical_stock_price)*amount_to_sell
  75.                     # 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)}")
  76.                     profit_to_limit += gains_on_this
  77.                     # We should be ready, and need not consider other historical buys. Time to sell them!
  78.                     break
  79.                 else:
  80.                     # We'll not reach the VRIJSTELLING limit by selling all of our stock that we bought at this historical price
  81.                     # So we sell it all!
  82.                     rebuy_sell_dict[historical_stock_price] = stock_dict[historical_stock_price]
  83.                     # 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)}")
  84.                     profit_to_limit += gains_on_this
  85.  
  86.             if not VRIJSTELLING_reached and verbose:
  87.                 # We didn't reach VRIJSTELLING amount of profit. Highly tax-efficient, for now!
  88.                 print(f"This rebuying session (Year {int(1+month_id/12)}) sold all stock!")
  89.  
  90.             ### Rebuy action
  91.  
  92.             ## Sell section
  93.             #available_for_buying = Total value extracted by selling
  94.             available_for_buying = 0
  95.             for historical_stock_price in rebuy_sell_dict:
  96.                 sell_amount = rebuy_sell_dict[historical_stock_price]
  97.                 total_stock_amount -= sell_amount
  98.                 stock_dict[historical_stock_price] -= sell_amount
  99.                 total_current_price -= sell_amount*historical_stock_price # Deducted from total buying price
  100.                 available_for_buying += sell_amount*current_stock_price
  101.                 if verbose:
  102.                     print(f"Sold {round(sell_amount,2)} stocks @{round(current_stock_price,2)}, that were bought @{round(historical_stock_price,2)}.")
  103.             if verbose:
  104.                 print(f"Year {int(month_id/12)}. Available for taxless selling (rebuying here): {int(available_for_buying)}")
  105.             yearly_buy.append(available_for_buying)
  106.            
  107.             ## Buy section
  108.             stocks_to_add = available_for_buying/current_stock_price
  109.             # print(f"Buying {round(stocks_to_add,2)} stocks @{round(stock_price,2)}.")
  110.             stock_dict[current_stock_price] += stocks_to_add
  111.             total_stock_amount += stocks_to_add
  112.             total_current_value = total_stock_amount*current_stock_price
  113.             total_current_price += stocks_to_add*current_stock_price # Added to total buying price
  114.  
  115.             # Costs of rebuying: 1x buy, 1x sell (other direction higher price) + 2x TOB on the total value
  116.             total_added_cost += DEGIRO_FIXED + DEGIRO_FIXED_OTHER_DIR + 2*TOB*available_for_buying
  117.  
  118.             if verbose:
  119.                 # print(f"stock_dict after buying: {stock_dict}")
  120.                 print(f"Total sell value after rebuying: {round(total_current_value,2)}")
  121.                 print(f"Total buy value after rebuying: {round(total_current_price,2)}")
  122.                 print(f"Total extra cost after rebuying: {round(total_added_cost,2)}")
  123.  
  124.     if verbose:
  125.         print(f"Selling after {int(1+month_id/12)} years.")
  126.     absolute_gains = total_current_value - total_current_price
  127.     # print(f"Theoretical gains before taxes and brokerage costs: {round(absolute_gains,2)}")
  128.     if use_new_rules and (absolute_gains > VRIJSTELLING):
  129.         sol_bijdrage = SOLIDARITEITS_BIJDRAGE*(absolute_gains-VRIJSTELLING)
  130.     else:
  131.         sol_bijdrage = 0
  132.     total_added_cost += DEGIRO_FIXED + TOB*total_current_value + sol_bijdrage
  133.     value_after_costs = total_current_value - total_added_cost
  134.     total_invested_money = ((monthly_add*sell_after_years*12)+start_capital)
  135.     profit_investor_viewpoint = (value_after_costs-total_invested_money)
  136.     if verbose:
  137.         print(f"Total sell value: {round(total_current_value,2)}")
  138.         print(f"Total 'buy' value (as government sees it): {round(total_current_price,2)}")
  139.         print(f"Total 'buy' value (as investor sees it): {round(total_invested_money,2)}")
  140.         print(f"Total added cost at sale (tax and brokerage): {round(total_added_cost,2)}")
  141.         print(f"Hits bank account: {round(value_after_costs)}")
  142.  
  143.     return([value_after_costs, profit_investor_viewpoint, yearly_buy])
  144.  
  145. ### This function stores a matplotlib plot as PDF
  146. def storefig(plt,plot_name):
  147.     plt.grid(True)
  148.     fig = plt.gcf()
  149.     horizontal_size = 20
  150.     fig.set_size_inches((horizontal_size,int((9/16)*horizontal_size)),forward=False)
  151.     fig.savefig(plot_name+'.pdf',dpi=500)
  152.     plt.clf()
  153.  
  154. ### This function creates a graph using the given inputs
  155. 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):
  156.     plt.title(title)
  157.     plt.xlabel(xlabel)
  158.     plt.ylabel(ylabel)
  159.     for [x_list, y_list] in values_matrix:    
  160.         plt.plot(x_list,y_list, 'o')
  161.     if log_scale:
  162.         plt.yscale('log',base=10)
  163.     plt.legend(legend)
  164.     plt.show()
  165.     storefig(plt, plot_name)
  166.  
  167. ### SIMULATIONS
  168.  
  169. verbosity = False # Set to True to see the intermediary values
  170. #Used in graph titles
  171. sim_summary = f"Portfolio size @start: {START_CAPITAL}, monthly addition: {MONTHLY_ADDITION}, expected yearly return: {YEARLY_RETURN}"
  172.  
  173. # This simulation compares the scenarios for a single selloff time, no graphs
  174. simulate_single_selloffpoint = False # Change to True to perform this simulation
  175.  
  176. # This simulation makes a graph of the yearly amount of assets that needs to be rebought for an optimal strategy
  177. show_paymentsize_graph = False # Change to True to see graph of payment sizes
  178.  
  179. # This simulation makes a graph where different expected yearly returns are compared.
  180. compare_returnrates_graph = False
  181. returnrates_to_compare = [5.0, 7.0]
  182.  
  183. # This simulation makes a graph where different expected yearly returns are compared.
  184. compare_returnrates_absolute_returns_graph = False
  185. returnrates_to_compare = [7.0]
  186.  
  187. # This simulation compares the scenarios for a single selloff time, no graphs
  188. if simulate_single_selloffpoint:
  189.     sell_after_years = 15 # Change this value to see the impact on a time certain horizon
  190.     print(f"The following scenarios sell everything after {sell_after_years} years.")
  191.     print(f"\n### Current scenario:")
  192.     [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)
  193.     print(f"Value after costs: {int(value_after_costs)}, Profit: {int(profit_investor_viewpoint)}.\nYearly buys: {[int(x) for x in yearly_buy]}")
  194.     print(f"\n### New tax, unchanged strategy scenario:")
  195.     [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)
  196.     print(f"Value after costs: {int(value_after_costs)}, Profit: {int(profit_investor_viewpoint)}.\nYearly buys: {[int(x) for x in yearly_buy]}")
  197.     print(f"\n### New tax, rebuying strategy scenario:")
  198.     [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)
  199.     print(f"Value after costs: {int(value_after_costs)}, Profit: {int(profit_investor_viewpoint)}.\nYearly buys: {[int(x) for x in yearly_buy]}")
  200.  
  201. # This simulation makes a graph of the yearly amount of assets that needs to be rebought for an optimal strategy
  202. if show_paymentsize_graph:
  203.     max_years_to_simulate = 30
  204.     [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)
  205.     print(f"Value after costs: {int(value_after_costs)}, Profit: {int(profit_investor_viewpoint)}.\nYearly buys: {[int(x) for x in yearly_buy]}")
  206.     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}")
  207.  
  208. # This simulation makes a graph where different expected yearly returns are compared.
  209. if compare_returnrates_graph:
  210.     sim_summary = f"Portfolio size @start: {START_CAPITAL}, monthly addition: {MONTHLY_ADDITION}"
  211.     legend = []
  212.     norebuy_matrix = []
  213.     rebuy_matrix = []
  214.     maxyear = 30
  215.  
  216.     for returnrate in returnrates_to_compare:
  217.         norebuy_values = []
  218.         rebuy_values = []
  219.        
  220.         for year_amount in range(1,maxyear):
  221.             # Calculate and store profit for the 3 strategies, normalize to the 'old/current/pre-2026' strategy
  222.             current_strat_profit = simulate(START_CAPITAL, MONTHLY_ADDITION, returnrate, sell_after_years=year_amount, use_new_rules=False, rebuy_yearly=False, verbose = verbosity)[1]
  223.             norebuy_strat_profit = simulate(START_CAPITAL, MONTHLY_ADDITION, returnrate, sell_after_years=year_amount, use_new_rules=True, rebuy_yearly=False, verbose = verbosity)[1]
  224.             rebuy_strat_profit = simulate(START_CAPITAL, MONTHLY_ADDITION, returnrate, sell_after_years=year_amount, use_new_rules=True, rebuy_yearly=True, verbose = verbosity)[1]
  225.  
  226.             norebuy_strat_normalized = 100.0*norebuy_strat_profit/current_strat_profit
  227.             norebuy_values.append(norebuy_strat_normalized)
  228.             rebuy_strat_normalized = 100.0*rebuy_strat_profit/current_strat_profit
  229.             rebuy_values.append(rebuy_strat_normalized)
  230.         norebuy_matrix.append(norebuy_values)
  231.         rebuy_matrix.append(rebuy_values)
  232.         legend.append(f"No Rebuy - {returnrate}%")
  233.         legend.append(f"Rebuy - {returnrate}%")
  234.  
  235.     years = [year for year in range(1,maxyear)]
  236.     datamatrix = []
  237.     for rate_index in range(len(returnrates_to_compare)):
  238.         datamatrix.append([years,norebuy_matrix[rate_index]])
  239.         datamatrix.append([years,rebuy_matrix[rate_index]])
  240.  
  241.     generate_points(datamatrix, 'ReturnRateComparison', legend=legend, title=f"Return Rate comparison\n{sim_summary}", xlabel="Selloff Year",ylabel="Profit as % of pre-ruling")
  242.  
  243. # This simulation makes a graph where different expected yearly returns are compared.
  244. if compare_returnrates_absolute_returns_graph:
  245.     sim_summary = f"Portfolio size @start: {START_CAPITAL}, monthly addition: {MONTHLY_ADDITION}"
  246.     legend = []
  247.     currentstrat_matrix = []
  248.     norebuy_matrix = []
  249.     rebuy_matrix = []
  250.     maxyear = 30
  251.  
  252.     for returnrate in returnrates_to_compare:
  253.         currentstrat_values = []
  254.         norebuy_values = []
  255.         rebuy_values = []
  256.        
  257.         for year_amount in range(1,maxyear):
  258.             # Calculate and store profit for the 3 strategies, normalize to the 'old/current/pre-2026' strategy
  259.             current_strat_profit = simulate(START_CAPITAL, MONTHLY_ADDITION, returnrate, sell_after_years=year_amount, use_new_rules=False, rebuy_yearly=False, verbose = verbosity)[1]
  260.             norebuy_strat_profit = simulate(START_CAPITAL, MONTHLY_ADDITION, returnrate, sell_after_years=year_amount, use_new_rules=True, rebuy_yearly=False, verbose = verbosity)[1]
  261.             rebuy_strat_profit = simulate(START_CAPITAL, MONTHLY_ADDITION, returnrate, sell_after_years=year_amount, use_new_rules=True, rebuy_yearly=True, verbose = verbosity)[1]
  262.  
  263.             currentstrat_values.append(current_strat_profit)
  264.             norebuy_values.append(norebuy_strat_profit)
  265.             rebuy_values.append(rebuy_strat_profit)
  266.  
  267.         currentstrat_matrix.append(currentstrat_values)
  268.         norebuy_matrix.append(norebuy_values)
  269.         rebuy_matrix.append(rebuy_values)
  270.        
  271.         legend.append(f"Current Strat - {returnrate}%")
  272.         legend.append(f"No Rebuy - {returnrate}%")
  273.         legend.append(f"Rebuy - {returnrate}%")
  274.  
  275.     years = [year for year in range(1,maxyear)]
  276.     datamatrix = []
  277.     for rate_index in range(len(returnrates_to_compare)):
  278.         datamatrix.append([years,currentstrat_matrix[rate_index]])
  279.         datamatrix.append([years,norebuy_matrix[rate_index]])
  280.         datamatrix.append([years,rebuy_matrix[rate_index]])
  281.  
  282.     generate_points(datamatrix, 'ReturnRateComparison-abs', legend=legend, title=f"Return Rate comparison absolute returns\n{sim_summary}", xlabel="Selloff Year",ylabel="Absolute Profit")
  283.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement