390 lines
21 KiB
C#
390 lines
21 KiB
C#
using YLErp.BLL.Calculation;
|
|
using YLErp.Helpers;
|
|
using YLErp.Modules;
|
|
using YLErp.Modules.PricingModule;
|
|
using YLErp.QdpModule;
|
|
|
|
namespace YLErp.BLL.MarginCalculation
|
|
{
|
|
/// <summary>
|
|
/// 招证
|
|
/// </summary>
|
|
public class ZhaoZhengMarginCalculation : MarginCalculationBase
|
|
{
|
|
public static readonly ZhaoZhengMarginCalculation Instance;
|
|
|
|
static ZhaoZhengMarginCalculation()
|
|
{
|
|
Instance = new ZhaoZhengMarginCalculation();
|
|
}
|
|
|
|
protected ZhaoZhengMarginCalculation()
|
|
{
|
|
}
|
|
|
|
public override List<trade_span> RunMarginCalculation(RunMarginCalculationReq req)
|
|
{
|
|
var result = new List<trade_span>();
|
|
|
|
if (req.CalcMarginType == Enums.CalcMarginTypeEnum.InitialMargin)
|
|
{
|
|
var swapList = req.tradeList.Where(O => O.TradeType == "收益互换");
|
|
RunMarginCalculationHelper helper = null;
|
|
foreach (var item in swapList)
|
|
{
|
|
helper ??= new RunMarginCalculationHelper(req, _underlyingDataProvider);
|
|
var clientRatio = helper.GetClient(item)?.Ratio1 ?? 1.0;
|
|
var tradeSpan = new trade_span();
|
|
var ratio = item.trade_swap.GetSwapRate - item.trade_swap.PaySwapRate;
|
|
logger.Info($"互换初始预付金:tradeId:{item.id} 名义本金 * (收取预付金比例 - 支付预付金比例) * 客户初始预付金系数");
|
|
logger.Info($"互换初始预付金:tradeId:{item.id} {item.OriginalStockEqvNotional} * ({item.trade_swap.GetSwapRate} - {item.trade_swap.PaySwapRate}) * {clientRatio}");
|
|
tradeSpan.SetAllSpvAndWorst((item.OriginalStockEqvNotional ?? 0) * ratio * clientRatio);
|
|
result.Add(tradeSpan);
|
|
}
|
|
var optionList = req.tradeList.Where(O => O.TradeType != "收益互换");
|
|
req.tradeList = optionList.ToList();
|
|
result.AddRange(calcPositionMargin(req));
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
return calcPositionMargin(req);
|
|
}
|
|
}
|
|
|
|
private List<trade_span> calcPositionMargin(RunMarginCalculationReq req)
|
|
{
|
|
var helper = new RunMarginCalculationHelper(req, _underlyingDataProvider);
|
|
var resultMap = new Dictionary<int, trade_span>();
|
|
|
|
helper.SetFieldsByTradeType();
|
|
var priceArr = helper.GetUpDownLimitPrices(6);
|
|
//var prices = priceArr.Select(O=> new (string key, IPriceProvider priceProvider){ ("up", upLimitPrices), ("down", downLimitPrices), ("normal", req.PriceProvider) };
|
|
var index = 0;
|
|
var loops = priceArr.Select(n =>
|
|
{
|
|
index++;
|
|
return new
|
|
{
|
|
pricekey = $"P{index}",
|
|
priceProvider = n,
|
|
};
|
|
}).ToArray();
|
|
foreach (var loop in loops)
|
|
{
|
|
var calcReq = helper.GetCalculateRisksForTradesReq(priceProvider: loop.priceProvider, addVolRateDic: null, overrideVols: null, pricingRequest: QdpPricingRequest.BASIC_PRICING);
|
|
|
|
var tradeRiskResult = CalculatorHelper.CalculateRisksForTrades(calcReq);
|
|
if (tradeRiskResult.Results == null || tradeRiskResult.Results.Count < 1)
|
|
{
|
|
continue;
|
|
}
|
|
var key = $"{loop.pricekey}";
|
|
|
|
foreach (var item in tradeRiskResult.Results)
|
|
{
|
|
var contains = resultMap.TryGetValue(item.Trade.id, out var tempTradeSpan);
|
|
|
|
if (!contains)
|
|
{
|
|
resultMap[item.Trade.id] = tempTradeSpan = helper.CreateTradeSpan(item.Trade);
|
|
}
|
|
|
|
switch (key)
|
|
{
|
|
case "P1":
|
|
logger.Info($"期权预付金:tradeId:{item.Trade.id} 价格1 = {loop.priceProvider.GetPrice(item.Trade.UnderlyingCode)} Pv = {item.ValueResult.Pv}");
|
|
tempTradeSpan.Spv1 = item.ValueResult.Pv;
|
|
break;
|
|
case "P2":
|
|
logger.Info($"期权预付金:tradeId:{item.Trade.id} 价格2 = {loop.priceProvider.GetPrice(item.Trade.UnderlyingCode)} Pv = {item.ValueResult.Pv}");
|
|
tempTradeSpan.Spv2 = item.ValueResult.Pv;
|
|
break;
|
|
case "P3":
|
|
logger.Info($"期权预付金:tradeId:{item.Trade.id} 价格3 = {loop.priceProvider.GetPrice(item.Trade.UnderlyingCode)} Pv = {item.ValueResult.Pv}");
|
|
tempTradeSpan.Spv3 = item.ValueResult.Pv;
|
|
break;
|
|
case "P4"://这里是现价计算的,所以同时保存一下Delta
|
|
tempTradeSpan.Spv4 = item.ValueResult.Pv;
|
|
var marginRatio = helper.GetMarginParamProvider().GetMarginRate(item.Trade.UnderlyingCode) ?? 1;
|
|
tempTradeSpan.DeltaMargin = item.ValueResult.DeltaCash * marginRatio;
|
|
logger.Info($"期权预付金:tradeId:{item.Trade.id} 价格4 = {loop.priceProvider.GetPrice(item.Trade.UnderlyingCode)} Pv = {item.ValueResult.Pv} Delta = {item.ValueResult.Delta} DeltaCash = {item.ValueResult.DeltaCash} 品种最低预付金率 = Delta * Price * 品种最低预付金比例 = {marginRatio} DeltaMargin = {tempTradeSpan.DeltaMargin}");
|
|
break;
|
|
case "P5":
|
|
logger.Info($"期权预付金:tradeId:{item.Trade.id} 价格5 = {loop.priceProvider.GetPrice(item.Trade.UnderlyingCode)} Pv = {item.ValueResult.Pv}");
|
|
tempTradeSpan.Spv5 = item.ValueResult.Pv;
|
|
break;
|
|
case "P6":
|
|
logger.Info($"期权预付金:tradeId:{item.Trade.id} 价格6 = {loop.priceProvider.GetPrice(item.Trade.UnderlyingCode)} Pv = {item.ValueResult.Pv}");
|
|
tempTradeSpan.Spv6 = item.ValueResult.Pv;
|
|
break;
|
|
case "P7":
|
|
logger.Info($"期权预付金:tradeId:{item.Trade.id} 价格7 = {loop.priceProvider.GetPrice(item.Trade.UnderlyingCode)} Pv = {item.ValueResult.Pv}");
|
|
tempTradeSpan.Spv7 = item.ValueResult.Pv;
|
|
break;
|
|
}
|
|
if (contains)
|
|
{
|
|
tempTradeSpan.SetWorstCastClientPayable();
|
|
|
|
if (tempTradeSpan.WorstCastClientPayable > 0)
|
|
{
|
|
var clientInfo = helper.GetClient(item.Trade);
|
|
var clientRatio = (req.CalcMarginType == Enums.CalcMarginTypeEnum.InitialMargin ? clientInfo?.Ratio1 : clientInfo?.Ratio) ?? 1.0;
|
|
if (key == "P7")
|
|
{
|
|
var ss = Math.Max(tempTradeSpan.WorstCastClientPayable ?? 0, tempTradeSpan.DeltaMargin ?? 0) * clientRatio;
|
|
logger.Info($"期权预付金:tradeId:{item.Trade.id} 预付金 = MaxPv > 0 Max(MaxPv, DeltaMargin) * 客户预付金率");
|
|
logger.Info($"期权预付金:tradeId:{item.Trade.id} {ss} = {tempTradeSpan.WorstCastClientPayable} > 0 Max({tempTradeSpan.WorstCastClientPayable}, {tempTradeSpan.DeltaMargin}) * {clientRatio}");
|
|
}
|
|
tempTradeSpan.WorstCastClientPayable = Math.Max(tempTradeSpan.WorstCastClientPayable ?? 0, tempTradeSpan.DeltaMargin ?? 0) * clientRatio;
|
|
}
|
|
else
|
|
{
|
|
if (key == "P7")
|
|
{
|
|
logger.Info($"期权预付金:tradeId:{item.Trade.id} 预付金 = MaxPv <= 0");
|
|
}
|
|
tempTradeSpan.WorstCastClientPayable = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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 tradeSpanInfo = (from tradeSpan in req.tradeSpans
|
|
join trade in tradeList on tradeSpan.TradeId equals trade.id
|
|
where tradeSpan.ValueDate == req.settleDate
|
|
select new { trade, tradeSpan }).ToList();
|
|
|
|
var clientGroups = tradeSpanInfo.GroupBy(t => t.trade.ClientId);
|
|
foreach (var clientGroup in clientGroups)
|
|
{
|
|
// 手动维护的预付金VarietyId是null,合计的时候需要排除掉,最后再加进来
|
|
var varietyGroup = clientGroup.Where(O => O.tradeSpan.VarietyId > 0).GroupBy(t => t.tradeSpan.VarietyId ?? 0).Select(t => new ClientSpan
|
|
{
|
|
VarietyId = t.Key,
|
|
ClientId = clientGroup.Key,
|
|
ValueDate = req.settleDate,
|
|
Spv1 = t.Sum(g => g.tradeSpan.Spv1) * (-1),
|
|
Spv2 = t.Sum(g => g.tradeSpan.Spv2) * (-1),
|
|
Spv3 = t.Sum(g => g.tradeSpan.Spv3) * (-1),
|
|
Spv4 = t.Sum(g => g.tradeSpan.Spv4) * (-1),
|
|
Spv5 = t.Sum(g => g.tradeSpan.Spv5) * (-1),
|
|
Spv6 = t.Sum(g => g.tradeSpan.Spv6) * (-1),
|
|
Spv7 = t.Sum(g => g.tradeSpan.Spv7) * (-1),
|
|
DeltaMargin = -Math.Abs(t.Sum(g => g.tradeSpan.DeltaMargin ?? 0)),
|
|
PositionWinLoss = t.Sum(g => g.tradeSpan.PositionWinLoss) * (-1),
|
|
OptId = req.userId,
|
|
OptName = req.userName,
|
|
OptDate = DateTime.Now,
|
|
SpanType = req.SpanType
|
|
}).ToList();
|
|
foreach (var varietyInfo in varietyGroup)
|
|
{
|
|
var tradeSpanIds = clientGroup.Where(O => O.tradeSpan.VarietyId == varietyInfo.VarietyId).Select(x => x.tradeSpan.id);
|
|
var tradeSpansUpdate = db.trade_span.Where(x => tradeSpanIds.Contains(x.id)).ToList();
|
|
var tradeSpansReq = req.tradeSpans.Where(x => tradeSpanIds.Contains(x.id)).ToList();
|
|
|
|
varietyInfo.SetWorstCastClientPayableMin();
|
|
|
|
if (varietyInfo.WorstCastClientPayable >= 0 || varietyInfo.WorstCastClientPayable.Value < (varietyInfo.DeltaMargin ?? 0))
|
|
{
|
|
if (varietyInfo.WorstCastClientPayable == varietyInfo.Spv1)
|
|
{
|
|
tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv1);
|
|
tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv1);
|
|
}
|
|
else if (varietyInfo.WorstCastClientPayable == varietyInfo.Spv2)
|
|
{
|
|
tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv2);
|
|
tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv2);
|
|
}
|
|
else if (varietyInfo.WorstCastClientPayable == varietyInfo.Spv3)
|
|
{
|
|
tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv3);
|
|
tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv3);
|
|
}
|
|
else if (varietyInfo.WorstCastClientPayable == varietyInfo.Spv4)
|
|
{
|
|
tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv4);
|
|
tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv4);
|
|
}
|
|
else if (varietyInfo.WorstCastClientPayable == varietyInfo.Spv5)
|
|
{
|
|
tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv5);
|
|
tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv5);
|
|
}
|
|
else if (varietyInfo.WorstCastClientPayable == varietyInfo.Spv6)
|
|
{
|
|
tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv6);
|
|
tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv6);
|
|
}
|
|
else
|
|
{
|
|
tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv7);
|
|
tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv7);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
varietyInfo.WorstCastClientPayable = varietyInfo.DeltaMargin ?? 0;
|
|
tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.DeltaMargin);
|
|
tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.DeltaMargin);
|
|
}
|
|
|
|
if (varietyInfo.WorstCastClientPayable < 0)
|
|
{
|
|
var clientRatio = 1.0;
|
|
var clientLevelId = DataCacheProvider.GetClientDataSource().GetData(varietyInfo.ClientId).LevelId;
|
|
if (clientLevelId > 0)
|
|
{
|
|
clientRatio = DataCacheProvider.GetClientLevelDataSource().GetData(clientLevelId ?? 0).Ratio ?? 1.0;
|
|
}
|
|
varietyInfo.WorstCastClientPayable *= clientRatio;
|
|
}
|
|
else
|
|
{
|
|
varietyInfo.WorstCastClientPayable = varietyInfo.TwoSideMargin = 0;
|
|
}
|
|
}
|
|
|
|
var clientSpan = new ClientSpan
|
|
{
|
|
ClientId = clientGroup.Key,
|
|
ValueDate = req.settleDate,
|
|
Spv1 = varietyGroup.Sum(g => g.Spv1),
|
|
Spv2 = varietyGroup.Sum(g => g.Spv2),
|
|
Spv3 = varietyGroup.Sum(g => g.Spv3),
|
|
Spv4 = varietyGroup.Sum(g => g.Spv4),
|
|
Spv5 = varietyGroup.Sum(g => g.Spv5),
|
|
Spv6 = varietyGroup.Sum(g => g.Spv6),
|
|
Spv7 = varietyGroup.Sum(g => g.Spv7),
|
|
DeltaMargin = varietyGroup.Sum(g => g.DeltaMargin),
|
|
//负数代表客户应缴预付金,正数代表客户应收预付金
|
|
WorstCastClientPayable = varietyGroup.Sum(g => g.WorstCastClientPayable),
|
|
MySideMargin = varietyGroup.Sum(g => g.WorstCastClientPayable),
|
|
TwoSideMargin = varietyGroup.Sum(g => g.TwoSideMargin),
|
|
OptId = req.userId,
|
|
OptName = req.userName,
|
|
OptDate = DateTime.Now,
|
|
SpanType = req.SpanType,
|
|
AdditionalWorstCastClientPayable = req.clientAdditionalMarginDic != null && req.clientAdditionalMarginDic.TryGetValue(clientGroup.Key, out var dd) ? dd : 0
|
|
};
|
|
//单笔预付金算法的交易不参与品种轧差;
|
|
var singleMarginTrade = clientGroup.Where(O => O.trade.MarginType != DBModels.Enums.MarginTypeEnum.DEFAULT).Select(O => O.tradeSpan);
|
|
clientSpan.Spv1 += singleMarginTrade.Sum(O => O.Spv1 * (-1));
|
|
clientSpan.Spv2 += singleMarginTrade.Sum(O => O.Spv2 * (-1));
|
|
clientSpan.Spv3 += singleMarginTrade.Sum(O => O.Spv3 * (-1));
|
|
clientSpan.Spv4 += singleMarginTrade.Sum(O => O.Spv4 * (-1));
|
|
clientSpan.Spv5 += singleMarginTrade.Sum(O => O.Spv5 * (-1));
|
|
clientSpan.Spv6 += singleMarginTrade.Sum(O => O.Spv6 * (-1));
|
|
clientSpan.Spv7 += singleMarginTrade.Sum(O => O.Spv7 * (-1));
|
|
clientSpan.DeltaMargin += singleMarginTrade.Sum(O => O.DeltaMargin * (-1));
|
|
clientSpan.WorstCastClientPayable += singleMarginTrade.Sum(O => O.WorstCastClientPayable * (-1));
|
|
clientSpan.MySideMargin += singleMarginTrade.Sum(O => O.WorstCastClientPayable * (-1));
|
|
clientSpan.TwoSideMargin += singleMarginTrade.Sum(O => O.TwoSideMargin);
|
|
clientSpan.MySideMargin = Math.Min(clientSpan.MySideMargin ?? 0, 0);
|
|
|
|
//交易员不支付预付金
|
|
clientSpan.WorstCastClientPayable = Math.Min(clientSpan.WorstCastClientPayable ?? 0, 0);
|
|
|
|
//手动维护的预付金,不参与以上算法
|
|
var maMargin = clientGroup.Where(O => O.tradeSpan.VarietyId == null).Sum(t => t.tradeSpan.WorstCastClientPayable * (-1));
|
|
clientSpan.WorstCastClientPayable += maMargin;
|
|
clientSpan.MySideMargin += maMargin;
|
|
|
|
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;
|
|
}
|
|
|
|
public override double GetTradeMargin(GetTradeMarginReq req)
|
|
{
|
|
using var db = new YLContext();
|
|
if (req.trade.TradeType == "结构化交易")
|
|
{
|
|
req.trade.SubTrades = db.trade.Where(x => x.ParentTradeId == req.realTradeId).ToList();
|
|
}
|
|
|
|
var marginReq = req.GetRunMarginCalculationReq();
|
|
if (req.trade.IsGroup == 1)
|
|
{
|
|
marginReq.tradeList = db.trade.Where(x => x.ParentTradeId == req.realTradeId).ToList();
|
|
}
|
|
if (req.trade.ParentTradeId > 0)
|
|
{
|
|
marginReq.tradeList.AddRange(db.trade.Where(x => x.ParentTradeId == req.trade.ParentTradeId && x.id != req.realTradeId).ToList());
|
|
}
|
|
var tradeMargin = RunMarginCalculation(marginReq);
|
|
if (null != tradeMargin)
|
|
{
|
|
|
|
double margin;
|
|
if (req.trade.IsGroup == 1)
|
|
{
|
|
margin = (double)tradeMargin.Sum(x => x.WorstCastClientPayable ?? 0);
|
|
}
|
|
else if (req.trade.ParentTradeId > 0)
|
|
{
|
|
PriceCalcService.GroupSpansCalc(tradeMargin);
|
|
margin = (double)(tradeMargin.FirstOrDefault(O => O.TradeId == req.trade.id)?.WorstCastClientPayable ?? 0);
|
|
}
|
|
else
|
|
{
|
|
margin = (double)(tradeMargin.FirstOrDefault()?.WorstCastClientPayable ?? 0);
|
|
}
|
|
|
|
return margin;
|
|
}
|
|
return 0.0;
|
|
}
|
|
}
|
|
}
|