#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
+296 -151
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)
// 根据场景计算利息
if (settment)
{
position.FloatRate = preEodPosition.FloatRate;
positionClone.FloatRate = preEodPosition.FloatRate;
// 收盘归档场景,使用 CalcEodInterest
interests.Add(CalcEodInterest(td, valueDate, positionClone, rate, floatRate, closePrincipal, posiPrincipal, annualDays, calcFirst, calcLast, preEodPosition, eventType, add));
}
else
{
// 如果没有 preEodPosition 数据或需要查找新 Rate,则去查询最新的浮动利率
if (EodPriceQueryService.TryGetPrice(rateDate, position.FloatRateUnderlyingCode, out double floatRate))
{
position.FloatRate = Convert.ToDecimal(floatRate);
positionClone.FloatRate = position.FloatRate;
// 盘中互换场景,使用 CalcUnwindInterest
interests.Add(CalcUnwindInterest(td, valueDate, endDate, positionClone, rate, floatRate, posiPrincipal, closePrincipal, newClosePercent, annualDays, preEodPosition, eventType, add, swap, orginPv));
}
else if (!swap)
{
throw new Exception($"获取不到{position.FloatRateUnderlyingCode}在{rateDate:yyyy年MM月dd日}的价格");
}
}
}
decimal rate = position.InterestRateDefault;
if (swapIntervalToday == null)//当日无适用观察日
{
var swapInterval = position.SwapIntervalList.Where(x => x.Date > startDate).OrderBy(o => o.Date).FirstOrDefault();
if (swapInterval != null)
{
rate = swapInterval.Rate;
}
}
else
{
rate = swapIntervalToday.Rate;
}
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);
}
// 如果不在重置周期内,使用 td.StartDate 来获取 rateDate
return QdpCalendarHelper.GetNonHolidayDefore(startDate.AddDays(interest_rule ?? 0));
/// <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;
}
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
)
{
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);
CalcDailyCompoundInterest(preEodPosition, endDate, 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);
}
}
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)
{
// 复利时:利息并入本金
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;
}
@@ -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; }
}
}
+30 -7
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 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>
+9 -4
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,6 +1013,7 @@
<thead>
<tr>
<td> 观察日期 </td>
<td v-if="!isMarginLeg"> 结算日期 </td>
<td> 互换利率</td>
<td> 是否结算 </td>
</tr>
@@ -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 = {
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,11 +669,64 @@ 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 = [];
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();
});
} 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) {
@@ -666,18 +747,19 @@ const vue = new Vue({
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 = {
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;
}
});
}
},