Merge branch 'glms/feature/1.4.2' of https://gitee.glmszq.com/gsty/onederiv/trs into glms/feature/1.4.2

This commit is contained in:
尹峰
2026-06-11 17:49:11 +08:00
17 changed files with 3216 additions and 1659 deletions
@@ -124,7 +124,7 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
protected override string GetOutputFileName(string contractNo, string contractIndex)
{
var trade = Context.Trade;
var rule = $"{trade.TradeType}交易确认书_{contractNo}";
var rule = $"【待用印】{trade.TradeType}交易确认书_{contractNo}";
return $"{rule}.{DocType.ToLower()}";
}
@@ -152,9 +152,9 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
dic["乙方名称"] = client.Name;
// 交易日期相关
dic["成交日期"] = trade.TradeDate?.ToString("yyyy年M月d日");
dic["开始日期"] = trade.StartDate?.ToString("yyyy年M月d日");
dic["到期日期"] = trade.ExerciseDate?.ToString("yyyy年M月d日");
dic["成交日期"] = trade.TradeDate?.ToString("yyyy】年【M】月【d】日");
dic["开始日期"] = trade.StartDate?.ToString("yyyy】年【M】月【d】日");
dic["到期日期"] = trade.ExerciseDate?.ToString("yyyy】年【M】月【d】日");
// 根据交易类型填充不同数据
switch (trade.TradeType)
@@ -222,8 +222,8 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
var bond = JsonHelper.Deserialize<UnderlyingBond>(underlying.ExJson) ?? new UnderlyingBond();
dic["参考标的发行人"] = bond.UnderlyingIssuer ?? "";
dic["参考标的担保人"] = "";
dic["票面利率"] = ((double)(bond.CouponRate ?? 0) * 100).ToString("N4");
dic["参考标的到期日"] = underlying.MaturityDate?.ToString("yyyy年M月d日") ?? "";
dic["票面利率"] = ((double)(bond.CouponRate ?? 0)).ToString("0.00");
dic["参考标的到期日"] = underlying.MaturityDate?.ToString("yyyy】年【M】月【d】日") ?? "";
}
// 从swap_position获取期初价格、保证金率等信息
@@ -235,7 +235,7 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
if (swapPosition != null)
{
// 期初全价和净价(转换为百分比格式)
dic["参考标的期初全价"] = ((double)swapPosition.PosiGrossPrice).ToString("N4");
dic["参考标的期初全价"] = ((double)swapPosition.PosiGrossPrice).ToString("0.000");
dic["参考标的期初净价"] = ((double)(swapPosition.PosiNetNoFeePrice ?? 0m)).ToString("N4");
dic["参考标的期初全价%"] = ((double)swapPosition.PosiGrossPrice * 100).ToString("N4");
@@ -257,40 +257,43 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
dic["维持保障金率"] = ((double)clientMarginRate.maintain_rate * 100).ToString("N4");
// 期初预付比例和金额
dic["期初预付比例"] = ((double)clientMarginRate.init_rate * 100).ToString("N4");
dic["期初预付比例"] = ((double)clientMarginRate.init_rate * 100).ToString("0.##");
dic["期初预付金额"] = ((trade.OriginalStockEqvNotional ?? 0) * (double)clientMarginRate.init_rate).ToString("N2");
}
else
{
dic["初始保障金率"] = "0.0000";
dic["维持保障金率"] = "0.0000";
dic["期初预付比例"] = "0.0000";
dic["期初预付比例"] = "0";
dic["期初预付金额"] = "0.00";
}
bool posiLong = IsCustomerLong(swapPosition);
// 计算平仓线、预警线、档位值(利率债TRS,固定4档)
var maintainRatePercent = (double)(clientMarginRate?.maintain_rate ?? 0) * 100; // 维持保证金率 A(%)
// A = 维持保证金率(%),B = 初始保证金率(%)diff = B - A
var maintainRatePercent = (double)(clientMarginRate?.maintain_rate ?? 0) * 100; // A(%)
var initRatePercent = (double)(clientMarginRate?.init_rate ?? 0) * 100; // B(%)
var diff = initRatePercent - maintainRatePercent; // B - A
if (maintainRatePercent > 0)
{
if (posiLong)
{
// 客户看多:档位n = 100 - n * A
dic["平仓线"] = (100 - 4 * maintainRatePercent).ToString("0.##");
dic["预警线"] = (100 - 3 * maintainRatePercent).ToString("0.##");
dic["档位1"] = (100 - 1 * maintainRatePercent).ToString("0.##");
dic["档位2"] = (100 - 2 * maintainRatePercent).ToString("0.##");
dic["档位3"] = (100 - 3 * maintainRatePercent).ToString("0.##");
dic["档位4"] = (100 - 4 * maintainRatePercent).ToString("0.##");
// 客户看多:档位n = 100 - n * (B-A)
dic["档位1"] = (100 - 1 * diff).ToString("0.##");
dic["档位2"] = (100 - 2 * diff).ToString("0.##");
dic["档位3"] = (100 - 3 * diff).ToString("0.##");
dic["档位4"] = (100 - 4 * diff).ToString("0.##");
dic["预警线"] = dic["档位4"]; // 预警线 = 最高档位
dic["平仓线"] = (100 - 5 * diff).ToString("0.##"); // 平仓线 = 最高档位 - (B-A)
}
else
{
// 客户看空:档位n = 100 + n * A
dic["平仓线"] = (100 + 4 * maintainRatePercent).ToString("0.##");
dic["预警线"] = (100 + 3 * maintainRatePercent).ToString("0.##");
dic["档位1"] = (100 + 1 * maintainRatePercent).ToString("0.##");
dic["档位2"] = (100 + 2 * maintainRatePercent).ToString("0.##");
dic["档位3"] = (100 + 3 * maintainRatePercent).ToString("0.##");
dic["档位4"] = (100 + 4 * maintainRatePercent).ToString("0.##");
// 客户看空:档位n = 100 + n * (B-A)
dic["档位1"] = (100 + 1 * diff).ToString("0.##");
dic["档位2"] = (100 + 2 * diff).ToString("0.##");
dic["档位3"] = (100 + 3 * diff).ToString("0.##");
dic["档位4"] = (100 + 4 * diff).ToString("0.##");
dic["预警线"] = dic["档位4"]; // 预警线 = 最高档位
dic["平仓线"] = (100 + 5 * diff).ToString("0.##"); // 平仓线 = 最高档位 + (B-A)
}
}
else
@@ -303,6 +306,96 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
dic["档位4"] = "";
}
// 追保事件/返还追保事件 价格区间和金额比例
if (maintainRatePercent > 0)
{
if (posiLong)
{
// === 客户看多 追保事件 ===
// 区间0:当前净价 ≥ {100-(B-A)}% × 期初净价,追保0%
dic["追保区间0下限"] = (100 - 1 * diff).ToString("0.##");
dic["追保金额比例0"] = "0";
// 区间1{100-2A}% ≤ 当前净价 < {100-A}%,追保{B-A}%
dic["追保区间1下限"] = (100 - 2 * maintainRatePercent).ToString("0.##");
dic["追保区间1上限"] = (100 - 1 * maintainRatePercent).ToString("0.##");
dic["追保金额比例1"] = (1 * diff).ToString("0.##");
// 区间2{100-3A}% ≤ 当前净价 < {100-2A}%,追保{2(B-A)}%
dic["追保区间2下限"] = (100 - 3 * maintainRatePercent).ToString("0.##");
dic["追保区间2上限"] = (100 - 2 * maintainRatePercent).ToString("0.##");
dic["追保金额比例2"] = (2 * diff).ToString("0.##");
// 区间3{100-4A}% ≤ 当前净价 < {100-3A}%,追保{3(B-A)}%
dic["追保区间3下限"] = (100 - 4 * maintainRatePercent).ToString("0.##");
dic["追保区间3上限"] = (100 - 3 * maintainRatePercent).ToString("0.##");
dic["追保金额比例3"] = (3 * diff).ToString("0.##");
// === 客户看多 返还追保事件 ===
// 区间0:当前净价 ≥ {100-(B-A)}% × 期初净价,返还0%
dic["返还区间0下限"] = (100 - 1 * diff).ToString("0.##");
dic["返还金额比例0"] = "0";
// 区间1{100-2(B-A)}% ≤ 当前净价 < {100-(B-A)}%,返还{B-A}%
dic["返还区间1下限"] = (100 - 2 * diff).ToString("0.##");
dic["返还区间1上限"] = (100 - 1 * diff).ToString("0.##");
dic["返还金额比例1"] = (1 * diff).ToString("0.##");
// 区间2{100-3(B-A)}% ≤ 当前净价 < {100-2(B-A)}%,返还{2(B-A)}%
dic["返还区间2下限"] = (100 - 3 * diff).ToString("0.##");
dic["返还区间2上限"] = (100 - 2 * diff).ToString("0.##");
dic["返还金额比例2"] = (2 * diff).ToString("0.##");
// 区间3{100-4(B-A)}% ≤ 当前净价 < {100-3(B-A)}%,返还{3(B-A)}%
dic["返还区间3下限"] = (100 - 4 * diff).ToString("0.##");
dic["返还区间3上限"] = (100 - 3 * diff).ToString("0.##");
dic["返还金额比例3"] = (3 * diff).ToString("0.##");
}
else
{
// === 客户看空 追保事件 ===
// 区间0:当前净价 ≤ {100+(B-A)}% × 期初净价,追保0%
dic["追保区间0上限"] = (100 + 1 * diff).ToString("0.##");
dic["追保金额比例0"] = "0";
// 区间1{100+(B-A)}% < 当前净价 ≤ {100+2(B-A)}%,追保{B-A}%
dic["追保区间1下限"] = (100 + 1 * diff).ToString("0.##");
dic["追保区间1上限"] = (100 + 2 * diff).ToString("0.##");
dic["追保金额比例1"] = (1 * diff).ToString("0.##");
// 区间2{100+2(B-A)}% < 当前净价 ≤ {100+3(B-A)}%,追保{2(B-A)}%
dic["追保区间2下限"] = (100 + 2 * diff).ToString("0.##");
dic["追保区间2上限"] = (100 + 3 * diff).ToString("0.##");
dic["追保金额比例2"] = (2 * diff).ToString("0.##");
// 区间3{100+3(B-A)}% < 当前净价 ≤ {100+4(B-A)}%,追保{3(B-A)}%
dic["追保区间3下限"] = (100 + 3 * diff).ToString("0.##");
dic["追保区间3上限"] = (100 + 4 * diff).ToString("0.##");
dic["追保金额比例3"] = (3 * diff).ToString("0.##");
// === 客户看空 返还追保事件 ===
// 区间0:当前净价 ≤ {100+(B-A)}% × 期初净价,返还0%
dic["返还区间0上限"] = (100 + 1 * diff).ToString("0.##");
dic["返还金额比例0"] = "0";
// 区间1{100+(B-A)}% < 当前净价 ≤ {100+2(B-A)}%,返还{B-A}%
dic["返还区间1下限"] = (100 + 1 * diff).ToString("0.##");
dic["返还区间1上限"] = (100 + 2 * diff).ToString("0.##");
dic["返还金额比例1"] = (1 * diff).ToString("0.##");
// 区间2{100+2(B-A)}% < 当前净价 ≤ {100+3(B-A)}%,返还{2(B-A)}%
dic["返还区间2下限"] = (100 + 2 * diff).ToString("0.##");
dic["返还区间2上限"] = (100 + 3 * diff).ToString("0.##");
dic["返还金额比例2"] = (2 * diff).ToString("0.##");
// 区间3{100+3(B-A)}% < 当前净价 ≤ {100+4(B-A)}%,返还{3(B-A)}%
dic["返还区间3下限"] = (100 + 3 * diff).ToString("0.##");
dic["返还区间3上限"] = (100 + 4 * diff).ToString("0.##");
dic["返还金额比例3"] = (3 * diff).ToString("0.##");
}
}
else
{
// 清空追保/返还相关字典值
for (int i = 0; i <= 3; i++)
{
dic[$"追保区间{i}下限"] = "";
dic[$"追保区间{i}上限"] = "";
dic[$"追保金额比例{i}"] = "";
dic[$"返还区间{i}下限"] = "";
dic[$"返还区间{i}上限"] = "";
dic[$"返还金额比例{i}"] = "";
}
}
// 期初预付金利率(InterestMode == 初始预付金)
var initialMarginPosition = swapPositions
.Where(x => x.InterestMode == (int)InterestModeEnum.)
@@ -326,8 +419,8 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
dic["IsFixed"] = "□";
dic["IsFloat"] = "□";
dic["期初观察日"] = trade.StartDate?.ToString("yyyy年M月d日") ?? "";
dic["期末观察日"] = trade.ExerciseDate?.ToString("yyyy年M月d日") ?? "";
dic["期初观察日"] = trade.StartDate?.ToString("yyyy】年【M】月【d】日") ?? "";
dic["期末观察日"] = trade.ExerciseDate?.ToString("yyyy】年【M】月【d】日") ?? "";
if (interestMargin != null)
{
if (string.IsNullOrEmpty(interestMargin.FloatRateUnderlyingCode))
@@ -349,8 +442,8 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
{
var firstInterval = interestMargin.SwapIntervalList.OrderBy(o=>o.Date).First();
var lastInterval = interestMargin.SwapIntervalList.OrderBy(o => o.Date).Last();
dic["期初观察日"] = firstInterval.Date.ToString("yyyy年M月d日") ?? "";
dic["期末观察日"] = lastInterval.Date.ToString("yyyy年M月d日") ?? "";
dic["期初观察日"] = firstInterval.Date.ToString("yyyy】年【M】月【d】日") ?? "";
dic["期末观察日"] = lastInterval.Date.ToString("yyyy】年【M】月【d】日") ?? "";
}
}
else
@@ -364,7 +457,7 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
var notional = trade.OriginalStockEqvNotional ?? 0;
var tradingFee = (double)swapPosition.PosiTradingFeePending;
var basicFeeRate = notional == 0 ? 0 : tradingFee / notional * 100;
dic["基本费率"] = basicFeeRate.ToString("N4");
dic["基本费率"] = basicFeeRate.ToString("0.##");
// 期初现金交换比例和金额(使用初始预付金数据)
dic["期初现金交换比例"] = initialMarginPosition != null
@@ -376,31 +469,31 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
}
else
{
dic["参考标的期初全价"] = "0.0000";
dic["参考标的期初全价"] = "0.000";
dic["参考标的期初净价"] = "0.0000";
dic["初始保障金率"] = "0.0000";
dic["维持保障金率"] = "0.0000";
dic["期初预付比例"] = "0.0000";
dic["期初预付比例"] = "0";
dic["期初预付金额"] = "0.00";
dic["期初预付金利率"] = "0.0000";
dic["利率类型"] = "固定利率";
dic["固定利率"] = "0.0000";
dic["利差"] = "";
dic["基本费率"] = "0.0000";
dic["基本费率"] = "0";
dic["期初现金交换比例"] = "0.0000";
dic["期初现金交换金额"] = "0.00";
}
// 参考标的券面总额(名义本金)
dic["参考标的券面总额"] = trade.OriginalStockEqvNotional?.ToString("N2") ?? "0.00";
dic["参考标的券面总额"] = swapPosition != null ? ((double)swapPosition.PosiQuantity).ToString("N2") : "0";
// 参考标的证券全称和参考标的名义份额(复用上方已声明的bond)
dic["参考标的证券全称"] = underlying != null
? (JsonHelper.Deserialize<UnderlyingBond>(underlying.ExJson)?.UnderlyingFullName ?? underlying.UnderlyingName)
: "";
dic["参考标的名义份额"] = swapPosition != null
? ((double)swapPosition.PosiQuantity).ToString("N2")
: "0.00";
? ((double)swapPosition.PosiQuantity).ToString("0.##")
: "0";
dic["参考标的基金管理人"] = "";
var contractTypeId = (Context.GetContractTypes().FirstOrDefault(O => O.ContactType == "交易确认书接收")?.id) ?? 0;
// 乙方联系人信息
@@ -417,8 +510,8 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
["标的代码"] = underlying?.UnderlyingCode ?? "",
["标的名称"] = underlying?.UnderlyingName ?? "",
["名义本金"] = trade.OriginalStockEqvNotional?.ToString("N2") ?? "0.00",
["成交日期"] = trade.TradeDate?.ToString("yyyy年M月d日"),
["到期日期"] = trade.ExerciseDate?.ToString("yyyy年M月d日")
["成交日期"] = trade.TradeDate?.ToString("yyyy】年【M】月【d】日"),
["到期日期"] = trade.ExerciseDate?.ToString("yyyy】年【M】月【d】日")
};
table1.Add(row);
dic["table1"] = table1;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+95 -41
View File
@@ -333,7 +333,7 @@ namespace YLErp.Modules.SwapModule
var grossPrice = realPostitions.Where(x => x.PosiDirection > 0).FirstOrDefault()?.PosiGrossPrice;
var closeList = DbContext.swap_flow_event.Where(x => x.SwapTradeId == tradeId && x.UnwindDate == unwindDate && eventTypes.Contains(x.EventType) && x.DataState == (int)SwapFlowDateStateEnum.).ToList();
bool tdClose = closeList.Count > 0;
interests = GetInterests(td, tradeExtend, valueDate, unwindDate, lastEodPositions, positions, stockEqvNotional, posiLongNotionalValue, posiShortNotionalValue, posiNotionalValue, closePercent, eventType, tdClose, false, grossPrice ?? 0, orginPv, true, false, closeList);
interests = GetInterests(td, tradeExtend, valueDate, unwindDate, lastEodPositions, positions, stockEqvNotional, posiLongNotionalValue, posiShortNotionalValue, posiNotionalValue, closePercent, eventType, tdClose, false, grossPrice ?? 0, orginPv, true, false,false, closeList);
return interests;
}
/// <summary>
@@ -372,6 +372,7 @@ namespace YLErp.Modules.SwapModule
decimal orginPv,
bool add = false,
bool settment = true,
bool newCalcLast= false,
List<swap_flow_event> closeList = null)
{
List<swap_flow_event> interests = new List<swap_flow_event>();
@@ -409,21 +410,35 @@ namespace YLErp.Modules.SwapModule
else
{
// 盘中互换场景,使用 CalcUnwindInterest
interests.Add(CalcUnwindInterest(td, valueDate, endDate, positionClone, rate, floatRate, posiPrincipal, closePrincipal, newClosePercent, annualDays, preEodPosition, eventType, add, swap, orginPv, calcFirst, calcLast));
interests.Add(CalcUnwindInterest(td, valueDate, endDate, positionClone, rate, floatRate, posiPrincipal, closePrincipal, newClosePercent, annualDays, preEodPosition, eventType, add, swap, orginPv, calcFirst, calcLast||newCalcLast));
}
}
//当日有平仓或互换记录时,需要把平仓或互换已经结算的利息从计算结果中扣除,避免重复
//当日有平仓或互换记录时,避免重复
if (closeList != null && closeList.Count > 0)
{
foreach (var item in interests)
{
var closeEvent = closeList.Where(x => x.PositionId == item.PositionId);
var closePnl = closeEvent.Sum(s => s.InterestClosePnL);
var closeAmount = closeEvent.Sum(s => s.InterestAmount);
var closeTdAmount = closeEvent.Sum(s => s.TdInterestAmount);
item.InterestAmount = item.InterestAmount - closeAmount;
item.TdInterestAmount = item.TdInterestAmount - closeTdAmount;
item.InterestClosePnL = item.InterestClosePnL - closePnl;
if (eventType == (int)SwapEventTypeEnum. || eventType == (int)SwapEventTypeEnum.)
{
// 互换:该仓位当天已有完成事件,直接归0
if (closeEvent.Any())
{
item.InterestAmount = 0;
item.TdInterestAmount = 0;
item.InterestClosePnL = 0;
}
}
else if (!calcLast && !newCalcLast)
{
// 平仓不算尾:扣除已结算的利息(算尾时利息已包含关闭日,无重叠)
var closePnl = closeEvent.Sum(s => s.InterestClosePnL);
var closeAmount = closeEvent.Sum(s => s.InterestAmount);
var closeTdAmount = closeEvent.Sum(s => s.TdInterestAmount);
item.InterestAmount = item.InterestAmount - closeAmount;
item.TdInterestAmount = item.TdInterestAmount - closeTdAmount;
item.InterestClosePnL = item.InterestClosePnL - closePnl;
}
}
}
return interests;
@@ -656,11 +671,11 @@ namespace YLErp.Modules.SwapModule
var floateRate = preEodPosition.FloatRate;
if (position.InterestType == (int)InterestTypeEnum.)
{
CalcDailyCompoundInterest(preEodPosition, endDate, position, closePosiNotionalValue, posiNotionalValue, interest, annualDays, needPrice, floateRate, closePrecent, orginPv, calcFirst, calcLast, ref InterestAmount, ref TdInterestAmount);
CalcDailyCompoundInterest( endDate, position, closePosiNotionalValue, interest, annualDays, needPrice, floateRate, closePrecent, orginPv, calcFirst, calcLast, ref InterestAmount, ref TdInterestAmount);
}
else
{
CalcDailySimpleInterest(preEodPosition, endDate, position, closePosiNotionalValue, posiNotionalValue, interest, annualDays, needPrice, floateRate, closePrecent, orginPv, calcFirst, calcLast, ref InterestAmount, ref TdInterestAmount);
CalcDailySimpleInterest(preEodPosition, endDate, position, posiNotionalValue, interest, annualDays, needPrice, floateRate, closePrecent, orginPv, calcFirst, calcLast, ref InterestAmount, ref TdInterestAmount);
}
interest.InterestAmount = Math.Round(InterestAmount, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
@@ -684,26 +699,73 @@ namespace YLErp.Modules.SwapModule
/// <param name="isAnnualized">是否年化</param>
/// <param name="annualDays">年化天数</param>
/// <returns></returns>
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, bool calcFirst, bool calcLast, ref decimal InterestAmount, ref decimal TdInterestAmount)
public void CalcDailyCompoundInterest( DateTime endDate, swap_position position, decimal principal, swap_flow_event flowEvent, int annualDays, bool needPrice, decimal floateRate, decimal closePercent, decimal orginPv, bool calcFirst, bool calcLast, ref decimal InterestAmount, ref decimal TdInterestAmount)
{
// 复利:利息并入本金
CalcDailyInterest(preEodPosition, endDate, position, principal, posiPrincipal, flowEvent, annualDays, needPrice, floateRate, closePercent, orginPv, compoundInterest: true, calcFirst, calcLast, ref InterestAmount, ref TdInterestAmount);
var startDate = position.PosiStartDate;
decimal interestProfitSum = 0;
decimal TdInterestPrincipal = 0;
decimal interest = interestProfitSum ;
decimal tdinterest = interestProfitSum ;
int interestPeriod = position.interest_rest_days ?? 1;
// 复利:只能用要平仓的名义本金从头开始算
decimal dynomicPrincipal = principal;
decimal tdDynomicPrincipal = dynomicPrincipal;
var calcDays = (endDate - startDate).Days;
double floatRate = Convert.ToDouble(floateRate);
for (int i = 0; i <= calcDays; i++)
{
var accrueDate = startDate.AddDays(i);
if (!calcFirst && accrueDate == startDate) continue; // 首日不算头
if (!calcLast && accrueDate == endDate) continue; // 到期日不算尾
if (accrueDate >= startDate)
{
if (i % interestPeriod == 0)
{
// 复利时:利息并入本金
dynomicPrincipal = principal + interest;
tdDynomicPrincipal = principal + interest;
// 获取新的浮动利率
if (!string.IsNullOrEmpty(position.FloatRateUnderlyingCode))
{
var fr007RateDate = QdpCalendarHelper.GetNonHolidayDefore(accrueDate.AddDays(position.interest_rule ?? 0));
if (TryGetFloatRate(fr007RateDate, position.FloatRateUnderlyingCode, out double floatRate1))
{
if (floatRate1 != 0) floatRate = floatRate1;
}
else
{
throw new Exception($"获取不到{position.FloatRateUnderlyingCode}在{fr007RateDate:yyyy年MM月dd日}的价格");
}
}
flowEvent.InterestPrincipal = tdDynomicPrincipal;
TdInterestPrincipal = tdDynomicPrincipal;
}
else
{
// 复利非重置日:利息不并入本金,不用closePercent缩放(principal已反映平仓比例)
flowEvent.InterestPrincipal = tdDynomicPrincipal;
TdInterestPrincipal = tdDynomicPrincipal;
}
flowEvent.FloatRate = Convert.ToDecimal(floatRate);
var interest1 = flowEvent.InterestPrincipal * (flowEvent.InterestRate + Convert.ToDecimal(floatRate));
var tdinterest1 = TdInterestPrincipal * (flowEvent.InterestRate + Convert.ToDecimal(floatRate));
if (position.IsAnnualized)
{
interest1 /= annualDays;
tdinterest1 /= annualDays;
}
interest += interest1;
tdinterest += tdinterest1;
}
}
InterestAmount = Math.Round(interest, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
TdInterestAmount = Math.Round(tdinterest, ConsGlobal.PriceRound, MidpointRounding.AwayFromZero);
}
/// <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, bool calcFirst, bool calcLast, ref decimal InterestAmount, ref decimal TdInterestAmount)
{
// 单利:利息不并入本金
CalcDailyInterest(preEodPosition, endDate, position, principal, posiPrincipal, flowEvent, annualDays, needPrice, floateRate, closePercent, orginPv, compoundInterest: false,calcFirst,calcLast, 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, bool calcFirst, bool calcLast, ref decimal InterestAmount, ref decimal TdInterestAmount)
public void CalcDailySimpleInterest(eod_swap_position preEodPosition, DateTime endDate, swap_position position, decimal posiPrincipal, swap_flow_event flowEvent, int annualDays, bool needPrice, decimal floateRate, decimal closePercent, decimal orginPv, bool calcFirst, bool calcLast, ref decimal InterestAmount, ref decimal TdInterestAmount)
{
var startDate = position.PosiStartDate;
decimal interestProfitSum = preEodPosition.InterestProfitSum;
@@ -711,35 +773,27 @@ namespace YLErp.Modules.SwapModule
decimal interest = interestProfitSum * closePercent;
decimal tdinterest = interestProfitSum * closePercent;
int interestPeriod = position.interest_rest_days ?? 1;
decimal dynomicPrincipal = principal;
decimal tdDynomicPrincipal = posiPrincipal;
// 单利:可用上一日计息基数
decimal dynomicPrincipal = preEodPosition.TdInterestPrincipal + posiPrincipal - orginPv;
decimal tdDynomicPrincipal = dynomicPrincipal;
var calcDays = (endDate - startDate).Days;
double floatRate = Convert.ToDouble(floateRate);
for (int i = 0; i <= calcDays; i++)
{
var accrueDate = startDate.AddDays(i);
if (!calcFirst && accrueDate == startDate) continue; // 首日不算头
if (!calcLast && accrueDate == endDate) continue; // 到期日不算尾
if (!calcFirst && accrueDate == startDate) continue; // 首日不算头
if (!calcLast && accrueDate == endDate) continue; // 到期日不算尾
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(accrueDate.AddDays(position.interest_rule ?? 0));
if (TryGetFloatRate(fr007RateDate, position.FloatRateUnderlyingCode, out double floatRate1))
{
if (floatRate1 != 0)
{
floatRate = floatRate1;
}
if (floatRate1 != 0) floatRate = floatRate1;
}
else
{
@@ -751,9 +805,9 @@ namespace YLErp.Modules.SwapModule
}
else
{
flowEvent.InterestPrincipal = (preEodPosition.TdInterestPrincipal + posiPrincipal - orginPv) * closePercent;
flowEvent.InterestPrincipal = tdDynomicPrincipal * closePercent;
tdDynomicPrincipal = flowEvent.InterestPrincipal;
TdInterestPrincipal = (preEodPosition.TdInterestPrincipal + posiPrincipal - orginPv);
TdInterestPrincipal = tdDynomicPrincipal;
}
flowEvent.FloatRate = Convert.ToDecimal(floatRate);
var interest1 = flowEvent.InterestPrincipal * (flowEvent.InterestRate + Convert.ToDecimal(floatRate));
@@ -62,7 +62,7 @@ namespace YLErp.Modules.SwapModule
public void SwapPositionCompose(DateTime settleDate, DateTime preSettleDate, IEnumerable<int> ClientIds)
{
var dateStr = settleDate.ToString("yyyy-MM-dd");
Log.Info("SwapPositionCompose:"+"settleDate:" + settleDate+ " preSettleDate:"+ preSettleDate+ " ClientIds:"+JsonHelper.Serialize(ClientIds));
Log.Info("SwapPositionCompose:" + "settleDate:" + settleDate + " preSettleDate:" + preSettleDate + " ClientIds:" + JsonHelper.Serialize(ClientIds));
var tradePredicate = PredicateBuilder.Create<trade>(n => n.ValidState != ConsGlobal.InValid
&& n.TradeType == "收益互换"
&& n.TradeDate <= settleDate
@@ -221,32 +221,32 @@ namespace YLErp.Modules.SwapModule
decimal orginPv)
{
Log.Info("===================处理利息腿归档====================");
// 添加详细的参数验证日志
Log.Info($"[DealInterests] 参数验证 - settleDate: {settleDate:yyyy-MM-dd}, td.id: {td?.id}, td.TradeNumber: {td?.TradeNumber}");
Log.Info($"[DealInterests] 参数验证 - interestList.Count: {interestList?.Count ?? 0}, eodPositions.Count: {eodPositions?.Count ?? 0}, todyEodPositions.Count: {todyEodPositions?.Count ?? 0}");
Log.Info($"[DealInterests] 参数验证 - flowEvents.Count: {flowEvents?.Count ?? 0}, autoInterests.Count: {autoInterests?.Count ?? 0}");
Log.Info($"[DealInterests] 参数验证 - posiLongNational: {posiLongNational}, posiShortNational: {posiShortNational}, closeNational: {closeNational}, grossPrice: {grossPrice}, orginPv: {orginPv}");
// 验证关键参数
if (td == null)
{
Log.Info("[DealInterests] 参数验证: td (trade) 为 null");
throw new ArgumentNullException(nameof(td), "交易对象不能为null");
}
if (interestList == null)
{
Log.Info($"[DealInterests] 参数验证: interestList 为 null, td.id: {td.id}");
throw new ArgumentNullException(nameof(interestList), "利息腿列表不能为null");
}
if (flowEvents == null)
{
Log.Info($"[DealInterests] 参数验证: flowEvents 为 null, td.id: {td.id}");
throw new ArgumentNullException(nameof(flowEvents), "流水事件列表不能为null");
}
if (autoInterests == null)
{
Log.Info($"[DealInterests] 参数验证: autoInterests 为 null, td.id: {td.id}");
@@ -390,14 +390,14 @@ namespace YLErp.Modules.SwapModule
}
unwindData.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? 0);
unwindData.PosiNotionalValue = StockEqvNotional;
// 预付金腿类型列表:初始预付金、追加预付金
var premiumModes = new List<int>() {(int)InterestModeEnum., (int)InterestModeEnum. };
var premiumModes = new List<int>() { (int)InterestModeEnum., (int)InterestModeEnum. };
// 分别计算预付金腿和利息腿的金额
var premiumInterests = autoInterests.Where(x => premiumModes.Contains(x.InterestMode)).ToList();
var interestLegs = autoInterests.Where(x => !premiumModes.Contains(x.InterestMode)).ToList();
// 预付金腿金额
decimal premiumTotal = 0;
premiumInterests.ForEach(x =>
@@ -406,7 +406,7 @@ namespace YLErp.Modules.SwapModule
premiumTotal += x.InterestClosePnL * ratio;
});
unwindData.SwapMarginRebatePnl = premiumTotal; // 预付金腿金额
// 利息腿金额(总金额减去预付金腿金额)
decimal interestTotal = 0;
interestLegs.ForEach(x =>
@@ -415,10 +415,10 @@ namespace YLErp.Modules.SwapModule
interestTotal += x.InterestClosePnL * ratio;
});
unwindData.SwapCloseAmount = interestTotal; // 利息腿金额
// 总实现盈亏
unwindData.SwapRealizedPnL = unwindData.SwapCloseAmount + unwindData.SwapMarginRebatePnl;
SaveAutoSwapDeal(td, autoInterests, unwindData, interval);
}
/// <summary>
@@ -432,7 +432,7 @@ namespace YLErp.Modules.SwapModule
//td.UnWindDate = unwindData.ValueDate;
//优先使用 interval.SettlementDate 作为资金记录发生日期,如果没有则使用 ValueDate
var cashHappenDate = interval?.SettlementDate ?? unwindData.ValueDate;
int clientCashId = 0;
// 利息腿:插入资金记录(使用系统操作_互换)
if (unwindData.SwapCloseAmount != 0)
@@ -445,7 +445,7 @@ namespace YLErp.Modules.SwapModule
{
AddClientCashInCashOut(td, Convert.ToDouble(-unwindData.SwapMarginRebatePnl), ClientCashInCashOut._预付金返息, unwindData.ValueDate);
}
string data = JsonConvert.SerializeObject(unwindData);
var swapEvent = new SwapEventService(this).AddSwapEventDate(unwindData.ValueDate, unwindData.SwapTradeId, (int)SwapEventTypeEnum., data, clientCashId, true, "系统操作-自动互换");//将互换总额存入事件
flowEvents.ForEach(x =>
@@ -731,7 +731,7 @@ namespace YLErp.Modules.SwapModule
newEodPayPosition.InterestFeeSum = eodPayPosition.InterestFeeSum + newEodPayPosition.TdInterestFee - newEodPayPosition.TdCloseInterestFee;
newEodPayPosition.InterestProfitSum = newEodPayPosition.InterestIncomeSum + newEodPayPosition.InterestFeeSum;
//持仓价值
newEodPayPosition.SwapPositionValue = newEodPayPosition.InterestProfitSum * ratio + newEodPayPosition.PosiProfitSum;
newEodPayPosition.SwapPositionValue = newEodPayPosition.InterestProfitSum * ratio + newEodPayPosition.PosiProfitSum;
//累计已实现
newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio;
@@ -759,41 +759,41 @@ namespace YLErp.Modules.SwapModule
private List<swap_flow_event> SaveAutoEodInterestPosition(eod_swap_position eodPayPosition, eod_swap_position newEodPayPosition, swap_position position, trade td, DateTime valueDate, IntervalModel interval, eod_swap lastEodSwap, decimal posiLongNotional, decimal posiShortNational, decimal grossPrice, decimal orginPv)
{
Log.Info($"[SaveAutoEodInterestPosition] 开始执行 - valueDate: {valueDate:yyyy-MM-dd}, td.id: {td?.id}, position.id: {position?.id}");
// 详细的参数验证
if (td == null)
{
Log.Info("[SaveAutoEodInterestPosition] 参数验证: td (trade) 为 null");
throw new ArgumentNullException(nameof(td), "交易对象不能为null");
}
if (position == null)
{
Log.Info($"[SaveAutoEodInterestPosition] 参数验证: position 为 null, td.id: {td.id}");
throw new ArgumentNullException(nameof(position), "持仓对象不能为null");
}
if (interval == null)
{
Log.Info($"[SaveAutoEodInterestPosition] 参数验证: interval 为 null, td.id: {td.id}, position.id: {position.id}");
throw new ArgumentNullException(nameof(interval), "观察日信息不能为null");
}
if (td.trade_extend == null)
{
Log.Info($"[SaveAutoEodInterestPosition] 参数验证: td.trade_extend 为 null, td.id: {td.id}, td.TradeNumber: {td.TradeNumber}");
throw new ArgumentNullException("td.trade_extend", "交易扩展信息不能为null");
}
Log.Info($"eodPayPosition is {JsonHelper.Serialize(eodPayPosition, false)},newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}");
// 验证 ExtendObj
if (td.trade_extend.ExtendObj == null)
{
Log.Info($"[SaveAutoEodInterestPosition] 参数验证: td.trade_extend.ExtendObj 为 null, td.id: {td.id}");
throw new ArgumentNullException("td.trade_extend.ExtendObj", "交易扩展对象不能为null");
}
var tradeExtend = td.trade_extend.ExtendObj;
decimal oriPosiNotionalValue = posiLongNotional + posiShortNational;
decimal posiNotionalValue = oriPosiNotionalValue;
@@ -902,20 +902,18 @@ namespace YLErp.Modules.SwapModule
Log.Info($"eodPayPosition is {JsonHelper.Serialize(eodPayPosition, false)},newEodPayPosition is {JsonHelper.Serialize(newEodPayPosition, false)}");
var tradeExtend = td.trade_extend.ExtendObj;
decimal oriPosiNotionalValue = posiLongNotional + posiShortNational + closeNational;
decimal posiNotionalValue = oriPosiNotionalValue;
decimal posiNotionalValue = posiLongNotional + posiShortNational;
decimal ratio = position.InterestDirection == (int)SwapDirectionEnum. ? 1m : -1m;//收取为正,支付为负
if (marginTypes.Contains(position.InterestMode))
{
ratio = -ratio;
}
if (eodPayPosition == null)
{
eodPayPosition = new eod_swap_position();
eodPayPosition.ClientId = td.ClientId;
eodPayPosition.SwapTradeId = td.id;
eodPayPosition.PosiStartDate = td.StartDate.Value;
eodPayPosition.PosiMatuirityDate = td.ExerciseDate.Value;
}
var lastInterestIncomeSum = eodPayPosition.InterestIncomeSum;
eodPayPosition = new eod_swap_position();
eodPayPosition.ClientId = td.ClientId;
eodPayPosition.SwapTradeId = td.id;
eodPayPosition.PosiStartDate = td.StartDate.Value;
eodPayPosition.PosiMatuirityDate = td.ExerciseDate.Value;
if (newEodPayPosition == null)
{
newEodPayPosition = eodPayPosition.Clone();
@@ -926,6 +924,10 @@ namespace YLErp.Modules.SwapModule
{
orginPv = eodPayPosition.InterestPrincipalFix;
}
else
{
orginPv = posiNotionalValue;
}
decimal closePercent = oriPosiNotionalValue == 0 ? 0 : closeNational / oriPosiNotionalValue;
var eventType = autoSwap ? (int)SwapEventTypeEnum. : (int)SwapEventTypeEnum.;
bool longShort = td.StructureType == ClientMarginTypeEnum..ToString();
@@ -941,9 +943,9 @@ namespace YLErp.Modules.SwapModule
positions.Add(position);
List<eod_swap_position> preEodPositions = new List<eod_swap_position>();
preEodPositions.Add(eodPayPosition);
var interests = new SwapDealService(this).GetInterests(td, td.trade_extend, valueDate, valueDate, preEodPositions, positions, posiNotionalValue, posiLongNotional, posiShortNational, closeNational, 1, eventType, false, true, grossPrice, orginPv, true);
var interests = new SwapDealService(this).GetInterests(td, td.trade_extend, valueDate, valueDate, preEodPositions, positions, posiNotionalValue, posiLongNotional, posiShortNational, closeNational, 1, eventType, false, true, grossPrice, orginPv, true, settment: false, newCalcLast: true);
decimal TdInterestAmount = interests.Sum(x => x.TdInterestAmount);
decimal InterestAmount = interests.Sum(x => x.InterestAmount);
newEodPayPosition.ValueDate = valueDate;
newEodPayPosition.PositionId = position.id;
UpdateDbOption(newEodPayPosition);
@@ -964,7 +966,7 @@ namespace YLErp.Modules.SwapModule
newEodPayPosition.interest_rest_days = position.interest_rest_days;
newEodPayPosition.interest_rule = position.interest_rule;
//利息端估值用信息
newEodPayPosition.TdInterestPrincipal = interestModes.Contains(position.InterestMode) ? eodPayPosition.InterestPrincipalFix : posiLongNotional + posiShortNational;
newEodPayPosition.TdInterestPrincipal = interests.Count > 0 ? interests.First().InterestPrincipal : 0;
if (interval != null)
{
newEodPayPosition.TdInterestRate = interval.Rate;
@@ -977,8 +979,13 @@ namespace YLErp.Modules.SwapModule
newEodPayPosition.TdInterestFee = flowEvents.Sum(s => s.InterestFee);
newEodPayPosition.TdCloseInterestFee = newEodPayPosition.TdInterestFee;
newEodPayPosition.TdCloseInterest = flowEvents.Sum(s => s.InterestClosePnL);
newEodPayPosition.TdInterestIncome = TdInterestAmount * (1 - closePercent);
Log.Info($"InterestIncomeSum is {eodPayPosition.InterestIncomeSum},TdInterestIncome is {newEodPayPosition.TdInterestIncome}" +
var intersetAcmount = newEodPayPosition.TdInterestPrincipal*(newEodPayPosition.TdInterestRate + newEodPayPosition.FloatRate);
if (position.IsAnnualized)
{
intersetAcmount /= tradeExtend.AnnualDays;
}
newEodPayPosition.TdInterestIncome = intersetAcmount;
Log.Info($"InterestIncomeSum is {lastInterestIncomeSum},TdInterestIncome is {newEodPayPosition.TdInterestIncome}" +
$",TdCloseInterest is {newEodPayPosition.TdCloseInterest}");
Log.Info($"InterestFeeSum is {eodPayPosition.InterestFeeSum},TdInterestFee is {newEodPayPosition.TdInterestFee}" +
$",TdCloseInterestFee is {newEodPayPosition.TdCloseInterestFee}");
@@ -988,7 +995,7 @@ namespace YLErp.Modules.SwapModule
}
else
{
newEodPayPosition.InterestIncomeSum = eodPayPosition.InterestIncomeSum + newEodPayPosition.TdInterestIncome - newEodPayPosition.TdCloseInterest * ratio;
newEodPayPosition.InterestIncomeSum = InterestAmount;
}
//持仓内容-利息腿-损益统计(本方视角)
newEodPayPosition.InterestFeeSum = eodPayPosition.InterestFeeSum + newEodPayPosition.TdInterestFee - newEodPayPosition.TdCloseInterestFee;
@@ -1001,9 +1008,9 @@ namespace YLErp.Modules.SwapModule
Log.Info($"InterestFeeSum is {eodPayPosition.InterestFeeSum},TdInterestFee is {newEodPayPosition.TdInterestFee}" +
$",TdCloseInterestFee is {newEodPayPosition.TdCloseInterestFee}");
//累计已实现
newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio ;
newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio;
newEodPayPosition.RealizedInterestFee = eodPayPosition.RealizedInterestFee + newEodPayPosition.TdCloseInterestFee;
newEodPayPosition.RealizedPnl = newEodPayPosition.RealizedInterest + newEodPayPosition.RealizedInterestFee;;
newEodPayPosition.RealizedPnl = newEodPayPosition.RealizedInterest + newEodPayPosition.RealizedInterestFee; ;
var currencyRate = new EodCurrencyRateService(UserInfo).GetCurrencyRate(td.QuoteCurrency, td.SettlementCurrency, valueDate
, seekPreday: true, currencyRateType: position.InterestDirection == (int)SwapDirectionEnum. ? CurrencyRateType.Buy : CurrencyRateType.Sell);
newEodPayPosition.TdCurrency = Convert.ToDecimal(currencyRate);
@@ -1040,7 +1047,7 @@ namespace YLErp.Modules.SwapModule
eodPayPosition.ClientId = td.ClientId;
eodPayPosition.SwapTradeId = td.id;
//eodPayPosition.PositionId = position.id; 为了算利息时找不到给日期重新赋值
eodPayPosition.InterestMode = position.InterestMode;
eodPayPosition.InterestPrincipalFix = position.InterestPrincipalFix;
eodPayPosition.InterestRateDefault = position.InterestRateDefault;
@@ -1123,7 +1130,7 @@ namespace YLErp.Modules.SwapModule
newEodPayPosition.InterestFeeSum = eodPayPosition.InterestFeeSum + newEodPayPosition.TdInterestFee - newEodPayPosition.TdCloseInterestFee;
newEodPayPosition.InterestProfitSum = newEodPayPosition.InterestIncomeSum + newEodPayPosition.InterestFeeSum;
//持仓价值
newEodPayPosition.SwapPositionValue = newEodPayPosition.InterestProfitSum * ratio + newEodPayPosition.PosiProfitSum;
newEodPayPosition.SwapPositionValue = newEodPayPosition.InterestProfitSum * ratio + newEodPayPosition.PosiProfitSum;
//累计已实现
newEodPayPosition.RealizedInterest = eodPayPosition.RealizedInterest + newEodPayPosition.TdCloseInterest * ratio;
@@ -2002,8 +2009,10 @@ namespace YLErp.Modules.SwapModule
{
var predicate = PredicateBuilder.Create<eod_swap_position>(n => !n.Invalid && n.PosiQuantity > 0);
var interestPredicate = PredicateBuilder.Create<eod_swap_position>(n => !n.Invalid && n.InterestDirection > 0);
var tradePredicate = PredicateBuilder.Create<trade>(n => n.StructureType == req.StructureType
&& n.ValidState != "InValid");
var tradePredicate = PredicateBuilder.Create<trade>(n => n.ValidState != "InValid");
// TODO 这里暂时忽略前端传的值 暂时使用临时方案:同时查普通债券类收益互换和普通收益互换
tradePredicate = tradePredicate.And(n => n.StructureType == "普通债券类收益互换" || n.StructureType == "普通收益互换");
if (req.ClientId > 0)
{
predicate = predicate.And(x => x.ClientId == req.ClientId);
@@ -2055,7 +2064,7 @@ namespace YLErp.Modules.SwapModule
{
eventDate = QdpCalendarHelper.GetNonHoliday(eventDate.AddDays(tradeExtend.ExtendObj.SettlementRules));
}
item.DayCount = Math.Max(0,(eventDate - item.position.PosiStartDate).Days + 1);
item.DayCount = Math.Max(0, (eventDate - item.position.PosiStartDate).Days + 1);
//item.position.PosiProfitSum += item.position.VTradingFee-item.position.PosiFeePending;
SetClientEodPosition(item.position);
//item.position.PosiProfitSum += item.TradingFee;
@@ -35,7 +35,7 @@ namespace YLErp.Modules.SwapModule
public eod_commodity_future_price SearchTodayFRData(DateTime dateTime)
{
var data = DbContext.eod_commodity_future_price.Where(a => a.ValueDate == dateTime).FirstOrDefault();
var data = DbContext.eod_commodity_future_price.Where(a => a.ValueDate == dateTime && a.UnderlyingCode == "FR007").FirstOrDefault();
if (data == null)
{
data = new eod_commodity_future_price();
@@ -80,7 +80,7 @@ namespace YLErp.Modules.SwapModule
public bool AddOrUpdateFRdata(Double price, DateTime dateTime)
{
string beforedate = "";
var frdata = DbContext.eod_commodity_future_price.Where(a => a.ValueDate == dateTime).FirstOrDefault();
var frdata = DbContext.eod_commodity_future_price.Where(a => a.ValueDate == dateTime && a.UnderlyingCode == "FR007").FirstOrDefault();
if (frdata == null)
{
frdata = new eod_commodity_future_price();
@@ -1,4 +1,5 @@
using YLErp.BLL;
using BaseOUDAL;
using YLErp.BLL;
using YLErp.BLL.Eod;
using YLErp.Model;
using YLErp.Model.Enum;
@@ -143,6 +144,7 @@ namespace YLErp.Modules.TradeModule.DealModule
var count = tradeProessQuery.Count();
if (count == 0 || td.ProcessOrderId == ProcessTradeLog.)//投资规模校验已经将数据设置为已通过
{
req.comments = FormatCommentsWithRole(td.ProcessOrderId, req.comments);
result.ChangeConfirmPath = SetTradeOpen(req, td);
return result;
}
@@ -166,7 +168,8 @@ namespace YLErp.Modules.TradeModule.DealModule
//}
if (nextOrder==null)
{
req.comments = FormatCommentsWithRole(td.ProcessOrderId, req.comments);
result.ChangeConfirmPath = SetTradeOpen(req, td);
return result;
}
@@ -175,6 +178,8 @@ namespace YLErp.Modules.TradeModule.DealModule
// 继续审批流转
//-----------------------------------------------
var oldOrderId = td.ProcessOrderId;
var formattedComments = FormatCommentsWithRole(oldOrderId, req.comments);
td.ProcessOrderId = nextOrder.order;
//client.ProcessStatus = "审批中";
td.ProcessOptDate = OptDate;
@@ -194,7 +199,12 @@ namespace YLErp.Modules.TradeModule.DealModule
if (!req.notNeedOperationHistory)
{
AddTradeOperationHistoryAndSetParentTradeInfo(false, td, req.ignoreMoneyCheck ? "交易特批-交易审批同意" : "交易审批同意", req.comments);
AddTradeOperationHistoryAndSetParentTradeInfo(false, td, req.ignoreMoneyCheck ? "交易特批-交易审批同意" : "交易审批同意", formattedComments);
}
if (td.TradeType == "收益互换")
{
new SwapEventService(UserInfo).AddSwapEventDate(DateTime.Now.Date, td.id, (int)SwapEventTypeEnum., "", 0, true, formattedComments);
}
DbContext.SaveChanges();
@@ -278,6 +288,8 @@ namespace YLErp.Modules.TradeModule.DealModule
/// </summary>
private TradeOpenResult TradeReject(TradeOpenReqModel req, trade td)
{
var oldOrderId = td.ProcessOrderId;
req.comments = FormatCommentsWithRole(oldOrderId, req.comments);
td.ProcessOrderId = Convert.ToInt32(PStatusEnum.reject);
td.ProcessOrderBranch = 0;
//td.IsApproval = false;
@@ -299,7 +311,7 @@ namespace YLErp.Modules.TradeModule.DealModule
var swapFlowEvents = DbContext.swap_flow_event.Where(x => x.SwapTradeId == td.id && x.EventType == swapTradeEventType && x.EventDate == swapEvent.ValueDate);
DbContext.swap_flow_event.RemoveRange(swapFlowEvents);
}
new SwapEventService(UserInfo).AddSwapEventDate(DateTime.Now.Date, td.id, (int)SwapEventTypeEnum., "", 0, true, "提前终止拒绝");
new SwapEventService(UserInfo).AddSwapEventDate(DateTime.Now.Date, td.id, (int)SwapEventTypeEnum., "", 0, true, req.comments);
}
}
else
@@ -311,7 +323,7 @@ namespace YLErp.Modules.TradeModule.DealModule
}
if (td.TradeType == "收益互换")
{
new SwapEventService(UserInfo).AddSwapEventDate(DateTime.Now.Date, td.id, (int)SwapEventTypeEnum., "", 0, true, "交易审批拒绝");
new SwapEventService(UserInfo).AddSwapEventDate(DateTime.Now.Date, td.id, (int)SwapEventTypeEnum., "", 0, true, req.comments);
}
}
td.ProcessStatus = ProcessTradeStatus..ToString();
@@ -515,7 +527,7 @@ namespace YLErp.Modules.TradeModule.DealModule
if (td.TradeType=="收益互换")
{
SwapTradeService swapTradeService = new SwapTradeService(this);
new SwapEventService(UserInfo).AddSwapEventDate(DateTime.Now.Date, td.id, (int)SwapEventTypeEnum., "", 0, true);
new SwapEventService(UserInfo).AddSwapEventDate(DateTime.Now.Date, td.id, (int)SwapEventTypeEnum., "", 0, true, req.comments);
new TradeConfirmService(UserInfo).SwapTradeConfirm(td, "确认交易",false, optlog: "手工操作");
}
//组合交易(非互换)
@@ -727,6 +739,27 @@ namespace YLErp.Modules.TradeModule.DealModule
throw;
}
}
private string GetRoleNameByOrderId(int orderId)
{
var node = DbContext.approvalprocess
.FirstOrDefault(x => x.processType == "TradeProcess" && x.order == orderId);
if (node == null)
return null;
return new ErpBaseContext().Roles
.Where(r => r.Id == node.roleId)
.Select(r => r.Name)
.FirstOrDefault();
}
private string FormatCommentsWithRole(int orderId, string comments)
{
var roleName = GetRoleNameByOrderId(orderId);
if (string.IsNullOrEmpty(roleName))
return comments ?? string.Empty;
return $"【角色:{roleName}】{comments}";
}
}
/// <summary>
@@ -8,8 +8,10 @@ namespace YLErp.Modules.TradeModule.DocGenerateModule
/// <summary>
/// 国贸交易确认书编号生成器
/// 规则:
/// - 对客交易(OpponentRole为"方"):GLMS-{对手方代码}-{成交日期(yyyyMMdd)}-{标的代码}-{当日第N笔对客交易(数字递增)},从001开始
/// - 非对客交易(OpponentRole为"乙方"):GLMS-{对手方代码}-{成交日期(yyyyMMdd)}-{标的代码}-{当日第N笔非对客交易(字母递增)},从A开始
/// - 对客交易(OpponentRole为"方"):GLMS-{对手方代码}-{成交日期(yyyyMMdd)}-{当日第N笔对客交易(数字递增)}-{标的代码},从01开始
/// 例:GLMS-IS-20260424-01-250210IB
/// - 非对客交易(OpponentRole为"甲方"):GLMS-{对手方代码}-{成交日期(yyyyMMdd)}-{当日第N笔非对客交易使用字母递增}-{标的代码},从A开始
/// 例:GLMS-IS-20260424-A-250210IB
/// </summary>
public class GuolianContractNoGenerator
{
@@ -27,15 +29,16 @@ namespace YLErp.Modules.TradeModule.DocGenerateModule
trade trade,
string clientCode)
{
// 根据trade的OpponentRole判断是否为对客交易
var isClientTrade = trade.OpponentRole == "方";
var underlyingCode = trade.UnderlyingCode ?? "";
// 根据trade的OpponentRole判断是否为对客交易(乙方=对客)
var isClientTrade = trade.OpponentRole == "方";
var underlyingCode = (trade.UnderlyingCode ?? "").Replace(".", ""); // 去掉标的代码中的点号
var tradeDate = trade.TradeDate ?? DateTime.MinValue;
var tradeDateStr = tradeDate.ToString("yyyyMMdd");
var prefix = $"GLMS-{clientCode}-{tradeDateStr}-{underlyingCode}-";
var prefix = $"GLMS-{clientCode}-{tradeDateStr}-";
lock (_syncLock)
{
// 幂等:如果已生成过确认书编号,直接返回
var existingForTrade = dbContext.trade_contract_r
.Where(r => r.TradeId == trade.id && r.Type == ContractTypeEnum.Trade && r.IsValid)
.Select(r => r.ContractCode)
@@ -46,111 +49,27 @@ namespace YLErp.Modules.TradeModule.DocGenerateModule
return existingForTrade;
}
var existingCodes = dbContext.trade_contract_r
.Where(d => d.ContractCode.StartsWith(prefix) && d.Type == ContractTypeEnum.Trade && d.IsValid)
.Select(d => d.ContractCode)
.ToList();
var existingCodeSet = new HashSet<string>();
for (var i = 0; i < existingCodes.Count; i++)
{
var code = existingCodes[i];
if (!string.IsNullOrWhiteSpace(code))
{
existingCodeSet.Add(code);
}
}
// 通过当天同客户同类交易的 id 排序确定序号(幂等),每个客户独立递增
var tradeDateStart = tradeDate.Date;
var sameDayTradesCount = dbContext.trade
.Count(t => t.TradeDate >= tradeDateStart
&& t.TradeDate < tradeDateStart.AddDays(1)
&& t.ClientId == trade.ClientId
&& t.OpponentRole == trade.OpponentRole
&& t.id < trade.id);
var sequenceNo = sameDayTradesCount + 1;
if (isClientTrade)
{
var maxNo = 0;
var prefixLen = prefix.Length;
for (var i = 0; i < existingCodes.Count; i++)
{
var code = existingCodes[i];
if (string.IsNullOrEmpty(code) || code.Length <= prefixLen)
{
continue;
}
var suffix = code.Substring(prefixLen);
if (int.TryParse(suffix, out var n) && n > maxNo)
{
maxNo = n;
}
}
var nextNo = maxNo + 1;
var contractNo = $"{prefix}{nextNo:D3}";
while (existingCodeSet.Contains(contractNo))
{
nextNo++;
contractNo = $"{prefix}{nextNo:D3}";
}
return contractNo;
return $"{prefix}{sequenceNo:D2}-{underlyingCode}";
}
else
{
var maxNo = 0;
var prefixLen = prefix.Length;
for (var i = 0; i < existingCodes.Count; i++)
{
var code = existingCodes[i];
if (string.IsNullOrEmpty(code) || code.Length <= prefixLen)
{
continue;
}
var suffix = code.Substring(prefixLen);
if (!TryParseLettersToNumber(suffix, out var n))
{
continue;
}
if (n > maxNo)
{
maxNo = n;
}
}
var nextNo = maxNo + 1;
var contractNo = $"{prefix}{NumberToLetter(nextNo)}";
while (existingCodeSet.Contains(contractNo))
{
nextNo++;
contractNo = $"{prefix}{NumberToLetter(nextNo)}";
}
return contractNo;
return $"{prefix}{NumberToLetter(sequenceNo)}-{underlyingCode}";
}
}
}
private static bool TryParseLettersToNumber(string letters, out int number)
{
number = 0;
if (string.IsNullOrEmpty(letters))
{
return false;
}
for (var i = 0; i < letters.Length; i++)
{
var c = letters[i];
if (c >= 'a' && c <= 'z')
{
c = (char)(c - 32);
}
else if (c < 'A' || c > 'Z')
{
number = 0;
return false;
}
number = (number * 26) + (c - 'A' + 1);
}
return true;
}
/// <summary>
/// 将数字转换为字母序列(1=A, 2=B, 26=Z, 27=AA, 28=AB...
/// </summary>
@@ -194,6 +194,27 @@ namespace YLErp.Modules.UnderlyingModule
}
}
/// <summary>
/// 清除指定标的的保证金率缓存
/// </summary>
/// <param name="underlyingCode">标的代码,为空时清除全部</param>
public static void ClearMarginRateCache(string underlyingCode = null)
{
try
{
if (ylCache == null) return;
var cachePattern = string.IsNullOrEmpty(underlyingCode)
? "ClientMarginRate:*"
: $"ClientMarginRate:*:{underlyingCode}:*";
ylCache.BatchDelete(cachePattern);
}
catch (Exception ex)
{
LogFactory.GetLogger("UnderlyingHelper").Error($"清除保证金率缓存时发生异常: {ex.Message}", ex);
}
}
/// <summary>
/// 获取篮子标的品种对象
/// </summary>
+5 -5
View File
@@ -2108,16 +2108,16 @@ namespace YLErp.Web.Controllers
OptName = n.OptName,
OptDate = n.OptDate
}).ToArray();
datas = datas.GroupBy(O => new { O.OptDate, O.OptName, O.OptType }).Select(O => O.First()).OrderByDescending(n => n.id).ToArray();
datas = datas.OrderByDescending(n => n.id).ToArray();
foreach (var item in datas)
{
if (IsExchange)
if (IsExchange && item.OptType == "批量调整-标的交易")
{
item.Changes = item.OptType == "批量调整-标的交易" ? new DatatraceQueryService(CurUser).ReplaceDataChanges(new trade(), item.Changes) : "";
item.Changes = new DatatraceQueryService(CurUser).ReplaceDataChanges(new trade(), item.Changes);
}
else
else if (!IsExchange && item.OptType == "修改交易")
{
item.Changes = item.OptType == "修改交易" ? new DatatraceQueryService(CurUser).ReplaceDataChanges(new trade(), item.Changes) : "";
item.Changes = new DatatraceQueryService(CurUser).ReplaceDataChanges(new trade(), item.Changes);
}
if (!string.IsNullOrEmpty(item.Changes))
{
@@ -477,6 +477,7 @@ namespace YLErp.Web.Controllers
model.ExJson = JsonHelper.Serialize(model.Bond);
}
var r = new UnderlyingDalService(CurUser).SaveUnderlyingData(model);
YLErp.Modules.UnderlyingModule.UnderlyingHelper.ClearMarginRateCache(model.UnderlyingCode);
return JsonSuccess("更新成功", r);
}
@@ -100,17 +100,17 @@
<div id="selectTemplate">
@Html.MyAceDropdownInput2("setTemplate", "模板名:&nbsp; ", YLErp.Web.Controllers.clientbalanceController.GetAllClientBalanceTemplate(), false, "", true, new { onchange = "changeTemplate()" })
</div>
<h3>发件人:</h3>
<select id="sendUser">
<option value="@defaultUser.From">@defaultUser.UserName</option>
@if (otherUsers != null)
{
foreach (var user in otherUsers)
{
<option value="@user.From">@user.UserName</option>
}
}
</select>
@* <h3>发件人:</h3> *@
@* <select id="sendUser"> *@
@* <option value="@defaultUser.From">@defaultUser.UserName</option> *@
@* @if (otherUsers != null) *@
@* { *@
@* foreach (var user in otherUsers) *@
@* { *@
@* <option value="@user.From">@user.UserName</option> *@
@* } *@
@* } *@
@* </select> *@
@if (!PS.Config.Is申万)
{
<div class="form-group">