finrb 0.1.12 → 1.0.1
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 +41 -0
- data/NOTICE.md +20 -0
- data/README.md +225 -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 +55 -21
- 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: b167e3a39320ed5f8026b7451063d08f6eeb8e1047dc06f7688e4afc617434c2
|
|
4
|
+
data.tar.gz: 9407136f5589353ab3704f9bd7bb37b802191aa3aa51ca2bc3248cf359a276da
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9ec1ff7159e4d1aadd5dde4fcb168a0f1a7679e20f59c50bb26a642ccbee1e6aa65b021e67906586382e64aca23a240e566625ea359dc48e9aa2987e24a9c433
|
|
7
|
+
data.tar.gz: 671ae01b065e636c0bf8ef60e851e53866b02e09238d3a233443150a11f09851af493a2462e891a305ce174c6cb57ba492f09f6cfef48ded3693a120e448c410
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# finrb changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.1
|
|
4
|
+
|
|
5
|
+
### Runtime compatibility
|
|
6
|
+
|
|
7
|
+
- Test Ruby 3.3, 3.4, and 4.0 across x86-64, native ARM64, and Docker environments.
|
|
8
|
+
- Add experimental JRuby and TruffleRuby compatibility suites and isolated development/test images.
|
|
9
|
+
- Add Docker Buildx tasks for developing and running the ARM64 image on x86-64 hosts.
|
|
10
|
+
- Add version-selectable, failure-aware Docker build, test, and run tasks.
|
|
11
|
+
|
|
12
|
+
### Packaging and assurance
|
|
13
|
+
|
|
14
|
+
- Verify gem metadata, packaged licenses, attribution, RBS declarations, and runtime dependencies.
|
|
15
|
+
- Install the built gem into an isolated gem home and smoke-test version loading, NPV, IRR, and opt-in core extensions.
|
|
16
|
+
- Add dependency review, ruby-advisory-db auditing, grouped Dependabot updates, workflow timeouts, and concurrency controls.
|
|
17
|
+
- Simplify CodeQL analysis and add reusable package and security verification tasks.
|
|
18
|
+
- Declare `ostruct` as a runtime dependency because it is required by `flt` and is no longer bundled with Ruby 4.
|
|
19
|
+
|
|
20
|
+
## 1.0.0
|
|
21
|
+
|
|
22
|
+
This release intentionally breaks parts of the 0.1 public API.
|
|
23
|
+
|
|
24
|
+
### Financial correctness
|
|
25
|
+
|
|
26
|
+
- Normalize XNPV date calculations and distinguish nominal APR from effective APY.
|
|
27
|
+
- Replace deprecated BigDecimal Newton solving with a decimal Brent–Dekker solver and explicit convergence/domain errors.
|
|
28
|
+
- Validate public financial inputs and centralize amortization rounding policy.
|
|
29
|
+
- Fix FIFO/LIFO ending inventory when a sale is satisfied before every layer is traversed.
|
|
30
|
+
|
|
31
|
+
### Public API
|
|
32
|
+
|
|
33
|
+
- Replace `Finrb::Utils` with `Finrb::TVM`, `Accounting`, `Ratios`, `Returns`, and `Yields`.
|
|
34
|
+
- Expose IRR, NPV, XIRR, and XNPV through `Finrb::Cashflow`.
|
|
35
|
+
- Stop modifying `Array` and `Numeric` by default; legacy fluent methods require `finrb/core_ext`.
|
|
36
|
+
- Publish immutable configuration snapshots with thread-scoped temporary overrides.
|
|
37
|
+
|
|
38
|
+
### Dependencies and assurance
|
|
39
|
+
|
|
40
|
+
- Remove ActiveSupport and `business_time` production dependencies.
|
|
41
|
+
- Add deterministic generated-root tests, SciPy/QuantLib reference verification, line and branch coverage, and packaged RBS signatures.
|
|
42
|
+
- Support Ruby 3.3, 3.4, and 4.0.
|
|
43
|
+
|
|
3
44
|
## 0.1.12
|
|
4
45
|
|
|
5
46
|
- 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,295 @@
|
|
|
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
|
-
|
|
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
|
|
211
|
+
bundle exec rake security:audit
|
|
212
|
+
bundle exec rake package:verify
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
The quality task runs the RSpec suite with line and branch coverage, generated
|
|
216
|
+
IRR/XIRR properties, committed SciPy/QuantLib reference fixtures, and RBS
|
|
217
|
+
validation.
|
|
218
|
+
|
|
219
|
+
`security:audit` updates ruby-advisory-db and checks the locked dependencies.
|
|
220
|
+
`package:verify` builds the gem, validates its contents and metadata, installs
|
|
221
|
+
it with only its declared runtime dependencies, and runs packaged API smoke
|
|
222
|
+
tests without publishing or retaining the temporary installation.
|
|
223
|
+
|
|
224
|
+
CI runs every supported MRI version on x86-64, native ARM64, and Docker, plus
|
|
225
|
+
compatibility specs and Docker builds for the current stable JRuby and
|
|
226
|
+
TruffleRuby. Alternative Ruby jobs are initially informational while their
|
|
227
|
+
dependency and numerical compatibility is assessed.
|
|
228
|
+
|
|
229
|
+
Select the MRI version used by the ordinary Docker tasks with `RUBY_VER`:
|
|
230
|
+
|
|
231
|
+
```shell
|
|
232
|
+
RUBY_VER=3.3 bundle exec rake docker:build
|
|
233
|
+
RUBY_VER=3.3 bundle exec rake docker:test
|
|
234
|
+
RUBY_VER=3.3 bundle exec rake docker:run
|
|
138
235
|
```
|
|
139
236
|
|
|
140
|
-
|
|
237
|
+
On an x86-64 development machine with Docker Buildx and ARM64 emulation
|
|
238
|
+
available, build, test, and run the ARM64 development image with:
|
|
239
|
+
|
|
240
|
+
```shell
|
|
241
|
+
bundle exec rake docker:arm64:build
|
|
242
|
+
bundle exec rake docker:arm64:test
|
|
243
|
+
bundle exec rake docker:arm64:run
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Docker Desktop normally provides the required emulation. A Linux Docker Engine
|
|
247
|
+
installation must have an ARM64-capable Buildx builder and binfmt/QEMU support
|
|
248
|
+
configured by the operator.
|
|
249
|
+
|
|
250
|
+
The alternative Ruby development images share a minimal, package-manager-
|
|
251
|
+
independent Dockerfile. Build, test, and run either implementation with:
|
|
252
|
+
|
|
253
|
+
```shell
|
|
254
|
+
bundle exec rake docker:jruby:build
|
|
255
|
+
bundle exec rake docker:jruby:test
|
|
256
|
+
bundle exec rake docker:jruby:run
|
|
257
|
+
|
|
258
|
+
bundle exec rake docker:truffleruby:build
|
|
259
|
+
bundle exec rake docker:truffleruby:test
|
|
260
|
+
bundle exec rake docker:truffleruby:run
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
The defaults track JRuby 10 on JDK 21 and the current TruffleRuby Community
|
|
264
|
+
image. Override them with `JRUBY_IMAGE` or `TRUFFLERUBY_IMAGE` when testing a
|
|
265
|
+
specific release. These images use `gemfiles/engines.gemfile`, which contains
|
|
266
|
+
only finrb's runtime dependencies and RSpec; MRI-only development tooling such
|
|
267
|
+
as RBS, RuboCop, and coverage is deliberately excluded from engine
|
|
268
|
+
compatibility runs.
|
|
269
|
+
|
|
270
|
+
Maintainers with the optional Python environment can run the larger seeded
|
|
271
|
+
solver verification campaign:
|
|
272
|
+
|
|
273
|
+
```shell
|
|
274
|
+
python3 -m pip install --requirement script/requirements-solver-verification.txt
|
|
275
|
+
bundle exec rake solver:verify
|
|
276
|
+
```
|
|
141
277
|
|
|
142
|
-
|
|
278
|
+
The Python packages are verification references, not gem dependencies. See
|
|
279
|
+
[the fixture documentation](spec/fixtures/README.md) for reproducibility,
|
|
280
|
+
Docker, batching, and tolerance details.
|
|
143
281
|
|
|
144
|
-
##
|
|
282
|
+
## Project links
|
|
145
283
|
|
|
146
|
-
- [RubyGems
|
|
147
|
-
- [Source
|
|
148
|
-
- [
|
|
284
|
+
- [RubyGems](https://rubygems.org/gems/finrb)
|
|
285
|
+
- [Source](https://github.com/ncs1/finrb)
|
|
286
|
+
- [Issue tracker](https://github.com/ncs1/finrb/issues)
|
|
149
287
|
|
|
150
288
|
## Acknowledgements
|
|
151
289
|
|
|
152
|
-
- Martin Bjeldbak Madsen
|
|
153
|
-
|
|
290
|
+
- Martin Bjeldbak Madsen, Bill Kranec, and the contributors to the original
|
|
291
|
+
[finance](https://github.com/Edward-Intelligence/finance) gem
|
|
292
|
+
- Yanhui Fan and the contributors to R's
|
|
293
|
+
[FinCal](https://github.com/felixfan/FinCal) package
|
|
154
294
|
|
|
155
295
|
## License
|
|
156
296
|
|
|
157
|
-
|
|
297
|
+
finrb is available under the GNU Lesser General Public License v3.0 or later.
|
|
298
|
+
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
|