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 = 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 = 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 }