Merge branch 'glms/feature/1.4.2' of http://git.yiliantech.com/gitlab/otc-dev/zszq-trs into glms/feature/1.4.2
This commit is contained in:
@@ -46,6 +46,27 @@ namespace YLErp.Models
|
||||
|
||||
public string Quantity { get; set; }
|
||||
|
||||
/// <summary>融资腿挂钩的基准利率代码;未挂钩时展示“固定利率”。</summary>
|
||||
public string BaseRate { get; set; }
|
||||
|
||||
/// <summary>ETF 在持有期间产生的客户视角分红金额。</summary>
|
||||
public string PeriodDividend { get; set; }
|
||||
|
||||
/// <summary>现券交易期初成交收益率。</summary>
|
||||
public string InitYtm { get; set; }
|
||||
|
||||
/// <summary>利率收益金额除以标的名义本金得到的绝对浮动利率。</summary>
|
||||
public string FloatRateAbs { get; set; }
|
||||
|
||||
/// <summary>InterestMode = 5 的期初预付金方向汇总金额。</summary>
|
||||
public string InitialMargin { get; set; }
|
||||
|
||||
/// <summary>InterestMode = 6 且结算日前已生效的追加预付金方向汇总金额。</summary>
|
||||
public string AdditionalMargin { get; set; }
|
||||
|
||||
/// <summary>净额结算金额加期初预付金、追加预付金后的到期结算金额。</summary>
|
||||
public string MaturitySettlementAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 期末标的结算收益率(EQD-6953)。普通债券类收益互换平仓收益率,展示态百分数,
|
||||
/// 固定 4 位小数不去零("0.0000");非债券/历史无值时为空串。
|
||||
|
||||
Binary file not shown.
@@ -4,6 +4,7 @@ using YLErp.DBModels;
|
||||
using YLErp.DBModels.Consts;
|
||||
using YLErp.DBModels.Enums;
|
||||
using YLErp.Models;
|
||||
using YLErp.Modules.TradeModule.DocGenerateModule;
|
||||
using YLErp.Plugins.TradeDocGenerator;
|
||||
using YLErp.Plugins.TradeDocGenerator.Abstracts;
|
||||
using YLErp.QdpModule;
|
||||
@@ -23,6 +24,7 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
|
||||
|
||||
protected override void PrepareViewData(Dictionary<string, object> dic)
|
||||
{
|
||||
// 普通 XLSX 结算单的公共上下文一次性加载,后续按平仓流水在内存中关联。
|
||||
var client = Context.GetClient();
|
||||
var flowEvents = Context.GetFlowEvents();
|
||||
var allFlowEvents = Context.GetAllFlowEvents();
|
||||
@@ -35,16 +37,18 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
|
||||
|
||||
foreach (var flowEventGroup in flowEvents)
|
||||
{
|
||||
// 为每条平仓流水组装交易、初始持仓腿及同 EventId 的结算流水。
|
||||
var tradeId = flowEventGroup.SwapTradeId;
|
||||
var positions = swapPositions.Where(x => x.SwapTradeId == tradeId && x.IsInitial).ToList();
|
||||
var posi = positions.FirstOrDefault(f => f.id == flowEventGroup.PositionId);
|
||||
var unwindFlowEvents = allFlowEvents.Where(x => x.EventId == flowEventGroup.EventId);
|
||||
var trade = trades.FirstOrDefault(x => x.id == tradeId);
|
||||
var preSettleDate = flowEventGroup.UnwindDate.Value.AddDays(-1);
|
||||
var swapEodPositions = Context.GetEodPositions(tradeId, preSettleDate);
|
||||
var posi = positions.FirstOrDefault(f => f.id == flowEventGroup.PositionId)
|
||||
?? throw new ServiceException($"平仓事件{flowEventGroup.id}未找到对应初始持仓");
|
||||
var unwindFlowEvents = allFlowEvents.Where(x => x.EventId == flowEventGroup.EventId).ToList();
|
||||
var trade = trades.OfType<trade>().FirstOrDefault(x => x.id == tradeId);
|
||||
if (trade == null)
|
||||
{
|
||||
throw new ServiceException($"平仓事件{flowEventGroup.id}未找到对应交易");
|
||||
}
|
||||
var tradeExtend = tradeExtends.FirstOrDefault(x => x.TradeId == tradeId);
|
||||
var ratio = posi.PositionType == (int)PositionTypeFlag.Long ? 1 : -1;
|
||||
ExcelReportModel row = new ExcelReportModel();
|
||||
var confirmNo = Context.Gettrade_contract_r(tradeId, ContractTypeEnum.Trade);
|
||||
if (string.IsNullOrEmpty(confirmNo))
|
||||
{
|
||||
@@ -52,78 +56,40 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
|
||||
// 报错交易常是扩张拉入的同客户同日平仓,并非用户勾选的那笔。
|
||||
throw new ServiceException($"{trade.TradeNumber}未生成交易确认书(平仓事件id={flowEventGroup.id}, tradeId={tradeId}, 客户={client.Name}, 平仓日={flowEventGroup.UnwindDate?.ToString("yyyy-MM-dd")});请先为该笔交易生成交易确认书后重试");
|
||||
}
|
||||
row.TradeNumber = confirmNo;
|
||||
row.ClientName = client.Name;
|
||||
row.UnderlyingCode = flowEventGroup.UnderlyingCode;
|
||||
row.StartDate = trade.StartDate.Value.ToString("yyyy-MM-dd");
|
||||
var eventDate = flowEventGroup.UnwindDate.Value;
|
||||
row.EventDate = eventDate.ToString("yyyy-MM-dd");
|
||||
var payDate = flowEventGroup.PayDate;
|
||||
var sr = tradeExtend == null ? 0 : tradeExtend.ExtendObj.SettlementRules;
|
||||
if (!payDate.HasValue)
|
||||
{
|
||||
payDate = QdpCalendarHelper.GetNonHoliday(flowEventGroup.EventDate.AddDays(sr));
|
||||
}
|
||||
row.PayDate = payDate.Value.ToString("yyyy-MM-dd");
|
||||
row.ClosePrice = ((flowEventGroup.TradingAmountAvg) * 100).ToString("0.00000000");
|
||||
decimal interestRate = unwindFlowEvents.Where(x => ConsTrade.InterestModels.Contains(x.InterestMode)).Sum(s => s.InterestRate);
|
||||
row.InterestRate = interestRate.ToString("0.00%");
|
||||
var PosiNotionalValue = flowEventGroup.Quantity * flowEventGroup.ContractSize * posi.PosiGrossPrice;
|
||||
// EQD-6953 期末标的结算收益率:平仓簿记时随 UnwindData 存进 swap_event.EventData,
|
||||
// 此处从浮动腿(PositionType>0)回读。存储态=展示态百分数(6.3721),导出固定 4 位不去零。
|
||||
decimal? exitYtm = null;
|
||||
|
||||
// 历史流水可能未落 PayDate,按交易结算规则补算后再进入统一构造器。
|
||||
flowEventGroup.PayDate = ResolvePayDate(flowEventGroup, tradeExtend);
|
||||
|
||||
// UnwindData 提供平仓名义本金及浮动腿期末收益率,均属于事件快照数据。
|
||||
UnwindData? unwindData = null;
|
||||
if (flowEventGroup.EventId.HasValue)
|
||||
{
|
||||
var swapEvent = Context.GetEvent(flowEventGroup.EventId.Value);
|
||||
if (swapEvent != null)
|
||||
{
|
||||
swapEvent.unwindData = JsonHelper.Deserialize<UnwindData>(swapEvent.EventData);
|
||||
PosiNotionalValue = swapEvent.unwindData.CloseNotionalValue;
|
||||
exitYtm = swapEvent.unwindData.FlowEvents?
|
||||
.FirstOrDefault(f => f.PositionType > 0)?
|
||||
.ExitYtm;
|
||||
unwindData = JsonHelper.Deserialize<UnwindData>(swapEvent.EventData);
|
||||
}
|
||||
}
|
||||
row.ExitYtm = exitYtm?.ToString("0.0000") ?? string.Empty;
|
||||
|
||||
row.Quantity = flowEventGroup.Quantity.ToString("0.00");
|
||||
row.PosiNotionalValue = PosiNotionalValue.ToString("0.00");
|
||||
row.PosiNetPrice = (posi.PosiGrossPrice * 100).ToString("0.00000000");
|
||||
//var tradingFee = (flowEventGroup.TradingAmountFeeAvg- posi.PosiNetPrice- flowEventGroup.TradingAmountAvg+ posi.PosiGrossPrice)* PosiNotionalValue* ratio;
|
||||
var tradingFee = flowEventGroup.TradingFee + flowEventGroup.TradingFeePending;
|
||||
row.Fee = (-tradingFee).ToString("0.00");
|
||||
var RateDays = (eventDate - trade.StartDate.Value).Days;
|
||||
RateDays = RateDays < 0 ? 0 : RateDays;
|
||||
row.RateDays = RateDays.ToString();
|
||||
var MarginAmout = positions.Where(x => x.InterestMode == (int)InterestModeEnum.初始预付金).Sum(s => s.InterestPrincipalFix * (s.InterestDirection == (int)SwapDirectionEnum.收取 ? 1 : -1));
|
||||
row.MarginAmout = Math.Abs(MarginAmout).ToString("0.00");
|
||||
var MarginRebateRate = positions.Where(x => x.InterestMode == (int)InterestModeEnum.初始预付金).Sum(s => s.InterestRateDefault);
|
||||
row.MarginRebateRate = MarginRebateRate.ToString("0.00%");
|
||||
var underlying = Context.GetTradeUnderlying(flowEventGroup.UnderlyingCode);
|
||||
var closeNotionalValue = unwindData?.CloseNotionalValue
|
||||
?? flowEventGroup.Quantity * flowEventGroup.ContractSize * posi.PosiGrossPrice;
|
||||
|
||||
var MarginInterestAmount = unwindFlowEvents.Where(x => ConsTrade.InterestMarginModels.Contains(x.InterestMode)).Sum(s => s.InterestClosePnL);
|
||||
row.MarginInterestAmount = (-MarginInterestAmount).ToString("0.00");
|
||||
|
||||
var InterestAmount = unwindFlowEvents.Where(x => ConsTrade.InterestModels.Contains(x.InterestMode)).Sum(s => s.InterestClosePnL) * -1;
|
||||
row.InterestAmount = InterestAmount.ToString("0.00");
|
||||
|
||||
//var lastInterestAmount= swapEodPositions.Where(x => ConsTrade.InterestModels.Contains(x.InterestMode)).Sum(s => s.InterestIncomeSum);
|
||||
//var lastPosiNotionalValue = swapEodPositions.Sum(s=>s.PosiNotionalValue);
|
||||
//var InterestAmount = lastPosiNotionalValue==0?0: - lastInterestAmount* PosiNotionalValue/ lastPosiNotionalValue;
|
||||
//row.InterestAmount = InterestAmount.ToString("0.00");
|
||||
|
||||
//var lastMarginAmount = swapEodPositions.Where(x => ConsTrade.InterestMarginModels.Contains(x.InterestMode)).Sum(s => s.InterestIncomeSum);
|
||||
//var MarginInterestAmount = lastMarginAmount == 0 ? 0 : -lastMarginAmount * PosiNotionalValue / lastMarginAmount;
|
||||
//row.MarginInterestAmount = MarginInterestAmount.ToString("0.00");
|
||||
var FloatRate = PosiNotionalValue == 0 ? 0 : InterestAmount / PosiNotionalValue;
|
||||
row.FloatRate = FloatRate.ToString("0.0000%");
|
||||
var PosiPnl = -flowEventGroup.MarkClosePnl;
|
||||
row.PosiPnl = PosiPnl.ToString("0.00");
|
||||
var markClosePnl = -flowEventGroup.FloatPnlSum;
|
||||
row.MarkClosePnl = markClosePnl.ToString("0.00");
|
||||
row.DividendIn = (-flowEventGroup.DividendIn).ToString("0.00");
|
||||
var marginBackAmount = unwindFlowEvents.Where(x => ConsTrade.InterestMarginModels.Contains(x.InterestMode)).Sum(s => s.InterestPrincipal);
|
||||
var NetSettleAmout = unwindFlowEvents.Sum(x => x.InterestClosePnL) * -1 + flowEventGroup.FloatPnlSum * -1 + marginBackAmount;
|
||||
row.NetSettleAmout = NetSettleAmout.ToString("0.00");
|
||||
// 行构造器统一处理客户视角、结算公式、品种差异和模板展示精度。
|
||||
var row = SwapSettlementBillRowBuilder.Build(new SwapSettlementBillRowInput
|
||||
{
|
||||
ConfirmNo = confirmNo,
|
||||
ClientName = client.Name,
|
||||
Trade = trade,
|
||||
CloseFlow = flowEventGroup,
|
||||
EventFlows = unwindFlowEvents,
|
||||
Positions = positions,
|
||||
UnderlyingInstrumentType = underlying?.UnderlyingInstrumentType,
|
||||
CloseNotionalValue = closeNotionalValue,
|
||||
ExitYtm = unwindData?.FlowEvents?
|
||||
.FirstOrDefault(x => x.PositionType > 0)?.ExitYtm,
|
||||
IncludePeriodPaymentInNetting = (tradeExtend?.ExtendObj?.DividendPayDate ?? 1) == 0
|
||||
});
|
||||
table.Add(row);
|
||||
}
|
||||
outPut.table = table;
|
||||
@@ -133,6 +99,7 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
|
||||
|
||||
protected override void PrepareViewData(Dictionary<string, JToken> dic)
|
||||
{
|
||||
// DMA/定义文件型结算确认书沿用原 DOCX 数据结构,与普通 XLSX 模板相互独立。
|
||||
var client = Context.GetClient();
|
||||
if (string.IsNullOrEmpty(client.SettleFileNumber))
|
||||
{
|
||||
@@ -191,6 +158,8 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
|
||||
{
|
||||
var trades = Context.Trades;
|
||||
var templatePath = string.Empty;
|
||||
|
||||
// 多空组合及定义文件型债券使用 DMA DOCX,其余普通结算单使用 27 列 XLSX。
|
||||
if (trades.Any(s => s.StructureType == "多空组合" || s.StructureType == "定义文件型债券收益互换"))
|
||||
{
|
||||
templatePath = MosPathHelper.Combine(GlobalConfig.PluginFolder, "App_Docs/settlement_template/dma_01.docx");
|
||||
@@ -209,13 +178,33 @@ namespace YLErp.Plugins.GuoLian.DocumentGenerator
|
||||
var valueDate = eventDate.ToString("yyyy-MM-dd");
|
||||
var count = Context.GetDMASelttementCount(client.id, eventDate);
|
||||
var countStr = count == 0 ? "" : "-" + (count + 1).ToString();
|
||||
var FullName = $"结算单-{client.Name}-{valueDate}{countStr}";
|
||||
if (trades.Any(s => s.StructureType == "多空组合" || s.StructureType == "定义文件型债券收益互换"))
|
||||
{
|
||||
FullName = $"{client.Name}-结算确认书-{valueDate}-交易平仓{countStr}";
|
||||
var FullName = $"{client.Name}-结算确认书-{valueDate}-交易平仓{countStr}";
|
||||
return $"{FullName}.docx";
|
||||
}
|
||||
return $"{FullName}.xlsx";
|
||||
|
||||
// 普通结算单以支付日命名;同一附件中的支付日必须唯一。
|
||||
var tradeExtends = Context.GetTrade_Extends(trades.Select(x => x.id).ToList());
|
||||
var payDate = SwapSettlementBillRowBuilder.GetSinglePayDate(
|
||||
flowEvents.Select(flow => (DateTime?)ResolvePayDate(
|
||||
flow,
|
||||
tradeExtends.FirstOrDefault(x => x.TradeId == flow.SwapTradeId))));
|
||||
return SwapSettlementBillRowBuilder.BuildOutputFileName(client.Name, payDate, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 优先使用平仓流水已保存的支付日;历史数据缺失时按事件日和交易结算规则补算。
|
||||
/// </summary>
|
||||
private static DateTime ResolvePayDate(swap_flow_event flowEvent, trade_extend? tradeExtend)
|
||||
{
|
||||
if (flowEvent.PayDate.HasValue)
|
||||
{
|
||||
return flowEvent.PayDate.Value;
|
||||
}
|
||||
|
||||
var settlementRules = tradeExtend?.ExtendObj?.SettlementRules ?? 0;
|
||||
return QdpCalendarHelper.GetNonHoliday(flowEvent.EventDate.AddDays(settlementRules));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,8 +148,9 @@ namespace YLErp.Modules.SwapModule
|
||||
/// <summary>
|
||||
/// 计算预付金利率。多条初始/追加预付金腿按本金绝对值加权,
|
||||
/// 不按收付方向轧差,避免相反方向本金抵消后放大利率。
|
||||
/// 对外公开以便结算单与每日估值复用同一计算口径。
|
||||
/// </summary>
|
||||
internal static decimal CalculateWeightedMarginRate(IEnumerable<swap_position> margins)
|
||||
public static decimal CalculateWeightedMarginRate(IEnumerable<swap_position> margins)
|
||||
{
|
||||
var marginList = margins.ToList();
|
||||
var totalWeight = marginList.Sum(x => Math.Abs(x.InterestPrincipalFix));
|
||||
|
||||
@@ -3556,7 +3556,7 @@ namespace YLErp.Modules.SwapModule
|
||||
// 主查询分页后再取同交易、同估值日的全部辅助腿,避免利息/保证金归集跨估值日串数据。
|
||||
var eodPositions = DbContext.eod_swap_position.Where(interestPredicate).ToList();
|
||||
var marginPositions = DbContext.swap_position
|
||||
.Where(x => tradeIds.Contains(x.SwapTradeId) && MarginModes.Contains(x.InterestMode) && x.IsInitial && !x.Invalid)
|
||||
.Where(x => tradeIds.Contains(x.SwapTradeId) && MarginModes.ForLinq.Contains(x.InterestMode) && x.IsInitial && !x.Invalid)
|
||||
.ToList();
|
||||
var tradeExtends = DbContext.trade_extend.Where(x => tradeIds.Contains(x.TradeId)).ToList();
|
||||
Dictionary<string, bool> tradeDic = new Dictionary<string, bool>();
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
using YLErp.DBModels;
|
||||
using YLErp.DBModels.Consts;
|
||||
using YLErp.DBModels.Enums;
|
||||
using YLErp.Models;
|
||||
using YLErp.Modules.SwapModule;
|
||||
using YLErp.Modules.SwapModule.ReturnLegs;
|
||||
|
||||
namespace YLErp.Modules.TradeModule.DocGenerateModule;
|
||||
|
||||
/// <summary>
|
||||
/// 构造普通收益互换结算单单行数据所需的业务输入。
|
||||
/// 数据均来自平仓事件及其关联交易,避免依赖平仓后可能已不存在的日终持仓。
|
||||
/// </summary>
|
||||
public sealed class SwapSettlementBillRowInput
|
||||
{
|
||||
/// <summary>有效交易确认书编号。</summary>
|
||||
public string ConfirmNo { get; set; }
|
||||
|
||||
/// <summary>结算单所属交易对手名称。</summary>
|
||||
public string ClientName { get; set; }
|
||||
|
||||
/// <summary>平仓事件关联的收益互换交易。</summary>
|
||||
public trade Trade { get; set; }
|
||||
|
||||
/// <summary>当前生成行对应的平仓流水。</summary>
|
||||
public swap_flow_event CloseFlow { get; set; }
|
||||
|
||||
/// <summary>与当前平仓事件同 EventId 的利息、预付金等流水。</summary>
|
||||
public IReadOnlyList<swap_flow_event> EventFlows { get; set; }
|
||||
|
||||
/// <summary>交易的初始持仓腿,用于识别融资腿、浮动腿和预付金。</summary>
|
||||
public IList<swap_position> Positions { get; set; }
|
||||
|
||||
/// <summary>标的品种,用于区分现券付息、ETF 分红和收益率字段。</summary>
|
||||
public string UnderlyingInstrumentType { get; set; }
|
||||
|
||||
/// <summary>本次平仓标的名义本金。</summary>
|
||||
public decimal CloseNotionalValue { get; set; }
|
||||
|
||||
/// <summary>平仓事件浮动腿记录的期末结算收益率(展示态数值)。</summary>
|
||||
public decimal? ExitYtm { get; set; }
|
||||
|
||||
/// <summary>期间付息或分红是否计入本次净额结算。</summary>
|
||||
public bool IncludePeriodPaymentInNetting { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将平仓事件数据按国联普通结算单口径计算并格式化为模板行。
|
||||
/// </summary>
|
||||
public static class SwapSettlementBillRowBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// 计算一条结算记录。金额方向统一转换为客户视角,返回值已按模板精度格式化。
|
||||
/// </summary>
|
||||
public static ExcelReportModel Build(SwapSettlementBillRowInput input)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(input);
|
||||
ArgumentNullException.ThrowIfNull(input.Trade);
|
||||
ArgumentNullException.ThrowIfNull(input.CloseFlow);
|
||||
|
||||
var positions = input.Positions ?? Array.Empty<swap_position>();
|
||||
var eventFlows = input.EventFlows ?? Array.Empty<swap_flow_event>();
|
||||
|
||||
// 三个业务日期是结算单和文件命名的必填项,缺失时直接阻止生成不完整附件。
|
||||
var startDate = input.Trade.StartDate
|
||||
?? throw new ServiceException("结算单缺少交易起始日");
|
||||
var eventDate = input.CloseFlow.UnwindDate
|
||||
?? throw new ServiceException("结算单缺少结算日");
|
||||
var payDate = input.CloseFlow.PayDate
|
||||
?? throw new ServiceException("结算单缺少支付日");
|
||||
|
||||
// 将同一事件的普通利息与预付金利息分开;预付金本金仅统计结算日前已生效的腿。
|
||||
var floatingPosition = positions.FirstOrDefault(x => x.PositionType > 0)
|
||||
?? positions.FirstOrDefault(x => !ConsTrade.InterestMarginModels.Contains(x.InterestMode));
|
||||
var interestEvents = eventFlows
|
||||
.Where(x => ConsTrade.InterestModels.Contains(x.InterestMode))
|
||||
.ToList();
|
||||
var marginEvents = eventFlows
|
||||
.Where(x => ConsTrade.InterestMarginModels.Contains(x.InterestMode))
|
||||
.ToList();
|
||||
var effectiveMargins = positions
|
||||
.Where(x => ConsTrade.InterestMarginModels.Contains(x.InterestMode)
|
||||
&& (!x.HappenDate.HasValue || x.HappenDate.Value <= eventDate))
|
||||
.ToList();
|
||||
|
||||
// 数据库存储的是系统/券商视角金额,结算单统一取反为客户视角;预付金本金按收付方向换算。
|
||||
var interestAmount = -interestEvents.Sum(x => x.InterestClosePnL);
|
||||
var floatingAmount = -input.CloseFlow.MarkClosePnl;
|
||||
var fee = -(input.CloseFlow.TradingFee + input.CloseFlow.TradingFeePending);
|
||||
var marginInterest = -marginEvents.Sum(x => x.InterestClosePnL);
|
||||
var periodAmount = -input.CloseFlow.DividendIn;
|
||||
var initialMargin = SumMargin(effectiveMargins, InterestModeEnum.初始预付金);
|
||||
var additionalMargin = SumMargin(effectiveMargins, InterestModeEnum.追加预付金);
|
||||
|
||||
// 净额结算按实际轧差项求和;到期结算在净额基础上返还或收取期初、追加预付金。
|
||||
var netSettlementAmount = interestAmount + floatingAmount + fee + marginInterest
|
||||
+ (input.IncludePeriodPaymentInNetting ? periodAmount : 0m);
|
||||
var maturitySettlementAmount = netSettlementAmount + initialMargin + additionalMargin;
|
||||
var floatRateAbs = input.CloseNotionalValue == 0m
|
||||
? 0m
|
||||
: interestAmount / input.CloseNotionalValue;
|
||||
|
||||
// 同一数据库字段在模板中按标的类型拆分:现券展示期间付息,ETF 展示期间分红。
|
||||
var isCashBond = ConsGlobal.InstrumentType.IsBond(input.UnderlyingInstrumentType);
|
||||
var isEtf = ConsGlobal.InstrumentType.Fund.Equals(
|
||||
input.UnderlyingInstrumentType,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
// 此处集中完成模板字段映射和展示精度处理,生成器只负责组装原始业务数据。
|
||||
return new ExcelReportModel
|
||||
{
|
||||
TradeNumber = input.ConfirmNo,
|
||||
ClientName = input.ClientName,
|
||||
StartDate = startDate.ToString("yyyy-MM-dd"),
|
||||
EventDate = eventDate.ToString("yyyy-MM-dd"),
|
||||
PayDate = payDate.ToString("yyyy-MM-dd"),
|
||||
UnderlyingCode = input.CloseFlow.UnderlyingCode,
|
||||
BaseRate = ResolveBaseRate(positions),
|
||||
InterestRate = interestEvents.Sum(x => x.InterestRate).ToString("0.00%"),
|
||||
PosiNotionalValue = input.CloseNotionalValue.ToString("0.00"),
|
||||
Quantity = input.CloseFlow.Quantity.ToString("0.00"),
|
||||
DividendIn = isCashBond ? periodAmount.ToString("0.00") : string.Empty,
|
||||
PeriodDividend = isEtf ? periodAmount.ToString("0.00") : string.Empty,
|
||||
PosiNetPrice = ((floatingPosition?.PosiGrossPrice ?? 0m) * 100m).ToString("0.00000000"),
|
||||
InitYtm = isCashBond && input.Trade.InitYtm.HasValue
|
||||
? input.Trade.InitYtm.Value.ToString("0.####%")
|
||||
: string.Empty,
|
||||
ClosePrice = (input.CloseFlow.TradingAmountAvg * 100m).ToString("0.00000000"),
|
||||
ExitYtm = isCashBond && input.ExitYtm.HasValue
|
||||
? input.ExitYtm.Value.ToString("0.0000")
|
||||
: string.Empty,
|
||||
RateDays = Math.Max(0, (eventDate - startDate).Days + 1).ToString(),
|
||||
FloatRateAbs = floatRateAbs.ToString("0.0000%"),
|
||||
FloatRate = floatRateAbs.ToString("0.0000%"),
|
||||
InterestAmount = interestAmount.ToString("0.00"),
|
||||
PosiPnl = floatingAmount.ToString("0.00"),
|
||||
Fee = fee.ToString("0.00"),
|
||||
MarginRebateRate = EodPnlCalculator.CalculateWeightedMarginRate(effectiveMargins)
|
||||
.ToString("0.0000%"),
|
||||
MarginInterestAmount = marginInterest.ToString("0.00"),
|
||||
InitialMargin = initialMargin.ToString("0.00"),
|
||||
AdditionalMargin = additionalMargin.ToString("0.00"),
|
||||
MarginAmout = Math.Abs(initialMargin).ToString("0.00"),
|
||||
MarkClosePnl = (-input.CloseFlow.FloatPnlSum).ToString("0.00"),
|
||||
NetSettleAmout = netSettlementAmount.ToString("0.00"),
|
||||
MaturitySettlementAmount = maturitySettlementAmount.ToString("0.00")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验同一附件中的支付日唯一,并返回用于文件名的支付日。
|
||||
/// </summary>
|
||||
public static DateTime GetSinglePayDate(IEnumerable<DateTime?> payDates)
|
||||
{
|
||||
var dates = payDates?.ToList()
|
||||
?? throw new ServiceException("结算单缺少支付日");
|
||||
if (dates.Count == 0 || dates.Any(x => !x.HasValue))
|
||||
{
|
||||
throw new ServiceException("结算单缺少支付日");
|
||||
}
|
||||
|
||||
var distinctDates = dates.Select(x => x.Value.Date).Distinct().OrderBy(x => x).ToList();
|
||||
if (distinctDates.Count != 1)
|
||||
{
|
||||
throw new ServiceException(
|
||||
$"同一结算单存在多个支付日:{string.Join("、", distinctDates.Select(x => x.ToString("yyyy-MM-dd")))}");
|
||||
}
|
||||
|
||||
return distinctDates[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按交易对手和支付日生成普通结算单文件名;重复生成时追加顺序号。
|
||||
/// </summary>
|
||||
public static string BuildOutputFileName(string clientName, DateTime payDate, int count)
|
||||
{
|
||||
var suffix = count == 0 ? string.Empty : $"-{count + 1}";
|
||||
return $"结算单-{clientName}-{payDate:yyyy-MM-dd}{suffix}.xlsx";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按预付金类型汇总本金,并根据收付方向转换为结算方向金额。
|
||||
/// </summary>
|
||||
private static decimal SumMargin(
|
||||
IEnumerable<swap_position> margins,
|
||||
InterestModeEnum interestMode)
|
||||
{
|
||||
return margins
|
||||
.Where(x => x.InterestMode == (int)interestMode)
|
||||
.Sum(x => x.InterestPrincipalFix * DirectionRatio.ReceivePay(x.InterestDirection));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从非预付金融资腿读取挂钩基准;未配置浮动基准时按固定利率展示。
|
||||
/// </summary>
|
||||
private static string ResolveBaseRate(IEnumerable<swap_position> positions)
|
||||
{
|
||||
return positions
|
||||
.Where(x => !ConsTrade.InterestMarginModels.Contains(x.InterestMode))
|
||||
.Select(x => x.FloatRateUnderlyingCode)
|
||||
.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x))
|
||||
?? "固定利率";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user