#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
@@ -60,6 +60,18 @@ namespace YLErp.DBModels
[DisplayName("观察起始日")]
[Column("observation_start")]
public DateTime? ObservationStart { get; set; }
/// <summary>
/// 交易日历
/// </summary>
[DisplayName("交易日历")]
[Column("observation_calendar")]
public string ObservationCalendar { get; set; }
/// <summary>
/// 结算规则
/// </summary>
[DisplayName("结算规则")]
[Column("observation_settlement_rules")]
public int? ObservationSettlementRules { get; set; }
/// <summary>
/// 互换观察日集合
+5 -1
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -20,5 +20,9 @@ namespace YLErp.Models
/// 是否结算 0:否 1:是
/// </summary>
public int Settlement { get; set; }
/// <summary>
/// 结算日期(观察日不一定等于结算日)
/// </summary>
public DateTime? SettlementDate { get; set; }
}
}
File diff suppressed because it is too large Load Diff
+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
@@ -136,7 +136,15 @@ namespace YLErp.Modules.SwapModule
var grossPrice = curEodPosis.Where(x => x.PosiDirection > 0).FirstOrDefault()?.PosiGrossPrice ?? 0;
//处理利息腿
DealInterests(interestList, eodPositions, todyEodPositions, settleDate, td, flowEvents, autoInterests, lastEodSwap, posiLongNotional, posiShortNotional, closePosiNotional, grossPrice, orginPv);
DealAutoInterests(autoInterests, td, settleDate, preDealDate, posiLongNotional + posiShortNotional);
//获取自动互换的 interval 信息,用于确定结算日期
IntervalModel autoInterval = null;
foreach (var interest in interestList)
{
autoInterval = interest.SwapIntervalList.FirstOrDefault(x => x.Date == settleDate && x.Settlement == 1);
if (autoInterval != null)
break;
}
DealAutoInterests(autoInterests, td, settleDate, preDealDate, posiLongNotional + posiShortNotional, autoInterval);
//多空组合判断是否已到到期日且无持仓信息
if (longShort && td.ExerciseDate.Value == settleDate && allPositionQty == 0)
{
@@ -361,7 +369,8 @@ namespace YLErp.Modules.SwapModule
/// <param name="td"></param>
/// <param name="settleDate"></param>
/// <param name="swapDeals"></param>
private void DealAutoInterests(List<swap_flow_event> autoInterests, trade td, DateTime settleDate, DateTime? preDealDate, decimal StockEqvNotional)
/// <param name="interval">自动互换观察日信息,用于获取结算日期</param>
private void DealAutoInterests(List<swap_flow_event> autoInterests, trade td, DateTime settleDate, DateTime? preDealDate, decimal StockEqvNotional, IntervalModel interval)
{
if (autoInterests.Count == 0)
{
@@ -387,17 +396,20 @@ namespace YLErp.Modules.SwapModule
unwindData.SwapCloseAmount = unwindData.SwapCloseAmount + x.InterestClosePnL;
unwindData.SwapRealizedPnL = unwindData.SwapCloseAmount;
});
SaveAutoSwapDeal(td, autoInterests, unwindData);
SaveAutoSwapDeal(td, autoInterests, unwindData, interval);
}
/// <summary>
/// 保存自动互换数据信息
/// </summary>
/// <param name="td"></param>
/// <param name="swap_Deal"></param>
private long SaveAutoSwapDeal(trade td, List<swap_flow_event> flowEvents, UnwindData unwindData)
/// <param name="interval">自动互换观察日信息,用于获取结算日期</param>
private long SaveAutoSwapDeal(trade td, List<swap_flow_event> flowEvents, UnwindData unwindData, IntervalModel interval)
{
//td.UnWindDate = unwindData.ValueDate;
int clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapCloseAmount), ClientCashInCashOut._互换, unwindData.ValueDate);
//优先使用 interval.SettlementDate 作为资金记录发生日期,如果没有则使用 ValueDate
var cashHappenDate = interval?.SettlementDate ?? unwindData.ValueDate;
int clientCashId = AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapCloseAmount), ClientCashInCashOut._互换, cashHappenDate);
string data = JsonConvert.SerializeObject(unwindData);
var swapEvent = new SwapEventService(this).AddSwapEventDate(unwindData.ValueDate, unwindData.SwapTradeId, (int)SwapEventTypeEnum., data, clientCashId, true, "系统操作-自动互换");//将互换总额存入事件
flowEvents.ForEach(x =>
@@ -777,7 +789,7 @@ namespace YLErp.Modules.SwapModule
positions.Add(position);
List<eod_swap_position> preEodPositions = new List<eod_swap_position>();
preEodPositions.Add(eodPayPosition);
if (position.InterestMode == (int)InterestModeEnum.)
if (position.InterestMode == (int)InterestModeEnum.||position.InterestMode == (int)InterestModeEnum. || position.InterestMode == (int)InterestModeEnum.)
{
orginPv = eodPayPosition.InterestPrincipalFix;
}
@@ -872,7 +884,7 @@ namespace YLErp.Modules.SwapModule
newEodPayPosition = eodPayPosition.Clone();
newEodPayPosition.id = 0;
}
if (position.InterestMode == (int)InterestModeEnum.)
if (position.InterestMode == (int)InterestModeEnum. || position.InterestMode == (int)InterestModeEnum. || position.InterestMode == (int)InterestModeEnum.)
{
orginPv = eodPayPosition.InterestPrincipalFix;
}
@@ -911,7 +923,6 @@ namespace YLErp.Modules.SwapModule
newEodPayPosition.InterestType = position.InterestType;
newEodPayPosition.FloatRate = interests.Count > 0 ? interests.First().FloatRate ?? 0 : 0;
newEodPayPosition.FloatRateUnderlyingCode = position.FloatRateUnderlyingCode;
newEodPayPosition.InterestFeePending = 0;
newEodPayPosition.interest_rest_days = position.interest_rest_days;
newEodPayPosition.interest_rule = position.interest_rule;
//利息端估值用信息
@@ -1013,7 +1024,7 @@ namespace YLErp.Modules.SwapModule
newEodPayPosition.id = 0;
newEodPayPosition.PositionId = position.id;
}
if (position.InterestMode == (int)InterestModeEnum.)
if (position.InterestMode == (int)InterestModeEnum. || position.InterestMode == (int)InterestModeEnum. || position.InterestMode == (int)InterestModeEnum.)
{
orginPv = eodPayPosition.InterestPrincipalFix;
}
@@ -325,7 +325,7 @@ namespace YLErp.Modules.SwapModule
/// <param name="interestMode">计息方式</param>
/// <param name="interestStart">计息开始日期</param>
/// <param name="interestEnd">计息结束日期</param>
public bool InitInterestDate(DateTime valueDate, DateTime? preSettleDate, trade td, bool tdClose,bool calcLastNew,out DateTime interestStart, out DateTime interestEnd)
public bool InitInterestDate(DateTime valueDate, DateTime? preSettleDate, trade td, bool tdClose,bool calcLastNew, out DateTime interestStart, out DateTime interestEnd)
{
interestStart = td.StartDate.Value;
var exerciseDate = td.ExerciseDate.Value;
@@ -382,6 +382,19 @@ namespace YLErp.Modules.SwapModule
var eodSwapPositions = DbContext.eod_swap_position.Where(x => x.SwapTradeId == td.id && x.ValueDate >= valueDate).ToList();
DbContext.eod_swap_position.RemoveRange(eodSwapPositions);
DbContext.swap_flow_event.RemoveRange(swapFlowEvents);
// 删除自动互换产生的资金记录(client_cash_in_out
var swapEventIds = swapEvents.Select(s => s.id).ToList();
if (swapEventIds.Any())
{
// 通过 swap_event 的 ClientCashId 删除对应的资金记录
var clientCashIds = swapEvents.Where(s => s.ClientCashId > 0).Select(s => s.ClientCashId).ToList();
if (clientCashIds.Any())
{
var clientCashRecords = DbContext.ClientCashInCashOut.Where(x => clientCashIds.Contains(x.id)).ToList();
DbContext.ClientCashInCashOut.RemoveRange(clientCashRecords);
}
}
}
DbContext.swap_event.RemoveRange(swapEvents);
DbContext.eod_swap.RemoveRange(eodSwaps);
+195 -1
View File
@@ -1,4 +1,4 @@
using Qdp.Foundation.Implementations;
using Qdp.Foundation.Implementations;
using Qdp.Pricing.Base.Enums;
using Qdp.Pricing.Base.Implementations;
using System.Runtime.CompilerServices;
@@ -220,5 +220,199 @@ namespace YLErp.QdpModule
var monthlyDates = GetDefaultKoObservationDatesForSnowbal(startDate, endDate);
return monthlyDates.Select(x => (Date)x).ToArray();
}
public static DateTime[] GetDatesWithFixedTerm(
DateTime startDate,
DateTime endDate,
string termStr,
string calendarStr,
BusinessDayConvention bdc = BusinessDayConvention.None,
bool alignEnd = false,
string calcMode = "01")
{
if (!Term.IsTerm(termStr))
{
return null;
}
var calendarName = string.IsNullOrEmpty(calendarStr) ? "chn" : calendarStr.ToLower();
return GetDatesWithFixedTerm(startDate, endDate, new Term(termStr), calendarName, bdc, alignEnd, calcMode);
}
public static DateTime[] GetDatesWithFixedTerm(
DateTime startDate,
DateTime endDate,
Term term,
string calendarStr,
BusinessDayConvention bdc = BusinessDayConvention.None,
bool alignEnd = false,
string calcMode = "01")
{
var calendarName = string.IsNullOrEmpty(calendarStr) ? "chn" : calendarStr.ToLower();
return alignEnd
? GetDatesWithFixedTermStartAlignEnd(startDate, endDate, term, calendarName, bdc, calcMode)
: GetDatesWithFixedTermStartAlignStart(startDate, endDate, term, calendarName, bdc, calcMode);
}
private static DateTime[] GetDatesWithFixedTermStartAlignEnd(
DateTime startDate,
DateTime endDate,
Term term,
string calendarName,
BusinessDayConvention bdc = BusinessDayConvention.None,
string calcMode = "01")
{
var qdpStart = new Date(startDate);
var qdpEnd = new Date(endDate);
var dates = new List<Date>();
var calendar = CalendarImpl.Get(calendarName);
if (calcMode == "11")
{
while (qdpEnd >= qdpStart)
{
dates.Add(qdpEnd);
qdpEnd = term.Prev(qdpEnd);
}
}
else if (calcMode == "10")
{
qdpEnd = term.Prev(qdpEnd);
while (qdpEnd >= qdpStart)
{
dates.Add(qdpEnd);
qdpEnd = term.Prev(qdpEnd);
}
}
else if (calcMode == "01")
{
while (qdpEnd > qdpStart)
{
dates.Add(qdpEnd);
qdpEnd = term.Prev(qdpEnd);
}
}
else if (calcMode == "00")
{
qdpEnd = term.Prev(qdpEnd);
while (qdpEnd > qdpStart)
{
dates.Add(qdpEnd);
qdpEnd = term.Prev(qdpEnd);
}
}
if (dates.Count == 0)
{
dates.Add(new Date(endDate));
}
dates = dates.Select(d => calendar.Adjust(d, bdc)).Distinct().ToList();
dates.Reverse();
return dates.Select(x => x.DateTime).ToArray();
}
private static DateTime[] GetDatesWithFixedTermStartAlignStart(
DateTime startDate,
DateTime endDate,
Term term,
string calendarName,
BusinessDayConvention bdc = BusinessDayConvention.None,
string calcMode = "01")
{
var qdpStart = new Date(startDate);
var qdpEnd = new Date(endDate);
var dates = new List<Date>();
var calendar = CalendarImpl.Get(calendarName);
if (calcMode == "11")
{
while (qdpStart <= qdpEnd)
{
dates.Add(qdpStart);
qdpStart = term.Next(qdpStart);
}
}
else if (calcMode == "10")
{
while (qdpStart < qdpEnd)
{
dates.Add(qdpStart);
qdpStart = term.Next(qdpStart);
}
}
else if (calcMode == "01")
{
qdpStart = term.Next(qdpStart);
while (qdpStart <= qdpEnd)
{
dates.Add(qdpStart);
qdpStart = term.Next(qdpStart);
}
}
else if (calcMode == "00")
{
qdpStart = term.Next(qdpStart);
while (qdpStart < qdpEnd)
{
dates.Add(qdpStart);
qdpStart = term.Next(qdpStart);
}
}
dates = dates.Select(d => calendar.Adjust(d, bdc)).Distinct().ToList();
if (dates.Count == 0)
{
dates.Add(qdpEnd);
}
return dates.Select(x => x.DateTime).ToArray();
}
public static ObservationSettleDateResult[] GetObservationAndSettleDates(
DateTime startDate,
DateTime endDate,
string termStr,
string calendarStr,
int settlementRules,
BusinessDayConvention bdc = BusinessDayConvention.None,
bool alignEnd = false,
string calcMode = "01")
{
if (!Term.IsTerm(termStr))
{
return null;
}
var calendarName = string.IsNullOrEmpty(calendarStr) ? "chn" : calendarStr.ToLower();
var calendar = CalendarImpl.Get(calendarName);
var term = new Term(termStr);
DateTime[] observationDates = alignEnd
? GetDatesWithFixedTermStartAlignEnd(startDate, endDate, term, calendarName, bdc, calcMode)
: GetDatesWithFixedTermStartAlignStart(startDate, endDate, term, calendarName, bdc, calcMode);
var results = new List<ObservationSettleDateResult>();
foreach (var obsDate in observationDates)
{
var settleDate = obsDate.AddDays(settlementRules);
settleDate = calendar.Adjust(new Date(settleDate), bdc).DateTime;
results.Add(new ObservationSettleDateResult
{
ObservationDate = obsDate,
SettleDate = settleDate
});
}
return results.ToArray();
}
}
public class ObservationSettleDateResult
{
public DateTime ObservationDate { get; set; }
public DateTime SettleDate { get; set; }
}
}
@@ -153,6 +153,17 @@ namespace YLErp.Web.Controllers
return JsonSuccess("");
}
/// <summary>
/// 校验收益结算操作(不检查收盘限制)
/// </summary>
/// <param name="enid"></param>
/// <returns></returns>
public JsonResult CheckEodTradeForIncome(string enid)
{
var intid = DecryptInt(enid);
new SwapDealService(CurUser).CheckEodTradeForIncome(intid);
return JsonSuccess("");
}
/// <summary>
/// 收益互换 平仓
/// </summary>
/// <param name="enid"></param>
+9 -1
View File
@@ -1,4 +1,4 @@
using iTextSharp.text;
using iTextSharp.text;
using iTextSharp.text.pdf;
using NPOI.POIFS.Crypt;
using Qdp.Pricing.Base.Enums;
@@ -3456,6 +3456,14 @@ namespace YLErp.Web.Controllers
return JsonSuccess("", QdpObservationHelper.GetDatesWithFixedTerm(req.startDate, req.endDate, req.termStr, bdc, req.alignEnd, req.calcMode));
}
[HttpPost]
public JsonResult GetSwapObservationDateList(GetSwapObservationDateRequest req)
{
var bdc = (BusinessDayConvention)Enum.Parse(typeof(BusinessDayConvention), req.holidayAdjustment);
var results = QdpObservationHelper.GetObservationAndSettleDates(req.startDate, req.endDate, req.termStr, req.calendar, req.settlementRules, bdc, req.alignEnd, req.calcMode);
return JsonSuccess("", results);
}
public ActionResult StructureList(string structure, string CalcId, bool onlyshow = false)
{
var ret = new DZStructureService(CurUser).getStructureList(structure);
@@ -0,0 +1,28 @@
namespace YLErp.Web.Models
{
public class GetSwapObservationDateRequest
{
public DateTime startDate { get; set; }
public DateTime endDate { get; set; }
public string termStr { get; set; }
public string holidayAdjustment { get; set; }
public bool alignEnd { get; set; }
public string calcMode { get; set; }
public string calendar { get; set; }
public int settlementRules { get; set; }
}
public class SwapObservationDateResult
{
public DateTime ObservationDate { get; set; }
public DateTime SettleDate { get; set; }
}
}
+31 -8
View File
@@ -1,4 +1,4 @@
@using YLErp.Web.Models.JsModels;
@using YLErp.Web.Models.JsModels;
@model trade
@{
ViewBag.Title = "交易信息 | 编辑";
@@ -294,7 +294,7 @@
</select>
</td>
<td>
<button class="btn btn-sm btn-outline-danger" type="button" v-on:click="initObservationDates(item,1)">设置观察日</button>
<button class="btn btn-sm btn-outline-danger" type="button" v-on:click="initObservationDates(item,0)">设置观察日</button>
</td>
<td>
<a href="javascript:void(0);" title="删除" v-on:click="deleteMarginSwapRate(item.index)"><span class="glyphicon glyphicon-minus" style="color:#ff0000"></span></a>
@@ -363,7 +363,7 @@
</select>
</td>
<td>
<vue-number-input v-model="item.interest_rest_days" style="width:70px;" v-bind:format="inputFormatInteger" :disabled="item.InterestType==0"></vue-number-input>
<vue-number-input v-model="item.interest_rest_days" style="width:70px;" v-bind:format="inputFormatInteger"></vue-number-input>
</td>
<td>
<select v-model="item.interest_rule" :disabled="item.FloatRateUnderlyingCode=='--'||item.FloatRateUnderlyingCode==''||item.FloatRateUnderlyingCode==null">
@@ -372,7 +372,7 @@
</select>
</td>
<td>
<button class="btn btn-sm btn-outline-danger" type="button" v-on:click="initObservationDates(item,0)">设置观察日</button>
<button class="btn btn-sm btn-outline-danger" type="button" v-on:click="initObservationDates(item,1)">设置观察日</button>
</td>
<td>
<a href="javascript:void(0);" title="删除" v-on:click="deleteSwapRate(item.index)"><span class="glyphicon glyphicon-minus" style="color:#ff0000"></span></a>
@@ -512,7 +512,22 @@
<option v-for="item in page.timeUnits" :value="item.Value">{{item.Text}}</option>
</select>
</div>
<div class="col-12"></div>
<div class="col-6 mb-2" v-show="observationType==1">
<span>交易日历</span>
<select id="ObservationCalendar" style="width: 126px;margin-left:10px" v-model="observation.ObservationCalendar">
<option value="Chn">系统日历</option>
<option value="IB">银行间日历</option>
</select>
</div>
<div class="col-6 mb-2" v-show="observationType==1">
<span>结算规则</span>
<select id="ObservationSettlementRules" style="width: 126px;margin-left:13px" v-model="observation.ObservationSettlementRules">
<option value=0>T+0</option>
<option value=1>T+1</option>
<option value=2>T+2</option>
<option value=3>T+3</option>
</select>
</div>
<div class="col-6 mb-2">
<span>节假日调整</span>
<select id="ObservationHolidayType" style="width:120px" v-model="observation.ObservationHolidayType">
@@ -538,12 +553,20 @@
<div class="div-group">
<div id="divItems" class="searchdiv" style="text-align: center;padding-left: 0px;padding-right: 0px;margin: 0px;">
<ol id="ol" style="text-align:left;">
<span style="min-width:100px;display: inline-block;">日期</span>
<span style="display: inline-block;">互换利率</span>
<span style="min-width:100px;display: inline-block;">观察日期</span>
<span v-show="observationType==1" style="min-width:100px;display: inline-block;">结算日期</span>
<span style="min-width:120px;display: inline-block;">互换利率</span>
<span style="display: inline-block;"><input type="checkbox" id="checkAllclose" v-on:click="checkedAll()" :checked="observation.CheckedAll">是否结算</span>
<li v-for="item in observation.ObservationDataList">
<input type="text" class="field1" style="min-width:86px;margin-right:1rem;" v-model="item.date">
<input type="text" class="field2" style="" v-model="item.val">%
<input v-show="observationType==1" type="text" class="field3" style="min-width:86px;margin-right:1rem;" v-model="item.SettlementDate" readonly>
<template v-if="observationType==1">
<span style="display:inline-block;text-align:center;margin-right:2px;">{{item.floatRateCode}}</span>+
<input type="text" class="field4" style="width:50px;" v-model="item.val">%
</template>
<template v-else>
<input type="text" class="field2" style="" v-model="item.val">%
</template>
<input type="checkbox" name="checkclose" class="field5" v-model="item.itemChecked" v-on:click="checkedItem(item)" :disabled="item.disabled">
<a href="#" class="delete" v-on:click="deleteObItem(item)">
<span class="glyphicon glyphicon-remove"></span>
+10 -5
View File
@@ -1,4 +1,4 @@
@using YLErp.Enums;
@using YLErp.Enums;
@model TradeViewModel
@{
@@ -265,7 +265,7 @@
<td>@item.InterestRateDefault.OtcFormat(OtcFormatFlag.marginRateP)</td>
<td>@(item.IsAnnualized ? "是" : "否")</td>
<td>
<button class="btn btn-sm btn-outline-danger" type="button" onclick="showSwapRate('@(item.InterestSwapInterval)')" style="height:22px;">查看</button>
<button class="btn btn-sm btn-outline-danger" type="button" onclick="showSwapRate('@(item.InterestSwapInterval)', true)" style="height:22px;">查看</button>
</td>
</tr>
}
@@ -337,7 +337,7 @@
<td>@item.interest_rest_days</td>
<td>@((item.interest_rule != null) ? (SwapInterestRule)item.interest_rule : "")</td>
<td>
<button class="btn btn-sm btn-outline-danger" type="button" onclick="showSwapRate('@(item.InterestSwapInterval)')" style="height:22px;">查看</button>
<button class="btn btn-sm btn-outline-danger" type="button" onclick="showSwapRate('@(item.InterestSwapInterval)', false)" style="height:22px;">查看</button>
</td>
</tr>
}
@@ -1013,7 +1013,8 @@
<thead>
<tr>
<td> 观察日期 </td>
<td> 互换利率 </td>
<td v-if="!isMarginLeg"> 结算日期 </td>
<td> 互换利率</td>
<td> 是否结算 </td>
</tr>
</thead>
@@ -1021,7 +1022,11 @@
<tbody>
<tr v-for="item in SwapIntervalList">
<td>{{formatDate(item.Date)}}</td>
<td>{{item.Rate}}</td>
<td v-if="!isMarginLeg">{{formatDate(item.SettlementDate)}}</td>
<td>
<span v-if="item.FloatRateCode">{{item.FloatRateCode}}+</span>
<span>{{item.Rate}}</span>
</td>
<td>{{item.Settlement?"是":"否"}}</td>
</tr>
@@ -1,4 +1,4 @@
//otcformat禁止千分位分组
//otcformat禁止千分位分组
window.otcformat.options.disableGrouping = true;
const consClients = ylotc.clients;
@@ -126,6 +126,8 @@ const vue = new Vue({
ObservationUnit: 'D',
ObservationHolidayType: 'Following',
ObservationAlignEnd: true,
ObservationCalendar: 'Chn',
ObservationSettlementRules: 0,
DefaultTitle1Value: 0.1,
ObservationDataList: [],
CheckedAll: false,
@@ -344,7 +346,7 @@ const vue = new Vue({
}
x.SwapIntervalList.push(interval);
}
if (x.InterestType == 1 && x.interest_rest_days<=0) {
if (x.interest_rest_days != null && x.interest_rest_days <= 0) {
main.message("利息端第" + (index + 1) + "行重置频率必须大于0");
errorcount++;
return false;
@@ -461,9 +463,7 @@ const vue = new Vue({
}
},
changeInterestType(item) {
if (item.InterestType == 0) {
item.interest_rest_days = null;
} else {
if (!item.interest_rest_days) {
item.interest_rest_days = 1;
}
},
@@ -553,7 +553,7 @@ const vue = new Vue({
arr[index].Rate = swapRate;
});
var observationDates = JSON.stringify(item.SwapIntervalList);
item.SwapIntervals = observationDates;
item.InterestSwapInterval = observationDates;
});
} else {
@@ -582,6 +582,10 @@ const vue = new Vue({
thisObj.observation.ObservationUnit = observation ? observation.ObservationUnit : 'D';
thisObj.observation.ObservationHolidayType = observation ? observation.ObservationHolidayType : 'Following';
thisObj.observation.ObservationAlignEnd = observation ? observation.ObservationAlignEnd : true;
if (type == 1) {
thisObj.observation.ObservationCalendar = observation ? observation.ObservationCalendar : 'Chn';
thisObj.observation.ObservationSettlementRules = observation ? observation.ObservationSettlementRules : 0;
}
thisObj.observation.IsDeductPrincipal = observation ? observation.IsDeductPrincipal : true;
if (type == 0) {
thisObj.observation.IsDeductPrincipal = false;
@@ -603,6 +607,7 @@ const vue = new Vue({
thisObj.observation.ObservationStart = endTime;
}
thisObj.observation.ObservationDataList = [];
var floatRateCode = type == 1 ? (item.FloatRateUnderlyingCode || '--') : '';
thisObj.observation.IntervalList.forEach((value, num, arr) => {
var val = value.Rate;
var _date = "";
@@ -616,24 +621,47 @@ const vue = new Vue({
disabled = true;
itemChecked = false;
}
var obdate = {
date: _date,
val: _.toString(val) ? parseFloat(consNumberFormat.umpriceP(val)) : "",
itemChecked: itemChecked,
disabled: disabled
var obdate = {};
if (type == 1) {
var SettlementDate = value.Date;
if (value.SettlementDate!=null) {
SettlementDate = value.SettlementDate;
}
obdate = {
date: _date,
SettlementDate: _date,
floatRateCode: floatRateCode,
val: _.toString(val) ? parseFloat(consNumberFormat.umpriceP(val)) : "",
itemChecked: itemChecked,
disabled: disabled
};
} else {
obdate = {
date: _date,
val: _.toString(val) ? parseFloat(consNumberFormat.umpriceP(val)) : "",
itemChecked: itemChecked,
disabled: disabled
};
}
thisObj.observation.ObservationDataList.push(obdate);
});
// thisObj.initObservationCheckedAll();
var area = type == 1 ? ['800px', '600px'] : ['750px', '600px'];
layer.open({
type: 1,
area: ['580px', '560px'],
area: area,
title: "设置互换日期",
shadeClose: false,
shade: 0.4,
content: $("#observationInfosEdit")
});
},
//计算结算日期
calcSettleDate(observationDate, settlementRules) {
if (!observationDate) return "";
var m = new moment(observationDate);
var settleDate = m.add(settlementRules, 'days').format("YYYY-MM-DD");
return settleDate;
},
//生成观察日操作
GetObservationDates() {
var thisObj = this;
@@ -641,43 +669,97 @@ const vue = new Vue({
var observationUnit = thisObj.observation.ObservationUnit;
var observationHolidayType = thisObj.observation.ObservationHolidayType;
var alignEnd = thisObj.observation.ObservationAlignEnd;
var postData = {
startDate: thisObj.observation.ObservationStart, endDate: thisObj.trade.ExerciseDate, termStr: observationNum + observationUnit,
holidayAdjustment: observationHolidayType, alignEnd: alignEnd, calcMode: thisObj.trade.trade_swap.RateCalcMode
};
var settlementRules = thisObj.observation.ObservationSettlementRules;
var calendar = thisObj.observation.ObservationCalendar;
var floatRateCode = this.observationType == 1 ? (thisObj.getSwapList[thisObj.observation.index]?.FloatRateUnderlyingCode || '--') : '';
thisObj.observation.ObservationDataList = [];
main.post("/trade/GetObservationDateList", postData).done(
function (res) {
$.each(res.obj, function (i) {
var _date = "";
var m = new moment(this);
if (!isNaN(m.date())) {
_date = m.format("YYYY-MM-DD");
}
var val = parseFloat(consNumberFormat.umpriceP(thisObj.observation.DefaultTitle1Value));
var itemChecked = true;
var disabled = false;
if (_date == thisObj.trade.ExerciseDate) {
disabled = true;
itemChecked = false;
}
var obdate = {
date: _date,
val: val,
itemChecked: itemChecked,
disabled: disabled
}
thisObj.observation.ObservationDataList.push(obdate);
if (this.observationType == 1) {
var postData = {
startDate: thisObj.observation.ObservationStart,
endDate: thisObj.trade.ExerciseDate,
termStr: observationNum + observationUnit,
holidayAdjustment: observationHolidayType,
alignEnd: alignEnd,
calcMode: thisObj.trade.trade_swap.RateCalcMode,
calendar: calendar,
settlementRules: settlementRules
};
main.post("/trade/GetSwapObservationDateList", postData).done(
function (res) {
$.each(res.obj, function (i) {
var _date = "";
var m = new moment(this.ObservationDate);
if (!isNaN(m.date())) {
_date = m.format("YYYY-MM-DD");
}
var _settleDate = "";
var sm = new moment(this.SettleDate);
if (!isNaN(sm.date())) {
_settleDate = sm.format("YYYY-MM-DD");
}
var val = parseFloat(consNumberFormat.umpriceP(thisObj.observation.DefaultTitle1Value));
var itemChecked = true;
var disabled = false;
if (_date == thisObj.trade.ExerciseDate) {
disabled = true;
itemChecked = false;
}
var obdate = {
date: _date,
SettlementDate: _settleDate,
floatRateCode: floatRateCode,
val: val,
itemChecked: itemChecked,
disabled: disabled
};
thisObj.observation.ObservationDataList.push(obdate);
});
thisObj.initObservationCheckedAll();
});
thisObj.initObservationCheckedAll();
});
} else {
var postData = {
startDate: thisObj.observation.ObservationStart,
endDate: thisObj.trade.ExerciseDate,
termStr: observationNum + observationUnit,
holidayAdjustment: observationHolidayType,
alignEnd: alignEnd,
calcMode: thisObj.trade.trade_swap.RateCalcMode
};
main.post("/trade/GetObservationDateList", postData).done(
function (res) {
$.each(res.obj, function (i) {
var _date = "";
var m = new moment(this);
if (!isNaN(m.date())) {
_date = m.format("YYYY-MM-DD");
}
var val = parseFloat(consNumberFormat.umpriceP(thisObj.observation.DefaultTitle1Value));
var itemChecked = true;
var disabled = false;
if (_date == thisObj.trade.ExerciseDate) {
disabled = true;
itemChecked = false;
}
var obdate = {
date: _date,
val: val,
itemChecked: itemChecked,
disabled: disabled
};
thisObj.observation.ObservationDataList.push(obdate);
});
thisObj.initObservationCheckedAll();
});
}
},
//编辑观察日功能数据处理
SetObservationDates() {
var observationStr = "";
if (this.observation.ObservationDataList != null) {
this.observation.ObservationDataList.forEach(item => {
observationStr = observationStr + item.date + ", " + (item.val * 0.01).toFixed(6) + ", " + item.itemChecked + ";\n";
observationStr = observationStr + item.date + ", " + item.SettlementDate + ", " + (item.val * 0.01).toFixed(6) + ", " + item.itemChecked + ";\n";
});
}
this.observation.ObservationInterval = observationStr;
@@ -712,18 +794,21 @@ const vue = new Vue({
var observationDates = thisObj.observation.ObservationInterval;
var items = observationDates.split(";").filter(o => o);
thisObj.observation.ObservationDataList = [];
var floatRateCode = thisObj.getSwapList[thisObj.observation.index]?.FloatRateUnderlyingCode || '--';
items.forEach(function (item) {
if (item) {
var values = item.split(",");
var itemChecked = JSON.parse(values[2].trim());
var itemChecked = JSON.parse(values[3].trim());
var disabled = false;
if (values[0] == thisObj.trade.ExerciseDate) {
disabled = true;
itemChecked = false;
}
var val = values[1].trim();
var val = values[2].trim();
var obdate = {
date: values[0],
SettlementDate: values[1] || "",
floatRateCode: floatRateCode,
val: _.toString(val) ? parseFloat(consNumberFormat.umpriceP(val)) : "",
itemChecked: itemChecked,
disabled: disabled
@@ -758,11 +843,24 @@ const vue = new Vue({
//添加观察日
addnewitem() {
var thisObj = this;
var obdate = {
date: '',
val: 0,
itemChecked: true,
disabled: false
var obdate = {};
if (this.observationType == 1) {
var floatRateCode = thisObj.getSwapList[thisObj.observation.index]?.FloatRateUnderlyingCode || '--';
obdate = {
date: '',
SettlementDate: '',
floatRateCode: floatRateCode,
val: 0,
itemChecked: true,
disabled: false
};
} else {
obdate = {
date: '',
val: 0,
itemChecked: true,
disabled: false
};
}
thisObj.observation.ObservationDataList.push(obdate);
},
@@ -792,7 +890,8 @@ const vue = new Vue({
var observation = {
Date: item.date,
Rate: item.val * 0.01,
Settlement: item.itemChecked ? 1 : 0
Settlement: item.itemChecked ? 1 : 0,
SettlementDate: item.SettlementDate || null // 结算日期
}
observationArr.push(observation);
@@ -805,11 +904,11 @@ const vue = new Vue({
alert("请输入正确的数字格式");
return false;
}
if (this.observationType == 0) {
if (this.observationType == 1) {
thisObj.getSwapList.forEach((val, num, arr) => {
if (val.index == thisObj.observation.index) {
arr[num].SwapIntervals = JSON.stringify(observationArr);
thisObj.observation.ObservationInterval = arr[num].SwapIntervals;
arr[num].InterestSwapInterval = JSON.stringify(observationArr);
thisObj.observation.ObservationInterval = arr[num].InterestSwapInterval;
arr[num].SwapIntervalList = observationArr;
arr[num].Obervation = JSON.parse(JSON.stringify(thisObj.observation));
}
@@ -817,8 +916,8 @@ const vue = new Vue({
} else {
thisObj.marginSwapList.forEach((val, num, arr) => {
if (val.index == thisObj.observation.index) {
arr[num].SwapIntervals = JSON.stringify(observationArr);
thisObj.observation.ObservationInterval = arr[num].SwapIntervals;
arr[num].InterestSwapInterval = JSON.stringify(observationArr);
thisObj.observation.ObservationInterval = arr[num].InterestSwapInterval;
arr[num].SwapIntervalList = observationArr;
arr[num].Obervation = JSON.parse(JSON.stringify(thisObj.observation));
}
@@ -1081,11 +1180,23 @@ const vue = new Vue({
thisObj.getSwapList = thisObj.trade.swap_positions.filter(x => { if ((x.UnderlyingCode == null || x.UnderlyingCode.length == 0) && x.IsInitial && (x.InterestMode == 1 || x.InterestMode == 2 || x.InterestMode == 7 || x.InterestMode == 8 || x.InterestMode == 9)) return x; });
thisObj.getSwapList.forEach((val, num, arr) => {
arr[num].index = num;
// 解析 InterestSwapInterval 为 SwapIntervalList
if (arr[num].InterestSwapInterval && !arr[num].SwapIntervalList) {
try {
arr[num].SwapIntervalList = JSON.parse(arr[num].InterestSwapInterval);
} catch (e) { }
}
});
thisObj.marginSwapList = thisObj.trade.swap_positions.filter(x => { if ((x.UnderlyingCode == null || x.UnderlyingCode.length == 0) && x.IsInitial && (x.InterestMode == 5 || x.InterestMode == 6)) return x; });
thisObj.marginSwapList.forEach((val, num, arr) => {
arr[num].index = num;
arr[num].index = 1000 + num; // 保证金列表使用 1000+ 偏移,避免与利息腿冲突
arr[num].HappenDate = thisObj.formatDate(arr[num].HappenDate);
// 解析 InterestSwapInterval 为 SwapIntervalList
if (arr[num].InterestSwapInterval && !arr[num].SwapIntervalList) {
try {
arr[num].SwapIntervalList = JSON.parse(arr[num].InterestSwapInterval);
} catch (e) { }
}
});
if (thisObj.trade.StructureType == '多空组合') {
thisObj.paySwapList = [];
@@ -44,8 +44,8 @@ function downloadFiles(files) {
window.open(files);
}
}
function showSwapRate(timeRate) {
vueDetails.initSwapIntervalList(timeRate);
function showSwapRate(timeRate, isMarginLeg) {
vueDetails.initSwapIntervalList(timeRate, isMarginLeg);
$("#swapIntervalModal").modal("show");
}
var layerIndex = -1;
@@ -192,7 +192,7 @@ function unWindSwapTrade(id) {
function unWindSwap(id) {
var title = "收益结算";
var srcurl = "/swaptrade2/SwapIncome/?enid=" + id;
main.post("/swaptrade2/CheckEodTrade?enid=" + id).done(function (res) {
main.post("/swaptrade2/CheckEodTradeForIncome?enid=" + id).done(function (res) {
if (res.success) {
main.open(title,
srcurl,
@@ -375,7 +375,8 @@ function reload() {
var vueDetails = new Vue({
el: "#swapIntervalModal",
data: {
SwapIntervalList:[]
SwapIntervalList:[],
isMarginLeg: false // 是否是保证金腿
},
created: function () {
},
@@ -383,13 +384,18 @@ var vueDetails = new Vue({
closeModal: function () {
$("#swapIntervalModal").modal("hide");
},
initSwapIntervalList(swapIntervals) {
initSwapIntervalList(swapIntervals, isMarginLeg) {
var that = this;
that.isMarginLeg = isMarginLeg;
that.SwapIntervalList = [];
if (swapIntervals.length>0) {
if (swapIntervals && swapIntervals.length>0) {
that.SwapIntervalList = JSON.parse(swapIntervals);
that.SwapIntervalList.forEach((item, index) => {
that.SwapIntervalList[index].Rate = otcformat.trading.marginRateP(item.Rate);
// 如果结算日期为空,默认等于观察日期
if (!item.SettlementDate) {
that.SwapIntervalList[index].SettlementDate = item.Date;
}
});
}
},