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.Api.Tests/FundValuationTests.fs | |
| 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.Api.Tests/FundValuationTests.fs')
| -rw-r--r-- | tests/FundLab.Api.Tests/FundValuationTests.fs | 77 |
1 files changed, 76 insertions, 1 deletions
diff --git a/tests/FundLab.Api.Tests/FundValuationTests.fs b/tests/FundLab.Api.Tests/FundValuationTests.fs index 1897fd3..197686d 100644 --- a/tests/FundLab.Api.Tests/FundValuationTests.fs +++ b/tests/FundLab.Api.Tests/FundValuationTests.fs @@ -19,6 +19,17 @@ module FundValuationSupport = AccruedInterest = None Date = None MaturityDate = None + ParValue = None + IssuePrice = None + ValueDate = None + ListingDate = None + PublishDate = None + PayInterestDay = None + CouponRate = None + CouponRateExplain = None + BondExpireYears = None + Rating = None + DataStatus = None } let stockProbeReturning (prices: Map<string, decimal>) = @@ -109,12 +120,52 @@ type FundValuationTests(fixture: PostgresFixture) = repository().CreateBondTrade( key, fundId, - { InstrumentCode = code; BondName = Some "南航转债"; Quantity = quantity; Price = price } + { + InstrumentCode = code + BondName = Some "南航转债" + Quantity = quantity + Price = price + CleanPrice = price + AccruedInterest = 0m + ParValue = 100m + SettlementDate = DateOnly(2026, 9, 21) + CouponRate = None + ValueDate = None + MaturityDate = None + TradeDate = Some(DateOnly(2026, 9, 21)) + } ) with | BondTradeWriteResult.BondTradeCreated _ -> () | other -> failwithf "unexpected bond trade result: %A" other + let buyBondWithTerms fundId code quantity cleanPrice = + let key = fixture.Key(sprintf "valuation-bond-terms-%s" (Guid.NewGuid().ToString("N"))) + + match + repository().CreateBondTrade( + key, + fundId, + { + InstrumentCode = code + BondName = Some "南航转债" + Quantity = quantity + Price = cleanPrice + CleanPrice = cleanPrice + AccruedInterest = 0m + ParValue = 100m + SettlementDate = DateOnly(2026, 9, 21) + CouponRate = Some 0.02m + ValueDate = Some(DateOnly(2020, 10, 15)) + MaturityDate = Some(DateOnly(2026, 10, 15)) + TradeDate = Some(DateOnly(2026, 9, 21)) + } + ) + with + | BondTradeWriteResult.BondTradeCreated _ -> () + | other -> failwithf "unexpected bond trade result: %A" other + + let app stockPrices bondPrices = App.createApplicationWithProbes (repository ()) (FundValuationSupport.probes stockPrices bondPrices) @@ -143,6 +194,30 @@ type FundValuationTests(fixture: PostgresFixture) = Assert.Contains("\"unavailablePositions\":0", body) [<Fact>] + member _.``bond valuation accrues interest point-in-time and values on a dirty basis``() = + let fundId = createFund 10000.00m + buyBondWithTerms fundId "110075" 10m 106.368m + + let valuationAt asOf = + PersistenceTestHelpers.invoke + (app Map.empty (Map.ofList [ "110075", 106.368m ])) + "GET" + (sprintf "/api/funds/%O/valuation?asOfDate=%s" fundId asOf) + [ "Authorization", "Bearer test-token" ] + "" + + let status, early = valuationAt "2026-09-21" + Assert.Equal(200, status) + Assert.Contains("\"accruedInterest\":\"1.86849300\"", early) + Assert.Contains("\"dirtyPrice\":\"108.23649300\"", early) + Assert.Contains("\"valueBasis\":\"dirty\"", early) + Assert.Contains("\"marketValue\":\"1082.36\"", early) + + let _, later = valuationAt "2026-10-01" + Assert.Contains("\"accruedInterest\":\"1.92328800\"", later) + Assert.Contains("\"marketValue\":\"1082.91\"", later) + + [<Fact>] member _.``all-unavailable valuation keeps cash intact and marks positions unavailable without zeroing``() = let fundId = createFund 5000.00m buyStock fundId "600519" 100m 1000.00m |
