using System.Text; using YLErp.Configuration; using YLErp.Modules.EodFileModule; using YLErp.Modules.EodModule.CompanySpecial; using YLErp.Modules.ExchangeOptionTradeModule; using YLErp.Modules.RiskModule; using YLErp.Modules.SystemModule; using YLErp.Modules.TradeDalModule; using YLErp.Modules.TradeModule.DealModule; namespace YLErp.Modules.EodModule.SettlementModule { /// /// /// public static class EodTaskRunner { [ThreadStatic] static string TaskStep; static InnerRunner _runner; static readonly EodTaskReportInner _report; static EodTaskRunner() { _report = new EodTaskReportInner(); } /// /// 锁定对象 /// public static object LockObj => _report; /// /// 最后一次执行的任务 /// public static EodTask LastTask => _report.EodTask; /// /// 定时计划任务调用 /// public static void Execute() { lock (_report) { if (_runner != null && _runner?.StopCountDown() > 0) { return; } _runner = new InnerRunner(); } try { using (var db = DbContextFactory.GetYLDbContext()) { EodTask t; var table = db.GetTableName(); var startDate = DateTime.Today.AddDays(-3); var startDateStr = startDate.ToString(ConsGlobal.DateFormat); if (db.EodTask.Any(n => n.TaskStartTime > startDate && (n.TaskState == EodTaskState.running || n.TaskState == EodTaskState.canceling))) { var sql = $@" update {table} set {nameof(t.TaskState)}={(int)EodTaskState.canceled} where {nameof(t.TaskStartTime)}>'{startDateStr}' and {nameof(t.TaskState)}={(int)EodTaskState.canceling}; update {table} set {nameof(t.TaskState)}={(int)EodTaskState.interrupted} where {nameof(t.TaskStartTime)}>'{startDateStr}' and {nameof(t.TaskState)}={(int)EodTaskState.running};"; db.Database.ExecuteSqlRaw(sql); } } _runner.Execute(); } catch (Exception ex) { LogFactory.GetLogger("日终结算").Error(ex); } finally { var runner = _runner; _runner = null; runner?.Dispose(); } } /// /// 调试状态下调用 /// public static Task ExecuteDebugAsync(EodTask eodTask) { if (eodTask is null) { throw new ArgumentNullException(nameof(eodTask)); } lock (_report) { if (_runner != null && _runner?.StopCountDown() > 0) { throw new ServiceException("正在执行中"); } _runner = new InnerRunner(); } return Task.Run(() => { _runner.Execute(eodTask); }).ContinueWith(t => { if (t.Exception != null) { LogFactory.GetLogger("日终结算").Error(t.Exception); } var runner = _runner; _runner = null; runner?.Dispose(); }); } /// /// 停止正在执行的任务 /// public static bool Stop(long taskId) { if (_runner != null && _runner.CurTask?.id == taskId) { _runner.Stop(); return true; } return false; } #region----任务运行报告---- class EodTaskReportInner { EodTask _eodTask; readonly List _details; public EodTaskReportInner() { _details = new List(); } public EodTask EodTask { get { if (_eodTask == null/* || _eodTask.TaskStartTime < DateTime.Today*/) { return null; } var clone = _eodTask; if (clone.TaskState == EodTaskState.running) { clone = _eodTask.Clone(); clone.TaskEndTime = DateTime.Now; StopTaskDetail(); } clone.Details = _details.Count < 5 ? _details.AsEnumerable() : _details.Skip(_details.Count - 4).ToArray(); return clone; } } private void StopTaskDetail() { if (_details.Count > 0) { var last = _details.Last(); last.ElapsedMilliseconds = (int)(DateTime.Now - last.StartTime).TotalMilliseconds; } } public void StartEodTask(EodTask eodTask) { if (eodTask != null) { _eodTask = eodTask; _details.Clear(); } } public void StartTaskDetail(EodTaskDetail detail) { StopTaskDetail(); _details.Add(detail.Clone()); } public void SetErrorTips(string errorTips) { if (_eodTask != null && !string.IsNullOrWhiteSpace(errorTips)) { _eodTask.ErrorTips += errorTips; } } public void StopEodTask() { if (_eodTask != null) { StopTaskDetail(); _eodTask = _eodTask.Clone(); } } } #endregion #region----执行批量收盘---- class InnerRunner : YLBaseService { readonly CancellationTokenSource _cancellationTokenSource; int _stopCountDown = int.MaxValue; public InnerRunner() : base(OptUserInfo.SystemUser) { _cancellationTokenSource = new CancellationTokenSource(); } /// /// 正在运行的任务 /// public EodTask CurTask { get; private set; } /// /// 停止倒数计数 /// /// public int StopCountDown() { return _stopCountDown--; } /// /// 停止 /// public void Stop() { if (CurTask != null && CurTask.TaskState < EodTaskState.completed) { CurTask.TaskState = EodTaskState.canceling; DbContext.SaveChanges(); } _stopCountDown = 600; try { //可能有ObjectDisposedException异常 _cancellationTokenSource.Cancel(); } catch { } } /// /// 执行 /// public void Execute(EodTask eodTask = null) { if (eodTask == null) { eodTask = QueryFirstAvailableTask(); } CurTask = eodTask; if (eodTask == null) { return; } _report.StartEodTask(eodTask); try { if (eodTask.ValueDate < eodTask.StartDate) { eodTask.ValueDate = eodTask.StartDate; } eodTask.TaskStartTime = DateTime.Now; eodTask.TaskState = EodTaskState.running; var settlementConfig = PS.Config.GetSettlementConfig(); if (settlementConfig.SkipLastPvCheck) { eodTask.StepFlag = 1; } if (!string.IsNullOrWhiteSpace(eodTask.ClientIds)) { eodTask.ListClientId = eodTask.ClientIds.Split(',').Select(l => int.Parse(l)).ToList(); eodTask.ClientNameStr = String.Join(",", ClientDataModelV1.GetAllOpenClient(isContainClosed: true).Where(l => eodTask.ListClientId.Contains(int.Parse(l.Value))).Select(l => l.Text)); } RunEodTask(eodTask, settlementConfig); } catch (Exception ex) { try { //可能有ObjectDisposedException异常 _cancellationTokenSource.Cancel(); } catch { } eodTask.SetFailure(ex.Messages()); LogFactory.GetLogger("收盘报错").Error(ex); } finally { if (eodTask.TaskState < EodTaskState.completed) { eodTask.TaskState = EodTaskState.completed; } else if (eodTask.TaskState == EodTaskState.canceling) { eodTask.TaskState = EodTaskState.canceled; } eodTask.TaskEndTime = DateTime.Now; DbContext.SaveChanges(); _report.StopEodTask(); } } //根据波动率和价格模式进行步骤分解 private void RunEodTask(EodTask eodTask, SettlementConfig settlementConfig) { var userInfo = new OptUserInfo(eodTask.OptId, eodTask.OptName, OptUserFrom.System); var request = new EodSettlementRequest(userInfo) { VolType = null, UseClosePrice = true, SettleDate = eodTask.ValueDate, ClientIds = eodTask.ListClientId, }; request.IsDelayTrade = eodTask.IsDelayTrade; if (!string.IsNullOrWhiteSpace(eodTask.PartFlag) && eodTask.PartFlag != "全部") { request.IsSettleOtcTrades = eodTask.PartFlag == "场外"; request.IsSettleExchangeTrades = !request.IsSettleOtcTrades; } var priceTypes = new[] { EodSettlePriceType.ClosePrice, EodSettlePriceType.SettlePrice }; //从开始日期递增执行到结束日期 for (var i = 0; i < 10000; i++) { _cancellationTokenSource.Token.ThrowIfCancellationRequested(); eodTask.ValueDate = BLL.valuedateBLL.GetNonHoliday(eodTask.ValueDate); if (eodTask.ValueDate > eodTask.EndDate) { break; } DbContext.SaveChanges(); //检查任务是否可以正确执行 var cloneTask = eodTask.Clone(); cloneTask.StartDate = eodTask.ValueDate; var result = new EodTaskSaveService(this).CheckEodTask(cloneTask); if (result.HasError) { eodTask.SetError(result.IsFatalError, result.ErrorMsg); return; } //准备执行 request.SettleDate = eodTask.ValueDate; if (request.IsDelayTrade) { request.DelayUnderlyIds = new TradeDelaySettlementService(userInfo).GetDelayUnderlyIds(eodTask.ValueDate); } else { request.DelayUnderlyIds = new List(); } //任务明细 var detail = new EodTaskDetail { TaskId = eodTask.id, ValueDate = eodTask.ValueDate, StartTime = DateTime.Now, ElapsedMilliseconds = 0 }; _report.StartTaskDetail(detail); if (eodTask.id > 0) { DbContext.EodTaskDetail.Add(detail); } foreach (var priceType in priceTypes) { if (eodTask.PriceType.HasFlag(priceType)) { foreach (var volType in result.VolTypes) { request.VolType = volType; request.UseClosePrice = priceType == EodSettlePriceType.ClosePrice; var runner = new InnerTaskRunner(request.Clone(true), settlementConfig, _cancellationTokenSource); runner.Execute(out var elapsedMilliseconds); detail.ElapsedMilliseconds += (int)elapsedMilliseconds; } } } new EodFileService(this.OptUser).GenerateFileAfterEod(eodTask.ValueDate); //执行下一日 eodTask.ValueDate = eodTask.ValueDate.AddDays(1); eodTask.TaskEndTime = DateTime.Now; } } /// /// 查找第一个可用的任务 /// private EodTask QueryFirstAvailableTask() { var startDate = DateTime.Today.AddDays(-3); return DbContext.EodTask.Where(n => n.TaskStartTime > startDate && n.TaskState < EodTaskState.completed) .OrderBy(n => n.StartDate).ThenBy(n => n.id).FirstOrDefault(); } } #endregion #region----执行每日收盘---- class InnerTaskRunner : IAsyncTaskManager { readonly EodSettlementRequest _request; readonly EodSettlementContextV2 _context; readonly CancellationTokenSource _cancellationTokenSource; public InnerTaskRunner(EodSettlementRequest request, SettlementConfig settlementConfig, CancellationTokenSource cancellationTokenSource) { _request = request ?? throw new ArgumentNullException(nameof(request)); _cancellationTokenSource = cancellationTokenSource ?? throw new ArgumentNullException(nameof(cancellationTokenSource)); _context = new EodSettlementContextV2(request, settlementConfig, _cancellationTokenSource.Token); } public void Execute(out long elapsedMilliseconds) { var sw = new System.Diagnostics.Stopwatch(); _context.LogInfo("!!!!!!收盘!!!!!!"); _context.LogInfo($"开始收盘:{_request.SettleDate:yyyy-MM-dd} + {_request.VolType} + {_request.GetPriceType()}"); sw.Start(); if (!_request.IsPartialSettlement) { AddOrUpdateEodStatus(_request, true); } else { UpdateEodCheckStatus(_request); } var execBasicTask = _request.VolType == "持仓" && _request.UseClosePrice; Task[] tasks; if (execBasicTask) { SetTaskStep("检查日终汇率"); if (PS.Config.ErpElement.SupportMultiCurrency) { new EodCheckCurrency(_context).Execute(); } tasks = new Task[3]; tasks[0] = Task.Run(() => { SetTaskStep("检查标的到期日"); new EodCheckMaturityDate(_context).Execute();//t SetTaskStep("合成篮子标的价格"); new EodBasketPriceSaveService(_context).Execute();//t if (PS.Config.ErpElement.SupportMultiCalendar) { SetTaskStep("为处于节假日的交易标的复制上日收盘价"); new EodCopyPreSettlePrice(_context).Execute(); } SetTaskStep("检查标的结算价格缺失"); new EodCheckSettlePrice(_context).Execute();//t if (PS.Config.Is厦门象屿) { SetTaskStep("检查标的参考价格缺失"); new EodCheckReferencePrice(_context).Execute();//t } }); if (_request.IsSettleOtcTrades) { tasks[1] = Task.Run(() => { //SetTaskStep("合成多空组合流水"); //new EodHandleSwapFlowService(_context).Execute(); SetTaskStep("检查当日交易"); new EodCheckNewTrade(_context).Execute();//t SetTaskStep("日终结算检查"); new EodCheckVolService(_context).Execute();//t SetTaskStep("合成互换交易"); new TradeSwapFlowMoreCheck(_context).Execute();//t }); } if (_request.IsSettleOtcTrades) { tasks[2] = Task.Run(() => { SetTaskStep("汇率收盘结算"); new EodCurrencyRateService(_request.UserInfo).HandleAmountByCurrencyRate(_context.SettleDate, _context.Request.ClientIds);//t }); } WaitTasks(tasks); if (_request.IsSettleOtcTrades) { tasks = new[] { Task.Run(() => { SetTaskStep("检查期权观察状态1"); new EodCheckMonitoredTrade(_context).ExecuteTask1(SetTaskStep);//t }), Task.Run(() => { SetTaskStep("检查期权观察状态2"); new EodCheckMonitoredTrade(_context).ExecuteTask2(SetTaskStep);//t }), // Task.Run(() => //{ // SetTaskStep("定期结算互换收益"); // new EodCheckMonitoredTrade(_context).ExecuteBalanceSwapProfit(SetTaskStep);//t //}) Task.Run(() => { SetTaskStep("定期归档收益互换"); new EodCheckMonitoredTrade(_context).ExecuteEodSwapPosition(SetTaskStep);//t }) }; WaitTasks(tasks); SetTaskStep("自动延期结算"); new EodDelaySettlementTrade(_context).Execute(_report.SetErrorTips);//t SetTaskStep("检查当日到期交易"); new EodCheckMaturityTrade(_context).Execute();//t } new EodCheckMonitoredTrade(_context).CalculateMargin(); new EodCheckMonitoredTrade(_context).ExecuteEodSwap(); tasks = new Task[4]; if (_request.IsSettleExchangeTrades) { //tasks[0] = Task.Run(() => //{ // if (_context.Request.ClientIds == null) // { // SetTaskStep("场内期权结算"); // new ExchangeOptionSettlementService(_context.UserInfo).Execute(_context.SettleDate); //t // } //}); } if (_request.IsSettleOtcTrades) { tasks[0] = Task.Run(() => { SetTaskStep("备份场外交易"); SaveEodTrade(_context, _cancellationTokenSource.Token);//t }); tasks[1] = Task.Run(() => { SetTaskStep("检查权利金应付日"); new EodCheckPremiumPayDate(_context).Execute();//t }); if (_context.SettlementConfig.CalcForwradMargin) { tasks[2] = Task.Run(() => { SetTaskStep("远期预付金成本结算"); new EodForwardMarginSettlement(_context).Execute();//t }); } } //敏感数据计算 WaitTasks(tasks); } SetTaskStep("日终持仓结算"); var eodPositionService = new EodPositionSettleService(_context); eodPositionService.Execute(_request.VolType, _request.UseClosePrice, this);//t? _context.LogInfo("结束任务:日终持仓结算"); if (_request.IsSettleExchangeTrades) { if (_context.Request.ClientIds == null || !_context.Request.ClientIds.Any()) { SetTaskStep("场内期权结算"); new ExchangeOptionSettlementService(_context.UserInfo).Execute(_context.SettleDate); //t } } if (execBasicTask) { //交易员结算:按簿记账户计算资金和盈亏等信息 //new EodBookBalanceCalc(context).BookBalanceCalc(); SetTaskStep("结算客户资金"); new EodClientBalanceCalc(_context).ClientBalanceCalc(); //客户资金买卖权分开算 if (_context.SettlementConfig.CalcBuySell) { new EodClientBalanceCalcBS(_context.UserInfo).ClientBalanceByDay(_context.SettleDate, null); } _context.LogInfo("结束任务:结算客户资金"); if (PS.Config.ErpElement.UseSettleDataAcquisition) { SetTaskStep("配合数据采集,限额监控数据落地"); var service = new QuotaMonitorService(_context.UserInfo); if (_context.IsCurrentDay) { service.EodSettlementExecute(_context.SettleDate); } service.EodSensitiveDataCalc(_context); _context.LogInfo("结束任务:限额监控数据落地"); } //todo:客户日终结算完成发送行权到期邮件提醒 tasks = new Task[2]; if (_request.IsSettleExchangeTrades) { tasks[0] = Task.Run(() => { SetTaskStep("准备次日数据"); new EodDividenService(_context).Execute(eodPositionService.EodTradePositions);//t }); } if (_request.IsSettleOtcTrades) { tasks[1] = Task.Run(() => { SetTaskStep("除权除息"); var otcTrades = _context.OtcTrades.Where(t => !ConsTrade.TradeCompleteStatus.Contains(t.TradeStatus)).ToList(); var tradeIds = otcTrades.Select(O => O.id); var parentTradeIds = otcTrades.Where(O => O.ParentTradeId > 0 && !tradeIds.Contains(O.ParentTradeId)).Select(O => O.ParentTradeId).ToHashSet(); if (parentTradeIds.Any()) { using var db = DbContextFactory.GetYLDbContext(); var parentTrades = new TradeDalService(_context.UserInfo).GetTradeOrEodTrade(parentTradeIds, _context.SettleDate); otcTrades.AddRange(parentTrades); } new EodDividenService(_context).Execute(otcTrades); }); } WaitTasks(tasks); } _cancellationTokenSource.Token.ThrowIfCancellationRequested(); //if (!_request.IsPartialSettlement) //{ AddOrUpdateEodStatus(_request); //} //兴证日终持仓数据上传数据中心的FTP服务器 if (PS.Config.Company == CompanyEnum.兴证 && _context.IsCurrentDay) { Task.Run(() => XingZhengService.EodExecute(_context)); } //日终数据采集 if (PS.Config.Company == CompanyEnum.长江) { Task.Run(() => ChangJiangService.EodExecute(_context, _request.VolType, _request.UseClosePrice ? "收盘价" : "结算价")); } //东证 if (PS.Config.Company == CompanyEnum.润和) { Task.Run(() => DongZhengService.EodExecute(_context)); } //广发商贸 if (PS.Config.Company == CompanyEnum.广发商贸 && _request.VolType == "对冲" && _request.UseClosePrice) { Task.Run(() => GuangFaService.EodExecute(_context)); } sw.Stop(); elapsedMilliseconds = sw.ElapsedMilliseconds; _context.LogInfo($"结束收盘:{_request.SettleDate:yyyy-MM-dd} + {_request.VolType} + {_request.GetPriceType()},用时:{elapsedMilliseconds}毫秒"); } public void SetTaskStep(string step) { TaskStep = step; _context.LogInfo("开始任务:" + step); _cancellationTokenSource.Token.ThrowIfCancellationRequested(); } public void WaitTasks(params Task[] tasks) { if (tasks is null || tasks.Length < 1) { return; } if (tasks.Any(n => n == null)) { tasks = tasks.Where(n => n != null).ToArray(); if (tasks.Length < 1) { return; } } foreach (var task in tasks) { task.ContinueWith(t => { //!t.Exception.InnerExceptions.All(n => n is OperationCanceledException) if (t.Exception != null) { _cancellationTokenSource.Cancel(); LogFactory.GetLogger("日终结算").Error(t.Exception, TaskStep); } _context.LogInfo("结束任务:" + TaskStep); }); } _cancellationTokenSource.Token.ThrowIfCancellationRequested(); Task.WaitAll(tasks); } /// /// 保存日终场外交易数据 /// private static void SaveEodTrade(EodSettlementContextV2 context, CancellationToken cancellationToken) { //在context中取OtcTrades时存在隐含赋值 MetaDic["from_eod_trade"] = "true" var otcTrades = context.InitOtcTrades(true); var trades = otcTrades.Where(t => !t.MetaDic.ContainsKey("from_eod_trade")).ToList(); var parentTradeIds = otcTrades.Where(x => x.ParentTradeId > 0).Select(x => x.ParentTradeId).ToHashSet(); //解决测试环境中存在的交易重复问题(比如新功能的增加导致前置逻辑未来得及处理) foreach (var t in trades) { parentTradeIds.Remove(t.id); } using var db = DbContextFactory.GetYLDbContext(); var eodGroupTradeIds = db.eod_trade.Where(x => parentTradeIds.Contains(x.TradeId) && x.ValueDate == context.SettleDate).Select(x => x.TradeId); var groupTrades = db.trade.AsNoTracking().Where(x => parentTradeIds.Contains(x.id) && !eodGroupTradeIds.Contains(x.id)).ToList(); groupTrades.ForEach(x => { EodSettleDataQueryService.ProcessTrade(context.SettleDate, new DataProviderModule.TradeCashDataProvider(), new DataProviderModule.TradeCashPreDataProvider(), x); }); trades.AddRange(groupTrades); var tradeIds = trades.Select(O => O.id); //移除修改了分红率或无风险利率的交易,以更新收盘数据; db.eod_trade.RemoveRange(db.eod_trade.Where(O => tradeIds.Contains(O.TradeId) && O.ValueDate == context.SettleDate)); var sql = new StringBuilder("delete from ").Append(db.GetTableName()) .AppendFormat(" where ValueDate='{0}'", context.SettleDateStr) .Append(" and not exists(select * from trade where id=TradeId)"); db.Database.ExecuteSqlRaw(sql.ToString()); foreach (var t in trades) { if (!t.MetaDic.ContainsKey("from_eod_trade")) { db.eod_trade.Add(new eod_trade { ValueDate = context.SettleDate, TradeId = t.id, TradeNumber = t.TradeNumber, ClientId = t.ClientId, AssetId = t.AssetId, UnderlyingId = t.UnderlyingId, UnderlyingCode = t.UnderlyingCode, TradeStatus = t.TradeStatus, ParentTradeId = t.ParentTradeId, TradeType = t.TradeType, TradeJson = t.Serialize(context.SettleDate), OptDate = DateTime.Now }); } } db.SaveChanges(cancellationToken); } /// /// 新增或更新EodStatus表(重新执行收盘前重置) /// private static void AddOrUpdateEodStatus(EodSettlementRequest request, bool reset = false) { using var db = DbContextFactory.GetYLDbContext(); var eodStatus = db.eodStatus.FirstOrDefault(n => n.ValueDate == request.SettleDate); if (eodStatus == null) { eodStatus = new EodStatus { ValueDate = request.SettleDate, IsHoliday = true, OptDate = DateTime.Now, OptId = request.UserInfo.UserId, OptName = request.UserInfo.UserName, Status = string.Empty, StepStatus = string.Empty, VolTypeFlag = VolTypeFlagEnum.None, VolTypeFlag_S = VolTypeFlagEnum.None }; db.eodStatus.Add(eodStatus); } if (request.VolType == "持仓" && request.UseClosePrice) { eodStatus.Status = String.Empty; } eodStatus.OptDate = DateTime.Now; eodStatus.OptId = request.UserInfo.UserId; eodStatus.OptName = request.UserInfo.UserName; eodStatus.CheckStatus = null; eodStatus.CheckDate = null; var volTypeFlag = VolTypeFlagEnumHelper.GetVolTypeFlag(request.VolType); if (reset) { volTypeFlag = (eodStatus.VolTypeFlag | volTypeFlag) ^ volTypeFlag; if (request.UseClosePrice) { eodStatus.VolTypeFlag = volTypeFlag; } else { eodStatus.VolTypeFlag_S = volTypeFlag; } } else { eodStatus.Status = "已收盘"; if (request.UseClosePrice) { eodStatus.VolTypeFlag |= volTypeFlag; } else { eodStatus.VolTypeFlag_S |= volTypeFlag; } } db.SaveChanges(); } private static void UpdateEodCheckStatus(EodSettlementRequest request) { using var db = DbContextFactory.GetYLDbContext(); var eodStatus = db.eodStatus.FirstOrDefault(n => n.ValueDate == request.SettleDate); if (eodStatus != null) { eodStatus.CheckStatus = null; eodStatus.CheckDate = null; db.SaveChanges(); } } } #endregion } }