using YLErp.Configuration; using YLErp.Modules.SystemModule; namespace YLErp.Modules.EodModule.SettlementModule { /// /// EodTask数据保存服务 /// public class EodTaskSaveService : YLBaseService { public EodTaskSaveService(OptUserInfo optUser) : base(optUser) { } public EodTaskSaveService(YLBaseService baseService) : base(baseService) { } public EodTask CancelTask(int taskId) { if (EodTaskRunner.Stop(taskId)) { return EodTaskRunner.LastTask; } var task = DbContext.EodTask.FirstOrDefault(n => n.id == taskId); if (task == null) { throw new ServiceException("任务数据已不存在"); } if (task.TaskState >= EodTaskState.completed) { throw new ServiceException("当前任务状态:" + EodTaskHelper.GetEodTaskStateDesc(task.TaskState)); } task.OptId = UserId; task.OptName = UserName; task.OptTime = DateTime.Now; task.TaskState = EodTaskState.canceled; DbContext.SaveChanges(); return task; } public EodTask RestartTask(int taskId, bool resetAll, bool isDelayTrade) { var task = DbContext.EodTask.FirstOrDefault(n => n.id == taskId); if (task == null) { throw new ServiceException("任务数据已不存在"); } switch (task.TaskState) { case EodTaskState.completed: case EodTaskState.failure: case EodTaskState.canceled: case EodTaskState.interrupted: break; case EodTaskState.running: if (EodTaskRunner.LastTask?.id == taskId) { throw new ServiceException("任务正在运行中"); } break; default: throw new ServiceException("不符合重新执行的条件,任务当前状态:" + EodTaskHelper.GetEodTaskStateDesc(task.TaskState)); } task.OptId = UserId; task.OptName = UserName; task.OptTime = DateTime.Now; task.TaskResult = string.Empty; task.TaskState = EodTaskState.pending; task.IsDelayTrade = isDelayTrade; if (resetAll) { task.ValueDate = task.StartDate; DbContext.EodTaskDetail.RemoveRange(DbContext.EodTaskDetail.Where(n => n.TaskId == taskId)); } DbContext.SaveChanges(); return task; } /// /// 保存结算任务 /// public EodTask SaveData(EodTaskSaveReq req, bool saveIntoDB) { if (req is null) { throw new ArgumentNullException(nameof(req)); } var task = new EodTask { EndDate = req.EndDate, StartDate = req.StartDate, TaskEndTime = DateTime.Now, TaskStartTime = DateTime.Now, TaskResult = string.Empty, TaskState = EodTaskState.pending, PartFlag = req.PartFlag, ValueDate = req.StartDate, PriceType = req.PriceType, VolTypes = req.VolTypes, IsDelayTrade = req.IsDelayTrade, CreatorId = UserId, CreatorName = UserName, CreateTime = DateTime.Now, OptId = UserId, OptName = UserName, OptTime = DateTime.Now, }; if (req.ClientIds != null) { task.ClientIds = String.Join(",", req.ClientIds); task.ListClientId = req.ClientIds; task.ClientNameStr = String.Join(",", ClientDataModelV1.GetAllOpenClient(isContainClosed: true).Where(l => task.ListClientId.Contains(int.Parse(l.Value))).Select(l => l.Text)); } var result = CheckEodTask(task); if (result.HasError) { throw new ServiceException(result.ErrorMsg); } task.VolTypes = string.Join(",", result.VolTypes); if (saveIntoDB) { DbContext.EodTask.Add(task); DbContext.SaveChanges(); } return task; } internal CheckPreEodStatusResult CheckEodTask(EodTask req) { var result = new CheckPreEodStatusResult(); if (req.StartDate.Year < 2000) { return result.SetError(true, "收盘开始日期 必须填写"); } if (req.EndDate == DateTime.MinValue) { req.EndDate = req.StartDate; } else if (req.EndDate.Year < 2000) { return result.SetError(true, "收盘结束日期 必须填写"); } if (req.EndDate < req.StartDate) { return result.SetError(true, "收盘结束日期 必须大于或等于 收盘开始日期"); } if (string.IsNullOrWhiteSpace(req.VolTypes)) { return result.SetError(true, "波动率类型 必须填写"); } if (req.PriceType < EodSettlePriceType.ClosePrice || req.PriceType > EodSettlePriceType.AllPrices) { return result.SetError(true, "价格模式 无效值"); } if (!EodTaskHelper.IsValidPartFlag(req.PartFlag)) { return result.SetError(true, "部分结算 无效值"); } var valueDate = BLL.valuedateBLL.GetNonHoliday(req.StartDate); if (valueDate > req.EndDate) { return result.SetError(true, "收盘开始日期至收盘结束日期没有交易日"); } //波动率结算是否满足条件 var volTypes = req.VolTypes.Split(',').Where(n => !string.IsNullOrWhiteSpace(n)).ToArray(); if (!volTypes.Any()) { return result.SetError(true, "波动率类型 必须填写"); } var volTypeFlag = VolTypeFlagEnum.None; var volTypeFlagList = new List(6); foreach (var voltype in volTypes) { var tempVolType = VolTypeFlagEnumHelper.GetVolTypeFlag(voltype); if (tempVolType == VolTypeFlagEnum.None && !string.IsNullOrWhiteSpace(voltype)) { return result.SetError(true, "不能辨认的波动率类型:" + voltype); } volTypeFlag |= tempVolType; volTypeFlagList.Add(tempVolType); } if (volTypeFlag == VolTypeFlagEnum.None) { return result.SetError(true, "波动率类型 必须有值"); } var curEodStatus = new Lazy(() => { using (var db = DbContextFactory.GetYLDbContext()) { return DbContext.eodStatus.FirstOrDefault(n => n.ValueDate == valueDate); } }); if (!volTypeFlag.HasFlag(VolTypeFlagEnum.PositionVol)) { if (req.UseClosePrice() && (curEodStatus.Value == null || !curEodStatus.Value.VolTypeFlag.HasFlag(VolTypeFlagEnum.PositionVol))) { return result.SetError(false, $"结算日'{valueDate:yyyy-MM-dd}'需执行'收盘价+持仓波动率'结算"); } if (req.UseSettlePrice() && (curEodStatus.Value == null || !curEodStatus.Value.VolTypeFlag_S.HasFlag(VolTypeFlagEnum.PositionVol))) { return result.SetError(false, $"结算日'{valueDate:yyyy-MM-dd}'需执行'结算价+持仓波动率'结算"); } } //按优先级重新排序 result.VolTypes = volTypeFlagList.OrderBy(n => n).Select(n => VolTypeFlagEnumHelper.GetSingleDesc(n)).ToArray(); //部分结算是否满足条件 if (req.PartFlag == "场外" || req.PartFlag == "场内") { if (req.UseClosePrice()) { var missingVolTypes = GetMissingVolTypes(curEodStatus.Value?.VolTypeFlag ?? VolTypeFlagEnum.None, volTypeFlag); if (!string.IsNullOrEmpty(missingVolTypes)) { return result.SetError(false, $"结算日'{valueDate:yyyy-MM-dd}'不能进行{req.PartFlag}部分结算,因未完成'收盘价+{missingVolTypes}波动率'的完整结算"); } } if (req.UseSettlePrice()) { var missingVolTypes = GetMissingVolTypes(curEodStatus.Value?.VolTypeFlag_S ?? VolTypeFlagEnum.None, volTypeFlag); if (!string.IsNullOrEmpty(missingVolTypes)) { return result.SetError(false, $"结算日'{valueDate:yyyy-MM-dd}'不能进行{req.PartFlag}部分结算,因未完成'结算价+{missingVolTypes}波动率'的完整结算"); } } } //上一交易日是否满足条件 var preDate = BLL.valuedateBLL.GetNonHolidayDefore(valueDate.AddDays(-1)); var preEodStatus = DbContext.eodStatus.AsNoTracking().Where(n => n.ValueDate < valueDate && n.Status == "已收盘") .OrderByDescending(n => n.ValueDate).FirstOrDefault(); if (preEodStatus != null && string.IsNullOrEmpty(req.ClientIds)) { if (preEodStatus.ValueDate < preDate) { return result.SetError(false, $"上一交易日'{preDate:yyyy-MM-dd}'未进行收盘,必须在上一交易日收盘后方能执行此次收盘任务"); } if (preEodStatus.ValueDate > preDate) { return result.SetError(false, $"是否存在日历变更?数据库中已执行的上一收盘日'{preEodStatus.ValueDate:yyyy-MM-dd}'并非交易日,请将开始日期调整为'{preDate:yyyy-MM-dd}'"); } //收盘价 if (req.UseClosePrice() && !preEodStatus.VolTypeFlag.HasFlag(volTypeFlag)) { var list = new List(); for (var i = 0; i < volTypeFlagList.Count; i++) { if (volTypeFlagList[i] != (volTypeFlagList[i] & preEodStatus.VolTypeFlag)) { list.Add(volTypes[i]); } } var str = string.Join(",", list); return result.SetError(false, $"上一交易日'{preDate:yyyy-MM-dd}'未进行'收盘价+波动率({str})'收盘,必须在上一交易日收盘后方能执行此次收盘任务"); } //结算价 if (req.UseSettlePrice() && !preEodStatus.VolTypeFlag_S.HasFlag(volTypeFlag)) { var list = new List(); for (var i = 0; i < volTypeFlagList.Count; i++) { if (volTypeFlagList[i] != (volTypeFlagList[i] & preEodStatus.VolTypeFlag_S)) { list.Add(volTypes[i]); } } var str = string.Join(",", list); return result.SetError(false, $"上一交易日'{preDate:yyyy-MM-dd}'未进行'结算价+波动率({str})'收盘,必须在上一交易日收盘后方能执行此次收盘任务"); } } //分客户收盘 if (!string.IsNullOrEmpty(req.ClientIds)) {//分客户收盘时通过ClientBalanceDaily表确定该客户前一天是否收盘; var clientIds = req.ListClientId; var eodClientIds = DbContext.ClientBalanceDaily.Where(O => O.BalanceDate == preDate && clientIds.Contains(O.ClientId)).Select(O => O.ClientId).ToList(); clientIds = clientIds.Except(eodClientIds).ToList(); if (clientIds.Any()) {//忽略掉收盘日之前没有出入金记录,授信记录和新交易的客户; var validClientIds = DbContext.credit .Where(O => clientIds.Contains(O.ClientId ?? 0) && O.CreditStartDate < valueDate).Select(O => O.ClientId ?? 0) .Union( DbContext.ClientCashInCashOut .Where(O => clientIds.Contains(O.ClientId ?? 0) && O.HappenDate < valueDate && O.ValidState != "InValid").Select(O => O.ClientId ?? 0)) .Union( DbContext.trade.Where(O => clientIds.Contains(O.ClientId) && O.TradeDate < valueDate && O.ValidState != "InValid").Select(O => O.ClientId)) .Distinct().ToArray(); if (validClientIds.Any()) { var clientNameList = validClientIds.Select(O => DataCacheProvider.GetClientDataSource().GetData(O).Name).ToList(); return result.SetError(false, $"上一交易日'{preDate:yyyy-MM-dd}' 客户:{string.Join(",", clientNameList)} 未进行收盘,必须在上一交易日收盘后方能执行此次收盘任务"); } } } return result; } private string GetMissingVolTypes(VolTypeFlagEnum existing, VolTypeFlagEnum expected) { if ((existing & expected) == expected) { return string.Empty; } var list = new List(); if (expected.HasFlag(VolTypeFlagEnum.PositionVol) && !existing.HasFlag(VolTypeFlagEnum.PositionVol)) { list.Add("持仓"); } if (expected.HasFlag(VolTypeFlagEnum.HedgeVol) && !existing.HasFlag(VolTypeFlagEnum.HedgeVol)) { list.Add("对冲"); } if (expected.HasFlag(VolTypeFlagEnum.OpenVol) && !existing.HasFlag(VolTypeFlagEnum.OpenVol)) { list.Add("开仓"); } if (expected.HasFlag(VolTypeFlagEnum.ExtendVol) && !existing.HasFlag(VolTypeFlagEnum.ExtendVol)) { if (PS.Config.Company == CompanyEnum.光大光子) { list.Add("光证"); } else if (PS.Config.Is格林大华 || PS.Config.Is伴兴) { list.Add("BidAskVol"); } else { list.Add("未知"); } } return string.Join(",", list); } } /// /// /// public class EodTaskSaveReq { /// /// 收盘开始日期 /// public DateTime StartDate { get; set; } /// /// 收盘结束日期 /// public DateTime EndDate { get; set; } /// /// 波动率类型 /// public string VolTypes { get; set; } /// /// 分红率类型 交易、0 国元特有 /// public string DividendRateTypes { get; set; } /// /// 1=收盘价,2=结算价,3=收盘价+结算价 /// public EodSettlePriceType PriceType { get; set; } /// /// 客户 /// public List ClientIds { get; set; } /// /// 部分收盘标识 /// public string PartFlag { get; set; } /// /// 是否延期交易 /// public bool IsDelayTrade { get; set; } } /// /// /// class CheckPreEodStatusResult { /// /// /// public bool HasError { get; set; } /// /// /// public bool IsFatalError { get; set; } /// /// /// public string ErrorMsg { get; set; } /// /// 根据优先级重新排序后的波动率集合 /// public string[] VolTypes { get; set; } public CheckPreEodStatusResult SetError(bool isFatalError, string errorMsg) { HasError = true; ErrorMsg = errorMsg; IsFatalError = isFatalError; return this; } } }