295 lines
18 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|