summaryrefslogtreecommitdiff
path: root/src/FundLab.Domain
diff options
context:
space:
mode:
Diffstat (limited to 'src/FundLab.Domain')
-rw-r--r--src/FundLab.Domain/FundLab.Domain.fsproj1
-rw-r--r--src/FundLab.Domain/Redemption.fs84
2 files changed, 85 insertions, 0 deletions
diff --git a/src/FundLab.Domain/FundLab.Domain.fsproj b/src/FundLab.Domain/FundLab.Domain.fsproj
index 9e46c00..a2c5c24 100644
--- a/src/FundLab.Domain/FundLab.Domain.fsproj
+++ b/src/FundLab.Domain/FundLab.Domain.fsproj
@@ -8,6 +8,7 @@
<ItemGroup>
<Compile Include="Domain.fs" />
<Compile Include="Ledger.fs" />
+ <Compile Include="Redemption.fs" />
<Compile Include="Performance.fs" />
</ItemGroup>
</Project>
diff --git a/src/FundLab.Domain/Redemption.fs b/src/FundLab.Domain/Redemption.fs
new file mode 100644
index 0000000..a3e9188
--- /dev/null
+++ b/src/FundLab.Domain/Redemption.fs
@@ -0,0 +1,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
+ }