You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DefaultFinancialFunctions.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. /*
  2. Copyright (c) 2017 James Ahlborn
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package com.healthmarketscience.jackcess.impl.expr;
  14. import com.healthmarketscience.jackcess.expr.EvalContext;
  15. import com.healthmarketscience.jackcess.expr.Function;
  16. import com.healthmarketscience.jackcess.expr.Value;
  17. import static com.healthmarketscience.jackcess.impl.expr.DefaultFunctions.*;
  18. /**
  19. *
  20. * @author James Ahlborn
  21. */
  22. public class DefaultFinancialFunctions
  23. {
  24. /** 0 - payment end of month (default) */
  25. private static final int PMT_END_MNTH = 0;
  26. /** 1 - payment start of month */
  27. private static final int PMT_BEG_MNTH = 1;
  28. private DefaultFinancialFunctions() {}
  29. static void init() {
  30. // dummy method to ensure this class is loaded
  31. }
  32. public static final Function NPER = registerFunc(new FuncVar("NPer", 3, 5) {
  33. @Override
  34. protected Value evalVar(EvalContext ctx, Value[] params) {
  35. double rate = params[0].getAsDouble();
  36. double pmt = params[1].getAsDouble();
  37. double pv = params[2].getAsDouble();
  38. double fv = 0d;
  39. if(params.length > 3) {
  40. fv = params[3].getAsDouble();
  41. }
  42. int pmtType = PMT_END_MNTH;
  43. if(params.length > 4) {
  44. pmtType = params[4].getAsLongInt();
  45. }
  46. double result = calculateLoanPaymentPeriods(rate, pmt, pv, pmtType);
  47. if(fv != 0d) {
  48. result += calculateAnnuityPaymentPeriods(rate, pmt, fv, pmtType);
  49. }
  50. return BuiltinOperators.toValue(result);
  51. }
  52. });
  53. public static final Function FV = registerFunc(new FuncVar("FV", 3, 5) {
  54. @Override
  55. protected Value evalVar(EvalContext ctx, Value[] params) {
  56. double rate = params[0].getAsDouble();
  57. double nper = params[1].getAsDouble();
  58. double pmt = params[2].getAsDouble();
  59. double pv = 0d;
  60. if(params.length > 3) {
  61. pv = params[3].getAsDouble();
  62. }
  63. int pmtType = PMT_END_MNTH;
  64. if(params.length > 4) {
  65. pmtType = params[4].getAsLongInt();
  66. }
  67. if(pv != 0d) {
  68. nper -= calculateLoanPaymentPeriods(rate, pmt, pv, pmtType);
  69. }
  70. double result = calculateFutureValue(rate, nper, pmt, pmtType);
  71. return BuiltinOperators.toValue(result);
  72. }
  73. });
  74. public static final Function PV = registerFunc(new FuncVar("PV", 3, 5) {
  75. @Override
  76. protected Value evalVar(EvalContext ctx, Value[] params) {
  77. double rate = params[0].getAsDouble();
  78. double nper = params[1].getAsDouble();
  79. double pmt = params[2].getAsDouble();
  80. double fv = 0d;
  81. if(params.length > 3) {
  82. fv = params[3].getAsDouble();
  83. }
  84. int pmtType = PMT_END_MNTH;
  85. if(params.length > 4) {
  86. pmtType = params[4].getAsLongInt();
  87. }
  88. if(fv != 0d) {
  89. nper -= calculateAnnuityPaymentPeriods(rate, pmt, fv, pmtType);
  90. }
  91. double result = calculatePresentValue(rate, nper, pmt, pmtType);
  92. return BuiltinOperators.toValue(result);
  93. }
  94. });
  95. public static final Function PMT = registerFunc(new FuncVar("Pmt", 3, 5) {
  96. @Override
  97. protected Value evalVar(EvalContext ctx, Value[] params) {
  98. double rate = params[0].getAsDouble();
  99. double nper = params[1].getAsDouble();
  100. double pv = params[2].getAsDouble();
  101. double fv = 0d;
  102. if(params.length > 3) {
  103. fv = params[3].getAsDouble();
  104. }
  105. int pmtType = PMT_END_MNTH;
  106. if(params.length > 4) {
  107. pmtType = params[4].getAsLongInt();
  108. }
  109. double result = calculateLoanPayment(rate, nper, pv, pmtType);
  110. if(fv != 0d) {
  111. result += calculateAnnuityPayment(rate, nper, fv, pmtType);
  112. }
  113. return BuiltinOperators.toValue(result);
  114. }
  115. });
  116. // FIXME not working for all param combos
  117. // public static final Function IPMT = registerFunc(new FuncVar("IPmt", 4, 6) {
  118. // @Override
  119. // protected Value evalVar(EvalContext ctx, Value[] params) {
  120. // double rate = params[0].getAsDouble();
  121. // double per = params[1].getAsDouble();
  122. // double nper = params[2].getAsDouble();
  123. // double pv = params[3].getAsDouble();
  124. // double fv = 0d;
  125. // if(params.length > 4) {
  126. // fv = params[4].getAsDouble();
  127. // }
  128. // int pmtType = PMT_END_MNTH;
  129. // if(params.length > 5) {
  130. // pmtType = params[5].getAsLongInt();
  131. // }
  132. // double pmt = calculateLoanPayment(rate, nper, pv, pmtType);
  133. // if(fv != 0d) {
  134. // pmt += calculateAnnuityPayment(rate, nper, fv, pmtType);
  135. // }
  136. // double result = calculateInterestPayment(pmt, rate, per, pv, pmtType);
  137. // return BuiltinOperators.toValue(result);
  138. // }
  139. // });
  140. // FIXME untested
  141. // public static final Function PPMT = registerFunc(new FuncVar("PPmt", 4, 6) {
  142. // @Override
  143. // protected Value evalVar(EvalContext ctx, Value[] params) {
  144. // double rate = params[0].getAsDouble();
  145. // double per = params[1].getAsDouble();
  146. // double nper = params[2].getAsDouble();
  147. // double pv = params[3].getAsDouble();
  148. // double fv = 0d;
  149. // if(params.length > 4) {
  150. // fv = params[4].getAsDouble();
  151. // }
  152. // int pmtType = PMT_END_MNTH;
  153. // if(params.length > 5) {
  154. // pmtType = params[5].getAsLongInt();
  155. // }
  156. // double pmt = calculateLoanPayment(rate, nper, pv, pmtType);
  157. // if(fv != 0d) {
  158. // pmt += calculateAnnuityPayment(rate, nper, fv, pmtType);
  159. // }
  160. // double result = pmt - calculateInterestPayment(pmt, rate, per, pv,
  161. // pmtType);
  162. // return BuiltinOperators.toValue(result);
  163. // }
  164. // });
  165. // FIXME, doesn't work for partial days
  166. // public static final Function DDB = registerFunc(new FuncVar("DDB", 4, 5) {
  167. // @Override
  168. // protected Value evalVar(EvalContext ctx, Value[] params) {
  169. // double cost = params[0].getAsDouble();
  170. // double salvage = params[1].getAsDouble();
  171. // double life = params[2].getAsDouble();
  172. // double period = params[3].getAsDouble();
  173. // double factor = 2d;
  174. // if(params.length > 4) {
  175. // factor = params[4].getAsDouble();
  176. // }
  177. // double result = 0d;
  178. // // fractional value always rounds up to one year
  179. // if(period < 1d) {
  180. // period = 1d;
  181. // }
  182. // // FIXME? apply partial period _first_
  183. // // double partPeriod = period % 1d;
  184. // // if(partPeriod != 0d) {
  185. // // result = calculateDoubleDecliningBalance(
  186. // // cost, salvage, life, factor) * partPeriod;
  187. // // period -= partPeriod;
  188. // // cost -= result;
  189. // // }
  190. // double prevResult = 0d;
  191. // while(period > 0d) {
  192. // prevResult = result;
  193. // double remPeriod = Math.min(period, 1d);
  194. // result = calculateDoubleDecliningBalance(
  195. // cost, salvage, life, factor);
  196. // if(remPeriod < 1d) {
  197. // result = (prevResult + result) / 2d;
  198. // }
  199. // period -= 1d;
  200. // cost -= result;
  201. // }
  202. // return BuiltinOperators.toValue(result);
  203. // }
  204. // });
  205. // FIXME, untested
  206. public static final Function SLN = registerFunc(new FuncVar("SLN", 3, 3) {
  207. @Override
  208. protected Value evalVar(EvalContext ctx, Value[] params) {
  209. double cost = params[0].getAsDouble();
  210. double salvage = params[1].getAsDouble();
  211. double life = params[2].getAsDouble();
  212. double result = calculateStraightLineDepreciation(cost, salvage, life);
  213. return BuiltinOperators.toValue(result);
  214. }
  215. });
  216. // FIXME, untested
  217. public static final Function SYD = registerFunc(new FuncVar("SYD", 4, 4) {
  218. @Override
  219. protected Value evalVar(EvalContext ctx, Value[] params) {
  220. double cost = params[0].getAsDouble();
  221. double salvage = params[1].getAsDouble();
  222. double life = params[2].getAsDouble();
  223. double period = params[3].getAsDouble();
  224. double result = calculateSumOfYearsDepreciation(
  225. cost, salvage, life, period);
  226. return BuiltinOperators.toValue(result);
  227. }
  228. });
  229. private static double calculateLoanPaymentPeriods(
  230. double rate, double pmt, double pv, int pmtType) {
  231. // https://brownmath.com/bsci/loan.htm
  232. // http://financeformulas.net/Number-of-Periods-of-Annuity-from-Present-Value.html
  233. if(pmtType == PMT_BEG_MNTH) {
  234. pv += pmt;
  235. }
  236. double v1 = Math.log(1d + (rate * pv / pmt));
  237. double v2 = Math.log(1d + rate);
  238. double result = -v1 / v2;
  239. if(pmtType == PMT_BEG_MNTH) {
  240. result += 1d;
  241. }
  242. return result;
  243. }
  244. private static double calculateAnnuityPaymentPeriods(
  245. double rate, double pmt, double fv, int pmtType) {
  246. // https://brownmath.com/bsci/loan.htm
  247. // http://financeformulas.net/Number-of-Periods-of-Annuity-from-Future-Value.html
  248. // https://accountingexplained.com/capital/tvm/fv-annuity
  249. if(pmtType == PMT_BEG_MNTH) {
  250. fv *= (1d + rate);
  251. }
  252. double v1 = Math.log(1d - (rate * fv / pmt));
  253. double v2 = Math.log(1d + rate);
  254. double result = v1 / v2;
  255. if(pmtType == PMT_BEG_MNTH) {
  256. result -= 1d;
  257. }
  258. return result;
  259. }
  260. private static double calculateFutureValue(
  261. double rate, double nper, double pmt, int pmtType) {
  262. double result = -pmt * ((Math.pow((1d + rate), nper) - 1d) / rate);
  263. if(pmtType == PMT_BEG_MNTH) {
  264. result *= (1d + rate);
  265. }
  266. return result;
  267. }
  268. private static double calculatePresentValue(
  269. double rate, double nper, double pmt, int pmtType) {
  270. if(pmtType == PMT_BEG_MNTH) {
  271. nper -= 1d;
  272. }
  273. double result = -pmt * ((1d - Math.pow((1d + rate), -nper)) / rate);
  274. if(pmtType == PMT_BEG_MNTH) {
  275. result -= pmt;
  276. }
  277. return result;
  278. }
  279. private static double calculateLoanPayment(
  280. double rate, double nper, double pv, int pmtType) {
  281. double result = -(rate * pv) / (1d - Math.pow((1d + rate), -nper));
  282. if(pmtType == PMT_BEG_MNTH) {
  283. result /= (1d + rate);
  284. }
  285. return result;
  286. }
  287. private static double calculateAnnuityPayment(
  288. double rate, double nper, double fv, int pmtType) {
  289. double result = -(fv * rate) / (Math.pow((1d + rate), nper) - 1d);
  290. if(pmtType == PMT_BEG_MNTH) {
  291. result /= (1d + rate);
  292. }
  293. return result;
  294. }
  295. private static double calculateInterestPayment(
  296. double pmt, double rate, double per, double pv, int pmtType) {
  297. // http://www.tvmcalcs.com/index.php/calculators/apps/excel_loan_amortization
  298. // http://financeformulas.net/Remaining_Balance_Formula.html
  299. double pvPer = per;
  300. double fvPer = per;
  301. if(pmtType == PMT_END_MNTH) {
  302. pvPer -= 1d;
  303. fvPer -= 1d;
  304. } else {
  305. pvPer -= 2d;
  306. fvPer -= 1d;
  307. }
  308. double remBalance = (pv * Math.pow((1d + rate), pvPer)) -
  309. // FIXME, always use pmtType of 0?
  310. calculateFutureValue(rate, fvPer, pmt, PMT_END_MNTH);
  311. double result = -(remBalance * rate);
  312. return result;
  313. }
  314. private static double calculateDoubleDecliningBalance(
  315. double cost, double salvage, double life, double factor) {
  316. double result1 = cost * (factor/life);
  317. double result2 = cost - salvage;
  318. return Math.min(result1, result2);
  319. }
  320. private static double calculateStraightLineDepreciation(
  321. double cost, double salvage, double life) {
  322. return ((cost - salvage) / life);
  323. }
  324. private static double calculateSumOfYearsDepreciation(
  325. double cost, double salvage, double life, double period) {
  326. double sumOfYears = (period * (period + 1)) / 2d;
  327. double result = ((cost - salvage) * ((life + 1 - period) / sumOfYears));
  328. return result;
  329. }
  330. }