#EQD-5718 【缺陷转需求】-国联民生-利息端计息方式与结算规则扩充

This commit is contained in:
吴方海
2026-05-08 15:09:58 +08:00
parent 689616820a
commit 95686f4c4d
14 changed files with 2227 additions and 242 deletions
+299 -154
View File
@@ -1,4 +1,4 @@
using MoreLinq.Extensions;
using MoreLinq.Extensions;
using Newtonsoft.Json;
using System.Linq.Expressions;
using YLErp.BLL;
@@ -14,6 +14,11 @@ namespace YLErp.Modules.SwapModule
{
public class SwapDealService : SwapTradeBaseService
{
protected virtual bool TryGetFloatRate(DateTime valueDate, string underlyingCode, out double rate)
{
return EodPriceQueryService.TryGetPrice(valueDate, underlyingCode, out rate);
}
public SwapDealService(OptUserInfo optUser) : base(optUser)
{
@@ -36,7 +41,7 @@ namespace YLErp.Modules.SwapModule
bool commodity = ConsGlobal.InstrumentType.CalcTypeIsFutures(um.UnderlyingInstrumentType);
List<int> eventTyps = new List<int>() { (int)SwapEventTypeEnum., (int)SwapEventTypeEnum. };
var dealDate = valuedateBLL.ValueDate <= td.ExerciseDate.Value ? valuedateBLL.ValueDate : td.ExerciseDate.Value;
CheckLastEod(dealDate, td.TradeDate.Value, tradeId);
//CheckLastEod(dealDate, td.TradeDate.Value, tradeId); //去掉平仓收盘限制
var tradeExtend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == tradeId);
td.trade_extend = tradeExtend;
var position = positions.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode) && !x.IsInitial).FirstOrDefault();
@@ -122,7 +127,28 @@ namespace YLErp.Modules.SwapModule
{
var td = DbContext.trade.Find(tradeId);
var dealDate = valuedateBLL.ValueDate <= td.ExerciseDate.Value ? valuedateBLL.ValueDate : td.ExerciseDate.Value;
CheckLastEod(dealDate, td.StartDate.Value, tradeId);
//CheckLastEod(dealDate, td.StartDate.Value, tradeId);
}
/// <summary>
/// 校验收益结算操作(不检查收盘限制)
/// </summary>
/// <param name="tradeId"></param>
public void CheckEodTradeForIncome(int tradeId)
{
var td = DbContext.trade.Find(tradeId);
// 收益结算不检查收盘限制,只检查交易状态
if (td.TradeType != "收益互换")
{
throw new ServiceException("该交易不是收益互换类型");
}
if (td.ValidState == "InValid")
{
throw new ServiceException("该交易已无效");
}
if (td.TradeStatus != ConsTrade. && td.TradeStatus != ConsTrade.)
{
throw new ServiceException($"该交易状态为【{td.TradeStatus}】,无法进行收益结算");
}
}
/// <summary>
/// 多空组合 平仓初始化
@@ -140,7 +166,7 @@ namespace YLErp.Modules.SwapModule
var positions = DbContext.swap_position.Where(x => x.SwapTradeId == tradeId && x.IsInitial && !x.Invalid);
List<int> eventTyps = new List<int>() { (int)SwapEventTypeEnum., (int)SwapEventTypeEnum., (int)SwapEventTypeEnum. };
var dealDate = valuedateBLL.ValueDate <= td.ExerciseDate.Value ? valuedateBLL.ValueDate : td.ExerciseDate.Value;
CheckLastEod(dealDate, td.TradeDate.Value, tradeId);
//CheckLastEod(dealDate, td.TradeDate.Value, tradeId); //去掉平仓收盘限制
var tradeExtend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == tradeId);
td.trade_extend = tradeExtend;
var preDealDate = GetPreDealDate(tradeId, dealDate, eventTyps);
@@ -194,7 +220,7 @@ namespace YLErp.Modules.SwapModule
var um = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
List<int> eventTypes = new List<int>() { (int)SwapFlowEventTypeEnum., (int)SwapFlowEventTypeEnum. };
var dealDate = valuedateBLL.ValueDate < td.ExerciseDate.Value ? valuedateBLL.ValueDate : td.ExerciseDate.Value;
CheckLastEod(dealDate, td.TradeDate.Value, tradeId);
// 收益结算不检查收盘限制
var tradeExtend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == tradeId);
td.trade_extend = tradeExtend;
var position = positions.Where(x => !string.IsNullOrEmpty(x.UnderlyingCode) && !x.IsInitial).FirstOrDefault();
@@ -296,16 +322,17 @@ namespace YLErp.Modules.SwapModule
var tradeExtend = DbContext.trade_extend.FirstOrDefault(x => x.TradeId == tradeId);
List<int> eventTypes = new List<int>() { (int)SwapEventTypeEnum., (int)SwapEventTypeEnum., (int)SwapEventTypeEnum. };
var lastEod = DbContext.eod_swap.Where(x => x.ValueDate < unwindDate && x.SwapTradeId == tradeId).OrderByDescending(o => o.ValueDate).FirstOrDefault();
var orginPv = lastEod != null ? lastEod.NotionalValue : 0;
var _preSetteDate = lastEod == null ? unwindDate.AddDays(-1) : lastEod.ValueDate;
List<eod_swap_position> lastEodPositions = new SwapEodPositionService(this).GetPreEodPositions(tradeId, _preSetteDate);//上一交易数据
var posiLongNotionalValue = longPositions.Sum(s => s.PosiNotionalValue);// 剩余名义本金
var posiShortNotionalValue = shortPositions.Sum(s => s.PosiNotionalValue);// 剩余名义本金
var stockEqvNotional = realPostitions.Where(x => x.PosiDirection > 0).Sum(s => s.PosiNotionalValue);
var posiNotionalValue = stockEqvNotional * closePercent;//剩余名义本金
var orginPv = lastEod != null ? lastEod.NotionalValue : stockEqvNotional;
var grossPrice = realPostitions.Where(x => x.PosiDirection > 0).FirstOrDefault()?.PosiGrossPrice;
bool tdClose = DbContext.swap_flow_event.Any(x => x.SwapTradeId == tradeId && x.UnwindDate == unwindDate && eventTypes.Contains(x.EventType) && x.DataState == (int)SwapFlowDateStateEnum.);
interests = GetInterests(td, tradeExtend, valueDate, unwindDate, lastEodPositions, positions, stockEqvNotional, posiLongNotionalValue, posiShortNotionalValue, posiNotionalValue, closePercent, eventType, tdClose, false, grossPrice ?? 0, orginPv, true, false, false);
var calcLastNew = tradeExtend?.ExtendObj?.InterestCalcMode?.EndsWith("1") ?? true;
interests = GetInterests(td, tradeExtend, valueDate, unwindDate, lastEodPositions, positions, stockEqvNotional, posiLongNotionalValue, posiShortNotionalValue, posiNotionalValue, closePercent, eventType, tdClose, false, grossPrice ?? 0, orginPv, true, calcLastNew, false);
return interests;
}
/// <summary>
@@ -348,120 +375,198 @@ namespace YLErp.Modules.SwapModule
{
List<swap_flow_event> interests = new List<swap_flow_event>();
var annualDays = tradeExtend == null ? 365 : tradeExtend.ExtendObj.AnnualDays;
bool calcFirst = tradeExtend?.ExtendObj.InterestCalcMode?.StartsWith("1") ?? true;
foreach (var position in positions)
{
var _closePosiNotionalValue = closePosiNotionalValue;
var _posiNotionalValue = posiNotionalValue;
var preEodPosition = eodPositions.FirstOrDefault(x => x.PositionId == position.id);
DateTime? preDealDate = null;
// 初始化持仓信息
var preEodPosition = eodPositions.FirstOrDefault(x => x.PositionId == position.id) ?? new eod_swap_position();
var positionClone = position.Clone();
var newClosePercent = closePrecent;
if (preEodPosition != null)
DateTime? preDealDate = preEodPosition.id != 0 ? preEodPosition.ValueDate : null;
// 计算计息区间
int interestPeriod = position.interest_rest_days ?? 1;
bool swap = InitInterestDate(unwindDate, preDealDate, td, tdClose, calcLast, out DateTime startDate, out DateTime endDate);
// 计算名义本金
var (closePrincipal, posiPrincipal, newClosePercent) = CalcNotionalByMode(position, closePrecent, posiNotionalValue, posiLongNotionalValue, posiShortNotionalValue);
if ((InterestModeEnum)position.InterestMode == InterestModeEnum. || (InterestModeEnum)position.InterestMode == InterestModeEnum.)
{
preDealDate = preEodPosition.ValueDate;
}
var swap = InitInterestDate(unwindDate, preDealDate, td, tdClose, calcLast, out DateTime startDate, out DateTime endDate);//不算头或不算尾情况,无利息
if (!preDealDate.HasValue)
{
preEodPosition = new eod_swap_position();
preEodPosition.PosiStartDate = position.PosiStartDate;
preEodPosition.ValueDate = position.PosiStartDate;
}
var swapIntervalToday = position.SwapIntervalList.Where(x => x.Date <= startDate).OrderByDescending(o => o.Date).FirstOrDefault();
if (position.InterestMode == (int)InterestModeEnum.)
{
_closePosiNotionalValue = position.InterestPrincipalFix;
_posiNotionalValue = position.InterestPrincipalFix;
newClosePercent = 1m;
}
else if (position.InterestMode == (int)InterestModeEnum.)
{
_closePosiNotionalValue = posiLongNotionalValue * closePrecent;
_posiNotionalValue = posiLongNotionalValue;
}
else if (position.InterestMode == (int)InterestModeEnum.)
{
_closePosiNotionalValue = posiShortNotionalValue * closePrecent;
_posiNotionalValue = posiShortNotionalValue;
}
else if (position.InterestMode == (int)InterestModeEnum.)
{
_closePosiNotionalValue = _posiNotionalValue * closePrecent;
_posiNotionalValue = _posiNotionalValue;
}
else if (position.InterestMode == (int)InterestModeEnum. || position.InterestMode == (int)InterestModeEnum.)
{
_closePosiNotionalValue = position.InterestPrincipalFix * closePrecent;
_posiNotionalValue = position.InterestPrincipalFix * closePrecent;
positionClone.InterestDirection = position.InterestDirection == (int)SwapDirectionEnum. ? (int)SwapDirectionEnum. : (int)SwapDirectionEnum.;
}
if (!string.IsNullOrEmpty(position.FloatRateUnderlyingCode))
{
// 获取重置频率,如果为空则默认为1
int interestPeriod = position.interest_rest_days ?? 1;
// 计算从 td.StartDate 到 endDate 的天数
var days = (endDate - td.StartDate.Value).Days;
// 获取合适的 rateDate
DateTime rateDate = GetRateDate(position.interest_rule, td.StartDate.Value, endDate, days, interestPeriod);
// 获取利率
decimal rate = GetFixedRate(position, startDate);
decimal floatRate = GetFloatRate(position, preEodPosition, td.StartDate.Value, endDate, interestPeriod, swap, positionClone);
// 如果不需要重置,并且上一日已有 FloatRate,则不再查找
if (preEodPosition.id != 0 && days % interestPeriod != 0)
{
position.FloatRate = preEodPosition.FloatRate;
positionClone.FloatRate = preEodPosition.FloatRate;
}
else
{
// 如果没有 preEodPosition 数据或需要查找新 Rate,则去查询最新的浮动利率
if (EodPriceQueryService.TryGetPrice(rateDate, position.FloatRateUnderlyingCode, out double floatRate))
{
position.FloatRate = Convert.ToDecimal(floatRate);
positionClone.FloatRate = position.FloatRate;
}
else if (!swap)
{
throw new Exception($"获取不到{position.FloatRateUnderlyingCode}在{rateDate:yyyy年MM月dd日}的价格");
}
}
}
decimal rate = position.InterestRateDefault;
if (swapIntervalToday == null)//当日无适用观察日
// 根据场景计算利息
if (settment)
{
var swapInterval = position.SwapIntervalList.Where(x => x.Date > startDate).OrderBy(o => o.Date).FirstOrDefault();
if (swapInterval != null)
{
rate = swapInterval.Rate;
}
// 收盘归档场景,使用 CalcEodInterest
interests.Add(CalcEodInterest(td, valueDate, positionClone, rate, floatRate, closePrincipal, posiPrincipal, annualDays, calcFirst, calcLast, preEodPosition, eventType, add));
}
else
{
rate = swapIntervalToday.Rate;
// 盘中互换场景,使用 CalcUnwindInterest
interests.Add(CalcUnwindInterest(td, valueDate, endDate, positionClone, rate, floatRate, posiPrincipal, closePrincipal, newClosePercent, annualDays, preEodPosition, eventType, add, swap, orginPv));
}
if (preEodPosition.id == 0)
{
preEodPosition.FloatRate = positionClone.FloatRate;
preEodPosition.TdInterestPrincipal = _posiNotionalValue;
preEodPosition.PosiNotionalValue = _posiNotionalValue;
}
swap_flow_event interest = InitSwapDealInterest(td, valueDate, endDate, rate, positionClone, add, swap, _posiNotionalValue, _closePosiNotionalValue, newClosePercent, annualDays, eventType, preEodPosition, needPrice, settment, orginPv);
interests.Add(interest);
}
return interests;
}
/// <summary>
/// 根据给定条件获取 rateDate
/// 根据计息模式计算名义本金
/// </summary>
private DateTime GetRateDate(int? interest_rule, DateTime startDate, DateTime endDate, int days, int interestPeriod)
private (decimal close, decimal posi, decimal closePct) CalcNotionalByMode(swap_position position, decimal closePercent, decimal posiNotional, decimal posiLong, decimal posiShort)
{
// 判断是否达到重置周期
if (days % interestPeriod == 0)
decimal closePrincipal = posiNotional;
decimal posiPrincipal = posiNotional;
decimal newClosePercent = closePercent;
switch ((InterestModeEnum)position.InterestMode)
{
return QdpCalendarHelper.GetNonHolidayDefore(endDate.AddDays(interest_rule ?? 0));
case InterestModeEnum.:
closePrincipal = posiPrincipal = position.InterestPrincipalFix;
newClosePercent = 1m;
break;
case InterestModeEnum.:
closePrincipal = posiLong * closePercent;
posiPrincipal = posiLong;
break;
case InterestModeEnum.:
closePrincipal = posiShort * closePercent;
posiPrincipal = posiShort;
break;
case InterestModeEnum.:
closePrincipal = posiNotional * closePercent;
break;
case InterestModeEnum.:
case InterestModeEnum.:
closePrincipal = position.InterestPrincipalFix * closePercent;
posiPrincipal = position.InterestPrincipalFix;
break;
}
return (closePrincipal, posiPrincipal, newClosePercent);
}
/// <summary>
/// 获取固定利率
/// </summary>
private decimal GetFixedRate(swap_position position, DateTime startDate)
{
var swapIntervalToday = position.SwapIntervalList?.Where(x => x.Date <= startDate).OrderByDescending(o => o.Date).FirstOrDefault();
if (swapIntervalToday != null) return swapIntervalToday.Rate;
var nextInterval = position.SwapIntervalList?.Where(x => x.Date > startDate).OrderBy(o => o.Date).FirstOrDefault();
return nextInterval?.Rate ?? position.InterestRateDefault;
}
/// <summary>
/// 获取浮动利率
/// </summary>
private decimal GetFloatRate(swap_position position, eod_swap_position preEod, DateTime startDate, DateTime endDate, int period, bool swap, swap_position positionClone)
{
if (string.IsNullOrEmpty(position.FloatRateUnderlyingCode)) return position.FloatRate;
int days = (endDate - startDate).Days;
DateTime rateDate = days % period == 0
? QdpCalendarHelper.GetNonHolidayDefore(endDate.AddDays(position.interest_rule ?? 0))
: QdpCalendarHelper.GetNonHolidayDefore(startDate.AddDays(position.interest_rule ?? 0));
if (preEod.id != 0 && days % period != 0)
{
position.FloatRate = positionClone.FloatRate = preEod.FloatRate;
return preEod.FloatRate;
}
// 如果不在重置周期内,使用 td.StartDate 来获取 rateDate
return QdpCalendarHelper.GetNonHolidayDefore(startDate.AddDays(interest_rule ?? 0));
if (TryGetFloatRate(rateDate, position.FloatRateUnderlyingCode, out double rate))
{
position.FloatRate = positionClone.FloatRate = Convert.ToDecimal(rate);
return position.FloatRate;
}
if (!swap) throw new Exception($"获取不到{position.FloatRateUnderlyingCode}在{rateDate:yyyy年MM月dd日}的价格");
return 0m;
}
/// <summary>
/// 计算收盘利息(EOD
/// </summary>
private swap_flow_event CalcEodInterest(trade td, DateTime valueDate, swap_position position, decimal rate, decimal floatRate, decimal closePrincipal, decimal posiPrincipal, int annualDays, bool calcFirst, bool calcLast, eod_swap_position preEod, int eventType, bool add)
{
// 判断当日是否计息:首日不算头或到期日不算尾则不计息
bool calcToday = true;
if (calcFirst == false && valueDate == td.StartDate.Value) calcToday = false; // 首日不算头
if (calcLast == false && valueDate == td.ExerciseDate.Value) calcToday = false; // 到期日不算尾
// 初始化EOD持仓信息
if (preEod.id == 0)
{
preEod.FloatRate = floatRate;
preEod.TdInterestPrincipal = posiPrincipal;
preEod.PosiNotionalValue = posiPrincipal;
}
// 构建利息事件
var interest = new swap_flow_event
{
SwapTradeId = td.id,
SwapTradeNo = td.TradeNumber,
EventType = eventType,
EventReason = "交易",
EventDate = valueDate,
PositionId = position.id,
InterestDirection = position.InterestDirection,
InterestRate = rate,
InterestPrincipal = closePrincipal,
InterestSwapInterval = position.InterestSwapInterval,
InterestMode = position.InterestMode,
FloatRate = floatRate,
DataState = (int)SwapFlowDateStateEnum.,
ClientId = td.ClientId,
UnwindDate = valueDate
};
// 收盘场景使用 preEod.FloatRate(历史浮动利率),与 InitSwapDealInterest 收盘场景保持一致
decimal eodFloatRate = preEod.id != 0 ? preEod.FloatRate : floatRate;
decimal interestAmount = 0;
decimal tdInterestAmount = 0;
if (calcToday)
{
if (position.InterestType == (int)InterestTypeEnum.)
{
// 复利计算
CalcDailyCompoundInterestByEod(preEod, valueDate, td.StartDate.Value, position, closePrincipal, posiPrincipal, interest, annualDays, false, eodFloatRate, 1m, posiPrincipal, ref interestAmount, ref tdInterestAmount);
}
else
{
// 单利计算
CalcDailySimpleInterestByEod(preEod, valueDate, td.StartDate.Value, position, closePrincipal, posiPrincipal, interest, annualDays, false, eodFloatRate, 1m, posiPrincipal, ref interestAmount, ref tdInterestAmount);
}
}
// 四舍五入并赋值
interest.InterestAmount = Math.Round(interestAmount, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
interest.TdInterestAmount = Math.Round(tdInterestAmount, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
// 计算InterestClosePnL(方向:收取=1为正,支付=-1为负)
var interestRatio = position.InterestDirection == 1 ? 1m : -1m;
interest.InterestClosePnL = interest.InterestAmount * interestRatio;
if (add) UpdateDbOption(interest);
return interest;
}
/// <summary>
/// 计算盘中利息(平仓/互换)
/// </summary>
private swap_flow_event CalcUnwindInterest(trade td, DateTime valueDate, DateTime endDate, swap_position position, decimal rate, decimal floatRate, decimal posiPrincipal, decimal closePrincipal, decimal closePercent, int annualDays, eod_swap_position preEod, int eventType, bool add, bool swap, decimal orginPv)
{
if (preEod.id == 0)
{
preEod.FloatRate = floatRate;
preEod.TdInterestPrincipal = posiPrincipal;
preEod.PosiNotionalValue = posiPrincipal;
preEod.ValueDate = td.TradeDate.Value;
}
return InitSwapDealInterest(td, valueDate, endDate, rate, position, add, swap, posiPrincipal, closePrincipal, closePercent, annualDays, eventType, preEod, false, orginPv);
}
/// <summary>
/// 初始化利息腿信息
@@ -492,11 +597,9 @@ namespace YLErp.Modules.SwapModule
int eventType,
eod_swap_position preEodPosition,
bool needPrice,
bool settment,
decimal orginPv
decimal orginPv
)
{
DateTime lastSwapDate = preEodPosition.ValueDate;
decimal interestProfitSum = preEodPosition.InterestProfitSum;
swap_flow_event interest = new swap_flow_event();
interest.SwapTradeId = td.id;
@@ -514,8 +617,6 @@ namespace YLErp.Modules.SwapModule
interest.DataState = (int)SwapFlowDateStateEnum.;
interest.ClientId = td.ClientId;
interest.UnwindDate = endDate;
var itemDays = (endDate - lastSwapDate).Days;
itemDays = itemDays == 0 ? 1 : itemDays;
if (swap)
{
interest.InterestAmount = 0;
@@ -528,43 +629,14 @@ namespace YLErp.Modules.SwapModule
decimal InterestAmount = 0;
decimal TdInterestAmount = 0;
var interestRatio = position.InterestDirection == 1 ? 1m : -1m;
var floateRate = preEodPosition.FloatRate;
if (position.InterestType == (int)InterestTypeEnum.)
{
var floateRate = preEodPosition.FloatRate;
if (settment)//收盘利息计算
{
CalcDailyCompoundInterestByEod(preEodPosition, endDate, td.StartDate.Value, position, closePosiNotionalValue, posiNotionalValue, interest, annualDays, needPrice, floateRate, closePrecent, orginPv, ref InterestAmount, ref TdInterestAmount);
}
else
{
CalcDailyCompoundInterest(preEodPosition, endDate, td.StartDate.Value, position, closePosiNotionalValue, posiNotionalValue, interest, annualDays, needPrice, floateRate, closePrecent, orginPv, ref InterestAmount, ref TdInterestAmount);
}
CalcDailyCompoundInterest(preEodPosition, endDate, position, closePosiNotionalValue, posiNotionalValue, interest, annualDays, needPrice, floateRate, closePrecent, orginPv, ref InterestAmount, ref TdInterestAmount);
}
else
{
decimal aDays = position.IsAnnualized ? annualDays : 1;
InterestAmount = closePosiNotionalValue * (interest.InterestRate + position.FloatRate);
TdInterestAmount = posiNotionalValue * (interest.InterestRate + position.FloatRate);
if (settment)
{
InterestAmount = InterestAmount * ((decimal)itemDays / aDays);
TdInterestAmount = TdInterestAmount * ((decimal)itemDays / aDays);
InterestAmount = (interestProfitSum * closePrecent) + InterestAmount;
}
else
{
if (endDate > lastSwapDate)//日期超算情况
{
InterestAmount = InterestAmount * ((decimal)itemDays / aDays);
TdInterestAmount = TdInterestAmount * ((decimal)itemDays / aDays);
InterestAmount += (interestProfitSum * closePrecent);
}
else
{
InterestAmount = interestProfitSum * closePrecent;
}
}
CalcDailySimpleInterest(preEodPosition, endDate, position, closePosiNotionalValue, posiNotionalValue, interest, annualDays, needPrice, floateRate, closePrecent, orginPv, ref InterestAmount, ref TdInterestAmount);
}
interest.InterestAmount = Math.Round(InterestAmount, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
@@ -582,16 +654,34 @@ namespace YLErp.Modules.SwapModule
/// </summary>
/// <param name="lastSwapDate">上一互换日</param>
/// <param name="endDate">结算日期</param>
/// <param name="tradeDate">开仓日</param>
/// <param name="floatUnderylingCode">浮动标的</param>
/// <param name="principal">计息基数</param>
/// <param name="interestRate">固定利率</param>
/// <param name="isAnnualized">是否年化</param>
/// <param name="annualDays">年化天数</param>
/// <returns></returns>
public void CalcDailyCompoundInterest(eod_swap_position preEodPosition, DateTime endDate, DateTime tradeDate, swap_position position, decimal principal, decimal posiPrincipal, swap_flow_event flowEvent, int annualDays, bool needPrice, decimal floateRate, decimal closePercent, decimal orginPv, ref decimal InterestAmount, ref decimal TdInterestAmount)
public void CalcDailyCompoundInterest(eod_swap_position preEodPosition, DateTime endDate, swap_position position, decimal principal, decimal posiPrincipal, swap_flow_event flowEvent, int annualDays, bool needPrice, decimal floateRate, decimal closePercent, decimal orginPv, ref decimal InterestAmount, ref decimal TdInterestAmount)
{
DateTime lastSwapDate = preEodPosition.ValueDate;
// 复利:利息并入本金
CalcDailyInterest(preEodPosition, endDate, position, principal, posiPrincipal, flowEvent, annualDays, needPrice, floateRate, closePercent, orginPv, compoundInterest: true, ref InterestAmount, ref TdInterestAmount);
}
/// <summary>
/// 计算单利 盘中(按重置天数分段,每段使用对应浮动利率)
/// </summary>
public void CalcDailySimpleInterest(eod_swap_position preEodPosition, DateTime endDate, swap_position position, decimal principal, decimal posiPrincipal, swap_flow_event flowEvent, int annualDays, bool needPrice, decimal floateRate, decimal closePercent, decimal orginPv, ref decimal InterestAmount, ref decimal TdInterestAmount)
{
// 单利:利息不并入本金
CalcDailyInterest(preEodPosition, endDate, position, principal, posiPrincipal, flowEvent, annualDays, needPrice, floateRate, closePercent, orginPv, compoundInterest: false, ref InterestAmount, ref TdInterestAmount);
}
/// <summary>
/// 通用日度利息计算方法(单利/复利共用)
/// </summary>
/// <param name="compoundInterest">是否复利:true=利息并入本金,false=单利</param>
private void CalcDailyInterest(eod_swap_position preEodPosition, DateTime endDate, swap_position position, decimal principal, decimal posiPrincipal, swap_flow_event flowEvent, int annualDays, bool needPrice, decimal floateRate, decimal closePercent, decimal orginPv, bool compoundInterest, ref decimal InterestAmount, ref decimal TdInterestAmount)
{
var startDate = position.PosiStartDate;
decimal interestProfitSum = preEodPosition.InterestProfitSum;
var TdInterestPrincipal = preEodPosition.TdInterestPrincipal;
decimal interest = interestProfitSum * closePercent;
@@ -599,21 +689,26 @@ namespace YLErp.Modules.SwapModule
int interestPeriod = position.interest_rest_days ?? 1;
decimal dynomicPrincipal = principal;
decimal tdDynomicPrincipal = posiPrincipal;
var calcDays = (endDate - lastSwapDate).Days;
var calcDays = (endDate - startDate).Days;
double floatRate = Convert.ToDouble(floateRate);
for (int i = 0; i <= calcDays; i++)
{
var rateDate = lastSwapDate.AddDays(i);
if (rateDate > lastSwapDate || endDate == lastSwapDate)
var accrueDate = startDate.AddDays(i);
if (accrueDate > preEodPosition.ValueDate)
{
if (i % interestPeriod == 0)
{
dynomicPrincipal = dynomicPrincipal + interest;
tdDynomicPrincipal = tdDynomicPrincipal + interest;
// 复利时:利息并入本金
if (compoundInterest)
{
dynomicPrincipal = dynomicPrincipal + interest;
tdDynomicPrincipal = tdDynomicPrincipal + interest;
}
// 获取新的浮动利率
if (!string.IsNullOrEmpty(position.FloatRateUnderlyingCode))
{
var fr007RateDate = QdpCalendarHelper.GetNonHolidayDefore(endDate.AddDays(position.interest_rule ?? 0)); // 获取前一工作日;
if (EodPriceQueryService.TryGetPrice(fr007RateDate, position.FloatRateUnderlyingCode, out double floatRate1))
var fr007RateDate = QdpCalendarHelper.GetNonHolidayDefore(accrueDate.AddDays(position.interest_rule ?? 0));
if (TryGetFloatRate(fr007RateDate, position.FloatRateUnderlyingCode, out double floatRate1))
{
if (floatRate1 != 0)
{
@@ -624,7 +719,6 @@ namespace YLErp.Modules.SwapModule
{
throw new Exception($"获取不到{position.FloatRateUnderlyingCode}在{fr007RateDate:yyyy年MM月dd日}的价格");
}
}
flowEvent.InterestPrincipal = tdDynomicPrincipal * closePercent;
TdInterestPrincipal = tdDynomicPrincipal;
@@ -645,9 +739,7 @@ namespace YLErp.Modules.SwapModule
}
interest += interest1;
tdinterest += tdinterest1;
}
}
InterestAmount = Math.Round(interest, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
TdInterestAmount = Math.Round(tdinterest, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
@@ -668,8 +760,8 @@ namespace YLErp.Modules.SwapModule
public void CalcDailyCompoundInterestByEod(eod_swap_position preEodPosition, DateTime endDate, DateTime tradeDate, swap_position position, decimal principal, decimal posiPrincipal, swap_flow_event flowEvent, int annualDays, bool needPrice, decimal floateRate, decimal closePercent, decimal orginPv, ref decimal InterestAmount, ref decimal TdInterestAmount)
{
decimal interestProfitSum = preEodPosition.InterestProfitSum;
decimal interest = preEodPosition.TdInterestIncome;
decimal tdinterest = preEodPosition.TdInterestIncome;
decimal interest = interestProfitSum * closePercent;
decimal tdinterest = interestProfitSum * closePercent;
int interestPeriod = position.interest_rest_days ?? 1;
decimal tdDynomicPrincipal = posiPrincipal;
double floatRate = Convert.ToDouble(floateRate);
@@ -681,7 +773,7 @@ namespace YLErp.Modules.SwapModule
{
// 获取合适的 rateDate
var fr007RateDate = QdpCalendarHelper.GetNonHolidayDefore(endDate.AddDays(position.interest_rule ?? 0)); // 获取前一工作日;
if (EodPriceQueryService.TryGetPrice(fr007RateDate, position.FloatRateUnderlyingCode, out double floatRate1))
if (TryGetFloatRate(fr007RateDate, position.FloatRateUnderlyingCode, out double floatRate1))
{
if (floatRate1 != 0)
{
@@ -721,6 +813,59 @@ namespace YLErp.Modules.SwapModule
TdInterestAmount = Math.Round(tdinterest, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
}
/// <summary>
/// 计算单利 收盘(按重置天数分段,每段使用对应浮动利率)
/// </summary>
public void CalcDailySimpleInterestByEod(eod_swap_position preEodPosition, DateTime endDate, DateTime tradeDate, swap_position position, decimal principal, decimal posiPrincipal, swap_flow_event flowEvent, int annualDays, bool needPrice, decimal floateRate, decimal closePercent, decimal orginPv, ref decimal InterestAmount, ref decimal TdInterestAmount)
{
decimal interestProfitSum = preEodPosition.InterestProfitSum;
int interestPeriod = position.interest_rest_days ?? 1;
double floatRate = Convert.ToDouble(floateRate);
var calcDays = (endDate - tradeDate).Days;
// 修复:首次操作时(preEodPosition.id == 0),TdInterestPrincipal 需要正确初始化
if (preEodPosition.id == 0)
{
preEodPosition.TdInterestPrincipal = posiPrincipal;
}
// 检查是否到达重置周期
if (calcDays % interestPeriod == 0)
{
// 获取新的浮动利率
if (!string.IsNullOrEmpty(position.FloatRateUnderlyingCode))
{
var fr007RateDate = QdpCalendarHelper.GetNonHolidayDefore(endDate.AddDays(position.interest_rule ?? 0));
if (TryGetFloatRate(fr007RateDate, position.FloatRateUnderlyingCode, out double newFloatRate))
{
if (newFloatRate != 0)
{
floatRate = newFloatRate;
}
}
else
{
throw new Exception($"获取不到{position.FloatRateUnderlyingCode}在{fr007RateDate:yyyy年MM月dd日}的价格");
}
}
}
flowEvent.FloatRate = Convert.ToDecimal(floatRate);
var baseTdInterestPrincipal = preEodPosition.TdInterestPrincipal + posiPrincipal - orginPv;
var baseInterestPrincipal = baseTdInterestPrincipal * closePercent;
// 修复:正确计算本次利息(基于实际持仓本金)
decimal interest = baseInterestPrincipal * (flowEvent.InterestRate + Convert.ToDecimal(floatRate));
decimal tdinterest = baseTdInterestPrincipal * (flowEvent.InterestRate + Convert.ToDecimal(floatRate));
if (position.IsAnnualized)
{
interest /= annualDays;
tdinterest /= annualDays;
}
InterestAmount = Math.Round(interest, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
TdInterestAmount = Math.Round(tdinterest, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
}
/// <summary>
/// 单标的平仓
/// </summary>
@@ -733,7 +878,7 @@ namespace YLErp.Modules.SwapModule
{
throw new ServiceException("未找到交易信息");
}
CheckLastEod(unwindData.ValueDate, td.StartDate.Value, unwindData.SwapTradeId);
//CheckLastEod(unwindData.ValueDate, td.StartDate.Value, unwindData.SwapTradeId); //去掉平仓收盘限制
var trans = DbContext.Database.BeginTransaction();
bool cofirm = false;
try
@@ -1232,7 +1377,7 @@ namespace YLErp.Modules.SwapModule
{
throw new ServiceException("未找到交易信息");
}
CheckLastEod(unwindData.ValueDate, td.StartDate.Value, unwindData.SwapTradeId);
//CheckLastEod(unwindData.ValueDate, td.StartDate.Value, unwindData.SwapTradeId); //去掉平仓收盘限制
var trans = DbContext.Database.BeginTransaction();
bool confirm = false;
try