using System.Text; using YLErp.BLL.EodSettlement; using YLErp.Configuration; using YLErp.Modules.ApiModule; 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; #region 收盘动作 回调 用于收盘后 部分接口功能开发 try { //回调地址不为空就启用回调 if (!String.IsNullOrWhiteSpace(PS.Config.ErpElement.EodExecCallbackUrl)) { var url = PS.Config.ErpElement.EodExecCallbackUrl; var callbackjson = request.ToJson(); LogFactory.GetLogger("收盘回调收盘参数:").Info(callbackjson); var asyncTask = HttpClientWrap.PostAsync(url, callbackjson); LogFactory.GetLogger("收盘回调结果:").Info(asyncTask?.Result); } } catch (Exception e) { LogFactory.GetLogger("收盘动作回调").Error("收盘回调:失败:", e); } #endregion } } } ClientBalanceUtility.saveClientRiskMonitor(eodTask.ValueDate); 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(); } SetTaskStep("检查日终流水"); new EodCheckSwapFlow(_context).Execute(); tasks = new Task[3]; tasks[0] = Task.Run(() => { SetTaskStep("检查标的到期日"); new EodCheckMaturityDate(_context).Execute();//t if (PS.Config.ErpElement.SupportMultiCalendar) { SetTaskStep("为处于节假日的交易标的复制上日收盘价"); new EodCopyPreSettlePrice(_context).Execute(); } SetTaskStep("检查标的出入金状态"); new EodClientBalanceCalc(_context).ClientCashInCashOutCheck(); SetTaskStep("检查标的结算价格缺失"); new EodCheckSettlePrice(_context).Execute();//t }); WaitTasks(tasks); if (_request.IsSettleOtcTrades) { SetTaskStep("检查当日交易"); new EodCheckNewTrade(_context).Execute();//t SetTaskStep("检查当日到期交易"); new EodCheckMaturityTrade(_context).Execute();//t SetTaskStep("定期归档收益互换"); new EodCheckMonitoredTrade(_context).ExecuteEodSwapPosition(SetTaskStep);//t } } 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(); _context.LogInfo("结束任务:结算客户资金"); } AddOrUpdateEodStatus(_request); if (PS.Config.ErpElement.UseSettleDataAcquisition) { SetTaskStep("配合数据采集,限额监控数据落地"); var service = new QuotaMonitorService(_context.UserInfo); service.EodSettlementExecute(_context.SettleDate); _context.LogInfo("结束任务:限额监控数据落地"); } _cancellationTokenSource.Token.ThrowIfCancellationRequested(); //if (!_request.IsPartialSettlement) //{ //} 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 } }