using System; using System.Collections.Generic; using System.Linq; using YLErp.DBModels; using YLErp.DBModels.Enums; using YLErp.Model; namespace YLErp.Modules.SwapModule { /// /// SwapTradeAutoService 的可测试子类。 /// 覆盖所有外部依赖方法,用内存数据替代数据库和静态调用。 /// public class TestableSwapTradeAutoService : SwapTradeAutoService { private readonly Func _nextBusinessDay; private readonly Func _nextBusinessDayBefore; private readonly Func _bondCalc; // 注入的数据 private readonly List _trades; private readonly List _tradeExtends; private readonly List _positions; private readonly Dictionary _clients; private readonly Dictionary _assets; private readonly Dictionary _underlyings; private readonly Func _etradingRuleFactory; private readonly Func, int, string, SwapFloatRate> _floatRateFactory; private readonly Func _newSwapTradeFactory; private readonly Action _autoSwapUnwind; private readonly List _flowEvents; private readonly List _validTrades; /// 捕获 PersistMerge 写入的所有 merge 记录 public List PersistedMerges { get; } = new List(); /// 捕获 CreateNewSwapTrade 创建的所有交易 public List CreatedTrades { get; } = new List(); /// 捕获 AutoSwapUnwind 调用 public List<(int tradeId, decimal qty, decimal fee)> UnwindCalls { get; } = new List<(int, decimal, decimal)>(); /// SaveChanges 调用次数 public int SaveChangesCount { get; private set; } public TestableSwapTradeAutoService( OptUserInfo optUser, Func nextBusinessDay = null, Func nextBusinessDayBefore = null, Func bondCalc = null, List trades = null, List tradeExtends = null, List positions = null, Dictionary clients = null, Dictionary assets = null, Dictionary underlyings = null, Func etradingRuleFactory = null, Func, int, string, SwapFloatRate> floatRateFactory = null, Func newSwapTradeFactory = null, Action autoSwapUnwind = null, List flowEvents = null, List validTrades = null ) : base(optUser) { _nextBusinessDay = nextBusinessDay ?? (d => d.AddDays(1)); _nextBusinessDayBefore = nextBusinessDayBefore ?? (d => d.AddDays(-1)); _bondCalc = bondCalc ?? ((code, price, date) => null); _trades = trades ?? new List(); _tradeExtends = tradeExtends ?? new List(); _positions = positions ?? new List(); _clients = clients ?? new Dictionary(); _assets = assets ?? new Dictionary(); _underlyings = underlyings ?? new Dictionary(); _etradingRuleFactory = etradingRuleFactory; _floatRateFactory = floatRateFactory; _newSwapTradeFactory = newSwapTradeFactory; _autoSwapUnwind = autoSwapUnwind; _flowEvents = flowEvents ?? new List(); _validTrades = validTrades ?? new List(); } #region Override 可测试化方法 protected override DateTime GetNextBusinessDay(DateTime date) => _nextBusinessDay(date); protected override DateTime GetNextBusinessDayBefore(DateTime date) => _nextBusinessDayBefore(date); protected override CalBondResult CalculateBondYtm(string underlyingCode, decimal avgPrice, DateTime settleDate) => _bondCalc(underlyingCode, avgPrice, settleDate); protected override void PersistMerge(swap_flow_merge merge) => PersistedMerges.Add(merge); protected override void SetModelOpt(DBModelBaseV2 model) { } protected override List FindActiveSwapTrades(DateTime valueDate) => _trades; protected override List FindTradeExtends(IEnumerable tradeIds) => _tradeExtends; protected override List FindActivePositions(IEnumerable tradeIds, int posiDirection) => _positions.Where(x => x.PosiDirection == posiDirection).ToList(); protected override List FindActivePositionsAll(IEnumerable tradeIds) => _positions; protected override IQueryable QueryFloatRates(DateTime valueDate, DateTime matuirityDate) => new List().AsQueryable(); protected override Client FindClient(int clientId) => _clients.TryGetValue(clientId, out var c) ? c : null; protected override AssetUnit FindAssetUnit(string assetAccountName) => _assets.TryGetValue(assetAccountName ?? "", out var a) ? a : null; protected override underlying_manager FindUnderlying(string underlyingCode) => _underlyings.TryGetValue(underlyingCode ?? "", out var u) ? u : null; protected override EtradingRule GetEtradingRule(BoundSideEnum boundSide, string clientNumber) => _etradingRuleFactory?.Invoke((int)boundSide, clientNumber); protected override SwapFloatRate GetSwapFloatRate(IQueryable query, int clientId, string underlyingCode) => _floatRateFactory?.Invoke(query, clientId, underlyingCode); protected override List FindFlowEventsForCashCheck(swap_flow_merge flowMerge) => _flowEvents; protected override List FindValidTrades(IEnumerable tradeIds) => _validTrades; protected override void SaveChanges() => SaveChangesCount++; protected override trade CreateNewSwapTrade(swap_flow_merge flowMerge, Client client, AssetUnit asset, underlying_manager underlying, SwapFloatRate floatRate, string clearingAgency, bool cashNeedAfter = false) { if (_newSwapTradeFactory != null) { var t = _newSwapTradeFactory(flowMerge, client, asset, underlying, floatRate, clearingAgency, cashNeedAfter); CreatedTrades.Add(t); return t; } var trade = new trade { id = CreatedTrades.Count + 1, TradeNumber = $"TEST-{CreatedTrades.Count + 1}" }; CreatedTrades.Add(trade); return trade; } protected override void AutoSwapUnwind(int tradeId, decimal tradingAmountAvg, decimal tradingAmountFeeAvg, decimal tradingAmountNetFeeAvg, decimal tradingAmountNetAvg, DateTime occurTime, decimal tradingQtyAbs, decimal tradingFeePending) { UnwindCalls.Add((tradeId, tradingQtyAbs, tradingFeePending)); _autoSwapUnwind?.Invoke(tradeId, tradingAmountAvg, tradingAmountFeeAvg, tradingAmountNetFeeAvg, tradingAmountNetAvg, occurTime, tradingQtyAbs, tradingFeePending); } #endregion /// 公开 SummaryFlow 供测试调用 public List ExecuteSummaryFlow( List swapFlows, DateTime valueDate, bool save = true, Action callback = null) => SummaryFlow(swapFlows, valueDate, save, callback); /// 公开 SummaryFlow 第二个重载 public List ExecuteSummaryFlowDeal( List swapFlows1, DateTime tradeDate, List swapFlows) => SummaryFlow(swapFlows1, tradeDate, swapFlows); /// 公开 MergeRestModeCompose 供测试调用 public void ExecuteMergeRestModeCompose(List mergeList, DateTime valueDate, Action? action = null) => MergeRestModeCompose(mergeList, valueDate, action); /// 公开 MergeAvgModeCompose 供测试调用 public Dictionary> ExecuteMergeAvgModeCompose(List mergeList, DateTime valueDate, Action? action = null) => MergeAvgModeCompose(mergeList, valueDate, action); } }