#EQD-6089 国联民生-利息多算一天:修复部分平仓利息计算错误问题

This commit is contained in:
吴方海
2026-06-01 17:00:44 +08:00
parent 1932a2e757
commit 30b6c6a429
2 changed files with 59 additions and 51 deletions
@@ -333,7 +333,7 @@ namespace YLErp.Modules.SwapModule
var grossPrice = realPostitions.Where(x => x.PosiDirection > 0).FirstOrDefault()?.PosiGrossPrice;
var closeList = DbContext.swap_flow_event.Where(x => x.SwapTradeId == tradeId && x.UnwindDate == unwindDate && eventTypes.Contains(x.EventType) && x.DataState == (int)SwapFlowDateStateEnum.).ToList();
bool tdClose = closeList.Count > 0;
interests = GetInterests(td, tradeExtend, valueDate, unwindDate, lastEodPositions, positions, stockEqvNotional, posiLongNotionalValue, posiShortNotionalValue, posiNotionalValue, closePercent, eventType, tdClose, false, grossPrice ?? 0, orginPv, true, false, closeList);
interests = GetInterests(td, tradeExtend, valueDate, unwindDate, lastEodPositions, positions, stockEqvNotional, posiLongNotionalValue, posiShortNotionalValue, posiNotionalValue, closePercent, eventType, tdClose, false, grossPrice ?? 0, orginPv, true, false,false, closeList);
return interests;
}
/// <summary>
@@ -372,6 +372,7 @@ namespace YLErp.Modules.SwapModule
decimal orginPv,
bool add = false,
bool settment = true,
bool newCalcLast= false,
List<swap_flow_event> closeList = null)
{
List<swap_flow_event> interests = new List<swap_flow_event>();
@@ -409,7 +410,7 @@ namespace YLErp.Modules.SwapModule
else
{
// 盘中互换场景,使用 CalcUnwindInterest
interests.Add(CalcUnwindInterest(td, valueDate, endDate, positionClone, rate, floatRate, posiPrincipal, closePrincipal, newClosePercent, annualDays, preEodPosition, eventType, add, swap, orginPv, calcFirst, calcLast));
interests.Add(CalcUnwindInterest(td, valueDate, endDate, positionClone, rate, floatRate, posiPrincipal, closePrincipal, newClosePercent, annualDays, preEodPosition, eventType, add, swap, orginPv, calcFirst, calcLast||newCalcLast));
}
}
//当日有平仓或互换记录时,需要把平仓或互换已经结算的利息从计算结果中扣除,避免重复计算
@@ -711,8 +712,8 @@ namespace YLErp.Modules.SwapModule
decimal interest = interestProfitSum * closePercent;
decimal tdinterest = interestProfitSum * closePercent;
int interestPeriod = position.interest_rest_days ?? 1;
decimal dynomicPrincipal = principal;
decimal tdDynomicPrincipal = posiPrincipal;
decimal dynomicPrincipal = (preEodPosition.TdInterestPrincipal + posiPrincipal - orginPv);
decimal tdDynomicPrincipal = dynomicPrincipal;
var calcDays = (endDate - startDate).Days;
double floatRate = Convert.ToDouble(floateRate);
for (int i = 0; i <= calcDays; i++)
@@ -727,8 +728,8 @@ namespace YLErp.Modules.SwapModule
// 复利时:利息并入本金
if (compoundInterest)
{
dynomicPrincipal = dynomicPrincipal + interest;
tdDynomicPrincipal = tdDynomicPrincipal + interest;
dynomicPrincipal = principal + interest;
tdDynomicPrincipal = principal + interest;
}
// 获取新的浮动利率
if (!string.IsNullOrEmpty(position.FloatRateUnderlyingCode))
@@ -751,9 +752,9 @@ namespace YLErp.Modules.SwapModule
}
else
{
flowEvent.InterestPrincipal = (preEodPosition.TdInterestPrincipal + posiPrincipal - orginPv) * closePercent;
flowEvent.InterestPrincipal = tdDynomicPrincipal * closePercent;
tdDynomicPrincipal = flowEvent.InterestPrincipal;
TdInterestPrincipal = (preEodPosition.TdInterestPrincipal + posiPrincipal - orginPv);
TdInterestPrincipal = tdDynomicPrincipal;
}
flowEvent.FloatRate = Convert.ToDecimal(floatRate);
var interest1 = flowEvent.InterestPrincipal * (flowEvent.InterestRate + Convert.ToDecimal(floatRate));
@@ -62,7 +62,7 @@ namespace YLErp.Modules.SwapModule
public void SwapPositionCompose(DateTime settleDate, DateTime preSettleDate, IEnumerable<int> ClientIds)
{
var dateStr = settleDate.ToString("yyyy-MM-dd");
Log.Info("SwapPositionCompose:"+"settleDate:" + settleDate+ " preSettleDate:"+ preSettleDate+ " ClientIds:"+JsonHelper.Serialize(ClientIds));
Log.Info("SwapPositionCompose:" + "settleDate:" + settleDate + " preSettleDate:" + preSettleDate + " ClientIds:" + JsonHelper.Serialize(ClientIds));
var tradePredicate = PredicateBuilder.Create<trade>(n => n.ValidState != ConsGlobal.InValid
&& n.TradeType == "收益互换"
&& n.TradeDate <= settleDate
@@ -221,32 +221,32 @@ namespace YLErp.Modules.SwapModule
decimal orginPv)
{
Log.Info("===================处理利息腿归档====================");
// 添加详细的参数验证日志
Log.Info($"[DealInterests] 参数验证 - settleDate: {settleDate:yyyy-MM-dd}, td.id: {td?.id}, td.TradeNumber: {td?.TradeNumber}");
Log.Info($"[DealInterests] 参数验证 - interestList.Count: {interestList?.Count ?? 0}, eodPositions.Count: {eodPositions?.Count ?? 0}, todyEodPositions.Count: {todyEodPositions?.Count ?? 0}");
Log.Info($"[DealInterests] 参数验证 - flowEvents.Count: {flowEvents?.Count ?? 0}, autoInterests.Count: {autoInterests?.Count ?? 0}");
Log.Info($"[DealInterests] 参数验证 - posiLongNational: {posiLongNational}, posiShortNational: {posiShortNational}, closeNational: {closeNational}, grossPrice: {grossPrice}, orginPv: {orginPv}");
// 验证关键参数
if (td == null)
{
Log.Info("[DealInterests] 参数验证: td (trade) 为 null");
throw new ArgumentNullException(nameof(td), "交易对象不能为null");
}
if (interestList == null)
{
Log.Info($"[DealInterests] 参数验证: interestList 为 null, td.id: {td.id}");
throw new ArgumentNullException(nameof(interestList), "利息腿列表不能为null");
}
if (flowEvents == null)
{
Log.Info($"[DealInterests] 参数验证: flowEvents 为 null, td.id: {td.id}");
throw new ArgumentNullException(nameof(flowEvents), "流水事件列表不能为null");
}
if (autoInterests == null)
{
Log.Info($"[DealInterests] 参数验证: autoInterests 为 null, td.id: {td.id}");
@@ -390,14 +390,14 @@ namespace YLErp.Modules.SwapModule
}
unwindData.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? 0);
unwindData.PosiNotionalValue = StockEqvNotional;
// 预付金腿类型列表:初始预付金、追加预付金
var premiumModes = new List<int>() {(int)InterestModeEnum., (int)InterestModeEnum. };
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 =>
@@ -406,7 +406,7 @@ namespace YLErp.Modules.SwapModule
premiumTotal += x.InterestClosePnL * ratio;
});
unwindData.SwapMarginRebatePnl = premiumTotal; // 预付金腿金额
// 利息腿金额(总金额减去预付金腿金额)
decimal interestTotal = 0;
interestLegs.ForEach(x =>
@@ -415,10 +415,10 @@ namespace YLErp.Modules.SwapModule
interestTotal += x.InterestClosePnL * ratio;
});
unwindData.SwapCloseAmount = interestTotal; // 利息腿金额
// 总实现盈亏
unwindData.SwapRealizedPnL = unwindData.SwapCloseAmount + unwindData.SwapMarginRebatePnl;
SaveAutoSwapDeal(td, autoInterests, unwindData, interval);
}
/// <summary>
@@ -432,7 +432,7 @@ namespace YLErp.Modules.SwapModule
//td.UnWindDate = unwindData.ValueDate;
//优先使用 interval.SettlementDate 作为资金记录发生日期,如果没有则使用 ValueDate
var cashHappenDate = interval?.SettlementDate ?? unwindData.ValueDate;
int clientCashId = 0;
// 利息腿:插入资金记录(使用系统操作_互换)
if (unwindData.SwapCloseAmount != 0)
@@ -445,7 +445,7 @@ namespace YLErp.Modules.SwapModule
{
AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapMarginRebatePnl), ClientCashInCashOut._预付金返息, unwindData.ValueDate);
}
string data = JsonConvert.SerializeObject(unwindData);
var swapEvent = new SwapEventService(this).AddSwapEventDate(unwindData.ValueDate, unwindData.SwapTradeId, (int)SwapEventTypeEnum., data, clientCashId, true, "系统操作-自动互换");//将互换总额存入事件
flowEvents.ForEach(x =>
@@ -731,7 +731,7 @@ namespace YLErp.Modules.SwapModule
newEodPayPosition.InterestFeeSum = eodPayPosition.InterestFeeSum + newEodPayPosition.TdInterestFee - newEodPayPosition.TdCloseInterestFee;
newEodPayPosition.InterestProfitSum = newEodPayPosition.InterestIncomeSum + newEodPayPosition.InterestFeeSum;
//持仓价值
newEodPayPosition.SwapPositionValue = newEodPayPosition.InterestProfitSum * ratio + newEodPayPosition.PosiProfitSum;
newEodPayPosition.SwapPositionValue = newEodPayPosition.InterestProfitSum * ratio + newEodPayPosition.PosiProfitSum;
//累计已实现
newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio;
@@ -759,41 +759,41 @@ namespace YLErp.Modules.SwapModule
private List<swap_flow_event> SaveAutoEodInterestPosition(eod_swap_position eodPayPosition, eod_swap_position newEodPayPosition, swap_position position, trade td, DateTime valueDate, IntervalModel interval, eod_swap lastEodSwap, decimal posiLongNotional, decimal posiShortNational, decimal grossPrice, decimal orginPv)
{
Log.Info($"[SaveAutoEodInterestPosition] 开始执行 - valueDate: {valueDate:yyyy-MM-dd}, td.id: {td?.id}, position.id: {position?.id}");
// 详细的参数验证
if (td == null)
{
Log.Info("[SaveAutoEodInterestPosition] 参数验证: td (trade) 为 null");
throw new ArgumentNullException(nameof(td), "交易对象不能为null");
}
if (position == null)
{
Log.Info($"[SaveAutoEodInterestPosition] 参数验证: position 为 null, td.id: {td.id}");
throw new ArgumentNullException(nameof(position), "持仓对象不能为null");
}
if (interval == null)
{
Log.Info($"[SaveAutoEodInterestPosition] 参数验证: interval 为 null, td.id: {td.id}, position.id: {position.id}");
throw new ArgumentNullException(nameof(interval), "观察日信息不能为null");
}
if (td.trade_extend == null)
{
Log.Info($"[SaveAutoEodInterestPosition] 参数验证: td.trade_extend 为 null, td.id: {td.id}, td.TradeNumber: {td.TradeNumber}");
throw new ArgumentNullException("td.trade_extend", "交易扩展信息不能为null");
}
Log.Info($"eodPayPosition is {JsonHelper.Serialize(eodPayPosition, false)},newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}");
// 验证 ExtendObj
if (td.trade_extend.ExtendObj == null)
{
Log.Info($"[SaveAutoEodInterestPosition] 参数验证: td.trade_extend.ExtendObj 为 null, td.id: {td.id}");
throw new ArgumentNullException("td.trade_extend.ExtendObj", "交易扩展对象不能为null");
}
var tradeExtend = td.trade_extend.ExtendObj;
decimal oriPosiNotionalValue = posiLongNotional + posiShortNational;
decimal posiNotionalValue = oriPosiNotionalValue;
@@ -902,20 +902,18 @@ namespace YLErp.Modules.SwapModule
Log.Info($"eodPayPosition is {JsonHelper.Serialize(eodPayPosition, false)},newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}");
var tradeExtend = td.trade_extend.ExtendObj;
decimal oriPosiNotionalValue = posiLongNotional + posiShortNational + closeNational;
decimal posiNotionalValue = oriPosiNotionalValue;
decimal posiNotionalValue = posiLongNotional + posiShortNational;
decimal ratio = position.InterestDirection == (int)SwapDirectionEnum. ? 1m : -1m;//收取为正,支付为负
if (marginTypes.Contains(position.InterestMode))
{
ratio = -ratio;
}
if (eodPayPosition == null)
{
eodPayPosition = new eod_swap_position();
eodPayPosition.ClientId = td.ClientId;
eodPayPosition.SwapTradeId = td.id;
eodPayPosition.PosiStartDate = td.StartDate.Value;
eodPayPosition.PosiMatuirityDate = td.ExerciseDate.Value;
}
var lastInterestIncomeSum = eodPayPosition.InterestIncomeSum;
eodPayPosition = new eod_swap_position();
eodPayPosition.ClientId = td.ClientId;
eodPayPosition.SwapTradeId = td.id;
eodPayPosition.PosiStartDate = td.StartDate.Value;
eodPayPosition.PosiMatuirityDate = td.ExerciseDate.Value;
if (newEodPayPosition == null)
{
newEodPayPosition = eodPayPosition.Clone();
@@ -926,6 +924,10 @@ namespace YLErp.Modules.SwapModule
{
orginPv = eodPayPosition.InterestPrincipalFix;
}
else
{
orginPv = posiNotionalValue;
}
decimal closePercent = oriPosiNotionalValue == 0 ? 0 : closeNational / oriPosiNotionalValue;
var eventType = autoSwap ? (int)SwapEventTypeEnum. : (int)SwapEventTypeEnum.;
bool longShort = td.StructureType == ClientMarginTypeEnum..ToString();
@@ -941,9 +943,9 @@ namespace YLErp.Modules.SwapModule
positions.Add(position);
List<eod_swap_position> preEodPositions = new List<eod_swap_position>();
preEodPositions.Add(eodPayPosition);
var interests = new SwapDealService(this).GetInterests(td, td.trade_extend, valueDate, valueDate, preEodPositions, positions, posiNotionalValue, posiLongNotional, posiShortNational, closeNational, 1, eventType, false, true, grossPrice, orginPv, true);
var interests = new SwapDealService(this).GetInterests(td, td.trade_extend, valueDate, valueDate, preEodPositions, positions, posiNotionalValue, posiLongNotional, posiShortNational, closeNational, 1, eventType, false, true, grossPrice, orginPv, true, settment: false, newCalcLast: true);
decimal TdInterestAmount = interests.Sum(x => x.TdInterestAmount);
decimal InterestAmount = interests.Sum(x => x.InterestAmount);
newEodPayPosition.ValueDate = valueDate;
newEodPayPosition.PositionId = position.id;
UpdateDbOption(newEodPayPosition);
@@ -964,7 +966,7 @@ namespace YLErp.Modules.SwapModule
newEodPayPosition.interest_rest_days = position.interest_rest_days;
newEodPayPosition.interest_rule = position.interest_rule;
//利息端估值用信息
newEodPayPosition.TdInterestPrincipal = interestModes.Contains(position.InterestMode) ? eodPayPosition.InterestPrincipalFix : posiLongNotional + posiShortNational;
newEodPayPosition.TdInterestPrincipal = interests.Count > 0 ? interests.First().InterestPrincipal : 0;
if (interval != null)
{
newEodPayPosition.TdInterestRate = interval.Rate;
@@ -977,8 +979,13 @@ namespace YLErp.Modules.SwapModule
newEodPayPosition.TdInterestFee = flowEvents.Sum(s => s.InterestFee);
newEodPayPosition.TdCloseInterestFee = newEodPayPosition.TdInterestFee;
newEodPayPosition.TdCloseInterest = flowEvents.Sum(s => s.InterestClosePnL);
newEodPayPosition.TdInterestIncome = TdInterestAmount * (1 - closePercent);
Log.Info($"InterestIncomeSum is {eodPayPosition.InterestIncomeSum},TdInterestIncome is {newEodPayPosition.TdInterestIncome}" +
var intersetAcmount = newEodPayPosition.TdInterestPrincipal*(newEodPayPosition.TdInterestRate + newEodPayPosition.FloatRate);
if (position.IsAnnualized)
{
intersetAcmount /= tradeExtend.AnnualDays;
}
newEodPayPosition.TdInterestIncome = intersetAcmount;
Log.Info($"InterestIncomeSum is {lastInterestIncomeSum},TdInterestIncome is {newEodPayPosition.TdInterestIncome}" +
$",TdCloseInterest is {newEodPayPosition.TdCloseInterest}");
Log.Info($"InterestFeeSum is {eodPayPosition.InterestFeeSum},TdInterestFee is {newEodPayPosition.TdInterestFee}" +
$",TdCloseInterestFee is {newEodPayPosition.TdCloseInterestFee}");
@@ -988,7 +995,7 @@ namespace YLErp.Modules.SwapModule
}
else
{
newEodPayPosition.InterestIncomeSum = eodPayPosition.InterestIncomeSum + newEodPayPosition.TdInterestIncome - newEodPayPosition.TdCloseInterest * ratio;
newEodPayPosition.InterestIncomeSum = InterestAmount;
}
//持仓内容-利息腿-损益统计(本方视角)
newEodPayPosition.InterestFeeSum = eodPayPosition.InterestFeeSum + newEodPayPosition.TdInterestFee - newEodPayPosition.TdCloseInterestFee;
@@ -1001,9 +1008,9 @@ namespace YLErp.Modules.SwapModule
Log.Info($"InterestFeeSum is {eodPayPosition.InterestFeeSum},TdInterestFee is {newEodPayPosition.TdInterestFee}" +
$",TdCloseInterestFee is {newEodPayPosition.TdCloseInterestFee}");
//累计已实现
newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio ;
newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio;
newEodPayPosition.RealizedInterestFee = eodPayPosition.RealizedInterestFee + newEodPayPosition.TdCloseInterestFee;
newEodPayPosition.RealizedPnl = newEodPayPosition.RealizedInterest + newEodPayPosition.RealizedInterestFee;;
newEodPayPosition.RealizedPnl = newEodPayPosition.RealizedInterest + newEodPayPosition.RealizedInterestFee; ;
var currencyRate = new EodCurrencyRateService(UserInfo).GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate
, seekPreday: true, currencyRateType: position.InterestDirection == (int)SwapDirectionEnum. ? CurrencyRateType.Buy : CurrencyRateType.Sell);
newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate);
@@ -1040,7 +1047,7 @@ namespace YLErp.Modules.SwapModule
eodPayPosition.ClientId = td.ClientId;
eodPayPosition.SwapTradeId = td.id;
//eodPayPosition.PositionId = position.id; 为了算利息时找不到给日期重新赋值
eodPayPosition.InterestMode = position.InterestMode;
eodPayPosition.InterestPrincipalFix = position.InterestPrincipalFix;
eodPayPosition.InterestRateDefault = position.InterestRateDefault;
@@ -1123,7 +1130,7 @@ namespace YLErp.Modules.SwapModule
newEodPayPosition.InterestFeeSum = eodPayPosition.InterestFeeSum + newEodPayPosition.TdInterestFee - newEodPayPosition.TdCloseInterestFee;
newEodPayPosition.InterestProfitSum = newEodPayPosition.InterestIncomeSum + newEodPayPosition.InterestFeeSum;
//持仓价值
newEodPayPosition.SwapPositionValue = newEodPayPosition.InterestProfitSum * ratio + newEodPayPosition.PosiProfitSum;
newEodPayPosition.SwapPositionValue = newEodPayPosition.InterestProfitSum * ratio + newEodPayPosition.PosiProfitSum;
//累计已实现
newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio;
@@ -2055,7 +2062,7 @@ namespace YLErp.Modules.SwapModule
{
eventDate = QdpCalendarHelper.GetNonHoliday(eventDate.AddDays(tradeExtend.ExtendObj.SettlementRules));
}
item.DayCount = Math.Max(0,(eventDate - item.position.PosiStartDate).Days + 1);
item.DayCount = Math.Max(0, (eventDate - item.position.PosiStartDate).Days + 1);
//item.position.PosiProfitSum += item.position.VTradingFee-item.position.PosiFeePending;
SetClientEodPosition(item.position);
//item.position.PosiProfitSum += item.TradingFee;