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 +4 -4
- data/CHANGELOG.md +24 -0
- data/NOTICE.md +20 -0
- data/README.md +172 -84
- data/lib/finrb/accounting.rb +180 -0
- data/lib/finrb/amortization.rb +28 -23
- data/lib/finrb/cashflows.rb +101 -66
- data/lib/finrb/config.rb +53 -4
- data/lib/finrb/core_ext/array.rb +6 -0
- data/lib/finrb/core_ext/numeric.rb +12 -0
- data/lib/finrb/core_ext.rb +11 -0
- data/lib/finrb/decimal.rb +0 -10
- data/lib/finrb/errors.rb +8 -0
- data/lib/finrb/numerical/brent.rb +135 -0
- data/lib/finrb/numerical/rate_search.rb +79 -0
- data/lib/finrb/precision.rb +29 -0
- data/lib/finrb/rates.rb +46 -18
- data/lib/finrb/ratios.rb +243 -0
- data/lib/finrb/returns.rb +155 -0
- data/lib/finrb/transaction.rb +23 -7
- data/lib/finrb/tvm.rb +126 -0
- data/lib/finrb/validation.rb +27 -0
- data/lib/finrb/version.rb +6 -0
- data/lib/finrb/yields.rb +224 -0
- data/lib/finrb.rb +9 -4
- data/sig/finrb.rbs +204 -0
- metadata +49 -29
- data/lib/finrb/utils.rb +0 -1094
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e783eb0696497a848cc5506b4bd0d75e4327b383a8a4c998fd34ed86c3092a8c
|
|
4
|
+
data.tar.gz: ca82637abc1703af93554215a2704469af63a3e4d95c4659da9ab86df289bdf2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
[](https://github.com/ncs1/finrb/actions/workflows/codeql.yml)
|
|
5
5
|
[](https://github.com/ncs1/finrb/actions/workflows/rubocop.yml)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Precision-first financial mathematics for Ruby.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18
|
+
## Installation
|
|
20
19
|
|
|
21
|
-
finrb
|
|
20
|
+
Add finrb to your bundle:
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
```shell
|
|
23
|
+
bundle add finrb
|
|
24
|
+
```
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
Or install it directly:
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
```shell
|
|
29
|
+
gem install finrb
|
|
30
|
+
```
|
|
28
31
|
|
|
29
|
-
|
|
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
|
-
|
|
34
|
+
## Quick start
|
|
38
35
|
|
|
39
|
-
|
|
36
|
+
```ruby
|
|
37
|
+
require 'finrb'
|
|
40
38
|
|
|
41
|
-
|
|
39
|
+
cashflows = [-4000, 1200, 1410, 1875, 1050]
|
|
42
40
|
|
|
43
|
-
|
|
41
|
+
Finrb::Cashflow.irr(cashflows).round(6)
|
|
42
|
+
# => Flt::DecNum('0.142993')
|
|
44
43
|
|
|
45
|
-
|
|
44
|
+
Finrb::Cashflow.npv(cashflows, 0.10).round(2)
|
|
45
|
+
# => Flt::DecNum('382.08')
|
|
46
|
+
```
|
|
46
47
|
|
|
47
|
-
|
|
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
|
-
|
|
51
|
+
## Financial domains
|
|
50
52
|
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
67
|
+
## Cashflows
|
|
56
68
|
|
|
57
|
-
|
|
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
|
-
|
|
72
|
+
```ruby
|
|
73
|
+
require 'date'
|
|
60
74
|
|
|
61
|
-
|
|
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
|
-
|
|
80
|
+
rate = Finrb::Cashflow.xirr(transactions, 0.10)
|
|
81
|
+
rate.apy.round(6)
|
|
82
|
+
# => Flt::DecNum('0.117863')
|
|
83
|
+
```
|
|
64
84
|
|
|
65
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
+
```ruby
|
|
93
|
+
cashflows = [-100, 230, -132] # roots at 10% and 20%
|
|
70
94
|
|
|
71
|
-
|
|
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
|
-
|
|
99
|
+
## Rates and amortization
|
|
74
100
|
|
|
75
|
-
|
|
101
|
+
APR is a nominal annual rate; APY is an effective annual rate. They are not
|
|
102
|
+
aliases:
|
|
76
103
|
|
|
77
|
-
|
|
104
|
+
```ruby
|
|
105
|
+
rate = Finrb::Rate.new(0.12, :apr)
|
|
78
106
|
|
|
79
|
-
|
|
107
|
+
rate.monthly # => Flt::DecNum('0.01')
|
|
108
|
+
rate.apy.round(6) # => Flt::DecNum('0.126825')
|
|
109
|
+
```
|
|
80
110
|
|
|
81
|
-
|
|
111
|
+
Create a fixed-rate loan by giving the rate a duration in months:
|
|
82
112
|
|
|
83
|
-
|
|
113
|
+
```ruby
|
|
114
|
+
rate = Finrb::Rate.new(0.0425, :apr, duration: 30 * 12)
|
|
115
|
+
loan = Finrb::Amortization.new(250_000, rate)
|
|
84
116
|
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
|
|
125
|
+
```ruby
|
|
126
|
+
faster = Finrb::Amortization.new(250_000, rate) do |period|
|
|
127
|
+
period.payment - 150
|
|
128
|
+
end
|
|
129
|
+
```
|
|
90
130
|
|
|
91
|
-
|
|
131
|
+
Payments and interest follow the sign convention used throughout finrb:
|
|
132
|
+
money received is positive and money paid is negative.
|
|
92
133
|
|
|
93
|
-
|
|
134
|
+
## Configuration
|
|
94
135
|
|
|
95
|
-
|
|
136
|
+
Configure process-wide defaults during application startup:
|
|
96
137
|
|
|
97
|
-
|
|
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
|
-
|
|
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
|
-
|
|
151
|
+
```ruby
|
|
152
|
+
Finrb.with_config(guess: 0.25) do
|
|
153
|
+
Finrb::Cashflow.irr(cashflows)
|
|
154
|
+
end
|
|
155
|
+
```
|
|
102
156
|
|
|
103
|
-
|
|
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
|
-
|
|
160
|
+
## Precision, rounding, and failures
|
|
106
161
|
|
|
107
|
-
|
|
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
|
-
|
|
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
|
-
|
|
172
|
+
finrb reports invalid financial or numerical states explicitly:
|
|
112
173
|
|
|
113
|
-
|
|
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
|
-
|
|
179
|
+
## Migrating from the legacy API
|
|
116
180
|
|
|
117
|
-
|
|
181
|
+
The current API contains intentional breaking changes:
|
|
118
182
|
|
|
119
|
-
|
|
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
|
-
|
|
190
|
+
Applications migrating gradually can explicitly load the legacy fluent core
|
|
191
|
+
extensions:
|
|
122
192
|
|
|
123
|
-
|
|
193
|
+
```ruby
|
|
194
|
+
require 'finrb/core_ext'
|
|
124
195
|
|
|
125
|
-
|
|
196
|
+
[-4000, 1200, 1410, 1875, 1050].irr
|
|
197
|
+
250_000.amortize(rate)
|
|
198
|
+
```
|
|
126
199
|
|
|
127
|
-
|
|
200
|
+
New code should use `Finrb::Cashflow.irr(cashflows)` and
|
|
201
|
+
`Finrb::Amortization.new(principal, rate)`.
|
|
128
202
|
|
|
129
|
-
|
|
203
|
+
## Development and verification
|
|
130
204
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
229
|
+
## Project links
|
|
145
230
|
|
|
146
|
-
- [RubyGems
|
|
147
|
-
- [Source
|
|
148
|
-
- [
|
|
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
|
|
153
|
-
|
|
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
|
-
|
|
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
|