diff --git a/Framework/YLErp.Core/Models/ClientOrder.cs b/Framework/YLErp.Core/Models/ClientOrder.cs new file mode 100644 index 00000000..67f55aad --- /dev/null +++ b/Framework/YLErp.Core/Models/ClientOrder.cs @@ -0,0 +1,257 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Security.Principal; +using System.Text; +using System.Threading.Tasks; + +namespace YLErp.DBModels +{ + [Table("client_order")] + public class ClientOrder + { + /// + /// 主键 + /// + [Key] + public long id { get; set; } + /// + /// 委托编号 + /// + public string order_sno { get; set; } + /// + /// 序号 + /// + public string num { get; set; } + /// + /// STC订单编号 + /// + public string stc_order_id { get; set; } + /// + /// 第三方订单编号 + /// + public string third_party_order_id { get; set; } + /// + /// 客户机构编码 + /// + public int? client_id { get; set; } + /// + /// 客户机构名称 + /// + public string client_name { get; set; } + /// + /// 客户编号 + /// + public long? client_user_id { get; set; } + /// + /// 客户名称 + /// + public string client_user_name { get; set; } + /// + /// 债券代码 + /// + public string security_id { get; set; } + /// + /// 债券简称 + /// + public string symbol { get; set; } + /// + /// 债券规模 + /// + public string bond_size { get; set; } + /// + /// 债券发行价格 + /// + public decimal? issue_price { get; set; } + /// + /// 合约期限(对应债券期限,例如:10Y) + /// + public string contract_period { get; set; } + /// + /// 结构类型(1:收益互换) + /// + public int? struct_type { get; set; } + /// + /// 当前操作(0:无操作,1:委托拒绝,2:合约成交,3:客户撤单,4:对冲撤单,5:确认对冲,6:自动撤单) + /// + public int? current_operation { get; set; } + /// + /// 订单状态(-2:意向成交,-1:待意向确认,0:已报,1:部分成交,2:全部成交,4:已撤单,6:撤单中,8:已拒绝,9:自动确认,10:下单失败) + /// + public int? status { get; set; } + /// + /// 操作前订单状态(-2:意向成交,-1:待意向确认,0:已报,1:部分成交,2:全部成交,4:已撤单,6:撤单中,8:已拒绝,9:自动确认,10:下单失败) + /// + public int? before_status { get; set; } + /// + /// 是否创建对冲(0:未创建,1:已创建) + /// + public int? is_create_hedge_order { get; set; } + /// + /// 对冲状态(0:已报,1:全部对冲,2:未完全对冲) + /// + public int? hedge_status { get; set; } + /// + /// 对冲单撤单状态(-1:未创建对冲单,1:未撤单,2:已撤单,3:已拒绝,4:下单失败) + /// + public int? hedge_cancel_status { get; set; } + /// + /// 交易方向(0:买入,1:卖出) + /// + public int? side { get; set; } + /// + /// 确认类型(0:自动确认,1:交易员手动确认) + /// + public int? confirm_type { get; set; } + /// + /// 委托收益率 + /// + public decimal? entrust_yield { get; set; } + /// + /// 委托净价 + /// + public decimal? price { get; set; } + /// + /// 委托净价(含费) + /// + public decimal? price_include_fee { get; set; } + /// + /// 委托全价 + /// + public decimal? full_price { get; set; } + /// + /// 委托全价(含费) + /// + public decimal? full_price_include_fee { get; set; } + /// + /// 委托数量类型:1:整量:,2:非整量 + /// + public int? order_qty_type { get; set; } + /// + /// 委托数量(万) + /// + public decimal? order_qty { get; set; } + /// + /// 成交数量(万) + /// + public decimal? last_shares { get; set; } + /// + /// 对冲成交数量(万) + /// + public decimal? hedge_deal_number { get; set; } + /// + /// 累计成交 + /// + public decimal? total_traded { get; set; } + /// + /// 成交收益率 + /// + public decimal? ytm { get; set; } + /// + /// 成交净价 + /// + public decimal? deal_price { get; set; } + /// + /// 成交净价(含费) + /// + public decimal? deal_price_include_fee { get; set; } + /// + /// 成交全价 + /// + public decimal? deal_full_price { get; set; } + /// + /// 成交全价(含费) + /// + public decimal? deal_full_price_include_fee { get; set; } + /// + /// 撤单数量(万) + /// + public decimal? cancel_qty { get; set; } + /// + /// 清算时间(0:T+0,1:T+1,2:T+2,远期:具体年月日) + /// + public string settl_type { get; set; } + /// + /// 清算日期 + /// + public string settl_date { get; set; } + /// + /// 剩余期限 + /// + public string remaining_term { get; set; } + /// + /// 债券票面利率类型(501001000:浮动利率,501002000:固定利率,501003000:累进利率) + /// + public string coupon_type { get; set; } + /// + /// 票面利率 + /// + public decimal? coupon_rate { get; set; } + /// + /// 开平标志 + /// + public int? open_close { get; set; } + /// + /// 币种 + /// + public string currency { get; set; } + /// + /// 交易费率类型(0:百分比,1:单价固定交易费) + /// + public int? commission_rate_type { get; set; } + /// + /// 交易费率 + /// + public decimal? commission_rate { get; set; } + /// + /// 交易费用 + /// + public decimal? commission { get; set; } + /// + /// 远期 + /// + public string future_date { get; set; } + /// + /// STC下单响应结果 + /// + public string stc_order_result { get; set; } + /// + /// 订单来源(0:TRS客户端,1:TRS交易端,2:第三方平台,3:成交补录) + /// + public byte? order_source { get; set; } + /// + /// 交易员ID + /// + public long? trader_id { get; set; } + /// + /// 委托时间 + /// + public DateTime? create_time { get; set; } + /// + /// 创建人 + /// + public long? create_user { get; set; } + /// + /// 更新时间 + /// + public DateTime? update_time { get; set; } + /// + /// 更新人 + /// + public long? update_user { get; set; } + /// + /// 限额告警成交备注 + /// + public string limit_alert_remark { get; set; } + /// + /// QT信息主键ID + /// + public long? qt_msg_primary_id { get; set; } + /// + /// 客户机构简称(客户简称) + /// + public string abbreviation { get; set; } + } +} diff --git a/YLErpDAL/BLL/EodSettlement/RealTimeClientBanlanceService.cs b/YLErpDAL/BLL/EodSettlement/RealTimeClientBanlanceService.cs index c4d1d0e7..ad4cf13e 100644 --- a/YLErpDAL/BLL/EodSettlement/RealTimeClientBanlanceService.cs +++ b/YLErpDAL/BLL/EodSettlement/RealTimeClientBanlanceService.cs @@ -59,7 +59,10 @@ namespace YLErp.BLL.Eod using var clientDb = new ClientDBContext(); var dmaClients = clientDb.client.ToList(); var clientIds = dmaClients.Select(s => s.id); - return GetBanlances(clientIds, valuedateBLL.ValueDate, calcDate: valuedateBLL.ValueDate); + var valuedate = valuedateBLL.ValueDate; + //获取根据系统时间 + var lastBalanceDate = EodOperationBase.GetLastSettlementDate(valuedate); + return GetBanlances(clientIds, lastBalanceDate.AddDays(1), calcDate: valuedateBLL.ValueDate); } public IEnumerable GetBanlances(IEnumerable clientIds, DateTime startDate, DateTime? endDate = null, DateTime? calcDate = null) { @@ -199,6 +202,7 @@ namespace YLErp.BLL.Eod //获取当日所有执行的交易 交易日为当前交易日或者行权日为当前交易日 ProcessClientPosition(); ProcessClientSwap(lastSettletDate, calcDate.Value); + ProcessClientFrozen(calcDate.Value, new List(), _clientBalanceDic); //if (PS.Config.IsGuoJun)//获取当日国君互换持仓 //{ // endDate = endDate.HasValue ? endDate.Value : startDate; @@ -1430,7 +1434,148 @@ namespace YLErp.BLL.Eod } } - class ClientBalanceEx : ClientSettleBalance + /// + /// 计算客户的冻结资金 + /// + /// + public void ProcessClientFrozen(DateTime startDate, List bondFlows, Dictionary clientBalanceDic) + { + var clientIds = clientBalanceDic.Keys.ToList(); + // step1 获取当天未簿记流水 + var flows = DbContext.swap_flow.Where(x => x.OccurTime == startDate && x.DataState == (int)SwapFlowDateStateEnum.等待完成 && clientIds.Contains(x.ClientId ?? 0)).AsNoTracking().ToList(); + + // step2 获取客户端当天已报,意向待确认,部分成交委托订单 + using var bondDb = new BondOmsDBContext(); + var nextDate = startDate.AddDays(1); + List calcStatus = new List() { -1, 0, 1 }; + var clientOrder = bondDb.client_order.Where(x => x.create_time > startDate && x.create_time < nextDate && calcStatus.Contains(x.status ?? 0) && clientIds.Contains(x.client_id ?? 0)).AsNoTracking().ToList(); + // 将clientOrder转换为swap_flow,方便合并 + clientOrder.ForEach(item => + { + swap_flow flow = new swap_flow() + { + BsType = (item.side ?? 0) + 1, + UnderlyingCode = item.security_id, + ClientId = item.client_id ?? 0, + ClientName = item.client_name, + TradingQty = (item.order_qty ?? 0) - (item.last_shares ?? 0) + }; + // clientOrder中数量单位为万 + flow.TradingQty *= 10000; + bondFlows.Add(flow); + }); + flows.AddRange(bondFlows); + // step3 从swap_position获取已簿记原持仓数据 + var positionQuery = from t in DbContext.trade.Where(x => clientIds.Contains(x.ClientId) && x.TradeType == "收益互换" && x.ValidState != ConsGlobal.InValid && ConsTrade.PositionTradeStatusList.Contains(x.TradeStatus)) + join p in DbContext.swap_position on t.id equals p.SwapTradeId + where !p.IsInitial && !p.Invalid + select new + { + t.ClientId, + p.PosiDirection, + p.PosiQuantity, + p.UnderlyingCode, + p.PositionType + }; + var positions = positionQuery.ToList(); + // step4 将未簿记持仓与已有持仓分别 按客户,标的分组计算各自轧差名义本金 + // 取设置的预付金比例 + var clientMarginTemplates = DbContext.client_marginrate.Where(x => x.ValueDate <= startDate).OrderByDescending(o => o.ValueDate).AsNoTracking().ToList(); + var flowGroup = flows.GroupBy(s => new { s.ClientId, s.UnderlyingCode }); + foreach (var itemGroup in flowGroup) + { + var balance = clientBalanceDic[itemGroup.Key.ClientId ?? 0]; + string marginType = balance.ClientType == 1 ? "多空组合" : "品种"; + // 获取客户预付金比例设置 + var clientMarginTemplate = clientMarginTemplates.FirstOrDefault(x => x.ClientId == itemGroup.Key.ClientId && x.Type == marginType); + if (clientMarginTemplate == null) + { + clientMarginTemplate = clientMarginTemplates.FirstOrDefault(x => x.ClientId == 0 && x.Type == marginType); + } + // 没有设置预付金比例则跳过 + if (clientMarginTemplate == null) + { + continue; + } + // 获取当前客户当前标的持仓数据,合并后的名义本金数量 + var positionLsit = positions.Where(x => x.UnderlyingCode == itemGroup.Key.UnderlyingCode && x.ClientId == itemGroup.Key.ClientId).ToList(); + var positionNotional = positionLsit.Sum(s => s.PosiQuantity * (s.PositionType == (int)PositionTypeFlag.Long ? 1 : -1)); + //获取虚拟持仓合并后的名义本金数量 + var virtualNotional = itemGroup.Sum(s => s.TradingQty * (s.BsType == (int)PositionTypeFlag.Long ? 1 : -1)); + decimal money = CalcFrozanMarginMoney(positionNotional, virtualNotional, balance.ClientType == 1, clientMarginTemplate); + balance.FrozenMarginMoney += Convert.ToDouble(money); + } + } + /// + /// 计算冻结的预付金变化 + /// + /// 持仓名义本金 + /// 虚拟持仓名义本金 + /// 是否定义文件类型客户 + /// 预付金设置信息 + /// + private decimal CalcFrozanMarginMoney(decimal positionNotional, decimal virtualNotional, bool dma, client_marginrate clientMarginTemplate) + { + decimal money = 0; + var virtualPosi = positionNotional + virtualNotional; + var posiType = positionNotional >= 0 ? (int)PositionTypeFlag.Long : (int)PositionTypeFlag.Short; + var virtualPosiType = virtualPosi >= 0 ? (int)PositionTypeFlag.Long : (int)PositionTypeFlag.Short; + var positionNotionalAbs = Math.Abs(positionNotional); + var virtualNotionalAbs = Math.Abs(virtualNotional); + decimal longMarginRate = 0; + decimal shortMarginRate = 0; + //普通客户 + if (!dma) + { + longMarginRate = Convert.ToDecimal(clientMarginTemplate.InitMarginRate); + shortMarginRate = longMarginRate; + } + else //定义文件类型客户 + { + longMarginRate = Convert.ToDecimal(clientMarginTemplate.LongInitMarginRate); + shortMarginRate = Convert.ToDecimal(clientMarginTemplate.ShortInitMarginRate); + } + // 当前持仓多头 + if (posiType == (int)PositionTypeFlag.Long) + { + if (virtualPosiType == (int)PositionTypeFlag.Long) + { + money = virtualNotional * longMarginRate; + } + else //虚拟持仓空头 + { + if (virtualNotionalAbs <= positionNotionalAbs) + { + money = virtualNotional * longMarginRate; + } + else + { + money = (-virtualNotional * shortMarginRate) - positionNotional * (longMarginRate + shortMarginRate); + } + } + } + // 当前持仓空头 + else + { + if (virtualPosiType == (int)PositionTypeFlag.Short) + { + money = (-virtualNotional) * shortMarginRate; + } + else //虚拟持仓多头 + { + if (virtualNotionalAbs <= positionNotionalAbs) + { + money = (-virtualNotional) * shortMarginRate; + } + else + { + money = (virtualNotional + positionNotional) * longMarginRate + positionNotional * shortMarginRate; + } + } + } + return money; + } + public class ClientBalanceEx : ClientSettleBalance { public double InFundOther { get; set; } diff --git a/YLErpDAL/DataBase/BondOmsDBContext.cs b/YLErpDAL/DataBase/BondOmsDBContext.cs index 12e51f3b..10805b55 100644 --- a/YLErpDAL/DataBase/BondOmsDBContext.cs +++ b/YLErpDAL/DataBase/BondOmsDBContext.cs @@ -14,5 +14,6 @@ namespace YLErp.DataBase } public DbSet client_deal { get; set; } public DbSet client_position { get; set; } + public DbSet client_order { get; set; } } } diff --git a/YLErpDAL/Model/ClientBalanceForTrsResponse.cs b/YLErpDAL/Model/ClientBalanceForTrsResponse.cs index d6e4fb74..db0a9777 100644 --- a/YLErpDAL/Model/ClientBalanceForTrsResponse.cs +++ b/YLErpDAL/Model/ClientBalanceForTrsResponse.cs @@ -38,5 +38,66 @@ namespace YLErp.Model /// 授信额度 /// public double Credit { get; set; } + /// + /// 期初结存 + /// + public double LastDayRemainFund { get; set; } + /// + /// 出金入金 + /// + public double NetFund { get; set; } + /// + /// 互换成交收支 + /// + public double OptionPremiumSwap { get; set; } + /// + /// 互换了结收支 + /// + public double SwapBalance { get; set; } + /// + /// 其他收支 + /// + public double OtherFund { get; set; } + /// + /// 期末结存 + /// + public double AmountFund { get; set; } + /// + /// 质押市值 + /// + public double GuaranteesTotalAmount { get; set; } + /// + /// 预付金占用 + /// + public double MinusPayableMarginTotal { get; set; } + /// + /// 应付了结交易款 + /// + public double ClosedTradePayableFundTotal { get; set; } + /// + /// 应付存续交易款 + /// + public double PositionTradePayableFundTotal { get; set; } + /// + /// 追保金额 + /// + public double MarginByPayableMarginTotal { get; set; } + /// + /// 应付资金总额 + /// + public double PayableFundTotal { get; set; } + /// + /// 可取资金 + /// + public double DesirableFundTotal { get; set; } + /// + /// 实现盈亏 + /// + public double WinLoss { get; set; } + + /// + /// 实际可用资金 + /// + public double AvailableMoney { get; set; } } } diff --git a/YLErpDAL/Model/TradeClientCashCalcReq.cs b/YLErpDAL/Model/TradeClientCashCalcReq.cs new file mode 100644 index 00000000..44c882ac --- /dev/null +++ b/YLErpDAL/Model/TradeClientCashCalcReq.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace YLErp.Model +{ + /// + /// trs下单假成交交易计算可用资金请求 + /// + public class TradeClientCashCalcReq + { + /// + /// 标的代码 + /// + public string underlyingCode { get; set; } + /// + /// 客户id + /// + public int clientId { get; set; } + /// + /// 交易数量 + /// + public decimal tradingQty { get; set; } + /// + /// 买卖方向 + /// + public int side { get; set; } + } +} diff --git a/YLErpDAL/Model/TradeClientCashCalcResp.cs b/YLErpDAL/Model/TradeClientCashCalcResp.cs new file mode 100644 index 00000000..542ad388 --- /dev/null +++ b/YLErpDAL/Model/TradeClientCashCalcResp.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace YLErp.Model +{ + /// + /// trs下单假成交交易计算可用资金返回 + /// + public class TradeClientCashCalcResp + { + public int clientId { get; set; } + + public double availableAmount { get;set; } + } +} diff --git a/YLErpDAL/Modules/EodModule/ClientFrozenFundsService.cs b/YLErpDAL/Modules/EodModule/ClientFrozenFundsService.cs index 9f4ae10a..94264f22 100644 --- a/YLErpDAL/Modules/EodModule/ClientFrozenFundsService.cs +++ b/YLErpDAL/Modules/EodModule/ClientFrozenFundsService.cs @@ -35,7 +35,7 @@ } //用户所有未确认出金信息 - ProcessClientCashInCashOut(valueDate); + // ProcessClientCashInCashOut(valueDate); //用户所有赎回申请抵押品 ProcessClientCashInOutProduct(valueDate); diff --git a/YLErpWeb/App/KafkaTask/ClientBalanceTask.cs b/YLErpWeb/App/KafkaTask/ClientBalanceTask.cs index 81d5813b..f1d389ba 100644 --- a/YLErpWeb/App/KafkaTask/ClientBalanceTask.cs +++ b/YLErpWeb/App/KafkaTask/ClientBalanceTask.cs @@ -12,6 +12,7 @@ namespace YLErp.Web.App private readonly IYcLogger _logger; private readonly CancellationTokenSource _cts = new CancellationTokenSource(); private KafkaConsumerHelper _kafkaConsumer; + private IYLCache _yLCache; private IKafkaProduce kafkaProduceHelper; private string onRspAccountCapitalTopicTopic = string.Empty; public ClientBalanceTask(IKafkaProduce kafkaProduce, IYLCache yLCache) @@ -68,8 +69,10 @@ namespace YLErp.Web.App Result result = new Result(); try { - var cb = ClientAssetDataService.GetClientLatestBalance(null, lastBalanceDate, req.clientId, false, false, false); - cb.AvailableAmount = cb.AmountFund + cb.TotalCredit + cb.PayableMargin + cb.GuaranteesTotalAmount; + var cbs = new RealTimeClientBanlanceService(new OptUserInfo(0, "实时客户资金服务", OptUserFrom.Service)).GetBanlances(new List() { req.clientId }, lastBalanceDate.AddDays(1), calcDate: valuedateBLL.ValueDate); + var cb = cbs.FirstOrDefault(); + var AvailableMoney = cb.AmountFund + cb.TotalCredit + cb.PayableMargin + cb.GuaranteesTotalAmount; + cb.AvailableAmount = AvailableMoney - cb.FrozenMarginMoney; var obj = new ClientBalanceForTrsResponse { TotalAmountTotal = cb.RoundedTotalAmountTotal, @@ -79,10 +82,15 @@ namespace YLErp.Web.App DaliyPnl = Math.Round(cb.DaliyPnl, 2), ClientId = req.clientId, ClientType = cb.ClientType, - Credit=cb.TotalCredit + Credit = cb.TotalCredit, + AvailableMoney = AvailableMoney }; result.success = true; result.obj = obj; + if (_yLCache != null) + { + _yLCache.StringSet("ClientBalance:" + cb.ClientId, obj); + } } catch (Exception ex) { diff --git a/YLErpWeb/Controllers/trade_spanController.cs b/YLErpWeb/Controllers/trade_spanController.cs index 13e5124f..04c88458 100644 --- a/YLErpWeb/Controllers/trade_spanController.cs +++ b/YLErpWeb/Controllers/trade_spanController.cs @@ -2,6 +2,8 @@ using iTextSharp.text.pdf; using Microsoft.AspNetCore.Authorization; using System.Text; +using YLErp.BLL.Eod; +using YLErp.Cache; using YLErp.DBModels; using YLErp.DBModels.Consts; using YLErp.Enums; @@ -11,11 +13,17 @@ using YLErp.Modules.EodModule; using YLErp.Modules.TradeModule.DocGenerateModule; using YLErp.Modules.TradeModule.QueryModule; using YLErp.Modules.TradeModule.SwapModule; +using static YLErp.BLL.Eod.RealTimeClientBanlanceService; namespace YLErp.Web.Controllers { public class trade_spanController : BaseController { + IYLCache _yLCache; + public trade_spanController(IYLCache yLCache) + { + _yLCache = yLCache; + } public JsonResult GeneratePaymentDoc(List tradeIds, DateTime? valueDate) { if (tradeIds == null || tradeIds.Count == 0) @@ -139,6 +147,69 @@ namespace YLErp.Web.Controllers }; return JsonSuccess("", obj); } + + /// + /// trs计算客户下单后可用资金 + /// + /// + /// + [AllowAnonymous] + [HttpPost] + public JsonResult CalcClientTradeAvailableAmount([FromBody] List calcReqs) + { + if (calcReqs.Count() == 0) + { + return JsonError("缺少参数"); + } + var clientIds = calcReqs.Select(s => s.clientId).ToList(); + Dictionary clientBalanceDic = new Dictionary(); + foreach (var clientId in clientIds) + { + var client = DataCacheProvider.GetClientDataSource().GetData(clientId); + + if (client == null) + { + continue; + } + clientBalanceDic[clientId] = new ClientBalanceEx + { + ClientId = clientId, + IsTradeCredit = client?.IsTradeCredit == 1, + MarginOptionType = client?.MarginOptionType, + CreditCanApplySwap = client.creditCanApplySwap, + TotalCreditStockEqvNotional = double.NaN, + SettlementCurrency = client?.SettlementCurrency, + ClientType = client.SwapTradeType ?? 0, + ClientName = client.Name + }; + } + List bondFlows = new List(); + foreach (var item in calcReqs) + { + swap_flow flow = new swap_flow() + { + BsType = item.side + 1, + UnderlyingCode = item.underlyingCode, + ClientId = item.clientId, + TradingQty = item.tradingQty + }; + bondFlows.Add(flow); + } + new RealTimeClientBanlanceService(new OptUserInfo(0, "计算客户资金变化服务", OptUserFrom.Service)).ProcessClientFrozen(valuedateBLL.ValueDate, bondFlows, clientBalanceDic); + List list = new List(); + foreach (var item in clientBalanceDic) + { + + TradeClientCashCalcResp tradeClientCashCalc = new TradeClientCashCalcResp() + { + clientId = item.Key + }; + var clientBalanceCache = _yLCache.StringGet("ClientBalance:" + item.Key); + tradeClientCashCalc.availableAmount = (clientBalanceCache?.AvailableMoney ?? 0) - item.Value.FrozenMarginMoney; + list.Add(tradeClientCashCalc); + } + return JsonSuccess("", list); + } public Dictionary clientBalanceQueryJson(TradeSpanReq req) { //按客户日期查询trade_span信息