finrb 0.1.12 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 312d19ce29f9c0fc5cb7efca39d9cd6ef9ceedec77e9432f817c1b639a4a7c47
4
- data.tar.gz: 46901c63d6c174af45186020bdcc0727ec7cc5e2cf138b38fec43ba1bbb5e90c
3
+ metadata.gz: e783eb0696497a848cc5506b4bd0d75e4327b383a8a4c998fd34ed86c3092a8c
4
+ data.tar.gz: ca82637abc1703af93554215a2704469af63a3e4d95c4659da9ab86df289bdf2
5
5
  SHA512:
6
- metadata.gz: 59c3c4c172cfd860f28afbaa07d294c7bef5d3f7a71020ddbb05ef4cefc0acac7cebc385f88efb5e0d9d7a12fedd45437c54c6fdd2234acfe1e5f5b7f9701ed9
7
- data.tar.gz: 3889d3561dc138d16f9696dc7850a3c5f55803c4f1ff17d98be962d82778e365df9ebd5d4c84cc2ec49e9e562e44d947553a7839e194c1dd7225d23e72b702dd
6
+ metadata.gz: a1b6da22366494309440b3095f6ea046512c27bf1aed43586701788f87c4fd25e67a246e35172419077cdf2c2a074c5bc9f2ae23ec110a2c3d4a65d690bdf0c0
7
+ data.tar.gz: 2d3fa7f43260c6d00767e899a37ca977e86a4b146cb500dd6f1fa9ffaeabcc1dbed38fa2a7f1e38168e0a86c4f69e960131e96d9d2e8f3dedffe24f2c3064cd8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # finrb changelog
2
2
 
3
+ ## 1.0.0
4
+
5
+ This release intentionally breaks parts of the 0.1 public API.
6
+
7
+ ### Financial correctness
8
+
9
+ - Normalize XNPV date calculations and distinguish nominal APR from effective APY.
10
+ - Replace deprecated BigDecimal Newton solving with a decimal Brent–Dekker solver and explicit convergence/domain errors.
11
+ - Validate public financial inputs and centralize amortization rounding policy.
12
+ - Fix FIFO/LIFO ending inventory when a sale is satisfied before every layer is traversed.
13
+
14
+ ### Public API
15
+
16
+ - Replace `Finrb::Utils` with `Finrb::TVM`, `Accounting`, `Ratios`, `Returns`, and `Yields`.
17
+ - Expose IRR, NPV, XIRR, and XNPV through `Finrb::Cashflow`.
18
+ - Stop modifying `Array` and `Numeric` by default; legacy fluent methods require `finrb/core_ext`.
19
+ - Publish immutable configuration snapshots with thread-scoped temporary overrides.
20
+
21
+ ### Dependencies and assurance
22
+
23
+ - Remove ActiveSupport and `business_time` production dependencies.
24
+ - Add deterministic generated-root tests, SciPy/QuantLib reference verification, line and branch coverage, and packaged RBS signatures.
25
+ - Support Ruby 3.3, 3.4, and 4.0.
26
+
3
27
  ## 0.1.12
4
28
 
5
29
  - bump gem versions
data/NOTICE.md ADDED
@@ -0,0 +1,20 @@
1
+ # finrb notices and attribution
2
+
3
+ finrb is licensed under the GNU Lesser General Public License version 3.0 or
4
+ later. The complete GPLv3 and LGPLv3 texts are distributed as `COPYING` and
5
+ `COPYING.LESSER`.
6
+
7
+ finrb is derived from the Ruby
8
+ [finance](https://github.com/Edward-Intelligence/finance) gem. Financial
9
+ calculation utilities were also ported from R's
10
+ [FinCal](https://github.com/felixfan/FinCal) package. Their contributors are
11
+ acknowledged in the README and repository history.
12
+
13
+ The bracketed scalar root solver implements the Brent–Dekker algorithm using
14
+ the method described by Richard P. Brent in *Algorithms for Minimization
15
+ Without Derivatives* (1973). The implementation is local Ruby code and is not
16
+ copied from SciPy or QuantLib.
17
+
18
+ SciPy and QuantLib are optional, independently maintained reference
19
+ implementations used by the maintainer verification scripts. They are not
20
+ runtime dependencies and their code is not distributed in the finrb gem.
data/README.md CHANGED
@@ -4,154 +4,242 @@
4
4
  [![CodeQL](https://github.com/ncs1/finrb/actions/workflows/codeql.yml/badge.svg)](https://github.com/ncs1/finrb/actions/workflows/codeql.yml)
5
5
  [![RuboCop](https://github.com/ncs1/finrb/actions/workflows/rubocop.yml/badge.svg)](https://github.com/ncs1/finrb/actions/workflows/rubocop.yml)
6
6
 
7
- <!-- TOC depthfrom:2 -->
7
+ Precision-first financial mathematics for Ruby.
8
8
 
9
- - [Overview](#overview)
10
- - [Features](#features)
11
- - [Configuration](#configuration)
12
- - [API and examples](#api-and-examples)
13
- - [Resources](#resources)
14
- - [Acknowledgements](#acknowledgements)
15
- - [License](#license)
9
+ finrb provides decimal calculations for cashflows, interest rates, loan
10
+ amortization, time value of money, investment returns, financial ratios, and
11
+ basic accounting. Public APIs live under explicit `Finrb` namespaces, and
12
+ loading the gem does not modify Ruby's core classes.
16
13
 
17
- <!-- /TOC -->
14
+ finrb is a fork of the Ruby [finance](https://github.com/Edward-Intelligence/finance)
15
+ gem and includes calculations ported from R's
16
+ [FinCal](https://github.com/felixfan/FinCal) package.
18
17
 
19
- Ruby gem for financial calculations/modeling.
18
+ ## Installation
20
19
 
21
- finrb forked from the ruby [finance](https://github.com/Edward-Intelligence/finance) gem.
20
+ Add finrb to your bundle:
22
21
 
23
- ## Overview
22
+ ```shell
23
+ bundle add finrb
24
+ ```
24
25
 
25
- ### Features
26
+ Or install it directly:
26
27
 
27
- Currently implemented features include:
28
+ ```shell
29
+ gem install finrb
30
+ ```
28
31
 
29
- - Uses the [flt](https://github.com/jgoizueta/flt) gem to ensure precision decimal arithmetic in all calculations.
30
- - Fixed-rate mortgage amortization (30/360).
31
- - Interest rates
32
- - Various cash flow computations, such as NPV and IRR.
33
- - Adjustable rate mortgage amortization.
34
- - Payment modifications (i.e., how does paying an additional $75 per month affect the amortization?)
35
- - Utils class provides basic financial calculation utilities (ported from R's [FinCal](https://github.com/felixfan/FinCal) library):
32
+ finrb requires Ruby 3.3 or newer.
36
33
 
37
- - Basic Earnings Per Share
34
+ ## Quick start
38
35
 
39
- - Bond-equivalent yield (BEY), 2 x the semiannual discount rate
36
+ ```ruby
37
+ require 'finrb'
40
38
 
41
- - Calculate the net increase in common shares from the potential exercise of stock options or warrants
39
+ cashflows = [-4000, 1200, 1410, 1875, 1050]
42
40
 
43
- - Calculate weighted average shares - weighted average number of common shares
41
+ Finrb::Cashflow.irr(cashflows).round(6)
42
+ # => Flt::DecNum('0.142993')
44
43
 
45
- - Cash ratio - Liquidity ratios measure the firm's ability to satisfy its short-term obligations as they come due.
44
+ Finrb::Cashflow.npv(cashflows, 0.10).round(2)
45
+ # => Flt::DecNum('382.08')
46
+ ```
46
47
 
47
- - Computing Coefficient of variation
48
+ Inputs are validated and calculations return `Flt::DecNum` values unless an
49
+ API explicitly returns another financial object, such as `Finrb::Rate`.
48
50
 
49
- - Computing HPR, the holding period return
51
+ ## Financial domains
50
52
 
51
- - Computing IRR, the internal rate of return
53
+ | Namespace | Purpose |
54
+ | --- | --- |
55
+ | `Finrb::Cashflow` | NPV, XNPV, IRR, and XIRR |
56
+ | `Finrb::Rate` | Nominal APR, effective APY, and compounding conversions |
57
+ | `Finrb::Amortization` | Fixed and adjustable-rate loan amortization |
58
+ | `Finrb::TVM` | Present value, future value, payments, periods, and perpetuities |
59
+ | `Finrb::Returns` | Holding-period, time-weighted, portfolio, and risk-adjusted returns |
60
+ | `Finrb::Yields` | Money-market, bond-equivalent, effective, and continuous yield conversions |
61
+ | `Finrb::Ratios` | Liquidity, leverage, profitability, and per-share ratios |
62
+ | `Finrb::Accounting` | Inventory costing and depreciation |
52
63
 
53
- - Computing NPV, the PV of the cash flows less the initial (time = 0) outlay
64
+ The detailed [API and examples guide](docs/api.md) lists each calculation and
65
+ its parameters. Packaged RBS declarations are available under `sig/`.
54
66
 
55
- - Computing Roy's safety-first ratio
67
+ ## Cashflows
56
68
 
57
- - Computing Sampling error
69
+ Periodic IRR is a per-period rate. XIRR evaluates irregularly dated
70
+ `Finrb::Transaction` objects and returns an effective annual `Finrb::Rate`.
58
71
 
59
- - Computing Sharpe Ratio
72
+ ```ruby
73
+ require 'date'
60
74
 
61
- - Computing TWRR, the time-weighted rate of return
75
+ transactions = [
76
+ Finrb::Transaction.new(-10_000, date: Date.new(2020, 1, 1)),
77
+ Finrb::Transaction.new(12_500, date: Date.new(2022, 1, 1))
78
+ ]
62
79
 
63
- - Computing bank discount yield (BDY) for a T-bill
80
+ rate = Finrb::Cashflow.xirr(transactions, 0.10)
81
+ rate.apy.round(6)
82
+ # => Flt::DecNum('0.117863')
83
+ ```
64
84
 
65
- - Computing money market yield (MMY) for a T-bill
85
+ Cashflows must contain at least one positive and one negative amount. Dated
86
+ cashflows must be chronological and every transaction must have a date. For
87
+ ordinary discrete discounting, rates and guesses must be greater than `-1`.
66
88
 
67
- - Computing the future value of an uneven cash flow series
89
+ IRR and XIRR can have more than one economically valid root. The optional
90
+ guess controls which nearby sign-changing root finrb selects:
68
91
 
69
- - Computing the present value of an uneven cash flow series
92
+ ```ruby
93
+ cashflows = [-100, 230, -132] # roots at 10% and 20%
70
94
 
71
- - Computing the rate of return for each period
95
+ Finrb::Cashflow.irr(cashflows, 0.05).round(2) # => 0.10
96
+ Finrb::Cashflow.irr(cashflows, 0.25).round(2) # => 0.20
97
+ ```
72
98
 
73
- - Convert a given continuous compounded rate to a norminal rate
99
+ ## Rates and amortization
74
100
 
75
- - Convert a given norminal rate to a continuous compounded rate
101
+ APR is a nominal annual rate; APY is an effective annual rate. They are not
102
+ aliases:
76
103
 
77
- - Convert holding period return to the effective annual rate
104
+ ```ruby
105
+ rate = Finrb::Rate.new(0.12, :apr)
78
106
 
79
- - Convert stated annual rate to the effective annual rate (with continuous compounding)
107
+ rate.monthly # => Flt::DecNum('0.01')
108
+ rate.apy.round(6) # => Flt::DecNum('0.126825')
109
+ ```
80
110
 
81
- - Cost of goods sold and ending inventory under three methods (FIFO,LIFO,Weighted average)
111
+ Create a fixed-rate loan by giving the rate a duration in months:
82
112
 
83
- - Current ratio - Liquidity ratios measure the firm's ability to satisfy its short-term obligations as they come due.
113
+ ```ruby
114
+ rate = Finrb::Rate.new(0.0425, :apr, duration: 30 * 12)
115
+ loan = Finrb::Amortization.new(250_000, rate)
84
116
 
85
- - Debt ratio - Solvency ratios measure the firm's ability to satisfy its long-term obligations.
117
+ loan.payment # => Flt::DecNum('-1229.85')
118
+ loan.interest.sum # => Flt::DecNum('192745.98')
119
+ loan.balance # => Flt::DecNum('0.00')
120
+ ```
86
121
 
87
- - Depreciation Expense Recognition - Straight-line depreciation (SL) allocates an equal amount of depreciation each year over the asset's useful life
122
+ Pass several duration-bearing rates for an adjustable-rate schedule. A block
123
+ can modify each scheduled payment, for example to add a $150 principal payment:
88
124
 
89
- - Depreciation Expense Recognition - double-declining balance (DDB), the most common declining balance method, which applies two times the straight-line rate to the declining balance.
125
+ ```ruby
126
+ faster = Finrb::Amortization.new(250_000, rate) do |period|
127
+ period.payment - 150
128
+ end
129
+ ```
90
130
 
91
- - Diluted Earnings Per Share
131
+ Payments and interest follow the sign convention used throughout finrb:
132
+ money received is positive and money paid is negative.
92
133
 
93
- - Equivalent/proportional Interest Rates
134
+ ## Configuration
94
135
 
95
- - Estimate future value (fv) (of a single sum)
136
+ Configure process-wide defaults during application startup:
96
137
 
97
- - Estimate future value of an annuity
138
+ ```ruby
139
+ Finrb.configure do |config|
140
+ config.eps = '1e-12'
141
+ config.guess = 0.10
142
+ config.business_days = false
143
+ config.periodic_compound = false
144
+ end
145
+ ```
98
146
 
99
- - Estimate period payment
147
+ Configuration is validated and published as one immutable snapshot.
148
+ `Finrb.config` is read-only. Use `Finrb.with_config` for a temporary override
149
+ that is restored afterward and does not leak into another thread:
100
150
 
101
- - Estimate present value (pv) (of a single sum) (of an annuity)
151
+ ```ruby
152
+ Finrb.with_config(guess: 0.25) do
153
+ Finrb::Cashflow.irr(cashflows)
154
+ end
155
+ ```
102
156
 
103
- - Estimate present value of a perpetuity
157
+ The `business_days` compatibility option excludes weekends only. It is not a
158
+ holiday calendar or a market business-day convention.
104
159
 
105
- - Estimate the number of periods
160
+ ## Precision, rounding, and failures
106
161
 
107
- - Financial leverage - Solvency ratios measure the firm's ability to satisfy its long-term obligations.
162
+ General calculations retain the active `Flt::DecNum` context and are not
163
+ rounded for display. Callers choose presentation precision with `round` or a
164
+ formatter. `Finrb.config.eps` controls root-solver convergence; it does not set
165
+ decimal arithmetic precision.
108
166
 
109
- - Geometric mean return
167
+ Amortization is deliberately different because payments and interest are
168
+ monetary postings. They are rounded to cents using half-up rounding, and any
169
+ remaining cent-level balance is allocated to the final payment. These policies
170
+ are exposed through `Finrb::Precision`.
110
171
 
111
- - Gross profit margin - Evaluate a company's financial performance
172
+ finrb reports invalid financial or numerical states explicitly:
112
173
 
113
- - Harmonic mean, average price
174
+ - `Finrb::InvalidCashflowError` for malformed cashflow sequences
175
+ - `Finrb::DomainError` for values outside a calculation's legal domain
176
+ - `Finrb::ConvergenceError` when a root cannot be bracketed or solved
177
+ - `ArgumentError` for invalid public inputs and options
114
178
 
115
- - Long-term debt-to-equity - Solvency ratios measure the firm's ability to satisfy its long-term obligations.
179
+ ## Migrating from the legacy API
116
180
 
117
- - Net profit margin - Evaluate a company's financial performance
181
+ The current API contains intentional breaking changes:
118
182
 
119
- - Quick ratio - Liquidity ratios measure the firm's ability to satisfy its short-term obligations as they come due.
183
+ - `Finrb::Utils` was removed. Use `Finrb::TVM`, `Returns`, `Yields`, `Ratios`,
184
+ or `Accounting` according to the calculation's domain.
185
+ - Loading `finrb` no longer adds methods to `Array` or `Numeric`.
186
+ - `Finrb.config` is immutable; use `Finrb.configure` or `Finrb.with_config`.
187
+ - APR is nominal and APY is effective, so conversions now follow their stated
188
+ financial semantics.
120
189
 
121
- - Rate of return for a perpetuity
190
+ Applications migrating gradually can explicitly load the legacy fluent core
191
+ extensions:
122
192
 
123
- - Total debt-to-equity - Solvency ratios measure the firm's ability to satisfy its long-term obligations.
193
+ ```ruby
194
+ require 'finrb/core_ext'
124
195
 
125
- - Weighted mean as a portfolio return
196
+ [-4000, 1200, 1410, 1875, 1050].irr
197
+ 250_000.amortize(rate)
198
+ ```
126
199
 
127
- ### Configuration
200
+ New code should use `Finrb::Cashflow.irr(cashflows)` and
201
+ `Finrb::Amortization.new(principal, rate)`.
128
202
 
129
- In `config/initializers/finrb.rb` Finrb allows to set tolerance (eps) and default guess for IRR and XIRR calculations, such as:
203
+ ## Development and verification
130
204
 
131
- ```ruby
132
- Finrb.configure do |config|
133
- config.eps = '1.0e-12'
134
- config.guess = 0.5
135
- config.business_days = false # only relevant when using Transaction object, skips weekends
136
- config.periodic_compound = false # only relevant when using Transaction object
137
- end
205
+ Install the bundle and run the self-contained quality checks:
206
+
207
+ ```shell
208
+ bundle install
209
+ bundle exec rake quality
210
+ bundle exec rubocop
138
211
  ```
139
212
 
140
- ### API and examples
213
+ The quality task runs the RSpec suite with line and branch coverage, generated
214
+ IRR/XIRR properties, committed SciPy/QuantLib reference fixtures, and RBS
215
+ validation.
216
+
217
+ Maintainers with the optional Python environment can run the larger seeded
218
+ solver verification campaign:
219
+
220
+ ```shell
221
+ python3 -m pip install --requirement script/requirements-solver-verification.txt
222
+ bundle exec rake solver:verify
223
+ ```
141
224
 
142
- See [api.md](docs/api.md)
225
+ The Python packages are verification references, not gem dependencies. See
226
+ [the fixture documentation](spec/fixtures/README.md) for reproducibility,
227
+ Docker, batching, and tolerance details.
143
228
 
144
- ## Resources
229
+ ## Project links
145
230
 
146
- - [RubyGems Page](https://rubygems.org/gems/finrb)
147
- - [Source Code](https://github.com/ncs1/finrb)
148
- - [Bug Tracker](https://github.com/ncs1/finrb/issues)
231
+ - [RubyGems](https://rubygems.org/gems/finrb)
232
+ - [Source](https://github.com/ncs1/finrb)
233
+ - [Issue tracker](https://github.com/ncs1/finrb/issues)
149
234
 
150
235
  ## Acknowledgements
151
236
 
152
- - Martin Bjeldbak Madsen (@martinbjeldbak), Bill Kranec (@wkranec) - original [finance](https://github.com/Edward-Intelligence/finance) gem maintainers.
153
- - Yanhui Fan (@felixfan) - maintainer of [FinCal](https://github.com/felixfan/FinCal) library.
237
+ - Martin Bjeldbak Madsen, Bill Kranec, and the contributors to the original
238
+ [finance](https://github.com/Edward-Intelligence/finance) gem
239
+ - Yanhui Fan and the contributors to R's
240
+ [FinCal](https://github.com/felixfan/FinCal) package
154
241
 
155
242
  ## License
156
243
 
157
- See [COPYING](./COPYING) and [COPYING.LESSER](./COPYING.LESSER)
244
+ finrb is available under the GNU Lesser General Public License v3.0 or later.
245
+ See [COPYING](COPYING) and [COPYING.LESSER](COPYING.LESSER).
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'decimal'
4
+ require_relative 'errors'
5
+
6
+ module Finrb
7
+ # Inventory costing and depreciation calculations.
8
+ module Accounting
9
+ def self.wrap_array(object)
10
+ if object.nil?
11
+ []
12
+ elsif object.respond_to?(:to_ary)
13
+ object.to_ary || [object]
14
+ else
15
+ [object]
16
+ end
17
+ end
18
+ private_class_method :wrap_array
19
+
20
+ # Cost of goods sold and ending inventory under three methods (FIFO,LIFO,Weighted average)
21
+ #
22
+ # @param uinv units of beginning inventory
23
+ # @param pinv price of beginning inventory
24
+ # @param units nx1 vector of inventory units. inventory purchased ordered by time (from first to last)
25
+ # @param price nx1 vector of inventory price. same order as units
26
+ # @param sinv units of sold inventory
27
+ # @param method inventory methods: FIFO (first in first out, permitted under both US and IFRS), LIFO (late in first out, US only), WAC (weighted average cost,US and IFRS)
28
+ # @example
29
+ # Finrb::Accounting.cogs(uinv=2,pinv=2,units=[3,5],price=[3,5],sinv=7,method="FIFO")
30
+ #
31
+ # @example
32
+ # Finrb::Accounting.cogs(uinv=2,pinv=2,units=[3,5],price=[3,5],sinv=7,method="LIFO")
33
+ #
34
+ # @example
35
+ # Finrb::Accounting.cogs(uinv=2,pinv=2,units=[3,5],price=[3,5],sinv=7,method="WAC")
36
+ def self.cogs(uinv:, pinv:, units:, price:, sinv:, method: 'FIFO')
37
+ uinv = Flt::DecNum(uinv.to_s)
38
+ pinv = Flt::DecNum(pinv.to_s)
39
+ units = wrap_array(units).map { |value| Flt::DecNum(value.to_s) }
40
+ price = wrap_array(price).map { |value| Flt::DecNum(value.to_s) }
41
+ sinv = Flt::DecNum(sinv.to_s)
42
+ method = method.to_s
43
+
44
+ n = units.size
45
+ m = price.size
46
+ cost_of_goods = 0
47
+ ending_inventory = 0
48
+ if m == n
49
+ case method
50
+ when 'FIFO'
51
+ if sinv <= uinv
52
+ cost_of_goods = sinv * pinv
53
+ ending_inventory = (uinv - sinv) * pinv
54
+ (0...n).each do |i|
55
+ ending_inventory += (units[i] * price[i])
56
+ end
57
+ else
58
+ cost_of_goods = uinv * pinv
59
+ sinv -= uinv
60
+ (0...n).each do |i|
61
+ if sinv <= units[i]
62
+ cost_of_goods += (sinv * price[i])
63
+ ending_inventory = (units[i] - sinv) * price[i]
64
+ if i < n
65
+ temp = i + 1
66
+ (temp...n).each do |j|
67
+ ending_inventory += (units[j] * price[j])
68
+ end
69
+ end
70
+ sinv = 0
71
+ break
72
+ else
73
+ cost_of_goods += (units[i] * price[i])
74
+ sinv -= units[i]
75
+ end
76
+ end
77
+ raise(Error, "Inventory is not enough to sell\n") if sinv.positive?
78
+ end
79
+ when 'WAC'
80
+ ending_inventory = uinv * pinv
81
+ tu = uinv
82
+ (0...n).each do |i|
83
+ ending_inventory += (units[i] * price[i])
84
+ tu += units[i]
85
+ end
86
+ if tu >= sinv
87
+ cost_of_goods = ending_inventory / tu * sinv
88
+ ending_inventory = ending_inventory / tu * (tu - sinv)
89
+ else
90
+ raise(Error, "Inventory is not enough to sell\n")
91
+ end
92
+
93
+ when 'LIFO'
94
+ (n - 1).downto(0).each do |i|
95
+ if sinv <= units[i]
96
+ cost_of_goods += (sinv * price[i])
97
+ ending_inventory = (units[i] - sinv) * price[i]
98
+ if i > 1
99
+ temp = i - 1
100
+ temp.downto(0).each do |j|
101
+ ending_inventory += (units[j] * price[j])
102
+ end
103
+ end
104
+ ending_inventory += (uinv * pinv)
105
+ sinv = 0
106
+ break
107
+ else
108
+ cost_of_goods += (units[i] * price[i])
109
+ sinv -= units[i]
110
+ end
111
+ end
112
+ if sinv.positive?
113
+ if sinv <= uinv
114
+ cost_of_goods += (sinv * pinv)
115
+ ending_inventory += ((uinv - sinv) * pinv)
116
+ else
117
+ raise(Error, "Inventory is not enough to sell\n")
118
+ end
119
+ end
120
+ end
121
+
122
+ else
123
+ raise(Error, "length of units and price are not the same\n")
124
+ end
125
+
126
+ {
127
+ cost_of_goods:,
128
+ ending_inventory:
129
+ }
130
+ end
131
+
132
+ # Depreciation Expense Recognition -- double-declining balance (DDB), the most common declining balance method, which applies two times the straight-line rate to the declining balance.
133
+ #
134
+ # @param cost cost of long-lived assets
135
+ # @param rv residual value of the long-lived assets at the end of its useful life. DDB does not explicitly use the asset's residual value in the calculations, but depreciation ends once the estimated residual value has been reached. If the asset is expected to have no residual value, the DB method will never fully depreciate it, so the DB method is typically changed to straight-line at some point in the asset's life.
136
+ # @param t length of the useful life
137
+ # @example
138
+ # Finrb::Accounting.ddb(cost=1200,rv=200,t=5)
139
+ def self.ddb(cost:, rv:, t:)
140
+ cost = Flt::DecNum(cost.to_s)
141
+ rv = Flt::DecNum(rv.to_s)
142
+ t = Flt::DecNum(t.to_s)
143
+
144
+ raise(Error, 't should be larger than 1') if t < 2
145
+
146
+ ddb = [Flt::DecNum(0)] * t
147
+ ddb[0] = cost * 2 / t
148
+ if cost - ddb.first <= rv
149
+ ddb[0] = cost - rv
150
+ else
151
+ cost -= ddb.first
152
+ (1...t).each do |i|
153
+ ddb[i] = cost * 2 / t
154
+ if cost - ddb[i] <= rv
155
+ ddb[i] = cost - rv
156
+ break
157
+ else
158
+ cost -= ddb[i]
159
+ end
160
+ end
161
+ end
162
+ { t: (0...t).to_a, ddb: }
163
+ end
164
+
165
+ # Depreciation Expense Recognition -- Straight-line depreciation (SL) allocates an equal amount of depreciation each year over the asset's useful life
166
+ #
167
+ # @param cost cost of long-lived assets
168
+ # @param rv residual value of the long-lived assets at the end of its useful life
169
+ # @param t length of the useful life
170
+ # @example
171
+ # Finrb::Accounting.slde(cost=1200,rv=200,t=5)
172
+ def self.slde(cost:, rv:, t:)
173
+ cost = Flt::DecNum(cost.to_s)
174
+ rv = Flt::DecNum(rv.to_s)
175
+ t = Flt::DecNum(t.to_s)
176
+
177
+ ((cost - rv) / t)
178
+ end
179
+ end
180
+ end