using System.Data; using YLErp.BLL; using YLErp.Commons; using YLErp.DBModels.Consts; using YLErp.DBModels.Helpers; using YLErp.Model; using YLErp.Modules.DataProviderModule; using YLErp.Modules.UnderlyingModule; using YLErp.QdpModule; namespace YLErp.Modules.TradeModule.DealModule { /// /// 除权除息操作(请使用正常英文单词) /// public class DividendService : YLBaseService { public DividendService(OptUserInfo userInfo) : base(userInfo) { } public DividendService(YLBaseService baseService) : base(baseService) { } /// /// 场内交易除权除息 /// /// 除权日 /// 持仓列表 public List Execute(DateTime settleDate, IEnumerable positions) { var result = new List(); var dict = GetExDividendQuery(settleDate) .ToDictionary(K => K.UnderlyingId, V => V); foreach (var item in positions) { double cost = item.Cost, amount = item.Amount; //averagePrice = item.AveragePrice; var bodPosition = new BodTradePosition(); if (dict.ContainsKey(item.UnderlyingId)) { var averagePrice = Math.Abs(item.Cost / item.Amount); amount = GetPositionAmount(amount, dict[item.UnderlyingId]); averagePrice = GetPrice(averagePrice, dict[item.UnderlyingId]); cost = amount * averagePrice; } bodPosition.ValueDate = settleDate; bodPosition.HedgeUniqueCode = item.HedgeUniqueCode; bodPosition.ExchangeOptionCode = item.ExchangeOptionCode; bodPosition.TradeType = item.TradeType; bodPosition.PositionType = item.PositionType; bodPosition.BookId = item.BookId; bodPosition.UnderlyingId = item.UnderlyingId; bodPosition.UnderlyingCode = item.UnderlyingCode; bodPosition.BuySell = item.BuySell; bodPosition.Cost = cost; bodPosition.Amount = amount; //bodPosition.AveragePrice = averagePrice; bodPosition.Commission = item.Commission; bodPosition.OptId = item.OptId; bodPosition.OptName = item.OptName; bodPosition.OptDate = DateTime.Now; result.Add(bodPosition); } return result; } /// /// 场外交易除权除息 /// /// 除权日 /// 交易列表 /// 是否将结果保存到trade表 /// public List Execute(DateTime settleDate, IEnumerable trades, out List useSaveTrades, out List useSaveUndedrlyings) { useSaveTrades = new List(); useSaveUndedrlyings = new List(); var result = new List(); var dict = GetExDividendQuery(settleDate) .ToDictionary(K => K.UnderlyingId, V => V); var tradeIds = trades.Select(O => O.id); var dividendRatioDict = new DbRecordChangesService(this) .GetValue(ConsInfoChangeType.UserChange, tradeIds, nameof(trade.DividendRatio), settleDate) .ToDictionary(K => K.RecordId, V => { return double.TryParse(V.NewValue, out var temp) ? (double?)temp : null; }); foreach (var t in trades) { var bodTrade = new bod_trade(); if (dict.TryGetValue(t.UnderlyingId, out var dividendInfo)) { var ratio = dividendRatioDict.ContainsKey(t.id) ? dividendRatioDict[t.id] : null; //避免该字段为NULL时amount和notion不一致 //股票的amount和notion应相等 var unwindNotional = t.OriginalNotional - t.Notional; t.TradeOriginalAmount = t.TradeOriginalAmount ?? t.OriginalNotional; t.SpotPrice = GetPrice(t.SpotPrice, dividendInfo, ratio); var annualizeFactor = t.AnnualizeFactor; if (t.TradeType == "凤凰期权") { annualizeFactor = t.trade_autocall.AnnualizeFactor2; } else if (t.TradeType == "雪球期权") { annualizeFactor = t.trade_snowball.AnnualizeFactor2; } t.StockEqvNotionalReal = t.StockEqvNotionalReal == 0 ? TradeHelper.GetStockEqvNotionalReal(t.OriginalStockEqvNotional, t.ParticipationRate, annualizeFactor) : t.StockEqvNotionalReal; t.OriginalNotional = t.StockEqvNotionalReal / t.SpotPrice; t.TradeOriginalAmount = t.OriginalNotional / (t.CountRatio ?? 1); //不管是不是名义本金方式了结,都应该按照比例了结。--时嬴政 //if (t.IsUsePremiumRate.HasValue && t.IsUsePremiumRate.Value) //{ var settlePercent = (t.StockEqvNotional / t.OriginalStockEqvNotional) ?? 0; t.Notional = (t.OriginalNotional * settlePercent) ?? 0; t.TradeAmount = (t.TradeOriginalAmount * settlePercent) ?? 0; //} //else //{ // t.Notional = (t.OriginalNotional - unwindNotional) ?? 0; // t.TradeAmount = (t.TradeOriginalAmount - unwindNotional) ?? 0; //} t.TradeSinglePrice = TradeHelper.GetTradeSinglePriceByTradePrice(t.TradePrice, t.OriginalNotional, t.PrincipalSum(), t.BuySell, t.TradeType, true); if (t.TradeType == "彩虹期权") { t.trade_rainbow_option.SpotPrice1 = GetPrice(t.trade_rainbow_option.SpotPrice1, dividendInfo, ratio); t.trade_rainbow_option.SpotPrice2 = GetPrice(t.trade_rainbow_option.SpotPrice2, dividendInfo, ratio); } if (!t.IsMoneynessOptionData) { t.Strike = GetPrice(t.Strike, dividendInfo, ratio); switch (t.TradeType) { case "亚式期权": //获取fixings的时候,函数内部处理除权除息逻辑; break; case "障碍期权": t.trade_barrier_option.BarrierPrice = GetPrice(t.trade_barrier_option.BarrierPrice, dividendInfo, ratio); t.trade_barrier_option.UpperBarrierPrice = GetPrice(t.trade_barrier_option.UpperBarrierPrice, dividendInfo, ratio); break; case "彩虹期权": t.trade_rainbow_option.Strike = GetPrice(t.trade_rainbow_option.Strike, dividendInfo, ratio); t.trade_rainbow_option.Strike2 = GetPrice(t.trade_rainbow_option.Strike2, dividendInfo, ratio); break; case "凤凰期权": t.trade_autocall.KIBarrier = GetPrice(t.trade_autocall.KIBarrier, dividendInfo, ratio); t.trade_autocall.KOBarrier = GetPrice(t.trade_autocall.KOBarrier, dividendInfo, ratio); t.trade_autocall.CouponBarrier = GetPrice(t.trade_autocall.CouponBarrier, dividendInfo, ratio); t.trade_autocall.SpreadStrike = GetPrice(t.trade_autocall.SpreadStrike, dividendInfo, ratio); t.trade_autocall.SpreadStrike1 = GetPrice(t.trade_autocall.SpreadStrike1, dividendInfo, ratio); var obsInfos = QdpHelper.ParseAutocallCustomizedInfo(t.trade_autocall.KOObservationDates); for (var i = 0; i < obsInfos.Item2.Length; i++) { obsInfos.Item2[i] = GetPrice(obsInfos.Item2[i], dividendInfo, ratio); } t.trade_autocall.KOObservationDates = $"{string.Join(",", obsInfos.Item1.Select(O => O.ToString()))};{string.Join(",", obsInfos.Item2.Select(O => O.OtcFormatUmPrice()))};{string.Join(",", obsInfos.Item3.Select(O => O.OtcFormatFlex(2)))}"; break; case "双鲨期权": t.trade_double_sharkfin_option.BarrierHigh = GetPrice(t.trade_double_sharkfin_option.BarrierHigh, dividendInfo, ratio); t.trade_double_sharkfin_option.BarrierLow = GetPrice(t.trade_double_sharkfin_option.BarrierLow, dividendInfo, ratio); t.trade_double_sharkfin_option.StrikeHigh = GetPrice(t.trade_double_sharkfin_option.StrikeHigh, dividendInfo, ratio); t.trade_double_sharkfin_option.StrikeLow = GetPrice(t.trade_double_sharkfin_option.StrikeLow, dividendInfo, ratio); break; case "区间累积期权": t.trade_rangeaccrual.LowerRange = GetPrice(t.trade_rangeaccrual.LowerRange, dividendInfo, ratio); t.trade_rangeaccrual.UpperRange = GetPrice(t.trade_rangeaccrual.UpperRange, dividendInfo, ratio); break; case "雪球期权": t.trade_snowball.KIBarrier = GetPrice(t.trade_snowball.KIBarrier, dividendInfo, ratio); t.trade_snowball.KOBarrier = GetPrice(t.trade_snowball.KOBarrier, dividendInfo, ratio); t.trade_snowball.SpreadStrikeAtKO = GetPrice(t.trade_snowball.SpreadStrikeAtKO, dividendInfo, ratio); t.trade_snowball.SpreadStrikeAtKO1 = GetPrice(t.trade_snowball.SpreadStrikeAtKO1, dividendInfo, ratio); t.trade_snowball.SpreadStrikeAtMaturity = GetPrice(t.trade_snowball.SpreadStrikeAtMaturity, dividendInfo, ratio); t.trade_snowball.SpreadStrikeAtMaturity1 = GetPrice(t.trade_snowball.SpreadStrikeAtMaturity1, dividendInfo, ratio); obsInfos = QdpHelper.ParseAutocallCustomizedInfo(t.trade_snowball.KOObservationDates); for (var i = 0; i < obsInfos.Item2.Length; i++) { obsInfos.Item2[i] = GetPrice(obsInfos.Item2[i], dividendInfo, ratio); } t.trade_snowball.KOObservationDates = $"{string.Join(",", obsInfos.Item1.Select(O => O.ToString()))};{string.Join(",", obsInfos.Item2.Select(O => O.OtcFormatUmPrice()))};{string.Join(",", obsInfos.Item3.Select(O => O.OtcFormatFlex(2)))}"; break; case "气囊结构": t.trade_airbag.Barrier = GetPrice(t.trade_airbag.Barrier, dividendInfo, ratio); t.trade_airbag.HighStrike = GetPrice(t.trade_airbag.HighStrike, dividendInfo, ratio); break; case "累计期权": t.trade_accumulator_option.KOBarrier = GetPrice(t.trade_accumulator_option.KOBarrier, dividendInfo, ratio); break; default: break; } } if (t.TradeType == "累计期权") { var customObservDates = QdpHelper.ParseObservationDate(t.trade_accumulator_option.KOObservationDates); if (customObservDates == null) { customObservDates = Qdp.Pricing.Base.Implementations.CalendarImpl.Get("chn") .BizDaysBetweenDatesExcluStartDay(t.TradeDate.Value, t.ExerciseDate.Value).ToArray(); } var unwindPercent = t.trade_accumulator_option.AccumuTradeAmount / t.trade_accumulator_option.OriginalAccumuTradeAmount; t.trade_accumulator_option.OriginalAccumuTradeAmount = (t.OriginalNotional ?? 0) / (t.CountRatio ?? 1) / customObservDates.Length; t.trade_accumulator_option.AccumuTradeAmount = t.trade_accumulator_option.OriginalAccumuTradeAmount * unwindPercent; } t.DividendDate = settleDate; useSaveTrades.Add(t); } if (t.TradeType == "收益互换") { var originalNotional = 0d; var notional = 0d; if ((t.trade_swap.PayUnderlyingId ?? 0) > 0) { if (t.pay_trade_swap_details?.Count > 0) { double? ratio = 0d; foreach (var item in t.pay_trade_swap_details) { var um = DataCacheProvider.GetUnderlyingDataSource().GetData(item.UnderlyingCode); if (dict.TryGetValue(um.id, out dividendInfo)) { ratio = dividendRatioDict.ContainsKey(t.id) ? dividendRatioDict[t.id] : null; var unwindNotional = item.OriginalNotional - item.Notional; var OriginalStockEqvNotional = item.SpotPrice * item.OriginalNotional; var StockEqvNotional = item.SpotPrice * item.Notional; item.SpotPrice = GetPrice(item.SpotPrice, dividendInfo, ratio); item.OriginalNotional = OriginalStockEqvNotional / item.SpotPrice; if (t.IsUsePremiumRate.HasValue && t.IsUsePremiumRate.Value) { var settlePercent = (StockEqvNotional / OriginalStockEqvNotional) ?? 0; item.Notional = (item.OriginalNotional * settlePercent) ?? 0; } else { item.Notional = (item.OriginalNotional - unwindNotional) ?? 0; } } } originalNotional = t.pay_trade_swap_details.Sum(O => O.OriginalNotional ?? 0); notional = t.pay_trade_swap_details.Sum(O => O.Notional ?? 0); if (!useSaveTrades.Contains(t)) { t.DividendDate = settleDate; useSaveTrades.Add(t); } } else { originalNotional = t.OriginalNotional ?? 1; notional = t.Notional; var useDividend = false; double? ratio = 0d; var payUm = DataCacheProvider.GetUnderlyingDataSource().GetData(t.trade_swap.PayUnderlyingId.Value); if (payUm.IsBasket() && payUm.SubData != null) { var umchanges = new DbRecordChangesService(this).GetValue(ConsInfoChangeType.Dividend, payUm.id, nameof(underlying_manager.SubData), settleDate); if (umchanges != null) { payUm.SubData = umchanges.FieldValue; } else { useSaveUndedrlyings.Add(payUm); } var data = JsonHelper.Deserialize>(payUm.SubData); var originWeight = data.Sum(O => O.weight); foreach (var item in data) { var tempUm = DataCacheProvider.GetUnderlyingDataSource().GetData(item.code); if (dict.TryGetValue(tempUm?.id ?? 0, out dividendInfo)) { ratio = (dividendRatioDict.ContainsKey(t.id) ? dividendRatioDict[t.id] : null) ?? GetRatio(dividendInfo); item.weight *= ratio.Value; useDividend = true; } } payUm.SubData = data.ToJson(); useSaveUndedrlyings.Add(payUm); var newWeight = data.Sum(O => O.weight); ratio = newWeight / originWeight; } else { if (dict.TryGetValue(t.trade_swap.PayUnderlyingId ?? 0, out dividendInfo)) { ratio = dividendRatioDict.ContainsKey(t.id) ? dividendRatioDict[t.id] : null; useDividend = true; } } if (useDividend) { t.trade_swap.PaySpotPrice = GetPrice(t.trade_swap.PaySpotPrice, dividendInfo, ratio); t.StockEqvNotionalReal = t.StockEqvNotionalReal == 0 ? TradeHelper.GetStockEqvNotionalReal(t.OriginalStockEqvNotional, t.ParticipationRate, t.AnnualizeFactor) : t.StockEqvNotionalReal; t.trade_swap.PayNotional = t.StockEqvNotionalReal / t.trade_swap.PaySpotPrice; //设置交易数量 var underlyingManager = DataCacheProvider.GetUnderlyingDataSource().GetData(t.trade_swap.PayUnderlyingCode); var CountRatio = underlyingManager?.CountRatio ?? 1; t.trade_swap.PayTradeAmount = t.trade_swap.PayNotional / CountRatio; t.DividendDate = settleDate; originalNotional = NumberHelper.Normalize(t.OriginalStockEqvNotional / t.trade_swap.PaySpotPrice); notional = NumberHelper.Normalize(t.StockEqvNotional / t.trade_swap.PaySpotPrice); if (!useSaveTrades.Contains(t)) { t.DividendDate = settleDate; useSaveTrades.Add(t); } } } } if ((t.trade_swap.GetUnderlyingId ?? 0) > 0) { if (t.get_trade_swap_details?.Count > 0) { double? ratio = 0d; foreach (var item in t.get_trade_swap_details) { var um = DataCacheProvider.GetUnderlyingDataSource().GetData(item.UnderlyingCode); if (dict.TryGetValue(um.id, out dividendInfo)) { ratio = dividendRatioDict.ContainsKey(t.id) ? dividendRatioDict[t.id] : null; var unwindNotional = item.OriginalNotional - item.Notional; var OriginalStockEqvNotional = item.SpotPrice * item.OriginalNotional; var StockEqvNotional = item.SpotPrice * item.Notional; item.SpotPrice = GetPrice(item.SpotPrice, dividendInfo, ratio); item.OriginalNotional = OriginalStockEqvNotional / item.SpotPrice; if (t.IsUsePremiumRate.HasValue && t.IsUsePremiumRate.Value) { var settlePercent = (StockEqvNotional / OriginalStockEqvNotional) ?? 0; item.Notional = (item.OriginalNotional * settlePercent) ?? 0; } else { item.Notional = (item.OriginalNotional - unwindNotional) ?? 0; } } } originalNotional = t.get_trade_swap_details.Sum(O => O.OriginalNotional ?? 0); notional = t.get_trade_swap_details.Sum(O => O.Notional ?? 0); if (!useSaveTrades.Contains(t)) { t.DividendDate = settleDate; useSaveTrades.Add(t); } } else { originalNotional = t.OriginalNotional ?? 1; notional = t.Notional; double? ratio = 0d; var useDividend = false; var getUm = DataCacheProvider.GetUnderlyingDataSource().GetData(t.trade_swap.GetUnderlyingId.Value); if (getUm.IsBasket() && getUm.SubData != null) { var umchanges = new DbRecordChangesService(this).GetValue(ConsInfoChangeType.Dividend, getUm.id, nameof(underlying_manager.SubData), settleDate); if (umchanges != null) { getUm.SubData = umchanges.FieldValue; } else { useSaveUndedrlyings.Add(getUm); } var data = JsonHelper.Deserialize>(getUm.SubData); var originWeight = data.Sum(O => O.weight); foreach (var item in data) { var tempUm = DataCacheProvider.GetUnderlyingDataSource().GetData(item.code); if (dict.TryGetValue(tempUm?.id ?? 0, out dividendInfo)) { ratio = (dividendRatioDict.ContainsKey(t.id) ? dividendRatioDict[t.id] : null) ?? GetRatio(dividendInfo); item.weight *= ratio.Value; useDividend = true; } } getUm.SubData = data.ToJson(); var newWeight = data.Sum(O => O.weight); ratio = newWeight / originWeight; } else { if (dict.TryGetValue(t.trade_swap.GetUnderlyingId ?? 0, out dividendInfo)) { ratio = dividendRatioDict.ContainsKey(t.id) ? dividendRatioDict[t.id] : null; useDividend = true; } } if (useDividend) { t.trade_swap.GetSpotPrice = GetPrice(t.trade_swap.GetSpotPrice, dividendInfo, ratio); t.StockEqvNotionalReal = t.StockEqvNotionalReal == 0 ? TradeHelper.GetStockEqvNotionalReal(t.OriginalStockEqvNotional, t.ParticipationRate, t.AnnualizeFactor) : t.StockEqvNotionalReal; t.trade_swap.GetNotional = t.StockEqvNotionalReal / t.trade_swap.GetSpotPrice; //设置交易数量 var underlyingManager = DataCacheProvider.GetUnderlyingDataSource().GetData(t.trade_swap.GetUnderlyingCode); var CountRatio = underlyingManager?.CountRatio ?? 1; t.trade_swap.GetTradeAmount = t.trade_swap.GetNotional / CountRatio; t.DividendDate = settleDate; originalNotional = NumberHelper.Normalize(t.OriginalStockEqvNotional / t.trade_swap.GetSpotPrice); notional = NumberHelper.Normalize(t.StockEqvNotional / t.trade_swap.GetSpotPrice); if (!useSaveTrades.Contains(t)) { t.DividendDate = settleDate; useSaveTrades.Add(t); } } } } t.OriginalNotional = originalNotional; t.TradeOriginalAmount = originalNotional; t.Notional = notional; } bodTrade.AssetId = t.AssetId; bodTrade.ClientId = t.ClientId; bodTrade.OptDate = DateTime.Now; bodTrade.ParentTradeId = t.ParentTradeId; bodTrade.TradeId = t.id; bodTrade.TradeNumber = t.TradeNumber; bodTrade.TradeStatus = t.TradeStatus; bodTrade.TradeType = t.TradeType; bodTrade.UnderlyingCode = t.UnderlyingCode; bodTrade.UnderlyingId = t.UnderlyingId; bodTrade.ValueDate = settleDate; bodTrade.TradeJson = TradeHelper2.Serialize(t, settleDate); result.Add(bodTrade); } return result; } /// /// /// public static List GetDividendChanges(trade trade, trade NewValue) { TradeChanges compar(double? originValue, double? newValue, params string[] fieldName) { TradeChanges tc = null; if ((originValue == null && newValue != null) || (originValue != null && newValue == null) || originValue != newValue || (ConsGlobal.IgnoreTSV((originValue ?? 0) - (newValue ?? 0)) != 0)) { tc = new TradeChanges { ChangeType = ConsInfoChangeType.Dividend, RecordId = trade.id, FieldName = string.Join(".", fieldName), FieldValue = originValue?.ToString() ?? "", NewValue = newValue?.ToString() ?? "", OptDate = NewValue.DividendDate, Update = DateTime.Now }; } return tc; } TradeChanges comparStr(string originValue, string newValue, params string[] fieldName) { TradeChanges tc = null; if ((originValue == null && newValue != null) || (originValue != null && newValue == null) || originValue != newValue) { tc = new TradeChanges { ChangeType = ConsInfoChangeType.Dividend, RecordId = trade.id, FieldName = string.Join(".", fieldName), FieldValue = originValue ?? "", NewValue = newValue ?? "", OptDate = NewValue.DividendDate, Update = DateTime.Now }; } return tc; } var list = new List { compar(trade.SpotPrice, NewValue.SpotPrice, nameof(trade.SpotPrice)), compar(trade.OriginalNotional, NewValue.OriginalNotional, nameof(trade.OriginalNotional)), compar(trade.TradeOriginalAmount ?? trade.OriginalNotional, NewValue.TradeOriginalAmount, nameof(trade.TradeOriginalAmount)), compar(trade.Notional, NewValue.Notional, nameof(trade.Notional)), compar(trade.TradeAmount, NewValue.TradeAmount, nameof(trade.TradeAmount)), compar(trade.TradeSinglePrice, NewValue.TradeSinglePrice, nameof(trade.TradeSinglePrice)), compar(trade.Strike, NewValue.Strike, nameof(trade.Strike)) }; switch (trade.TradeType) { case "障碍期权": list.Add(compar(trade.trade_barrier_option.BarrierPrice, NewValue.trade_barrier_option.BarrierPrice, nameof(trade.trade_barrier_option), nameof(trade.trade_barrier_option.BarrierPrice))); list.Add(compar(trade.trade_barrier_option.UpperBarrierPrice, NewValue.trade_barrier_option.UpperBarrierPrice, nameof(trade.trade_barrier_option), nameof(trade.trade_barrier_option.UpperBarrierPrice))); break; case "彩虹期权": list.Add(compar(trade.trade_rainbow_option.SpotPrice1, NewValue.trade_rainbow_option.SpotPrice1, nameof(trade.trade_rainbow_option), nameof(trade.trade_rainbow_option.SpotPrice1))); list.Add(compar(trade.trade_rainbow_option.SpotPrice2, NewValue.trade_rainbow_option.SpotPrice2, nameof(trade.trade_rainbow_option), nameof(trade.trade_rainbow_option.SpotPrice2))); list.Add(compar(trade.trade_rainbow_option.Strike, NewValue.trade_rainbow_option.Strike, nameof(trade.trade_rainbow_option), nameof(trade.trade_rainbow_option.Strike))); list.Add(compar(trade.trade_rainbow_option.Strike2, NewValue.trade_rainbow_option.Strike2, nameof(trade.trade_rainbow_option), nameof(trade.trade_rainbow_option.Strike2))); break; case "凤凰期权": list.Add(compar(trade.trade_autocall.KIBarrier, NewValue.trade_autocall.KIBarrier, nameof(trade.trade_autocall), nameof(trade.trade_autocall.KIBarrier))); list.Add(compar(trade.trade_autocall.KOBarrier, NewValue.trade_autocall.KOBarrier, nameof(trade.trade_autocall), nameof(trade.trade_autocall.KOBarrier))); list.Add(compar(trade.trade_autocall.CouponBarrier, NewValue.trade_autocall.CouponBarrier, nameof(trade.trade_autocall), nameof(trade.trade_autocall.CouponBarrier))); list.Add(compar(trade.trade_autocall.SpreadStrike, NewValue.trade_autocall.SpreadStrike, nameof(trade.trade_autocall), nameof(trade.trade_autocall.SpreadStrike))); list.Add(compar(trade.trade_autocall.SpreadStrike1, NewValue.trade_autocall.SpreadStrike, nameof(trade.trade_autocall), nameof(trade.trade_autocall.SpreadStrike1))); list.Add(comparStr(trade.trade_autocall.KOObservationDates, NewValue.trade_autocall.KOObservationDates, nameof(trade.trade_autocall), nameof(trade.trade_autocall.KOObservationDates))); break; case "双鲨期权": list.Add(compar(trade.trade_double_sharkfin_option.BarrierHigh, NewValue.trade_double_sharkfin_option.BarrierHigh, nameof(trade.trade_double_sharkfin_option), nameof(trade.trade_double_sharkfin_option.BarrierHigh))); list.Add(compar(trade.trade_double_sharkfin_option.BarrierLow, NewValue.trade_double_sharkfin_option.BarrierLow, nameof(trade.trade_double_sharkfin_option), nameof(trade.trade_double_sharkfin_option.BarrierLow))); list.Add(compar(trade.trade_double_sharkfin_option.StrikeHigh, NewValue.trade_double_sharkfin_option.StrikeHigh, nameof(trade.trade_double_sharkfin_option), nameof(trade.trade_double_sharkfin_option.StrikeHigh))); list.Add(compar(trade.trade_double_sharkfin_option.StrikeLow, NewValue.trade_double_sharkfin_option.StrikeLow, nameof(trade.trade_double_sharkfin_option), nameof(trade.trade_double_sharkfin_option.StrikeLow))); break; case "区间累积期权": list.Add(compar(trade.trade_rangeaccrual.LowerRange, NewValue.trade_rangeaccrual.LowerRange, nameof(trade.trade_rangeaccrual), nameof(trade.trade_rangeaccrual.LowerRange))); list.Add(compar(trade.trade_rangeaccrual.UpperRange, NewValue.trade_rangeaccrual.UpperRange, nameof(trade.trade_rangeaccrual), nameof(trade.trade_rangeaccrual.UpperRange))); break; case "雪球期权": list.Add(compar(trade.trade_snowball.KIBarrier, NewValue.trade_snowball.KIBarrier, nameof(trade.trade_snowball), nameof(trade.trade_snowball.KIBarrier))); list.Add(compar(trade.trade_snowball.KOBarrier, NewValue.trade_snowball.KOBarrier, nameof(trade.trade_snowball), nameof(trade.trade_snowball.KOBarrier))); list.Add(compar(trade.trade_snowball.SpreadStrikeAtKO, NewValue.trade_snowball.SpreadStrikeAtKO, nameof(trade.trade_snowball), nameof(trade.trade_snowball.SpreadStrikeAtKO))); list.Add(compar(trade.trade_snowball.SpreadStrikeAtKO1, NewValue.trade_snowball.SpreadStrikeAtKO1, nameof(trade.trade_snowball), nameof(trade.trade_snowball.SpreadStrikeAtKO1))); list.Add(compar(trade.trade_snowball.SpreadStrikeAtMaturity, NewValue.trade_snowball.SpreadStrikeAtMaturity, nameof(trade.trade_snowball), nameof(trade.trade_snowball.SpreadStrikeAtMaturity))); list.Add(compar(trade.trade_snowball.SpreadStrikeAtMaturity1, NewValue.trade_snowball.SpreadStrikeAtMaturity1, nameof(trade.trade_snowball), nameof(trade.trade_snowball.SpreadStrikeAtMaturity1))); list.Add(comparStr(trade.trade_snowball.KOObservationDates, NewValue.trade_snowball.KOObservationDates, nameof(trade.trade_snowball), nameof(trade.trade_snowball.KOObservationDates))); break; case "气囊结构": list.Add(compar(trade.trade_airbag.Barrier, NewValue.trade_airbag.Barrier, nameof(trade.trade_airbag), nameof(trade.trade_airbag.Barrier))); list.Add(compar(trade.trade_airbag.HighStrike, NewValue.trade_airbag.HighStrike, nameof(trade.trade_airbag), nameof(trade.trade_airbag.HighStrike))); break; case "收益互换": if (trade.trade_swap != null) { list.Add(compar(trade.trade_swap.PaySpotPrice, NewValue.trade_swap.PaySpotPrice, nameof(trade.trade_swap), nameof(trade.trade_swap.PaySpotPrice))); list.Add(compar(trade.trade_swap.PayNotional, NewValue.trade_swap.PayNotional, nameof(trade.trade_swap), nameof(trade.trade_swap.PayNotional))); list.Add(compar(trade.trade_swap.PayTradeAmount, NewValue.trade_swap.PayTradeAmount, nameof(trade.trade_swap), nameof(trade.trade_swap.PayTradeAmount))); if (trade.pay_trade_swap_details != null) { foreach (var item in trade.pay_trade_swap_details) { if (!ConsGlobal.IsValid(item.ValidState)) { continue; } var newDetail = NewValue.pay_trade_swap_details.Find(O => O.id == item.id); list.Add(compar(item.SpotPrice, newDetail.SpotPrice, $"{nameof(trade.pay_trade_swap_details)}[{item.id}]", nameof(trade_swap_detail.SpotPrice))); list.Add(compar(item.OriginalNotional, newDetail.OriginalNotional, $"{nameof(trade.pay_trade_swap_details)}[{item.id}]", nameof(trade_swap_detail.OriginalNotional))); list.Add(compar(item.Notional, newDetail.Notional, $"{nameof(trade.pay_trade_swap_details)}[{item.id}]", nameof(trade_swap_detail.Notional))); } } list.Add(compar(trade.trade_swap.GetSpotPrice, NewValue.trade_swap.GetSpotPrice, nameof(trade.trade_swap), nameof(trade.trade_swap.GetSpotPrice))); list.Add(compar(trade.trade_swap.GetNotional, NewValue.trade_swap.GetNotional, nameof(trade.trade_swap), nameof(trade.trade_swap.GetNotional))); list.Add(compar(trade.trade_swap.GetTradeAmount, NewValue.trade_swap.GetTradeAmount, nameof(trade.trade_swap), nameof(trade.trade_swap.GetTradeAmount))); if (trade.get_trade_swap_details != null) { foreach (var item in trade.get_trade_swap_details) { if (!ConsGlobal.IsValid(item.ValidState)) { continue; } var newDetail = NewValue.get_trade_swap_details.Find(O => O.id == item.id); list.Add(compar(item.SpotPrice, newDetail.SpotPrice, $"{nameof(trade.get_trade_swap_details)}[{item.id}]", nameof(trade_swap_detail.SpotPrice))); list.Add(compar(item.OriginalNotional, newDetail.OriginalNotional, $"{nameof(trade.get_trade_swap_details)}[{item.id}]", nameof(trade_swap_detail.OriginalNotional))); list.Add(compar(item.Notional, newDetail.Notional, $"{nameof(trade.get_trade_swap_details)}[{item.id}]", nameof(trade_swap_detail.Notional))); } } } break; case "累计期权": list.Add(compar(trade.trade_accumulator_option.KOBarrier, NewValue.trade_accumulator_option.AccumuTradeAmount, nameof(trade.trade_accumulator_option), nameof(trade.trade_accumulator_option.KOBarrier))); list.Add(compar(trade.trade_accumulator_option.AccumuTradeAmount, NewValue.trade_accumulator_option.AccumuTradeAmount, nameof(trade.trade_accumulator_option), nameof(trade.trade_accumulator_option.AccumuTradeAmount))); list.Add(compar(trade.trade_accumulator_option.OriginalAccumuTradeAmount, NewValue.trade_accumulator_option.OriginalAccumuTradeAmount, nameof(trade.trade_accumulator_option), nameof(trade.trade_accumulator_option.OriginalAccumuTradeAmount))); break; default: break; } list.RemoveAll(O => O == null); return list; } /// /// /// public static void SetDividendTradeData(trade trade, trade NewValue) { trade.SpotPrice = NewValue.SpotPrice; trade.OriginalNotional = NewValue.OriginalNotional; trade.OriginalStockEqvNotional = NewValue.OriginalStockEqvNotional; trade.StockEqvNotionalReal = NewValue.StockEqvNotionalReal; trade.StockEqvNotional = NewValue.StockEqvNotional; trade.Notional = NewValue.Notional; trade.TradeSinglePrice = TradeHelper.GetTradeSinglePriceByTradePrice(NewValue.TradePrice, NewValue.OriginalNotional, NewValue.PrincipalSum(), NewValue.BuySell, NewValue.TradeType, true); trade.TradeOriginalAmount = NewValue.TradeOriginalAmount; trade.TradeAmount = NewValue.TradeAmount; trade.Strike = NewValue.Strike; trade.DividendDate = NewValue.DividendDate; switch (trade.TradeType) { case "亚式期权": //获取fixings的时候,函数内部处理除权除息逻辑; break; case "障碍期权": trade.trade_barrier_option.BarrierPrice = NewValue.trade_barrier_option.BarrierPrice; trade.trade_barrier_option.UpperBarrierPrice = NewValue.trade_barrier_option.UpperBarrierPrice; break; case "彩虹期权": trade.trade_rainbow_option.SpotPrice1 = NewValue.trade_rainbow_option.SpotPrice1; trade.trade_rainbow_option.SpotPrice2 = NewValue.trade_rainbow_option.SpotPrice2; trade.trade_rainbow_option.Strike = NewValue.trade_rainbow_option.Strike; trade.trade_rainbow_option.Strike2 = NewValue.trade_rainbow_option.Strike2; break; case "凤凰期权": trade.trade_autocall.KIBarrier = NewValue.trade_autocall.KIBarrier; trade.trade_autocall.KOBarrier = NewValue.trade_autocall.KOBarrier; trade.trade_autocall.CouponBarrier = NewValue.trade_autocall.CouponBarrier; trade.trade_autocall.SpreadStrike = NewValue.trade_autocall.SpreadStrike; trade.trade_autocall.SpreadStrike1 = NewValue.trade_autocall.SpreadStrike1; trade.trade_autocall.KOObservationDates = NewValue.trade_autocall.KOObservationDates; break; case "双鲨期权": trade.trade_double_sharkfin_option.BarrierHigh = NewValue.trade_double_sharkfin_option.BarrierHigh; trade.trade_double_sharkfin_option.BarrierLow = NewValue.trade_double_sharkfin_option.BarrierLow; trade.trade_double_sharkfin_option.StrikeHigh = NewValue.trade_double_sharkfin_option.StrikeHigh; trade.trade_double_sharkfin_option.StrikeLow = NewValue.trade_double_sharkfin_option.StrikeLow; break; case "区间累积期权": trade.trade_rangeaccrual.LowerRange = NewValue.trade_rangeaccrual.LowerRange; trade.trade_rangeaccrual.UpperRange = NewValue.trade_rangeaccrual.UpperRange; break; case "雪球期权": trade.trade_snowball.KIBarrier = NewValue.trade_snowball.KIBarrier; trade.trade_snowball.KOBarrier = NewValue.trade_snowball.KOBarrier; trade.trade_snowball.SpreadStrikeAtKO = NewValue.trade_snowball.SpreadStrikeAtKO; trade.trade_snowball.SpreadStrikeAtKO1 = NewValue.trade_snowball.SpreadStrikeAtKO1; trade.trade_snowball.SpreadStrikeAtMaturity = NewValue.trade_snowball.SpreadStrikeAtMaturity; trade.trade_snowball.SpreadStrikeAtMaturity1 = NewValue.trade_snowball.SpreadStrikeAtMaturity1; trade.trade_snowball.KOObservationDates = NewValue.trade_snowball.KOObservationDates; break; case "气囊结构": trade.trade_airbag.Barrier = NewValue.trade_airbag.Barrier; trade.trade_airbag.HighStrike = NewValue.trade_airbag.HighStrike; break; case "收益互换": if (trade.trade_swap != null) { trade.trade_swap.PaySpotPrice = NewValue.trade_swap.PaySpotPrice; trade.trade_swap.PayNotional = NewValue.trade_swap.PayNotional; trade.trade_swap.PayTradeAmount = NewValue.trade_swap.PayTradeAmount; if (trade.pay_trade_swap_details != null) { foreach (var item in trade.pay_trade_swap_details) { if (!ConsGlobal.IsValid(item.ValidState)) { continue; } var newDetail = NewValue.pay_trade_swap_details.Find(O => O.id == item.id); item.SpotPrice = newDetail.SpotPrice; item.OriginalNotional = newDetail.OriginalNotional; item.Notional = newDetail.Notional; } } trade.trade_swap.GetSpotPrice = NewValue.trade_swap.GetSpotPrice; trade.trade_swap.GetNotional = NewValue.trade_swap.GetNotional; trade.trade_swap.GetTradeAmount = NewValue.trade_swap.GetTradeAmount; if (trade.get_trade_swap_details != null) { foreach (var item in trade.get_trade_swap_details) { if (!ConsGlobal.IsValid(item.ValidState)) { continue; } var newDetail = NewValue.get_trade_swap_details.Find(O => O.id == item.id); item.SpotPrice = newDetail.SpotPrice; item.OriginalNotional = newDetail.OriginalNotional; item.Notional = newDetail.Notional; } } } break; case "累计期权": trade.trade_accumulator_option.KOBarrier = NewValue.trade_accumulator_option.KOBarrier; trade.trade_accumulator_option.AccumuTradeAmount = NewValue.trade_accumulator_option.AccumuTradeAmount; trade.trade_accumulator_option.OriginalAccumuTradeAmount = NewValue.trade_accumulator_option.OriginalAccumuTradeAmount; break; default: break; } } /// /// 获取除权价 /// /// 除权日收盘价 /// 除权除息配置 /// 除权除息系数 /// public double? GetPrice(double? closePrice, ex_dividend_info info, double? overrideDividendRatio = null) { if (!closePrice.HasValue) { return closePrice; } return GetPrice(closePrice.Value, info, overrideDividendRatio); } /// /// 获取除权价 /// /// 除权日收盘价 /// 除权除息配置 /// public double GetPrice(double price, ex_dividend_info info, double? overrideDividendRatio = null) { if (price == 0) { return 0; } var decimalRatio = overrideDividendRatio.HasValue ? (decimal)overrideDividendRatio.Value : GetRatioDecimal(info); if (decimalRatio == 0) { return 0; } // 价格 / 系数 var result = (decimal)price / decimalRatio; return (double)Math.Round(result, 4, MidpointRounding.AwayFromZero); } /// /// 获取除权除息系数 /// /// /// public double GetRatio(ex_dividend_info info) { return (double)GetRatioDecimal(info); } internal readonly struct CorporateActionFactors { public CorporateActionFactors(decimal priceRatio) { PriceRatio = priceRatio; } public decimal PriceRatio { get; } } /// /// 按 Excel 公式计算公司行为的除权系数。 /// GiveShareAmount 只表示每 10 份的送股数量,Split 表示独立的拆/合股倍数; /// Split 为空按 1 兼容历史记录。TRS Stock/Fund 使用 PriceRatio 同时调整期初价格 /// 和持仓数量,不再维护独立的旧数量系数。 /// /// 现金分红不参与 TRS Stock/Fund 的期初价格公司行为系数;现金权益由既有分红流水单独处理。 /// 本方法只返回系数,不修改持仓,也不判断公司行动是否已经执行;幂等边界由调用方保证。 /// /// internal static CorporateActionFactors CalculateCorporateActionFactors( ex_dividend_info info, decimal closePrice, decimal dividendRate, bool adjustCashDividendPrice = true) { // 价格调整模式除权参考价 = // 登记日收盘价 * 10 - 【每股派息 * 10 * (1-分红税率)】 + 配股数 * 配股价 // --------------------------------------------------------------- // (10 + 送股数 + 配股数) * 拆股倍数 // 场内链路默认继续把现金派息计入除权参考价; // TRS Stock/Fund 现金模式显式关闭该项 :“【】” 号内数据。 var cashPriceAdjustment = adjustCashDividendPrice ? info.GiveCashAmount * (1m - dividendRate) : 0m; // 拆股倍数 var splitFactor = GetSplitFactor(info); // 除权参考价(TRS) : // 登记日收盘价 * 10 + 配股数 * 配股价 // ------------------------------------- // (10 + 送股数 + 配股数) * 拆股倍数 var exDividendPrice = ((closePrice * 10m - cashPriceAdjustment + info.RationedSharesAmount * info.RationedSharesPrice) / (10m + info.GiveShareAmount + info.RationedSharesAmount)) / splitFactor; // 除权系数 = 股权登记日收盘价 / 除权除息参考价 var priceRatio = exDividendPrice == 0 ? 0 : closePrice / exDividendPrice; return new CorporateActionFactors(priceRatio); } private static decimal GetSplitFactor(ex_dividend_info info) { if (info == null) { throw new ArgumentNullException(nameof(info)); } if (info.Split.HasValue && info.Split.Value <= 0m) { throw new ArgumentOutOfRangeException(nameof(info.Split), "拆/合股倍数必须大于 0"); } // Split 为空表示未提供拆合股信息,按 1 兼容历史记录;例如 Split=0.1 时, // 1000 份/100 元调整为 100 份/1000 元。0 或负数无法表达有效份额比例,直接拒绝。 return info.Split ?? 1m; } /// /// 将系统配置中的百分数税率转换为公司行为公式使用的小数税率。 /// 例如配置 13 表示 13%,返回 0.13;送股系数不使用该税率,只有现金分红的税后金额使用。 /// internal decimal GetDividendTaxRateDecimal() { return (decimal)valuedateBLL.SystemDate.DividendRate / 100m; } /** * GiveShareAmount 表示每 10 份送股数量,Split 表示独立拆/合股倍数(空值按 1); * 调整后数量 = 原数量 × (1 + GiveShareAmount / 10) × Split; * 调整后价格 = 原价格 ÷ 上述数量系数(配股只参与非现金价格公式)。 */ private decimal GetRatioDecimal(ex_dividend_info info) { // 除权系数依赖除权登记日收盘价;调用方若在收盘前或使用非标准日期调用, // EodPriceProvider 可能拿不到价格并返回无效系数,不能把该情况默认为 1。 var dividendRate = GetDividendTaxRateDecimal(); var closePrice = new EodPriceProvider(info.ExDividendDate.Value).GetPrice(info.UnderlyingCode, SettlementTypeEnum.ClosePrice); var decimalClosePrice = (decimal)closePrice; return CalculateCorporateActionFactors(info, decimalClosePrice, dividendRate).PriceRatio; } /// /// 获取除权后持仓量 /// /// 持仓数量 /// 除权除息配置 /// public double? GetPositionAmount(double? amount, ex_dividend_info info) { if (!amount.HasValue) { return amount; } return GetPositionAmount(amount.Value, info); } /// /// 获取除权后持仓量 /// /// 持仓数量 /// 除权除息配置 /// public double GetPositionAmount(double amount, ex_dividend_info info) { // 这是旧场内/兼容链路的数量接口;TRS Stock/Fund 不走这里,而是在 // SwapEodPositionService 中按 Excel公式 使用 PriceRatio。旧链路数量只按 // 送股和独立拆合股调整,现金分红和配股不增加持仓数量。 var result = (decimal)amount * (1m + info.GiveShareAmount / 10m) * GetSplitFactor(info); return (double)Math.Round(result, 12, MidpointRounding.AwayFromZero); } public IQueryable GetExDividendQuery(DateTime valueDate) { // 该查询沿用作业的“日期已归一化”约定,要求 valueDate 与存量 ExDividendDate // 同为当天 00:00;自然日业务键的时分秒兼容由保存路径 FindExDividendByBusinessKey 负责。 return DbContext.ex_dividend_info .Where(O => O.ValidStatus && O.ExDividendDate == valueDate); } public IQueryable GetExDividendQuery(DateTime dateStart, DateTime dateEnd) { return DbContext.ex_dividend_info .Where(O => O.ValidStatus && O.ExDividendDate >= dateStart && O.ExDividendDate <= dateEnd); } public IEnumerable GetExDividends(DateTime valueDate, params int[] underlyingIds) { var query = DbContext.ex_dividend_info.Where(O => O.ValidStatus && O.ExDividendDate == valueDate); if (underlyingIds != null && underlyingIds.Any()) { query = query.Where(n => underlyingIds.Contains(n.UnderlyingId)); } return query; } public IQueryable GetExDividendInfos(string underlyingCode) { if (string.IsNullOrWhiteSpace(underlyingCode)) { throw new ArgumentNullException(nameof(underlyingCode)); } return from di in DbContext.ex_dividend_info join un in DbContext.underlying_manager on di.UnderlyingId equals un.id where di.ValidStatus && un.UnderlyingCode == underlyingCode select di; } public void ImportDividendInfos(Stream stream) { var dt = new ExcelHelper().ExcelToDataTable(stream, null, true); if (!dt.Columns.Contains("股票代码") || !dt.Columns.Contains("股权登记日") || !dt.Columns.Contains("真实除权日")) { throw new ServiceException("请使用正确的模板上传"); } var dividendInfos = new List(); for (var i = 0; i < dt.Rows.Count; i++) { var info = new ex_dividend_info { UnderlyingCode = dt.Rows[i]["股票代码"]?.ToString(), ExDividendDate = DateTime.TryParse(getColValueFromTable(dt.Rows[i], "股权登记日"), out var date) ? date : DateTime.MinValue, EffectiveDate = DateTime.TryParse( getColValueFromTable(dt.Rows[i], "真实除权日"), out var effectiveDate) ? effectiveDate : (DateTime?)null, GiveCashAmount = decimal.TryParse(getColValueFromTable(dt.Rows[i], "派息金额"), out var value) ? value : 0, GiveShareAmount = decimal.TryParse(getColValueFromTable(dt.Rows[i], "送股股数"), out value) ? value : 0, Split = decimal.TryParse(getColValueFromTable(dt.Rows[i], "拆/合股倍数"), out var split) ? split : (decimal?)null, RationedSharesAmount = decimal.TryParse(getColValueFromTable(dt.Rows[i], "配股股数"), out value) ? value : 0, RationedSharesPrice = decimal.TryParse(getColValueFromTable(dt.Rows[i], "配股股价"), out value) ? value : 0, OptId = OptUser.UserId, OptName = OptUser.UserName, OptDate = DateTime.Now }; if (string.IsNullOrWhiteSpace(info.UnderlyingCode) && info.ExDividendDate < DateTime.Now.AddYears(10)) { continue; } if (string.IsNullOrWhiteSpace(info.UnderlyingCode)) { throw new ServiceException($"第{i + 1}行股票代码不应为空"); } if (info.ExDividendDate < DateTime.Now.AddYears(-10)) { throw new ServiceException($"第{i + 1}行股权登记日不正确"); } if (!info.EffectiveDate.HasValue) { throw new ServiceException($"第{i + 1}行真实除权日不正确"); } if (info.Split.HasValue && info.Split.Value <= 0m) { throw new ServiceException($"第{i + 1}行拆/合股倍数必须大于0"); } if (info.EffectiveDate.Value.Date < info.ExDividendDate.Value.Date) { throw new ServiceException($"第{i + 1}行真实除权日不应早于股权登记日"); } dividendInfos.Add(info); } if (!AddDividendInfos(dividendInfos, out var errMsg)) { throw new ServiceException(errMsg); } } private string getColValueFromTable(DataRow dr, string colName) { if (dr.Table.Columns.Contains(colName)) { return dr[colName].ToString(); } return ""; } private ex_dividend_info FindExDividendByBusinessKey(int underlyingId, DateTime exDividendDate, int excludedId = 0) { // 业务唯一键按“标的 + 自然日”定义,而不是按完整 DateTime 定义。 // 因此这里使用 [当天 00:00, 次日 00:00) 查询,兼容历史数据中可能存在的时分秒。 // excludedId 用于编辑已有记录时排除自身,避免把当前记录误判为重复记录。 return DbContext.ex_dividend_info.FirstOrDefault(O => O.UnderlyingId == underlyingId && O.ExDividendDate >= exDividendDate && O.ExDividendDate < exDividendDate.AddDays(1) && (excludedId <= 0 || O.id != excludedId)); } private static void MergeNonZeroDividendValues(ex_dividend_info target, ex_dividend_info source) { if (target == null) { throw new ArgumentNullException(nameof(target)); } if (source == null) { throw new ArgumentNullException(nameof(source)); } // 同一业务键可能分别来自多行导入,或来自“数据库旧记录 + 当前导入记录”。 // 每个字段独立合并:当前值非零时覆盖旧值,当前值为零时保留旧值, // 这样派息、送股、配股数量、配股价格可以从不同来源补齐到同一行。 // 该约定将零解释为“未提供”,因此不能通过普通导入把已有字段显式清零。 if (source.GiveCashAmount != 0m) { target.GiveCashAmount = source.GiveCashAmount; } if (source.GiveShareAmount != 0m) { target.GiveShareAmount = source.GiveShareAmount; } if (source.RationedSharesAmount != 0m) { target.RationedSharesAmount = source.RationedSharesAmount; } if (source.RationedSharesPrice != 0m) { target.RationedSharesPrice = source.RationedSharesPrice; } if (source.Split.HasValue) { // Split 为空表示本次未提供,不能按历史兼容值 1 清空或覆盖旧倍数;明确提供 1 才覆盖。 target.Split = source.Split.Value; } if (source.EffectiveDate.HasValue) { // EffectiveDate 是日期语义,导入/接口可能带时分秒;统一只保留自然日。 // 为空时不覆盖数据库已有值,避免旧记录在不完整导入中丢失真实生效日。 target.EffectiveDate = source.EffectiveDate.Value.Date; } } public bool AddDividendInfos(IEnumerable infos, out string errMsg) { try { var dividendInfos = infos?.ToList(); if (dividendInfos == null || dividendInfos.Count == 0) { errMsg = "没有可保存的除权除息信息"; return false; } var preparedInfos = new List<(ex_dividend_info Item, underlying_manager Underlying, DateTime ExDividendDate)>(); var preparedIndexes = new Dictionary<(int UnderlyingId, DateTime ExDividendDate), int>(); var recordKeys = new Dictionary(); foreach (var item in dividendInfos) { if (item == null || string.IsNullOrWhiteSpace(item.UnderlyingCode)) { errMsg = "标的代码信息不存在"; return false; } var underlying = underlying_managerBLL.GetByCode(item.UnderlyingCode); if (underlying == null) { errMsg = $"{item.UnderlyingCode} 标的信息不存在"; return false; } if (!item.ExDividendDate.HasValue) { errMsg = "股权登记日信息不存在"; return false; } if (item.Split.HasValue && item.Split.Value <= 0m) { errMsg = "拆/合股倍数必须大于0"; return false; } // 保存前统一截断时间部分,确保 Excel/接口传入的同一天不同时间 // 能命中同一个自然日业务键,也与数据库的一行模型保持一致。 var exDividendDate = item.ExDividendDate.Value.Date; if (item.EffectiveDate.HasValue) { item.EffectiveDate = item.EffectiveDate.Value.Date; } var businessKey = (underlying.id, exDividendDate); if (item.id > 0 && recordKeys.TryGetValue(item.id, out var existingRecordKey) && existingRecordKey != businessKey) { errMsg = "同一除权信息不能重复保存"; return false; } item.UnderlyingId = underlying.id; item.ExDividendDate = exDividendDate; item.GiveCashAmount = OtcFormatHelper.FormatValue(item.GiveCashAmount, 6); item.RationedSharesAmount = OtcFormatHelper.FormatValue(item.RationedSharesAmount, 6); item.RationedSharesPrice = OtcFormatHelper.FormatValue(item.RationedSharesPrice, 6); item.GiveShareAmount = OtcFormatHelper.FormatValue(item.GiveShareAmount, 6); if (item.Split.HasValue) { // 拆合股比例可能为 0.01、0.001 等小数,保留 12 位避免导入时 // 被 6 位金额精度截断;日期字段则在上方统一归一化为自然日。 item.Split = OtcFormatHelper.FormatValue(item.Split.Value, 12); } // 先在当前批次内按业务键归并。第一条记录作为待保存目标,后续记录 // 只补充/覆盖非零字段,不会因为重复行而生成多条数据库记录。 if (preparedIndexes.TryGetValue(businessKey, out var preparedIndex)) { var preparedItem = preparedInfos[preparedIndex].Item; // 同一业务键下允许重复的是同一条记录(两个新对象都为 id=0, // 或两个对象的 id 相同);不同 id 代表不同存量记录,不能静默合并。 if ((preparedItem.id == 0) != (item.id == 0) || preparedItem.id > 0 && item.id > 0 && preparedItem.id != item.id) { errMsg = $"{item.UnderlyingCode} {exDividendDate:yyyy-MM-dd}除权信息不能合并不同记录"; return false; } MergeNonZeroDividendValues(preparedItem, item); if (item.id > 0) { recordKeys[item.id] = businessKey; } continue; } if (item.id > 0) { recordKeys[item.id] = businessKey; } preparedIndexes.Add(businessKey, preparedInfos.Count); preparedInfos.Add((item, underlying, exDividendDate)); } var basketList = DataCacheProvider.GetUnderlyingDataSource() .AsQueryable().Where(O => O.CommodityCode == "篮子标的" && O.SubData != null) .Select(O => new { O.UnderlyingCode, O.SubData }); IEnumerable priceList = null; foreach (var prepared in preparedInfos) { var item = prepared.Item; var underlying = prepared.Underlying; var itemDate = prepared.ExDividendDate; // id>0 表示前端正在编辑指定的存量记录;id=0 时先按自然日业务键 // 查找数据库旧记录,使“新增导入”也能与已有记录合并,而不是重复插入。 var dividend = item.id > 0 ? DbContext.ex_dividend_info.FirstOrDefault(O => O.id == item.id) : FindExDividendByBusinessKey(underlying.id, itemDate); if (dividend == null) { if (item.id > 0) { errMsg = "未找到要修改的除权除息信息"; return false; } item.DataSource = ExDividendDataSources.Manual; item.SourceUpdatedAt = null; item.ValidStatus = true; item.OptId = OptUser.UserId; item.OptName = OptUser.UserName; item.OptDate = DateTime.Now; DbContext.ex_dividend_info.Add(item); } else { var executingTradeNumber = GetDividendInfoExecutingTradeNumber(dividend); if (!string.IsNullOrWhiteSpace(executingTradeNumber)) { errMsg = $"不可修改,有交易【{executingTradeNumber}】使用了该条除权除息数据"; return false; } var conflictingDividend = FindExDividendByBusinessKey(underlying.id, itemDate, dividend.id); if (conflictingDividend != null) { errMsg = $"{item.UnderlyingCode} {itemDate:yyyy-MM-dd}除权信息已存在,不能修改为该业务键"; return false; } var sourceUpdatedAt = dividend.SourceUpdatedAt; dividend.UnderlyingCode = item.UnderlyingCode; dividend.UnderlyingId = item.UnderlyingId; dividend.ExDividendDate = item.ExDividendDate; // 数据库已有记录也必须走与批次内重复行相同的合并规则:导入字段非零 // 才覆盖旧值,导入字段为零则保留数据库存量值,避免一次不完整导入 // 把旧的派息/送股/配股信息误清零。 MergeNonZeroDividendValues(dividend, item); dividend.ValidStatus = true; dividend.DataSource = ExDividendDataSources.Manual; dividend.SourceUpdatedAt = sourceUpdatedAt; dividend.OptId = OptUser.UserId; dividend.OptName = OptUser.UserName; dividend.OptDate = DateTime.Now; } if (!basketList.Any()) { continue; } var basketCodes = basketList.Where(O => O.SubData.Contains(item.UnderlyingCode)).Select(O => O.UnderlyingCode); if (!basketCodes.Any()) { continue; } var removePriceList = DbContext.eod_stock_price.Where(O => basketCodes.Contains(O.UnderlyingCode) && O.ValueDate > item.ExDividendDate); if (!removePriceList.Any()) { continue; } DbContext.eod_stock_price.RemoveRange(removePriceList); } if (priceList != null) { DbContext.eod_stock_price.RemoveRange(priceList); } DbContext.SaveChanges(); errMsg = ""; return true; } catch (Exception ex) { errMsg = "保存失败"; LogFactory.GetLogger().Error(ex); } return false; } /// /// 检查除权除息记录有没有被执行 /// /// /// public bool checkDividendInfoExecuteStatus(ex_dividend_info info) { return !string.IsNullOrWhiteSpace(GetDividendInfoExecutingTradeNumber(info)); } /// /// 返回仍在引用已执行公司行为的交易编号;无引用时返回空字符串。 /// public string GetDividendInfoExecutingTradeNumber(ex_dividend_info info) { // TRS 公司行为以 EffectiveDate 为真正生效边界。登记日创建待生效事件不应锁定 // 维护;只有交易已经完成 EffectiveDate(例如收盘到 7 月 30 日,而真实除权日为 // 7 月 29 日)才禁止修改,避免修改后无法解释已落库的调整前后快照。 if (info?.EffectiveDate.HasValue == true) { var effectiveDate = info.EffectiveDate.Value.Date; var trsTrades = DbContext.trade .Where(x => x.ValidState != ConsGlobal.InValid && x.TradeType == "收益互换" && x.UnderlyingCode == info.UnderlyingCode && x.TradeDate <= effectiveDate && x.ExerciseDate >= effectiveDate) .Select(x => new { x.id, x.TradeNumber }) .ToList(); if (trsTrades.Count > 0) { // 是否仍被交易引用以当前有效 EOD 为准。公司行为事件本身是不可篡改 // 历史,交易回退后仍会保留;若仅凭 Applied 事件锁定,回退到登记日前 // 也无法纠错。生效日及以后还有有效 EOD 才表示当前仍已执行。 var trsTradeIds = trsTrades.Select(x => x.id).ToList(); var appliedTradeId = DbContext.eod_swap_position.Where(x => trsTradeIds.Contains(x.SwapTradeId) && !x.Invalid && x.UnderlyingCode == info.UnderlyingCode && x.ValueDate >= effectiveDate) .Select(x => x.SwapTradeId) .FirstOrDefault(); if (appliedTradeId > 0) { return trsTrades.First(x => x.id == appliedTradeId).TradeNumber; } // EffectiveDate 已存在时,当前有效 EOD 是唯一执行状态来源。 // 回退会清理生效日及之后的 EOD,但不会删除 eodStatus 或不可篡改的 // 公司行为审计事件;此处不能继续落入旧的登记日 eodStatus 判断, // 否则交易已回退仍会被错误判定为“已执行”而无法修改。 return string.Empty; } } var eodStatus = DbContext.eodStatus.Where(O => O.ValueDate == info.ExDividendDate && O.OptDate > info.OptDate).Any(); if (eodStatus) { //查询标的对应交易是否执行过收盘操作; var tradeQuery = from t in DbContext.trade.Where(O => O.UnderlyingCode == info.UnderlyingCode && O.TradeDate <= info.ExDividendDate && O.ExerciseDate >= info.ExDividendDate && O.DividendDate >= O.TradeDate) join et in DbContext.eod_trade.Where(O => ConsTrade.LiveTradeStatusList.Contains(O.TradeStatus)) on new { t.id, ValueDate = t.TradeDate.Value } equals new { id = et.TradeId, et.ValueDate } select t.TradeNumber; var executingTradeNumber = tradeQuery.FirstOrDefault(); if (!string.IsNullOrWhiteSpace(executingTradeNumber)) { return executingTradeNumber; } //查询篮子标的对应交易是否执行过收盘操作; var umList = DataCacheProvider.GetUnderlyingDataSource().AsQueryable(O => O.CommodityCode == "篮子标的" && O.SubData != null && O.SubData.Contains(info.UnderlyingCode)).Select(O => O.UnderlyingCode).ToArray(); tradeQuery = from t in DbContext.trade.Where(O => umList.Contains(O.UnderlyingCode) && O.TradeDate <= info.ExDividendDate && O.ExerciseDate >= info.ExDividendDate && O.DividendDate >= O.TradeDate) join et in DbContext.eod_trade.Where(O => ConsTrade.LiveTradeStatusList.Contains(O.TradeStatus)) on new { t.id, ValueDate = t.TradeDate.Value } equals new { id = et.TradeId, et.ValueDate } select t.TradeNumber; executingTradeNumber = tradeQuery.FirstOrDefault(); if (!string.IsNullOrWhiteSpace(executingTradeNumber)) { return executingTradeNumber; } //查询多标的对应交易是否执行过收盘操作; tradeQuery = from ts in DbContext.trade_swap_detail.Where(O => O.UnderlyingCode == info.UnderlyingCode) join t in DbContext.trade.Where(O => O.TradeDate <= info.ExDividendDate && O.ExerciseDate >= info.ExDividendDate && O.DividendDate >= O.TradeDate) on ts.TradeId equals t.id join et in DbContext.eod_trade.Where(O => ConsTrade.LiveTradeStatusList.Contains(O.TradeStatus)) on new { t.id, ValueDate = t.TradeDate.Value } equals new { id = et.TradeId, et.ValueDate } select t.TradeNumber; executingTradeNumber = tradeQuery.FirstOrDefault(); if (!string.IsNullOrWhiteSpace(executingTradeNumber)) { return executingTradeNumber; } } return string.Empty; } public List QueryDividendTrade(DividendTradeReq req) { TradeChanges changes = null; var result = new List(); var predicate = PredicateBuilder.Create(O => O.ValidState != "InValid" && ConsTrade.TradeStatusAfterConfirmed.Contains(O.TradeStatus) && O.TradeType != "结构化交易"); if (!string.IsNullOrWhiteSpace(req.TradeNumber)) { predicate = predicate.And(O => O.TradeNumber == req.TradeNumber); } if (req.ClientIds != null && req.ClientIds.Any(O => O > 0)) { predicate = predicate.And(O => req.ClientIds.Contains(O.ClientId)); } if (req.AssetUnitIds != null && req.AssetUnitIds.Any(O => O > 0)) { predicate = predicate.And(O => req.AssetUnitIds.Contains(O.AssetId)); } if (req.TradeTypes != null && req.TradeTypes.Any(O => !string.IsNullOrWhiteSpace(O))) { predicate = predicate.And(O => req.TradeTypes.Contains(O.TradeType) || req.TradeTypes.Contains(O.StructureType)); } if (req.UnderlyingIds != null && req.UnderlyingIds.Any(O => O > 0)) { predicate = predicate.And(O => req.UnderlyingIds.Contains(O.UnderlyingId)); } if (req.TraderIds != null && req.TraderIds.Any(O => O > 0)) { predicate = predicate.And(O => req.TraderIds.Contains(O.TraderId)); } if (req.TradeDateStart > DateTime.MinValue) { predicate = predicate.And(O => req.TradeDateStart <= O.TradeDate); } if (req.TradeDateEnd > DateTime.MinValue) { predicate = predicate.And(O => req.TradeDateEnd >= O.TradeDate); } if (req.ExerciseDateStart > DateTime.MinValue) { predicate = predicate.And(O => req.ExerciseDateStart <= O.ExerciseDate); } if (req.ExerciseDateEnd > DateTime.MinValue) { predicate = predicate.And(O => req.ExerciseDateEnd >= O.ExerciseDate); } var query = DbContext.trade.Where(predicate); var tradeInfos = (from diInfo in GetExDividendQuery(req.DividendDate, req.DividendDateEnd.Value) join t in query on diInfo.UnderlyingCode equals t.UnderlyingCode where t.TradeDate <= diInfo.ExDividendDate && t.ValidState != "InValid" && (ConsTrade.PositionTradeStatusList.Contains(t.TradeStatus) || (ConsTrade.TradeCompleteStatus.Contains(t.TradeStatus) && ((t.UnWindDate == null && t.ExerciseDate > diInfo.ExDividendDate) || (t.UnWindDate != null && t.UnWindDate > diInfo.ExDividendDate)))) select new { diInfo, t })//单标的 .Concat(from diInfo in GetExDividendQuery(req.DividendDate, req.DividendDateEnd.Value) join ts in DbContext.trade_swap_detail on diInfo.UnderlyingCode equals ts.UnderlyingCode join t in query on ts.TradeId equals t.id where t.TradeDate <= diInfo.ExDividendDate && t.ValidState != "InValid" && (ConsTrade.PositionTradeStatusList.Contains(t.TradeStatus) || (ConsTrade.TradeCompleteStatus.Contains(t.TradeStatus) && ((t.UnWindDate == null && t.ExerciseDate > diInfo.ExDividendDate) || (t.UnWindDate != null && t.UnWindDate > diInfo.ExDividendDate)))) select new { diInfo, t })//多标的 .ToList(); //篮子标的 var dividendDict = GetExDividendQuery(req.DividendDate, req.DividendDateEnd.Value).ToDictionary(K => K.UnderlyingCode, V => V); var umList = DbContext.underlying_manager.Where(O => O.CommodityCode == "篮子标的" && O.SubData != null).ToList(); var codeDict = new Dictionary(); foreach (var item in dividendDict) { var ums = umList.Where(O => O.SubData.Contains(item.Key)).ToList(); ums.ForEach(O => codeDict[O.UnderlyingCode.ToLower()] = item.Value); } var tempTrade = query.Where(O => codeDict.Keys.Contains(O.UnderlyingCode)); foreach (var t in tempTrade) { var code = t.UnderlyingCode.ToLower(); var diInfo = codeDict.ContainsKey(code) ? codeDict[code] : null; if (diInfo == null) { continue; } if (t.TradeDate <= diInfo.ExDividendDate && ((t.UnWindDate == null && t.ExerciseDate > diInfo.ExDividendDate) || (t.UnWindDate != null && t.UnWindDate > diInfo.ExDividendDate))) { tradeInfos.Add(new { diInfo, t }); } } var ids = tradeInfos.Select(O => O.t.id).ToArray(); var subTrades = tradeInfos.Select(O => O.t).ToList(); tradeBLL.SetFieldsByTradeType(subTrades); var changeService = new DbRecordChangesService(this); var tradeChangeDict = changeService.GetValue(ConsInfoChangeType.Dividend, ids, req.DividendDate).GroupBy(O => O.RecordId).ToDictionary(K => K.Key, V => V.ToList()); var ratioDict = changeService.GetValue( ConsInfoChangeType.UserChange, ids, nameof(trade.DividendRatio), req.DividendDate).ToDictionary(K => K.RecordId, V => { return double.TryParse(V.NewValue, out var temp) ? (double?)temp : null; }); foreach (var item in tradeInfos) { var info = new DividendTrade { TradeId = item.t.id, EncryptId = item.t.EncryptId, TradeNumber = item.t.TradeNumber, ExDividendDate = item.diInfo.ExDividendDate.Value, DividendStatus = tradeChangeDict.ContainsKey(item.t.id), TradeStatus = item.t.TradeStatus, ClientName = item.t.ClientName, TradeType = item.t.StructureType ?? item.t.TradeType, TradeDate = item.t.TradeDate.Value, ExerciseDate = item.t.ExerciseDate.Value, UnderlyingCode = item.t.UnderlyingCode, DividendRatio = ratioDict.ContainsKey(item.t.id) ? ratioDict[item.t.id].Value : double.NaN, trade = item.t }; var tradeFullInfo = subTrades.Where(O => O.id == item.t.id).FirstOrDefault(); if (info.DividendStatus) { changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == nameof(trade.OriginalStockEqvNotional)).FirstOrDefault(); info.StockEqvNotional = TryParseDouble(changes?.NewValue, item.t.OriginalStockEqvNotional ?? double.NaN); info.StockEqvNotional_After = TryParseDouble(changes?.FieldValue, double.NaN); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == nameof(trade.SpotPrice)).FirstOrDefault(); info.SpotPrice = TryParseDouble(changes?.NewValue, info.SpotPrice); info.SpotPrice_After = TryParseDouble(changes?.FieldValue, double.NaN); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == nameof(trade.TradeOriginalAmount)).FirstOrDefault(); info.TradeOriginalAmount = TryParseDouble(changes?.NewValue, info.TradeOriginalAmount); info.TradeOriginalAmount_After = TryParseDouble(changes?.FieldValue, double.NaN); if (item.t.IsMoneynessOptionData) { var spotPrice = info.SpotPrice_After; info.Strike = info.SpotPrice * (item.t.Strike ?? 1); info.Strike_After = spotPrice * (item.t.Strike ?? 1); switch (item.t.TradeType) { case "障碍期权": info.Barrier = info.SpotPrice * (tradeFullInfo.trade_barrier_option.BarrierPrice ?? 1); info.Strike = info.SpotPrice; //info.Strike_After = spotPrice * (tradeFullInfo.trade_barrier_option.BarrierPrice ?? 1); //info.StrikeHigh = info.SpotPrice * (tradeFullInfo.trade_barrier_option.UpperBarrierPrice ?? 1); //info.StrikeHigh_After = spotPrice * (tradeFullInfo.trade_barrier_option.UpperBarrierPrice ?? 1); break; //case "彩虹期权": // trade.trade_rainbow_option.SpotPrice1 = NewValue.trade_rainbow_option.SpotPrice1; // trade.trade_rainbow_option.SpotPrice2 = NewValue.trade_rainbow_option.SpotPrice2; // trade.trade_rainbow_option.Strike = NewValue.trade_rainbow_option.Strike; // trade.trade_rainbow_option.Strike2 = NewValue.trade_rainbow_option.Strike2; // break; case "凤凰期权": info.KIBarrier = info.SpotPrice * tradeFullInfo.trade_autocall.KIBarrier; info.KIBarrier_After = spotPrice * tradeFullInfo.trade_autocall.KIBarrier; info.KOBarrier = info.SpotPrice * tradeFullInfo.trade_autocall.KOBarrier; info.KOBarrier_After = spotPrice * tradeFullInfo.trade_autocall.KOBarrier; info.CouponBarrier = info.SpotPrice * tradeFullInfo.trade_autocall.CouponBarrier; info.CouponBarrier_After = spotPrice * tradeFullInfo.trade_autocall.CouponBarrier; info.Strike = info.SpotPrice * (tradeFullInfo.trade_autocall.SpreadStrike ?? 1); info.Strike_After = spotPrice * (tradeFullInfo.trade_autocall.SpreadStrike ?? 1); info.StrikeHigh = info.SpotPrice * (tradeFullInfo.trade_autocall.SpreadStrike1 ?? 1); info.StrikeHigh_After = spotPrice * (tradeFullInfo.trade_autocall.SpreadStrike1 ?? 1); break; case "双鲨期权": info.BarrierHigh = info.SpotPrice * tradeFullInfo.trade_double_sharkfin_option.BarrierHigh; info.BarrierHigh_After = spotPrice * tradeFullInfo.trade_double_sharkfin_option.BarrierHigh; info.Barrier = info.SpotPrice * tradeFullInfo.trade_double_sharkfin_option.BarrierLow; info.Barrier_After = spotPrice * tradeFullInfo.trade_double_sharkfin_option.BarrierLow; info.StrikeHigh = info.SpotPrice * (tradeFullInfo.trade_double_sharkfin_option.StrikeHigh ?? 1); info.StrikeHigh_After = spotPrice * (tradeFullInfo.trade_double_sharkfin_option.StrikeHigh ?? 1); info.Strike = info.SpotPrice * (tradeFullInfo.trade_double_sharkfin_option.StrikeLow ?? 1); info.Strike_After = spotPrice * (tradeFullInfo.trade_double_sharkfin_option.StrikeLow ?? 1); break; case "区间累积期权": info.LowerRange = info.SpotPrice * tradeFullInfo.trade_rangeaccrual.LowerRange; info.LowerRange_After = spotPrice * tradeFullInfo.trade_rangeaccrual.LowerRange; info.UpperRange = info.SpotPrice * tradeFullInfo.trade_rangeaccrual.UpperRange; info.UpperRange_After = spotPrice * tradeFullInfo.trade_rangeaccrual.UpperRange; break; case "雪球期权": info.KIBarrier = info.SpotPrice * tradeFullInfo.trade_snowball.KIBarrier; info.KIBarrier_After = spotPrice * tradeFullInfo.trade_snowball.KIBarrier; info.KOBarrier = info.SpotPrice * tradeFullInfo.trade_snowball.KOBarrier; info.KOBarrier_After = spotPrice * tradeFullInfo.trade_snowball.KOBarrier; info.SpreadStrikeAtKO = info.SpotPrice * tradeFullInfo.trade_snowball.SpreadStrikeAtKO; info.SpreadStrikeAtKO_After = spotPrice * tradeFullInfo.trade_snowball.SpreadStrikeAtKO; info.SpreadStrikeAtKO1 = info.SpotPrice * tradeFullInfo.trade_snowball.SpreadStrikeAtKO1; info.SpreadStrikeAtKO1_After = spotPrice * tradeFullInfo.trade_snowball.SpreadStrikeAtKO1; info.SpreadStrikeAtMaturity = info.SpotPrice * tradeFullInfo.trade_snowball.SpreadStrikeAtMaturity; info.SpreadStrikeAtMaturity_After = spotPrice * tradeFullInfo.trade_snowball.SpreadStrikeAtMaturity; info.SpreadStrikeAtMaturity1 = info.SpotPrice * tradeFullInfo.trade_snowball.SpreadStrikeAtMaturity1; info.SpreadStrikeAtMaturity1_After = spotPrice * tradeFullInfo.trade_snowball.SpreadStrikeAtMaturity1; break; case "气囊结构": info.Barrier = info.SpotPrice * tradeFullInfo.trade_airbag.Barrier; info.Barrier_After = spotPrice * tradeFullInfo.trade_airbag.Barrier; info.StrikeHigh = info.SpotPrice * tradeFullInfo.trade_airbag.HighStrike; info.StrikeHigh_After = spotPrice * tradeFullInfo.trade_airbag.HighStrike; break; case "收益互换": info.PayUnderlyingCode = tradeFullInfo.trade_swap.PayUnderlyingCode; info.GetUnderlyingCode = tradeFullInfo.trade_swap.GetUnderlyingCode; break; } } else { changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == nameof(trade.Strike)).FirstOrDefault(); info.Strike = TryParseDouble(changes?.NewValue, info.Strike); info.Strike_After = TryParseDouble(changes?.FieldValue, double.NaN); var fileName = ""; switch (item.t.TradeType) { case "障碍期权": fileName = nameof(trade.trade_barrier_option) + "." + nameof(trade_barrier_option.BarrierPrice); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == fileName).FirstOrDefault(); info.Strike = TryParseDouble(changes?.NewValue, info.Strike); info.Strike_After = TryParseDouble(changes?.FieldValue, double.NaN); fileName = nameof(trade.trade_barrier_option) + "." + nameof(trade_barrier_option.UpperBarrierPrice); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == fileName).FirstOrDefault(); info.StrikeHigh = TryParseDouble(changes?.NewValue, info.StrikeHigh); info.StrikeHigh_After = TryParseDouble(changes?.FieldValue, double.NaN); break; //case "彩虹期权": // trade.trade_rainbow_option.SpotPrice1 = NewValue.trade_rainbow_option.SpotPrice1; // trade.trade_rainbow_option.SpotPrice2 = NewValue.trade_rainbow_option.SpotPrice2; // trade.trade_rainbow_option.Strike = NewValue.trade_rainbow_option.Strike; // trade.trade_rainbow_option.Strike2 = NewValue.trade_rainbow_option.Strike2; // break; case "凤凰期权": fileName = nameof(trade.trade_autocall) + "." + nameof(trade_autocall.KIBarrier); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == fileName).FirstOrDefault(); info.KIBarrier = TryParseDouble(changes?.NewValue, info.KIBarrier); info.KIBarrier_After = TryParseDouble(changes?.FieldValue, double.NaN); fileName = nameof(trade.trade_autocall) + "." + nameof(trade_autocall.KOBarrier); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == fileName).FirstOrDefault(); info.KOBarrier = TryParseDouble(changes?.NewValue, info.KOBarrier); info.KOBarrier_After = TryParseDouble(changes?.FieldValue, double.NaN); fileName = nameof(trade.trade_autocall) + "." + nameof(trade_autocall.CouponBarrier); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == fileName).FirstOrDefault(); info.CouponBarrier = TryParseDouble(changes?.NewValue, info.CouponBarrier); info.CouponBarrier_After = TryParseDouble(changes?.FieldValue, double.NaN); fileName = nameof(trade.trade_autocall) + "." + nameof(trade_autocall.SpreadStrike); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == fileName).FirstOrDefault(); info.Strike = TryParseDouble(changes?.NewValue, info.Strike); info.Strike_After = TryParseDouble(changes?.FieldValue, double.NaN); fileName = nameof(trade.trade_autocall) + "." + nameof(trade_autocall.SpreadStrike1); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == fileName).FirstOrDefault(); info.StrikeHigh = TryParseDouble(changes?.NewValue, info.StrikeHigh); info.StrikeHigh_After = TryParseDouble(changes?.FieldValue, double.NaN); break; case "双鲨期权": fileName = nameof(trade.trade_double_sharkfin_option) + "." + nameof(trade_double_sharkfin_option.BarrierHigh); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == fileName).FirstOrDefault(); info.BarrierHigh = TryParseDouble(changes?.NewValue, info.BarrierHigh); info.BarrierHigh_After = TryParseDouble(changes?.FieldValue, double.NaN); fileName = nameof(trade.trade_double_sharkfin_option) + "." + nameof(trade_double_sharkfin_option.BarrierLow); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == fileName).FirstOrDefault(); info.Barrier = TryParseDouble(changes?.NewValue, info.Barrier); info.Barrier_After = TryParseDouble(changes?.FieldValue, double.NaN); fileName = nameof(trade.trade_double_sharkfin_option) + "." + nameof(trade_double_sharkfin_option.StrikeHigh); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == fileName).FirstOrDefault(); info.StrikeHigh = TryParseDouble(changes?.NewValue, info.StrikeHigh); info.StrikeHigh_After = TryParseDouble(changes?.FieldValue, double.NaN); fileName = nameof(trade.trade_double_sharkfin_option) + "." + nameof(trade_double_sharkfin_option.StrikeLow); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == fileName).FirstOrDefault(); info.Strike = TryParseDouble(changes?.NewValue, info.Strike); info.Strike_After = TryParseDouble(changes?.FieldValue, double.NaN); break; case "区间累积期权": fileName = nameof(trade.trade_rangeaccrual) + "." + nameof(trade_rangeaccrual.LowerRange); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == fileName).FirstOrDefault(); info.LowerRange = TryParseDouble(changes?.NewValue, info.LowerRange); info.LowerRange_After = TryParseDouble(changes?.FieldValue, double.NaN); fileName = nameof(trade.trade_rangeaccrual) + "." + nameof(trade_rangeaccrual.UpperRange); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == fileName).FirstOrDefault(); info.UpperRange = TryParseDouble(changes?.NewValue, info.UpperRange); info.UpperRange_After = TryParseDouble(changes?.FieldValue, double.NaN); break; case "雪球期权": fileName = nameof(trade.trade_snowball) + "." + nameof(trade_snowball.KIBarrier); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == fileName).FirstOrDefault(); info.KIBarrier = TryParseDouble(changes?.NewValue, info.KIBarrier); info.KIBarrier_After = TryParseDouble(changes?.FieldValue, double.NaN); fileName = nameof(trade.trade_snowball) + "." + nameof(trade_snowball.KOBarrier); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == fileName).FirstOrDefault(); info.KOBarrier = TryParseDouble(changes?.NewValue, info.KOBarrier); info.KOBarrier_After = TryParseDouble(changes?.FieldValue, double.NaN); fileName = nameof(trade.trade_snowball) + "." + nameof(trade_snowball.SpreadStrikeAtKO); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == fileName).FirstOrDefault(); info.SpreadStrikeAtKO = TryParseDouble(changes?.NewValue, info.SpreadStrikeAtKO ?? double.NaN); info.SpreadStrikeAtKO_After = TryParseDouble(changes?.FieldValue, double.NaN); fileName = nameof(trade.trade_snowball) + "." + nameof(trade_snowball.SpreadStrikeAtKO1); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == fileName).FirstOrDefault(); info.SpreadStrikeAtKO1 = TryParseDouble(changes?.NewValue, info.SpreadStrikeAtKO1 ?? double.NaN); info.SpreadStrikeAtKO1_After = TryParseDouble(changes?.FieldValue, double.NaN); fileName = nameof(trade.trade_snowball) + "." + nameof(trade_snowball.SpreadStrikeAtMaturity); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == fileName).FirstOrDefault(); info.SpreadStrikeAtMaturity = TryParseDouble(changes?.NewValue, info.SpreadStrikeAtMaturity ?? double.NaN); info.SpreadStrikeAtMaturity_After = TryParseDouble(changes?.FieldValue, double.NaN); fileName = nameof(trade.trade_snowball) + "." + nameof(trade_snowball.SpreadStrikeAtMaturity1); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == fileName).FirstOrDefault(); info.SpreadStrikeAtMaturity1 = TryParseDouble(changes?.NewValue, info.SpreadStrikeAtMaturity1 ?? double.NaN); info.SpreadStrikeAtMaturity1_After = TryParseDouble(changes?.FieldValue, double.NaN); break; case "气囊结构": fileName = nameof(trade.trade_airbag) + "." + nameof(trade_airbag.Barrier); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == fileName).FirstOrDefault(); info.Barrier = TryParseDouble(changes?.NewValue, info.Barrier); info.Barrier_After = TryParseDouble(changes?.FieldValue, double.NaN); fileName = nameof(trade.trade_airbag) + "." + nameof(trade_airbag.HighStrike); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == fileName).FirstOrDefault(); info.StrikeHigh = TryParseDouble(changes?.NewValue, info.StrikeHigh); info.StrikeHigh_After = TryParseDouble(changes?.FieldValue, double.NaN); break; case "收益互换": info.PayUnderlyingCode = tradeFullInfo.trade_swap.PayUnderlyingCode; info.GetUnderlyingCode = tradeFullInfo.trade_swap.GetUnderlyingCode; fileName = nameof(trade.trade_swap) + "." + nameof(trade_swap.PaySpotPrice); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == fileName).FirstOrDefault(); info.PaySpotPrice = TryParseDouble(changes?.NewValue, info.PaySpotPrice); info.PaySpotPrice_After = TryParseDouble(changes?.FieldValue, double.NaN); fileName = nameof(trade.trade_swap) + "." + nameof(trade_swap.GetSpotPrice); changes = tradeChangeDict[item.t.id].Where(O => O.FieldName == fileName).FirstOrDefault(); info.GetSpotPrice = TryParseDouble(changes?.NewValue, info.GetSpotPrice); info.GetSpotPrice_After = TryParseDouble(changes?.FieldValue, double.NaN); break; case "累计期权": throw new NotImplementedException("QueryDividendTrade中未实现累计期权逻辑"); } } } else { info.StockEqvNotional = item.t.OriginalStockEqvNotional ?? double.NaN; info.StockEqvNotional_After = double.NaN; info.SpotPrice = item.t.SpotPrice ?? double.NaN; info.SpotPrice_After = double.NaN; info.TradeOriginalAmount = item.t.TradeOriginalAmount ?? item.t.OriginalNotional ?? 0; info.TradeOriginalAmount_After = double.NaN; if (item.t.IsMoneynessOptionData) { info.Strike = info.SpotPrice * (item.t.Strike ?? 1); info.Strike_After = double.NaN; switch (item.t.TradeType) { case "障碍期权": info.Strike = info.SpotPrice * (tradeFullInfo.trade_barrier_option.BarrierPrice ?? 1); info.Strike_After = double.NaN; info.StrikeHigh = info.SpotPrice * (tradeFullInfo.trade_barrier_option.UpperBarrierPrice ?? 1); info.StrikeHigh_After = double.NaN; break; //case "彩虹期权": // trade.trade_rainbow_option.SpotPrice1 = NewValue.trade_rainbow_option.SpotPrice1; // trade.trade_rainbow_option.SpotPrice2 = NewValue.trade_rainbow_option.SpotPrice2; // trade.trade_rainbow_option.Strike = NewValue.trade_rainbow_option.Strike; // trade.trade_rainbow_option.Strike2 = NewValue.trade_rainbow_option.Strike2; // break; case "凤凰期权": info.KIBarrier = info.SpotPrice * tradeFullInfo.trade_autocall.KIBarrier; info.KIBarrier_After = double.NaN; info.KOBarrier = info.SpotPrice * tradeFullInfo.trade_autocall.KOBarrier; info.KOBarrier_After = double.NaN; info.CouponBarrier = info.SpotPrice * tradeFullInfo.trade_autocall.CouponBarrier; info.CouponBarrier_After = double.NaN; info.Strike = info.SpotPrice * (tradeFullInfo.trade_autocall.SpreadStrike ?? 1); info.Strike_After = double.NaN; info.StrikeHigh = info.SpotPrice * (tradeFullInfo.trade_autocall.SpreadStrike1 ?? 1); info.StrikeHigh_After = double.NaN; break; case "双鲨期权": info.BarrierHigh = info.SpotPrice * tradeFullInfo.trade_double_sharkfin_option.BarrierHigh; info.BarrierHigh_After = double.NaN; info.Barrier = info.SpotPrice * tradeFullInfo.trade_double_sharkfin_option.BarrierLow; info.Barrier_After = double.NaN; info.StrikeHigh = info.SpotPrice * (tradeFullInfo.trade_double_sharkfin_option.StrikeHigh ?? 1); info.StrikeHigh_After = double.NaN; info.Strike = info.SpotPrice * (tradeFullInfo.trade_double_sharkfin_option.StrikeLow ?? 1); info.Strike_After = double.NaN; break; case "区间累积期权": info.LowerRange = info.SpotPrice * tradeFullInfo.trade_rangeaccrual.LowerRange; info.LowerRange_After = double.NaN; info.UpperRange = info.SpotPrice * tradeFullInfo.trade_rangeaccrual.UpperRange; info.UpperRange_After = double.NaN; break; case "雪球期权": info.KIBarrier = info.SpotPrice * tradeFullInfo.trade_snowball.KIBarrier; info.KIBarrier_After = double.NaN; info.KOBarrier = info.SpotPrice * tradeFullInfo.trade_snowball.KOBarrier; info.KOBarrier_After = double.NaN; info.SpreadStrikeAtKO = info.SpotPrice * tradeFullInfo.trade_snowball.SpreadStrikeAtKO; info.SpreadStrikeAtKO_After = double.NaN; info.SpreadStrikeAtKO1 = info.SpotPrice * tradeFullInfo.trade_snowball.SpreadStrikeAtKO1; info.SpreadStrikeAtKO1_After = double.NaN; info.SpreadStrikeAtMaturity = info.SpotPrice * tradeFullInfo.trade_snowball.SpreadStrikeAtMaturity; info.SpreadStrikeAtMaturity_After = double.NaN; info.SpreadStrikeAtMaturity1 = info.SpotPrice * tradeFullInfo.trade_snowball.SpreadStrikeAtMaturity1; info.SpreadStrikeAtMaturity1_After = double.NaN; break; case "气囊结构": info.Barrier = info.SpotPrice * tradeFullInfo.trade_airbag.Barrier; info.Barrier_After = double.NaN; info.StrikeHigh = info.SpotPrice * tradeFullInfo.trade_airbag.HighStrike; info.StrikeHigh_After = double.NaN; break; case "收益互换": info.PayUnderlyingCode = tradeFullInfo.trade_swap.PayUnderlyingCode; info.GetUnderlyingCode = tradeFullInfo.trade_swap.GetUnderlyingCode; break; } } else { info.Strike = item.t.Strike ?? double.NaN; info.Strike_After = double.NaN; switch (item.t.TradeType) { case "障碍期权": info.Barrier = item.t.trade_barrier_option.BarrierPrice ?? 1; info.Barrier_After = double.NaN; info.BarrierHigh = item.t.trade_barrier_option.UpperBarrierPrice ?? 1; info.BarrierHigh_After = double.NaN; break; //case "彩虹期权": // trade.trade_rainbow_option.SpotPrice1 = NewValue.trade_rainbow_option.SpotPrice1; // trade.trade_rainbow_option.SpotPrice2 = NewValue.trade_rainbow_option.SpotPrice2; // trade.trade_rainbow_option.Strike = NewValue.trade_rainbow_option.Strike; // trade.trade_rainbow_option.Strike2 = NewValue.trade_rainbow_option.Strike2; // break; case "凤凰期权": info.KIBarrier = item.t.trade_autocall.KIBarrier; info.KIBarrier_After = double.NaN; info.KOBarrier = item.t.trade_autocall.KOBarrier; info.KOBarrier_After = double.NaN; info.CouponBarrier = item.t.trade_autocall.CouponBarrier; info.CouponBarrier_After = double.NaN; info.Strike = item.t.trade_autocall.SpreadStrike ?? 1; info.Strike_After = double.NaN; info.StrikeHigh = item.t.trade_autocall.SpreadStrike1 ?? 1; info.StrikeHigh_After = double.NaN; break; case "双鲨期权": info.BarrierHigh = item.t.trade_double_sharkfin_option.BarrierHigh; info.BarrierHigh_After = double.NaN; info.Barrier = item.t.trade_double_sharkfin_option.BarrierLow; info.Barrier_After = double.NaN; info.StrikeHigh = item.t.trade_double_sharkfin_option.StrikeHigh ?? 0; info.StrikeHigh_After = double.NaN; info.Strike = item.t.trade_double_sharkfin_option.StrikeLow ?? 0; info.Strike_After = double.NaN; break; case "区间累积期权": info.LowerRange = item.t.trade_rangeaccrual.LowerRange; info.LowerRange_After = double.NaN; info.UpperRange = item.t.trade_rangeaccrual.UpperRange; info.UpperRange_After = double.NaN; break; case "雪球期权": info.KIBarrier = item.t.trade_snowball.KIBarrier; info.KIBarrier_After = double.NaN; info.KOBarrier = item.t.trade_snowball.KOBarrier; info.KOBarrier_After = double.NaN; info.SpreadStrikeAtKO = info.SpotPrice * item.t.trade_snowball.SpreadStrikeAtKO; info.SpreadStrikeAtKO_After = double.NaN; info.SpreadStrikeAtKO1 = info.SpotPrice * item.t.trade_snowball.SpreadStrikeAtKO1; info.SpreadStrikeAtKO1_After = double.NaN; info.SpreadStrikeAtMaturity = info.SpotPrice * item.t.trade_snowball.SpreadStrikeAtMaturity; info.SpreadStrikeAtMaturity_After = double.NaN; info.SpreadStrikeAtMaturity1 = info.SpotPrice * item.t.trade_snowball.SpreadStrikeAtMaturity1; info.SpreadStrikeAtMaturity1_After = double.NaN; break; case "气囊结构": info.Barrier = item.t.trade_airbag.Barrier; info.Barrier_After = double.NaN; info.StrikeHigh = item.t.trade_airbag.HighStrike; info.StrikeHigh_After = double.NaN; break; case "收益互换": info.PayUnderlyingCode = tradeFullInfo.trade_swap.PayUnderlyingCode; info.GetUnderlyingCode = tradeFullInfo.trade_swap.GetUnderlyingCode; info.PaySpotPrice = item.t.trade_swap.PaySpotPrice ?? double.NaN; info.PaySpotPrice_After = double.NaN; info.GetSpotPrice = item.t.trade_swap.GetSpotPrice ?? double.NaN; info.GetSpotPrice_After = double.NaN; break; } } } info.TradeOriginalAmountV = CalculationModule.TradeCalcHelper.GetTradeAmountV(item.t, info.TradeOriginalAmount, 1); info.TradeOriginalAmountV_After = CalculationModule.TradeCalcHelper.GetTradeAmountV(item.t, info.TradeOriginalAmount_After, 1); result.Add(info); } if (req.DividendStatus != null) { result = result.Where(O => O.DividendStatus == req.DividendStatus.Value).ToList(); } return result; } private double TryParseDouble(string str, double defaultValue) { return double.TryParse(str, out var result) ? result : defaultValue; } public byte[] ExportDividendTrade(DividendTradeReq req) { var ret = QueryDividendTrade(req); var dc = new List { new ExcelHelper.DataColumnModel("交易编号", "TradeNumber"), new ExcelHelper.DataColumnModel("股权登记日", "ExDividendDate", dateFormatter), new ExcelHelper.DataColumnModel("除权除息状态", "DividendStatus", (cv, obj) => { var status = (bool)cv; return status ? "已除权" : "未除权"; }), new ExcelHelper.DataColumnModel("交易状态", "TradeStatus"), new ExcelHelper.DataColumnModel("交易对手方", "ClientName"), new ExcelHelper.DataColumnModel("结构类型", "TradeType"), new ExcelHelper.DataColumnModel("名义本金", "StockEqvNotional", (cv, obj) => { var result = ""; if (obj is DividendTrade trade) { result = priceFormatter(trade.StockEqvNotional, trade.StockEqvNotional_After, OtcFormatFlag.umprice); } return result; }), new ExcelHelper.DataColumnModel("成交日期", "TradeDate", dateFormatter), new ExcelHelper.DataColumnModel("到期日期", "ExerciseDate", dateFormatter), new ExcelHelper.DataColumnModel("标的代码", "UnderlyingCode"), new ExcelHelper.DataColumnModel("除权除息系数", "DividendRatio", (cv, obj) => { var result = ""; if (obj is DividendTrade trade) { result = priceFormatter(trade.DividendRatio, double.NaN, 6); } return result; }), new ExcelHelper.DataColumnModel("期初标的价格", "SpotPrice", (cv, obj) => { var result = ""; if (obj is DividendTrade trade) { result = priceFormatter(trade.SpotPrice, trade.SpotPrice_After, OtcFormatFlag.umprice); } return result; }), new ExcelHelper.DataColumnModel("执行价格", "Strike", (cv, obj) => { var result = ""; if (obj is DividendTrade trade) { result = priceFormatter(trade.Strike, trade.Strike_After, OtcFormatFlag.umprice); } return result; }), new ExcelHelper.DataColumnModel("成交数量", "TradeOriginalAmount", (cv, obj) => { var result = ""; if (obj is DividendTrade trade) { var amountv = CalculationModule.TradeCalcHelper.GetTradeAmountV(trade.trade,trade.TradeOriginalAmount,1); var amountv_after = CalculationModule.TradeCalcHelper.GetTradeAmountV(trade.trade,trade.TradeOriginalAmount_After,1); result = priceFormatter(amountv, amountv_after, OtcFormatFlag.notional); } return result; }), new ExcelHelper.DataColumnModel("有效成交数量", "TradeOriginalAmount", (cv, obj) => { var result = ""; if (obj is DividendTrade trade) { result = priceFormatter(trade.TradeOriginalAmount, trade.TradeOriginalAmount_After, OtcFormatFlag.notional); } return result; }), new ExcelHelper.DataColumnModel("高行权价", "StrikeHigh", (cv, obj) => { var result = ""; if (obj is DividendTrade trade) { result = priceFormatter(trade.StrikeHigh, trade.StrikeHigh_After, OtcFormatFlag.umprice); } return result; }), new ExcelHelper.DataColumnModel("障碍价格", "Barrier", (cv, obj) => { var result = ""; if (obj is DividendTrade trade) { result = priceFormatter(trade.Barrier, trade.Barrier_After, OtcFormatFlag.umprice); } return result; }), new ExcelHelper.DataColumnModel("高障碍价格", "BarrierHigh", (cv, obj) => { var result = ""; if (obj is DividendTrade trade) { result = priceFormatter(trade.BarrierHigh, trade.BarrierHigh_After, OtcFormatFlag.umprice); } return result; }), new ExcelHelper.DataColumnModel("敲入障碍价格", "KIBarrier", (cv, obj) => { var result = ""; if (obj is DividendTrade trade) { result = priceFormatter(trade.KIBarrier, trade.KIBarrier_After, OtcFormatFlag.umprice); } return result; }), new ExcelHelper.DataColumnModel("敲出障碍价格", "KOBarrier", (cv, obj) => { var result = ""; if (obj is DividendTrade trade) { result = priceFormatter(trade.KOBarrier, trade.KOBarrier_After, OtcFormatFlag.umprice); } return result; }), new ExcelHelper.DataColumnModel("票息障碍价格", "CouponBarrier", (cv, obj) => { var result = ""; if (obj is DividendTrade trade) { result = priceFormatter(trade.CouponBarrier, trade.CouponBarrier_After, OtcFormatFlag.umprice); } return result; }), new ExcelHelper.DataColumnModel("互换标的代码(收取方)", "GetUnderlyingCode"), new ExcelHelper.DataColumnModel("互换标的价格(收取方)", "GetSpotPrice", (cv, obj) => { var result = ""; if (obj is DividendTrade trade) { result = priceFormatter(trade.GetSpotPrice, trade.GetSpotPrice_After, OtcFormatFlag.umprice); } return result; }), new ExcelHelper.DataColumnModel("互换标的代码(支付方)", "PayUnderlyingCode"), new ExcelHelper.DataColumnModel("互换标的价格(支付方)", "PaySpotPrice", (cv, obj) => { var result = ""; if (obj is DividendTrade trade) { result = priceFormatter(trade.PaySpotPrice, trade.PaySpotPrice_After, OtcFormatFlag.umprice); } return result; }), new ExcelHelper.DataColumnModel("区间下限", "LowerRange", (cv, obj) => { var result = ""; if (obj is DividendTrade trade) { result = priceFormatter(trade.LowerRange, trade.LowerRange_After, OtcFormatFlag.umprice); } return result; }), new ExcelHelper.DataColumnModel("区间上限", "UpperRange", (cv, obj) => { var result = ""; if (obj is DividendTrade trade) { result = priceFormatter(trade.UpperRange, trade.UpperRange_After, OtcFormatFlag.umprice); } return result; }), new ExcelHelper.DataColumnModel("敲出期权行权价1", "SpreadStrikeAtKO1", (cv, obj) => { var result = ""; if (obj is DividendTrade trade) { result = priceFormatter(trade.SpreadStrikeAtKO1 ?? double.NaN, trade.SpreadStrikeAtKO1_After ?? double.NaN, OtcFormatFlag.umprice); } return result; }), new ExcelHelper.DataColumnModel("敲出期权行权价2", "SpreadStrikeAtKO", (cv, obj) => { var result = ""; if (obj is DividendTrade trade) { result = priceFormatter(trade.SpreadStrikeAtKO ?? double.NaN, trade.SpreadStrikeAtKO_After ?? double.NaN, OtcFormatFlag.umprice); } return result; }), new ExcelHelper.DataColumnModel("敲入期权行权价1", "SpreadStrikeAtMaturity1", (cv, obj) => { var result = ""; if (obj is DividendTrade trade) { result = priceFormatter(trade.SpreadStrikeAtMaturity1 ?? double.NaN, trade.SpreadStrikeAtMaturity1_After ?? double.NaN, OtcFormatFlag.umprice); } return result; }), new ExcelHelper.DataColumnModel("封顶/封底行权价", "SpreadStrikeAtMaturity", (cv, obj) => { var result = ""; if (obj is DividendTrade trade) { result = priceFormatter(trade.SpreadStrikeAtMaturity ?? double.NaN, trade.SpreadStrikeAtMaturity_After ?? double.NaN, OtcFormatFlag.umprice); } return result; }) }; new ExcelHelper().ListToExcel(dc.ToArray(), ret, "Sheet1", true, out var buffer); return buffer; } private string dateFormatter(dynamic cv, object obj) { var result = ""; if (cv is DateTime dt) { result = dt.ToString("yyyy-MM-dd"); } return result; } private string priceFormatter(double p1, double p2, int precision) { var result = ""; if (!double.IsNaN(p1) && p1 != 0) { result = OtcFormatExtensions.OtcFormatFlex(p1, minDecimals: precision, maxDecimals: precision); } if (!double.IsNaN(p2) && p2 != 0) { result += $"({OtcFormatExtensions.OtcFormatFlex(p2, minDecimals: precision, maxDecimals: precision)})"; } return result; } private string priceFormatter(double p1, double p2, OtcFormatFlag formatFlag) { var result = ""; if (!double.IsNaN(p1) && p1 != 0) { result = OtcFormatExtensions.OtcFormat(p1, formatFlag); } if (!double.IsNaN(p2) && p2 != 0) { result += $"({OtcFormatExtensions.OtcFormat(p2, formatFlag)})"; } return result; } } }