Merge remote-tracking branch 'origin/glms/feature/1.4.2' into glms/feature/1.4.2

This commit is contained in:
tengyufan
2026-06-25 13:55:10 +08:00
3 changed files with 105 additions and 96 deletions
@@ -145,7 +145,10 @@ namespace YLErp.Modules.SwapModule
if (autoInterval != null)
break;
}
DealAutoInterests(autoInterests, td, settleDate, preDealDate, posiLongNotional + posiShortNotional, autoInterval, curEodPosis, tradeExtend);
// 自动互换(仅利息/预付金,不含分红)
DealAutoInterests(autoInterests, td, settleDate, preDealDate, posiLongNotional + posiShortNotional, autoInterval);
// 分红独立处理:只要当天有债券需要分红,则生成分红自动互换,与利息互换无关
DealDividends(curEodPosis, td, settleDate, tradeExtend);
//多空组合判断是否已到到期日且无持仓信息
if (longShort && td.ExerciseDate.Value == settleDate && allPositionQty == 0)
{
@@ -373,114 +376,121 @@ namespace YLErp.Modules.SwapModule
/// <param name="interval">自动互换观察日信息,用于获取结算日期</param>
/// <param name="curEodPositions">当日浮动端EOD持仓</param>
/// <param name="tradeExtend">交易扩展信息</param>
private void DealAutoInterests(List<swap_flow_event> autoInterests, trade td, DateTime settleDate, DateTime? preDealDate, decimal StockEqvNotional, IntervalModel interval, List<eod_swap_position> curEodPositions, trade_extend tradeExtend)
/// <summary>
/// 自动互换(仅利息/预付金,不含分红)
/// </summary>
private void DealAutoInterests(List<swap_flow_event> autoInterests, trade td, DateTime settleDate, DateTime? preDealDate, decimal StockEqvNotional, IntervalModel interval)
{
if (autoInterests.Count == 0 ||curEodPositions == null )
{
return;
}
//生成自动互换事件
if (autoInterests.Count == 0) return;
UnwindData unwindData = new UnwindData();
unwindData.SwapTradeId = td.id;
unwindData.ValueDate = settleDate;
if (preDealDate.HasValue)
{
unwindData.StartDate = preDealDate.Value;
}
else
{
unwindData.StartDate = td.StartDate.Value;
}
unwindData.StartDate = preDealDate ?? td.StartDate.Value;
unwindData.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? 0);
unwindData.PosiNotionalValue = StockEqvNotional;
unwindData.PayDate = settleDate;
// PayDate 统一用派息金额支付日 + 日历调整
var dividendPayDateOffset = tradeExtend?.ExtendObj?.DividendPayDate ?? 1;
// 0到期结算日 1派息日+0 2派息日+1 3派息日+2
var payDays = dividendPayDateOffset > 0 ? dividendPayDateOffset - 1 : 0;
var autoSwapPayDate = QdpCalendarHelper.GetNonHoliday(settleDate.AddDays(payDays));
unwindData.PayDate = autoSwapPayDate;
autoInterests.ForEach(x => x.PayDate = settleDate);
// 给已有利息腿统一赋 PayDate
autoInterests.ForEach(x => x.PayDate = autoSwapPayDate);
// 预付金腿类型列表:初始预付金、追加预付金
var premiumModes = new List<int>() { (int)InterestModeEnum., (int)InterestModeEnum. };
// 分别计算预付金腿和利息腿的金额
var premiumInterests = autoInterests.Where(x => premiumModes.Contains(x.InterestMode)).ToList();
var interestLegs = autoInterests.Where(x => !premiumModes.Contains(x.InterestMode)).ToList();
// 预付金腿金额
decimal premiumTotal = 0;
premiumInterests.ForEach(x =>
{
var ratio = x.InterestDirection == (int)SwapDirectionEnum. ? -1 : 1;
premiumTotal += x.InterestClosePnL * ratio;
});
unwindData.SwapMarginRebatePnl = premiumTotal; // 预付金腿金额
unwindData.SwapMarginRebatePnl = premiumTotal;
// 利息腿金额(总金额减去预付金腿金额)
decimal interestTotal = 0;
interestLegs.ForEach(x =>
{
var ratio = x.InterestDirection == (int)SwapDirectionEnum. ? 1 : -1;
interestTotal += x.InterestClosePnL * ratio;
});
unwindData.SwapCloseAmount = interestTotal; // 利息腿金额
unwindData.SwapCloseAmount = interestTotal;
unwindData.SwapDividendPnl = 0;
unwindData.SwapRealizedPnL = unwindData.SwapCloseAmount + unwindData.SwapMarginRebatePnl;
SaveAutoSwapDeal(td, autoInterests, unwindData, interval);
}
/// <summary>
/// 分红独立处理:当天有债券需要分红时,生成独立的分红自动互换事件
/// </summary>
private void DealDividends(List<eod_swap_position> curEodPositions, trade td, DateTime settleDate, trade_extend tradeExtend)
{
if (curEodPositions == null) return;
var hasDividend = curEodPositions.Any(x => x.PosiDividendSum != 0);
if (!hasDividend) return;
var dividendPayDateOffset = tradeExtend?.ExtendObj?.DividendPayDate ?? 1;
if (dividendPayDateOffset <= 0) return;
var payDays = dividendPayDateOffset - 1;
var dividendPayDate = QdpCalendarHelper.GetNonHoliday(settleDate.AddDays(payDays));
// 处理浮动端待实现分红:将其转为已实现(到期结算日不处理)
List<swap_flow_event> dividendEvents = new List<swap_flow_event>();
decimal dividendTotal = 0;
if (curEodPositions != null && dividendPayDateOffset > 0)
{
foreach (var eodPosi in curEodPositions.Where(x => x.PosiDividendSum != 0))
{
var dividendEvent = new swap_flow_event
{
SwapTradeId = td.id,
SwapTradeNo = td.TradeNumber,
EventType = (int)SwapEventTypeEnum.,
EventReason = "系统操作-自动互换",
EventDate = settleDate,
UnwindDate = settleDate,
PayDate = autoSwapPayDate,
PositionId = eodPosi.PositionId,
UnderlyingCode = eodPosi.UnderlyingCode,
PayDirection = eodPosi.PosiDirection,
PositionType = eodPosi.PositionType,
PositionQty = eodPosi.PosiQuantity,
Quantity = 0,
ContractSize = eodPosi.ContractSize,
TradingAmountAvg = eodPosi.PosiNetPrice,
PosiGrossPrice = eodPosi.PosiGrossPrice,
PosiNetPrice = eodPosi.PosiNetPrice,
MarkClosePnl = eodPosi.PosiDividendSum,
DividendIn = eodPosi.PosiDividendSum,
CloseFee = 0,
TradingFee = 0,
TradingFeePending = 0,
ClientId = td.ClientId,
DataState = (int)SwapFlowDateStateEnum.,
};
dividendEvents.Add(dividendEvent);
dividendTotal += eodPosi.PosiDividendSum;
// 将EOD持仓的待实现分红转为已实现
eodPosi.TdCloseDividend += eodPosi.PosiDividendSum;
eodPosi.TdPosiDividend = 0;
eodPosi.RealizedDividend += eodPosi.PosiDividendSum;
eodPosi.PosiDividendSum = 0;
}
foreach (var eodPosi in curEodPositions.Where(x => x.PosiDividendSum != 0))
{
var dividendEvent = new swap_flow_event
{
SwapTradeId = td.id,
SwapTradeNo = td.TradeNumber,
EventType = (int)SwapEventTypeEnum.,
EventReason = "系统操作-分红",
EventDate = settleDate,
UnwindDate = settleDate,
PayDate = dividendPayDate,
PositionId = eodPosi.PositionId,
UnderlyingCode = eodPosi.UnderlyingCode,
PayDirection = eodPosi.PosiDirection,
PositionType = eodPosi.PositionType,
PositionQty = eodPosi.PosiQuantity,
Quantity = 0,
ContractSize = eodPosi.ContractSize,
TradingAmountAvg = eodPosi.PosiNetPrice,
PosiGrossPrice = eodPosi.PosiGrossPrice,
PosiNetPrice = eodPosi.PosiNetPrice,
MarkClosePnl = eodPosi.PosiDividendSum,
DividendIn = eodPosi.PosiDividendSum,
CloseFee = 0,
TradingFee = 0,
TradingFeePending = 0,
ClientId = td.ClientId,
DataState = (int)SwapFlowDateStateEnum.,
};
dividendEvents.Add(dividendEvent);
dividendTotal += eodPosi.PosiDividendSum;
eodPosi.TdCloseDividend += eodPosi.PosiDividendSum;
eodPosi.TdPosiDividend = 0;
eodPosi.RealizedDividend += eodPosi.PosiDividendSum;
eodPosi.PosiDividendSum = 0;
eodPosi.PosiProfitSum -= eodPosi.TdCloseDividend;
//互换持仓价值要去掉已实现的
eodPosi.SwapPositionValue -= eodPosi.PosiDividendSum;
//已实现盈亏要加上已实现的
eodPosi.RealizedPnl += eodPosi.PosiDividendSum;
}
// 分红收支加入总实现盈亏
UnwindData unwindData = new UnwindData();
unwindData.SwapTradeId = td.id;
unwindData.ValueDate = settleDate;
unwindData.StartDate = td.StartDate.Value;
unwindData.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? 0);
unwindData.PosiNotionalValue = curEodPositions.Sum(x => x.PosiNotionalValue);
unwindData.PayDate = dividendPayDate;
unwindData.SwapDividendPnl = dividendTotal;
unwindData.SwapCloseAmount = 0;
unwindData.SwapMarginRebatePnl = 0;
unwindData.SwapRealizedPnL = dividendTotal;
// 总实现盈亏
unwindData.SwapRealizedPnL = unwindData.SwapCloseAmount + unwindData.SwapMarginRebatePnl + dividendTotal;
SaveAutoSwapDeal(td, autoInterests, unwindData, interval, dividendEvents);
SaveAutoSwapDeal(td,null , unwindData,null, dividendEvents:dividendEvents);
}
/// <summary>
/// 保存自动互换数据信息
@@ -504,7 +514,7 @@ namespace YLErp.Modules.SwapModule
// 预付金腿:单独插入一条资金记录(系统操作_预付金返息)
if (unwindData.SwapMarginRebatePnl != 0)
{
AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapMarginRebatePnl), ClientCashInCashOut._预付金返息, unwindData.ValueDate);
clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapMarginRebatePnl), ClientCashInCashOut._预付金返息, unwindData.ValueDate);
}
// 分红:使用派息支付日偏移记录资金记录
@@ -513,16 +523,21 @@ namespace YLErp.Modules.SwapModule
var dividendPayDate = (dividendEvents != null && dividendEvents.Count > 0)
? dividendEvents.First().PayDate.Value
: unwindData.ValueDate;
AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapDividendPnl), ClientCashInCashOut._互换, dividendPayDate);
clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapDividendPnl), ClientCashInCashOut._互换, dividendPayDate);
}
string data = JsonConvert.SerializeObject(unwindData);
var swapEvent = new SwapEventService(this).AddSwapEventDate(unwindData.ValueDate, unwindData.SwapTradeId, (int)SwapEventTypeEnum., data, clientCashId, true, "系统操作-自动互换");//将互换总额存入事件
flowEvents.ForEach(x =>
if (flowEvents!=null)
{
x.EventId = swapEvent.id;
DbContext.swap_flow_event.Add(x);
});
flowEvents.ForEach(x =>
{
x.EventId = swapEvent.id;
DbContext.swap_flow_event.Add(x);
});
UpdateInitalPostion(flowEvents, td.id);
}
// 保存分红事件
if (dividendEvents != null)
{
@@ -531,8 +546,8 @@ namespace YLErp.Modules.SwapModule
x.EventId = swapEvent.id;
DbContext.swap_flow_event.Add(x);
});
UpdateInitalPostion(dividendEvents, td.id);
}
UpdateInitalPostion(flowEvents, td.id);
return swapEvent.id;
}
/// <summary>
@@ -1450,14 +1465,12 @@ namespace YLErp.Modules.SwapModule
curretEod.UnderlyingMarketValue = curretEod.UnderlyingPrice * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio;
curretEod.PosiMtmPnL = (curretEod.UnderlyingPrice - curretEod.PosiGrossPrice) * curretEod.PosiQuantity * curretEod.ContractSize * shortRatio * directionRatio;
curretEod.TdPosiDividend = 0;
// 有互换事件时,分红已全量结算,不再查分红,TdPosiDividend和PosiDividendSum都归0
var hasSwapEvent = unwindEvents.Any(e => e.EventType == (int)SwapFlowEventTypeEnum. || e.EventType == (int)SwapFlowEventTypeEnum.);
if (!hasSwapEvent && valueDate > td.StartDate.Value && (curretEod.PosiQuantity > 0))
// 分红与互换无关,只要持仓>0且起始日早于当前日,正常计算当日分红
if (valueDate > td.StartDate.Value && (curretEod.PosiQuantity > 0))
{
decimal tax = um.ValueAddedTax ?? 0;
BondPaymentService bondPaymentService = new BondPaymentService(UserInfo);
decimal payment = bondPaymentService.CalcPayment(curretEod.UnderlyingCode, eod.ValueDate, valueDate, curretEod.PosiQuantity, shortRatio, directionRatio);
// 考虑增值税
curretEod.TdPosiDividend = Math.Round(payment / (1 + tax) * (1 - tax), 2);
}
curretEod.RealizedMtmPnL = eod.RealizedMtmPnL + curretEod.TdCloseMtmPnl;
@@ -1466,16 +1479,12 @@ namespace YLErp.Modules.SwapModule
curretEod.RealizedPnl = eod.RealizedPnl + curretEod.TdCloseMtmPnl;
curretEod.PosiStatus = curretEod.PosiQuantity == 0 ? 1 : 0;
var closeQty = unwindEvents.Where(x => x.EventType == (int)SwapFlowEventTypeEnum.).ToList().Sum(s => s.Quantity);
// 当日浮动端平仓盈亏·分红
// 当日浮动端平仓盈亏·分红(仅来自平仓事件中已实现的分红)
curretEod.TdCloseDividend = unwindEvents.Sum(e => e.DividendIn);
curretEod.RealizedDividend = curretEod.RealizedDividend + curretEod.TdCloseDividend;
// 浮动端待实现收益·分红(有互换事件时全量结算归0;否则用当前持仓从起始日重算)
if (hasSwapEvent)
{
curretEod.PosiDividendSum = 0;
}
else if (curretEod.PosiQuantity > 0)
// 分红与互换解耦:持仓>0时从起始日重算待实现分红,不再受互换事件影响
if (curretEod.PosiQuantity > 0)
{
decimal tax = um.ValueAddedTax ?? 0;
BondPaymentService bondPaymentService = new BondPaymentService(UserInfo);
@@ -184,7 +184,7 @@ const vue = new Vue({
let totalDividend = parseFloat(thisObj.deal.PositionQty) * resp.obj.totalInterest * ratio * floatRatio;
let consumedDividend = parseFloat(resp.obj.consumedDividend ?? 0);
// 互换是全量消费,consumedDividend>0 表示分红已被当天互换消费,归0
thisObj.floatPosition.DividendIn = Math.abs(consumedDividend) > 0 ? 0 : parseFloat(totalDividend.toFixed(2));
thisObj.floatPosition.DividendIn = Math.abs(consumedDividend) > 0 ? parseFloat((totalDividend - consumedDividend).toFixed(2)) : parseFloat(totalDividend.toFixed(2));
thisObj.floatPosition.DividendPending = 0;
thisObj.calcFloatClosePnl();
thisObj.dataFormat();
@@ -283,7 +283,7 @@ const vue = new Vue({
let totalDividend = parseFloat(thisObj.deal.CloseQty) * resp.obj.totalInterest * ratio * floatRatio;
let consumedDividend = parseFloat(resp.obj.consumedDividend ?? 0);
// 互换是全量消费,consumedDividend>0 表示分红已被当天互换消费,归0
thisObj.floatPosition.DividendIn = Math.abs(consumedDividend) > 0 ? 0 : parseFloat(totalDividend.toFixed(2));
thisObj.floatPosition.DividendIn = Math.abs(consumedDividend) > 0 ? parseFloat((totalDividend - consumedDividend).toFixed(2)) : parseFloat(totalDividend.toFixed(2));
var posiQty = parseFloat(thisObj.floatPosition.Quantity) - parseFloat(thisObj.deal.CloseQty);
thisObj.floatPosition.DividendPending = parseFloat((posiQty * resp.obj.totalInterest * ratio * floatRatio).toFixed(2));
thisObj.calcFloatClosePnl();