1370 lines
103 KiB
C#
1370 lines
103 KiB
C#
using Qdp.Foundation.Implementations;
|
|
using Qdp.Pricing.Library.Options.Products.Autocall.Phoenix;
|
|
using Qdp.Pricing.Library.Options.Products.Autocall.Snowball;
|
|
using YLErp.Abstract.DataProviders;
|
|
using YLErp.BLL.Calculation;
|
|
using YLErp.BLL.Calculation.V2;
|
|
using YLErp.DBModels.Helpers;
|
|
using YLErp.Enums;
|
|
using YLErp.Helpers;
|
|
using YLErp.Models;
|
|
using YLErp.Modules;
|
|
using YLErp.Modules.CalculationModule;
|
|
using YLErp.Modules.DataProviderModule;
|
|
using YLErp.Modules.MarginModule;
|
|
using YLErp.Modules.VolatilityModule;
|
|
using YLErp.QdpModule;
|
|
|
|
namespace YLErp.BLL.MarginCalculation
|
|
{
|
|
class ChangJiangMarginCalculation : MarginCalculationBase
|
|
{
|
|
// 定义一个静态变量来保存类的实例
|
|
public static readonly ChangJiangMarginCalculation Instance;
|
|
|
|
static ChangJiangMarginCalculation()
|
|
{
|
|
Instance = new ChangJiangMarginCalculation();
|
|
}
|
|
|
|
// 定义私有构造函数,使外界不能创建该类实例
|
|
protected ChangJiangMarginCalculation()
|
|
{
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
return RunMarginCalculationForCommodity(helper);
|
|
}
|
|
|
|
//商品类预付金计算
|
|
private static List<trade_span> RunMarginCalculationForCommodity(RunMarginCalculationHelper helper)
|
|
{
|
|
var mpProvider = helper.GetMarginParamProvider(MarginParamTypeEnum.MarginRate | MarginParamTypeEnum.UpDownLimit);
|
|
var resultMap = new Dictionary<int, trade_span>();
|
|
|
|
//为了算客户角度的一个预付金数值
|
|
helper.ReverseTradeSide();
|
|
|
|
helper.SetFieldsByTradeType();
|
|
|
|
helper.GetUpDownLimitPrices(out var upLimitPrices, out var downLimitPrices);
|
|
|
|
helper.GetTradVolRateDic(out var tradeVolRateDic);
|
|
|
|
helper.GetMarginTradVolRateDic(out var tradeMarginVolRateDic);
|
|
|
|
var vols = new[] { null, tradeVolRateDic };
|
|
var prices = new (string key, IPriceProvider priceProvider)[] { ("up", upLimitPrices), ("down", downLimitPrices), ("normal", helper.req.PriceProvider) };
|
|
var loops = prices.SelectMany(n => vols.Select(m => new
|
|
{
|
|
pricekey = n.key,
|
|
priceProvider = n.priceProvider,
|
|
addVolRateDic = m
|
|
})).ToArray();
|
|
|
|
foreach (var loop in loops)
|
|
{
|
|
var calcReq = helper.GetCalculateRisksForTradesReq(priceProvider: loop.priceProvider, addVolRateDic: loop.addVolRateDic, overrideVols: tradeMarginVolRateDic);
|
|
|
|
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 "normal_0":
|
|
tempTradeSpan.Spv = pv;
|
|
tempTradeSpan.Delta = item.ValueResult.Delta;
|
|
tempTradeSpan.UnderlyingPrice = helper.req.PriceProvider.GetPrice(item.ValueResult.UnderlyingCode);
|
|
mpProvider.TryGetMarginRate(item.ValueResult.UnderlyingCode, out var marginRate);
|
|
tempTradeSpan.DeltaMargin = (Math.Abs(tempTradeSpan.Delta ?? 0) * (item.Trade.BuySell == "买入" ? 1 : -1) * tempTradeSpan.UnderlyingPrice * marginRate) ?? 0;
|
|
break;
|
|
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.IsSpanMargin = true;
|
|
}
|
|
}
|
|
|
|
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 tradeTemplates = db.trade_margin_template.AsNoTracking().Where(t => tradeIds.Contains(t.TradeId) && t.ValueDate <= req.settleDate);
|
|
var groupQuery = from tt in tradeTemplates
|
|
group tt by tt.TradeId into tts
|
|
select new
|
|
{
|
|
TradeId = tts.Key,
|
|
ValueDate = tts.Max(n => n.ValueDate)
|
|
};
|
|
var groupTemplates = from gq in groupQuery
|
|
join tt in tradeTemplates
|
|
on new { gq.TradeId, gq.ValueDate } equals new { tt.TradeId, tt.ValueDate }
|
|
select tt;
|
|
|
|
var tradeSpanInfo = (from tradeSpan in req.tradeSpans
|
|
join trade in tradeList on tradeSpan.TradeId equals trade.id
|
|
join tradetemplate in groupTemplates on tradeSpan.TradeId equals tradetemplate.TradeId into templates
|
|
from tradetemplate in templates.DefaultIfEmpty()
|
|
where tradeSpan.ValueDate == req.settleDate
|
|
select new { trade, tradeSpan, tradetemplate }).ToList();
|
|
|
|
//商品类预付金计算
|
|
if (!PS.Config.ErpElement.IsStockMargin)
|
|
{
|
|
if (req.tradeSpans != null && req.tradeSpans.Count > 0)
|
|
{
|
|
var spans = req.tradeSpansOtherSide != null
|
|
? req.tradeSpansOtherSide.Where(n => n != null).ToArray() : Enumerable.Empty<trade_span>();
|
|
var tradeSpanInfoOtherSide = (from tradeSpan in spans
|
|
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 clientTradeSpans = clientGroup.ToList();
|
|
//交易员方向持仓盈亏
|
|
var positionWinLoss = clientTradeSpans.Sum(x => (x.tradeSpan.Spv ?? 0) + (x.trade.TradePrice * x.trade.Notional / x.trade.OriginalNotional * ((x.trade.BuySell == "卖出") ? 1 : -1) ?? 0));
|
|
|
|
var underlyingGroupDefault = clientGroup.Where(O => O.trade.MarginType == DBModels.Enums.MarginTypeEnum.DEFAULT).AsEnumerable().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),
|
|
DeltaMargin = t.Sum(g => g.tradeSpan.DeltaMargin) * (-1),
|
|
OptId = req.userId,
|
|
OptName = req.userName,
|
|
OptDate = DateTime.Now,
|
|
SpanType = req.SpanType
|
|
}).ToList();
|
|
foreach (var item in underlyingGroupDefault)
|
|
{
|
|
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 underlyingGroup = clientGroup.AsEnumerable().GroupBy(t => t.trade.UnderlyingId).Select(t => new ClientSpan
|
|
{
|
|
UnderlyingId = t.Key,
|
|
ClientId = clientGroup.Key,
|
|
ValueDate = req.settleDate,
|
|
DeltaMargin = t.Sum(g => g.tradeSpan.DeltaMargin) * (-1)
|
|
}).ToList();
|
|
|
|
foreach (var item in underlyingGroup)
|
|
{
|
|
if (!HasTwoSideMargin(clientGroup.Key))
|
|
{
|
|
item.DeltaMargin = Math.Min(item.DeltaMargin ?? 0, 0);
|
|
}
|
|
}
|
|
|
|
//客户方向delta预付金
|
|
var deltaMargin = underlyingGroup.Sum(g => (g.DeltaMargin ?? 0)) - positionWinLoss;
|
|
if (!HasTwoSideMargin(clientGroup.Key))
|
|
{
|
|
deltaMargin = Math.Min(deltaMargin, 0);
|
|
}
|
|
|
|
var clientSpan = new ClientSpan
|
|
{
|
|
ClientId = clientGroup.Key,
|
|
ValueDate = req.settleDate,
|
|
Spv1 = underlyingGroupDefault.Sum(g => g.Spv1),
|
|
Spv2 = underlyingGroupDefault.Sum(g => g.Spv2),
|
|
Spv3 = underlyingGroupDefault.Sum(g => g.Spv3),
|
|
Spv4 = underlyingGroupDefault.Sum(g => g.Spv4),
|
|
//负数代表客户应缴预付金,正数代表客户应收预付金
|
|
DeltaMargin = deltaMargin,
|
|
//负数代表客户应缴预付金,正数代表客户应收预付金
|
|
WorstCastClientPayable = underlyingGroupDefault.Sum(g => g.WorstCastClientPayable),
|
|
MySideMargin = underlyingGroupDefault.Sum(g => g.WorstCastClientPayable),
|
|
TwoSideMargin = underlyingGroupDefault.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.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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//权益类预付金计算
|
|
else
|
|
{
|
|
var defaultMarginTemplates = db.margin_template_v2.Where(x => x.IsDefault && !x.IsForClient).ToList();
|
|
var groupMarginTemplates = db.margin_template_v2.Where(x => !x.IsDefault && x.MarginType == (int)MarginTypeV2Enum.多腿).ToList();
|
|
|
|
var clientGroups = tradeSpanInfo.GroupBy(t => t.trade.ClientId);
|
|
foreach (var clientGroup in clientGroups)
|
|
{
|
|
var clientTradeSpans = clientGroup.ToList();
|
|
//交易员方向持仓盈亏
|
|
var positionWinLoss = clientTradeSpans.Sum(x => (x.tradeSpan.Spv ?? 0) + (x.trade.TradePrice * x.trade.Notional / x.trade.OriginalNotional * ((x.trade.BuySell == "卖出") ? 1 : -1) ?? 0));
|
|
|
|
var clientGroupICIHIF = clientGroup.Where(x => x.tradeSpan.IsSpanMargin == true);
|
|
var clientGroupOther = clientGroup.Where(x => x.tradeSpan.IsSpanMargin != true);
|
|
|
|
//交易员方向,正数代表交易员收预付金
|
|
var worstCastClientPayable = 0.0;
|
|
var groupTradeIds = new List<int>();
|
|
|
|
#region 默认组合的交易类型的交易处理
|
|
|
|
//客户级别默认预付金规则
|
|
var clientMarginTemplates = (from cmt in db.client_margin_template.Where(x => x.ClientId == clientGroup.Key && x.ValueDate <= req.settleDate)
|
|
join mtv in db.margin_template_v2
|
|
on cmt.MarginTemplateId equals mtv.id
|
|
select new { cmt, mtv }).ToList();
|
|
|
|
var defaultTradeTypesDone = new List<string>();
|
|
if (clientMarginTemplates.Any())
|
|
{
|
|
var valueDate = clientMarginTemplates.Max(x => x.cmt.ValueDate);
|
|
clientMarginTemplates = clientMarginTemplates.Where(x => x.cmt.ValueDate == valueDate).ToList();
|
|
clientMarginTemplates.ForEach(x =>
|
|
{
|
|
var marginDetail = db.margin_template_detail.Where(y => y.MarginTemplateId == x.mtv.id && y.ValueDate <= req.settleDate).OrderByDescending(y => y.ValueDate).FirstOrDefault();
|
|
if (marginDetail == null && (x.mtv.RuleType != (int)MarginRuleTypeEnum.无预付金 && x.mtv.RuleType != (int)MarginRuleTypeEnum.自动赎回买入预付金规则 && x.mtv.RuleType != (int)MarginRuleTypeEnum.含赔付预付金计算规则 && x.mtv.RuleType != (int)MarginRuleTypeEnum.保底预付金规则))
|
|
{
|
|
throw new Exception(string.Format("[{0}]不在{1}有效期内", req.settleDate.ToString("yyyy-MM-dd"), x.mtv.Name));
|
|
}
|
|
|
|
if (x.mtv.RuleType == (int)MarginRuleTypeEnum.类香草预付金规则)
|
|
{
|
|
var groupRatio = marginDetail.GroupRatio ?? 0;
|
|
var stockEqvNotional = marginDetail.StockEqvNotional ?? 0;
|
|
var singleStockEqvNotional = marginDetail.SingleStockEqvNotional ?? 0;
|
|
var amount = marginDetail.PositionUnderlyingAmount ?? 0;
|
|
|
|
//香草期权只考虑交易员买入的交易,卖出的交易不需要考虑预付金
|
|
var tradeVanillaSpanInfo = clientGroupOther.Where(y => x.mtv.TradeTypes.Contains(y.trade.TradeType) && y.trade.BuySell == "买入" && y.tradetemplate == null && y.tradeSpan.IsSingleMargin != true);
|
|
//自定义预付金
|
|
var tradeVanillaSpanInfoSingle = clientGroupOther.Where(y => x.mtv.TradeTypes.Contains(y.trade.TradeType) && y.trade.BuySell == "买入" && y.tradetemplate == null && y.tradeSpan.IsSingleMargin == true);
|
|
var umdStockEqvNotional = tradeVanillaSpanInfo.GroupBy(y => y.trade.UnderlyingId).Select(t => new
|
|
{
|
|
UnderlyingId = t.Key,
|
|
StockEqvNotional = t.Sum(g => TradeHelper.GetStockEqvNotionalReal(g.trade.StockEqvNotional, g.trade.ParticipationRate, g.trade.AnnualizeFactor)),
|
|
PositionStockEqvNotional = t.Sum(g => TradeHelper.GetStockEqvNotionalReal(g.trade.StockEqvNotional, g.trade.ParticipationRate, g.trade.AnnualizeFactor) * g.tradeSpan.UnderlyingPrice / g.trade.SpotPrice)
|
|
}).ToList();
|
|
var underlyingIds = umdStockEqvNotional.Select(y => y.UnderlyingId);
|
|
var totalStockEqvNotional = umdStockEqvNotional.Sum(y => y.PositionStockEqvNotional);
|
|
if (totalStockEqvNotional >= stockEqvNotional && umdStockEqvNotional.Any(y => y.PositionStockEqvNotional >= singleStockEqvNotional) && underlyingIds.Count() >= amount)
|
|
{
|
|
//获取组合持仓名义本金和初始名义本金的较高者
|
|
var maxStockEqvNotional = Math.Max(umdStockEqvNotional.Sum(y => y.StockEqvNotional >= singleStockEqvNotional ? singleStockEqvNotional : y.StockEqvNotional), umdStockEqvNotional.Sum(y => y.PositionStockEqvNotional >= singleStockEqvNotional ? singleStockEqvNotional : y.PositionStockEqvNotional ?? 0));
|
|
|
|
var groupMargins = maxStockEqvNotional * groupRatio + tradeVanillaSpanInfo.Sum(y => y.tradeSpan.PositionWin ?? 0);
|
|
worstCastClientPayable += groupMargins;
|
|
|
|
#region 更新tradeSpan,使得每笔交易的持仓预付金和组合预付金保持一致
|
|
|
|
var tradeIdList = tradeVanillaSpanInfo.Select(y => y.tradeSpan.TradeId);
|
|
var sumStockEqvNotional = tradeVanillaSpanInfo.Sum(y => y.trade.StockEqvNotional);
|
|
var tradeSpansUpdate = db.trade_span.Where(y => tradeIdList.Contains(y.TradeId) && y.ValueDate == req.settleDate).ToList();
|
|
var tradeSpansReq = req.tradeSpans.Where(y => tradeIdList.Contains(y.TradeId) && y.ValueDate == req.settleDate).ToList();
|
|
|
|
tradeSpansUpdate.ForEach(y =>
|
|
{
|
|
y.WorstCastClientPayable = groupMargins * tradeVanillaSpanInfo.FirstOrDefault(z => z.trade.id == y.TradeId).trade.StockEqvNotional / sumStockEqvNotional;
|
|
});
|
|
tradeSpansReq.ForEach(y =>
|
|
{
|
|
y.WorstCastClientPayable = groupMargins * tradeVanillaSpanInfo.FirstOrDefault(z => z.trade.id == y.TradeId).trade.StockEqvNotional / sumStockEqvNotional;
|
|
});
|
|
|
|
#endregion
|
|
}
|
|
else
|
|
{
|
|
worstCastClientPayable += tradeVanillaSpanInfo.Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
|
}
|
|
|
|
//自定义预付金处理的交易
|
|
worstCastClientPayable += tradeVanillaSpanInfoSingle.Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
|
}
|
|
else if (x.mtv.RuleType == (int)MarginRuleTypeEnum.触发条件预付金规则1 && x.mtv.DefaultParameterType == (int)DefaultMarginParameterTypeEnum.个股)
|
|
{
|
|
//排除指数类交易
|
|
var clientGroupWithoutIndex = clientGroupOther.Where(y => !(y.trade.UnderlyingCode.StartsWith("000") && y.trade.UnderlyingCode.EndsWith(".SH") || y.trade.UnderlyingCode.StartsWith("39") || y.trade.UnderlyingCode.StartsWith("IF") || y.trade.UnderlyingCode.StartsWith("IH") || y.trade.UnderlyingCode.StartsWith("IC")) && y.tradetemplate == null);
|
|
|
|
//初始预付金率
|
|
var redeemRatio = marginDetail.MarginRatio1 ?? 0;
|
|
//持仓预付金率
|
|
var redeemRatio1 = marginDetail.MarginRatio2 ?? 0;
|
|
var redeemPriceLimit = marginDetail.RedeemPriceLimit ?? 0;
|
|
var redeemPriceMax = marginDetail.RedeemPriceMax ?? 0;
|
|
var redeemAmount = marginDetail.PositionUnderlyingAmount ?? 0;
|
|
var tradeSpanInfoGroupSingle = clientGroupWithoutIndex.Where(y => x.mtv.TradeTypes.Contains(y.trade.TradeType) && (y.trade.BuySell == "卖出" && y.tradeSpan.IsSingleMargin == true || y.trade.BuySell == "买入"));
|
|
var tradeSpanInfoGroup = clientGroupWithoutIndex.Where(y => x.mtv.TradeTypes.Contains(y.trade.TradeType) && y.trade.BuySell == "卖出" && y.tradeSpan.IsSingleMargin != true);
|
|
if (tradeSpanInfoGroup.Select(y => y.trade.UnderlyingId).Distinct().Count() >= redeemAmount)
|
|
{
|
|
double groupMargins = 0;
|
|
var groupStrikeWithNotional = tradeSpanInfoGroup.Sum(y => (y.trade.ActualStrike * y.trade.Notional) ?? 0);
|
|
//计算单一客户当日标的整体跌跌幅时需要判断单个标的的跌幅是否超过上限
|
|
var groupPriceWithNotional = tradeSpanInfoGroup.Sum(y => (((1 - y.tradeSpan.UnderlyingPrice / y.trade.SpotPrice) > redeemPriceMax ? (y.trade.SpotPrice * (1 - redeemPriceMax)) : y.tradeSpan.UnderlyingPrice) * y.trade.Notional) ?? 0);
|
|
var groupStockEqvNotional = tradeSpanInfoGroup.Sum(y => TradeHelper.GetStockEqvNotionalReal(y.trade.StockEqvNotional, y.trade.ParticipationRate, y.trade.AnnualizeFactor));
|
|
var groupStrikeStockEqvNotional = tradeSpanInfoGroup.Sum(y => TradeHelper.GetStockEqvNotionalReal(y.trade.StockEqvNotional, y.trade.ParticipationRate, y.trade.AnnualizeFactor) * (y.trade.ActualStrike ?? 0) / y.trade.SpotPrice.Value);
|
|
|
|
if (groupPriceWithNotional / groupStrikeWithNotional <= (1 - redeemPriceLimit))
|
|
{
|
|
groupMargins = groupStockEqvNotional * redeemRatio1 + groupStrikeStockEqvNotional * (1 - groupPriceWithNotional / groupStrikeWithNotional);
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
else
|
|
{
|
|
groupMargins = groupStockEqvNotional * redeemRatio;
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
|
|
#region 更新tradeSpan,使得每笔交易的持仓预付金和组合预付金保持一致
|
|
|
|
var tradeIdList = tradeSpanInfoGroup.Select(y => y.tradeSpan.TradeId);
|
|
var sumStockEqvNotional = tradeSpanInfoGroup.Sum(y => y.trade.StockEqvNotional);
|
|
var tradeSpansUpdate = db.trade_span.Where(y => tradeIdList.Contains(y.TradeId) && y.ValueDate == req.settleDate).ToList();
|
|
var tradeSpansReq = req.tradeSpans.Where(y => tradeIdList.Contains(y.TradeId) && y.ValueDate == req.settleDate).ToList();
|
|
|
|
tradeSpansUpdate.ForEach(y =>
|
|
{
|
|
y.WorstCastClientPayable = groupMargins * tradeSpanInfoGroup.FirstOrDefault(z => z.trade.id == y.TradeId).trade.StockEqvNotional / sumStockEqvNotional;
|
|
});
|
|
tradeSpansReq.ForEach(y =>
|
|
{
|
|
y.WorstCastClientPayable = groupMargins * tradeSpanInfoGroup.FirstOrDefault(z => z.trade.id == y.TradeId).trade.StockEqvNotional / sumStockEqvNotional;
|
|
});
|
|
|
|
#endregion
|
|
|
|
//卖出的保本雪球,指数类的凤凰雪球需要单独计算预付金,买入的期权,进行累加
|
|
//以及自定义预付金处理的交易
|
|
worstCastClientPayable += tradeSpanInfoGroupSingle.Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
|
}
|
|
else
|
|
{
|
|
worstCastClientPayable += clientGroupWithoutIndex.Where(y => x.mtv.TradeTypes.Contains(y.trade.TradeType)).Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
|
}
|
|
}
|
|
else if (x.mtv.RuleType == (int)MarginRuleTypeEnum.触发条件预付金规则2 && x.mtv.DefaultParameterType == (int)DefaultMarginParameterTypeEnum.个股)
|
|
{
|
|
//排除指数类交易
|
|
var clientGroupWithoutIndex = clientGroupOther.Where(y => !(y.trade.UnderlyingCode.StartsWith("000") && y.trade.UnderlyingCode.EndsWith(".SH") || y.trade.UnderlyingCode.StartsWith("39") || y.trade.UnderlyingCode.StartsWith("IF") || y.trade.UnderlyingCode.StartsWith("IH") || y.trade.UnderlyingCode.StartsWith("IC")) && y.tradetemplate == null);
|
|
|
|
//初始预付金率
|
|
var redeemRatio = marginDetail.MarginRatio1 ?? 0;
|
|
//持仓预付金率
|
|
var redeemRatio1 = marginDetail.MarginRatio2 ?? 0;
|
|
var redeemPriceLimit = marginDetail.RedeemPriceLimit ?? 0;
|
|
var redeemPriceMax = marginDetail.RedeemPriceMax ?? 0;
|
|
var redeemAmount = marginDetail.PositionUnderlyingAmount ?? 0;
|
|
var tradeSpanInfoGroupSingle = clientGroupWithoutIndex.Where(y => x.mtv.TradeTypes.Contains(y.trade.TradeType) && (y.trade.BuySell == "卖出" && y.tradeSpan.IsSingleMargin == true || y.trade.BuySell == "买入"));
|
|
var tradeSpanInfoGroup = clientGroupWithoutIndex.Where(y => x.mtv.TradeTypes.Contains(y.trade.TradeType) && y.trade.BuySell == "卖出" && y.tradeSpan.IsSingleMargin != true);
|
|
if (tradeSpanInfoGroup.Select(y => y.trade.UnderlyingId).Distinct().Count() >= redeemAmount)
|
|
{
|
|
double groupMargins = 0;
|
|
var groupStrikeWithNotional = tradeSpanInfoGroup.Sum(y => (y.trade.ActualStrike * y.trade.Notional) ?? 0);
|
|
//计算单一客户当日标的整体跌跌幅时需要判断单个标的的跌幅是否超过上限
|
|
var groupPriceWithNotional = tradeSpanInfoGroup.Sum(y => (((1 - y.tradeSpan.UnderlyingPrice / y.trade.SpotPrice) > redeemPriceMax ? (y.trade.SpotPrice * (1 - redeemPriceMax)) : y.tradeSpan.UnderlyingPrice) * y.trade.Notional) ?? 0);
|
|
var groupStockEqvNotional = tradeSpanInfoGroup.Sum(y => TradeHelper.GetStockEqvNotionalReal(y.trade.StockEqvNotional, y.trade.ParticipationRate, y.trade.AnnualizeFactor));
|
|
var groupStockEqvNotionalNow = tradeSpanInfoGroup.Sum(y => (TradeHelper.GetStockEqvNotionalReal(y.trade.StockEqvNotional, y.trade.ParticipationRate, y.trade.AnnualizeFactor) * y.tradeSpan.UnderlyingPrice / y.trade.SpotPrice) ?? 0);
|
|
var groupStrikeStockEqvNotional = tradeSpanInfoGroup.Sum(y => TradeHelper.GetStockEqvNotionalReal(y.trade.StockEqvNotional, y.trade.ParticipationRate, y.trade.AnnualizeFactor) * (y.trade.ActualStrike ?? 0) / y.trade.SpotPrice.Value);
|
|
|
|
if (groupPriceWithNotional / groupStrikeWithNotional <= (1 - redeemPriceLimit))
|
|
{
|
|
groupMargins = groupStockEqvNotionalNow * redeemRatio1 + groupStrikeStockEqvNotional * (1 - groupPriceWithNotional / groupStrikeWithNotional);
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
else
|
|
{
|
|
groupMargins = groupStockEqvNotional * redeemRatio;
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
|
|
#region 更新tradeSpan,使得每笔交易的持仓预付金和组合预付金保持一致
|
|
|
|
var tradeIdList = tradeSpanInfoGroup.Select(y => y.tradeSpan.TradeId);
|
|
var sumStockEqvNotional = tradeSpanInfoGroup.Sum(y => y.trade.StockEqvNotional);
|
|
var tradeSpansUpdate = db.trade_span.Where(y => tradeIdList.Contains(y.TradeId) && y.ValueDate == req.settleDate).ToList();
|
|
var tradeSpansReq = req.tradeSpans.Where(y => tradeIdList.Contains(y.TradeId) && y.ValueDate == req.settleDate).ToList();
|
|
|
|
tradeSpansUpdate.ForEach(y =>
|
|
{
|
|
y.WorstCastClientPayable = groupMargins * tradeSpanInfoGroup.FirstOrDefault(z => z.trade.id == y.TradeId).trade.StockEqvNotional / sumStockEqvNotional;
|
|
});
|
|
tradeSpansReq.ForEach(y =>
|
|
{
|
|
y.WorstCastClientPayable = groupMargins * tradeSpanInfoGroup.FirstOrDefault(z => z.trade.id == y.TradeId).trade.StockEqvNotional / sumStockEqvNotional;
|
|
});
|
|
|
|
#endregion
|
|
|
|
//卖出的保本雪球,指数类的凤凰雪球需要单独计算预付金,买入的期权,进行累加
|
|
//以及自定义预付金处理的交易
|
|
worstCastClientPayable += tradeSpanInfoGroupSingle.Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
|
}
|
|
else
|
|
{
|
|
worstCastClientPayable += clientGroupWithoutIndex.Where(y => x.mtv.TradeTypes.Contains(y.trade.TradeType)).Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
|
}
|
|
}
|
|
else if ((x.mtv.RuleType == (int)MarginRuleTypeEnum.触发条件预付金规则1 || x.mtv.RuleType == (int)MarginRuleTypeEnum.触发条件预付金规则2) && x.mtv.DefaultParameterType == (int)DefaultMarginParameterTypeEnum.指数)
|
|
{
|
|
//指数类交易
|
|
var clientGroupIndex = clientGroupOther.Where(y => (y.trade.UnderlyingCode.StartsWith("000") && y.trade.UnderlyingCode.EndsWith(".SH") || y.trade.UnderlyingCode.StartsWith("39") || y.trade.UnderlyingCode.StartsWith("IF") || y.trade.UnderlyingCode.StartsWith("IH") || y.trade.UnderlyingCode.StartsWith("IC")) && y.tradetemplate == null);
|
|
|
|
worstCastClientPayable += clientGroupIndex.Where(y => x.mtv.TradeTypes.Contains(y.trade.TradeType)).Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
|
}
|
|
else if (x.mtv.RuleType == (int)MarginRuleTypeEnum.触发条件预付金规则3)
|
|
{
|
|
//初始预付金比例
|
|
var redeemRatio = marginDetail.MarginRatio1 ?? 0;
|
|
//持仓预付金比例
|
|
var redeemRatio2 = marginDetail.MarginRatio3 ?? 0;
|
|
//追加比例
|
|
var redeemRatio1 = marginDetail.MarginRatio2 ?? 0;
|
|
var redeemPriceLimit = marginDetail.RedeemPriceLimit ?? 0;
|
|
var redeemPriceMax = marginDetail.RedeemPriceMax ?? 0;
|
|
var redeemAmount = marginDetail.PositionUnderlyingAmount ?? 0;
|
|
var tradeSpanInfoGroupSingle = clientGroupOther.Where(y => x.mtv.TradeTypes.Contains(y.trade.TradeType) && (y.trade.BuySell == "卖出" && y.tradeSpan.IsSingleMargin == true || y.trade.BuySell == "买入"));
|
|
var tradeSpanInfoGroup = clientGroupOther.Where(y => x.mtv.TradeTypes.Contains(y.trade.TradeType) && y.trade.BuySell == "卖出" && y.tradeSpan.IsSingleMargin != true);
|
|
var groupStrikeWithNotional = tradeSpanInfoGroup.Sum(y => (y.trade.ActualStrike * y.trade.Notional) ?? 0);
|
|
//计算单一客户当日标的整体涨跌跌幅时需要判断单个标的的涨跌幅是否超过上限
|
|
var groupPriceWithNotional = tradeSpanInfoGroup.Sum(y => (((1 - y.tradeSpan.UnderlyingPrice / y.trade.SpotPrice) > redeemPriceMax && marginDetail.PriceLimitType == 0 || (y.tradeSpan.UnderlyingPrice / y.trade.SpotPrice - 1) > redeemPriceMax && marginDetail.PriceLimitType == 1 ? (y.trade.SpotPrice * (1 + (marginDetail.PriceLimitType == 0 ? -1 : 1) * redeemPriceMax)) : y.tradeSpan.UnderlyingPrice) * y.trade.Notional) ?? 0);
|
|
var groupStockEqvNotional = tradeSpanInfoGroup.Sum(y => TradeHelper.GetStockEqvNotionalReal(y.trade.StockEqvNotional, y.trade.ParticipationRate, y.trade.AnnualizeFactor));
|
|
var groupStrikeStockEqvNotional = tradeSpanInfoGroup.Sum(y => TradeHelper.GetStockEqvNotionalReal(y.trade.StockEqvNotional, y.trade.ParticipationRate, y.trade.AnnualizeFactor) * (y.trade.ActualStrike ?? 0) / y.trade.SpotPrice.Value);
|
|
|
|
if (tradeSpanInfoGroup.Select(y => y.trade.UnderlyingId).Distinct().Count() >= redeemAmount)
|
|
{
|
|
double groupMargins = 0;
|
|
if (groupStrikeWithNotional > 0)
|
|
{
|
|
var times = 0.0;
|
|
if (marginDetail.MarginRatio2 > 0)
|
|
{
|
|
if (marginDetail.PriceLimitType == 0)
|
|
{
|
|
times = Math.Floor(((1 - groupPriceWithNotional / groupStrikeWithNotional) - redeemPriceLimit) / marginDetail.MarginRatio2.Value);
|
|
}
|
|
else
|
|
{
|
|
times = Math.Floor(((groupPriceWithNotional / groupStrikeWithNotional - 1) - redeemPriceLimit) / marginDetail.MarginRatio2.Value);
|
|
}
|
|
}
|
|
|
|
if (marginDetail.UsePositionStockEqvNotional)
|
|
{
|
|
if (marginDetail.MarginRatio2 > 0 && groupPriceWithNotional / groupStrikeWithNotional <= (1 - redeemPriceLimit) && marginDetail.PriceLimitType == 0 || groupPriceWithNotional / groupStrikeWithNotional >= (1 + redeemPriceLimit) && marginDetail.PriceLimitType == 1)
|
|
{
|
|
//满足追加比例时,持仓名本分段式处理
|
|
var rateTemp = 0.0;
|
|
if (marginDetail.PriceLimitType == 0)
|
|
{
|
|
rateTemp = 1 - (redeemPriceLimit + marginDetail.MarginRatio2.Value * times);
|
|
}
|
|
else
|
|
{
|
|
rateTemp = 1 + (redeemPriceLimit + marginDetail.MarginRatio2.Value * times);
|
|
}
|
|
groupMargins = groupStockEqvNotional * rateTemp * redeemRatio2;
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
else
|
|
{
|
|
groupMargins = groupStockEqvNotional * redeemRatio;
|
|
//不满足追加比例时,用初始预付金
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (marginDetail.MarginRatio2 > 0 && groupPriceWithNotional / groupStrikeWithNotional <= (1 - redeemPriceLimit) && marginDetail.PriceLimitType == 0 || groupPriceWithNotional / groupStrikeWithNotional >= (1 + redeemPriceLimit) && marginDetail.PriceLimitType == 1)
|
|
{
|
|
groupMargins = groupStockEqvNotional * redeemRatio2;
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
else
|
|
{
|
|
groupMargins = groupStockEqvNotional * redeemRatio;
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
}
|
|
|
|
if (marginDetail.MarginRatio2 > 0 && groupPriceWithNotional / groupStrikeWithNotional <= (1 - redeemPriceLimit) && marginDetail.PriceLimitType == 0 || groupPriceWithNotional / groupStrikeWithNotional >= (1 + redeemPriceLimit) && marginDetail.PriceLimitType == 1)
|
|
{
|
|
worstCastClientPayable += groupStrikeStockEqvNotional * (redeemPriceLimit + marginDetail.MarginRatio2.Value * times);
|
|
groupMargins += groupStrikeStockEqvNotional * (redeemPriceLimit + marginDetail.MarginRatio2.Value * times);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
groupMargins = groupStockEqvNotional * redeemRatio;
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
|
|
#region 更新tradeSpan,使得每笔交易的持仓预付金和组合预付金保持一致
|
|
|
|
var tradeIdList = tradeSpanInfoGroup.Select(y => y.tradeSpan.TradeId);
|
|
var sumStockEqvNotional = tradeSpanInfoGroup.Sum(y => y.trade.StockEqvNotional);
|
|
var tradeSpansUpdate = db.trade_span.Where(y => tradeIdList.Contains(y.TradeId) && y.ValueDate == req.settleDate).ToList();
|
|
var tradeSpansReq = req.tradeSpans.Where(y => tradeIdList.Contains(y.TradeId) && y.ValueDate == req.settleDate).ToList();
|
|
|
|
tradeSpansUpdate.ForEach(y =>
|
|
{
|
|
y.WorstCastClientPayable = groupMargins * tradeSpanInfoGroup.FirstOrDefault(z => z.trade.id == y.TradeId).trade.StockEqvNotional / sumStockEqvNotional;
|
|
});
|
|
tradeSpansReq.ForEach(y =>
|
|
{
|
|
y.WorstCastClientPayable = groupMargins * tradeSpanInfoGroup.FirstOrDefault(z => z.trade.id == y.TradeId).trade.StockEqvNotional / sumStockEqvNotional;
|
|
});
|
|
|
|
#endregion
|
|
|
|
//卖出的保本雪球,指数类的凤凰雪球需要单独计算预付金,买入的期权,进行累加
|
|
//以及自定义预付金处理的交易
|
|
worstCastClientPayable += tradeSpanInfoGroupSingle.Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
|
}
|
|
else
|
|
{
|
|
worstCastClientPayable += clientGroupOther.Where(y => x.mtv.TradeTypes.Contains(y.trade.TradeType)).Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
worstCastClientPayable += clientGroupOther.Where(y => x.mtv.TradeTypes.Contains(y.trade.TradeType) && y.tradetemplate == null).Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
|
}
|
|
|
|
defaultTradeTypesDone.AddRange(x.mtv.TradeTypes.Split(','));
|
|
});
|
|
}
|
|
|
|
//系统默认预付金规则(客户级别有过默认规则的交易类型对应的默认交易需要被排除)
|
|
defaultMarginTemplates.ForEach(x =>
|
|
{
|
|
var clientGroupOtherRemain = clientGroupOther.Where(y => !defaultTradeTypesDone.Contains(y.trade.TradeType));
|
|
var marginDetail = db.margin_template_detail.Where(y => y.MarginTemplateId == x.id && y.ValueDate <= req.settleDate).OrderByDescending(y => y.ValueDate).FirstOrDefault();
|
|
if (marginDetail == null && (x.RuleType != (int)MarginRuleTypeEnum.无预付金 && x.RuleType != (int)MarginRuleTypeEnum.自动赎回买入预付金规则 && x.RuleType != (int)MarginRuleTypeEnum.含赔付预付金计算规则 && x.RuleType != (int)MarginRuleTypeEnum.保底预付金规则))
|
|
{
|
|
throw new Exception(string.Format("[{0}]不在{1}有效期内", req.settleDate.ToString("yyyy-MM-dd"), x.Name));
|
|
}
|
|
|
|
if (x.RuleType == (int)MarginRuleTypeEnum.类香草预付金规则)
|
|
{
|
|
var groupRatio = marginDetail.GroupRatio ?? 0;
|
|
var stockEqvNotional = marginDetail.StockEqvNotional ?? 0;
|
|
var singleStockEqvNotional = marginDetail.SingleStockEqvNotional ?? 0;
|
|
var amount = marginDetail.PositionUnderlyingAmount ?? 0;
|
|
|
|
//香草期权只考虑交易员买入的交易,卖出的交易不需要考虑预付金
|
|
var tradeVanillaSpanInfo = clientGroupOtherRemain.Where(y => x.TradeTypes.Contains(y.trade.TradeType) && y.trade.BuySell == "买入" && y.tradetemplate == null && y.tradeSpan.IsSingleMargin != true);
|
|
//自定义预付金
|
|
var tradeVanillaSpanInfoSingle = clientGroupOtherRemain.Where(y => x.TradeTypes.Contains(y.trade.TradeType) && y.trade.BuySell == "买入" && y.tradetemplate == null && y.tradeSpan.IsSingleMargin == true);
|
|
var umdStockEqvNotional = tradeVanillaSpanInfo.GroupBy(y => y.trade.UnderlyingId).Select(t => new
|
|
{
|
|
UnderlyingId = t.Key,
|
|
StockEqvNotional = t.Sum(g => TradeHelper.GetStockEqvNotionalReal(g.trade.StockEqvNotional, g.trade.ParticipationRate, g.trade.AnnualizeFactor)),
|
|
PositionStockEqvNotional = t.Sum(g => TradeHelper.GetStockEqvNotionalReal(g.trade.StockEqvNotional, g.trade.ParticipationRate, g.trade.AnnualizeFactor) * g.tradeSpan.UnderlyingPrice / g.trade.SpotPrice)
|
|
}).ToList();
|
|
var underlyingIds = umdStockEqvNotional.Select(y => y.UnderlyingId);
|
|
var totalStockEqvNotional = umdStockEqvNotional.Sum(y => y.PositionStockEqvNotional);
|
|
if (totalStockEqvNotional >= stockEqvNotional && umdStockEqvNotional.Any(y => y.PositionStockEqvNotional >= singleStockEqvNotional) && underlyingIds.Count() >= amount)
|
|
{
|
|
//获取组合持仓名义本金和初始名义本金的较高者
|
|
var maxStockEqvNotional = Math.Max(umdStockEqvNotional.Sum(y => y.StockEqvNotional >= singleStockEqvNotional ? singleStockEqvNotional : y.StockEqvNotional), umdStockEqvNotional.Sum(y => y.PositionStockEqvNotional >= singleStockEqvNotional ? singleStockEqvNotional : y.PositionStockEqvNotional ?? 0));
|
|
var groupMargins = maxStockEqvNotional * groupRatio + tradeVanillaSpanInfo.Sum(y => y.tradeSpan.PositionWin ?? 0);
|
|
worstCastClientPayable += groupMargins;
|
|
|
|
#region 更新tradeSpan,使得每笔交易的持仓预付金和组合预付金保持一致
|
|
|
|
var tradeIdList = tradeVanillaSpanInfo.Select(y => y.tradeSpan.TradeId);
|
|
var sumStockEqvNotional = tradeVanillaSpanInfo.Sum(y => y.trade.StockEqvNotional);
|
|
var tradeSpansUpdate = db.trade_span.Where(y => tradeIdList.Contains(y.TradeId) && y.ValueDate == req.settleDate).ToList();
|
|
var tradeSpansReq = req.tradeSpans.Where(y => tradeIdList.Contains(y.TradeId) && y.ValueDate == req.settleDate).ToList();
|
|
|
|
tradeSpansUpdate.ForEach(y =>
|
|
{
|
|
y.WorstCastClientPayable = groupMargins * tradeVanillaSpanInfo.FirstOrDefault(z => z.trade.id == y.TradeId).trade.StockEqvNotional / sumStockEqvNotional;
|
|
});
|
|
tradeSpansReq.ForEach(y =>
|
|
{
|
|
y.WorstCastClientPayable = groupMargins * tradeVanillaSpanInfo.FirstOrDefault(z => z.trade.id == y.TradeId).trade.StockEqvNotional / sumStockEqvNotional;
|
|
});
|
|
|
|
#endregion
|
|
}
|
|
else
|
|
{
|
|
worstCastClientPayable += tradeVanillaSpanInfo.Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
|
}
|
|
|
|
//自定义预付金处理的交易
|
|
worstCastClientPayable += tradeVanillaSpanInfoSingle.Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
|
}
|
|
else if (x.RuleType == (int)MarginRuleTypeEnum.触发条件预付金规则1 && x.DefaultParameterType == (int)DefaultMarginParameterTypeEnum.个股)
|
|
{
|
|
//排除指数类交易
|
|
var clientGroupWithoutIndex = clientGroupOtherRemain.Where(y => !(y.trade.UnderlyingCode.StartsWith("000") && y.trade.UnderlyingCode.EndsWith(".SH") || y.trade.UnderlyingCode.StartsWith("39") || y.trade.UnderlyingCode.StartsWith("IF") || y.trade.UnderlyingCode.StartsWith("IH") || y.trade.UnderlyingCode.StartsWith("IC")) && y.tradetemplate == null);
|
|
|
|
//初始预付金率
|
|
var redeemRatio = marginDetail.MarginRatio1 ?? 0;
|
|
//持仓预付金率
|
|
var redeemRatio1 = marginDetail.MarginRatio2 ?? 0;
|
|
var redeemPriceLimit = marginDetail.RedeemPriceLimit ?? 0;
|
|
var redeemPriceMax = marginDetail.RedeemPriceMax ?? 0;
|
|
var redeemAmount = marginDetail.PositionUnderlyingAmount ?? 0;
|
|
var tradeSpanInfoGroupSingle = clientGroupWithoutIndex.Where(y => x.TradeTypes.Contains(y.trade.TradeType) && (y.trade.BuySell == "卖出" && y.tradeSpan.IsSingleMargin == true || y.trade.BuySell == "买入"));
|
|
var tradeSpanInfoGroup = clientGroupWithoutIndex.Where(y => x.TradeTypes.Contains(y.trade.TradeType) && y.trade.BuySell == "卖出" && y.tradeSpan.IsSingleMargin != true);
|
|
if (tradeSpanInfoGroup.Select(y => y.trade.UnderlyingId).Distinct().Count() >= redeemAmount)
|
|
{
|
|
double groupMargins = 0;
|
|
var groupStrikeWithNotional = tradeSpanInfoGroup.Sum(y => (y.trade.ActualStrike * y.trade.Notional) ?? 0);
|
|
//计算单一客户当日标的整体跌跌幅时需要判断单个标的的跌幅是否超过上限
|
|
var groupPriceWithNotional = tradeSpanInfoGroup.Sum(y => (((1 - y.tradeSpan.UnderlyingPrice / y.trade.SpotPrice) > redeemPriceMax ? (y.trade.SpotPrice * (1 - redeemPriceMax)) : y.tradeSpan.UnderlyingPrice) * y.trade.Notional) ?? 0);
|
|
var groupStockEqvNotional = tradeSpanInfoGroup.Sum(y => TradeHelper.GetStockEqvNotionalReal(y.trade.StockEqvNotional, y.trade.ParticipationRate, y.trade.AnnualizeFactor));
|
|
var groupStrikeStockEqvNotional = tradeSpanInfoGroup.Sum(y => TradeHelper.GetStockEqvNotionalReal(y.trade.StockEqvNotional, y.trade.ParticipationRate, y.trade.AnnualizeFactor) * (y.trade.ActualStrike ?? 0) / y.trade.SpotPrice.Value);
|
|
|
|
if (groupPriceWithNotional / groupStrikeWithNotional <= (1 - redeemPriceLimit))
|
|
{
|
|
groupMargins = groupStockEqvNotional * redeemRatio1 + groupStrikeStockEqvNotional * (1 - groupPriceWithNotional / groupStrikeWithNotional);
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
else
|
|
{
|
|
groupMargins = groupStockEqvNotional * redeemRatio;
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
|
|
#region 更新tradeSpan,使得每笔交易的持仓预付金和组合预付金保持一致
|
|
|
|
var tradeIdList = tradeSpanInfoGroup.Select(y => y.tradeSpan.TradeId);
|
|
var sumStockEqvNotional = tradeSpanInfoGroup.Sum(y => y.trade.StockEqvNotional);
|
|
var tradeSpansUpdate = db.trade_span.Where(y => tradeIdList.Contains(y.TradeId) && y.ValueDate == req.settleDate).ToList();
|
|
var tradeSpansReq = req.tradeSpans.Where(y => tradeIdList.Contains(y.TradeId) && y.ValueDate == req.settleDate).ToList();
|
|
|
|
tradeSpansUpdate.ForEach(y =>
|
|
{
|
|
y.WorstCastClientPayable = groupMargins * tradeSpanInfoGroup.FirstOrDefault(z => z.trade.id == y.TradeId).trade.StockEqvNotional / sumStockEqvNotional;
|
|
});
|
|
tradeSpansReq.ForEach(y =>
|
|
{
|
|
y.WorstCastClientPayable = groupMargins * tradeSpanInfoGroup.FirstOrDefault(z => z.trade.id == y.TradeId).trade.StockEqvNotional / sumStockEqvNotional;
|
|
});
|
|
|
|
#endregion
|
|
|
|
//卖出的保本雪球,指数类的凤凰雪球需要单独计算预付金,买入的期权,进行累加
|
|
//以及自定义预付金处理的交易
|
|
worstCastClientPayable += tradeSpanInfoGroupSingle.Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
|
}
|
|
else
|
|
{
|
|
worstCastClientPayable += clientGroupWithoutIndex.Where(y => x.TradeTypes.Contains(y.trade.TradeType)).Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
|
}
|
|
}
|
|
else if (x.RuleType == (int)MarginRuleTypeEnum.触发条件预付金规则2 && x.DefaultParameterType == (int)DefaultMarginParameterTypeEnum.个股)
|
|
{
|
|
//排除指数类交易
|
|
var clientGroupWithoutIndex = clientGroupOtherRemain.Where(y => !(y.trade.UnderlyingCode.StartsWith("000") && y.trade.UnderlyingCode.EndsWith(".SH") || y.trade.UnderlyingCode.StartsWith("39") || y.trade.UnderlyingCode.StartsWith("IF") || y.trade.UnderlyingCode.StartsWith("IH") || y.trade.UnderlyingCode.StartsWith("IC")) && y.tradetemplate == null);
|
|
|
|
//初始预付金率
|
|
var redeemRatio = marginDetail.MarginRatio1 ?? 0;
|
|
//持仓预付金率
|
|
var redeemRatio1 = marginDetail.MarginRatio2 ?? 0;
|
|
var redeemPriceLimit = marginDetail.RedeemPriceLimit ?? 0;
|
|
var redeemPriceMax = marginDetail.RedeemPriceMax ?? 0;
|
|
var redeemAmount = marginDetail.PositionUnderlyingAmount ?? 0;
|
|
var tradeSpanInfoGroupSingle = clientGroupWithoutIndex.Where(y => x.TradeTypes.Contains(y.trade.TradeType) && (y.trade.BuySell == "卖出" && y.tradeSpan.IsSingleMargin == true || y.trade.BuySell == "买入"));
|
|
var tradeSpanInfoGroup = clientGroupWithoutIndex.Where(y => x.TradeTypes.Contains(y.trade.TradeType) && y.trade.BuySell == "卖出" && y.tradeSpan.IsSingleMargin != true);
|
|
if (tradeSpanInfoGroup.Select(y => y.trade.UnderlyingId).Distinct().Count() >= redeemAmount)
|
|
{
|
|
double groupMargins = 0;
|
|
var groupStrikeWithNotional = tradeSpanInfoGroup.Sum(y => (y.trade.ActualStrike * y.trade.Notional) ?? 0);
|
|
//计算单一客户当日标的整体跌跌幅时需要判断单个标的的跌幅是否超过上限
|
|
var groupPriceWithNotional = tradeSpanInfoGroup.Sum(y => (((1 - y.tradeSpan.UnderlyingPrice / y.trade.SpotPrice) > redeemPriceMax ? (y.trade.SpotPrice * (1 - redeemPriceMax)) : y.tradeSpan.UnderlyingPrice) * y.trade.Notional) ?? 0);
|
|
var groupStockEqvNotional = tradeSpanInfoGroup.Sum(y => TradeHelper.GetStockEqvNotionalReal(y.trade.StockEqvNotional, y.trade.ParticipationRate, y.trade.AnnualizeFactor));
|
|
var groupStockEqvNotionalNow = tradeSpanInfoGroup.Sum(y => (TradeHelper.GetStockEqvNotionalReal(y.trade.StockEqvNotional, y.trade.ParticipationRate, y.trade.AnnualizeFactor) * y.tradeSpan.UnderlyingPrice / y.trade.SpotPrice) ?? 0);
|
|
var groupStrikeStockEqvNotional = tradeSpanInfoGroup.Sum(y => TradeHelper.GetStockEqvNotionalReal(y.trade.StockEqvNotional, y.trade.ParticipationRate, y.trade.AnnualizeFactor) * (y.trade.ActualStrike ?? 0) / y.trade.SpotPrice.Value);
|
|
|
|
if (groupPriceWithNotional / groupStrikeWithNotional <= (1 - redeemPriceLimit))
|
|
{
|
|
groupMargins = groupStockEqvNotionalNow * redeemRatio1 + groupStrikeStockEqvNotional * (1 - groupPriceWithNotional / groupStrikeWithNotional);
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
else
|
|
{
|
|
groupMargins = groupStockEqvNotional * redeemRatio;
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
|
|
#region 更新tradeSpan,使得每笔交易的持仓预付金和组合预付金保持一致
|
|
|
|
var tradeIdList = tradeSpanInfoGroup.Select(y => y.tradeSpan.TradeId);
|
|
var sumStockEqvNotional = tradeSpanInfoGroup.Sum(y => y.trade.StockEqvNotional);
|
|
var tradeSpansUpdate = db.trade_span.Where(y => tradeIdList.Contains(y.TradeId) && y.ValueDate == req.settleDate).ToList();
|
|
var tradeSpansReq = req.tradeSpans.Where(y => tradeIdList.Contains(y.TradeId) && y.ValueDate == req.settleDate).ToList();
|
|
|
|
tradeSpansUpdate.ForEach(y =>
|
|
{
|
|
y.WorstCastClientPayable = groupMargins * tradeSpanInfoGroup.FirstOrDefault(z => z.trade.id == y.TradeId).trade.StockEqvNotional / sumStockEqvNotional;
|
|
});
|
|
tradeSpansReq.ForEach(y =>
|
|
{
|
|
y.WorstCastClientPayable = groupMargins * tradeSpanInfoGroup.FirstOrDefault(z => z.trade.id == y.TradeId).trade.StockEqvNotional / sumStockEqvNotional;
|
|
});
|
|
|
|
#endregion
|
|
|
|
//卖出的保本雪球,指数类的凤凰雪球需要单独计算预付金,买入的期权,进行累加
|
|
//以及自定义预付金处理的交易
|
|
worstCastClientPayable += tradeSpanInfoGroupSingle.Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
|
}
|
|
else
|
|
{
|
|
worstCastClientPayable += clientGroupWithoutIndex.Where(y => x.TradeTypes.Contains(y.trade.TradeType)).Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
|
}
|
|
}
|
|
else if ((x.RuleType == (int)MarginRuleTypeEnum.触发条件预付金规则1 || x.RuleType == (int)MarginRuleTypeEnum.触发条件预付金规则2) && x.DefaultParameterType == (int)DefaultMarginParameterTypeEnum.指数)
|
|
{
|
|
//指数类交易
|
|
var clientGroupIndex = clientGroupOtherRemain.Where(y => (y.trade.UnderlyingCode.StartsWith("000") && y.trade.UnderlyingCode.EndsWith(".SH") || y.trade.UnderlyingCode.StartsWith("39") || y.trade.UnderlyingCode.StartsWith("IF") || y.trade.UnderlyingCode.StartsWith("IH") || y.trade.UnderlyingCode.StartsWith("IC")) && y.tradetemplate == null);
|
|
|
|
worstCastClientPayable += clientGroupIndex.Where(y => x.TradeTypes.Contains(y.trade.TradeType)).Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
|
}
|
|
else if (x.RuleType == (int)MarginRuleTypeEnum.触发条件预付金规则3)
|
|
{
|
|
//初始预付金比例
|
|
var redeemRatio = marginDetail.MarginRatio1 ?? 0;
|
|
//持仓预付金比例
|
|
var redeemRatio2 = marginDetail.MarginRatio3 ?? 0;
|
|
//追加比例
|
|
var redeemRatio1 = marginDetail.MarginRatio2 ?? 0;
|
|
var redeemPriceLimit = marginDetail.RedeemPriceLimit ?? 0;
|
|
var redeemPriceMax = marginDetail.RedeemPriceMax ?? 0;
|
|
var redeemAmount = marginDetail.PositionUnderlyingAmount ?? 0;
|
|
var tradeSpanInfoGroupSingle = clientGroupOtherRemain.Where(y => x.TradeTypes.Contains(y.trade.TradeType) && (y.trade.BuySell == "卖出" && y.tradeSpan.IsSingleMargin == true || y.trade.BuySell == "买入"));
|
|
var tradeSpanInfoGroup = clientGroupOtherRemain.Where(y => x.TradeTypes.Contains(y.trade.TradeType) && y.trade.BuySell == "卖出" && y.tradeSpan.IsSingleMargin != true);
|
|
var groupStrikeWithNotional = tradeSpanInfoGroup.Sum(y => (y.trade.ActualStrike * y.trade.Notional) ?? 0);
|
|
//计算单一客户当日标的整体涨跌跌幅时需要判断单个标的的涨跌幅是否超过上限
|
|
var groupPriceWithNotional = tradeSpanInfoGroup.Sum(y => (((1 - y.tradeSpan.UnderlyingPrice / y.trade.SpotPrice) > redeemPriceMax && marginDetail.PriceLimitType == 0 || (y.tradeSpan.UnderlyingPrice / y.trade.SpotPrice - 1) > redeemPriceMax && marginDetail.PriceLimitType == 1 ? (y.trade.SpotPrice * (1 + (marginDetail.PriceLimitType == 0 ? -1 : 1) * redeemPriceMax)) : y.tradeSpan.UnderlyingPrice) * y.trade.Notional) ?? 0);
|
|
var groupStockEqvNotional = tradeSpanInfoGroup.Sum(y => TradeHelper.GetStockEqvNotionalReal(y.trade.StockEqvNotional, y.trade.ParticipationRate, y.trade.AnnualizeFactor));
|
|
var groupStrikeStockEqvNotional = tradeSpanInfoGroup.Sum(y => TradeHelper.GetStockEqvNotionalReal(y.trade.StockEqvNotional, y.trade.ParticipationRate, y.trade.AnnualizeFactor) * (y.trade.ActualStrike ?? 0) / y.trade.SpotPrice.Value);
|
|
|
|
if (tradeSpanInfoGroup.Select(y => y.trade.UnderlyingId).Distinct().Count() >= redeemAmount)
|
|
{
|
|
double groupMargins = 0;
|
|
if (groupStrikeWithNotional > 0)
|
|
{
|
|
var times = 0.0;
|
|
if (marginDetail.MarginRatio2 > 0)
|
|
{
|
|
if (marginDetail.PriceLimitType == 0)
|
|
{
|
|
times = Math.Floor(((1 - groupPriceWithNotional / groupStrikeWithNotional) - redeemPriceLimit) / marginDetail.MarginRatio2.Value);
|
|
}
|
|
else
|
|
{
|
|
times = Math.Floor(((groupPriceWithNotional / groupStrikeWithNotional - 1) - redeemPriceLimit) / marginDetail.MarginRatio2.Value);
|
|
}
|
|
}
|
|
|
|
if (marginDetail.UsePositionStockEqvNotional)
|
|
{
|
|
if (marginDetail.MarginRatio2 > 0 && groupPriceWithNotional / groupStrikeWithNotional <= (1 - redeemPriceLimit) && marginDetail.PriceLimitType == 0 || groupPriceWithNotional / groupStrikeWithNotional >= (1 + redeemPriceLimit) && marginDetail.PriceLimitType == 1)
|
|
{
|
|
//满足追加比例时,持仓名本分段式处理
|
|
var rateTemp = 0.0;
|
|
if (marginDetail.PriceLimitType == 0)
|
|
{
|
|
rateTemp = 1 - (redeemPriceLimit + marginDetail.MarginRatio2.Value * times);
|
|
}
|
|
else
|
|
{
|
|
rateTemp = 1 + (redeemPriceLimit + marginDetail.MarginRatio2.Value * times);
|
|
}
|
|
groupMargins = groupStockEqvNotional * rateTemp * redeemRatio2;
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
else
|
|
{
|
|
//不满足追加比例时,用初始预付金
|
|
groupMargins = groupStockEqvNotional * redeemRatio;
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (marginDetail.MarginRatio2 > 0 && groupPriceWithNotional / groupStrikeWithNotional <= (1 - redeemPriceLimit) && marginDetail.PriceLimitType == 0 || groupPriceWithNotional / groupStrikeWithNotional >= (1 + redeemPriceLimit) && marginDetail.PriceLimitType == 1)
|
|
{
|
|
groupMargins = groupStockEqvNotional * redeemRatio2;
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
else
|
|
{
|
|
groupMargins = groupStockEqvNotional * redeemRatio;
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
}
|
|
|
|
if (marginDetail.MarginRatio2 > 0 && groupPriceWithNotional / groupStrikeWithNotional <= (1 - redeemPriceLimit) && marginDetail.PriceLimitType == 0 || groupPriceWithNotional / groupStrikeWithNotional >= (1 + redeemPriceLimit) && marginDetail.PriceLimitType == 1)
|
|
{
|
|
worstCastClientPayable += groupStrikeStockEqvNotional * (redeemPriceLimit + marginDetail.MarginRatio2.Value * times);
|
|
groupMargins += groupStrikeStockEqvNotional * (redeemPriceLimit + marginDetail.MarginRatio2.Value * times);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
groupMargins = groupStockEqvNotional * redeemRatio;
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
|
|
#region 更新tradeSpan,使得每笔交易的持仓预付金和组合预付金保持一致
|
|
|
|
var tradeIdList = tradeSpanInfoGroup.Select(y => y.tradeSpan.TradeId);
|
|
var sumStockEqvNotional = tradeSpanInfoGroup.Sum(y => y.trade.StockEqvNotional);
|
|
var tradeSpansUpdate = db.trade_span.Where(y => tradeIdList.Contains(y.TradeId) && y.ValueDate == req.settleDate).ToList();
|
|
var tradeSpansReq = req.tradeSpans.Where(y => tradeIdList.Contains(y.TradeId) && y.ValueDate == req.settleDate).ToList();
|
|
|
|
tradeSpansUpdate.ForEach(y =>
|
|
{
|
|
y.WorstCastClientPayable = groupMargins * tradeSpanInfoGroup.FirstOrDefault(z => z.trade.id == y.TradeId).trade.StockEqvNotional / sumStockEqvNotional;
|
|
});
|
|
tradeSpansReq.ForEach(y =>
|
|
{
|
|
y.WorstCastClientPayable = groupMargins * tradeSpanInfoGroup.FirstOrDefault(z => z.trade.id == y.TradeId).trade.StockEqvNotional / sumStockEqvNotional;
|
|
});
|
|
|
|
#endregion
|
|
|
|
//卖出的保本雪球,指数类的凤凰雪球需要单独计算预付金,买入的期权,进行累加
|
|
//以及自定义预付金处理的交易
|
|
worstCastClientPayable += tradeSpanInfoGroupSingle.Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
|
}
|
|
else
|
|
{
|
|
worstCastClientPayable += clientGroupOtherRemain.Where(y => x.TradeTypes.Contains(y.trade.TradeType)).Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
worstCastClientPayable += clientGroupOtherRemain.Where(y => x.TradeTypes.Contains(y.trade.TradeType) && y.tradetemplate == null).Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
|
}
|
|
});
|
|
|
|
#endregion
|
|
|
|
#region 自定义组合预付金规则
|
|
|
|
groupMarginTemplates.ForEach(x =>
|
|
{
|
|
var marginDetail = db.margin_template_detail.Where(y => y.MarginTemplateId == x.id && y.ValueDate <= req.settleDate).OrderByDescending(y => y.ValueDate).FirstOrDefault();
|
|
//非自定义预付金的交易
|
|
var tradeSpanInfoGroup = clientGroupOther.Where(y => y.tradetemplate != null && y.tradetemplate.MarginTemplateId == x.id && y.tradeSpan.IsSingleMargin != true);
|
|
if (marginDetail == null && tradeSpanInfoGroup.Any())
|
|
{
|
|
throw new Exception(string.Format("[{0}]不在{1}有效期内", req.settleDate.ToString("yyyy-MM-dd"), x.Name));
|
|
}
|
|
//自定义预付金的交易
|
|
var tradeSpanInfoGroupSingle = clientGroupOther.Where(y => y.tradetemplate != null && y.tradetemplate.MarginTemplateId == x.id && y.tradeSpan.IsSingleMargin == true);
|
|
groupTradeIds.AddRange(tradeSpanInfoGroup.Select(y => y.trade.id));
|
|
groupTradeIds.AddRange(tradeSpanInfoGroupSingle.Select(y => y.trade.id));
|
|
if (x.RuleType == (int)MarginRuleTypeEnum.触发条件预付金规则1)
|
|
{
|
|
double groupMargins = 0;
|
|
//初始预付金率
|
|
var redeemRatio = marginDetail.MarginRatio1 ?? 0;
|
|
//持仓预付金率
|
|
var redeemRatio1 = marginDetail.MarginRatio2 ?? 0;
|
|
var redeemPriceLimit = marginDetail.RedeemPriceLimit ?? 0;
|
|
var redeemPriceMax = marginDetail.RedeemPriceMax ?? 0;
|
|
|
|
var groupStrikeWithNotional = tradeSpanInfoGroup.Sum(y => (y.trade.ActualStrike * y.trade.Notional) ?? 0);
|
|
//计算单一客户当日标的整体跌跌幅时需要判断单个标的的跌幅是否超过上限
|
|
var groupPriceWithNotional = tradeSpanInfoGroup.Sum(y => (((1 - y.tradeSpan.UnderlyingPrice / y.trade.SpotPrice) > redeemPriceMax ? (y.trade.SpotPrice * (1 - redeemPriceMax)) : y.tradeSpan.UnderlyingPrice) * y.trade.Notional) ?? 0);
|
|
var groupStockEqvNotional = tradeSpanInfoGroup.Sum(y => TradeHelper.GetStockEqvNotionalReal(y.trade.StockEqvNotional, y.trade.ParticipationRate, y.trade.AnnualizeFactor));
|
|
var groupStrikeStockEqvNotional = tradeSpanInfoGroup.Sum(y => TradeHelper.GetStockEqvNotionalReal(y.trade.StockEqvNotional, y.trade.ParticipationRate, y.trade.AnnualizeFactor) * (y.trade.ActualStrike ?? 0) / y.trade.SpotPrice.Value);
|
|
|
|
if (groupPriceWithNotional / groupStrikeWithNotional <= (1 - redeemPriceLimit))
|
|
{
|
|
groupMargins = groupStockEqvNotional * redeemRatio1 + groupStrikeStockEqvNotional * (1 - groupPriceWithNotional / groupStrikeWithNotional);
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
else
|
|
{
|
|
groupMargins = groupStockEqvNotional * redeemRatio;
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
|
|
#region 更新tradeSpan,使得每笔交易的持仓预付金和组合预付金保持一致
|
|
|
|
var tradeIdList = tradeSpanInfoGroup.Select(y => y.tradeSpan.TradeId);
|
|
var sumStockEqvNotional = tradeSpanInfoGroup.Sum(y => y.trade.StockEqvNotional);
|
|
var tradeSpansUpdate = db.trade_span.Where(y => tradeIdList.Contains(y.TradeId) && y.ValueDate == req.settleDate).ToList();
|
|
var tradeSpansReq = req.tradeSpans.Where(y => tradeIdList.Contains(y.TradeId) && y.ValueDate == req.settleDate).ToList();
|
|
|
|
tradeSpansUpdate.ForEach(y =>
|
|
{
|
|
y.WorstCastClientPayable = groupMargins * tradeSpanInfoGroup.FirstOrDefault(z => z.trade.id == y.TradeId).trade.StockEqvNotional / sumStockEqvNotional;
|
|
});
|
|
tradeSpansReq.ForEach(y =>
|
|
{
|
|
y.WorstCastClientPayable = groupMargins * tradeSpanInfoGroup.FirstOrDefault(z => z.trade.id == y.TradeId).trade.StockEqvNotional / sumStockEqvNotional;
|
|
});
|
|
|
|
#endregion
|
|
}
|
|
else if (x.RuleType == (int)MarginRuleTypeEnum.触发条件预付金规则2)
|
|
{
|
|
double groupMargins = 0;
|
|
//初始预付金率
|
|
var redeemRatio = marginDetail.MarginRatio1 ?? 0;
|
|
//持仓预付金率
|
|
var redeemRatio1 = marginDetail.MarginRatio2 ?? 0;
|
|
var redeemPriceLimit = marginDetail.RedeemPriceLimit ?? 0;
|
|
var redeemPriceMax = marginDetail.RedeemPriceMax ?? 0;
|
|
|
|
var groupStrikeWithNotional = tradeSpanInfoGroup.Sum(y => (y.trade.ActualStrike * y.trade.Notional) ?? 0);
|
|
//计算单一客户当日标的整体跌跌幅时需要判断单个标的的跌幅是否超过上限
|
|
var groupPriceWithNotional = tradeSpanInfoGroup.Sum(y => (((1 - y.tradeSpan.UnderlyingPrice / y.trade.SpotPrice) > redeemPriceMax ? (y.trade.SpotPrice * (1 - redeemPriceMax)) : y.tradeSpan.UnderlyingPrice) * y.trade.Notional) ?? 0);
|
|
var groupStockEqvNotional = tradeSpanInfoGroup.Sum(y => TradeHelper.GetStockEqvNotionalReal(y.trade.StockEqvNotional, y.trade.ParticipationRate, y.trade.AnnualizeFactor));
|
|
var groupStockEqvNotionalNow = tradeSpanInfoGroup.Sum(y => (TradeHelper.GetStockEqvNotionalReal(y.trade.StockEqvNotional, y.trade.ParticipationRate, y.trade.AnnualizeFactor) * y.tradeSpan.UnderlyingPrice / y.trade.SpotPrice) ?? 0);
|
|
var groupStrikeStockEqvNotional = tradeSpanInfoGroup.Sum(y => TradeHelper.GetStockEqvNotionalReal(y.trade.StockEqvNotional, y.trade.ParticipationRate, y.trade.AnnualizeFactor) * (y.trade.ActualStrike ?? 0) / y.trade.SpotPrice.Value);
|
|
|
|
if (groupPriceWithNotional / groupStrikeWithNotional <= (1 - redeemPriceLimit))
|
|
{
|
|
groupMargins = groupStockEqvNotionalNow * redeemRatio1 + groupStrikeStockEqvNotional * (1 - groupPriceWithNotional / groupStrikeWithNotional);
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
else
|
|
{
|
|
groupMargins = groupStockEqvNotional * redeemRatio;
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
|
|
#region 更新tradeSpan,使得每笔交易的持仓预付金和组合预付金保持一致
|
|
|
|
var tradeIdList = tradeSpanInfoGroup.Select(y => y.tradeSpan.TradeId);
|
|
var sumStockEqvNotional = tradeSpanInfoGroup.Sum(y => y.trade.StockEqvNotional);
|
|
var tradeSpansUpdate = db.trade_span.Where(y => tradeIdList.Contains(y.TradeId) && y.ValueDate == req.settleDate).ToList();
|
|
var tradeSpansReq = req.tradeSpans.Where(y => tradeIdList.Contains(y.TradeId) && y.ValueDate == req.settleDate).ToList();
|
|
|
|
tradeSpansUpdate.ForEach(y =>
|
|
{
|
|
y.WorstCastClientPayable = groupMargins * tradeSpanInfoGroup.FirstOrDefault(z => z.trade.id == y.TradeId).trade.StockEqvNotional / sumStockEqvNotional;
|
|
});
|
|
tradeSpansReq.ForEach(y =>
|
|
{
|
|
y.WorstCastClientPayable = groupMargins * tradeSpanInfoGroup.FirstOrDefault(z => z.trade.id == y.TradeId).trade.StockEqvNotional / sumStockEqvNotional;
|
|
});
|
|
|
|
#endregion
|
|
}
|
|
else if (x.RuleType == (int)MarginRuleTypeEnum.触发条件预付金规则3)
|
|
{
|
|
double groupMargins = 0;
|
|
//初始预付金比例
|
|
var redeemRatio = marginDetail.MarginRatio1 ?? 0;
|
|
//持仓预付金比例
|
|
var redeemRatio2 = marginDetail.MarginRatio3 ?? 0;
|
|
//追加比例
|
|
var redeemRatio1 = marginDetail.MarginRatio2 ?? 0;
|
|
var redeemPriceLimit = marginDetail.RedeemPriceLimit ?? 0;
|
|
var redeemPriceMax = marginDetail.RedeemPriceMax ?? 0;
|
|
|
|
var groupStrikeWithNotional = tradeSpanInfoGroup.Sum(y => (y.trade.ActualStrike * y.trade.Notional) ?? 0);
|
|
//计算单一客户当日标的整体涨跌跌幅时需要判断单个标的的涨跌幅是否超过上限
|
|
var groupPriceWithNotional = tradeSpanInfoGroup.Sum(y => (((1 - y.tradeSpan.UnderlyingPrice / y.trade.SpotPrice) > redeemPriceMax && marginDetail.PriceLimitType == 0 || (y.tradeSpan.UnderlyingPrice / y.trade.SpotPrice - 1) > redeemPriceMax && marginDetail.PriceLimitType == 1 ? (y.trade.SpotPrice * (1 + (marginDetail.PriceLimitType == 0 ? -1 : 1) * redeemPriceMax)) : y.tradeSpan.UnderlyingPrice) * y.trade.Notional) ?? 0);
|
|
var groupStockEqvNotional = tradeSpanInfoGroup.Sum(y => TradeHelper.GetStockEqvNotionalReal(y.trade.StockEqvNotional, y.trade.ParticipationRate, y.trade.AnnualizeFactor));
|
|
var groupStrikeStockEqvNotional = tradeSpanInfoGroup.Sum(y => TradeHelper.GetStockEqvNotionalReal(y.trade.StockEqvNotional, y.trade.ParticipationRate, y.trade.AnnualizeFactor) * (y.trade.ActualStrike ?? 0) / y.trade.SpotPrice.Value);
|
|
|
|
if (groupStrikeWithNotional > 0)
|
|
{
|
|
var times = 0.0;
|
|
if (marginDetail.MarginRatio2 > 0)
|
|
{
|
|
if (marginDetail.PriceLimitType == 0)
|
|
{
|
|
times = Math.Floor(((1 - groupPriceWithNotional / groupStrikeWithNotional) - redeemPriceLimit) / marginDetail.MarginRatio2.Value);
|
|
}
|
|
else
|
|
{
|
|
times = Math.Floor(((groupPriceWithNotional / groupStrikeWithNotional - 1) - redeemPriceLimit) / marginDetail.MarginRatio2.Value);
|
|
}
|
|
}
|
|
|
|
if (marginDetail.UsePositionStockEqvNotional)
|
|
{
|
|
if (marginDetail.MarginRatio2 > 0 && groupPriceWithNotional / groupStrikeWithNotional <= (1 - redeemPriceLimit) && marginDetail.PriceLimitType == 0 || groupPriceWithNotional / groupStrikeWithNotional >= (1 + redeemPriceLimit) && marginDetail.PriceLimitType == 1)
|
|
{
|
|
//满足追加比例时,持仓名本分段式处理
|
|
var rateTemp = 0.0;
|
|
if (marginDetail.PriceLimitType == 0)
|
|
{
|
|
rateTemp = 1 - (redeemPriceLimit + marginDetail.MarginRatio2.Value * times);
|
|
}
|
|
else
|
|
{
|
|
rateTemp = 1 + (redeemPriceLimit + marginDetail.MarginRatio2.Value * times);
|
|
}
|
|
groupMargins = groupStockEqvNotional * rateTemp * redeemRatio2;
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
else
|
|
{
|
|
//不满足追加比例时,用初始预付金
|
|
groupMargins = groupStockEqvNotional * redeemRatio;
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (marginDetail.MarginRatio2 > 0 && groupPriceWithNotional / groupStrikeWithNotional <= (1 - redeemPriceLimit) && marginDetail.PriceLimitType == 0 || groupPriceWithNotional / groupStrikeWithNotional >= (1 + redeemPriceLimit) && marginDetail.PriceLimitType == 1)
|
|
{
|
|
groupMargins = groupStockEqvNotional * redeemRatio2;
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
else
|
|
{
|
|
groupMargins = groupStockEqvNotional * redeemRatio;
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
}
|
|
|
|
if (marginDetail.MarginRatio2 > 0 && groupPriceWithNotional / groupStrikeWithNotional <= (1 - redeemPriceLimit) && marginDetail.PriceLimitType == 0 || groupPriceWithNotional / groupStrikeWithNotional >= (1 + redeemPriceLimit) && marginDetail.PriceLimitType == 1)
|
|
{
|
|
worstCastClientPayable += groupStrikeStockEqvNotional * (redeemPriceLimit + marginDetail.MarginRatio2.Value * times);
|
|
groupMargins += groupStrikeStockEqvNotional * (redeemPriceLimit + marginDetail.MarginRatio2.Value * times);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
groupMargins = groupStockEqvNotional * redeemRatio;
|
|
worstCastClientPayable += groupMargins;
|
|
}
|
|
|
|
#region 更新tradeSpan,使得每笔交易的持仓预付金和组合预付金保持一致
|
|
|
|
var tradeIdList = tradeSpanInfoGroup.Select(y => y.tradeSpan.TradeId);
|
|
var sumStockEqvNotional = tradeSpanInfoGroup.Sum(y => y.trade.StockEqvNotional);
|
|
var tradeSpansUpdate = db.trade_span.Where(y => tradeIdList.Contains(y.TradeId) && y.ValueDate == req.settleDate).ToList();
|
|
var tradeSpansReq = req.tradeSpans.Where(y => tradeIdList.Contains(y.TradeId) && y.ValueDate == req.settleDate).ToList();
|
|
|
|
tradeSpansUpdate.ForEach(y =>
|
|
{
|
|
y.WorstCastClientPayable = groupMargins * tradeSpanInfoGroup.FirstOrDefault(z => z.trade.id == y.TradeId).trade.StockEqvNotional / sumStockEqvNotional;
|
|
});
|
|
tradeSpansReq.ForEach(y =>
|
|
{
|
|
y.WorstCastClientPayable = groupMargins * tradeSpanInfoGroup.FirstOrDefault(z => z.trade.id == y.TradeId).trade.StockEqvNotional / sumStockEqvNotional;
|
|
});
|
|
|
|
#endregion
|
|
}
|
|
else
|
|
{
|
|
worstCastClientPayable += tradeSpanInfoGroup.Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
|
}
|
|
|
|
//自定义预付金处理的交易
|
|
worstCastClientPayable += tradeSpanInfoGroupSingle.Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
|
});
|
|
|
|
#endregion
|
|
|
|
//自定义单腿规则的预付金加总
|
|
worstCastClientPayable += clientGroupOther.Where(x => x.tradetemplate != null && !groupTradeIds.Contains(x.trade.id)).Sum(x => x.tradeSpan.WorstCastClientPayable ?? 0);
|
|
|
|
//IC,IH,IF用span算法合计预付金
|
|
if (clientGroupICIHIF.Any())
|
|
{
|
|
var underlyingGroupICIHIF = clientGroupICIHIF.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),
|
|
}).ToList();
|
|
foreach (var item in underlyingGroupICIHIF)
|
|
{
|
|
item.WorstCastClientPayable = Math.Min(Math.Min(Math.Min(item.Spv1 ?? 0, item.Spv2 ?? 0), item.Spv3 ?? 0), item.Spv4 ?? 0);
|
|
worstCastClientPayable += -(item.WorstCastClientPayable ?? 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 underlyingGroup = clientGroup.GroupBy(t => t.trade.UnderlyingId).Select(t => new ClientSpan
|
|
{
|
|
UnderlyingId = t.Key,
|
|
ClientId = clientGroup.Key,
|
|
ValueDate = req.settleDate,
|
|
DeltaMargin = t.Sum(g => g.tradeSpan.DeltaMargin) * (-1)
|
|
}).ToList();
|
|
foreach (var item in underlyingGroup)
|
|
{
|
|
if (!HasTwoSideMargin(clientGroup.Key))
|
|
{
|
|
item.DeltaMargin = Math.Min(item.DeltaMargin ?? 0, 0);
|
|
}
|
|
}
|
|
|
|
//客户方向delta预付金
|
|
var deltaMargin = underlyingGroup.Sum(g => (g.DeltaMargin ?? 0)) - positionWinLoss;
|
|
if (!HasTwoSideMargin(clientGroup.Key))
|
|
{
|
|
deltaMargin = Math.Min(deltaMargin, 0);
|
|
}
|
|
|
|
var clientSpan = new ClientSpan
|
|
{
|
|
ClientId = clientGroup.Key,
|
|
ValueDate = req.settleDate,
|
|
//负数代表客户应缴预付金,正数代表客户应收预付金
|
|
WorstCastClientPayable = -worstCastClientPayable,
|
|
DeltaMargin = deltaMargin,
|
|
OptId = req.userId,
|
|
OptName = req.userName,
|
|
OptDate = DateTime.Now,
|
|
SpanType = req.SpanType
|
|
};
|
|
|
|
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.trade.id).ToList();
|
|
}
|
|
|
|
var marginReq = req.GetRunMarginCalculationReq();
|
|
if (req.trade.IsGroup == 1)
|
|
{
|
|
marginReq.tradeList = db.trade.Where(x => x.ParentTradeId == req.trade.id).ToList();
|
|
if (marginReq.CalcMarginType == CalcMarginTypeEnum.InitialMargin)
|
|
{
|
|
foreach (var item in marginReq.tradeList)
|
|
{
|
|
item.id = 0;
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|