88 lines
4.0 KiB
C#
88 lines
4.0 KiB
C#
using Newtonsoft.Json;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using YLErp.BLL;
|
|
|
|
namespace YLErp.Modules.RiskEngine
|
|
{
|
|
/// <summary>
|
|
/// 风控规则专用日历辅助类。
|
|
/// 当前主要用于债券类规则按银行间日历确认“上一收盘日”,避免简单按估值表倒序取最近日期导致口径偏差。
|
|
/// </summary>
|
|
public static class RiskCalendarHelper
|
|
{
|
|
/// <summary>
|
|
/// 获取指定日期的上一银行间交易日。
|
|
/// </summary>
|
|
/// <param name="dbContext">当前风控执行使用的数据库上下文。</param>
|
|
/// <param name="date">当前交易日或业务基准日。</param>
|
|
/// <returns>严格早于入参日期的上一银行间交易日。</returns>
|
|
/// <exception cref="ArgumentNullException">数据库上下文为空。</exception>
|
|
/// <exception cref="Exception">缺少银行间日历、日历内容异常或在保护范围内找不到上一交易日。</exception>
|
|
public static DateTime GetPreviousInterbankTradingDay(YLContext dbContext, DateTime date)
|
|
{
|
|
if (dbContext == null)
|
|
throw new ArgumentNullException(nameof(dbContext));
|
|
|
|
var holidayCache = new Dictionary<int, HashSet<string>>();
|
|
var currentDate = date.Date.AddDays(-1);
|
|
|
|
// 最多向前查 370 天,既覆盖跨年和长假场景,也避免日历配置异常时出现无限循环。
|
|
for (var i = 0; i < 370; i++)
|
|
{
|
|
var holidays = GetInterbankHolidays(dbContext, currentDate.Year, holidayCache);
|
|
var currentDateText = currentDate.ToString("yyyy,MM,dd", CultureInfo.InvariantCulture);
|
|
|
|
// calendar.HolidayJson 存的是非交易日;不在非交易日集合内,即认为是银行间交易日。
|
|
if (!holidays.Contains(currentDateText))
|
|
return currentDate;
|
|
|
|
currentDate = currentDate.AddDays(-1);
|
|
}
|
|
|
|
throw new Exception($"未找到{date:yyyy-MM-dd}的上一银行间交易日");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取指定年份的银行间非交易日集合。
|
|
/// </summary>
|
|
/// <param name="dbContext">当前风控执行使用的数据库上下文。</param>
|
|
/// <param name="year">日历年份。</param>
|
|
/// <param name="holidayCache">单次查询过程内的年份级缓存,跨年查找时避免重复读取同一年日历。</param>
|
|
/// <returns>格式为 yyyy,MM,dd 的非交易日集合。</returns>
|
|
private static HashSet<string> GetInterbankHolidays(YLContext dbContext, int year, Dictionary<int, HashSet<string>> holidayCache)
|
|
{
|
|
if (holidayCache.TryGetValue(year, out var holidays))
|
|
return holidays;
|
|
|
|
// 同一年可能存在多种市场日历;规则 12 明确使用 Country=IB 的银行间日历。
|
|
var calendar = dbContext.calendar
|
|
.Where(c => c.Year == year && (c.ValidState == null || c.ValidState != ConsGlobal.InValid))
|
|
.ToList()
|
|
.FirstOrDefault(c => string.Equals(c.Country, "IB", StringComparison.OrdinalIgnoreCase));
|
|
|
|
if (calendar == null)
|
|
throw new Exception($"未找到{year}年银行间日历");
|
|
|
|
if (string.IsNullOrWhiteSpace(calendar.HolidayJson))
|
|
throw new Exception($"{year}年银行间日历HolidayJson为空");
|
|
|
|
List<string> holidayList;
|
|
try
|
|
{
|
|
holidayList = JsonConvert.DeserializeObject<List<string>>(calendar.HolidayJson);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new Exception($"{year}年银行间日历HolidayJson解析失败", ex);
|
|
}
|
|
|
|
holidays = new HashSet<string>(holidayList ?? new List<string>());
|
|
holidayCache[year] = holidays;
|
|
return holidays;
|
|
}
|
|
}
|
|
}
|