Esketit - Add Net Return to Statement

Proving that it's important

当前为 2025-04-30 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Esketit - Add Net Return to Statement
  3. // @namespace http://esketit.com/
  4. // @version 2025-04-30
  5. // @description Proving that it's important
  6. // @author rs232
  7. // @match https://esketit.com/investor/account-statement
  8. // @icon https://www.google.com/s2/favicons?sz=32&domain_url=https%3A%2F%2Fwww.esketit.com
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. // Helper: parse a currency string (e.g. "€2 089,10") to a number.
  16. function parseCurrency(value) {
  17. return parseFloat(value.replace('€', '').replace(/\s/g, '').replace(',', '.')) || 0;
  18. }
  19.  
  20. // Calculate Net Return from the 5 rows in the Summary table.
  21. function calculateNetReturn() {
  22. const rows = [
  23. "Interest received",
  24. "Bonus received",
  25. "Referral bonus received",
  26. "Secondary market income",
  27. "Secondary market expense"
  28. ];
  29. let total = 0;
  30. rows.forEach(rowLabel => {
  31. const row = document.evaluate(
  32. `//tr[td[text()='${rowLabel}']]`,
  33. document,
  34. null,
  35. XPathResult.FIRST_ORDERED_NODE_TYPE,
  36. null
  37. ).singleNodeValue;
  38. if (row) {
  39. const valueCell = row.querySelector('td:nth-child(2)');
  40. if (valueCell) {
  41. const valueText = valueCell.textContent.replace('€', '').replace(/\s/g, '').replace(',', '.');
  42. total += parseCurrency(valueText);
  43. }
  44. }
  45. });
  46. return total;
  47. }
  48.  
  49. // Update the Net Return row below the Closing Balance row.
  50. function addNetReturnRow() {
  51. const closingBalanceRow = document.evaluate(
  52. "//tr[td[text()='Closing balance']]",
  53. document,
  54. null,
  55. XPathResult.FIRST_ORDERED_NODE_TYPE,
  56. null
  57. ).singleNodeValue;
  58. if (closingBalanceRow) {
  59. const netReturn = calculateNetReturn();
  60. const formattedNetReturn = `€${netReturn.toFixed(2).replace('.', ',').replace(/\B(?=(\d{3})+(?!\d))/g, '.')}`;
  61. let netReturnRow = document.getElementById('net-return-row');
  62. if (!netReturnRow) {
  63. netReturnRow = document.createElement('tr');
  64. netReturnRow.id = 'net-return-row';
  65. netReturnRow.setAttribute('data-v-344f568a', '');
  66. closingBalanceRow.parentNode.insertBefore(netReturnRow, closingBalanceRow.nextSibling);
  67. }
  68. netReturnRow.innerHTML = `
  69. <td data-v-344f568a="" style="font-weight: bold; color: green;">Net Return</td>
  70. <td data-v-344f568a="" style="text-align: right; font-weight: bold; color: green;">${formattedNetReturn}</td>
  71. `;
  72. // Colorize the Summary rows after updating the Net Return value.
  73. colorizeSummaryRows();
  74. } else {
  75. console.warn("Closing Balance row not found.");
  76. }
  77. }
  78.  
  79. // Colorizes each Summary row:
  80. // - For "Interest received", "Bonus received", "Referral bonus received", and "Secondary market income",
  81. // color both label and value green if the value > 0.
  82. // - For "Secondary market expense", color red if its value is negative.
  83. function colorizeSummaryRows() {
  84. const greenRows = [
  85. "Interest received",
  86. "Bonus received",
  87. "Referral bonus received",
  88. "Secondary market income"
  89. ];
  90. greenRows.forEach(label => {
  91. const row = document.evaluate(
  92. `//tr[td[text()='${label}']]`,
  93. document,
  94. null,
  95. XPathResult.FIRST_ORDERED_NODE_TYPE,
  96. null
  97. ).singleNodeValue;
  98. if (row) {
  99. const valueCell = row.querySelector('td:nth-child(2)');
  100. if (valueCell) {
  101. const value = parseCurrency(valueCell.textContent);
  102. if (value > 0) {
  103. row.querySelector('td:nth-child(1)').style.color = "green";
  104. row.querySelector('td:nth-child(2)').style.color = "green";
  105. } else {
  106. row.querySelector('td:nth-child(1)').style.color = "";
  107. row.querySelector('td:nth-child(2)').style.color = "";
  108. }
  109. }
  110. }
  111. });
  112. // For "Secondary market expense"
  113. const redLabel = "Secondary market expense";
  114. const redRow = document.evaluate(
  115. `//tr[td[text()='${redLabel}']]`,
  116. document,
  117. null,
  118. XPathResult.FIRST_ORDERED_NODE_TYPE,
  119. null
  120. ).singleNodeValue;
  121. if (redRow) {
  122. const valueCell = redRow.querySelector('td:nth-child(2)');
  123. if (valueCell) {
  124. const value = parseCurrency(valueCell.textContent);
  125. if (value < 0) {
  126. redRow.querySelector('td:nth-child(1)').style.color = "red";
  127. redRow.querySelector('td:nth-child(2)').style.color = "red";
  128. } else {
  129. redRow.querySelector('td:nth-child(1)').style.color = "";
  130. redRow.querySelector('td:nth-child(2)').style.color = "";
  131. }
  132. }
  133. }
  134. }
  135.  
  136. // Debounce helper to avoid multiple rapid recalculations.
  137. let recalcTimeout;
  138. function scheduleRecalculation() {
  139. if (recalcTimeout) clearTimeout(recalcTimeout);
  140. recalcTimeout = setTimeout(addNetReturnRow, 50);
  141. }
  142.  
  143. // Monitor the value cells for changes.
  144. function monitorValueChanges() {
  145. const rows = [
  146. "Interest received",
  147. "Bonus received",
  148. "Referral bonus received",
  149. "Secondary market income",
  150. "Secondary market expense"
  151. ];
  152. rows.forEach(rowLabel => {
  153. const row = document.evaluate(
  154. `//tr[td[text()='${rowLabel}']]`,
  155. document,
  156. null,
  157. XPathResult.FIRST_ORDERED_NODE_TYPE,
  158. null
  159. ).singleNodeValue;
  160. if (row) {
  161. const valueCell = row.querySelector('td:nth-child(2)');
  162. if (valueCell) {
  163. const observer = new MutationObserver(scheduleRecalculation);
  164. observer.observe(valueCell, { childList: true, characterData: true, subtree: true });
  165. }
  166. }
  167. });
  168. }
  169.  
  170. // On page load, add the Net Return row and start monitoring.
  171. window.addEventListener('load', () => {
  172. addNetReturnRow();
  173. monitorValueChanges();
  174. });
  175. })();