Files
zszq-trs/YLErpDAL/BLL/MarginCalculation/ZhongJinMarginCalculation.cs
T
2024-05-09 14:06:26 +08:00

295 lines
18 KiB
C#

using YLErp.BLL.Calculation;
using YLErp.Helpers;
using YLErp.Modules;
using YLErp.Modules.CalculationModule;
using YLErp.Modules.EodModule;
using YLErp.QdpModule;
namespace YLErp.BLL.MarginCalculation
{
public class ZhongJinMarginCalculation : MarginCalculationBase
{
// 定义一个静态变量来保存类的实例
public static readonly ZhongJinMarginCalculation Instance;
static ZhongJinMarginCalculation()
{
Instance = new ZhongJinMarginCalculation();
}
// 定义私有构造函数,使外界不能创建该类实例
protected ZhongJinMarginCalculation()
{
}
public override List<trade_span> RunMarginCalculation(RunMarginCalculationReq req)
{
if (req.tradeList == null || !req.tradeList.Any())
{
return new List<trade_span>();
}
var helper = new RunMarginCalculationHelper(req, _underlyingDataProvider);
var tradeSpans = RunMarginCalculationForCommodity(helper);
return tradeSpans;
}
//商品类预付金计算
private static List<trade_span> RunMarginCalculationForCommodity(RunMarginCalculationHelper helper)
{
var resultMap = new Dictionary<int, trade_span>();
var reqConv = new CalculateRisksForTradesReq
{
valueDate = helper.req.settleDate,
tradeList = helper.req.tradeList,
priceProvider = helper.req.PriceProvider,
pricingRequest = QdpPricingRequest.PV_ONLY,
addVolRateDic = null,
volType = helper.req.volType,
isUseTradeVol = PS.Config.IsTradeVol,
PreciseTimeMode = helper.req.CalcMarginType != Enums.CalcMarginTypeEnum.EodMargin,
isAddVolPercent = true,
overrideVolsForTrade = null,
isMarginCalc = true
};
if (helper.req.CalcMarginType == Enums.CalcMarginTypeEnum.EodMargin)
{
reqConv.calcScenario = Enums.CalcScenarioEnum.EodSettlement;
}
var tradeRiskResult = CalculatorHelper.CalculateRisksForTrades(reqConv);
foreach (var item in tradeRiskResult.Results)
{
resultMap[item.Trade.id] = helper.CreateTradeSpan(item.Trade);
resultMap[item.Trade.id].WorstCastClientPayable = item.ValueResult.Pv;
resultMap[item.Trade.id].Commission = item.ValueResult.ExtendInfo.Commission;
resultMap[item.Trade.id].VM = item.ValueResult.ExtendInfo.FloatingWinLoss + item.ValueResult.ExtendInfo.AnnualFee;
resultMap[item.Trade.id].IM = item.ValueResult.ExtendInfo.IM;
resultMap[item.Trade.id].PFE = item.ValueResult.ExtendInfo.PFE;
resultMap[item.Trade.id].Notional = item.Trade.Notional;
resultMap[item.Trade.id].StockEqvNotional = item.Trade.StockEqvNotional;
}
return resultMap.Values.ToList();
}
public override List<trade_span> CalcClientMargin(CalcClientMarginReq req)
{
var clientSpanNews = new List<ClientSpan>();
using (var db = new YLContext())
{
var tradeIds = req.tradeSpans.Select(t => t.TradeId).ToList();
var tradeList = db.trade.AsNoTracking().Where(t => tradeIds.Contains(t.id)).ToList();
var underlyingIds = tradeList.Select(x => x.UnderlyingId);
var underlyings = db.underlying_manager.AsNoTracking().Where(x => underlyingIds.Contains(x.id));
var tradeSwaps = db.trade_swap.AsNoTracking().Where(x => tradeIds.Contains(x.TradeId)).ToList();
var tradeSpanInfo = (from tradeSpan in req.tradeSpans
join trade in tradeList on tradeSpan.TradeId equals trade.id
join tradeSwap in tradeSwaps on tradeSpan.TradeId equals tradeSwap.TradeId
join underlying in underlyings on trade.UnderlyingId equals underlying.id
where tradeSpan.ValueDate == req.settleDate
select new { trade, tradeSwap, tradeSpan, underlying }).ToList();
if (req.tradeSpans != null && req.tradeSpans.Count > 0)
{
var clientGroups = tradeSpanInfo.GroupBy(t => t.trade.ClientId);
foreach (var clientGroup in clientGroups)
{
var client = DataCacheProvider.GetClientDataSource().GetData(clientGroup.Key);
var credit = db.credit.FirstOrDefault(x => x.CreditStartDate <= req.settleDate && x.CreditDeadLine >= req.settleDate && x.ClientId == clientGroup.Key && x.ProcessStatus == "已审批");
var hasCredit = credit != null && credit.PFECredit > 0;
var IMCredit = credit != null ? credit.IMCredit : 0;
var PFECredit = credit != null ? credit.PFECredit : 0;
var ClientCredit = credit != null ? credit.Credit : 0;
var clientSpan = new ClientSpan
{
ClientId = clientGroup.Key,
ValueDate = req.settleDate,
OptId = req.userId,
OptName = req.userName,
OptDate = DateTime.Now,
SpanType = req.SpanType
};
//负数代表客户应缴预付金,正数代表客户应收预付金
if (client.BoundSide == BoundSideEnum.北向)
{
var IM = Math.Max(clientGroup.Sum(x => x.tradeSpan.IM ?? 0) - (hasCredit ? (IMCredit ?? 0) : 0), 0);
clientSpan.IM = IM;
clientSpan.VM = clientGroup.Sum(x => x.tradeSpan.VM ?? 0);
clientSpan.Commission = clientGroup.Sum(x => x.tradeSpan.Commission ?? 0);
clientSpan.WorstCastClientPayable = -(Math.Max(IM + clientSpan.VM.Value + clientSpan.Commission.Value, 0));
}
else
{
#region IM抵扣逻辑
var totalStockEqvNotional = credit == null ? 0 : credit.DeductStockEqvNotional ?? 0;
var imDeduct = 0.00;
if (totalStockEqvNotional > 0)
{
var varietyGroups = clientGroup.Where(O => O.trade.MarginType == DBModels.Enums.MarginTypeEnum.DEFAULT).GroupBy(t => t.underlying.UnderlyingTypeId).Select(t => new ClientSpan
{
VarietyId = t.Key,
ClientId = clientGroup.Key,
ValueDate = req.settleDate,
Notional = t.Sum(g => g.tradeSpan.Notional * (g.tradeSwap.PayLongShort == "多头" ? 1 : -1)),
StockEqvNotional = Math.Abs(t.Sum(g => g.tradeSpan.StockEqvNotional * new EodCurrencyRateService(OptUserInfo.SystemUser).GetCurrencyRate(g.trade.QuoteCurrency, g.trade.SettlementCurrency, req.settleDate) * (g.tradeSwap.PayLongShort == "多头" ? 1 : -1))),
IM = Math.Abs(t.Sum(g => (g.tradeSpan.IM ?? 0) * (g.tradeSwap.PayLongShort == "多头" ? 1 : -1))),
OptId = req.userId,
OptName = req.userName,
OptDate = DateTime.Now,
SpanType = req.SpanType
}).ToList();
var varietyMappingDeducts = db.variety_mapping_deduct.AsNoTracking().OrderBy(x => x.Index).ToList();
varietyMappingDeducts.ForEach(x =>
{
if (totalStockEqvNotional > 0)
{
var varietyGroup = varietyGroups.FirstOrDefault(y => y.VarietyId == x.VarietyId);
var varietyGroupOther = varietyGroups.FirstOrDefault(y => y.VarietyId == x.VarietyIdOther);
if (varietyGroup != null && varietyGroupOther != null && varietyGroup.Notional * varietyGroupOther.Notional < 0 && varietyGroup.StockEqvNotional > 0 && varietyGroupOther.StockEqvNotional > 0)
{
var deductRate = Math.Min(1, Math.Max(totalStockEqvNotional / 2 / (varietyGroup.StockEqvNotional ?? 0), totalStockEqvNotional / 2 / (varietyGroupOther.StockEqvNotional ?? 0)));
var stockEqvNotional = Math.Min(Math.Min(totalStockEqvNotional / 2, varietyGroup.StockEqvNotional ?? 0), varietyGroupOther.StockEqvNotional ?? 0);
varietyGroup.StockEqvNotional -= stockEqvNotional;
varietyGroupOther.StockEqvNotional -= stockEqvNotional;
totalStockEqvNotional -= (stockEqvNotional * 2);
var deltaDeduct = Math.Min(Math.Abs(varietyGroup.Notional.Value * x.Unit / x.DPSR), Math.Abs(varietyGroupOther.Notional.Value * x.UnitOther / x.DPSROther));
imDeduct += (varietyGroup.IM ?? 0) * deltaDeduct / Math.Abs(varietyGroup.Notional.Value * x.Unit / x.DPSR) * deductRate * x.ConcessionRate;
imDeduct += (varietyGroupOther.IM ?? 0) * deltaDeduct / Math.Abs(varietyGroupOther.Notional.Value * x.UnitOther / x.DPSROther) * deductRate * x.ConcessionRate;
}
}
});
}
#endregion
#region PFE
var pfe = 0.00;
var varietyPFEDeduct = db.variety_pfe_deduct.AsNoTracking().ToList();
List<string> varietyIdsDeduct = new List<string>();
varietyPFEDeduct.ForEach(x =>
{
var varietyIds = x.VarietyIds.Split(',');
varietyIdsDeduct.AddRange(varietyIds);
var underlyingGroups = clientGroup.Where(O => O.trade.MarginType == DBModels.Enums.MarginTypeEnum.DEFAULT && varietyIds.Contains(O.underlying.UnderlyingTypeId.ToString())).GroupBy(t => t.underlying.id).Select(t => new ClientSpan
{
UnderlyingId = t.Key,
ClientId = clientGroup.Key,
ValueDate = req.settleDate,
PFE = t.Sum(g => (g.tradeSpan.PFE ?? 0) * (g.tradeSwap.PayLongShort == "多头" ? 1 : -1)),
OptId = req.userId,
OptName = req.userName,
OptDate = DateTime.Now,
SpanType = req.SpanType
}).ToList();
pfe += Math.Sqrt(Math.Pow(x.Rate, 2) * Math.Pow(underlyingGroups.Sum(u => u.PFE ?? 0), 2) + (1 - Math.Pow(x.Rate, 2)) * underlyingGroups.Sum(u => Math.Pow(u.PFE ?? 0, 2)));
});
var underlyingGroupsOutOfDeduct = clientGroup.Where(O => O.trade.MarginType == DBModels.Enums.MarginTypeEnum.DEFAULT && !varietyIdsDeduct.Contains(O.underlying.UnderlyingTypeId.ToString())).GroupBy(t => new { underlyingId = t.underlying.id, varietyId = t.underlying.UnderlyingTypeId }).Select(t => new ClientSpan
{
VarietyId = t.Key.varietyId,
UnderlyingId = t.Key.underlyingId,
ClientId = clientGroup.Key,
ValueDate = req.settleDate,
PFE = t.Sum(g => (g.tradeSpan.PFE ?? 0) * (g.tradeSwap.PayLongShort == "多头" ? 1 : -1)),
OptId = req.userId,
OptName = req.userName,
OptDate = DateTime.Now,
SpanType = req.SpanType
}).ToList();
var varietyIdsOutOfDeduct = underlyingGroupsOutOfDeduct.Select(x => x.VarietyId).ToList();
varietyIdsOutOfDeduct.ForEach(x =>
{
var underlyingGroups = underlyingGroupsOutOfDeduct.Where(y => y.VarietyId == x);
pfe += Math.Sqrt(Math.Pow(0.9, 2) * Math.Pow(underlyingGroups.Sum(u => u.PFE ?? 0), 2) + (1 - Math.Pow(0.9, 2)) * underlyingGroups.Sum(u => Math.Pow(u.PFE ?? 0, 2)));
});
#endregion
clientSpan.PFE = pfe;
var IM = Math.Max(clientGroup.Sum(x => x.tradeSpan.IM ?? 0) - (IMCredit ?? 0) - imDeduct, 0);
var PFE = Math.Max(pfe - (PFECredit ?? 0), 0);
var IMNeed = Math.Max(IM, PFE);
if (IM > PFE)
{
clientSpan.Deduct = imDeduct;
}
clientSpan.IM = IMNeed;
//远期标的未结算但已平仓的VM需要统计到预付金的VM里参与预付金计算
var tradePres = db.trade.Where(x => x.ClientId == client.id && x.SettlementDate != null && x.SettlementDate > req.settleDate && x.ValidState != "InValid").ToList();
var tradeCurrencyRates = tradePres.Select(x => new { TradeId = x.id, Rate = new EodCurrencyRateService(OptUserInfo.SystemUser).GetCurrencyRate(x.QuoteCurrency, x.SettlementCurrency, req.settleDate) });
var tradeSettledIds = tradePres.Select(x => x.id);
var tradeCashPreIds = db.trade_cash_pre.Where(x => tradeSettledIds.Contains(x.TradeId) && x.HappenedDate != null && x.HappenedDate <= req.settleDate && x.Action == ClientCashInCashOut.系统操作_平仓费).Select(x => x.id).ToList();
var tradeCashDetails = db.trade_cash_detail.Where(x => tradeCashPreIds.Contains(x.TradeCashPreId) && (x.TradeCashType == "浮动收益" || x.TradeCashType == "利息")).ToList();
var tradeCashDetailsCommission = db.trade_cash_detail.Where(x => tradeCashPreIds.Contains(x.TradeCashPreId) && (x.TradeCashType == "了结手续费" || x.TradeCashType == "开仓手续费")).ToList();
clientSpan.VM = clientGroup.Sum(x => x.tradeSpan.VM ?? 0) + tradeCashDetails.Sum(x => (x.QuoteAmount ?? 0) * tradeCurrencyRates.FirstOrDefault(y => y.TradeId == x.TradeId).Rate);
clientSpan.Commission = clientGroup.Sum(x => x.tradeSpan.Commission ?? 0) + tradeCashDetailsCommission.Sum(x => (x.QuoteAmount ?? 0) * tradeCurrencyRates.FirstOrDefault(y => y.TradeId == x.TradeId).Rate);
clientSpan.PFEUsed = pfe + clientSpan.VM + clientSpan.Commission;
clientSpan.WorstCastClientPayable = -(Math.Max(IMNeed + (clientSpan.VM ?? 0) + (clientSpan.Commission ?? 0) - (ClientCredit ?? 0), 0));
}
clientSpanNews.Add(clientSpan);
}
}
//span类型为实时删除所有实时计算的交易的预付金信息
if (req.SpanType == ClientSpan.SpanType_RealTime)
{
if (req.RefreshClientIds != null)
{
db.BulkDelete<ClientSpan>($"{nameof(ClientSpan.ClientId)} in @ids", new { ids = req.RefreshClientIds });
}
else
{
db.BulkDelete<ClientSpan>($"{nameof(ClientSpan.SpanType)}=@SpanType", new { req.SpanType });
}
}
else
{
if (req.ClientIds != null)
{
var sql = $"{nameof(ClientSpan.ClientId)} in @ids and {nameof(ClientSpan.ValueDate)}='{req.settleDate.ToSqlDate()}' and {nameof(ClientSpan.SpanType)}={req.SpanType} and {nameof(ClientSpan.ModifiedFlag)}=0";
db.BulkDelete<ClientSpan>(sql, new { ids = req.ClientIds });
}
else
{
var sql = $"{nameof(ClientSpan.ValueDate)}='{req.settleDate.ToSqlDate()}' and {nameof(ClientSpan.SpanType)}={req.SpanType} and {nameof(ClientSpan.ModifiedFlag)}=0";
db.BulkDelete<ClientSpan>(sql);
}
var clientSpanOldsWithFlag = db.client_span.Where(t => t.ValueDate == req.settleDate && t.SpanType == req.SpanType && t.ModifiedFlag)
.Select(n => new { n.ValueDate, n.ClientId }).ToList();
//筛选出可以修改的clientSpan
clientSpanNews = clientSpanNews.Where(c => !clientSpanOldsWithFlag.Any(t => t.ValueDate == c.ValueDate && t.ClientId == c.ClientId)).ToList();
}
if (clientSpanNews.Count > 0)
{
MySqlBulkExtensions.BulkInsert(db, clientSpanNews);
}
db.SaveChanges();
return req.tradeSpans;
}
}
}
}