diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-22 08:58:52 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-22 08:58:52 +0800 |
| commit | 72f466b9b3ea6ece726e6b182cfc36bbd3f6fa15 (patch) | |
| tree | 8d1bf9285314eb37ce47525e4d0c052fcdf08ca8 /tests | |
| parent | d4b0c26b396bfcf1be029d8db3a1c0fc033a6765 (diff) | |
| download | fund-lab-72f466b9b3ea6ece726e6b182cfc36bbd3f6fa15.tar.gz | |
Add stock probe market/adjust/suspension fields and StockRules (3d-30 B1-B2)
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/FundLab.Api.Tests/FundValuationTests.fs | 2 | ||||
| -rw-r--r-- | tests/FundLab.Api.Tests/StockQuoteProbeTests.fs | 18 | ||||
| -rw-r--r-- | tests/FundLab.Api.Tests/StockSellTests.fs | 2 | ||||
| -rw-r--r-- | tests/FundLab.Api.Tests/StockTradeTests.fs | 3 | ||||
| -rw-r--r-- | tests/FundLab.Domain.Tests/FundLab.Domain.Tests.fsproj | 1 | ||||
| -rw-r--r-- | tests/FundLab.Domain.Tests/StockRulesTests.fs | 62 |
6 files changed, 86 insertions, 2 deletions
diff --git a/tests/FundLab.Api.Tests/FundValuationTests.fs b/tests/FundLab.Api.Tests/FundValuationTests.fs index 197686d..409bfba 100644 --- a/tests/FundLab.Api.Tests/FundValuationTests.fs +++ b/tests/FundLab.Api.Tests/FundValuationTests.fs @@ -7,7 +7,7 @@ open FundLab.Api module FundValuationSupport = let stockQuote (code: string) (price: decimal) : StockQuote = - { Code = code; Name = Some "贵州茅台"; Price = Some price; Currency = "CNY" } + { Code = code; Name = Some "贵州茅台"; Price = Some price; Currency = "CNY"; Market = Some "sh"; Adjust = Some "none"; Suspended = Some false } let bondQuote (code: string) (price: decimal) : BondQuote = { diff --git a/tests/FundLab.Api.Tests/StockQuoteProbeTests.fs b/tests/FundLab.Api.Tests/StockQuoteProbeTests.fs index 2cad557..b869f17 100644 --- a/tests/FundLab.Api.Tests/StockQuoteProbeTests.fs +++ b/tests/FundLab.Api.Tests/StockQuoteProbeTests.fs @@ -58,6 +58,21 @@ type StockQuoteProbeTests(fixture: PostgresFixture) = | Error failure -> failwithf "expected a stock quote, got %A" failure [<Fact>] + member _.``probe parses market, adjust and suspension flags``() = + let json = + "{\"schema_version\":\"fund-lab.akshare.v1\",\"operation\":\"stock-quote\",\"source\":\"akshare\",\"source_revision\":\"akshare-test/eastmoney\",\"collected_at\":\"2026-09-21T08:00:00Z\",\"instrument\":{\"code\":\"000001\"},\"quote\":{\"name\":\"平安银行\",\"price\":\"11.2300\",\"currency\":\"CNY\",\"market\":\"sz\",\"adjust\":\"none\",\"suspended\":true}}" + + let probe = + AkshareStockQuoteProbe(StockQuoteProbeSupport.collectorReturning (fun () -> Ok json)) :> IStockQuoteProbe + + match probe.GetQuote("000001", CancellationToken.None) with + | Ok quote -> + Assert.Equal<string option>(Some "sz", quote.Market) + Assert.Equal<string option>(Some "none", quote.Adjust) + Assert.Equal<bool option>(Some true, quote.Suspended) + | Error failure -> failwithf "expected a stock quote, got %A" failure + + [<Fact>] member _.``probe keeps a missing name missing instead of fabricating data``() = let json = StockQuoteProbeSupport.payload "600519" None (Some "1252.57") @@ -139,6 +154,9 @@ type StockQuoteProbeTests(fixture: PostgresFixture) = Name = Some "贵州茅台" Price = Some 1252.57m Currency = "CNY" + Market = Some "sh" + Adjust = Some "none" + Suspended = Some false }) let status, body = diff --git a/tests/FundLab.Api.Tests/StockSellTests.fs b/tests/FundLab.Api.Tests/StockSellTests.fs index cbdef8b..972293a 100644 --- a/tests/FundLab.Api.Tests/StockSellTests.fs +++ b/tests/FundLab.Api.Tests/StockSellTests.fs @@ -6,7 +6,7 @@ open FundLab.Api module StockSellSupport = let quote (code: string) (price: decimal) : StockQuote = - { Code = code; Name = Some "招商银行"; Price = Some price; Currency = "CNY" } + { Code = code; Name = Some "招商银行"; Price = Some price; Currency = "CNY"; Market = Some "sh"; Adjust = Some "none"; Suspended = Some false } let stubQuoteProbe (quotes: Map<string, decimal>) = { new IStockQuoteProbe with diff --git a/tests/FundLab.Api.Tests/StockTradeTests.fs b/tests/FundLab.Api.Tests/StockTradeTests.fs index a2269a1..f8a8492 100644 --- a/tests/FundLab.Api.Tests/StockTradeTests.fs +++ b/tests/FundLab.Api.Tests/StockTradeTests.fs @@ -20,6 +20,9 @@ module StockTradeSupport = Name = Some "贵州茅台" Price = Some 1252.57m Currency = "CNY" + Market = Some "sh" + Adjust = Some "none" + Suspended = Some false } let appWithQuote (probe: IStockQuoteProbe) = diff --git a/tests/FundLab.Domain.Tests/FundLab.Domain.Tests.fsproj b/tests/FundLab.Domain.Tests/FundLab.Domain.Tests.fsproj index 468ee16..c5b87a1 100644 --- a/tests/FundLab.Domain.Tests/FundLab.Domain.Tests.fsproj +++ b/tests/FundLab.Domain.Tests/FundLab.Domain.Tests.fsproj @@ -22,6 +22,7 @@ <Compile Include="DomainTests.fs" /> <Compile Include="InvestmentPlanTests.fs" /> <Compile Include="BondRulesTests.fs" /> + <Compile Include="StockRulesTests.fs" /> <Compile Include="Program.fs" /> </ItemGroup> </Project> diff --git a/tests/FundLab.Domain.Tests/StockRulesTests.fs b/tests/FundLab.Domain.Tests/StockRulesTests.fs new file mode 100644 index 0000000..ddf943f --- /dev/null +++ b/tests/FundLab.Domain.Tests/StockRulesTests.fs @@ -0,0 +1,62 @@ +namespace FundLab.Domain.Tests + +module StockRulesTests = + + open System + open Xunit + open FundLab.Domain + + let private terms = StockTerms.aShareDefault + + [<Fact>] + 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) + + [<Fact>] + 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) + + [<Fact>] + let ``sell proceeds are lower than buy cost on a round trip`` () = + Assert.True(StockRules.sellProceeds terms 100m 10m < StockRules.buyCost terms 100m 10m) + + [<Fact>] + 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) + + [<Fact>] + 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))) + + [<Fact>] + 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))) + + [<Fact>] + 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))) |
