summaryrefslogtreecommitdiff
path: root/src/FundLab.Domain/Redemption.fs
blob: a3e91885fe2ef834bdda34fc06ac107c81508390 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
namespace FundLab.Domain

open System

module RedemptionPolicy =
    let unitsMaximum = 99999999999999999999.99999999m
    let cashMaximum = 999999999999999999.99m

    let roundDown (scale: int) (value: decimal) : decimal =
        let factor = decimal (pown 10 scale)
        Decimal.Truncate(value * factor) / factor

    let validateRequest
        (availableUnits: decimal)
        (requestedUnits: decimal)
        (feeAmount: decimal)
        : Result<unit, string> =
        if requestedUnits <= 0m then
            Error "redemption units must be positive"
        elif requestedUnits > unitsMaximum then
            Error "redemption units exceed supported precision"
        elif requestedUnits > availableUnits then
            Error "requested units exceed available holdings"
        elif feeAmount < 0m then
            Error "redemption fee cannot be negative"
        elif feeAmount > cashMaximum then
            Error "redemption fee exceeds supported precision"
        else
            Ok ()

    type RedemptionComputation =
        {
            GrossCash: decimal
            Fee: decimal
            Proceeds: decimal
            RedeemedCost: decimal
        }

    let compute
        (requestedUnits: decimal)
        (unitNav: decimal)
        (feeAmount: decimal)
        (positionCost: decimal)
        (positionUnits: decimal)
        : Result<RedemptionComputation, string> =
        if unitNav <= 0m then
            Error "unit nav must be positive"
        elif unitNav < 0.00000001m then
            Error "unit nav is below database precision"
        elif requestedUnits <= 0m then
            Error "redemption units must be positive"
        elif positionUnits <= 0m then
            Error "position units must be positive"
        elif requestedUnits > positionUnits then
            Error "redemption units exceed position units"
        elif feeAmount < 0m then
            Error "redemption fee cannot be negative"
        else
            let gross = requestedUnits * unitNav
            let grossRounded = roundDown 2 gross

            if grossRounded <= 0m then
                Error "redemption cash rounds to zero"
            elif feeAmount > grossRounded then
                Error "redemption fee exceeds redemption value"
            else
                let proceeds = grossRounded - feeAmount

                if proceeds <= 0m then
                    Error "redemption proceeds rounds to zero"
                else
                    let redeemedCost =
                        if requestedUnits = positionUnits then
                            positionCost
                        else
                            roundDown 2 (positionCost * requestedUnits / positionUnits)

                    Ok
                        {
                            GrossCash = grossRounded
                            Fee = feeAmount
                            Proceeds = proceeds
                            RedeemedCost = redeemedCost
                        }