423 lines
21 KiB
C#
423 lines
21 KiB
C#
using Qdp.Pricing.Base.Implementations;
|
|
using YLErp.Abstract.DataProviders;
|
|
using YLErp.BLL.Calculation;
|
|
using YLErp.Enums;
|
|
using YLErp.Helpers;
|
|
using YLErp.Modules.CalculationModule;
|
|
using YLErp.Modules.DataProviderModule;
|
|
using YLErp.Modules.MarginModule;
|
|
|
|
namespace YLErp.BLL.MarginCalculation
|
|
{
|
|
/// <summary>
|
|
/// 国泰君安预付金计算
|
|
/// </summary>
|
|
public class XMXYMarginCalculation : MarginCalculationBase
|
|
{
|
|
// 定义一个静态变量来保存类的实例
|
|
public static readonly XMXYMarginCalculation Instance;
|
|
|
|
static XMXYMarginCalculation()
|
|
{
|
|
Instance = new XMXYMarginCalculation();
|
|
}
|
|
|
|
// 定义私有构造函数,使外界不能创建该类实例
|
|
protected XMXYMarginCalculation()
|
|
{
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
using (var db = new YLContext())
|
|
{
|
|
var tradeIds = req.tradeList.Select(x => x.id);
|
|
var tradeMarginTemplates = db.trade_margin_template.Where(x => tradeIds.Contains(x.TradeId)).ToList();
|
|
tradeSpans.ForEach(x =>
|
|
{
|
|
var trade = req.tradeList.FirstOrDefault(y => y.id == x.TradeId);
|
|
var tradeMarginTemplate = tradeMarginTemplates.Where(y => y.TradeId == x.TradeId && x.ValueDate <= req.settleDate).OrderByDescending(y => y.ValueDate).FirstOrDefault();
|
|
if (tradeMarginTemplate != null)
|
|
{
|
|
var marginTemplate = db.margin_template_v2.Find(tradeMarginTemplate.MarginTemplateId);
|
|
if (marginTemplate != null)
|
|
{
|
|
switch (marginTemplate.RuleType)
|
|
{
|
|
case (int)MarginRuleTypeEnum.无预付金:
|
|
CalcAsNo(x);
|
|
break;
|
|
case (int)MarginRuleTypeEnum.交易所基本规则:
|
|
var PriceProvider = new EodPriceProvider(req.settleDate).GetPriceProvider(SettlementTypeEnum.ClosePrice);
|
|
CalcAsExchangeBasicRule(x, trade, req, PriceProvider);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
return tradeSpans;
|
|
}
|
|
|
|
private void CalcAsNo(trade_span span)
|
|
{
|
|
span.Spv1 = 0;
|
|
span.Spv2 = 0;
|
|
span.Spv3 = 0;
|
|
span.Spv4 = 0;
|
|
span.WorstCastClientPayable = 0;
|
|
span.MaxlossMargin = 0;
|
|
span.IsSingleMargin = true;
|
|
}
|
|
|
|
private void CalcAsExchangeBasicRule(trade_span span, trade trade, RunMarginCalculationReq req, IEodPriceProviderWrap priceProvider)
|
|
{
|
|
if (trade.BuySell == "买入")
|
|
{
|
|
var helper = new RunMarginCalculationHelper(req, _underlyingDataProvider);
|
|
var mpProvider = helper.GetMarginParamProvider(MarginParamTypeEnum.MarginRate | MarginParamTypeEnum.UpDownLimit);
|
|
mpProvider.TryGetMarginRate(trade.UnderlyingCode, out var marginRate);
|
|
|
|
var price = req.PriceProvider.GetPrice(trade.UnderlyingCode);
|
|
if (req.CalcMarginType == CalcMarginTypeEnum.EodMargin)
|
|
{
|
|
price = new EodPriceProvider(req.settleDate).GetPrice(trade.UnderlyingCode, SettlementTypeEnum.ClosePrice);
|
|
}
|
|
|
|
double RoundedPv = 0;
|
|
|
|
var reqConv = new CalculateRisksForTradesReq
|
|
{
|
|
valueDate = req.settleDate,
|
|
tradeList = new List<trade> { trade },
|
|
priceProvider = priceProvider,//req.PriceProvider,
|
|
pricingRequest = PricingRequest.Pv | PricingRequest.Delta | PricingRequest.Gamma,
|
|
addVolRateDic = null,
|
|
volType = req.volType,
|
|
isUseTradeVol = PS.Config.IsTradeVol,
|
|
PreciseTimeMode = req.CalcMarginType != Enums.CalcMarginTypeEnum.EodMargin,
|
|
isAddVolPercent = true,
|
|
overrideVolsForTrade = null,
|
|
isMarginCalc = true
|
|
};
|
|
|
|
if (req.CalcMarginType == Enums.CalcMarginTypeEnum.EodMargin)
|
|
{
|
|
reqConv.calcScenario = Enums.CalcScenarioEnum.EodSettlement;
|
|
}
|
|
|
|
var tradeRiskResult = CalculatorHelper.CalculateRisksForTrades(reqConv);
|
|
|
|
RoundedPv = tradeRiskResult.Results.Where(x => x.Trade.id == trade.id).FirstOrDefault().ValueResult.Pv;
|
|
|
|
var margin1 = RoundedPv + 0.5 * price * trade.TradeAmount * marginRate;
|
|
var margin2 = RoundedPv + price * trade.TradeAmount * marginRate - (trade.OptionType == "看涨" ? Math.Max((trade.ActualStrike ?? 0) - price, 0) : Math.Max(price - (trade.ActualStrike ?? 0), 0)) * trade.Notional / 2;
|
|
|
|
//var margin1 = price * trade.Notional + 0.5 * trade.StockEqvNotional * marginRate;
|
|
//var margin2 = price * trade.Notional + trade.StockEqvNotional * marginRate - (trade.OptionType == "看涨" ? Math.Max((trade.ActualStrike ?? 0) - price, 0) : Math.Max(price - (trade.ActualStrike ?? 0), 0)) * trade.Notional / 2;
|
|
var value = Math.Max(margin1, margin2);
|
|
span.Spv1 = value;
|
|
span.Spv2 = value;
|
|
span.Spv3 = value;
|
|
span.Spv4 = value;
|
|
span.WorstCastClientPayable = value;
|
|
span.MaxlossMargin = value;
|
|
span.IsSingleMargin = true;
|
|
}
|
|
}
|
|
|
|
//商品类预付金计算
|
|
private static List<trade_span> RunMarginCalculationForCommodity(RunMarginCalculationHelper helper)
|
|
{
|
|
var resultMap = new Dictionary<int, trade_span>();
|
|
|
|
//为了算客户角度的一个预付金数值
|
|
helper.ReverseTradeSide();
|
|
|
|
helper.SetFieldsByTradeType();
|
|
|
|
helper.GetUpDownLimitPrices(out var upLimitPrices, out var downLimitPrices);
|
|
|
|
helper.GetTradVolRateDic(out var tradeVolRateDic);
|
|
|
|
var vols = new[] { null, tradeVolRateDic };
|
|
var prices = new (string, IPriceProvider)[] { ("up", upLimitPrices), ("down", downLimitPrices) };
|
|
var loops = prices.SelectMany(n => vols.Select(m => new
|
|
{
|
|
pricekey = n.Item1,
|
|
priceProvider = n.Item2,
|
|
addVolRateDic = m
|
|
})).ToArray();
|
|
|
|
foreach (var loop in loops)
|
|
{
|
|
var calcReq = helper.GetCalculateRisksForTradesReq(priceProvider: loop.priceProvider, addVolRateDic: loop.addVolRateDic, overrideVols: null);
|
|
|
|
var tradeRiskResult = CalculatorHelper.CalculateRisksForTrades(calcReq);
|
|
|
|
if (tradeRiskResult.Results == null || tradeRiskResult.Results.Count < 1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var key = $"{loop.pricekey}_{(loop.addVolRateDic == null ? 0 : 1)}";
|
|
|
|
foreach (var item in tradeRiskResult.Results)
|
|
{
|
|
var pv = item.ValueResult.Pv;
|
|
|
|
if (!helper.GetSpecialMargin(item.Trade, pv, out var value, helper.req.forOtherSide))
|
|
{
|
|
var clientRatio = helper.GetClient(item.Trade)?.Ratio ?? 1.0;
|
|
|
|
value = double.IsNaN(pv) ? 0 : pv * clientRatio;
|
|
}
|
|
|
|
var contains = resultMap.TryGetValue(item.Trade.id, out var tempTradeSpan);
|
|
|
|
if (!contains)
|
|
{
|
|
resultMap[item.Trade.id] = tempTradeSpan = helper.CreateTradeSpan(item.Trade);
|
|
}
|
|
|
|
switch (key)
|
|
{
|
|
case "up_0":
|
|
tempTradeSpan.Spv1 = value; break;
|
|
case "up_1":
|
|
tempTradeSpan.Spv2 = value; break;
|
|
case "down_0":
|
|
tempTradeSpan.Spv3 = value; break;
|
|
case "down_1":
|
|
tempTradeSpan.Spv4 = value; break;
|
|
}
|
|
|
|
if (contains)
|
|
{
|
|
tempTradeSpan.SetWorstCastClientPayable();
|
|
tempTradeSpan.MaxlossMargin = tempTradeSpan.WorstCastClientPayable;
|
|
}
|
|
}
|
|
}
|
|
|
|
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();
|
|
|
|
if (req.tradeSpans != null && req.tradeSpans.Count > 0)
|
|
{
|
|
var tradeSpanInfoOtherSide = (from tradeSpan in req.tradeSpansOtherSide
|
|
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)
|
|
{
|
|
var underlyingGroup = clientGroup.Where(O => O.trade.MarginType == DBModels.Enums.MarginTypeEnum.DEFAULT && O.tradeSpan.IsSingleMargin != true).GroupBy(t => t.trade.UnderlyingId).Select(t => new ClientSpan
|
|
{
|
|
UnderlyingId = 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),
|
|
OptId = req.userId,
|
|
OptName = req.userName,
|
|
OptDate = DateTime.Now,
|
|
SpanType = req.SpanType
|
|
}).ToList();
|
|
foreach (var item in underlyingGroup)
|
|
{
|
|
item.WorstCastClientPayable = Math.Min(Math.Min(Math.Min(item.Spv1 ?? 0, item.Spv2 ?? 0), item.Spv3 ?? 0), item.Spv4 ?? 0);
|
|
|
|
item.TwoSideMargin = Math.Min(Math.Min(Math.Min(item.Spv1 ?? 0, item.Spv2 ?? 0), item.Spv3 ?? 0), item.Spv4 ?? 0);
|
|
|
|
#region 更新tradeSpan,使得每笔交易的持仓预付金和客户预付金计算用的Spv组保持一致
|
|
|
|
var tradeIdList = clientGroup.Select(x => x.tradeSpan.TradeId);
|
|
var tradeSpansUpdate = db.trade_span.Where(x => tradeIdList.Contains(x.TradeId) && x.ClientId == item.ClientId && x.UnderlyingId == item.UnderlyingId && x.ValueDate == req.settleDate).ToList();
|
|
var tradeSpansReq = req.tradeSpans.Where(x => tradeIdList.Contains(x.TradeId) && x.ClientId == item.ClientId && x.UnderlyingId == item.UnderlyingId && x.ValueDate == req.settleDate).ToList();
|
|
|
|
if (item.WorstCastClientPayable == item.Spv1)
|
|
{
|
|
tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv1);
|
|
tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv1);
|
|
}
|
|
else if (item.WorstCastClientPayable == item.Spv2)
|
|
{
|
|
tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv2);
|
|
tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv2);
|
|
}
|
|
else if (item.WorstCastClientPayable == item.Spv3)
|
|
{
|
|
tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv3);
|
|
tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv3);
|
|
}
|
|
else if (item.WorstCastClientPayable == item.Spv4)
|
|
{
|
|
tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.Spv4);
|
|
tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.Spv4);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
var clientSpan = new ClientSpan
|
|
{
|
|
ClientId = clientGroup.Key,
|
|
ValueDate = req.settleDate,
|
|
Spv1 = underlyingGroup.Sum(g => g.Spv1),
|
|
Spv2 = underlyingGroup.Sum(g => g.Spv2),
|
|
Spv3 = underlyingGroup.Sum(g => g.Spv3),
|
|
Spv4 = underlyingGroup.Sum(g => g.Spv4),
|
|
//负数代表客户应缴预付金,正数代表客户应收预付金
|
|
WorstCastClientPayable = underlyingGroup.Sum(g => g.WorstCastClientPayable),
|
|
MySideMargin = underlyingGroup.Sum(g => g.WorstCastClientPayable),
|
|
TwoSideMargin = underlyingGroup.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 || O.tradeSpan.IsSingleMargin == true).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.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);
|
|
|
|
clientSpanNews.Add(clientSpan);
|
|
}
|
|
|
|
//处理从客户角度的预付金计算(将交易买卖方向反向处理)
|
|
var clientGroupsOtherSide = tradeSpanInfoOtherSide.GroupBy(t => t.trade.ClientId);
|
|
foreach (var clientGroup in clientGroupsOtherSide)
|
|
{
|
|
var underlyingGroup = clientGroup.Where(O => O.trade.MarginType == DBModels.Enums.MarginTypeEnum.DEFAULT).GroupBy(t => t.trade.UnderlyingId).Select(t => new ClientSpan
|
|
{
|
|
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),
|
|
OptId = req.userId,
|
|
OptName = req.userName,
|
|
OptDate = DateTime.Now,
|
|
SpanType = req.SpanType
|
|
}).ToList();
|
|
foreach (var item in underlyingGroup)
|
|
{
|
|
item.WorstCastClientPayable = new[] { item.Spv1 ?? 0, item.Spv2 ?? 0, item.Spv3 ?? 0, item.Spv4 ?? 0 }.Min();
|
|
}
|
|
var clientSpan = clientSpanNews.FirstOrDefault(x => x.ClientId == clientGroup.Key && x.ValueDate == req.settleDate);
|
|
clientSpan.OtherSideMargin = underlyingGroup.Sum(g => g.WorstCastClientPayable);
|
|
|
|
var singleMarginTrade = clientGroup.Where(O => O.trade.MarginType != DBModels.Enums.MarginTypeEnum.DEFAULT).Select(O => O.tradeSpan);
|
|
clientSpan.OtherSideMargin += singleMarginTrade.Sum(O => O.WorstCastClientPayable * (-1));
|
|
clientSpan.OtherSideMargin = Math.Min(clientSpan.OtherSideMargin ?? 0, 0);
|
|
|
|
if (HasTwoSideMargin(clientGroup.Key))
|
|
{
|
|
clientSpan.WorstCastClientPayable = clientSpan.MySideMargin - clientSpan.OtherSideMargin;
|
|
}
|
|
}
|
|
}
|
|
|
|
//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.trade.id).ToList();
|
|
}
|
|
|
|
var marginReq = req.GetRunMarginCalculationReq();
|
|
if (req.trade.IsGroup == 1)
|
|
{
|
|
marginReq.tradeList = db.trade.Where(x => x.ParentTradeId == req.trade.id).ToList();
|
|
}
|
|
var tradeMargin = RunMarginCalculation(marginReq);
|
|
if (null != tradeMargin)
|
|
{
|
|
var margin = req.trade.IsGroup == 1 ? tradeMargin.Sum(x => x.WorstCastClientPayable ?? 0) : (tradeMargin.FirstOrDefault()?.WorstCastClientPayable ?? 0);
|
|
return margin;
|
|
}
|
|
return 0.0;
|
|
}
|
|
}
|
|
}
|
|
}
|