diff --git a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs
index 13dbc964..98691d38 100644
--- a/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs
+++ b/YLErpDAL/Modules/SwapModule/SwapEodPositionService.cs
@@ -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
/// 自动互换观察日信息,用于获取结算日期
/// 当日浮动端EOD持仓
/// 交易扩展信息
- private void DealAutoInterests(List autoInterests, trade td, DateTime settleDate, DateTime? preDealDate, decimal StockEqvNotional, IntervalModel interval, List curEodPositions, trade_extend tradeExtend)
+ ///
+ /// 自动互换(仅利息/预付金,不含分红)
+ ///
+ private void DealAutoInterests(List 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)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);
+ }
+ ///
+ /// 分红独立处理:当天有债券需要分红时,生成独立的分红自动互换事件
+ ///
+ private void DealDividends(List 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 dividendEvents = new List();
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);
}
///
/// 保存自动互换数据信息
@@ -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;
}
///
@@ -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);
diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/incomeSwapTrade.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/incomeSwapTrade.js
index eea6de93..9cdd68fa 100644
--- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/incomeSwapTrade.js
+++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/incomeSwapTrade.js
@@ -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();
diff --git a/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js b/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js
index 7539f08b..21243e1b 100644
--- a/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js
+++ b/YLErpWeb/wwwroot/Scripts/app/swaptrade/unwindSwapTrade.js
@@ -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();