Skip to content

Commit 9d73f49

Browse files
authored
Net Present Value (NPV) added. (#285)
# Describe Request Net Present Value (NPV) added. # Change Type Valuation algorithm. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Added Net Present Value (NPV) calculation to the valuation toolkit. - Documentation - Updated main and valuation guides to include NPV explanations and formula. - Expanded helper documentation with additional utilities and updated index/anchors. - No behavioral or API changes beyond the new valuation capability. - Tests - Added comprehensive unit tests and dataset coverage for NPV to ensure accuracy. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 9c0fd14 commit 9d73f49

File tree

6 files changed

+142
-0
lines changed

6 files changed

+142
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ The following list of indicators are currently supported by this package:
106106

107107
### 💰 Asset Valuation
108108
- [Future Value (FV)](valuation/README.md#Fv)
109+
- [Net Present Value (NPV)](valuation/README.md#Npv)
109110
- [Present Value (PV)](valuation/README.md#Pv)
110111

111112
🧠 Strategies Provided

helper/README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,19 @@ The information provided on this project is strictly for informational purposes
5454
- [func First\[T any\]\(c \<\-chan T, count int\) \<\-chan T](<#First>)
5555
- [func Gcd\(values ...int\) int](<#Gcd>)
5656
- [func Head\[T Number\]\(c \<\-chan T, count int\) \<\-chan T](<#Head>)
57+
- [func Highest\[T Number\]\(c \<\-chan T, w int\) \<\-chan T](<#Highest>)
5758
- [func IncrementBy\[T Number\]\(c \<\-chan T, i T\) \<\-chan T](<#IncrementBy>)
5859
- [func JSONToChan\[T any\]\(r io.Reader\) \<\-chan T](<#JSONToChan>)
5960
- [func JSONToChanWithLogger\[T any\]\(r io.Reader, logger \*slog.Logger\) \<\-chan T](<#JSONToChanWithLogger>)
6061
- [func KeepNegatives\[T Number\]\(c \<\-chan T\) \<\-chan T](<#KeepNegatives>)
6162
- [func KeepPositives\[T Number\]\(c \<\-chan T\) \<\-chan T](<#KeepPositives>)
6263
- [func Last\[T any\]\(c \<\-chan T, count int\) \<\-chan T](<#Last>)
6364
- [func Lcm\(values ...int\) int](<#Lcm>)
65+
- [func Lowest\[T Number\]\(c \<\-chan T, w int\) \<\-chan T](<#Lowest>)
6466
- [func Map\[F, T any\]\(c \<\-chan F, f func\(F\) T\) \<\-chan T](<#Map>)
6567
- [func MapWithPrevious\[F, T any\]\(c \<\-chan F, f func\(T, F\) T, previous T\) \<\-chan T](<#MapWithPrevious>)
68+
- [func MaxSince\[T Number\]\(c \<\-chan T, w int\) \<\-chan T](<#MaxSince>)
69+
- [func MinSince\[T Number\]\(c \<\-chan T, w int\) \<\-chan T](<#MinSince>)
6670
- [func Multiply\[T Number\]\(ac, bc \<\-chan T\) \<\-chan T](<#Multiply>)
6771
- [func MultiplyBy\[T Number\]\(c \<\-chan T, m T\) \<\-chan T](<#MultiplyBy>)
6872
- [func Operate\[A any, B any, R any\]\(ac \<\-chan A, bc \<\-chan B, o func\(A, B\) R\) \<\-chan R](<#Operate>)
@@ -81,10 +85,12 @@ The information provided on this project is strictly for informational purposes
8185
- [func Skip\[T any\]\(c \<\-chan T, count int\) \<\-chan T](<#Skip>)
8286
- [func SkipLast\[T any\]\(c \<\-chan T, count int\) \<\-chan T](<#SkipLast>)
8387
- [func SliceToChan\[T any\]\(slice \[\]T\) \<\-chan T](<#SliceToChan>)
88+
- [func SlicesReverse\[T any\]\(r \[\]T, i int, f func\(T\) bool\)](<#SlicesReverse>)
8489
- [func Sqrt\[T Number\]\(c \<\-chan T\) \<\-chan T](<#Sqrt>)
8590
- [func Subtract\[T Number\]\(ac, bc \<\-chan T\) \<\-chan T](<#Subtract>)
8691
- [func SyncPeriod\[T any\]\(commonPeriod, period int, c \<\-chan T\) \<\-chan T](<#SyncPeriod>)
8792
- [func Waitable\[T any\]\(wg \*sync.WaitGroup, c \<\-chan T\) \<\-chan T](<#Waitable>)
93+
- [func Window\[T any\]\(c \<\-chan T, f func\(\[\]T, int\) T, w int\) \<\-chan T](<#Window>)
8894
- [type Bst](<#Bst>)
8995
- [func NewBst\[T Number\]\(\) \*Bst\[T\]](<#NewBst>)
9096
- [func \(b \*Bst\[T\]\) Contains\(value T\) bool](<#Bst[T].Contains>)
@@ -582,6 +588,15 @@ actual := helper.Head(c, 2)
582588
fmt.Println(helper.ChanToSlice(actual)) // [2, 4]
583589
```
584590

591+
<a name="Highest"></a>
592+
## func [Highest](<https://github.com/cinar/indicator/blob/master/helper/highest.go#L11>)
593+
594+
```go
595+
func Highest[T Number](c <-chan T, w int) <-chan T
596+
```
597+
598+
Highest returns a channel that emits the highest value within a sliding window of size w from the input channel c.
599+
585600
<a name="IncrementBy"></a>
586601
## func [IncrementBy](<https://github.com/cinar/indicator/blob/master/helper/increment_by.go#L16>)
587602

@@ -669,6 +684,15 @@ func Lcm(values ...int) int
669684

670685
Lcm calculates the Least Common Multiple of the given numbers.
671686

687+
<a name="Lowest"></a>
688+
## func [Lowest](<https://github.com/cinar/indicator/blob/master/helper/Lowest.go#L11>)
689+
690+
```go
691+
func Lowest[T Number](c <-chan T, w int) <-chan T
692+
```
693+
694+
Lowest returns a channel that emits the lowest value within a sliding window of size w from the input channel c.
695+
672696
<a name="Map"></a>
673697
## func [Map](<https://github.com/cinar/indicator/blob/master/helper/map.go#L17>)
674698

@@ -703,6 +727,24 @@ sum := helper.MapWithPrevious(c, func(p, c int) int {
703727
}, 0)
704728
```
705729

730+
<a name="MaxSince"></a>
731+
## func [MaxSince](<https://github.com/cinar/indicator/blob/master/helper/max_since.go#L14>)
732+
733+
```go
734+
func MaxSince[T Number](c <-chan T, w int) <-chan T
735+
```
736+
737+
MaxSince returns a channel of T indicating since when \(number of previous values\) the respective value was the maximum within the window of size w.
738+
739+
<a name="MinSince"></a>
740+
## func [MinSince](<https://github.com/cinar/indicator/blob/master/helper/min_since.go#L13>)
741+
742+
```go
743+
func MinSince[T Number](c <-chan T, w int) <-chan T
744+
```
745+
746+
MinSince returns a channel of T indicating since when \(number of previous values\) the respective value was the minimum.
747+
706748
<a name="Multiply"></a>
707749
## func [Multiply](<https://github.com/cinar/indicator/blob/master/helper/multiply.go#L20>)
708750

@@ -987,6 +1029,15 @@ fmt.Println(<- c) // 6
9871029
fmt.Println(<- c) // 8
9881030
```
9891031

1032+
<a name="SlicesReverse"></a>
1033+
## func [SlicesReverse](<https://github.com/cinar/indicator/blob/master/helper/slices_reverse.go#L6>)
1034+
1035+
```go
1036+
func SlicesReverse[T any](r []T, i int, f func(T) bool)
1037+
```
1038+
1039+
SlicesReverse loops through a slice in reverse order starting from the given index. The given function is called for each element in the slice. If the function returns false, the loop is terminated.
1040+
9901041
<a name="Sqrt"></a>
9911042
## func [Sqrt](<https://github.com/cinar/indicator/blob/master/helper/sqrt.go#L16>)
9921043

@@ -1040,6 +1091,15 @@ func Waitable[T any](wg *sync.WaitGroup, c <-chan T) <-chan T
10401091

10411092
Waitable increments the wait group before reading from the channel and signals completion when the channel is closed.
10421093

1094+
<a name="Window"></a>
1095+
## func [Window](<https://github.com/cinar/indicator/blob/master/helper/window.go#L12>)
1096+
1097+
```go
1098+
func Window[T any](c <-chan T, f func([]T, int) T, w int) <-chan T
1099+
```
1100+
1101+
Window returns a channel that emits the passed function result within a sliding window of size w from the input channel c. Note: the slice is in the same order than in source channel but the 1st element may not be 0, use modulo window size if order is important.
1102+
10431103
<a name="Bst"></a>
10441104
## type [Bst](<https://github.com/cinar/indicator/blob/master/helper/bst.go#L15-L17>)
10451105

valuation/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import "github.com/cinar/indicator/v2/valuation"
99
## Index
1010

1111
- [func Fv\(pv, rate float64, years int\) float64](<#Fv>)
12+
- [func Npv\(rate float64, cfs \[\]float64\) float64](<#Npv>)
1213
- [func Pv\(fv, rate float64, years int\) float64](<#Pv>)
1314

1415

@@ -25,6 +26,19 @@ Fv calculates the Future Value \(FV\) of a Present Value \(PV\).
2526
Formula: FV = PV * (1 + rate)^years
2627
```
2728

29+
<a name="Npv"></a>
30+
## func [Npv](<https://github.com/cinar/indicator/blob/master/valuation/npv.go#L12>)
31+
32+
```go
33+
func Npv(rate float64, cfs []float64) float64
34+
```
35+
36+
Npv calculates the Net Present Value \(NPV\) of a series of cash flows.
37+
38+
```
39+
Formula: NPV = sum(CF_i / (1 + rate)^i) for i = 1 to n
40+
```
41+
2842
<a name="Pv"></a>
2943
## func [Pv](<https://github.com/cinar/indicator/blob/master/valuation/pv.go#L12>)
3044

valuation/npv.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) 2021-2024 Onur Cinar.
2+
// The source code is provided under GNU AGPLv3 License.
3+
// https://github.com/cinar/indicator
4+
5+
package valuation
6+
7+
import "math"
8+
9+
// Npv calculates the Net Present Value (NPV) of a series of cash flows.
10+
//
11+
// Formula: NPV = sum(CF_i / (1 + rate)^i) for i = 1 to n
12+
func Npv(rate float64, cfs []float64) float64 {
13+
var npv float64
14+
for i, cf := range cfs {
15+
npv += cf / math.Pow(1+rate, float64(i+1))
16+
}
17+
18+
return npv
19+
}
20+

valuation/npv_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) 2021-2024 Onur Cinar.
2+
// The source code is provided under GNU AGPLv3 License.
3+
// https://github.com/cinar/indicator
4+
5+
package valuation_test
6+
7+
import (
8+
"strconv"
9+
"strings"
10+
"testing"
11+
12+
"github.com/cinar/indicator/v2/helper"
13+
"github.com/cinar/indicator/v2/valuation"
14+
)
15+
16+
func TestNpv(t *testing.T) {
17+
type NpvData struct {
18+
Rate float64
19+
CashFlows string
20+
NPV float64
21+
}
22+
23+
parseCashFlows := func(s string) []float64 {
24+
var cashFlows []float64
25+
for _, cfStr := range strings.Split(s, " ") {
26+
cf, _ := strconv.ParseFloat(cfStr, 64)
27+
cashFlows = append(cashFlows, cf)
28+
}
29+
return cashFlows
30+
}
31+
32+
input, err := helper.ReadFromCsvFile[NpvData]("testdata/npv.csv")
33+
if err != nil {
34+
t.Fatal(err)
35+
}
36+
37+
for row := range input {
38+
cashFlows := parseCashFlows(row.CashFlows)
39+
npv := helper.RoundDigit(valuation.Npv(row.Rate, cashFlows), 2)
40+
if npv != row.NPV {
41+
t.Fatalf("actual %v expected %v", npv, row.NPV)
42+
}
43+
}
44+
}

valuation/testdata/npv.csv

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Rate,CashFlows,NPV
2+
0.1,"100 100 100",248.69
3+
0.05,"100 200",276.64

0 commit comments

Comments
 (0)