2719 lines
168 KiB
C#
2719 lines
168 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.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
|
||
{
|
||
/// <summary>
|
||
/// 国泰君安预付金计算
|
||
/// </summary>
|
||
public class GTJAMarginCalculation : MarginCalculationBase
|
||
{
|
||
// 定义一个静态变量来保存类的实例
|
||
public static readonly GTJAMarginCalculation Instance;
|
||
|
||
static GTJAMarginCalculation()
|
||
{
|
||
Instance = new GTJAMarginCalculation();
|
||
}
|
||
|
||
// 定义私有构造函数,使外界不能创建该类实例
|
||
protected GTJAMarginCalculation()
|
||
{
|
||
|
||
}
|
||
|
||
public override List<trade_span> RunMarginCalculation(RunMarginCalculationReq req)
|
||
{
|
||
if (req.tradeList == null || !req.tradeList.Any())
|
||
{
|
||
return new List<trade_span>();
|
||
}
|
||
|
||
//剔除收益互换交易
|
||
req.tradeList = req.tradeList.Where(t => t.TradeType != "收益互换").ToList();
|
||
|
||
var helper = new RunMarginCalculationHelper(req, _underlyingDataProvider);
|
||
|
||
var mpProvider = helper.GetMarginParamProvider(MarginParamTypeEnum.MarginRate | MarginParamTypeEnum.UpDownLimit);
|
||
|
||
//为了算客户角度的一个预付金数值
|
||
helper.ReverseTradeSide();
|
||
|
||
//商品类预付金计算
|
||
if (!PS.Config.ErpElement.IsStockMargin)
|
||
{
|
||
return RunMarginCalculationForCommodity(helper, mpProvider);
|
||
}
|
||
//权益类预付金计算
|
||
else
|
||
{
|
||
return new GTJAStockMarginCalculation(req, helper).Calculate(mpProvider);
|
||
}
|
||
}
|
||
|
||
//商品类预付金计算
|
||
private static List<trade_span> RunMarginCalculationForCommodity(RunMarginCalculationHelper helper, MarginParamProvider mpProvider, double extendLimitRate = 1, bool isUsePriceLimit = true, double? overrideVolRate = null)
|
||
{
|
||
var resultMap = new Dictionary<int, trade_span>();
|
||
|
||
helper.SetFieldsByTradeType();
|
||
|
||
helper.GetUpDownLimitPrices(out var upLimitPrices, out var downLimitPrices, extendLimitRate: extendLimitRate, isUsePriceLimit: isUsePriceLimit);
|
||
|
||
helper.GetTradVolRateDic(out var tradeVolRateDic);
|
||
if (overrideVolRate != null)
|
||
{
|
||
tradeVolRateDic = helper.req.tradeList.Select(t => new
|
||
{
|
||
tradeId = t.id,
|
||
volRate = overrideVolRate.Value
|
||
}).ToDictionary(d => d.tradeId, d => d.volRate);
|
||
}
|
||
|
||
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: 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 "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 TradeSpanTemplate { trade = trade, tradeSpan = tradeSpan, marginTemplateId = tradetemplate == null ? 0 : tradetemplate.MarginTemplateId }).ToList();
|
||
|
||
var tradeSpanInfoOtherSide = (from tradeSpan in req.tradeSpansOtherSide
|
||
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 TradeSpanTemplate { trade = trade, tradeSpan = tradeSpan, marginTemplateId = tradetemplate == null ? 0 : tradetemplate.MarginTemplateId }).ToList();
|
||
|
||
//商品类预付金计算
|
||
if (!PS.Config.ErpElement.IsStockMargin)
|
||
{
|
||
if (req.tradeSpans != null && req.tradeSpans.Count > 0)
|
||
{
|
||
var clientGroups = tradeSpanInfo.Where(g => (g.trade.TradeType != "收益互换" || (g.trade.TradeType == "收益互换" && g.trade.IsGroup != 2))).GroupBy(t => t.trade.ClientId);
|
||
var tradeIdListAll = tradeSpanInfo.Select(x => x.trade.id).ToArray();
|
||
var tradeSpans = db.trade_span.Where(x => tradeIdListAll.Contains(x.TradeId)).ToList();
|
||
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).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 = tradeSpans.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,
|
||
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);
|
||
//收益互换单独计算 剔除多空组合子交易
|
||
clientSpan.SwapWorstCastClientPayable = clientGroup.Where(g => g.trade.TradeType == "收益互换" && (g.trade.IsGroup != 2)).Sum(g => g.tradeSpan.WorstCastClientPayable) * (-1);//收益互换预付金
|
||
clientSpan.SwapInitMargin = clientGroup.Where(g => g.trade.TradeType == "收益互换" && (g.trade.IsGroup != 2)).Sum(g => g.tradeSpan.SwapInitMargin) * (-1);//收益互换预付金
|
||
clientSpan.SwapWinLoss = clientGroup.Where(g => g.trade.TradeType == "收益互换" && (g.trade.IsGroup != 2)).Sum(g => g.tradeSpan.SwapWinLoss) * (-1);//收益互换预付金
|
||
//互换容忍部分预付金(此部分不占用预付金)
|
||
clientSpan.SwapUnMargin = clientGroup.Where(g => g.trade.TradeType == "收益互换" && (g.trade.IsGroup != 2) && g.tradeSpan.Margin == 0).Sum(g => Math.Max(g.tradeSpan.SwapWinLoss ?? 0.0, 0)) * (-1);
|
||
|
||
clientSpanNews.Add(clientSpan);
|
||
}
|
||
|
||
//处理从客户角度的预付金计算(将交易买卖方向反向处理)
|
||
var clientGroupsOtherSide = tradeSpanInfoOtherSide.Where(g => (g.trade.TradeType != "收益互换" || (g.trade.TradeType == "收益互换" && g.trade.IsGroup != 2))).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
|
||
{
|
||
clientSpanNews = CalcEquityClientMargin(db, tradeSpanInfo, req, false);
|
||
if (tradeSpanInfoOtherSide.Any())
|
||
{
|
||
var clientSpanOthers = CalcEquityClientMargin(db, tradeSpanInfoOtherSide, req, true);
|
||
|
||
clientSpanNews.ForEach(clientSpan =>
|
||
{
|
||
var clientSpanOther = clientSpanOthers.FirstOrDefault(x => x.ClientId == clientSpan.ClientId);
|
||
clientSpan.OtherSideMargin = clientSpanOther.WorstCastClientPayable;
|
||
if (HasTwoSideMargin(clientSpan.ClientId))
|
||
{
|
||
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)}=@settleDate and {nameof(ClientSpan.SpanType)}={req.SpanType} and {nameof(ClientSpan.ModifiedFlag)}=0";
|
||
db.BulkDelete<ClientSpan>(sql, new { req.settleDate, ids = req.ClientIds });
|
||
}
|
||
else
|
||
{
|
||
var sql = $"{nameof(ClientSpan.ValueDate)}=@settleDate and {nameof(ClientSpan.SpanType)}={req.SpanType} and {nameof(ClientSpan.ModifiedFlag)}=0";
|
||
db.BulkDelete<ClientSpan>(sql, new { req.settleDate });
|
||
}
|
||
|
||
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;
|
||
}
|
||
}
|
||
|
||
private List<ClientSpan> CalcEquityClientMargin(YLContext db, List<TradeSpanTemplate> tradeSpanInfo, CalcClientMarginReq req, bool isForOtherSide)
|
||
{
|
||
var clientSpanNews = new List<ClientSpan>();
|
||
var defaultMarginTemplates = db.margin_template_v2.Where(x => x.IsDefault && !x.IsForClient && (x.MarginScenarios & MarginScenarioEnum.Position) > 0 && x.ValueDate <= req.settleDate).OrderByDescending(x => x.ValueDate).ToList();
|
||
var defaultMarginTemplateIds = defaultMarginTemplates.Select(x => x.id).ToArray();
|
||
var defaultMarginTemplateDetails = db.margin_template_detail.Where(x => defaultMarginTemplateIds.Contains(x.MarginTemplateId)).ToList();
|
||
var groupMarginTemplates = db.margin_template_v2.Where(x => !x.IsDefault && x.MarginType == (int)MarginTypeV2Enum.多腿 && !isForOtherSide && (x.MarginScenarios & MarginScenarioEnum.Position) > 0).ToList();
|
||
var groupMarginTemplateIds = groupMarginTemplates.Select(x => x.id).ToArray();
|
||
var groupMarginTemplateDetails = db.margin_template_detail.Where(x => groupMarginTemplateIds.Contains(x.MarginTemplateId)).ToList();
|
||
|
||
var clientGroups = tradeSpanInfo.Where(g => (g.trade.TradeType != "收益互换" || (g.trade.TradeType == "收益互换" && g.trade.IsGroup != 2))).GroupBy(t => t.trade.ClientId);
|
||
var clientIds = clientGroups.Select(x => x.Key).ToArray();
|
||
var tradeIdListAll = tradeSpanInfo.Select(x => x.trade.id).ToArray();
|
||
var tradeSpans = db.trade_span.Where(x => tradeIdListAll.Contains(x.TradeId) && x.ValueDate == req.settleDate).ToList();
|
||
var cmts = db.client_margin_template.Where(x => clientIds.Contains(x.ClientId) && x.ValueDate <= req.settleDate).ToList();
|
||
var cmtIds = cmts.Select(x => x.MarginTemplateId).ToArray();
|
||
var cMarginTemplates = db.margin_template_v2.Where(x => cmtIds.Contains(x.id) && (x.MarginScenarios & MarginScenarioEnum.Position) > 0).ToList();
|
||
var cMarginTemplateDetails = db.margin_template_detail.Where(x => cmtIds.Contains(x.MarginTemplateId)).ToList();
|
||
|
||
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 clientGroupSpan = clientGroup.Where(x => x.tradeSpan.IsSpanMargin == true);
|
||
var clientGroupOther = clientGroup.Where(x => x.tradeSpan.IsSpanMargin != true).ToList();
|
||
|
||
//交易员方向,正数代表交易员收预付金
|
||
var worstCastClientPayable = 0.0;
|
||
var groupTradeIds = new List<int>();
|
||
|
||
#region 默认组合的交易类型的交易处理
|
||
|
||
//客户级别默认预付金规则
|
||
var clientMarginTemplates = (from cmt in cmts.Where(x => x.ClientId == clientGroup.Key)
|
||
join mtv in cMarginTemplates
|
||
on cmt.MarginTemplateId equals mtv.id
|
||
select new { cmt, mtv }).ToList();
|
||
|
||
if (clientMarginTemplates.Any())
|
||
{
|
||
var valueDate = clientMarginTemplates.Max(x => x.cmt.ValueDate);
|
||
clientMarginTemplates = clientMarginTemplates.Where(x => x.cmt.ValueDate == valueDate).ToList();
|
||
clientMarginTemplates.ForEach(x =>
|
||
{
|
||
var clientGroupOtherBuySell = clientGroupOther.Where(y => ((y.trade.BuySell == "买入" ? BuySellEnum.Buy : BuySellEnum.Sell) & x.mtv.BuySellType) > 0 && y.marginTemplateId == 0 && x.mtv.TradeTypes.Contains(y.trade.TradeType));
|
||
|
||
var marginDetail = cMarginTemplateDetails.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.类香草预付金规则)
|
||
{
|
||
clientGroupOtherBuySell = clientGroupOtherBuySell.Where(y => y.trade.BuySell == "买入");
|
||
var tradeIds = clientGroupOtherBuySell.Select(x => x.trade.id);
|
||
clientGroupOther = clientGroupOther.Where(y => !tradeIds.Contains(y.trade.id)).ToList();
|
||
worstCastClientPayable += CalcVanillaGroup(tradeSpans, marginDetail, req, clientGroupOtherBuySell, x.mtv);
|
||
}
|
||
else if (x.mtv.RuleType == (int)MarginRuleTypeEnum.触发条件预付金规则1 && x.mtv.DefaultParameterType == (int)DefaultMarginParameterTypeEnum.个股)
|
||
{
|
||
clientGroupOtherBuySell = clientGroupOtherBuySell.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")));
|
||
var tradeIds = clientGroupOtherBuySell.Select(x => x.trade.id);
|
||
clientGroupOther = clientGroupOther.Where(y => !tradeIds.Contains(y.trade.id)).ToList();
|
||
worstCastClientPayable += CalcSellSnowBallAndAutoCallGroup(tradeSpans, marginDetail, req, clientGroupOtherBuySell, x.mtv);
|
||
}
|
||
else if (x.mtv.RuleType == (int)MarginRuleTypeEnum.触发条件预付金规则2 && x.mtv.DefaultParameterType == (int)DefaultMarginParameterTypeEnum.个股)
|
||
{
|
||
clientGroupOtherBuySell = clientGroupOtherBuySell.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")));
|
||
var tradeIds = clientGroupOtherBuySell.Select(x => x.trade.id);
|
||
clientGroupOther = clientGroupOther.Where(y => !tradeIds.Contains(y.trade.id)).ToList();
|
||
worstCastClientPayable += CalcSellSnowBallAndAutoCallGroup2(tradeSpans, marginDetail, req, clientGroupOtherBuySell, x.mtv);
|
||
}
|
||
else if ((x.mtv.RuleType == (int)MarginRuleTypeEnum.触发条件预付金规则1 || x.mtv.RuleType == (int)MarginRuleTypeEnum.触发条件预付金规则2) && x.mtv.DefaultParameterType == (int)DefaultMarginParameterTypeEnum.指数)
|
||
{
|
||
//指数类交易
|
||
var clientGroupIndex = clientGroupOtherBuySell.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")));
|
||
var tradeIds = clientGroupIndex.Select(x => x.trade.id);
|
||
clientGroupOther = clientGroupOther.Where(y => !tradeIds.Contains(y.trade.id)).ToList();
|
||
|
||
worstCastClientPayable += clientGroupIndex.Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
||
}
|
||
else if (x.mtv.RuleType == (int)MarginRuleTypeEnum.触发条件预付金规则3)
|
||
{
|
||
var tradeIds = clientGroupOtherBuySell.Select(x => x.trade.id);
|
||
clientGroupOther = clientGroupOther.Where(y => !tradeIds.Contains(y.trade.id)).ToList();
|
||
worstCastClientPayable += CalcSellSnowBallAndAutoCallGroup3(tradeSpans, marginDetail, req, clientGroupOtherBuySell, x.mtv);
|
||
}
|
||
else
|
||
{
|
||
var tradeIds = clientGroupOtherBuySell.Select(x => x.trade.id);
|
||
clientGroupOther = clientGroupOther.Where(y => !tradeIds.Contains(y.trade.id)).ToList();
|
||
worstCastClientPayable += clientGroupOtherBuySell.Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
||
}
|
||
});
|
||
}
|
||
|
||
//系统默认预付金规则
|
||
defaultMarginTemplates.ForEach(x =>
|
||
{
|
||
var marginDetail = defaultMarginTemplateDetails.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 clientGroupOtherFilter = clientGroupOther.Where(y => x.TradeTypes.Contains(y.trade.TradeType) && y.trade.BuySell == "买入" && y.marginTemplateId == 0);
|
||
var tradeIds = clientGroupOtherFilter.Select(x => x.trade.id);
|
||
clientGroupOther = clientGroupOther.Where(y => !tradeIds.Contains(y.trade.id)).ToList();
|
||
|
||
worstCastClientPayable += CalcVanillaGroup(tradeSpans, marginDetail, req, clientGroupOtherFilter, x);
|
||
}
|
||
else if (x.RuleType == (int)MarginRuleTypeEnum.触发条件预付金规则1 && x.DefaultParameterType == (int)DefaultMarginParameterTypeEnum.个股)
|
||
{
|
||
var clientGroupOtherFilter = 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.marginTemplateId == 0 && x.TradeTypes.Contains(y.trade.TradeType));
|
||
var tradeIds = clientGroupOtherFilter.Select(x => x.trade.id);
|
||
clientGroupOther = clientGroupOther.Where(y => !tradeIds.Contains(y.trade.id)).ToList();
|
||
worstCastClientPayable += CalcSellSnowBallAndAutoCallGroup(tradeSpans, marginDetail, req, clientGroupOtherFilter, x);
|
||
}
|
||
else if (x.RuleType == (int)MarginRuleTypeEnum.触发条件预付金规则2 && x.DefaultParameterType == (int)DefaultMarginParameterTypeEnum.个股)
|
||
{
|
||
var clientGroupOtherFilter = 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.marginTemplateId == 0 && x.TradeTypes.Contains(y.trade.TradeType));
|
||
var tradeIds = clientGroupOtherFilter.Select(x => x.trade.id);
|
||
clientGroupOther = clientGroupOther.Where(y => !tradeIds.Contains(y.trade.id)).ToList();
|
||
worstCastClientPayable += CalcSellSnowBallAndAutoCallGroup2(tradeSpans, marginDetail, req, clientGroupOtherFilter, x);
|
||
}
|
||
else if ((x.RuleType == (int)MarginRuleTypeEnum.触发条件预付金规则1 || x.RuleType == (int)MarginRuleTypeEnum.触发条件预付金规则2) && x.DefaultParameterType == (int)DefaultMarginParameterTypeEnum.指数)
|
||
{
|
||
//指数类交易
|
||
var clientGroupOtherFilter = 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.marginTemplateId == 0 && x.TradeTypes.Contains(y.trade.TradeType));
|
||
var tradeIds = clientGroupOtherFilter.Select(x => x.trade.id);
|
||
clientGroupOther = clientGroupOther.Where(y => !tradeIds.Contains(y.trade.id)).ToList();
|
||
worstCastClientPayable += clientGroupOtherFilter.Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
||
}
|
||
else if (x.RuleType == (int)MarginRuleTypeEnum.触发条件预付金规则3)
|
||
{
|
||
var clientGroupOtherFilter = clientGroupOther.Where(y => y.marginTemplateId == 0 && x.TradeTypes.Contains(y.trade.TradeType));
|
||
var tradeIds = clientGroupOtherFilter.Select(x => x.trade.id);
|
||
clientGroupOther = clientGroupOther.Where(y => !tradeIds.Contains(y.trade.id)).ToList();
|
||
worstCastClientPayable += CalcSellSnowBallAndAutoCallGroup3(tradeSpans, marginDetail, req, clientGroupOtherFilter, x);
|
||
}
|
||
});
|
||
|
||
worstCastClientPayable += clientGroupOther.Where(y => y.marginTemplateId == 0).Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
||
|
||
#endregion
|
||
|
||
#region 自定义组合预付金规则
|
||
|
||
groupMarginTemplates.ForEach(x =>
|
||
{
|
||
var marginDetail = groupMarginTemplateDetails.Where(y => y.MarginTemplateId == x.id && y.ValueDate <= req.settleDate).OrderByDescending(y => y.ValueDate).FirstOrDefault();
|
||
//非自定义预付金的交易
|
||
var tradeSpanInfoGroup = clientGroupOther.Where(y => y.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.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 groupSpotPriceWithNotional = tradeSpanInfoGroup.Sum(y => (y.trade.SpotPrice * y.trade.Notional) ?? 0);
|
||
var groupStrikeWithNotional = tradeSpanInfoGroup.Sum(y => (y.trade.ActualStrike * y.trade.Notional) ?? 0);
|
||
//计算单一客户当日标的整体跌跌幅时需要判断单个标的的跌幅是否超过上限
|
||
var groupPriceWithNotional = tradeSpanInfoGroup.Sum(y => (((y.tradeSpan.UnderlyingPrice / y.trade.SpotPrice - 1) > 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 (marginDetail.IsUseStrike && groupPriceWithNotional / groupStrikeWithNotional <= (1 - redeemPriceLimit))
|
||
{
|
||
groupMargins = groupStockEqvNotional * redeemRatio1 + groupStrikeStockEqvNotional * (1 - groupPriceWithNotional / groupStrikeWithNotional);
|
||
worstCastClientPayable += groupMargins;
|
||
}
|
||
else if (!marginDetail.IsUseStrike && groupPriceWithNotional / groupSpotPriceWithNotional <= (1 - redeemPriceLimit))
|
||
{
|
||
groupMargins = groupStockEqvNotional * redeemRatio1 + groupStockEqvNotional * (1 - groupPriceWithNotional / groupSpotPriceWithNotional);
|
||
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 = tradeSpans.Where(y => tradeIdList.Contains(y.TradeId)).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 groupSpotPriceWithNotional = tradeSpanInfoGroup.Sum(y => (y.trade.SpotPrice * y.trade.Notional) ?? 0);
|
||
var groupStrikeWithNotional = tradeSpanInfoGroup.Sum(y => (y.trade.ActualStrike * y.trade.Notional) ?? 0);
|
||
//计算单一客户当日标的整体跌跌幅时需要判断单个标的的跌幅是否超过上限
|
||
var groupPriceWithNotional = tradeSpanInfoGroup.Sum(y => (((y.tradeSpan.UnderlyingPrice / y.trade.SpotPrice - 1) > 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 (marginDetail.IsUseStrike && groupPriceWithNotional / groupStrikeWithNotional <= (1 - redeemPriceLimit))
|
||
{
|
||
groupMargins = groupStockEqvNotionalNow * redeemRatio1 + groupStrikeStockEqvNotional * (1 - groupPriceWithNotional / groupStrikeWithNotional);
|
||
worstCastClientPayable += groupMargins;
|
||
}
|
||
else if (!marginDetail.IsUseStrike && groupPriceWithNotional / groupSpotPriceWithNotional <= (1 - redeemPriceLimit))
|
||
{
|
||
groupMargins = groupStockEqvNotionalNow * redeemRatio1 + groupStockEqvNotional * (1 - groupPriceWithNotional / groupSpotPriceWithNotional);
|
||
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 = tradeSpans.Where(y => tradeIdList.Contains(y.TradeId)).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 groupSpotPriceWithNotional = tradeSpanInfoGroup.Sum(y => (y.trade.SpotPrice * y.trade.Notional) ?? 0);
|
||
var groupStrikeWithNotional = tradeSpanInfoGroup.Sum(y => (y.trade.ActualStrike * y.trade.Notional) ?? 0);
|
||
//计算单一客户当日标的整体涨跌跌幅时需要判断单个标的的涨跌幅是否超过上限
|
||
var groupPriceWithNotional = tradeSpanInfoGroup.Sum(y => (((y.tradeSpan.UnderlyingPrice / y.trade.SpotPrice - 1) > redeemPriceMax && marginDetail.PriceLimitType == 0 || (1 - y.tradeSpan.UnderlyingPrice / y.trade.SpotPrice) > 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 (marginDetail.IsUseStrike && 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 if (!marginDetail.IsUseStrike && groupSpotPriceWithNotional > 0)
|
||
{
|
||
var times = 0.0;
|
||
if (marginDetail.MarginRatio2 > 0)
|
||
{
|
||
if (marginDetail.PriceLimitType == 0)
|
||
{
|
||
times = Math.Floor(((1 - groupPriceWithNotional / groupSpotPriceWithNotional) - redeemPriceLimit) / marginDetail.MarginRatio2.Value);
|
||
}
|
||
else
|
||
{
|
||
times = Math.Floor(((groupPriceWithNotional / groupSpotPriceWithNotional - 1) - redeemPriceLimit) / marginDetail.MarginRatio2.Value);
|
||
}
|
||
}
|
||
|
||
if (marginDetail.UsePositionStockEqvNotional)
|
||
{
|
||
if (marginDetail.MarginRatio2 > 0 && groupPriceWithNotional / groupSpotPriceWithNotional <= (1 - redeemPriceLimit) && marginDetail.PriceLimitType == 0 || groupPriceWithNotional / groupSpotPriceWithNotional >= (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 / groupSpotPriceWithNotional <= (1 - redeemPriceLimit) && marginDetail.PriceLimitType == 0 || groupPriceWithNotional / groupSpotPriceWithNotional >= (1 + redeemPriceLimit) && marginDetail.PriceLimitType == 1)
|
||
{
|
||
groupMargins = groupStockEqvNotional * redeemRatio2;
|
||
worstCastClientPayable += groupMargins;
|
||
}
|
||
else
|
||
{
|
||
groupMargins = groupStockEqvNotional * redeemRatio;
|
||
worstCastClientPayable += groupMargins;
|
||
}
|
||
}
|
||
|
||
if (marginDetail.MarginRatio2 > 0 && groupPriceWithNotional / groupSpotPriceWithNotional <= (1 - redeemPriceLimit) && marginDetail.PriceLimitType == 0 || groupPriceWithNotional / groupSpotPriceWithNotional >= (1 + redeemPriceLimit) && marginDetail.PriceLimitType == 1)
|
||
{
|
||
worstCastClientPayable += groupStockEqvNotional * (redeemPriceLimit + marginDetail.MarginRatio2.Value * times);
|
||
groupMargins += groupStockEqvNotional * (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 = tradeSpans.Where(y => tradeIdList.Contains(y.TradeId)).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.marginTemplateId != 0 && !groupTradeIds.Contains(x.trade.id)).Sum(x => x.tradeSpan.WorstCastClientPayable ?? 0);
|
||
|
||
//span算法合计预付金
|
||
if (clientGroupSpan.Any())
|
||
{
|
||
var clientSpanWorstSum = 0d;
|
||
var underlyingGroupSpan = clientGroupSpan.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),
|
||
InitialMargin = t.Sum(g => g.tradeSpan.InitialMargin) * (-1)
|
||
}).ToList();
|
||
foreach (var item in underlyingGroupSpan)
|
||
{
|
||
//客户角度
|
||
item.WorstCastClientPayable = Math.Min(Math.Min(Math.Min(item.Spv1 ?? 0, item.Spv2 ?? 0), item.Spv3 ?? 0), item.Spv4 ?? 0);
|
||
if (!isForOtherSide)
|
||
{
|
||
item.WorstCastClientPayable = Math.Min(item.WorstCastClientPayable ?? 0, item.InitialMargin ?? 0);
|
||
}
|
||
|
||
//交易员角度
|
||
clientSpanWorstSum += -(item.WorstCastClientPayable ?? 0);
|
||
|
||
#region 更新tradeSpan,使得每笔交易的持仓预付金和客户预付金计算用的Spv组保持一致
|
||
|
||
if(!isForOtherSide)
|
||
{
|
||
var tradeIdList = clientGroupSpan.Select(x => x.tradeSpan.TradeId);
|
||
var tradeSpansUpdate = tradeSpans.Where(x => tradeIdList.Contains(x.TradeId) && x.ClientId == item.ClientId && x.UnderlyingId == item.UnderlyingId).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);
|
||
}
|
||
else if (item.WorstCastClientPayable == item.InitialMargin)
|
||
{
|
||
tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.InitialMargin);
|
||
tradeSpansReq.ForEach(x => x.WorstCastClientPayable = x.InitialMargin);
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
|
||
if (HasTwoSideMargin(clientGroup.Key))
|
||
{
|
||
worstCastClientPayable += clientSpanWorstSum;
|
||
}
|
||
else
|
||
{
|
||
worstCastClientPayable += Math.Max(clientSpanWorstSum, 0);
|
||
}
|
||
}
|
||
|
||
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 = Math.Min(-worstCastClientPayable, 0),
|
||
MySideMargin = Math.Min(-worstCastClientPayable, 0),
|
||
DeltaMargin = deltaMargin,
|
||
SwapWorstCastClientPayable = clientGroup.Where(g => g.trade.TradeType == "收益互换" && (g.trade.IsGroup != 2)).Sum(t => t.tradeSpan.WorstCastClientPayable) * (-1),
|
||
OptId = req.userId,
|
||
OptName = req.userName,
|
||
OptDate = DateTime.Now,
|
||
SpanType = req.SpanType,
|
||
SwapInitMargin = clientGroup.Where(g => g.trade.TradeType == "收益互换" && (g.trade.IsGroup != 2)).Sum(t => t.tradeSpan.SwapInitMargin) * (-1),
|
||
SwapWinLoss = clientGroup.Where(g => g.trade.TradeType == "收益互换" && (g.trade.IsGroup != 2)).Sum(t => t.tradeSpan.SwapWinLoss) * (-1),
|
||
//互换容忍部分预付金(此部分不占用预付金)
|
||
SwapUnMargin = clientGroup.Where(g => g.trade.TradeType == "收益互换" && (g.trade.IsGroup != 2) && g.tradeSpan.Margin == 0).Sum(g => Math.Max(g.tradeSpan.SwapWinLoss ?? 0.0, 0)) * (-1)
|
||
};
|
||
|
||
clientSpanNews.Add(clientSpan);
|
||
}
|
||
|
||
return clientSpanNews;
|
||
}
|
||
|
||
private double CalcVanillaGroup(List<trade_span> tradeSpans, margin_template_detail marginDetail, CalcClientMarginReq req, IEnumerable<TradeSpanTemplate> clientGroupOther, margin_template_v2 mtv)
|
||
{
|
||
double worstCastClientPayable = 0;
|
||
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 => y.tradeSpan.IsSingleMargin != true);
|
||
//自定义预付金
|
||
var tradeVanillaSpanInfoSingle = clientGroupOther.Where(y => 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 = tradeSpans.Where(y => tradeIdList.Contains(y.TradeId)).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);
|
||
|
||
return worstCastClientPayable;
|
||
}
|
||
|
||
private double CalcSellSnowBallAndAutoCallGroup(List<trade_span> tradeSpans, margin_template_detail marginDetail, CalcClientMarginReq req, IEnumerable<TradeSpanTemplate> clientGroupOther, margin_template_v2 mtv)
|
||
{
|
||
double worstCastClientPayable = 0;
|
||
|
||
//初始预付金率
|
||
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 = clientGroupOther.Where(y => (y.trade.BuySell == "卖出" && y.tradeSpan.IsSingleMargin == true || y.trade.BuySell == "买入"));
|
||
var tradeSpanInfoGroup = clientGroupOther.Where(y => y.trade.BuySell == "卖出" && y.tradeSpan.IsSingleMargin != true);
|
||
if (tradeSpanInfoGroup.Select(y => y.trade.UnderlyingId).Distinct().Count() >= redeemAmount)
|
||
{
|
||
double groupMargins = 0;
|
||
var groupSpotPriceWithNotional = tradeSpanInfoGroup.Sum(y => (y.trade.SpotPrice * y.trade.Notional) ?? 0);
|
||
var groupStrikeWithNotional = tradeSpanInfoGroup.Sum(y => (y.trade.ActualStrike * y.trade.Notional) ?? 0);
|
||
//计算单一客户当日标的整体跌跌幅时需要判断单个标的的跌幅是否超过上限
|
||
var groupPriceWithNotional = tradeSpanInfoGroup.Sum(y => (((y.tradeSpan.UnderlyingPrice / y.trade.SpotPrice - 1) > 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 (marginDetail.IsUseStrike && groupPriceWithNotional / groupStrikeWithNotional <= (1 - redeemPriceLimit))
|
||
{
|
||
groupMargins = groupStockEqvNotional * redeemRatio1 + groupStrikeStockEqvNotional * (1 - groupPriceWithNotional / groupStrikeWithNotional);
|
||
worstCastClientPayable += groupMargins;
|
||
}
|
||
else if (!marginDetail.IsUseStrike && groupPriceWithNotional / groupSpotPriceWithNotional <= (1 - redeemPriceLimit))
|
||
{
|
||
groupMargins = groupStockEqvNotional * redeemRatio1 + groupStockEqvNotional * (1 - groupPriceWithNotional / groupSpotPriceWithNotional);
|
||
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 = tradeSpans.Where(y => tradeIdList.Contains(y.TradeId)).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.Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
||
}
|
||
|
||
return worstCastClientPayable;
|
||
}
|
||
|
||
private double CalcSellSnowBallAndAutoCallGroup2(List<trade_span> tradeSpans, margin_template_detail marginDetail, CalcClientMarginReq req, IEnumerable<TradeSpanTemplate> clientGroupOther, margin_template_v2 mtv)
|
||
{
|
||
double worstCastClientPayable = 0;
|
||
|
||
//初始预付金率
|
||
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 = clientGroupOther.Where(y => (y.trade.BuySell == "卖出" && y.tradeSpan.IsSingleMargin == true || y.trade.BuySell == "买入"));
|
||
var tradeSpanInfoGroup = clientGroupOther.Where(y => y.trade.BuySell == "卖出" && y.tradeSpan.IsSingleMargin != true);
|
||
if (tradeSpanInfoGroup.Select(y => y.trade.UnderlyingId).Distinct().Count() >= redeemAmount)
|
||
{
|
||
double groupMargins = 0;
|
||
var groupSpotPriceWithNotional = tradeSpanInfoGroup.Sum(y => (y.trade.SpotPrice * y.trade.Notional) ?? 0);
|
||
var groupStrikeWithNotional = tradeSpanInfoGroup.Sum(y => (y.trade.ActualStrike * y.trade.Notional) ?? 0);
|
||
//计算单一客户当日标的整体跌跌幅时需要判断单个标的的跌幅是否超过上限
|
||
var groupPriceWithNotional = tradeSpanInfoGroup.Sum(y => (((y.tradeSpan.UnderlyingPrice / y.trade.SpotPrice - 1) > 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 (marginDetail.IsUseStrike && groupPriceWithNotional / groupStrikeWithNotional <= (1 - redeemPriceLimit))
|
||
{
|
||
groupMargins = groupStockEqvNotionalNow * redeemRatio1 + groupStrikeStockEqvNotional * (1 - groupPriceWithNotional / groupStrikeWithNotional);
|
||
worstCastClientPayable += groupMargins;
|
||
}
|
||
else if (!marginDetail.IsUseStrike && groupPriceWithNotional / groupSpotPriceWithNotional <= (1 - redeemPriceLimit))
|
||
{
|
||
groupMargins = groupStockEqvNotionalNow * redeemRatio1 + groupStockEqvNotional * (1 - groupPriceWithNotional / groupSpotPriceWithNotional);
|
||
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 = tradeSpans.Where(y => tradeIdList.Contains(y.TradeId)).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.Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
||
}
|
||
|
||
return worstCastClientPayable;
|
||
}
|
||
|
||
private double CalcSellSnowBallAndAutoCallGroup3(List<trade_span> tradeSpans, margin_template_detail marginDetail, CalcClientMarginReq req, IEnumerable<TradeSpanTemplate> clientGroupOther, margin_template_v2 mtv)
|
||
{
|
||
double worstCastClientPayable = 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 redeemAmount = marginDetail.PositionUnderlyingAmount ?? 0;
|
||
var tradeSpanInfoGroupSingle = clientGroupOther.Where(y => (y.trade.BuySell == "卖出" && y.tradeSpan.IsSingleMargin == true || y.trade.BuySell == "买入"));
|
||
var tradeSpanInfoGroup = clientGroupOther.Where(y => y.trade.BuySell == "卖出" && y.tradeSpan.IsSingleMargin != true);
|
||
var groupSpotPriceWithNotional = tradeSpanInfoGroup.Sum(y => (y.trade.SpotPrice * y.trade.Notional) ?? 0);
|
||
var groupStrikeWithNotional = tradeSpanInfoGroup.Sum(y => (y.trade.ActualStrike * y.trade.Notional) ?? 0);
|
||
//计算单一客户当日标的整体涨跌跌幅时需要判断单个标的的涨跌幅是否超过上限
|
||
var groupPriceWithNotional = tradeSpanInfoGroup.Sum(y => (((y.tradeSpan.UnderlyingPrice / y.trade.SpotPrice - 1) > redeemPriceMax && marginDetail.PriceLimitType == 0 || (1 - y.tradeSpan.UnderlyingPrice / y.trade.SpotPrice) > 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 (marginDetail.IsUseStrike && 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 if (!marginDetail.IsUseStrike && groupSpotPriceWithNotional > 0)
|
||
{
|
||
var times = 0.0;
|
||
if (marginDetail.MarginRatio2 > 0)
|
||
{
|
||
if (marginDetail.PriceLimitType == 0)
|
||
{
|
||
times = Math.Floor(((1 - groupPriceWithNotional / groupSpotPriceWithNotional) - redeemPriceLimit) / marginDetail.MarginRatio2.Value);
|
||
}
|
||
else
|
||
{
|
||
times = Math.Floor(((groupPriceWithNotional / groupSpotPriceWithNotional - 1) - redeemPriceLimit) / marginDetail.MarginRatio2.Value);
|
||
}
|
||
}
|
||
|
||
if (marginDetail.UsePositionStockEqvNotional)
|
||
{
|
||
if (marginDetail.MarginRatio2 > 0 && groupPriceWithNotional / groupSpotPriceWithNotional <= (1 - redeemPriceLimit) && marginDetail.PriceLimitType == 0 || groupPriceWithNotional / groupSpotPriceWithNotional >= (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 / groupSpotPriceWithNotional <= (1 - redeemPriceLimit) && marginDetail.PriceLimitType == 0 || groupPriceWithNotional / groupSpotPriceWithNotional >= (1 + redeemPriceLimit) && marginDetail.PriceLimitType == 1)
|
||
{
|
||
groupMargins = groupStockEqvNotional * redeemRatio2;
|
||
worstCastClientPayable += groupMargins;
|
||
}
|
||
else
|
||
{
|
||
groupMargins = groupStockEqvNotional * redeemRatio;
|
||
worstCastClientPayable += groupMargins;
|
||
}
|
||
}
|
||
|
||
if (marginDetail.MarginRatio2 > 0 && groupPriceWithNotional / groupSpotPriceWithNotional <= (1 - redeemPriceLimit) && marginDetail.PriceLimitType == 0 || groupPriceWithNotional / groupSpotPriceWithNotional >= (1 + redeemPriceLimit) && marginDetail.PriceLimitType == 1)
|
||
{
|
||
worstCastClientPayable += groupStockEqvNotional * (redeemPriceLimit + marginDetail.MarginRatio2.Value * times);
|
||
groupMargins += groupStockEqvNotional * (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 = tradeSpans.Where(y => tradeIdList.Contains(y.TradeId)).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.Sum(y => y.tradeSpan.WorstCastClientPayable ?? 0);
|
||
}
|
||
|
||
return worstCastClientPayable;
|
||
}
|
||
|
||
class TradeSpanTemplate
|
||
{
|
||
public trade trade { get; set; }
|
||
public trade_span tradeSpan { get; set; }
|
||
public int marginTemplateId { get; set; }
|
||
}
|
||
|
||
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 && req.trade.TradeType != "收益互换")
|
||
{
|
||
marginReq.tradeList = db.trade.Where(x => x.ParentTradeId == req.realTradeId).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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 权益类预付金计算
|
||
/// </summary>
|
||
class GTJAStockMarginCalculation
|
||
{
|
||
readonly RunMarginCalculationReq _req;
|
||
|
||
readonly RunMarginCalculationHelper _helper;
|
||
|
||
public GTJAStockMarginCalculation(RunMarginCalculationReq req, RunMarginCalculationHelper helper)
|
||
{
|
||
_req = req;
|
||
_helper = helper;
|
||
}
|
||
|
||
//权益类预付金计算
|
||
public List<trade_span> Calculate(MarginParamProvider mpProvider)
|
||
{
|
||
var tradeSpans = new List<trade_span>(_req.tradeList.Count);
|
||
|
||
using (var db = new YLContext())
|
||
{
|
||
var tradeIds = _req.tradeList.Select(x => x.id);
|
||
if (_req.CalcMarginType == CalcMarginTypeEnum.InitialMargin)
|
||
{
|
||
tradeIds = new List<int> { _req.realTradeId };
|
||
}
|
||
var tradeMarginTemplates = db.trade_margin_template.Where(x => tradeIds.Contains(x.TradeId)).ToList();
|
||
var tradeMarginTemplateIds = tradeMarginTemplates.Select(x => x.MarginTemplateId).ToArray();
|
||
var marginTemplates = db.margin_template_v2.Where(x => tradeMarginTemplateIds.Contains(x.id)).ToList();
|
||
var marginTemplateDetails = db.margin_template_detail.Where(x => tradeMarginTemplateIds.Contains(x.MarginTemplateId)).ToList();
|
||
|
||
var clientIds = _req.tradeList.Select(x => x.ClientId).Distinct();
|
||
var cmts = db.client_margin_template.Where(x => clientIds.Contains(x.ClientId) && x.ValueDate <= _req.settleDate).OrderByDescending(x => x.ValueDate).ToList();
|
||
var cmtIds = cmts.Select(x => x.MarginTemplateId).ToArray();
|
||
var cMarginTemplates = db.margin_template_v2.Where(x => cmtIds.Contains(x.id) && (x.MarginScenarios & (_req.CalcMarginType == CalcMarginTypeEnum.InitialMargin ? MarginScenarioEnum.Initial : MarginScenarioEnum.Position)) > 0 && x.IsValid).ToList();
|
||
var cMarginTemplateDetails = db.margin_template_detail.Where(x => cmtIds.Contains(x.MarginTemplateId)).ToList();
|
||
|
||
var defaultMarginTemplates = db.margin_template_v2.Where(x => x.IsDefault && !x.IsForClient && x.ValueDate <= _req.settleDate && (x.MarginScenarios & (_req.CalcMarginType == CalcMarginTypeEnum.InitialMargin ? MarginScenarioEnum.Initial : MarginScenarioEnum.Position)) > 0 && x.IsValid).OrderByDescending(x => x.ValueDate).ToList();
|
||
var defaultMarginTemplateIds = defaultMarginTemplates.Select(x => x.id).ToArray();
|
||
var defaultMarginTemplateDetails = db.margin_template_detail.Where(x => defaultMarginTemplateIds.Contains(x.MarginTemplateId)).ToList();
|
||
|
||
List<trade> tradesICIHIF = new List<trade>();
|
||
|
||
foreach (var td in _req.tradeList)
|
||
{
|
||
var underlyingType = GetUnderlyingType(td);
|
||
var span = new trade_span();
|
||
|
||
if (_helper.GetSpecialMargin(td, 0, out var value))
|
||
{
|
||
span = _helper.CreateTradeSpan(td, null);
|
||
span.WorstCastClientPayable = value;
|
||
span.IsSingleMargin = true;
|
||
}
|
||
else
|
||
{
|
||
//设置了特殊的规则
|
||
var tradeMarginTemplate = tradeMarginTemplates.Where(x => x.TradeId == td.id && x.ValueDate <= _req.settleDate).OrderByDescending(x => x.ValueDate).FirstOrDefault();
|
||
|
||
//由于初始预付金计算时,td.id会被置0,所以需要根据realTradeId找到对应的特殊模板
|
||
if (tradeMarginTemplate == null && _req.tradeList.Count() == 1 && _req.CalcMarginType == CalcMarginTypeEnum.InitialMargin)
|
||
{
|
||
tradeMarginTemplate = db.trade_margin_template.Where(x => x.TradeId == _req.realTradeId && x.ValueDate <= _req.settleDate).OrderByDescending(x => x.ValueDate).FirstOrDefault();
|
||
}
|
||
|
||
if (tradeMarginTemplate != null && !_req.forOtherSide)
|
||
{
|
||
var marginTemplate = marginTemplates.FirstOrDefault(x => x.id == tradeMarginTemplate.MarginTemplateId);
|
||
if (marginTemplate == null)
|
||
{
|
||
throw new Exception(string.Format("{0}未找到配置的预付金模板", td.TradeNumber));
|
||
}
|
||
|
||
var detail = marginTemplateDetails.Where(x => x.MarginTemplateId == tradeMarginTemplate.MarginTemplateId && x.ValueDate <= _req.settleDate).OrderByDescending(x => x.ValueDate).FirstOrDefault();
|
||
if (detail == null && (marginTemplate.RuleType != (int)MarginRuleTypeEnum.无预付金 && marginTemplate.RuleType != (int)MarginRuleTypeEnum.自动赎回买入预付金规则 && marginTemplate.RuleType != (int)MarginRuleTypeEnum.含赔付预付金计算规则 && marginTemplate.RuleType != (int)MarginRuleTypeEnum.保底预付金规则))
|
||
{
|
||
throw new Exception(string.Format("{0}在{1}未找到有效的预付金参数", td.TradeNumber, _req.settleDate.ToString("yyyy-MM-dd")));
|
||
}
|
||
|
||
switch (marginTemplate.RuleType)
|
||
{
|
||
case (int)MarginRuleTypeEnum.无预付金:
|
||
span = CalcAsNo(td);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.按固定利率:
|
||
span = CalcAsFixed(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.按浮动盈亏:
|
||
span = CalcAsFloat(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.类香草预付金规则:
|
||
span = CalcAsVanilla(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.自动赎回买入预付金规则:
|
||
span = CalcBuySnowBallAndAutoCall(td);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.触发条件预付金规则1:
|
||
span = CalcSellSnowBallAndAutoCall(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.触发条件预付金规则2:
|
||
span = CalcSellSnowBallAndAutoCall2(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.触发条件预付金规则3:
|
||
span = CalcSellSnowBallAndAutoCall3(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.保底预付金规则:
|
||
span = CalcSingleSnowBallAndAutoCall(td);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.含赔付预付金计算规则:
|
||
span = CalcBinary(td);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.买入自动赎回规则1:
|
||
span = CalcBuyAutoRedeem(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.卖出自动赎回规则1:
|
||
span = CalcSellAutoRedeem(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.标准SPAN:
|
||
_helper.req.tradeList = new List<trade> { td };
|
||
span = RunMarginCalculationForCommodity(_helper, mpProvider, detail.MarginRatio1 ?? 0, detail.IsUsePriceLimit, detail.MarginRatio2).FirstOrDefault();
|
||
span.InitialMargin = td.InitialMargin * td.StockEqvNotional / td.OriginalStockEqvNotional * detail.ComparedInitialMarginRate;
|
||
if (_helper.req.CalcMarginType != CalcMarginTypeEnum.InitialMargin && !_helper.req.forOtherSide)
|
||
{
|
||
span.WorstCastClientPayable = Math.Max(span.WorstCastClientPayable ?? 0, span.InitialMargin ?? 0);
|
||
}
|
||
else if (_helper.req.CalcMarginType == CalcMarginTypeEnum.InitialMargin && !new GTJAMarginCalculation().HasTwoSideMargin(td.ClientId))
|
||
{
|
||
span.WorstCastClientPayable = Math.Max(span.WorstCastClientPayable ?? 0, 0);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
//走默认规则
|
||
else if (td.TradeType != "现金流交易")
|
||
{
|
||
var variety = DataCacheProvider.GetVariety(td.UnderlyingCode);
|
||
|
||
var hasSpanTemplate = defaultMarginTemplates.Any(x => x.RuleType == (int)MarginRuleTypeEnum.标准SPAN);
|
||
//IC,IH,IF默认通过span算法
|
||
if (variety != null && new List<string>() { "IC", "IH", "IF" }.Contains(variety.VarietyCode) && !hasSpanTemplate)
|
||
{
|
||
tradesICIHIF.Add(td);
|
||
span = null;
|
||
}
|
||
else
|
||
{
|
||
var defaultParameterType = (int)DefaultMarginParameterTypeEnum.个股;
|
||
//指数
|
||
if (td.UnderlyingCode != null && (td.UnderlyingCode.StartsWith("000") && td.UnderlyingCode.EndsWith(".SH") || td.UnderlyingCode.StartsWith("39") || td.UnderlyingCode.StartsWith("IF") || td.UnderlyingCode.StartsWith("IH") || td.UnderlyingCode.StartsWith("IC")))
|
||
{
|
||
defaultParameterType = (int)DefaultMarginParameterTypeEnum.指数;
|
||
}
|
||
|
||
var clientMarginTemplates = (from cmt in cmts.Where(x => x.ClientId == td.ClientId)
|
||
join mtv in cMarginTemplates.Where(x => x.TradeTypes.Contains(td.TradeType) && (x.BuySellType & (td.BuySell == "买入" ? BuySellEnum.Buy : BuySellEnum.Sell)) > 0 && (x.DefaultParameterType == 0 || x.DefaultParameterType == defaultParameterType))
|
||
on cmt.MarginTemplateId equals mtv.id
|
||
select new { cmt, mtv }).ToList();
|
||
//客户默认预付金
|
||
if (clientMarginTemplates.Any())
|
||
{
|
||
var clientMarginTemplate = clientMarginTemplates.OrderByDescending(x => x.cmt.ValueDate).First();
|
||
|
||
var detail = cMarginTemplateDetails.Where(x => x.MarginTemplateId == clientMarginTemplate.mtv.id && x.ValueDate <= _req.settleDate && ((x.UnderlyingType & underlyingType) > 0 || x.UnderlyingType == UnderlyingTypeEnum.None)).OrderByDescending(x => x.ValueDate).FirstOrDefault();
|
||
if (detail == null && (clientMarginTemplate.mtv.RuleType != (int)MarginRuleTypeEnum.无预付金 && clientMarginTemplate.mtv.RuleType != (int)MarginRuleTypeEnum.自动赎回买入预付金规则 && clientMarginTemplate.mtv.RuleType != (int)MarginRuleTypeEnum.含赔付预付金计算规则 && clientMarginTemplate.mtv.RuleType != (int)MarginRuleTypeEnum.保底预付金规则))
|
||
{
|
||
throw new Exception(string.Format("{0}在{1}未找到有效的预付金参数", td.TradeNumber, _req.settleDate.ToString("yyyy-MM-dd")));
|
||
}
|
||
|
||
switch (clientMarginTemplate.mtv.RuleType)
|
||
{
|
||
case (int)MarginRuleTypeEnum.无预付金:
|
||
span = CalcAsNo(td);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.按固定利率:
|
||
span = CalcAsFixed(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.按浮动盈亏:
|
||
span = CalcAsFloat(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.类香草预付金规则:
|
||
span = CalcAsVanilla(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.触发条件预付金规则1:
|
||
span = CalcSnowBallAndAutoCall(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.触发条件预付金规则2:
|
||
span = CalcSnowBallAndAutoCall2(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.触发条件预付金规则3:
|
||
span = CalcSnowBallAndAutoCall3(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.含赔付预付金计算规则:
|
||
span = CalcBinary(td);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.买入自动赎回规则1:
|
||
span = CalcBuyAutoRedeem(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.卖出自动赎回规则1:
|
||
span = CalcSellAutoRedeem(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.标准SPAN:
|
||
_helper.req.tradeList = new List<trade> { td };
|
||
span = RunMarginCalculationForCommodity(_helper, mpProvider, detail.MarginRatio1 ?? 0, detail.IsUsePriceLimit, detail.MarginRatio2).FirstOrDefault();
|
||
span.InitialMargin = td.InitialMargin * td.StockEqvNotional / td.OriginalStockEqvNotional * detail.ComparedInitialMarginRate;
|
||
if (_helper.req.CalcMarginType != CalcMarginTypeEnum.InitialMargin && !_helper.req.forOtherSide)
|
||
{
|
||
span.WorstCastClientPayable = Math.Max(span.WorstCastClientPayable ?? 0, span.InitialMargin ?? 0);
|
||
}
|
||
else if (_helper.req.CalcMarginType == CalcMarginTypeEnum.InitialMargin && !new GTJAMarginCalculation().HasTwoSideMargin(td.ClientId))
|
||
{
|
||
span.WorstCastClientPayable = Math.Max(span.WorstCastClientPayable ?? 0, 0);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
//系统默认预付金
|
||
else
|
||
{
|
||
var defaultMarginTemplate = defaultMarginTemplates.FirstOrDefault(x => x.TradeTypes.Contains(td.TradeType) && (x.BuySellType & (td.BuySell == "买入" ? BuySellEnum.Buy : BuySellEnum.Sell)) > 0 && (x.DefaultParameterType == 0 || x.DefaultParameterType == defaultParameterType));
|
||
if (defaultMarginTemplate == null)
|
||
{
|
||
throw new Exception(string.Format("{0}未找到对应交易结构的默认预付金模板", td.TradeNumber));
|
||
}
|
||
|
||
var detail = defaultMarginTemplateDetails.Where(x => x.MarginTemplateId == defaultMarginTemplate.id && x.ValueDate <= _req.settleDate && ((x.UnderlyingType & underlyingType) > 0 || x.UnderlyingType == UnderlyingTypeEnum.None)).OrderByDescending(x => x.ValueDate).FirstOrDefault();
|
||
if (detail == null && (defaultMarginTemplate.RuleType != (int)MarginRuleTypeEnum.无预付金 && defaultMarginTemplate.RuleType != (int)MarginRuleTypeEnum.自动赎回买入预付金规则 && defaultMarginTemplate.RuleType != (int)MarginRuleTypeEnum.含赔付预付金计算规则 && defaultMarginTemplate.RuleType != (int)MarginRuleTypeEnum.保底预付金规则))
|
||
{
|
||
throw new Exception(string.Format("{0}在{1}未找到有效的预付金参数", td.TradeNumber, _req.settleDate.ToString("yyyy-MM-dd")));
|
||
}
|
||
|
||
switch (defaultMarginTemplate.RuleType)
|
||
{
|
||
case (int)MarginRuleTypeEnum.无预付金:
|
||
span = CalcAsNo(td);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.按固定利率:
|
||
span = CalcAsFixed(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.按浮动盈亏:
|
||
span = CalcAsFloat(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.类香草预付金规则:
|
||
span = CalcAsVanilla(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.触发条件预付金规则1:
|
||
span = CalcSnowBallAndAutoCall(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.触发条件预付金规则2:
|
||
span = CalcSnowBallAndAutoCall2(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.触发条件预付金规则3:
|
||
span = CalcSnowBallAndAutoCall3(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.含赔付预付金计算规则:
|
||
span = CalcBinary(td);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.买入自动赎回规则1:
|
||
span = CalcBuyAutoRedeem(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.卖出自动赎回规则1:
|
||
span = CalcSellAutoRedeem(td, detail);
|
||
break;
|
||
case (int)MarginRuleTypeEnum.标准SPAN:
|
||
_helper.req.tradeList = new List<trade> { td };
|
||
span = RunMarginCalculationForCommodity(_helper, mpProvider, detail.MarginRatio1 ?? 0, detail.IsUsePriceLimit, detail.MarginRatio2).FirstOrDefault();
|
||
span.InitialMargin = td.InitialMargin * td.StockEqvNotional / td.OriginalStockEqvNotional * detail.ComparedInitialMarginRate;
|
||
if (_helper.req.CalcMarginType != CalcMarginTypeEnum.InitialMargin && !_helper.req.forOtherSide)
|
||
{
|
||
span.WorstCastClientPayable = Math.Max(span.WorstCastClientPayable ?? 0, span.InitialMargin ?? 0);
|
||
}
|
||
else if(_helper.req.CalcMarginType == CalcMarginTypeEnum.InitialMargin && !new GTJAMarginCalculation().HasTwoSideMargin(td.ClientId))
|
||
{
|
||
span.WorstCastClientPayable = Math.Max(span.WorstCastClientPayable ?? 0, 0);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
span = null;
|
||
}
|
||
}
|
||
|
||
if (span != null)
|
||
{
|
||
var udm = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
|
||
udm.QuotationDate = _req.settleDate;
|
||
var positionVol = VolatilityHelper.GetTradeVol(td, _req.settleDate, _req.CalcMarginType == CalcMarginTypeEnum.EodMargin);
|
||
|
||
double closePrice;
|
||
if (_req.CalcMarginType == CalcMarginTypeEnum.InitialMargin)
|
||
{
|
||
closePrice = td.SpotPrice ?? 0;
|
||
}
|
||
else
|
||
{
|
||
new EodPriceProvider(_req.settleDate, false).TryGetEodPrice(td.UnderlyingCode, out var eodPrice);
|
||
|
||
closePrice = eodPrice?.ClosePrice ?? udm.Price ?? 0;
|
||
}
|
||
|
||
mpProvider.TryGetMarginRate(td.UnderlyingCode, out var marginRate);
|
||
|
||
var optionValue = CalcTradeValueResult(td, udm, closePrice);
|
||
|
||
span.Spv = optionValue.Pv;
|
||
span.Delta = optionValue.Delta;
|
||
span.UnderlyingPrice = closePrice;
|
||
span.DeltaMargin = (span.Delta * span.UnderlyingPrice * marginRate) ?? 0;
|
||
span.DeltaMargin = (Math.Abs(span.Delta ?? 0) * (td.BuySell == "买入" ? 1 : -1) * span.UnderlyingPrice * marginRate) ?? 0;
|
||
tradeSpans.Add(span);
|
||
}
|
||
}
|
||
|
||
//IC,IH,IF走span算法
|
||
if (tradesICIHIF.Any())
|
||
{
|
||
_helper.req.tradeList = tradesICIHIF;
|
||
var commodityTradeSpans = RunMarginCalculationForCommodity(_helper, mpProvider);
|
||
if (commodityTradeSpans.Any())
|
||
{
|
||
tradeSpans.AddRange(commodityTradeSpans);
|
||
}
|
||
}
|
||
}
|
||
|
||
return tradeSpans;
|
||
}
|
||
|
||
private UnderlyingTypeEnum GetUnderlyingType(trade td)
|
||
{
|
||
var startCode = td.UnderlyingCode.Substring(0, 3);
|
||
int startCodeNum = 0;
|
||
if (Int32.TryParse(startCode, out startCodeNum))
|
||
{
|
||
//市场为SH+标的代码以000开头;市场为SZ+标的代码以399开头;
|
||
if (startCodeNum == 0 && td.UnderlyingCode.EndsWith(".SH") || startCodeNum == 399 && td.UnderlyingCode.EndsWith(".SZ"))
|
||
{
|
||
return UnderlyingTypeEnum.Index;
|
||
}
|
||
//市场为SH + 标的代码以600~605、510~518开头;(主板股票、指数类开放式基金)
|
||
//市场为SZ + 标的代码以000~004、159开头(主板股票、ETF)
|
||
else if (td.UnderlyingCode.EndsWith(".SH") && (startCodeNum >= 600 && startCodeNum <= 605 || startCodeNum >= 510 && startCodeNum <= 518)
|
||
|| td.UnderlyingCode.EndsWith(".SZ") && (startCodeNum == 159 || startCodeNum >= 0 && startCodeNum <= 4))
|
||
{
|
||
return UnderlyingTypeEnum.StockApproval;
|
||
}
|
||
//市场为SH + 标的代码以688、588开头;(科创板股票、科创板ETF)
|
||
//市场为SZ + 标的代码以300~309开头;(创业板股票)
|
||
else if (td.UnderlyingCode.EndsWith(".SH") && (startCodeNum == 688 || startCodeNum == 588)
|
||
|| td.UnderlyingCode.EndsWith(".SZ") && startCodeNum >= 300 && startCodeNum <= 309)
|
||
{
|
||
return UnderlyingTypeEnum.StockNonApproval;
|
||
}
|
||
else
|
||
{
|
||
return UnderlyingTypeEnum.Other;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
return UnderlyingTypeEnum.Other;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 按无预付金
|
||
/// </summary>
|
||
/// <param name="td"></param>
|
||
/// <returns></returns>
|
||
private trade_span CalcAsNo(trade td)
|
||
{
|
||
var span = _helper.CreateTradeSpan(td, null);
|
||
span.WorstCastClientPayable = 0;
|
||
span.UnderlyingPrice = _req.PriceProvider.GetPrice(td.UnderlyingCode);
|
||
|
||
return span;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 按固定利率计算
|
||
/// </summary>
|
||
/// <param name="td"></param>
|
||
/// <param name="detail"></param>
|
||
/// <returns></returns>
|
||
private trade_span CalcAsFixed(trade td, margin_template_detail detail)
|
||
{
|
||
var span = _helper.CreateTradeSpan(td, null);
|
||
span.UnderlyingPrice = _req.PriceProvider.GetPrice(td.UnderlyingCode);
|
||
if (!detail.UsePositionStockEqvNotional)
|
||
{
|
||
span.WorstCastClientPayable = TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) * detail.MarginRatio2;
|
||
}
|
||
else
|
||
{
|
||
span.WorstCastClientPayable = TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) * span.UnderlyingPrice / (td.SpotPrice ?? 0) * detail.MarginRatio2;
|
||
}
|
||
|
||
return span;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 按浮动盈亏
|
||
/// </summary>
|
||
/// <param name="td"></param>
|
||
/// <param name="detail"></param>
|
||
/// <returns></returns>
|
||
private trade_span CalcAsFloat(trade td, margin_template_detail detail)
|
||
{
|
||
var span = _helper.CreateTradeSpan(td, null);
|
||
span.UnderlyingPrice = _req.PriceProvider.GetPrice(td.UnderlyingCode);
|
||
|
||
var value = 0.0;
|
||
if (!detail.UsePositionStockEqvNotional)
|
||
{
|
||
value = TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) * (detail.MarginRatio2 ?? 0);
|
||
}
|
||
else
|
||
{
|
||
value = TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) * (span.UnderlyingPrice ?? 0) / (td.SpotPrice ?? 0) * (detail.MarginRatio2 ?? 0);
|
||
}
|
||
|
||
if (_req.CalcMarginType != CalcMarginTypeEnum.InitialMargin)
|
||
{
|
||
var tradeRiskResult = CalculatorHelper.CalculateRisksForTrades(
|
||
valueDate: _req.settleDate,
|
||
tradeList: new[] { td },
|
||
calcScenario: _req.GetCalcScenario(),
|
||
priceProvider: _req.PriceProvider,
|
||
pricingRequest: QdpPricingRequest.PV_ONLY,
|
||
addVolRateDic: null,
|
||
volType: _req.volType, isUseTradeVol: PS.Config.IsTradeVol,
|
||
preciseTimeMode: _req.CalcMarginType != CalcMarginTypeEnum.EodMargin);
|
||
if (!string.IsNullOrWhiteSpace(tradeRiskResult.ErrorMessage))
|
||
{
|
||
LogFactory.GetLogger<MarginCalculationBase>().Error("Pv计算失败:" + tradeRiskResult.ErrorMessage);
|
||
}
|
||
var tradePrice = (td.TradePrice * td.Notional / td.OriginalNotional) ?? 0;
|
||
double pnl = 0;
|
||
if (td.BuySell == "买入")
|
||
{
|
||
pnl = tradePrice - Math.Abs(tradeRiskResult.Results.First().ValueResult.Pv);
|
||
}
|
||
else
|
||
{
|
||
pnl = Math.Abs(tradeRiskResult.Results.First().ValueResult.Pv) - tradePrice;
|
||
}
|
||
value += pnl;
|
||
if (value < 0)
|
||
{ value = 0; }
|
||
}
|
||
span.WorstCastClientPayable = value;
|
||
|
||
return span;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 类似香草期权tradespan处理
|
||
/// </summary>
|
||
/// <param name="td"></param>
|
||
/// <param name="detail"></param>
|
||
/// <returns></returns>
|
||
private trade_span CalcAsVanilla(trade td, margin_template_detail detail)
|
||
{
|
||
if (td.BuySell != "买入")
|
||
{
|
||
return null;
|
||
}
|
||
|
||
var udm = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
|
||
if (udm == null)
|
||
{
|
||
udm = new underlying_manager();
|
||
}
|
||
EodPrice eodPrice = null;
|
||
udm.QuotationDate = _req.settleDate;
|
||
double closePrice;
|
||
var underlyingStatus = underlying_manager.Status_Working;
|
||
if (_req.CalcMarginType == CalcMarginTypeEnum.InitialMargin)
|
||
{
|
||
closePrice = td.SpotPrice ?? 0;
|
||
}
|
||
else
|
||
{
|
||
new EodPriceProvider(_req.settleDate, false).TryGetEodPrice(td.UnderlyingCode, out eodPrice);
|
||
|
||
closePrice = eodPrice?.ClosePrice ?? udm.Price ?? 0;
|
||
underlyingStatus = eodPrice?.UnderlyingStatus ?? udm.UnderlyingStatus;
|
||
}
|
||
|
||
var lastDate = QdpCalendarHelper.GetNonHolidayDefore(_req.settleDate.AddDays(-1));
|
||
var preClosePrice = new EodPriceProvider(lastDate, true).GetPrice(td.UnderlyingCode, SettlementTypeEnum.ClosePrice);
|
||
|
||
double? positionWin = 0.0, baseMargin;
|
||
|
||
var optionValue = CalcTradeValueResult(td, udm, closePrice);
|
||
|
||
var pv = optionValue.Pv;
|
||
//默认取大的名义本金
|
||
var stockEqvNotionalForMargin = (closePrice > td.SpotPrice ? closePrice / td.SpotPrice : 1) * TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor);
|
||
if (detail.IsIntrinsicValue)
|
||
{
|
||
//交易员方向是赚钱的,客户需要补交预付金
|
||
if (_req.CalcMarginType != CalcMarginTypeEnum.InitialMargin && (td.OptionType == "看涨" && closePrice > td.ActualStrike || td.OptionType == "看跌" && closePrice < td.ActualStrike))
|
||
{
|
||
//实值名义本金处理
|
||
if (td.OptionType == "看涨")
|
||
{
|
||
//看涨取持仓名义本金
|
||
stockEqvNotionalForMargin = closePrice / td.SpotPrice * TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor);
|
||
}
|
||
else
|
||
{
|
||
//看跌取期初名义本金
|
||
stockEqvNotionalForMargin = TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor);
|
||
}
|
||
|
||
baseMargin = stockEqvNotionalForMargin * detail.MarginRatio1;
|
||
|
||
baseMargin += Math.Abs(closePrice - (td.ActualStrike ?? 0)) * td.Notional;
|
||
positionWin += Math.Abs(closePrice - (td.ActualStrike ?? 0)) * td.Notional;
|
||
}
|
||
//客户方向赚钱,客户可以抵扣预付金,最多抵扣值为期权费
|
||
else if (_req.CalcMarginType != CalcMarginTypeEnum.InitialMargin && (td.OptionType == "看涨" && closePrice < td.ActualStrike || td.OptionType == "看跌" && closePrice > td.ActualStrike))
|
||
{
|
||
//虚值名义本金处理
|
||
if (td.OptionType == "看涨")
|
||
{
|
||
//看涨取期初名义本金
|
||
stockEqvNotionalForMargin = TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor);
|
||
}
|
||
else
|
||
{
|
||
//看跌取持仓名义本金
|
||
stockEqvNotionalForMargin = closePrice / td.SpotPrice * TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor);
|
||
}
|
||
|
||
//虚值名义本金取小的
|
||
baseMargin = stockEqvNotionalForMargin * detail.MarginRatio1;
|
||
|
||
baseMargin -= Math.Min(Math.Abs(closePrice - (td.ActualStrike ?? 0)) * td.Notional, td.TradePrice * td.Notional / td.OriginalNotional ?? 0);
|
||
positionWin -= Math.Min(Math.Abs(closePrice - (td.ActualStrike ?? 0)) * td.Notional, td.TradePrice * td.Notional / td.OriginalNotional ?? 0);
|
||
}
|
||
else
|
||
{
|
||
baseMargin = stockEqvNotionalForMargin * detail.MarginRatio1;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
baseMargin = stockEqvNotionalForMargin * detail.MarginRatio1;
|
||
|
||
if (_req.CalcMarginType != CalcMarginTypeEnum.InitialMargin && pv > td.TradePrice * td.Notional / td.OriginalNotional)
|
||
{
|
||
baseMargin += pv - td.TradePrice * td.Notional / td.OriginalNotional;
|
||
positionWin += pv - td.TradePrice * td.Notional / td.OriginalNotional;
|
||
}
|
||
}
|
||
|
||
if (preClosePrice > 0 && _req.CalcMarginType != CalcMarginTypeEnum.InitialMargin)
|
||
{
|
||
//涨跌幅超过30%以及停牌的交易
|
||
if (closePrice / preClosePrice >= 1.3 || closePrice / preClosePrice <= 0.7 || underlyingStatus == underlying_manager.Status_Suspension)
|
||
{
|
||
baseMargin += stockEqvNotionalForMargin * detail.MarginRatio4;
|
||
}
|
||
else //涨跌停
|
||
{
|
||
var upDownLimit = _helper.GetStockUpDownLimit(td.UnderlyingCode, preClosePrice);
|
||
|
||
//涨跌停暂时不使用精确等于处理
|
||
if (upDownLimit.DownLimitPrice >= closePrice || upDownLimit.UpLimitPrice <= closePrice)
|
||
{
|
||
//一字涨跌停
|
||
if (eodPrice != null && eodPrice.HighPrice == eodPrice.LowPrice)
|
||
{
|
||
baseMargin += stockEqvNotionalForMargin * detail.MarginRatio3;
|
||
}
|
||
else
|
||
{
|
||
baseMargin += stockEqvNotionalForMargin * detail.MarginRatio2;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
var tradeSpan = _helper.CreateTradeSpan(td, udm);
|
||
tradeSpan.WorstCastClientPayable = baseMargin;
|
||
tradeSpan.PositionWin = positionWin;
|
||
tradeSpan.UnderlyingPrice = _req.PriceProvider.GetPrice(td.UnderlyingCode);
|
||
return tradeSpan;
|
||
}
|
||
|
||
private TradeValueResult CalcTradeValueResult(trade td, underlying_manager udm, double closePrice)
|
||
{
|
||
var positionVol = VolatilityHelper.GetTradeVol(td, _req.settleDate, _req.CalcMarginType == CalcMarginTypeEnum.EodMargin);
|
||
TradeValueResult optionValue;
|
||
switch (td.TradeType)
|
||
{
|
||
case "远期":
|
||
if (PS.Config.ErpElement.ForwardTradePriceModel == Configuration.Enums.ForwardTradePriceModel.STANDARD && !string.IsNullOrWhiteSpace(td.BasisUnderlyingCode) && _req.CalcMarginType != CalcMarginTypeEnum.InitialMargin)
|
||
{
|
||
var Basisudm = DataCacheProvider.GetUnderlyingDataSource().GetData(td.BasisUnderlyingCode);
|
||
if (Basisudm == null)
|
||
{
|
||
Basisudm = new underlying_manager();
|
||
}
|
||
new EodPriceProvider(_req.settleDate, false).TryGetEodPrice(td.BasisUnderlyingCode, out var BasiseodPrice);
|
||
closePrice -= BasiseodPrice?.ClosePrice ?? Basisudm.Price ?? 0;
|
||
}
|
||
optionValue = ForwardradeCalcService.CalcValue(td, closePrice);
|
||
break;
|
||
case "信用债":
|
||
case "商品期货":
|
||
case "股票":
|
||
optionValue = new TradeValueResult { Pv = closePrice * td.Notional, Delta = td.Notional, DeltaCash = closePrice * td.Notional };
|
||
break;
|
||
case "自定义交易":
|
||
var calcMarginType = _req.CalcMarginType == CalcMarginTypeEnum.InitialMargin ? CalcScenarioEnum.InitialMargin : CalcScenarioEnum.EodMargin;
|
||
optionValue = TradeRiskCalcUtil.GetManualOptionValue(_req.settleDate, td, closePrice, positionVol, true, calcMarginType, _req.volType, _req.settlementType, positionVol, _req.CalcMarginType == CalcMarginTypeEnum.EodMargin).optionValue;
|
||
break;
|
||
case "收益互换":
|
||
optionValue = PayoffSwapCalcService.CalcValue(td, _req.settleDate, null, _req.CalcMarginType == CalcMarginTypeEnum.EodMargin);
|
||
break;
|
||
default:
|
||
optionValue = ValueCalculator.GetOptionValueResultV2(
|
||
_req.userId.ToString(),
|
||
udm,
|
||
td,
|
||
new double[] { positionVol },
|
||
new double[] { closePrice },
|
||
request: QdpPricingRequest.PV_ONLY,
|
||
preciseTimeMode: td.ExerciseDate == _req.settleDate);
|
||
break;
|
||
}
|
||
|
||
return optionValue;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 二元期权tradespan处理
|
||
/// </summary>
|
||
/// <param name="td"></param>
|
||
/// <returns></returns>
|
||
private trade_span CalcBinary(trade td)
|
||
{
|
||
var tradeSpan = _helper.CreateTradeSpan(td, null);
|
||
|
||
if (td.TradeType == "二元期权" && td.BuySell == "买入")
|
||
{
|
||
var margin = td.IsUsePremiumRate == true ? Math.Abs(Math.Max((td.trade_binary_option.CashOrNothingAmountRate ?? 0), (td.trade_binary_option.CashOrNothingAmountHighRate ?? 0)) * TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor)) : Math.Abs(Math.Max((td.trade_binary_option.CashOrNothingAmount ?? 0), (td.trade_binary_option.CashOrNothingAmountHigh ?? 0)) * td.Notional);
|
||
tradeSpan.WorstCastClientPayable = margin;
|
||
|
||
}
|
||
else
|
||
{
|
||
tradeSpan.WorstCastClientPayable = 0;
|
||
}
|
||
|
||
tradeSpan.UnderlyingPrice = _req.PriceProvider.GetPrice(td.UnderlyingCode);
|
||
return tradeSpan;
|
||
}
|
||
|
||
#region 雪球凤凰期权tradespan处理
|
||
|
||
/// <summary>
|
||
/// 触发条件预付金规则1
|
||
/// </summary>
|
||
/// <param name="td"></param>
|
||
/// <param name="detail"></param>
|
||
/// <returns></returns>
|
||
private trade_span CalcSnowBallAndAutoCall(trade td, margin_template_detail detail)
|
||
{
|
||
if (td.TradeType != "凤凰期权" && td.TradeType != "雪球期权")
|
||
{
|
||
var tradeSpan = _helper.CreateTradeSpan(td, null);
|
||
tradeSpan.WorstCastClientPayable = 0.0;
|
||
tradeSpan.IsSingleMargin = true;
|
||
tradeSpan.UnderlyingPrice = _req.PriceProvider.GetPrice(td.UnderlyingCode);
|
||
return tradeSpan;
|
||
}
|
||
|
||
if (td.BuySell == "买入")
|
||
{
|
||
return CalcBuySnowBallAndAutoCall(td);
|
||
}
|
||
else
|
||
{
|
||
//敲入转价差期权
|
||
var isSingleMargin = td.TradeType == "雪球期权" && (td.trade_snowball.KIPayoffType == KIPayoffTypeEnum.ToCallSpreadOption || td.trade_snowball.KIPayoffType == KIPayoffTypeEnum.ToPutSpreadOption)
|
||
|| td.TradeType == "凤凰期权" && (td.trade_autocall.KIPayoffType == KIPayoffTypeEnum.ToCallSpreadOption || td.trade_autocall.KIPayoffType == KIPayoffTypeEnum.ToPutSpreadOption);
|
||
//保底的雪球凤凰期权
|
||
if (isSingleMargin)
|
||
{
|
||
return CalcSingleSnowBallAndAutoCall(td);
|
||
}
|
||
|
||
return CalcSellSnowBallAndAutoCall(td, detail);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 触发条件预付金规则2
|
||
/// </summary>
|
||
/// <param name="td"></param>
|
||
/// <param name="detail"></param>
|
||
/// <returns></returns>
|
||
private trade_span CalcSnowBallAndAutoCall2(trade td, margin_template_detail detail)
|
||
{
|
||
if (td.TradeType != "凤凰期权" && td.TradeType != "雪球期权")
|
||
{
|
||
var tradeSpan = _helper.CreateTradeSpan(td, null);
|
||
tradeSpan.WorstCastClientPayable = 0.0;
|
||
tradeSpan.IsSingleMargin = true;
|
||
tradeSpan.UnderlyingPrice = _req.PriceProvider.GetPrice(td.UnderlyingCode);
|
||
return tradeSpan;
|
||
}
|
||
|
||
if (td.BuySell == "买入")
|
||
{
|
||
return CalcBuySnowBallAndAutoCall(td);
|
||
}
|
||
else
|
||
{
|
||
//敲入转价差期权
|
||
var isSingleMargin = td.TradeType == "雪球期权" && (td.trade_snowball.KIPayoffType == KIPayoffTypeEnum.ToCallSpreadOption || td.trade_snowball.KIPayoffType == KIPayoffTypeEnum.ToPutSpreadOption)
|
||
|| td.TradeType == "凤凰期权" && (td.trade_autocall.KIPayoffType == KIPayoffTypeEnum.ToCallSpreadOption || td.trade_autocall.KIPayoffType == KIPayoffTypeEnum.ToPutSpreadOption);
|
||
//保底的雪球凤凰期权
|
||
if (isSingleMargin)
|
||
{
|
||
return CalcSingleSnowBallAndAutoCall(td);
|
||
}
|
||
|
||
return CalcSellSnowBallAndAutoCall2(td, detail);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 触发条件预付金规则3
|
||
/// </summary>
|
||
/// <param name="td"></param>
|
||
/// <param name="detail"></param>
|
||
/// <returns></returns>
|
||
private trade_span CalcSnowBallAndAutoCall3(trade td, margin_template_detail detail)
|
||
{
|
||
if (td.TradeType != "凤凰期权" && td.TradeType != "雪球期权")
|
||
{
|
||
var tradeSpan = _helper.CreateTradeSpan(td, null);
|
||
tradeSpan.WorstCastClientPayable = 0.0;
|
||
tradeSpan.IsSingleMargin = true;
|
||
tradeSpan.UnderlyingPrice = _req.PriceProvider.GetPrice(td.UnderlyingCode);
|
||
return tradeSpan;
|
||
}
|
||
|
||
if (td.BuySell == "买入")
|
||
{
|
||
return CalcBuySnowBallAndAutoCall(td);
|
||
}
|
||
else
|
||
{
|
||
//敲入转价差期权
|
||
var isSingleMargin = td.TradeType == "雪球期权" && (td.trade_snowball.KIPayoffType == KIPayoffTypeEnum.ToCallSpreadOption || td.trade_snowball.KIPayoffType == KIPayoffTypeEnum.ToPutSpreadOption)
|
||
|| td.TradeType == "凤凰期权" && (td.trade_autocall.KIPayoffType == KIPayoffTypeEnum.ToCallSpreadOption || td.trade_autocall.KIPayoffType == KIPayoffTypeEnum.ToPutSpreadOption);
|
||
//保底的雪球凤凰期权
|
||
if (isSingleMargin)
|
||
{
|
||
return CalcSingleSnowBallAndAutoCall(td);
|
||
}
|
||
|
||
return CalcSellSnowBallAndAutoCall3(td, detail);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 触发条件预付金规则1
|
||
/// </summary>
|
||
/// <param name="td"></param>
|
||
/// <param name="detail"></param>
|
||
/// <returns></returns>
|
||
private trade_span CalcSellSnowBallAndAutoCall(trade td, margin_template_detail detail)
|
||
{
|
||
var margin = 0.0;
|
||
|
||
var marginRatio = detail.MarginRatio1 ?? 0;
|
||
var marginRatio1 = detail.MarginRatio2 ?? 0;
|
||
var redeemPriceLimit = detail.RedeemPriceLimit ?? 0;
|
||
|
||
var closePrice = td.SpotPrice ?? 0;
|
||
var udm = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
|
||
if (_req.CalcMarginType != CalcMarginTypeEnum.InitialMargin)
|
||
{
|
||
if (!new EodPriceProvider(_req.settleDate, false).TryGetPrice(td.UnderlyingCode, SettlementTypeEnum.ClosePrice, out closePrice))
|
||
{
|
||
closePrice = udm.Price ?? 0;
|
||
}
|
||
}
|
||
|
||
var strike = td.ActualStrike ?? 0;
|
||
if (detail.IsUseStrike && strike > 0 && closePrice / strike <= (1 - redeemPriceLimit))
|
||
{
|
||
margin = TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) * marginRatio1;
|
||
margin += TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) / td.SpotPrice.Value * (strike - closePrice);
|
||
}
|
||
else if (!detail.IsUseStrike && td.SpotPrice > 0 && closePrice / td.SpotPrice <= (1 - redeemPriceLimit))
|
||
{
|
||
margin = TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) * marginRatio1;
|
||
margin += TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) * (1 - closePrice / td.SpotPrice.Value);
|
||
}
|
||
else
|
||
{
|
||
margin = TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) * marginRatio;
|
||
}
|
||
|
||
var tradeSpan = _helper.CreateTradeSpan(td, null);
|
||
tradeSpan.WorstCastClientPayable = margin;
|
||
tradeSpan.IsSingleMargin = false;
|
||
tradeSpan.UnderlyingPrice = _req.PriceProvider.GetPrice(td.UnderlyingCode);
|
||
return tradeSpan;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 触发条件预付金规则2
|
||
/// </summary>
|
||
/// <param name="td"></param>
|
||
/// <param name="detail"></param>
|
||
/// <returns></returns>
|
||
private trade_span CalcSellSnowBallAndAutoCall2(trade td, margin_template_detail detail)
|
||
{
|
||
var margin = 0.0;
|
||
|
||
var redeemPriceLimit = detail.RedeemPriceLimit ?? 0;
|
||
|
||
var closePrice = td.SpotPrice ?? 0;
|
||
var udm = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
|
||
if (_req.CalcMarginType != CalcMarginTypeEnum.InitialMargin)
|
||
{
|
||
if (!new EodPriceProvider(_req.settleDate, false).TryGetPrice(td.UnderlyingCode, SettlementTypeEnum.ClosePrice, out closePrice))
|
||
{
|
||
closePrice = udm.Price ?? 0;
|
||
}
|
||
}
|
||
|
||
var strike = td.ActualStrike ?? 0;
|
||
//满足触发条件,需要增加浮亏部分
|
||
if (detail.IsUseStrike && strike > 0 && closePrice / strike <= (1 - redeemPriceLimit))
|
||
{
|
||
margin = TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) * closePrice / td.SpotPrice.Value * (detail.MarginRatio2 ?? 0);
|
||
margin += TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) / td.SpotPrice.Value * (strike - closePrice);
|
||
}
|
||
else if (!detail.IsUseStrike && td.SpotPrice > 0 && closePrice / td.SpotPrice <= (1 - redeemPriceLimit))
|
||
{
|
||
margin = TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) * closePrice / td.SpotPrice.Value * (detail.MarginRatio2 ?? 0);
|
||
margin += TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) * (1 - closePrice / td.SpotPrice.Value);
|
||
}
|
||
else
|
||
{
|
||
margin = TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) * (detail.MarginRatio1 ?? 0);
|
||
}
|
||
|
||
var tradeSpan = _helper.CreateTradeSpan(td, null);
|
||
tradeSpan.WorstCastClientPayable = margin;
|
||
tradeSpan.IsSingleMargin = false;
|
||
tradeSpan.UnderlyingPrice = _req.PriceProvider.GetPrice(td.UnderlyingCode);
|
||
return tradeSpan;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 触发条件预付金规则3
|
||
/// </summary>
|
||
/// <param name="td"></param>
|
||
/// <param name="detail"></param>
|
||
/// <returns></returns>
|
||
private trade_span CalcSellSnowBallAndAutoCall3(trade td, margin_template_detail detail)
|
||
{
|
||
var margin = 0.0;
|
||
|
||
var redeemPriceLimit = detail.RedeemPriceLimit ?? 0;
|
||
|
||
var closePrice = td.SpotPrice ?? 0;
|
||
var udm = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
|
||
if (_req.CalcMarginType != CalcMarginTypeEnum.InitialMargin)
|
||
{
|
||
if (!new EodPriceProvider(_req.settleDate, false).TryGetPrice(td.UnderlyingCode, SettlementTypeEnum.ClosePrice, out closePrice))
|
||
{
|
||
closePrice = udm.Price ?? 0;
|
||
}
|
||
}
|
||
|
||
var strike = td.ActualStrike ?? 0;
|
||
if (detail.IsUseStrike && strike > 0)
|
||
{
|
||
var rate = (double)((decimal)closePrice / (decimal)strike);
|
||
|
||
var times = 0.0;
|
||
if (detail.MarginRatio2 > 0)
|
||
{
|
||
if (detail.PriceLimitType == 0)
|
||
{
|
||
times = Math.Floor(((1 - rate) - redeemPriceLimit) / detail.MarginRatio2.Value);
|
||
}
|
||
else
|
||
{
|
||
times = Math.Floor(((rate - 1) - redeemPriceLimit) / detail.MarginRatio2.Value);
|
||
}
|
||
}
|
||
|
||
if (detail.UsePositionStockEqvNotional)
|
||
{
|
||
if (detail.MarginRatio2 > 0 && (rate <= (1 - redeemPriceLimit) && detail.PriceLimitType == 0 || rate >= (1 + redeemPriceLimit) && detail.PriceLimitType == 1))
|
||
{
|
||
//满足追加比例时,持仓名本分段式处理
|
||
var rateTemp = 0.0;
|
||
if (detail.PriceLimitType == 0)
|
||
{
|
||
rateTemp = 1 - (redeemPriceLimit + detail.MarginRatio2.Value * times);
|
||
}
|
||
else
|
||
{
|
||
rateTemp = 1 + (redeemPriceLimit + detail.MarginRatio2.Value * times);
|
||
}
|
||
margin = TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) * rateTemp * (detail.MarginRatio3 ?? 0);
|
||
}
|
||
else
|
||
{
|
||
//不满足追加比例时,用初始预付金
|
||
margin = TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) * (detail.MarginRatio1 ?? 0);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (detail.MarginRatio2 > 0 && (rate <= (1 - redeemPriceLimit) && detail.PriceLimitType == 0 || rate >= (1 + redeemPriceLimit) && detail.PriceLimitType == 1))
|
||
{
|
||
margin = TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) * (detail.MarginRatio3 ?? 0);
|
||
}
|
||
else
|
||
{
|
||
margin = TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) * (detail.MarginRatio1 ?? 0);
|
||
}
|
||
}
|
||
|
||
//满足触发条件,需要增加浮亏部分
|
||
if (detail.MarginRatio2 > 0 && (rate <= (1 - redeemPriceLimit) && detail.PriceLimitType == 0 || rate >= (1 + redeemPriceLimit) && detail.PriceLimitType == 1))
|
||
{
|
||
margin += TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) * strike / td.SpotPrice.Value * (redeemPriceLimit + detail.MarginRatio2.Value * times);
|
||
}
|
||
}
|
||
else if (!detail.IsUseStrike && td.SpotPrice > 0)
|
||
{
|
||
var rate = (double)((decimal)closePrice / (decimal)td.SpotPrice.Value);
|
||
|
||
var times = 0.0;
|
||
if (detail.MarginRatio2 > 0)
|
||
{
|
||
if (detail.PriceLimitType == 0)
|
||
{
|
||
times = Math.Floor(((1 - rate) - redeemPriceLimit) / detail.MarginRatio2.Value);
|
||
}
|
||
else
|
||
{
|
||
times = Math.Floor(((rate - 1) - redeemPriceLimit) / detail.MarginRatio2.Value);
|
||
}
|
||
}
|
||
|
||
if (detail.UsePositionStockEqvNotional)
|
||
{
|
||
if (detail.MarginRatio2 > 0 && (rate <= (1 - redeemPriceLimit) && detail.PriceLimitType == 0 || rate >= (1 + redeemPriceLimit) && detail.PriceLimitType == 1))
|
||
{
|
||
//满足追加比例时,持仓名本分段式处理
|
||
var rateTemp = 0.0;
|
||
if (detail.PriceLimitType == 0)
|
||
{
|
||
rateTemp = 1 - (redeemPriceLimit + detail.MarginRatio2.Value * times);
|
||
|
||
}
|
||
else
|
||
{
|
||
rateTemp = 1 + (redeemPriceLimit + detail.MarginRatio2.Value * times);
|
||
|
||
}
|
||
margin = TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) * rateTemp * (detail.MarginRatio3 ?? 0);
|
||
}
|
||
else
|
||
{
|
||
//不满足追加比例时,用初始预付金
|
||
margin = TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) * (detail.MarginRatio1 ?? 0);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (detail.MarginRatio2 > 0 && (rate <= (1 - redeemPriceLimit) && detail.PriceLimitType == 0 || rate >= (1 + redeemPriceLimit) && detail.PriceLimitType == 1))
|
||
{
|
||
margin = TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) * (detail.MarginRatio3 ?? 0);
|
||
}
|
||
else
|
||
{
|
||
margin = TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) * (detail.MarginRatio1 ?? 0);
|
||
}
|
||
}
|
||
|
||
//满足触发条件,需要增加浮亏部分
|
||
if (detail.MarginRatio2 > 0 && (rate <= (1 - redeemPriceLimit) && detail.PriceLimitType == 0 || rate >= (1 + redeemPriceLimit) && detail.PriceLimitType == 1))
|
||
{
|
||
margin += TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) * (redeemPriceLimit + detail.MarginRatio2.Value * times);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
margin = TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) * (detail.MarginRatio1 ?? 0);
|
||
}
|
||
|
||
var tradeSpan = _helper.CreateTradeSpan(td, null);
|
||
tradeSpan.WorstCastClientPayable = margin;
|
||
tradeSpan.IsSingleMargin = false;
|
||
tradeSpan.UnderlyingPrice = _req.PriceProvider.GetPrice(td.UnderlyingCode);
|
||
return tradeSpan;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 自动赎回买入预付金规则
|
||
/// </summary>
|
||
/// <param name="td"></param>
|
||
/// <returns></returns>
|
||
private trade_span CalcBuySnowBallAndAutoCall(trade td)
|
||
{
|
||
if ((td.TradeType == "凤凰期权" || td.TradeType == "雪球期权") && td.BuySell == "买入")
|
||
{
|
||
var margin = 0.0;
|
||
|
||
if (td.TradeType == "雪球期权")
|
||
{
|
||
var optionTrade = QdpTradeBuilder.GetSnowballOptionTrade(td, td.trade_snowball);
|
||
var snowball = (SimpleSnowball)optionTrade.Instrument;
|
||
//雪球期权算预付金,买入按照当前日期往后两个观察日算的票息作为预付金额,如果接近到期日,按到期日处理
|
||
var valueDate = _req.settleDate;
|
||
if (snowball.CustomizedKOBarriers != null && snowball.CustomizedKOBarriers.Length > 0)
|
||
{
|
||
var kOObsDates = snowball.KOObsDates.Select(y => y.DateTime).Where(y => y > _req.settleDate).ToList();
|
||
if (kOObsDates.Any() && kOObsDates.Count() >= 2)
|
||
{
|
||
kOObsDates = kOObsDates.OrderBy(y => y).ToList();
|
||
valueDate = kOObsDates[1];
|
||
}
|
||
else
|
||
{
|
||
valueDate = td.ExerciseDate.Value;
|
||
}
|
||
}
|
||
margin = snowball.CouponPayment(new Date(valueDate), td.trade_snowball.CouponIncludeStartDate == true);
|
||
}
|
||
else
|
||
{
|
||
var optionTrade = QdpTradeBuilder.GetAutocallOptionTrade(td, td.trade_autocall);
|
||
|
||
var autocall = (AutoCall)optionTrade.Instrument;
|
||
|
||
if (autocall.CustomizedKOBarriers != null && autocall.CustomizedKOBarriers.Length > 0)
|
||
{
|
||
var kOObsDates = autocall.KOObsDates.Select(y => y.DateTime).Where(y => y > _req.settleDate).ToList();
|
||
var n = (td.SpotPrice > 0) ? (TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor) / td.SpotPrice.Value) : double.NaN;
|
||
if (kOObsDates.Any() && kOObsDates.Count() >= 2)
|
||
{
|
||
kOObsDates = kOObsDates.OrderBy(y => y).ToList();
|
||
|
||
var observation0 = autocall.GetEffectiveObservation(
|
||
kOObsDates[0], includeTradeStartDate: td.trade_autocall.CouponIncludeStartDate == true && td.trade_autocall.CouponDayCount != "Monthly", n: n);
|
||
var observation1 = autocall.GetEffectiveObservation(
|
||
kOObsDates[1], includeTradeStartDate: td.trade_autocall.CouponIncludeStartDate == true && td.trade_autocall.CouponDayCount != "Monthly", n: n);
|
||
margin = observation0.PaymentAmount + observation1.PaymentAmount;
|
||
}
|
||
else
|
||
{
|
||
var observation0 = autocall.GetEffectiveObservation(
|
||
td.ExerciseDate.Value, includeTradeStartDate: td.trade_autocall.CouponIncludeStartDate == true, n: n);
|
||
margin = observation0.PaymentAmount;
|
||
}
|
||
}
|
||
}
|
||
|
||
var tradeSpan = _helper.CreateTradeSpan(td, null);
|
||
tradeSpan.WorstCastClientPayable = margin;
|
||
tradeSpan.UnderlyingPrice = _req.PriceProvider.GetPrice(td.UnderlyingCode);
|
||
return tradeSpan;
|
||
}
|
||
else
|
||
{
|
||
var tradeSpan = _helper.CreateTradeSpan(td, null);
|
||
tradeSpan.WorstCastClientPayable = 0;
|
||
tradeSpan.UnderlyingPrice = _req.PriceProvider.GetPrice(td.UnderlyingCode);
|
||
return tradeSpan;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保底预付金规则
|
||
/// </summary>
|
||
/// <param name="td"></param>
|
||
/// <returns></returns>
|
||
private trade_span CalcSingleSnowBallAndAutoCall(trade td)
|
||
{
|
||
if ((td.TradeType == "凤凰期权" || td.TradeType == "雪球期权") && td.BuySell == "卖出")
|
||
{
|
||
var margin = 0.0;
|
||
|
||
//敲入转价差期权
|
||
var isSingleMargin = td.TradeType == "雪球期权" && (td.trade_snowball.KIPayoffType == KIPayoffTypeEnum.ToCallSpreadOption || td.trade_snowball.KIPayoffType == KIPayoffTypeEnum.ToPutSpreadOption)
|
||
|| td.TradeType == "凤凰期权" && (td.trade_autocall.KIPayoffType == KIPayoffTypeEnum.ToCallSpreadOption || td.trade_autocall.KIPayoffType == KIPayoffTypeEnum.ToPutSpreadOption);
|
||
//保底的雪球凤凰期权预付金计算
|
||
if (isSingleMargin)
|
||
{
|
||
if (td.TradeType == "雪球期权")
|
||
{
|
||
margin = Math.Abs(((td.trade_snowball.SpreadStrikeAtMaturity1 ?? 0) - (td.trade_snowball.SpreadStrikeAtMaturity ?? 0)) * (td.IsMoneynessOptionData ? (td.SpotPrice ?? 0) : 1) * (td.SpotPrice > 0 ? td.StockEqvNotional / td.SpotPrice.Value : td.Notional));
|
||
}
|
||
else
|
||
{
|
||
margin = Math.Abs(((td.trade_autocall.SpreadStrike1 ?? 0) - (td.trade_autocall.SpreadStrike ?? 0)) * (td.IsMoneynessOptionData ? (td.SpotPrice ?? 0) : 1) * (td.SpotPrice > 0 ? td.StockEqvNotional / td.SpotPrice.Value : td.Notional));
|
||
}
|
||
}
|
||
|
||
var tradeSpan = _helper.CreateTradeSpan(td, null);
|
||
tradeSpan.WorstCastClientPayable = margin;
|
||
tradeSpan.IsSingleMargin = isSingleMargin;
|
||
tradeSpan.UnderlyingPrice = _req.PriceProvider.GetPrice(td.UnderlyingCode);
|
||
return tradeSpan;
|
||
}
|
||
else
|
||
{
|
||
var tradeSpan = _helper.CreateTradeSpan(td, null);
|
||
tradeSpan.WorstCastClientPayable = 0.0;
|
||
tradeSpan.IsSingleMargin = true;
|
||
tradeSpan.UnderlyingPrice = _req.PriceProvider.GetPrice(td.UnderlyingCode);
|
||
return tradeSpan;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
/// <summary>
|
||
/// 买入自动赎回1
|
||
/// </summary>
|
||
/// <param name="td"></param>
|
||
/// <param name="detail"></param>
|
||
/// <returns></returns>
|
||
private trade_span CalcBuyAutoRedeem(trade td, margin_template_detail detail)
|
||
{
|
||
var margin = 0.0;
|
||
var initialMargin = 0.0;
|
||
|
||
if ((td.TradeType == "凤凰期权" || td.TradeType == "雪球期权") && td.BuySell == "买入")
|
||
{
|
||
var udm = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
|
||
if (udm == null)
|
||
{
|
||
udm = new underlying_manager();
|
||
}
|
||
EodPrice eodPrice = null;
|
||
udm.QuotationDate = _req.settleDate;
|
||
double closePrice;
|
||
if (_req.CalcMarginType == CalcMarginTypeEnum.InitialMargin)
|
||
{
|
||
closePrice = td.SpotPrice ?? 0;
|
||
}
|
||
else
|
||
{
|
||
new EodPriceProvider(_req.settleDate, false).TryGetEodPrice(td.UnderlyingCode, out eodPrice);
|
||
|
||
closePrice = eodPrice?.ClosePrice ?? udm.Price ?? 0;
|
||
}
|
||
|
||
var optionValue = CalcTradeValueResult(td, udm, closePrice);
|
||
|
||
double singleConple = 0.0;
|
||
double allCouples = 0.0;
|
||
if (td.TradeType == "雪球期权")
|
||
{
|
||
var optionTrade = QdpTradeBuilder.GetSnowballOptionTrade(td, td.trade_snowball);
|
||
var snowball = (SimpleSnowball)optionTrade.Instrument;
|
||
|
||
if (snowball.KOObsDates != null && snowball.KOObsDates.Length > 0)
|
||
{
|
||
var firstKOObsDate = snowball.KOObsDates.Select(x => x.DateTime).OrderBy(x => x).FirstOrDefault();
|
||
singleConple = snowball.CouponPayment(new Date(firstKOObsDate), td.trade_snowball.CouponIncludeStartDate == true);
|
||
allCouples = snowball.CouponPayment(new Date(td.ExerciseDate), td.trade_snowball.CouponIncludeStartDate == true);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
var optionTrade = QdpTradeBuilder.GetAutocallOptionTrade(td, td.trade_autocall);
|
||
var autocall = (AutoCall)optionTrade.Instrument;
|
||
|
||
if (autocall.KOObsDates != null && autocall.KOObsDates.Length > 0)
|
||
{
|
||
var koDates = autocall.KOObsDates.Select(x => x.DateTime).OrderBy(x => x).ToList();
|
||
var firstKOObsDate = koDates.FirstOrDefault();
|
||
singleConple = autocall.CouponPayment(new Date(firstKOObsDate), td.trade_autocall.CouponIncludeStartDate == true);
|
||
koDates.ForEach(x => {
|
||
if(x == firstKOObsDate)
|
||
{
|
||
allCouples += autocall.CouponPayment(new Date(x), td.trade_autocall.CouponIncludeStartDate == true);
|
||
}
|
||
else
|
||
{
|
||
allCouples += autocall.CouponPayment(new Date(x));
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
margin = Math.Min((detail.MarginRatio1 ?? 0) * singleConple + (detail.MarginRatio2 ?? 0) * Math.Max(0, optionValue.Pv), allCouples);
|
||
|
||
margin = CompareWithSpreadOptionMargin(td, detail, margin);
|
||
|
||
initialMargin = td.InitialMargin * td.StockEqvNotional / td.OriginalStockEqvNotional * detail.ComparedInitialMarginRate ?? 0;
|
||
if (_helper.req.CalcMarginType != CalcMarginTypeEnum.InitialMargin && !_helper.req.forOtherSide)
|
||
{
|
||
margin = Math.Max(margin, initialMargin);
|
||
}
|
||
}
|
||
|
||
var tradeSpan = _helper.CreateTradeSpan(td, null);
|
||
tradeSpan.WorstCastClientPayable = margin;
|
||
tradeSpan.InitialMargin = initialMargin;
|
||
tradeSpan.IsSingleMargin = true;
|
||
tradeSpan.UnderlyingPrice = _req.PriceProvider.GetPrice(td.UnderlyingCode);
|
||
return tradeSpan;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 卖出自动赎回1
|
||
/// </summary>
|
||
/// <param name="td"></param>
|
||
/// <param name="detail"></param>
|
||
/// <returns></returns>
|
||
private trade_span CalcSellAutoRedeem(trade td, margin_template_detail detail)
|
||
{
|
||
var margin = 0.0;
|
||
var initialMargin = 0.0;
|
||
|
||
var udm = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
|
||
if (udm == null)
|
||
{
|
||
udm = new underlying_manager();
|
||
}
|
||
EodPrice eodPrice = null;
|
||
udm.QuotationDate = _req.settleDate;
|
||
double closePrice;
|
||
if (_req.CalcMarginType == CalcMarginTypeEnum.InitialMargin)
|
||
{
|
||
closePrice = td.SpotPrice ?? 0;
|
||
}
|
||
else
|
||
{
|
||
new EodPriceProvider(_req.settleDate, false).TryGetEodPrice(td.UnderlyingCode, out eodPrice);
|
||
closePrice = eodPrice?.ClosePrice ?? udm.Price ?? 0;
|
||
}
|
||
|
||
var optionValue = CalcTradeValueResult(td, udm, closePrice);
|
||
|
||
var virtureRate = td.SpotPrice != null && td.SpotPrice != 0 ? Math.Max(0, (_req.CalcMarginType == CalcMarginTypeEnum.InitialMargin ? td.SpotPrice.Value : closePrice) - (td.ActualStrike ?? 0)) / td.SpotPrice.Value : 0;
|
||
var flag = _helper.req.CalcMarginType == CalcMarginTypeEnum.InitialMargin ? 0 : 1;
|
||
margin = td.StockEqvNotional * Math.Max(detail.MarginRatio1 ?? 0, (detail.MarginRatio2 ?? 0) - 0.5 * virtureRate) + flag * Math.Max(0, optionValue.Pv);
|
||
|
||
margin = CompareWithSpreadOptionMargin(td, detail, margin);
|
||
initialMargin = td.InitialMargin * td.StockEqvNotional / td.OriginalStockEqvNotional * detail.ComparedInitialMarginRate ?? 0;
|
||
if (_helper.req.CalcMarginType != CalcMarginTypeEnum.InitialMargin && !_helper.req.forOtherSide)
|
||
{
|
||
margin = Math.Max(margin, initialMargin);
|
||
}
|
||
|
||
var tradeSpan = _helper.CreateTradeSpan(td, null);
|
||
tradeSpan.WorstCastClientPayable = margin;
|
||
tradeSpan.InitialMargin = initialMargin;
|
||
tradeSpan.IsSingleMargin = true;
|
||
tradeSpan.UnderlyingPrice = closePrice;
|
||
return tradeSpan;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保底部分逻辑处理
|
||
/// </summary>
|
||
/// <param name="td"></param>
|
||
/// <param name="detail"></param>
|
||
/// <param name="margin"></param>
|
||
private double CompareWithSpreadOptionMargin(trade td, margin_template_detail detail, double margin)
|
||
{
|
||
if (detail.IsNeedCompare)
|
||
{
|
||
//敲入转价差期权
|
||
var isSpreadOption = td.TradeType == "雪球期权" && (td.trade_snowball.KIPayoffType == KIPayoffTypeEnum.ToCallSpreadOption || td.trade_snowball.KIPayoffType == KIPayoffTypeEnum.ToPutSpreadOption)
|
||
|| td.TradeType == "凤凰期权" && (td.trade_autocall.KIPayoffType == KIPayoffTypeEnum.ToCallSpreadOption || td.trade_autocall.KIPayoffType == KIPayoffTypeEnum.ToPutSpreadOption);
|
||
//保底的雪球凤凰期权预付金计算
|
||
if (isSpreadOption)
|
||
{
|
||
var spreadOptionMargin = 0.0;
|
||
if (td.TradeType == "雪球期权")
|
||
{
|
||
spreadOptionMargin = Math.Abs(((td.trade_snowball.SpreadStrikeAtMaturity1 ?? 0) - (td.trade_snowball.SpreadStrikeAtMaturity ?? 0)) * (td.IsMoneynessOptionData ? (td.SpotPrice ?? 0) : 1) * td.Notional);
|
||
}
|
||
else
|
||
{
|
||
spreadOptionMargin = Math.Abs(((td.trade_autocall.SpreadStrike1 ?? 0) - (td.trade_autocall.SpreadStrike ?? 0)) * (td.IsMoneynessOptionData ? (td.SpotPrice ?? 0) : 1) * td.Notional);
|
||
}
|
||
|
||
margin = Math.Min(margin, spreadOptionMargin);
|
||
}
|
||
}
|
||
|
||
return margin;
|
||
}
|
||
}
|
||
}
|
||
|
||
public class TradeTypeAndBuySell
|
||
{
|
||
public string TradeType { get; set; }
|
||
|
||
public BuySellEnum BuySellType { get; set; }
|
||
}
|
||
}
|