namespace FundLab.Domain.Tests module StockRulesTests = open System open Xunit open FundLab.Domain let private terms = StockTerms.aShareDefault [] let ``buy cost applies commission minimum and transfer fee`` () = // gross 1000.00 -> commission 0.25 floors to 5.00, transfer 0.01. Assert.Equal(1005.01m, StockRules.buyCost terms 100m 10m) // gross 100000.00 -> commission 25.00 (above the minimum), transfer 1.00. Assert.Equal(100026.00m, StockRules.buyCost terms 1000m 100m) [] let ``sell proceeds deduct commission, stamp duty and transfer fee`` () = Assert.Equal(994.49m, StockRules.sellProceeds terms 100m 10m) Assert.Equal(99924.00m, StockRules.sellProceeds terms 1000m 100m) [] let ``sell proceeds are lower than buy cost on a round trip`` () = Assert.True(StockRules.sellProceeds terms 100m 10m < StockRules.buyCost terms 100m 10m) [] let ``quantity must be a whole multiple of the lot size`` () = match StockRules.validateQuantity terms 100m with | Ok value -> Assert.Equal(100m, value) | Error message -> failwithf "expected a valid lot, got %s" message match StockRules.validateQuantity terms 50m with | Ok _ -> failwith "expected a lot-size error" | Error message -> Assert.Contains("multiple of 100", message) match StockRules.validateQuantity terms 0m with | Ok _ -> failwith "expected a positive-quantity error" | Error message -> Assert.Contains("positive", message) [] let ``settlement applies the cutoff then T+1 and skips the weekend`` () = let thursday = DateOnly(2026, 9, 24) // Before the 15:00 cutoff: T+1 lands on Friday. Assert.Equal(DateOnly(2026, 9, 25), StockRules.settlementDate terms thursday (TimeOnly(10, 0))) // At/after the cutoff: the order slips to Friday, T+1 lands on Monday. Assert.Equal(DateOnly(2026, 9, 28), StockRules.settlementDate terms thursday (TimeOnly(15, 0))) Assert.Equal(DateOnly(2026, 9, 28), StockRules.settlementDate terms thursday (TimeOnly(15, 30))) [] let ``effective trade date shifts to the next trading day after the cutoff`` () = let thursday = DateOnly(2026, 9, 24) Assert.Equal(thursday, StockRules.effectiveTradeDate terms thursday (TimeOnly(14, 59))) Assert.Equal(DateOnly(2026, 9, 25), StockRules.effectiveTradeDate terms thursday (TimeOnly(15, 0))) [] let ``a stock bought today cannot be sold until T+1`` () = let thursday = DateOnly(2026, 9, 24) Assert.False(StockRules.isSellAllowed terms thursday thursday) Assert.True(StockRules.isSellAllowed terms thursday (DateOnly(2026, 9, 25)))