- 重命名 MergeNonZeroDividendValues 方法为 MergeDividendValues - 修改合并逻辑,数值字段(包括 0)都作为有效覆盖值进行合并 - 更新注释说明合并规则的变化 - 添加零现金分红覆盖现有值的单元测试 - 修复数据库记录合并时的字段覆盖逻辑
2103 lines
127 KiB
C#
2103 lines
127 KiB
C#
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 MergeDividendValues(ex_dividend_info target, ex_dividend_info source)
|
||
{
|
||
if (target == null)
|
||
{
|
||
throw new ArgumentNullException(nameof(target));
|
||
}
|
||
if (source == null)
|
||
{
|
||
throw new ArgumentNullException(nameof(source));
|
||
}
|
||
|
||
// 数值字段(包括 0)都是有效的覆盖值。
|
||
target.GiveCashAmount = source.GiveCashAmount;
|
||
target.GiveShareAmount = source.GiveShareAmount;
|
||
target.RationedSharesAmount = source.RationedSharesAmount;
|
||
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;
|
||
}
|
||
|
||
MergeDividendValues(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;
|
||
MergeDividendValues(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;
|
||
}
|
||
}
|
||
}
|