diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-22 13:03:36 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-22 13:03:36 +0800 |
| commit | 2dedc30358d1724f60ab646b7a24e93f3672dff1 (patch) | |
| tree | 515258bf4b3e1546fdf97c481e12740c573ac7c8 | |
| parent | 39965954968436911b32e774d32d3cca9527e4e2 (diff) | |
| download | fund-lab-2dedc30358d1724f60ab646b7a24e93f3672dff1.tar.gz | |
Debit available cash on manual stock and bond buys with ledger events (3d-34)
| -rw-r--r-- | qa/driver/browser-test.js | 23 | ||||
| -rw-r--r-- | src/FundLab.Api/App.fs | 2 | ||||
| -rw-r--r-- | src/FundLab.Api/Persistence.fs | 140 | ||||
| -rw-r--r-- | tests/FundLab.Api.Tests/CashReconciliationTests.fs | 144 | ||||
| -rw-r--r-- | tests/FundLab.Api.Tests/FundValuationTests.fs | 9 | ||||
| -rw-r--r-- | tests/FundLab.Api.Tests/RebalanceStockTests.fs | 3 | ||||
| -rw-r--r-- | tests/FundLab.Api.Tests/StockSellTests.fs | 8 | ||||
| -rw-r--r-- | tests/FundLab.Api.Tests/StockTradeTests.fs | 2 |
8 files changed, 264 insertions, 67 deletions
diff --git a/qa/driver/browser-test.js b/qa/driver/browser-test.js index fda7c6f..37c27a0 100644 --- a/qa/driver/browser-test.js +++ b/qa/driver/browser-test.js @@ -921,6 +921,29 @@ async function stockTradeScenario(page) { const token = process.env.QA_TOKEN || "qa-token"; + // 3d-34: 手动买入现在会扣减可用现金;P6 再平衡后该基金仅剩 0.01, + // 因此先追加资金,使股票/债券买入能够成交并保持 B6 现金守恒成立。 + const depositStatus = await page.evaluate( + async ({ token, fundId }) => { + const randomKey = () => { + if (window.crypto && window.crypto.randomUUID) return window.crypto.randomUUID().replace(/-/g, ""); + return Math.random().toString(16).slice(2).padEnd(32, "0").slice(0, 32); + }; + const response = await fetch(`/api/funds/${fundId}/capital/deposit`, { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + "Idempotency-Key": randomKey(), + }, + body: JSON.stringify({ amount: "130000.00", note: "3d-34 手动买入前追加资金" }), + }); + return response.status; + }, + { token, fundId: await currentFundId(page) } + ); + check("S4z 手动买入前追加资金成功", depositStatus === 201, `status=${depositStatus}`); + lastStockTradeResponse = null; lastStockPositionsResponse = null; await page.fill(".stock-code-input", "600519"); diff --git a/src/FundLab.Api/App.fs b/src/FundLab.Api/App.fs index c14ab66..4facf2b 100644 --- a/src/FundLab.Api/App.fs +++ b/src/FundLab.Api/App.fs @@ -2863,6 +2863,8 @@ module App = return! invokeHandler (errorResponse 400 "INVALID_BOND_TRADE_REQUEST" message) next ctx | BondTradeWriteResult.BondTradeFundNotFound -> return! invokeHandler (errorResponse 404 "FUND_NOT_FOUND" "fund was not found") next ctx + | BondTradeWriteResult.BondTradeInsufficientFunds message -> + return! invokeHandler (errorResponse 400 "INSUFFICIENT_FUNDS" message) next ctx with _ -> return! invokeHandler (errorResponse 500 "PERSISTENCE_ERROR" "bond trade persistence failed") next ctx } diff --git a/src/FundLab.Api/Persistence.fs b/src/FundLab.Api/Persistence.fs index 2ca11cd..f18ad68 100644 --- a/src/FundLab.Api/Persistence.fs +++ b/src/FundLab.Api/Persistence.fs @@ -554,6 +554,7 @@ type BondTradeWriteResult = | BondTradeIdempotencyConflict | BondTradeInvalid of string | BondTradeFundNotFound + | BondTradeInsufficientFunds of string type SipPlanRecord = { @@ -5999,7 +6000,7 @@ type FundRepository(connectionString: string) = let costCash = Decimal.Round(normalized.Quantity * normalized.Price, 2, MidpointRounding.AwayFromZero) let debited = - if defaultArg debitAvailableCash false then + if defaultArg debitAvailableCash true then use cashCommand = commandWithTransaction connection @@ -6037,7 +6038,7 @@ type FundRepository(connectionString: string) = insertStockTrade connection (Some transaction) trade insertStockTradeIdempotency connection (Some transaction) idempotencyKey fingerprint trade.Id fundId - if defaultArg debitAvailableCash false then + if defaultArg debitAvailableCash true then insertCashLedgerEvent connection (Some transaction) @@ -6481,7 +6482,7 @@ type FundRepository(connectionString: string) = raise error - member _.CreateBondTrade(idempotencyKey: string, fundId: Guid, command: BondTradeCommand, ?executedAtOverride: DateTimeOffset) : BondTradeWriteResult = + member _.CreateBondTrade(idempotencyKey: string, fundId: Guid, command: BondTradeCommand, ?executedAtOverride: DateTimeOffset, ?debitAvailableCash: bool) : BondTradeWriteResult = if String.IsNullOrWhiteSpace idempotencyKey then BondTradeWriteResult.BondTradeInvalid "idempotency key cannot be empty" else @@ -6544,61 +6545,96 @@ type FundRepository(connectionString: string) = MidpointRounding.AwayFromZero ) - let trade: BondTradeRecord = - { - Id = Guid.NewGuid() - FundId = fundId - InstrumentCode = normalized.InstrumentCode - BondName = normalized.BondName - Quantity = normalized.Quantity - Price = normalized.Price - CleanPrice = normalized.CleanPrice - AccruedInterest = normalized.AccruedInterest - ParValue = normalized.ParValue - SettlementDate = normalized.SettlementDate - CouponRate = normalized.CouponRate - ValueDate = normalized.ValueDate - MaturityDate = normalized.MaturityDate - TradeDate = normalized.TradeDate |> Option.defaultValue normalized.SettlementDate - CostCash = costCash - IsSynthetic = isSynthetic - ExecutedAt = executedAt - } + let debited = + if defaultArg debitAvailableCash true then + use cashCommand = + commandWithTransaction + connection + (Some transaction) + "UPDATE funds SET available_cash = available_cash - @cost WHERE id = @fund_id AND available_cash >= @cost" - insertBondTrade connection (Some transaction) trade - insertBondTradeIdempotency connection (Some transaction) idempotencyKey fingerprint trade.Id fundId + addParameter cashCommand "cost" NpgsqlDbType.Numeric (box costCash) |> ignore + addParameter cashCommand "fund_id" NpgsqlDbType.Uuid (box fundId) |> ignore + cashCommand.ExecuteNonQuery() = 1 + else + true - use positionCommand = - commandWithTransaction - connection - (Some transaction) - """ - INSERT INTO bond_positions - (fund_id, instrument_code, bond_name, quantity, cost_cash, last_traded_at) - VALUES (@fund_id, @code, @name, @quantity, @cost_cash, @last_traded_at) - ON CONFLICT (fund_id, instrument_code) DO UPDATE - SET quantity = bond_positions.quantity + EXCLUDED.quantity, - cost_cash = bond_positions.cost_cash + EXCLUDED.cost_cash, - bond_name = COALESCE(EXCLUDED.bond_name, bond_positions.bond_name), - last_traded_at = EXCLUDED.last_traded_at - """ + if not debited then + transaction.Rollback() - addParameter positionCommand "fund_id" NpgsqlDbType.Uuid (box fundId) |> ignore - addParameter positionCommand "code" NpgsqlDbType.Text (box normalized.InstrumentCode) |> ignore + BondTradeWriteResult.BondTradeInsufficientFunds( + sprintf + "available cash is not enough for a bond purchase of %s" + (costCash.ToString("0.00", CultureInfo.InvariantCulture)) + ) + else + let trade: BondTradeRecord = + { + Id = Guid.NewGuid() + FundId = fundId + InstrumentCode = normalized.InstrumentCode + BondName = normalized.BondName + Quantity = normalized.Quantity + Price = normalized.Price + CleanPrice = normalized.CleanPrice + AccruedInterest = normalized.AccruedInterest + ParValue = normalized.ParValue + SettlementDate = normalized.SettlementDate + CouponRate = normalized.CouponRate + ValueDate = normalized.ValueDate + MaturityDate = normalized.MaturityDate + TradeDate = normalized.TradeDate |> Option.defaultValue normalized.SettlementDate + CostCash = costCash + IsSynthetic = isSynthetic + ExecutedAt = executedAt + } - let nameParameter = - match normalized.BondName with - | Some name -> box name - | None -> box DBNull.Value + insertBondTrade connection (Some transaction) trade + insertBondTradeIdempotency connection (Some transaction) idempotencyKey fingerprint trade.Id fundId - addParameter positionCommand "name" NpgsqlDbType.Text nameParameter |> ignore - addParameter positionCommand "quantity" NpgsqlDbType.Numeric (box normalized.Quantity) |> ignore - addParameter positionCommand "cost_cash" NpgsqlDbType.Numeric (box costCash) |> ignore - addParameter positionCommand "last_traded_at" NpgsqlDbType.TimestampTz (box executedAt) |> ignore - positionCommand.ExecuteNonQuery() |> ignore + if defaultArg debitAvailableCash true then + insertCashLedgerEvent + connection + (Some transaction) + fundId + CashLedger.BondBuy + (DateOnly.FromDateTime executedAt.UtcDateTime) + -costCash + "bond_buy" + (trade.Id.ToString("D")) + None - transaction.Commit() - BondTradeWriteResult.BondTradeCreated trade + use positionCommand = + commandWithTransaction + connection + (Some transaction) + """ + INSERT INTO bond_positions + (fund_id, instrument_code, bond_name, quantity, cost_cash, last_traded_at) + VALUES (@fund_id, @code, @name, @quantity, @cost_cash, @last_traded_at) + ON CONFLICT (fund_id, instrument_code) DO UPDATE + SET quantity = bond_positions.quantity + EXCLUDED.quantity, + cost_cash = bond_positions.cost_cash + EXCLUDED.cost_cash, + bond_name = COALESCE(EXCLUDED.bond_name, bond_positions.bond_name), + last_traded_at = EXCLUDED.last_traded_at + """ + + addParameter positionCommand "fund_id" NpgsqlDbType.Uuid (box fundId) |> ignore + addParameter positionCommand "code" NpgsqlDbType.Text (box normalized.InstrumentCode) |> ignore + + let nameParameter = + match normalized.BondName with + | Some name -> box name + | None -> box DBNull.Value + + addParameter positionCommand "name" NpgsqlDbType.Text nameParameter |> ignore + addParameter positionCommand "quantity" NpgsqlDbType.Numeric (box normalized.Quantity) |> ignore + addParameter positionCommand "cost_cash" NpgsqlDbType.Numeric (box costCash) |> ignore + addParameter positionCommand "last_traded_at" NpgsqlDbType.TimestampTz (box executedAt) |> ignore + positionCommand.ExecuteNonQuery() |> ignore + + transaction.Commit() + BondTradeWriteResult.BondTradeCreated trade with error -> try transaction.Rollback() diff --git a/tests/FundLab.Api.Tests/CashReconciliationTests.fs b/tests/FundLab.Api.Tests/CashReconciliationTests.fs index d8dc7bb..6fb761e 100644 --- a/tests/FundLab.Api.Tests/CashReconciliationTests.fs +++ b/tests/FundLab.Api.Tests/CashReconciliationTests.fs @@ -83,6 +83,18 @@ type CashReconciliationTests(fixture: PostgresFixture) = | CapitalDepositWriteResult.CapitalDepositReplayed _ -> () | other -> failwithf "unexpected deposit result: %A" other + let buyStock fundId code quantity price idempotencyKey = + match + repository().CreateStockTrade( + idempotencyKey, + fundId, + { InstrumentCode = code; StockName = Some "贵州茅台"; Quantity = quantity; Price = price } + ) + with + | StockTradeWriteResult.StockTradeCreated _ + | StockTradeWriteResult.StockTradeReplayed _ -> () + | other -> failwithf "unexpected stock trade result: %A" other + let buyStockDebit fundId code quantity price idempotencyKey = match repository().CreateStockTrade( @@ -96,6 +108,19 @@ type CashReconciliationTests(fixture: PostgresFixture) = | StockTradeWriteResult.StockTradeReplayed _ -> () | other -> failwithf "unexpected stock trade result: %A" other + let buyStockNoDebit fundId code quantity price idempotencyKey = + match + repository().CreateStockTrade( + idempotencyKey, + fundId, + { InstrumentCode = code; StockName = Some "贵州茅台"; Quantity = quantity; Price = price }, + debitAvailableCash = false + ) + with + | StockTradeWriteResult.StockTradeCreated _ + | StockTradeWriteResult.StockTradeReplayed _ -> () + | other -> failwithf "unexpected stock trade result: %A" other + let sellStock fundId code quantity price idempotencyKey = match repository().CreateStockSell( @@ -108,7 +133,7 @@ type CashReconciliationTests(fixture: PostgresFixture) = | StockSellWriteResult.StockSellReplayed _ -> () | other -> failwithf "unexpected stock sell result: %A" other - let buyBond fundId code quantity price = + let buyBondWith fundId code quantity price debitAvailableCash = match repository().CreateBondTrade( fixture.Key(sprintf "cash-recon-bond-%s" (Guid.NewGuid().ToString("N"))), @@ -126,12 +151,19 @@ type CashReconciliationTests(fixture: PostgresFixture) = ValueDate = None MaturityDate = None TradeDate = Some today - } + }, + debitAvailableCash = debitAvailableCash ) with | BondTradeWriteResult.BondTradeCreated _ -> () | other -> failwithf "unexpected bond trade result: %A" other + let buyBond fundId code quantity price = + buyBondWith fundId code quantity price true + + let buyBondNoDebit fundId code quantity price = + buyBondWith fundId code quantity price false + let recordBondCoupon fundId code amount idempotencyKey = match repository().RecordBondCashflow( @@ -229,14 +261,15 @@ type CashReconciliationTests(fixture: PostgresFixture) = let status, body = reconciliation fundId Assert.Equal(200, status) Assert.Contains("\"openingCash\":\"10000.00\"", body) - Assert.Contains("\"netInflow\":\"3450.00\"", body) - Assert.Contains("\"closingCash\":\"13450.00\"", body) - Assert.Contains("\"ledgerBalance\":\"13450.00\"", body) + Assert.Contains("\"netInflow\":\"2450.00\"", body) + Assert.Contains("\"closingCash\":\"12450.00\"", body) + Assert.Contains("\"ledgerBalance\":\"12450.00\"", body) Assert.Contains("\"difference\":\"0.00\"", body) Assert.Equal("5000.00", (sourceEntry body "capital_deposit").GetProperty("netAmount").GetString()) Assert.Equal("-1000.00", (sourceEntry body "stock_buy").GetProperty("netAmount").GetString()) Assert.Equal("400.00", (sourceEntry body "stock_sell").GetProperty("netAmount").GetString()) + Assert.Equal("-1000.00", (sourceEntry body "bond_buy").GetProperty("netAmount").GetString()) Assert.Equal("50.00", (sourceEntry body "bond_coupon").GetProperty("netAmount").GetString()) Assert.Equal("-1000.00", (sourceEntry body "subscription").GetProperty("netAmount").GetString()) @@ -274,7 +307,7 @@ type CashReconciliationTests(fixture: PostgresFixture) = deposit fundId 5000.00m (fixture.Key "cash-backfill-deposit") buyStockDebit fundId "600519" 100m 10.00m (fixture.Key "cash-backfill-buy") sellStock fundId "600519" 40m 10.00m (fixture.Key "cash-backfill-sell") - buyBond fundId "110075" 10m 100.00m + buyBondNoDebit fundId "110075" 10m 100.00m recordBondCoupon fundId "110075" 50.00m (fixture.Key "cash-backfill-coupon") subscribe fundId (seedInstrument ()) 1000.00m (fixture.Key "cash-backfill-subscribe") @@ -295,3 +328,102 @@ type CashReconciliationTests(fixture: PostgresFixture) = Assert.Equal(expected, ledgerEventCount fundId) Assert.Equal(0.00m, (repository().GetCashReconciliation fundId |> Result.defaultWith failwith).Difference) + + [<Fact>] + member _.``manual stock buy debits available cash and records a stock_buy event``() = + let fundId = createFund 10000.00m + buyStock fundId "600519" 100m 10.00m (fixture.Key "cash-manual-stock-buy") + + let status, body = reconciliation fundId + Assert.Equal(200, status) + Assert.Contains("\"openingCash\":\"10000.00\"", body) + Assert.Contains("\"closingCash\":\"9000.00\"", body) + Assert.Contains("\"ledgerBalance\":\"9000.00\"", body) + Assert.Contains("\"difference\":\"0.00\"", body) + Assert.Equal("-1000.00", (sourceEntry body "stock_buy").GetProperty("netAmount").GetString()) + Assert.Equal(1, eventCount body "stock_buy") + + [<Fact>] + member _.``manual bond buy debits available cash and records a bond_buy event``() = + let fundId = createFund 10000.00m + buyBond fundId "110075" 10m 100.00m + + let status, body = reconciliation fundId + Assert.Equal(200, status) + Assert.Contains("\"openingCash\":\"10000.00\"", body) + Assert.Contains("\"closingCash\":\"9000.00\"", body) + Assert.Contains("\"ledgerBalance\":\"9000.00\"", body) + Assert.Contains("\"difference\":\"0.00\"", body) + Assert.Equal("-1000.00", (sourceEntry body "bond_buy").GetProperty("netAmount").GetString()) + Assert.Equal(1, eventCount body "bond_buy") + + [<Fact>] + member _.``non-debiting buys leave cash and ledger untouched``() = + let fundId = createFund 10000.00m + buyStockNoDebit fundId "600519" 100m 10.00m (fixture.Key "cash-nodebit-stock") + buyBondNoDebit fundId "110075" 10m 100.00m + + let status, body = reconciliation fundId + Assert.Equal(200, status) + Assert.Contains("\"closingCash\":\"10000.00\"", body) + Assert.Contains("\"ledgerBalance\":\"10000.00\"", body) + Assert.Contains("\"difference\":\"0.00\"", body) + Assert.Equal(0, eventCount body "stock_buy") + Assert.Equal(0, eventCount body "bond_buy") + + [<Fact>] + member _.``manual stock buy beyond available cash is rejected and changes nothing``() = + let fundId = createFund 1000.00m + + let result = + repository().CreateStockTrade( + fixture.Key "cash-manual-stock-overspend", + fundId, + { InstrumentCode = "600519"; StockName = Some "贵州茅台"; Quantity = 100m; Price = 20.00m } + ) + + Assert.Equal( + StockTradeWriteResult.StockTradeInsufficientFunds "available cash is not enough for a stock purchase of 2000.00", + result + ) + + let status, body = reconciliation fundId + Assert.Equal(200, status) + Assert.Contains("\"closingCash\":\"1000.00\"", body) + Assert.Contains("\"difference\":\"0.00\"", body) + Assert.Equal(0, eventCount body "stock_buy") + + [<Fact>] + member _.``manual bond buy beyond available cash is rejected and changes nothing``() = + let fundId = createFund 1000.00m + + let result = + repository().CreateBondTrade( + fixture.Key "cash-manual-bond-overspend", + fundId, + { + InstrumentCode = "110075" + BondName = Some "南航转债" + Quantity = 10m + Price = 200.00m + CleanPrice = 200.00m + AccruedInterest = 0m + ParValue = 100m + SettlementDate = today + CouponRate = None + ValueDate = None + MaturityDate = None + TradeDate = Some today + } + ) + + Assert.Equal( + BondTradeWriteResult.BondTradeInsufficientFunds "available cash is not enough for a bond purchase of 2000.00", + result + ) + + let status, body = reconciliation fundId + Assert.Equal(200, status) + Assert.Contains("\"closingCash\":\"1000.00\"", body) + Assert.Contains("\"difference\":\"0.00\"", body) + Assert.Equal(0, eventCount body "bond_buy") diff --git a/tests/FundLab.Api.Tests/FundValuationTests.fs b/tests/FundLab.Api.Tests/FundValuationTests.fs index 409bfba..eacd5cd 100644 --- a/tests/FundLab.Api.Tests/FundValuationTests.fs +++ b/tests/FundLab.Api.Tests/FundValuationTests.fs @@ -107,7 +107,8 @@ type FundValuationTests(fixture: PostgresFixture) = repository().CreateStockTrade( key, fundId, - { InstrumentCode = code; StockName = Some "贵州茅台"; Quantity = quantity; Price = price } + { InstrumentCode = code; StockName = Some "贵州茅台"; Quantity = quantity; Price = price }, + debitAvailableCash = false ) with | StockTradeWriteResult.StockTradeCreated _ -> () @@ -133,7 +134,8 @@ type FundValuationTests(fixture: PostgresFixture) = ValueDate = None MaturityDate = None TradeDate = Some(DateOnly(2026, 9, 21)) - } + }, + debitAvailableCash = false ) with | BondTradeWriteResult.BondTradeCreated _ -> () @@ -159,7 +161,8 @@ type FundValuationTests(fixture: PostgresFixture) = ValueDate = Some(DateOnly(2020, 10, 15)) MaturityDate = Some(DateOnly(2026, 10, 15)) TradeDate = Some(DateOnly(2026, 9, 21)) - } + }, + debitAvailableCash = false ) with | BondTradeWriteResult.BondTradeCreated _ -> () diff --git a/tests/FundLab.Api.Tests/RebalanceStockTests.fs b/tests/FundLab.Api.Tests/RebalanceStockTests.fs index 3aed5ae..caaa7c9 100644 --- a/tests/FundLab.Api.Tests/RebalanceStockTests.fs +++ b/tests/FundLab.Api.Tests/RebalanceStockTests.fs @@ -69,7 +69,8 @@ type RebalanceStockTests(fixture: PostgresFixture) = repository().CreateStockTrade( key, fundId, - { InstrumentCode = code; StockName = Some "贵州茅台"; Quantity = quantity; Price = price } + { InstrumentCode = code; StockName = Some "贵州茅台"; Quantity = quantity; Price = price }, + debitAvailableCash = false ) with | StockTradeWriteResult.StockTradeCreated _ -> () diff --git a/tests/FundLab.Api.Tests/StockSellTests.fs b/tests/FundLab.Api.Tests/StockSellTests.fs index 972293a..fb17a50 100644 --- a/tests/FundLab.Api.Tests/StockSellTests.fs +++ b/tests/FundLab.Api.Tests/StockSellTests.fs @@ -129,7 +129,7 @@ type StockSellTests(fixture: PostgresFixture) = let status, fundBody = fund sellQuotes fundId Assert.Equal(200, status) - Assert.Contains("\"availableCash\":\"1480.00\"", fundBody) + Assert.Contains("\"availableCash\":\"480.00\"", fundBody) [<Fact>] member _.``selling all units removes the position row and credits the proceeds``() = @@ -150,7 +150,7 @@ type StockSellTests(fixture: PostgresFixture) = Assert.Contains("\"positions\":[]", positionsBody) let _, fundBody = fund sellQuotes fundId - Assert.Contains("\"availableCash\":\"2200.00\"", fundBody) + Assert.Contains("\"availableCash\":\"1200.00\"", fundBody) [<Fact>] member _.``overselling is rejected with a reason and leaves cash and position unchanged``() = @@ -171,7 +171,7 @@ type StockSellTests(fixture: PostgresFixture) = Assert.Contains("\"quantity\":\"100.00000000\"", positionsBody) let _, fundBody = fund quotes fundId - Assert.Contains("\"availableCash\":\"1000.00\"", fundBody) + Assert.Contains("\"availableCash\":\"0.00\"", fundBody) [<Fact>] member _.``valuation after a partial sale prices only the remaining units``() = @@ -215,7 +215,7 @@ type StockSellTests(fixture: PostgresFixture) = Assert.DoesNotContain("\"quantity\":\"40.00000000\"", positionsBody) let _, fundBody = fund quotes fundId - Assert.Contains("\"availableCash\":\"1300.00\"", fundBody) + Assert.Contains("\"availableCash\":\"300.00\"", fundBody) [<Fact>] member _.``a persisted snapshot price wins over the live quote when selling``() = diff --git a/tests/FundLab.Api.Tests/StockTradeTests.fs b/tests/FundLab.Api.Tests/StockTradeTests.fs index f8a8492..0b52faf 100644 --- a/tests/FundLab.Api.Tests/StockTradeTests.fs +++ b/tests/FundLab.Api.Tests/StockTradeTests.fs @@ -46,7 +46,7 @@ type StockTradeTests(fixture: PostgresFixture) = let command = { Name = "股票闭环测试 FOF" - InitialCash = 100000.00m + InitialCash = 200000.00m InitialUnitNav = 1.00000000m IsSynthetic = true } |
