From b05e728ae23d088ca6c9ecccf6ed00d2ab6f3839 Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Tue, 22 Sep 2026 08:07:00 +0800 Subject: Add bond full milestone: profile probe, coupon/accrual rules, ledger and point-in-time valuation (3d-29) --- src/FundLab.Api/MarketData.fs | 67 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 9 deletions(-) (limited to 'src/FundLab.Api/MarketData.fs') diff --git a/src/FundLab.Api/MarketData.fs b/src/FundLab.Api/MarketData.fs index e621740..152ffd7 100644 --- a/src/FundLab.Api/MarketData.fs +++ b/src/FundLab.Api/MarketData.fs @@ -49,6 +49,17 @@ type MarketDataBondQuotePayload = AccruedInterest: decimal option Date: DateOnly option MaturityDate: DateOnly option + ParValue: decimal option + IssuePrice: decimal option + ValueDate: DateOnly option + ListingDate: DateOnly option + PublishDate: DateOnly option + PayInterestDay: string option + CouponRate: decimal option + CouponRateExplain: string option + BondExpireYears: string option + Rating: string option + DataStatus: string option } type MarketDataStockQuotePayload = @@ -379,17 +390,44 @@ module MarketData = let! name = optionalString quoteProperty "name" let! priceProperty = requiredProperty quoteProperty "price" let! price = optionalDecimal "price" priceProperty - let! cleanPrice = - optionalProperty quoteProperty "clean_price" - |> Result.bind (function Some property -> optionalDecimal "clean_price" property | None -> Ok None) - let! accruedInterest = - optionalProperty quoteProperty "accrued_interest" - |> Result.bind (function Some property -> optionalDecimal "accrued_interest" property | None -> Ok None) - let! date = - optionalProperty quoteProperty "date" - |> Result.bind (function Some property -> optionalIsoDate "date" property | None -> Ok None) + let optionalDecimalField fieldName = + optionalProperty quoteProperty fieldName + |> Result.bind (function + | Some property -> optionalDecimal fieldName property + | None -> Ok None) + + let optionalDateField fieldName = + optionalProperty quoteProperty fieldName + |> Result.bind (function + | Some property -> optionalIsoDate fieldName property + | None -> Ok None) + + let optionalStringField fieldName = + optionalProperty quoteProperty fieldName + |> Result.bind (function + | Some property when property.ValueKind = JsonValueKind.Null -> Ok None + | Some property when property.ValueKind = JsonValueKind.String -> + let value = property.GetString() + if String.IsNullOrWhiteSpace value then Ok None else Ok(Some value) + | Some _ -> Error(sprintf "payload property '%s' must be null or a string" fieldName) + | None -> Ok None) + + let! cleanPrice = optionalDecimalField "clean_price" + let! accruedInterest = optionalDecimalField "accrued_interest" + let! date = optionalDateField "date" let! maturityProperty = requiredProperty quoteProperty "maturity_date" let! maturityDate = optionalIsoDate "maturity_date" maturityProperty + let! parValue = optionalDecimalField "par_value" + let! issuePrice = optionalDecimalField "issue_price" + let! valueDate = optionalDateField "value_date" + let! listingDate = optionalDateField "listing_date" + let! publishDate = optionalDateField "publish_date" + let! payInterestDay = optionalStringField "pay_interest_day" + let! couponRate = optionalDecimalField "coupon_rate" + let! couponRateExplain = optionalStringField "coupon_rate_explain" + let! bondExpireYears = optionalStringField "bond_expire_years" + let! rating = optionalStringField "rating" + let! dataStatus = optionalStringField "data_status" return { @@ -403,6 +441,17 @@ module MarketData = AccruedInterest = accruedInterest Date = date MaturityDate = maturityDate + ParValue = parValue + IssuePrice = issuePrice + ValueDate = valueDate + ListingDate = listingDate + PublishDate = publishDate + PayInterestDay = payInterestDay + CouponRate = couponRate + CouponRateExplain = couponRateExplain + BondExpireYears = bondExpireYears + Rating = rating + DataStatus = dataStatus } } with -- cgit v1.2.3