Files
zszq-trs/YLErpDAL/Modules/TradeModule/DealModule/DividendService.cs
T
张名锐 f7766fb723 fix(dividend): 公司行为优化
- 债券付息查询/BondPayment/BondPaymentList这个界面 不再查询公司行为除权表

- 公司行为事件的说明 格式化一下变为:调整前:名义本金:XXXX  期初标的价格:XXXX 持仓数量:XXXX
调整后:名义本金:XXXX  期初标的价格:XXXX 持仓数量:XXXX

- 报错信息修改:当某个标的的除权除息数据仍有交易引用时
则修改标的公司行为数据时则提示“不可修改,有交易【交易编号】使用了该条除权除息数据”。
2026-08-25 15:39:01 +08:00

2121 lines
128 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
{
/// <summary>
/// 除权除息操作(请使用正常英文单词)
/// </summary>
public class DividendService : YLBaseService
{
public DividendService(OptUserInfo userInfo) : base(userInfo)
{
}
public DividendService(YLBaseService baseService) : base(baseService)
{
}
/// <summary>
/// 场内交易除权除息
/// </summary>
/// <param name="settleDate">除权日</param>
/// <param name="positions">持仓列表</param>
public List<BodTradePosition> Execute(DateTime settleDate, IEnumerable<EodTradePosition> positions)
{
var result = new List<BodTradePosition>();
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;
}
/// <summary>
/// 场外交易除权除息
/// </summary>
/// <param name="settleDate">除权日</param>
/// <param name="trades">交易列表</param>
/// <param name="saveToDb">是否将结果保存到trade表</param>
/// <returns></returns>
public List<bod_trade> Execute(DateTime settleDate, IEnumerable<trade> trades, out List<trade> useSaveTrades, out List<underlying_manager> useSaveUndedrlyings)
{
useSaveTrades = new List<trade>();
useSaveUndedrlyings = new List<underlying_manager>();
var result = new List<bod_trade>();
var dict = GetExDividendQuery(settleDate)
.ToDictionary(K => K.UnderlyingId, V => V);
var tradeIds = trades.Select(O => O.id);
var dividendRatioDict = new DbRecordChangesService<TradeChanges>(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<UnderlyingChanges>(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<List<BasketUnderlyingItem>>(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<UnderlyingChanges>(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<List<BasketUnderlyingItem>>(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;
}
/// <summary>
///
/// </summary>
public static List<TradeChanges> 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<TradeChanges>
{
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;
}
/// <summary>
///
/// </summary>
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;
}
}
/// <summary>
/// 获取除权价
/// </summary>
/// <param name="closePrice">除权日收盘价</param>
/// <param name="info">除权除息配置</param>
/// <param name="overrideDividendRatio">除权除息系数</param>
/// <returns></returns>
public double? GetPrice(double? closePrice, ex_dividend_info info, double? overrideDividendRatio = null)
{
if (!closePrice.HasValue) { return closePrice; }
return GetPrice(closePrice.Value, info, overrideDividendRatio);
}
/// <summary>
/// 获取除权价
/// </summary>
/// <param name="pirce">除权日收盘价</param>
/// <param name="info">除权除息配置</param>
/// <returns></returns>
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);
}
/// <summary>
/// 获取除权除息系数
/// </summary>
/// <param name="info"></param>
/// <returns></returns>
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; }
}
/// <summary>
/// 按 Excel 公式计算公司行为的除权系数。
/// GiveShareAmount 只表示每 10 份的送股数量,Split 表示独立的拆/合股倍数;
/// Split 为空按 1 兼容历史记录。TRS Stock/Fund 使用 PriceRatio 同时调整期初价格
/// 和持仓数量,不再维护独立的旧数量系数。
/// <para>
/// 现金分红不参与 TRS Stock/Fund 的期初价格公司行为系数;现金权益由既有分红流水单独处理。
/// 本方法只返回系数,不修改持仓,也不判断公司行动是否已经执行;幂等边界由调用方保证。
/// </para>
/// </summary>
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;
}
/// <summary>
/// 将系统配置中的百分数税率转换为公司行为公式使用的小数税率。
/// 例如配置 13 表示 13%,返回 0.13;送股系数不使用该税率,只有现金分红的税后金额使用。
/// </summary>
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;
}
/// <summary>
/// 获取除权后持仓量
/// </summary>
/// <param name="amount">持仓数量</param>
/// <param name="info">除权除息配置</param>
/// <returns></returns>
public double? GetPositionAmount(double? amount, ex_dividend_info info)
{
if (!amount.HasValue) { return amount; }
return GetPositionAmount(amount.Value, info);
}
/// <summary>
/// 获取除权后持仓量
/// </summary>
/// <param name="amount">持仓数量</param>
/// <param name="info">除权除息配置</param>
/// <returns></returns>
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<ex_dividend_info> GetExDividendQuery(DateTime valueDate)
{
// 该查询沿用作业的“日期已归一化”约定,要求 valueDate 与存量 ExDividendDate
// 同为当天 00:00;自然日业务键的时分秒兼容由保存路径 FindExDividendByBusinessKey 负责。
return DbContext.ex_dividend_info
.Where(O => O.ValidStatus && O.ExDividendDate == valueDate);
}
public IQueryable<ex_dividend_info> GetExDividendQuery(DateTime dateStart, DateTime dateEnd)
{
return DbContext.ex_dividend_info
.Where(O => O.ValidStatus && O.ExDividendDate >= dateStart && O.ExDividendDate <= dateEnd);
}
public IEnumerable<ex_dividend_info> 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<ex_dividend_info> 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<ex_dividend_info>();
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<ex_dividend_info> 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<int, (int UnderlyingId, DateTime ExDividendDate)>();
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<eod_stock_price> 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<DividendService>().Error(ex);
}
return false;
}
/// <summary>
/// 检查除权除息记录有没有被执行
/// </summary>
/// <param name="info"></param>
/// <returns></returns>
public bool checkDividendInfoExecuteStatus(ex_dividend_info info)
{
return !string.IsNullOrWhiteSpace(GetDividendInfoExecutingTradeNumber(info));
}
/// <summary>
/// 返回仍在引用已执行公司行为的交易编号;无引用时返回空字符串。
/// </summary>
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<DividendTrade> QueryDividendTrade(DividendTradeReq req)
{
TradeChanges changes = null;
var result = new List<DividendTrade>();
var predicate = PredicateBuilder.Create<trade>(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<string, ex_dividend_info>();
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<TradeChanges>(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<ExcelHelper.DataColumnModel>
{
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<DividendTrade>(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;
}
}
}