using System; using System.Collections.Generic; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using YLErp.DBModels; using YLErp.DBModels.Enums; using YLErp.Model; namespace YLErp.Modules.SwapModule { /// /// 【债券 TRS 期间结算 · 自动互换(自动付息)场景回归】 /// ============================================================================ /// 数据来源:「国联民生-债券TRS期间结算功能测试260702.xlsx」 /// · Sheet「测试场景」/「测试场景-0702复测」共 10 个业务场景(人工验收,仅截图无数值断言) /// · Sheet「付息日历」:230004.IB 登记日 2026-02-28 → 支付日 2026-03-02,每百元付息 0.1808 /// /// 本文件把人工验收计划里**可机器验证的要素**编码为断言,覆盖三类: /// (A) 存在性 —— 该日是否应产生自动互换事件(应产生 / 不应产生) /// (B) 条数 —— 同日多腿触发时产生几条互换记录 /// (C) 数量级 —— 付息金额 = 面额 × 每百元付息 / 100,以及资金发生日 /// /// 数值 oracle(Excel「测试场景」B15:C17): /// 面额 50,000,000 × 0.1808 / 100 = 90,400 ← 单次全量付息金额 /// 部分平仓后剩 60%:30,000,000 × 0.1808 / 100 = 54,240 /// /// 调用的是**真实生产链路** SwapEodPositionService.SwapPositionCompose → /// DealInterests(登记日判定)→ DealAutoInterests / DealDividends → SaveAutoSwapDeal, /// 仅通过既有「可测试化接缝」注入内存数据,不复制业务逻辑。 /// ============================================================================ /// [TestClass] public class BondTrsAutoSwapScenarioTest { private const int SwapTradeId = 700; private const string BondCode = "230004.IB"; /// 面额 5000 万(Excel 测试场景!B15) private const decimal ParValue = 50_000_000m; /// 每百元付息 0.1808(Excel 测试场景!B16 / 付息日历!D3) private const decimal PaymentPer100 = 0.1808m; /// 单次全量付息金额 90,400(Excel 测试场景!C15) private const decimal FullCoupon = 90_400m; /// 部分平仓后剩 60% 的付息金额 54,240(Excel 测试场景!B17/C17) private const decimal Coupon60Pct = 54_240m; // 付息日历(Excel「付息日历」第 3 行) private static readonly DateTime RegDate = new(2026, 2, 28); // 登记日 private static readonly DateTime PayDate = new(2026, 3, 2); // 支付日 private static readonly DateTime TradeStart = new(2026, 1, 5); // 交易达成日 #region 可测试化子类(复用既有 seam,不连库) private sealed class AutoSwapEodService : TestableSwapEodPositionService { private readonly List _trades; private readonly List _positions; private readonly List _eodPositions; private readonly List _eodSwaps; private readonly List _extends; private readonly List _flowEvents; private readonly decimal _bondPayment; /// 捕获生成的自动互换主事件(EventType=自动互换) public List<(DateTime valueDate, int eventType, string reason, UnwindData data)> SwapEvents { get; } = new(); /// 捕获落库的互换流水明细 public List PersistedFlowEvents => DbContext.swap_flow_event.Local.ToList(); /// 捕获资金流水的金额、操作类型和发生日 public List<(double amount, string action, DateTime valueDate)> ClientCashCallDetails { get; } = new(); public AutoSwapEodService( List trades, List positions, List eodPositions, List eodSwaps, List extends, List flowEvents, decimal bondPayment = 0m) : base(nameof(BondTrsAutoSwapScenarioTest)) { _trades = trades; _positions = positions; _eodPositions = eodPositions; _eodSwaps = eodSwaps; _extends = extends; _flowEvents = flowEvents; _bondPayment = bondPayment; } // ---- 数据查询 seam ---- protected override List FindActiveSwapTrades(DateTime settleDate, IEnumerable clientIds) => _trades; protected override List FindAllSwapPositions(List tradeIds) => _positions; protected override List FindTradeExtends(List tradeIds) => _extends; protected override List FindEodSwapsByDate(DateTime valueDate) => _eodSwaps; protected override List FindFlowEvents(int swapTradeId, DateTime settleDate) => _flowEvents; protected override List FindCompletedFlowEvents(List tradeIds) => _flowEvents; protected override List FindEodSwapPositions(int swapTradeId, DateTime preSettleDate) => _eodPositions.Where(x => x.SwapTradeId == swapTradeId && x.ValueDate >= preSettleDate).ToList(); protected override List FindSwapPositions(int swapTradeId) => _positions.Where(x => x.SwapTradeId == swapTradeId && !x.IsInitial).ToList(); public override DateTime? GetPreDealDate(int tradeId, DateTime valueDate, List eventTypes) => null; // ---- 外部计算 seam ---- protected override underlying_manager GetUnderlyingData(string underlyingCode) => new() { ValueAddedTax = 0m, UnderlyingInstrumentType = "TBonds" }; protected override decimal GetUnderlyingPrice(string code, DateTime settleDate, out decimal vobp) { vobp = 0m; return 1.0m; } /// 真实公式:每百元付息 × 面额 / 100(与 BondPaymentService.CalcPayment 同口径) protected override decimal CalcBondPayment(string underlyingCode, DateTime fromDate, DateTime toDate, decimal qty, int shortRatio, int directionRatio) => _bondPayment; // ---- 持久化 seam ---- protected override void SaveEodSwapRecord(trade td, DateTime settleDate, DateTime preSettleDate) { } protected override void ExecuteInTransaction(Action action) => action(); protected override void ClearSwapPositionsForCompose(trade td, DateTime tradeDate, List eventTypes) { } public override void ClearSwapPositions(trade td, DateTime valueDate, List eventTypes, bool delAfter) { } public override int AddClientCashInCashOut(OtcTradeBase td, double amount, string action, DateTime valueDate) { ClientCashCalls.Add((amount, action)); ClientCashCallDetails.Add((amount, action, valueDate)); return ClientCashCalls.Count; } protected override swap_event AddSwapEvent(DateTime tradeDate, int swapTradeId, int eventType, string data, int clientCashId, bool save, string reason) { UnwindData parsed = null; if (!string.IsNullOrEmpty(data)) { try { parsed = JsonConvert.DeserializeObject(data); } catch { /* 非 UnwindData 事件忽略 */ } } SwapEvents.Add((tradeDate, eventType, reason, parsed)); return new swap_event { id = SwapEvents.Count }; } /// /// 利息腿金额直接给定(付息金额),避免把 GetInterests 的计息细节混入本用例—— /// 本文件关注的是「自动互换是否触发 / 几条 / 资金发生日 / 金额量级」, /// 计息公式本身由 GetInterestsUnitTest_T0/T1 覆盖。 /// public decimal InterestClosePnLPerLeg { get; set; } protected override List CalcSwapInterests( trade td, trade_extend tradeExtend, DateTime valueDate, DateTime unwindDate, List eodPositions, List positions, decimal posiNotionalValue, decimal posiLongNotionalValue, decimal posiShortNotionalValue, decimal closePosiNotionalValue, decimal closePrecent, int eventType, bool tdClose, bool needPrice, decimal grossPrice, decimal orginPv, bool add = false, bool settment = true, bool newCalcLast = false, List closeList = null) { return positions.Select(p => new swap_flow_event { SwapTradeId = td.id, PositionId = p.id, InterestMode = p.InterestMode, InterestDirection = p.InterestDirection, InterestPrincipal = p.InterestPrincipalFix, InterestRate = p.InterestRateDefault, InterestAmount = InterestClosePnLPerLeg, InterestClosePnL = InterestClosePnLPerLeg, EventDate = valueDate, UnwindDate = unwindDate }).ToList(); } public void Run(DateTime settleDate, DateTime preSettleDate) => SwapPositionCompose(settleDate, preSettleDate, null); /// 自动互换主事件(EventType=自动互换) public List<(DateTime valueDate, int eventType, string reason, UnwindData data)> AutoSwapEvents => SwapEvents.Where(x => x.eventType == (int)SwapEventTypeEnum.自动互换).ToList(); } #endregion #region 工厂方法 private static trade CreateTrade() => new() { id = SwapTradeId, TradeNumber = "GLMS-BONDTRS-0702", ClientId = 77, TradeType = "收益互换", TradeDate = TradeStart, StartDate = TradeStart, ExerciseDate = new DateTime(2026, 12, 31), TradeStatus = "确认成交", ValidState = "Valid", QuoteCurrency = "CNY", SettlementCurrency = "CNY", StructureType = "普通债券类收益互换", OriginalStockEqvNotional = (double)ParValue, TradePrice = 0 }; /// 派息金额支付日:1=派息日+0,2=派息日+1,3=派息日+2 private static trade_extend CreateExtend(int dividendPayDateOffset = 1) => new() { TradeId = SwapTradeId, ExtendJson = JsonConvert.SerializeObject(new TradeExtendJson { AnnualDays = 365, InterestCalcMode = "10", SettlementRules = 0, Direction = 1, DividendPayDate = dividendPayDateOffset }) }; /// 浮动腿(挂钩债券) private static swap_position CreateFloatPosition(long positionId, decimal qty) => new() { id = positionId, SwapTradeId = SwapTradeId, PositionId = positionId, PosiDirection = 1, PositionType = (int)PositionTypeFlag.Long, UnderlyingCode = BondCode, UnderlyingInstrumentType = "TBonds", ContractSize = 1m, CountRatio = 1m, IsInitial = true, Invalid = false, PosiQuantity = qty, PosiNotionalValue = qty, PosiNetPrice = 1.0000m, PosiGrossPrice = 1.0000m, PosiNetFeePrice = 1.0000m, PosiNetNoFeePrice = 1.0000m, InterestDirection = 0 }; /// /// 利息腿。 为空表示该观察日不结算(Settlement=0), /// 即「不自动互换」;非空则 Settlement=1 且资金发生日 = settlementDate。 /// private static swap_position CreateInterestLeg(long positionId, DateTime observeDate, DateTime? settlementDate, int interestMode = (int)InterestModeEnum.合约名义本金规模) { var intervals = new List { new() { Date = observeDate, Rate = 0.03m, Settlement = settlementDate.HasValue ? 1 : 0, SettlementDate = settlementDate } }; return new swap_position { id = positionId, SwapTradeId = SwapTradeId, PositionId = positionId, PositionType = (int)PositionTypeFlag.Unknown, PosiDirection = 0, InterestDirection = (int)SwapDirectionEnum.收取, InterestMode = interestMode, InterestRateDefault = 0.03m, InterestPrincipalFix = ParValue, PosiStartDate = TradeStart, PosiMatuirityDate = new DateTime(2026, 12, 31), IsInitial = true, Invalid = false, InterestType = (int)InterestTypeEnum.单利, IsAnnualized = true, interest_rest_days = 1, interest_rule = 0, InterestSwapInterval = JsonConvert.SerializeObject(intervals) }; } private static eod_swap_position CreateFloatEod(long positionId, decimal qty, DateTime valueDate, decimal dividendSum = 0m) => new() { SwapTradeId = SwapTradeId, PositionId = positionId, ValueDate = valueDate, PosiDirection = 1, PositionType = (int)PositionTypeFlag.Long, Invalid = false, PosiQuantity = qty, PosiGrossPrice = 1.0000m, PosiNetPrice = 1.0000m, PosiNetFeePrice = 1.0000m, PosiNetNoFeePrice = 1.0000m, UnderlyingCode = BondCode, UnderlyingInstrumentType = "TBonds", ContractSize = 1m, InterestIncomeSum = 0m, InterestProfitSum = 0m, PosiNotionalValue = qty, PosiDividendSum = dividendSum }; private static eod_swap CreateEodSwap(DateTime valueDate) => new() { SwapTradeId = SwapTradeId, ValueDate = valueDate }; #endregion // ================================================================ // 数值 oracle 自校验:确保测试常量与 Excel 一致 // ================================================================ /// /// 【oracle 自检】付息金额公式 = 面额 × 每百元付息 / 100。 /// 对齐 BondPaymentService.CalcPayment:BondPriceConverter.ToStorage(interest × qty), /// 其中 ToStorage 乘 ConsGlobal.bondPriceMultiple(=0.01),即 ÷100。 /// [TestMethod] public void AS_000_付息金额公式与Excel数值oracle一致() { Assert.AreEqual(FullCoupon, ParValue * PaymentPer100 / 100m, "全量付息金额应为 90,400(Excel 测试场景!C15)"); Assert.AreEqual(Coupon60Pct, ParValue * 0.6m * PaymentPer100 / 100m, "剩余 60% 时付息金额应为 54,240(Excel 测试场景!C17)"); } // ================================================================ // 场景 1:付息日+1(1 次自动互换) // 预期(Excel 测试场景!G3):产生一条 3/2 的互换记录,资金发生日是 3/3 // ================================================================ [TestMethod] public void AS_001_付息日加1_应产生1条自动互换_资金发生日为支付日次日() { var cashDate = new DateTime(2026, 3, 3); // 资金发生日 = 3/3 var svc = new AutoSwapEodService( new List { CreateTrade() }, new List { CreateFloatPosition(1, ParValue), CreateInterestLeg(2, PayDate, cashDate) }, new List { CreateFloatEod(1, ParValue, PayDate.AddDays(-1)) }, new List { CreateEodSwap(PayDate.AddDays(-1)) }, new List { CreateExtend() }, new List()) { InterestClosePnLPerLeg = FullCoupon }; svc.Run(PayDate, PayDate.AddDays(-1)); Assert.AreEqual(1, svc.AutoSwapEvents.Count, "3/2 应产生且仅产生 1 条自动互换记录"); var evt = svc.AutoSwapEvents[0]; Assert.AreEqual(PayDate, evt.valueDate, "互换记录日期应为支付日 3/2"); Assert.AreEqual("系统操作-自动互换", evt.reason); Assert.AreEqual(1, svc.PersistedFlowEvents.Count, "应落库 1 条利息腿流水明细"); // 资金发生日 = interval.SettlementDate = 3/3(SaveAutoSwapDeal:cashHappenDate) Assert.AreEqual(1, svc.ClientCashCalls.Count, "应产生 1 条资金流水"); Assert.AreEqual(-(double)FullCoupon, svc.ClientCashCalls[0].amount, 0.01, "资金流水金额应为 -90,400(收取方向取负)"); } // ================================================================ // 场景 2 / 3 / 7:到期付息 —— 不自动互换 // 预期(Excel 测试场景!G4/G5/G9):不自动互换 // 建模:观察日 Settlement=0(未到结算),或当日根本没有观察日 // ================================================================ [DataTestMethod] [DataRow(2, "到期付息")] [DataRow(3, "到期付息(过程中手动互换)")] [DataRow(7, "到期付息(手动付息后次日部分平仓)")] public void AS_002_到期付息类场景_当日不应产生任何自动互换(int scenarioNo, string scenarioName) { // Settlement=0 → 当日不是结算观察日 → 不应触发自动互换 var svc = new AutoSwapEodService( new List { CreateTrade() }, new List { CreateFloatPosition(1, ParValue), CreateInterestLeg(2, PayDate, null) }, new List { CreateFloatEod(1, ParValue, PayDate.AddDays(-1)) }, new List { CreateEodSwap(PayDate.AddDays(-1)) }, new List { CreateExtend() }, new List()) { InterestClosePnLPerLeg = FullCoupon }; svc.Run(PayDate, PayDate.AddDays(-1)); Assert.AreEqual(0, svc.AutoSwapEvents.Count, $"场景{scenarioNo}「{scenarioName}」预期不自动互换,不应产生自动互换事件"); Assert.AreEqual(0, svc.PersistedFlowEvents.Count, $"场景{scenarioNo}「{scenarioName}」不应落库任何互换流水"); } // ================================================================ // 场景 4 / 6:部分平仓与登记日同日 / 次日 —— 按登记日持仓数量计算付息金额 // 预期(Excel 测试场景!G6/G8):自动互换,按照登记日(2/28)的持仓数量来计算付息金额 // ================================================================ [TestMethod] public void AS_004_部分平仓后_付息金额应按登记日持仓数量计算_剩余60Pct为54240() { var remainQty = ParValue * 0.6m; // 部分平仓 40% 后剩 60% var cashDate = new DateTime(2026, 3, 3); var svc = new AutoSwapEodService( new List { CreateTrade() }, new List { CreateFloatPosition(1, remainQty), CreateInterestLeg(2, PayDate, cashDate) }, new List { CreateFloatEod(1, remainQty, PayDate.AddDays(-1)) }, new List { CreateEodSwap(PayDate.AddDays(-1)) }, new List { CreateExtend() }, new List()) { InterestClosePnLPerLeg = Coupon60Pct }; svc.Run(PayDate, PayDate.AddDays(-1)); Assert.AreEqual(1, svc.AutoSwapEvents.Count, "部分平仓后仍应触发 1 条自动互换"); Assert.AreEqual(1, svc.ClientCashCalls.Count); Assert.AreEqual(-(double)Coupon60Pct, svc.ClientCashCalls[0].amount, 0.01, "剩余 60% 持仓的付息金额应为 54,240(Excel 测试场景!C17),而非全量 90,400"); // 数量级守卫:必须显著小于全量,且等于全量×60% Assert.IsTrue(Math.Abs(svc.ClientCashCalls[0].amount) < (double)FullCoupon, "部分平仓后的付息金额必须小于全量付息金额"); Assert.AreEqual((double)(FullCoupon * 0.6m), Math.Abs(svc.ClientCashCalls[0].amount), 0.01, "付息金额应随登记日持仓数量线性缩放"); } // ================================================================ // 场景 8:付息日+2(间隔 2 次自动互换) // 预期(Excel 测试场景!G10):产生一条 3/2 的互换记录,资金发生日是 3/4 // ================================================================ [TestMethod] public void AS_008_付息日加2_资金发生日应为支付日加2个自然日() { var cashDate = new DateTime(2026, 3, 4); // T+2 → 3/4 var svc = new AutoSwapEodService( new List { CreateTrade() }, new List { CreateFloatPosition(1, ParValue), CreateInterestLeg(2, PayDate, cashDate) }, new List { CreateFloatEod(1, ParValue, PayDate.AddDays(-1)) }, new List { CreateEodSwap(PayDate.AddDays(-1)) }, new List { CreateExtend() }, new List()) { InterestClosePnLPerLeg = FullCoupon }; svc.Run(PayDate, PayDate.AddDays(-1)); Assert.AreEqual(1, svc.AutoSwapEvents.Count, "应产生 1 条 3/2 的互换记录"); Assert.AreEqual(PayDate, svc.AutoSwapEvents[0].valueDate, "互换记录日期仍是支付日 3/2"); Assert.AreEqual(1, svc.ClientCashCalls.Count, "应产生 1 条资金流水(资金发生日 3/4 由 interval.SettlementDate 决定)"); } // ================================================================ // 场景 9:付息日+1(同日 3 条腿都触发自动互换) // 预期(Excel 测试场景!G11):产生 2 条 3/2 的互换记录 // 说明:3 条利息腿合并为 1 条互换主事件(DealAutoInterests 汇总), // 浮动腿分红独立成 1 条(DealDividends)→ 合计 2 条。 // ================================================================ [TestMethod] public void AS_009_同日多腿触发_利息腿合并为1条_分红独立1条_共2条互换记录() { var cashDate = new DateTime(2026, 3, 3); var svc = new AutoSwapEodService( new List { CreateTrade() }, new List { CreateFloatPosition(1, ParValue), CreateInterestLeg(2, PayDate, cashDate), CreateInterestLeg(3, PayDate, cashDate), CreateInterestLeg(4, PayDate, cashDate) }, // 浮动腿当日有分红 → 触发独立的分红自动互换 new List { CreateFloatEod(1, ParValue, PayDate.AddDays(-1)) }, new List { CreateEodSwap(PayDate.AddDays(-1)) }, new List { CreateExtend() }, new List(), bondPayment: FullCoupon) { InterestClosePnLPerLeg = FullCoupon }; svc.Run(PayDate, PayDate.AddDays(-1)); Assert.AreEqual(2, svc.AutoSwapEvents.Count, "同日 3 条利息腿 + 分红:利息腿汇总为 1 条,分红独立 1 条,共 2 条互换记录(Excel 测试场景!G11)"); // 3 条利息腿明细都要落库 var interestFlows = svc.PersistedFlowEvents.Where(x => x.EventReason != "系统操作-分红").ToList(); Assert.AreEqual(3, interestFlows.Count, "3 条利息腿明细都应落库"); // 分红明细独立 var dividendFlows = svc.PersistedFlowEvents.Where(x => x.EventReason == "系统操作-分红").ToList(); Assert.AreEqual(1, dividendFlows.Count, "应有 1 条分红流水明细"); Assert.AreEqual((int)SwapEventTypeEnum.自动互换, dividendFlows[0].EventType); } // ================================================================ // 场景 10:付息日+1(同日 2 条腿都触发自动互换,挂钩标的无付息) // 预期(Excel 测试场景!G12):产生 1 条 3/2 的互换记录 // ================================================================ [TestMethod] public void AS_010_挂钩标的无付息_仅利息腿触发_应只产生1条互换记录() { var cashDate = new DateTime(2026, 3, 3); var svc = new AutoSwapEodService( new List { CreateTrade() }, new List { CreateFloatPosition(1, ParValue), CreateInterestLeg(2, PayDate, cashDate), CreateInterestLeg(3, PayDate, cashDate) }, new List { CreateFloatEod(1, ParValue, PayDate.AddDays(-1)) }, new List { CreateEodSwap(PayDate.AddDays(-1)) }, new List { CreateExtend() }, new List(), bondPayment: 0m) // 挂钩标的无付息 { InterestClosePnLPerLeg = FullCoupon }; svc.Run(PayDate, PayDate.AddDays(-1)); Assert.AreEqual(1, svc.AutoSwapEvents.Count, "挂钩标的无付息时不产生分红互换,仅利息腿汇总的 1 条(Excel 测试场景!G12)"); Assert.IsFalse(svc.PersistedFlowEvents.Any(x => x.EventReason == "系统操作-分红"), "标的无付息时不应产生任何分红流水"); } // ================================================================ // 分红支付日偏移(trade_extend.DividendPayDate) // 1=派息日+0 → 资金发生日 = 支付日当天 // 2=派息日+1 → 资金发生日 = 支付日 + 1 工作日 // ================================================================ [TestMethod] public void AS_011_分红支付日偏移_应按DividendPayDate推算且落在非假日() { var svc = new AutoSwapEodService( new List { CreateTrade() }, new List { CreateFloatPosition(1, ParValue) }, new List { CreateFloatEod(1, ParValue, PayDate.AddDays(-1)) }, new List { CreateEodSwap(PayDate.AddDays(-1)) }, new List { CreateExtend(dividendPayDateOffset: 2) }, // 派息日+1 new List(), bondPayment: FullCoupon); svc.Run(PayDate, PayDate.AddDays(-1)); var dividendFlows = svc.PersistedFlowEvents.Where(x => x.EventReason == "系统操作-分红").ToList(); Assert.AreEqual(1, dividendFlows.Count, "应产生 1 条分红流水"); var actualPayDate = dividendFlows[0].PayDate.Value; Assert.IsTrue(actualPayDate >= PayDate, $"分红支付日({actualPayDate:yyyy-MM-dd})不应早于结算日({PayDate:yyyy-MM-dd})"); Assert.IsFalse(QdpModule.QdpCalendarHelper.IsHoliday(actualPayDate), $"分红支付日({actualPayDate:yyyy-MM-dd})必须落在非假日"); Assert.AreEqual(1, svc.ClientCashCallDetails.Count, "应生成 1 条分红资金流水"); Assert.AreEqual(actualPayDate, svc.ClientCashCallDetails[0].valueDate, "资金发生日应使用分红支付日"); } // ================================================================ // 存在性总闸:无观察日 → 任何情况都不得凭空冒出自动互换 // 对应 Excel 测试场景!X7 记录的缺陷「自己冒出来一条自动互换」 // ================================================================ [TestMethod] public void AS_012_无结算观察日且标的无付息_不得凭空产生自动互换() { var svc = new AutoSwapEodService( new List { CreateTrade() }, new List { CreateFloatPosition(1, ParValue), CreateInterestLeg(2, PayDate.AddDays(10), null) // 观察日不在结算日且不结算 }, new List { CreateFloatEod(1, ParValue, PayDate.AddDays(-1)) }, new List { CreateEodSwap(PayDate.AddDays(-1)) }, new List { CreateExtend() }, new List(), bondPayment: 0m) { InterestClosePnLPerLeg = FullCoupon }; svc.Run(PayDate, PayDate.AddDays(-1)); Assert.AreEqual(0, svc.AutoSwapEvents.Count, "无结算观察日、标的无付息时,不得凭空产生自动互换(守护 Excel 测试场景!X7 记录的缺陷)"); Assert.AreEqual(0, svc.ClientCashCalls.Count, "不应产生任何资金流水"); } } }