473 lines
17 KiB
C#
473 lines
17 KiB
C#
using YLErp.Configuration;
|
|
using YLErp.Modules.SystemModule;
|
|
|
|
namespace YLErp.Modules.EodModule.SettlementModule
|
|
{
|
|
/// <summary>
|
|
/// EodTask数据保存服务
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保存结算任务
|
|
/// </summary>
|
|
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<VolTypeFlagEnum>(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<EodStatus>(() =>
|
|
{
|
|
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<string>();
|
|
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<string>();
|
|
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<string>();
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public class EodTaskSaveReq
|
|
{
|
|
/// <summary>
|
|
/// 收盘开始日期
|
|
/// </summary>
|
|
public DateTime StartDate { get; set; }
|
|
|
|
/// <summary>
|
|
/// 收盘结束日期
|
|
/// </summary>
|
|
public DateTime EndDate { get; set; }
|
|
|
|
/// <summary>
|
|
/// 波动率类型
|
|
/// </summary>
|
|
public string VolTypes { get; set; }
|
|
/// <summary>
|
|
/// 分红率类型 交易、0 国元特有
|
|
/// </summary>
|
|
public string DividendRateTypes { get; set; }
|
|
/// <summary>
|
|
/// 1=收盘价,2=结算价,3=收盘价+结算价
|
|
/// </summary>
|
|
public EodSettlePriceType PriceType { get; set; }
|
|
|
|
/// <summary>
|
|
/// 客户
|
|
/// </summary>
|
|
public List<int> ClientIds { get; set; }
|
|
/// <summary>
|
|
/// 部分收盘标识
|
|
/// </summary>
|
|
public string PartFlag { get; set; }
|
|
|
|
/// <summary>
|
|
/// 是否延期交易
|
|
/// </summary>
|
|
public bool IsDelayTrade { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
class CheckPreEodStatusResult
|
|
{
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public bool HasError { get; set; }
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public bool IsFatalError { get; set; }
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public string ErrorMsg { get; set; }
|
|
|
|
/// <summary>
|
|
/// 根据优先级重新排序后的波动率集合
|
|
/// </summary>
|
|
public string[] VolTypes { get; set; }
|
|
|
|
public CheckPreEodStatusResult SetError(bool isFatalError, string errorMsg)
|
|
{
|
|
HasError = true;
|
|
ErrorMsg = errorMsg;
|
|
IsFatalError = isFatalError;
|
|
return this;
|
|
}
|
|
}
|
|
}
|