itsapastebinacc

Eth staking reward condenser

Jul 11th, 2022 (edited)
300
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 10.90 KB | None | 0 0
  1. # This is provided at absolutely zero guarantee of correctness, obligation or warranty. Use at your own risk.
  2.  
  3. # ***Before using*** You'll need the csv from https://eth2.tax/ put it in the same folder as this script.
  4.  
  5. # RPL minipool handling. E.g. if you have 2 minipools, one at 5% commission, one at 15%, these arrays would look like
  6. #   [{'index': '123456', 'rate': 0.15}, {'index': '234561', 'rate': 0.05}]
  7. # Your commission rate can be found by plugging your validator index into beaconcha.in then
  8. #   clicking on the rocketpool tab. E.g: https://beaconcha.in/validator/123456#rocketpool
  9. # ETH calculations on RPL minipools do: ETH * (0.5 + 0.5 * commission_rate).
  10. #   That is, 50% of the block reward is yours + your commission rate of the remaining 50%
  11.  
  12. rpl_minipool_validator_rates = [{'index': '123456', 'rate': 0.15}]
  13.  
  14. date_table_income_fiat = {}
  15. date_price_table = {}
  16. date_table_income_eth = {}
  17.  
  18. def main():
  19.     lines = read_file("combinedRewardsTable.csv")
  20.     parse_lines_fill_table(lines)
  21.     #fiat_range_condense_table() # Uncomment if you want this, the condense value is adjustable but also
  22.     # please scroll down and read the condenser comment thoroughly to understand what it's doing.
  23.     table_to_csv_file("cointrackingIncome.csv")
  24.  
  25. # Cointracking.info expected CSV order:
  26. #   "Type", "Buy", "Cur.", "Sell", "Cur.", "Fee", "Cur.", "Exchange", "Group", "Comment", "Date"
  27. #   "Income", "0.1106600", "ETH", "", "", "", "", "Validator Stake Reward", "", "", "19.06.2022 13:44:55"
  28. # Update to whatever your tax reporting software expects.
  29. def table_to_csv_file(filename):
  30.     keys = date_table_income_eth.keys()
  31.     keys = sorted(keys)
  32.  
  33.  
  34.     lineOutput = ""
  35.     with open(filename, "w") as file:
  36.         file.write('"Type", "Buy", "Cur.", "Sell", "Cur.", "Fee", "Cur.", "Exchange", "Group", "Comment", "Date"')
  37.         file.write('\n')
  38.         for k in keys:
  39.             dateSplit = k.split("-")
  40.             ct_date = f'{dateSplit[2]}.{dateSplit[1]}.{dateSplit[0]} 00:00:00'
  41.             lineOutput = f'"Income","{date_table_income_eth[k]}","ETH","","","","","Validator Stake Reward","","","{ct_date}"'
  42.             file.write(lineOutput)
  43.             file.write('\n')
  44.  
  45.     totalETHvalue = 0.0
  46.     totalFiatIncome = 0.0
  47.     for k in keys:
  48.         totalETHvalue += date_table_income_eth[k]
  49.         totalFiatIncome += date_table_income_fiat[k]
  50.  
  51.     print(f'\n*** Wrote {len(keys)} lines that look like this: {lineOutput}\n    to {filename}. Total ETH: {totalETHvalue} / Total Fiat: {totalFiatIncome}')
  52.  
  53.     file.close()
  54.  
  55.  
  56. def read_file(filename):
  57.     lines = []
  58.     f = open(filename, encoding="utf8")
  59.     for line in f:
  60.         lines.append(line)
  61.  
  62.     return lines
  63.  
  64. def rpl_minipool_rate(validator_index):
  65.     for pool in rpl_minipool_validator_rates:
  66.         if pool['index'] == validator_index:
  67.             return 0.5 + (0.5 * pool['rate'])
  68.     return 0
  69.  
  70.  
  71. def parse_lines_fill_table(lines):
  72.  
  73.     lines.pop(0) # we dont want the header
  74.     # expected order:
  75.     #   date / validator index / end of day balance / income eth / price fiat / income fiat
  76.     #   "2022-02-16";"123456";"32.1234";"0.0039123456";"1234.566789";"12.34567890"
  77.  
  78.     for line in lines:
  79.         line = line.replace('"', '')
  80.  
  81.         split = line.split(";") # despite it being a csv, we're actually semicolon delimited
  82.         date = split[0]
  83.         validator_index = split[1] # keep as string
  84.         income_eth = float(split[3])
  85.         price_fiat = float(split[4])
  86.         income_fiat = float(split[5])
  87.  
  88.         # RPL minipool initial 'matching' is reported strangely, we will filter these lines out.
  89.         #   2021-11-xx  16  -16
  90.         #   2021-11-xx  16  0
  91.         #   2021-11-xx  32.000951869    16.000951868999998
  92.         if income_eth >= 15 or income_eth <= 0: continue
  93.  
  94.         percentage_rate = rpl_minipool_rate(validator_index)
  95.         if percentage_rate != 0:
  96.             income_fiat *= percentage_rate
  97.             income_eth *= percentage_rate
  98.  
  99.         if date in date_table_income_fiat:
  100.             date_table_income_fiat[date] += income_fiat
  101.             date_table_income_eth[date] += income_eth
  102.         else:
  103.             date_table_income_fiat[date] = income_fiat
  104.             date_price_table[date] = price_fiat
  105.             date_table_income_eth[date] = income_eth
  106.     return
  107.  
  108.  
  109. #
  110. # Fiat Range Condenser
  111. #   This will attempt to condense similar validator rewards in a given FIAT price range.
  112. #   Example:
  113. #       0.1 ETH reward on 2021-05-01 for $100 USD
  114. #       0.1 ETH reward on 2022-02-30 for $102 USD
  115. #   Merged result: 0.2 ETH reward on 2021-05-1 for $101 USD.
  116. #
  117. #   It will do this twice, once with a smaller range, and then again with a larger range.
  118. #   You can change this. Or you can even keep condensing if you want to. See line 218 or so
  119. #
  120. #   Reasoning: The above example is not too uncommon, and I don't want to bloat my tax report with
  121. #       so many near-identical transactions.
  122. #
  123. #   **NOTE**: There's some half-decent logic to not condense block proposals to avoid this:
  124. #       0.1 ETH reward on 2021-05-01 for $100
  125. #       0.5 ETH reward on 2022-05-03 for $120
  126. #   Merged result = 0.6 ETH on 2021-05-01 for $110
  127. #   You will be overpaying on the 0.1 reward in this example (or underpaying, the other way).
  128. #
  129. #   The detection is fairly simple, take the median (not mean) ETH value, times it by 3, if either value is above,
  130. #       we don't merge. [in the example above for example, the median would be 0.3 ETH]
  131. #       (x2 would probably be fine but since we're merging some date rewards (and hence their ETH values), x3 is safer).
  132. #   You can change that on line 202 or so if you want to (e.g. set a high value if you want to turn this functionality off)
  133. #
  134.  
  135. # There are two vars you can adjust here.
  136. fiat_condense_tolerance = float(20) # Default value = $20.
  137.  
  138. # If you DON'T want it to double condense, set this anything other than 0, and double the value above
  139. #   (since it will be halved for the first condense, sorry, bit unusual code flow)
  140. fiat_double_condense = 0
  141.  
  142. # This bool is here if you want assurance it worked. Put it to non-zero if you don't care.
  143. condense_sanity_check = 0
  144.  
  145. def fiat_range_condense_table():
  146.     eth_values = []
  147.     fiat_prices = []
  148.     dates = []
  149.     fiatIncome = []
  150.  
  151.     medianEthValueTimes3 = 0
  152.  
  153.     condensed_eth = []
  154.     condensed_dates = []
  155.     condensed_prices = []
  156.     condensed_income = []
  157.  
  158.     def condense(tolerance):
  159.         condensed_eth.clear()
  160.         condensed_dates.clear()
  161.         condensed_prices.clear()
  162.         condensed_income.clear()
  163.         i = 0
  164.         while i < len(fiat_prices):
  165.             price = fiat_prices[i]
  166.             eth1 = eth_values[i]
  167.             date1 = dates[i]
  168.             income = fiatIncome[i]
  169.  
  170.             if (condense_sanity_check == 0):
  171.                 print(f'{i} = {price} on {date1}')
  172.  
  173.             if i + 1 < len(fiat_prices):
  174.                 j = i + 1
  175.                 next_price = fiat_prices[j]
  176.  
  177.                 if (next_price - price) < tolerance:
  178.                     eth2 = eth_values[j]
  179.                     if eth2 < medianEthValueTimes3 and eth1 < medianEthValueTimes3:
  180.                         midPrice = (price + next_price) / 2
  181.                         if (condense_sanity_check == 0):
  182.                             print(
  183.                                 f'Rolling: {next_price} on {dates[j]} into {price} on {date1} as {midPrice} - ETH: {eth1} + {eth2} = {eth1 + eth2}')
  184.                         eth1 += eth2
  185.                         price = midPrice
  186.                         income = price * eth1
  187.                         i += 1  # skip this on next pass.
  188.                     else:
  189.                         if (condense_sanity_check == 0):
  190.                             print(f'SKIPPING MERGE as ETH values {eth1} or {eth2} are higher than the expected median: {medianEthValueTimes3}')
  191.  
  192.  
  193.  
  194.             condensed_prices.append(price)
  195.             condensed_eth.append(eth1)
  196.             condensed_dates.append(date1)
  197.             condensed_income.append(income)
  198.             i += 1
  199.         return
  200.  
  201.     keys = date_table_income_fiat.keys()
  202.     for k in keys:
  203.         fiat_prices.append(date_price_table[k])
  204.         dates.append(k)
  205.  
  206.     fiat_prices, dates = (list(t) for t in zip(*sorted(zip(fiat_prices, dates))))
  207.  
  208.     for d in dates:
  209.         fiatIncome.append(date_table_income_fiat[d])
  210.         eth_values.append(date_table_income_eth[d])
  211.  
  212.  
  213.     sortedEthValues = sorted(eth_values)
  214.     #for e in sortedEthValues:
  215.     #    print(f"SORTED E: {e}")
  216.     medianEthValueTimes3 = sortedEthValues[int(len(eth_values) / 2)] * 3
  217.     orig_prices = fiat_prices
  218.     orig_dates = dates
  219.  
  220.     condense(fiat_condense_tolerance / 2)
  221.  
  222.     eth_values = condensed_eth.copy()
  223.     fiat_prices = condensed_prices.copy()
  224.     dates = condensed_dates.copy()
  225.     fiatIncome = condensed_income.copy()
  226.  
  227.     firstCondenseText = f"First Condense: {len(eth_values)} normal: {len(orig_prices)}"
  228.     if (condense_sanity_check == 0):
  229.         print(firstCondenseText)
  230.  
  231.     if (fiat_double_condense == 0):
  232.         # Copy paste the next 5 lines if you want to keep condensing.
  233.         condense(fiat_condense_tolerance)
  234.  
  235.         eth_values = condensed_eth.copy()
  236.         fiat_prices = condensed_prices.copy()
  237.         dates = condensed_dates.copy()
  238.         fiatIncome = condensed_income.copy()
  239.  
  240.         # i.e it'd look like this
  241.         # condense(fiat_condense_tolerance)
  242.         #
  243.         #eth_values = condensed_eth.copy()
  244.         #fiat_prices = condensed_prices.copy()
  245.         #dates = condensed_dates.copy()
  246.         #fiatIncome = condensed_income.copy()
  247.  
  248.         if (condense_sanity_check == 0):
  249.             print('\n')
  250.             print(firstCondenseText)
  251.             print(f"Second Condense: {len(eth_values)} normal: {len(orig_prices)}")
  252.  
  253.     totalETHvalue = 0.0
  254.     totalFiatIncome = 0.0
  255.     for d in orig_dates:
  256.         totalETHvalue += date_table_income_eth[d]
  257.         totalFiatIncome += date_table_income_fiat[d]
  258.  
  259.     condensedTotalValue = 0.0
  260.     condensedTotalFiatValue = 0.0
  261.     date_table_income_eth.clear()
  262.     date_table_income_fiat.clear()
  263.     index = 0
  264.     while index < len(condensed_eth):
  265.         date_table_income_eth[dates[index]] = eth_values[index]
  266.         date_table_income_fiat[dates[index]] = fiatIncome[index]
  267.         condensedTotalValue += eth_values[index]
  268.         condensedTotalFiatValue += fiatIncome[index]
  269.         index += 1
  270.  
  271.     # If you want more sanity checking, compare the output file to the input file.
  272.     if (condense_sanity_check == 0):
  273.         print('\n')
  274.         print(f"Fiat total pre-condense {totalFiatIncome} after: {condensedTotalFiatValue} -- ETH total (this should NOT have changed)  pre-condense: {totalETHvalue} after: {condensedTotalValue}")
  275.         print(f'The fiat difference should be fairly minor, if not, your fiat tolerance is probably too high.')
  276.  
  277.     return
  278.  
  279.  
  280. # Press the green button in the gutter to run the script.
  281. if __name__ == '__main__':
  282.     main()
  283.  
  284.  
  285.  
Add Comment
Please, Sign In to add comment