diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-22 08:07:00 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-22 08:07:00 +0800 |
| commit | b05e728ae23d088ca6c9ecccf6ed00d2ab6f3839 (patch) | |
| tree | 4846648aa87263742c1181c189287b53c3bf84de /tests/FundLab.Domain.Tests | |
| parent | 27a85d9070abd000245b2a2e6460ddf9fd5eb97e (diff) | |
| download | fund-lab-b05e728ae23d088ca6c9ecccf6ed00d2ab6f3839.tar.gz | |
Add bond full milestone: profile probe, coupon/accrual rules, ledger and point-in-time valuation (3d-29)
Diffstat (limited to 'tests/FundLab.Domain.Tests')
| -rw-r--r-- | tests/FundLab.Domain.Tests/BondRulesTests.fs | 70 | ||||
| -rw-r--r-- | tests/FundLab.Domain.Tests/FundLab.Domain.Tests.fsproj | 1 |
2 files changed, 71 insertions, 0 deletions
diff --git a/tests/FundLab.Domain.Tests/BondRulesTests.fs b/tests/FundLab.Domain.Tests/BondRulesTests.fs new file mode 100644 index 0000000..31069f1 --- /dev/null +++ b/tests/FundLab.Domain.Tests/BondRulesTests.fs @@ -0,0 +1,70 @@ +namespace FundLab.Domain.Tests + +module BondRulesTests = + + open System + open Xunit + open FundLab.Domain + + let private annualCb = + BondTerms.create + 100m + 0.02m + 1 + (DateOnly(2020, 10, 15)) + (DateOnly(2026, 10, 15)) + 10m + 0 + 0m + + [<Fact>] + let ``coupon schedule runs from value date through maturity inclusive`` () = + let schedule = BondRules.couponSchedule annualCb + + Assert.Equal(7, List.length schedule) + Assert.Equal(DateOnly(2020, 10, 15), List.head schedule) + Assert.Equal(DateOnly(2026, 10, 15), List.last schedule) + Assert.Equal(DateOnly(2025, 10, 15), List.item 5 schedule) + + [<Fact>] + let ``accrued interest uses actual over actual days in the current period`` () = + // Period 2025-10-15 .. 2026-10-15 is 365 days; 2026-04-15 is 182 days in. + let accrued = BondRules.accruedInterest annualCb (DateOnly(2026, 4, 15)) + Assert.Equal(0.997260m, accrued) + + [<Fact>] + let ``accrued interest is zero before the value date and after maturity`` () = + Assert.Equal(0m, BondRules.accruedInterest annualCb (DateOnly(2020, 10, 14))) + Assert.Equal(0m, BondRules.accruedInterest annualCb (DateOnly(2027, 1, 1))) + + [<Fact>] + let ``dirty price is clean price plus accrued interest`` () = + Assert.Equal(106.368m, BondRules.dirtyPrice 106.368m 0m) + Assert.Equal(107.368m, BondRules.dirtyPrice 106.368m 1.0m) + + [<Fact>] + let ``trade cost is quantity times price for a 100 par bond`` () = + // 10 张 * 106.368 = 1063.68, matching the existing bond-trade contract. + Assert.Equal(1063.68m, BondRules.tradeCost annualCb 10m 106.368m) + + [<Fact>] + let ``trade cost applies the instrument fee rate`` () = + let terms = { annualCb with FeeRate = 0.0002m } + // 1063.68 gross + 0.212736 -> 0.21 fee. + Assert.Equal(1063.89m, BondRules.tradeCost terms 10m 106.368m) + + [<Fact>] + let ``settlement date is T+0 for exchange bonds and skips weekends otherwise`` () = + Assert.Equal(DateOnly(2026, 9, 18), BondRules.settlementDate annualCb (DateOnly(2026, 9, 18))) + let t1 = { annualCb with SettlementDays = 1 } + Assert.Equal(DateOnly(2026, 9, 21), BondRules.settlementDate t1 (DateOnly(2026, 9, 18))) + + [<Fact>] + let ``quantity must be positive and a whole multiple of the minimum unit`` () = + match BondRules.validateQuantity annualCb 10m with + | Ok value -> Assert.Equal(10m, value) + | Error message -> failwith message + + Assert.True(BondRules.validateQuantity annualCb 15m |> Result.isError) + Assert.True(BondRules.validateQuantity annualCb 0m |> Result.isError) + Assert.True(BondRules.validateQuantity annualCb -10m |> Result.isError) diff --git a/tests/FundLab.Domain.Tests/FundLab.Domain.Tests.fsproj b/tests/FundLab.Domain.Tests/FundLab.Domain.Tests.fsproj index a9c6fc0..468ee16 100644 --- a/tests/FundLab.Domain.Tests/FundLab.Domain.Tests.fsproj +++ b/tests/FundLab.Domain.Tests/FundLab.Domain.Tests.fsproj @@ -21,6 +21,7 @@ <ItemGroup> <Compile Include="DomainTests.fs" /> <Compile Include="InvestmentPlanTests.fs" /> + <Compile Include="BondRulesTests.fs" /> <Compile Include="Program.fs" /> </ItemGroup> </Project> |
