Talk:Comparison of accumulating ETFs and distributing ETFs

From Bogleheads
Jump to navigation Jump to search

Python table generation

The tables in this page were generated using a Python script. The source code for this script is below.

If Python is not available locally, run the script online with Repl.it, or similar. For example, for Repl.it, copy-and-paste the code into the source code editing window (left panel), and press "Run>". The output window (right panel) will contain the table formatted in wikitext.

Code listing
  1 #!/usr/bin/python3
  2 # -*- coding: utf-8 -*-
  3 # ... or run online through https://repl.it/languages/python3
  4 
  5 import math
  6 
  7 DIST_DIV = 0.03
  8 DIST_GAIN = 0.07
  9 YEARS = range(1, 11)
 10 
 11 
 12 class Holding:
 13     def __init__(self, shares, nav, div, gain):
 14         self.shares = shares
 15         self.nav = nav
 16         self.cash = 0.0
 17         self.__div = div
 18         self.__gain = gain
 19 
 20     def balance(self):
 21         return self.shares * self.nav
 22 
 23     def annual(self):
 24         dividend = (self.shares * self.nav) * self.__div
 25         self.nav *= 1 + self.__gain
 26         return dividend
 27 
 28     def buy(self, value):
 29         frac_share, new_shares = math.modf(value / self.nav)
 30         self.cash += frac_share * self.nav
 31         self.shares += new_shares
 32         while self.cash > self.nav:
 33           self.shares += 1
 34           self.cash -= self.nav
 35 
 36     def sell(self, value):
 37         sale_shares = math.floor(value / self.nav)
 38         self.cash = sale_shares * self.nav
 39         self.shares -= sale_shares
 40 
 41 
 42 dist = Holding(10000, 1.0, DIST_DIV, DIST_GAIN)
 43 accm = Holding(10000, 1.0, 0.0, DIST_DIV + DIST_GAIN)
 44 
 45 
 46 # Returns: (year,
 47 #          accm.nav, accm.shares, accm.balance(), accm.cash,
 48 #          dist.nav, dist.shares, dist.balance(), dist.cash)
 49 
 50 def record(year=None):
 51     return (year,
 52          accm.nav, accm.shares, accm.balance(), accm.cash,
 53          dist.nav, dist.shares, dist.balance(), dist.cash)
 54 
 55 
 56 # Returns: [(year,
 57 #           accm.nav, accm.shares, accm.balance(), accm.cash,
 58 #           dist.nav, dist.shares, dist.balance(), dist.cash), ...]
 59 
 60 def accumulation():
 61     data = [record()]
 62     for year in YEARS:
 63         dividend = dist.annual()
 64         accm.annual()
 65         dist.buy(dividend)
 66         data += [record(year)]
 67     return data
 68 
 69 
 70 # Returns: [(year,
 71 #           accm.nav, accm.shares, accm.balance(), accm.cash,
 72 #           dist.nav, dist.shares, dist.balance(), dist.cash), ...],
 73 #           accm_total, dist_total
 74 
 75 def decumulation():
 76     data = [record()]
 77     (dist_total, accm_total) = (0.0, 0.0)
 78     for year in YEARS:
 79         dist.cash = dist.annual()
 80         accm.annual()
 81         accm.sell(dist.cash)
 82         data += [record(year)]
 83         accm_total += accm.cash
 84         dist_total += dist.cash
 85     return data, accm_total, dist_total
 86 
 87 
 88 # Returns: ['line', ...] in wikitable format
 89 
 90 def format_wiki_table(table_data, phase):
 91 
 92     def __currency(value, places):
 93         string = ('€{:,.%df}' % places).format(value)
 94         if value > 100 and value < 1000:
 95             return '{{0}}' + string
 96         else:
 97             return string
 98 
 99     if phase == 'accumulation':
100         cash = 'Cash balance'
101     elif phase == 'decumulation':
102         cash = 'Cash withdrawal'
103 
104     table = [
105         '{| class="wikitable mw-datatable" style="text-align: center;"',
106         '|+ Comparison of accumulating and distributing ETF investor outcomes,'
107         ' %s phase' % phase,
108         '|-',
109         '! scope="col" colspan="1" |',
110         '! scope="col" colspan="4" | Accumulating ETF investor',
111         '! scope="col" colspan="4" | Distributing ETF investor',
112         '|-',
113         '! scope="col" | Year',
114         '! scope="col" | ACCM net asset value',
115         '! scope="col" | Shares held',
116         '! scope="col" | Holding balance',
117         '! scope="col" | %s' % cash,
118         '! scope="col" | DIST net asset value',
119         '! scope="col" | Shares held',
120         '! scope="col" | Holding balance',
121         '! scope="col" | %s' % cash
122         ]
123     for elements in table_data:
124         table += ['|-']
125         year = elements[0]
126         accm_cash = ' || %s' % __currency(elements[4], 2) if year else ' ||'
127         dist_cash = ' || %s' % __currency(elements[8], 2) if year else ' ||'
128         table += [('! %d' % year if year else '!')]
129         table += [' | %s' % __currency(elements[1], 4)
130                   + ' || %d' % elements[2]
131                   + ' || %s' % __currency(elements[3], 2)
132                   + accm_cash
133                   + ' || %s' % __currency(elements[5], 4)
134                   + ' || %d' % elements[6]
135                   + ' || %s' % __currency(elements[7], 2)
136                   + dist_cash
137                  ]
138     table += ['|}']
139     return table
140 
141 
142 def main():
143     data = accumulation()
144     for line in format_wiki_table(data, 'accumulation'):
145         print(line)
146     print()
147 
148     (dist.cash, accm.cash) = (0.0, 0.0)
149     (data, accm_total, dist_total) = decumulation()
150     for line in format_wiki_table(data, 'decumulation'):
151         print(line)
152     print()
153 
154     print('Dist total withdrawal =', round(dist_total, 2))
155     print('Accm total withdrawal =', round(accm_total, 2))
156     print()
157 
158     print('Dist investor result =',
159           round(dist.shares * dist.nav + dist_total, 2))
160     print('Accm investor result =',
161           round(accm.shares * accm.nav + accm_total, 2))
162 
163 
164 if __name__ == '__main__':
165     main()

--TedSwippet 09:57, 18 August 2019 (UTC)
Updated: --TedSwippet 15:21, 30 December 2020 (UTC)