410 lines
24 KiB
C#
410 lines
24 KiB
C#
using YLErp.Abstract.DataProviders;
|
||
using YLErp.BLL.Calculation;
|
||
using YLErp.BLL.Eod;
|
||
using YLErp.BLL.MarginCalculation;
|
||
using YLErp.Enums;
|
||
using YLErp.Helpers;
|
||
using YLErp.Modules.DataProviderModule;
|
||
using YLErp.QdpModule;
|
||
|
||
namespace YLErp.BLL
|
||
{
|
||
/// <summary>
|
||
/// 浙期
|
||
/// </summary>
|
||
public class ZheQiMarginCalculation : MarginCalculationBase
|
||
{
|
||
// 定义一个静态变量来保存类的实例
|
||
public static readonly ZheQiMarginCalculation Instance;
|
||
|
||
static ZheQiMarginCalculation()
|
||
{
|
||
Instance = new ZheQiMarginCalculation();
|
||
}
|
||
|
||
// 定义私有构造函数,使外界不能创建该类实例
|
||
protected ZheQiMarginCalculation()
|
||
{
|
||
|
||
}
|
||
|
||
public override List<trade_span> RunMarginCalculation(RunMarginCalculationReq req)
|
||
{
|
||
var helper = new RunMarginCalculationHelper(req, _underlyingDataProvider);
|
||
var resultMap = new Dictionary<int, trade_span>();
|
||
|
||
helper.SetFieldsByTradeType();
|
||
helper.GetUpDownLimitPrices(out var upLimitPrices, out var downLimitPrices);
|
||
helper.GetTradVolRateDic(out var tradeVolRateDic);
|
||
|
||
var vols = new[] { null, tradeVolRateDic };
|
||
var prices = new (string key, IPriceProvider priceProvider)[] { ("up", upLimitPrices), ("down", downLimitPrices), ("normal", req.PriceProvider) };
|
||
var loops = prices.SelectMany(n => vols.Select(m => new
|
||
{
|
||
pricekey = n.key,
|
||
priceProvider = n.priceProvider,
|
||
addVolRateDic = m
|
||
})).ToArray();
|
||
var uProvider = new UnderlyingDataProvider();
|
||
|
||
foreach (var loop in loops)
|
||
{
|
||
var calcReq = helper.GetCalculateRisksForTradesReq(priceProvider: loop.priceProvider, addVolRateDic: loop.addVolRateDic, overrideVols: null, pricingRequest: QdpPricingRequest.BASIC_PRICING);
|
||
|
||
var tradeRiskResult = CalculatorHelper.CalculateRisksForTrades(calcReq);
|
||
if (tradeRiskResult.Results == null || tradeRiskResult.Results.Count < 1)
|
||
{
|
||
continue;
|
||
}
|
||
var key = $"{loop.pricekey}_{(loop.addVolRateDic == null ? 0 : 1)}";
|
||
if (key == "normal_1") continue;
|
||
|
||
foreach (var item in tradeRiskResult.Results)
|
||
{
|
||
// 远期掉期是否走现货算法
|
||
bool isStock = false;
|
||
if (item.Trade.TradeType == "远期")
|
||
{
|
||
isStock = uProvider.GetUnderlying(item.Trade.UnderlyingCode).UnderlyingInstrumentType == ConsGlobal.InstrumentType.CommoditySpot;
|
||
if (!isStock && !string.IsNullOrWhiteSpace(item.Trade.BasisUnderlyingCode))
|
||
{
|
||
isStock = uProvider.GetUnderlying(item.Trade.BasisUnderlyingCode).UnderlyingInstrumentType == ConsGlobal.InstrumentType.CommoditySpot;
|
||
}
|
||
}
|
||
if (isStock && key != "normal_0")
|
||
{
|
||
continue;
|
||
}
|
||
|
||
var pv = item.ValueResult.Pv;
|
||
var clientRatio = helper.GetClient(item.Trade)?.Ratio ?? 1.0;
|
||
if (!helper.GetSpecialMargin(item.Trade, pv, out var value, helper.req.forOtherSide))
|
||
{
|
||
value = double.IsNaN(pv) ? 0 : pv * clientRatio;
|
||
}
|
||
|
||
var contains = resultMap.TryGetValue(item.Trade.id, out var tempTradeSpan);
|
||
|
||
if (!contains)
|
||
{
|
||
resultMap[item.Trade.id] = tempTradeSpan = helper.CreateTradeSpan(item.Trade);
|
||
}
|
||
|
||
switch (key)
|
||
{
|
||
case "up_0":
|
||
tempTradeSpan.Spv1 = value; break;
|
||
case "up_1":
|
||
tempTradeSpan.Spv2 = value; break;
|
||
case "normal_0":
|
||
if (isStock)
|
||
{
|
||
// 现货预付金:
|
||
// 普通掉期开仓固定 15%,
|
||
// 基差掉期开仓预付金固定 10%
|
||
// 持仓预付金观察:
|
||
// 1. -5%*名义本金<单笔持仓盈亏<0,
|
||
// 一般掉期持仓预付金为15%
|
||
// 基差掉期持仓预付金为10%
|
||
// 2. 单笔持仓盈亏<-5%*名义本金
|
||
// 一般掉期持仓预付金为: -单笔持仓盈亏+15%
|
||
// 基差掉期持仓预付金为: -单笔持仓盈亏+10%
|
||
var config = valuedateBLL.SystemDate.ConfigInfoData;
|
||
if (helper.req.CalcMarginType == CalcMarginTypeEnum.InitialMargin)
|
||
{
|
||
double rate = string.IsNullOrWhiteSpace(item.Trade.BasisUnderlyingCode) ? config.NormalSwapInitMarginRatio : config.BasisSwapInitMarginRatio;
|
||
rate /= 100;
|
||
value = rate * item.Trade.StockEqvNotional;
|
||
logger.Info($"远期预付金计算,交易:{item.Trade.TradeNumber}预付金 = 普通掉期开仓固定 15%,基差掉期开仓预付金固定 10%");
|
||
logger.Info($"远期预付金计算,交易:{item.Trade.TradeNumber}预付金 = {rate} * {item.Trade.StockEqvNotional}");
|
||
}
|
||
else
|
||
{
|
||
double rate = string.IsNullOrWhiteSpace(item.Trade.BasisUnderlyingCode) ? config.NormalSwapMarginRatio : config.BasisSwapMarginRatio;
|
||
rate /= 100;
|
||
var tradePrice = (item.Trade.TradePrice * item.Trade.Notional / item.Trade.OriginalNotional) ?? 0;
|
||
double pnl = item.ValueResult.Pv + tradePrice;
|
||
logger.Info($"远期预付金计算,交易:{item.Trade.TradeNumber} pnl = Pv + tradePrice");
|
||
logger.Info($"远期预付金计算,交易:{item.Trade.TradeNumber} pnl = {item.ValueResult.Pv} + {tradePrice}");
|
||
|
||
// 客户持仓盈亏
|
||
logger.Info($"远期预付金计算,交易:{item.Trade.TradeNumber}预付金 = 1. -5%*名义本金<单笔持仓盈亏<0,\r\n一般掉期持仓预付金为15%,\r\n基差掉期持仓预付金为10%\r\n2. 单笔持仓盈亏<-5%*名义本金,一般掉期持仓预付金为:\r\n-单笔持仓盈亏+15%,\r\n基差掉期持仓预付金为:\r\n-单笔持仓盈亏+10%");
|
||
double clientpnl = -pnl;
|
||
double rateStockEqvNotional = config.StockEqvNotionalRatio / 100d * item.Trade.StockEqvNotional;
|
||
if (rateStockEqvNotional < clientpnl && clientpnl < 0)
|
||
{
|
||
logger.Info($"远期预付金计算,交易:{item.Trade.TradeNumber} if({config.StockEqvNotionalRatio / 100d} * {item.Trade.StockEqvNotional} < {clientpnl} && {clientpnl} > 0)");
|
||
value = rate * item.Trade.StockEqvNotional;
|
||
logger.Info($"远期预付金计算,交易:{item.Trade.TradeNumber}预付金 = {rate} * {item.Trade.StockEqvNotional}");
|
||
}
|
||
else if (clientpnl < rateStockEqvNotional)
|
||
{
|
||
logger.Info($"远期预付金计算,交易:{item.Trade.TradeNumber} if({clientpnl} < {rateStockEqvNotional} > 0)");
|
||
value = -clientpnl + rate * item.Trade.StockEqvNotional;
|
||
logger.Info($"远期预付金计算,交易:{item.Trade.TradeNumber}预付金 = -{clientpnl} + {rate} * {item.Trade.StockEqvNotional}");
|
||
}
|
||
else
|
||
{
|
||
value = rate * item.Trade.StockEqvNotional;
|
||
logger.Info($"远期预付金计算,交易:{item.Trade.TradeNumber}预付金 = {rate} * {item.Trade.StockEqvNotional}");
|
||
}
|
||
}
|
||
tempTradeSpan.SetAllSpvAndWorst(value);
|
||
}
|
||
else
|
||
{
|
||
var um = uProvider.GetUnderlying(item.Trade.UnderlyingCode);
|
||
var variety = uProvider.GetVariety(item.Trade.UnderlyingCode);
|
||
var marginRate = ((um?.MarginRate ?? 0) > 0 ? um.MarginRate : variety?.Margin) ?? 0;
|
||
double deltaCash = 0;
|
||
if ((item.Trade.BuySell == "买入" && item.Trade.OptionType == "看涨") || (item.Trade.BuySell == "卖出" && item.Trade.OptionType == "看跌"))
|
||
{
|
||
deltaCash = Math.Abs(item.ValueResult.DeltaCash);
|
||
}
|
||
else
|
||
{
|
||
deltaCash = -Math.Abs(item.ValueResult.DeltaCash);
|
||
}
|
||
value = deltaCash * marginRate * clientRatio;
|
||
tempTradeSpan.DeltaMargin = value;
|
||
tempTradeSpan.PositionWinLoss = EodOperationBase.GetPositionPnl(item.ValueResult.Pv, item.Trade.TradePrice ?? 0.0, item.Trade.Notional, item.Trade.OriginalNotional ?? 0, item.Trade.BuySell);
|
||
}
|
||
break;
|
||
case "down_0":
|
||
tempTradeSpan.Spv3 = value; break;
|
||
case "down_1":
|
||
tempTradeSpan.Spv4 = value; break;
|
||
}
|
||
|
||
if (contains)
|
||
{
|
||
tempTradeSpan.SetWorstCastClientPayable();
|
||
|
||
if (tempTradeSpan.WorstCastClientPayable > 0)
|
||
{
|
||
tempTradeSpan.WorstCastClientPayable = Math.Max(tempTradeSpan.WorstCastClientPayable.Value, Math.Abs(tempTradeSpan.DeltaMargin ?? 0) + (tempTradeSpan.PositionWinLoss ?? 0));
|
||
}
|
||
else
|
||
{
|
||
tempTradeSpan.WorstCastClientPayable = 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return resultMap.Values.ToList();
|
||
}
|
||
|
||
public override List<trade_span> CalcClientMargin(CalcClientMarginReq req)
|
||
{
|
||
var clientSpanNews = new List<ClientSpan>();
|
||
using (var db = new YLContext())
|
||
{
|
||
var tradeIds = req.tradeSpans.Select(t => t.TradeId).ToList();
|
||
var tradeList = db.trade.AsNoTracking().Where(t => tradeIds.Contains(t.id)).ToList();
|
||
|
||
//获取收盘日那天对应的预付金模板
|
||
var tradeTemplates = db.trade_margin_template.AsNoTracking().Where(t => tradeIds.Contains(t.TradeId) && t.ValueDate <= req.settleDate);
|
||
var groupQuery = from tt in tradeTemplates
|
||
group tt by tt.TradeId into tts
|
||
select new
|
||
{
|
||
TradeId = tts.Key,
|
||
ValueDate = tts.Max(n => n.ValueDate)
|
||
};
|
||
var groupTemplates = from gq in groupQuery
|
||
join tt in tradeTemplates
|
||
on new { gq.TradeId, gq.ValueDate } equals new { tt.TradeId, tt.ValueDate }
|
||
select tt;
|
||
|
||
var tradeSpanInfo = (from tradeSpan in req.tradeSpans
|
||
join trade in tradeList on tradeSpan.TradeId equals trade.id
|
||
join tradetemplate in groupTemplates on tradeSpan.TradeId equals tradetemplate.TradeId into templates
|
||
from tradetemplate in templates.DefaultIfEmpty()
|
||
where tradeSpan.ValueDate == req.settleDate
|
||
select new { trade, tradeSpan, tradetemplate }).ToList();
|
||
|
||
if (req.tradeSpans != null && req.tradeSpans.Count > 0)
|
||
{
|
||
var spans = req.tradeSpansOtherSide != null
|
||
? req.tradeSpansOtherSide.Where(n => n != null).ToArray() : Enumerable.Empty<trade_span>();
|
||
var tradeSpanInfoOtherSide = (from tradeSpan in spans
|
||
join trade in tradeList on tradeSpan.TradeId equals trade.id
|
||
where tradeSpan.ValueDate == req.settleDate
|
||
select new { trade, tradeSpan }).ToList();
|
||
|
||
var clientGroups = tradeSpanInfo.GroupBy(t => t.trade.ClientId);
|
||
foreach (var clientGroup in clientGroups)
|
||
{
|
||
var underlyingGroup = clientGroup.Where(O => O.tradeSpan.IsSingleMargin != true).GroupBy(t => t.trade.UnderlyingId).Select(t => new ClientSpan
|
||
{
|
||
UnderlyingId = t.Key,
|
||
ClientId = clientGroup.Key,
|
||
ValueDate = req.settleDate,
|
||
Spv1 = t.Sum(g => g.tradeSpan.Spv1) * (-1),
|
||
Spv2 = t.Sum(g => g.tradeSpan.Spv2) * (-1),
|
||
Spv3 = t.Sum(g => g.tradeSpan.Spv3) * (-1),
|
||
Spv4 = t.Sum(g => g.tradeSpan.Spv4) * (-1),
|
||
DeltaMargin = t.Sum(g => g.tradeSpan.DeltaMargin) * (-1),
|
||
PositionWinLoss = t.Sum(g => g.tradeSpan.PositionWinLoss) * (-1),
|
||
OptId = req.userId,
|
||
OptName = req.userName,
|
||
OptDate = DateTime.Now,
|
||
SpanType = req.SpanType
|
||
}).ToList();
|
||
|
||
foreach (var item in underlyingGroup)
|
||
{
|
||
var tradeSpanIds = clientGroup.Where(O => O.tradeSpan.IsSingleMargin != true).Select(x => x.tradeSpan.id);
|
||
var tradeSpansUpdate = db.trade_span.Where(x => tradeSpanIds.Contains(x.id) && x.ClientId == item.ClientId && x.UnderlyingId == item.UnderlyingId && x.ValueDate == req.settleDate).ToList();
|
||
var tradeSpansReq = req.tradeSpans.Where(x => tradeSpanIds.Contains(x.id) && x.ClientId == item.ClientId && x.UnderlyingId == item.UnderlyingId && x.ValueDate == req.settleDate).ToList();
|
||
|
||
var minSpv = Math.Min(Math.Min(Math.Min(item.Spv1 ?? 0, item.Spv2 ?? 0), item.Spv3 ?? 0), item.Spv4 ?? 0);
|
||
if (minSpv < 0)
|
||
{
|
||
item.WorstCastClientPayable = Math.Max(Math.Abs(minSpv), Math.Abs(item.DeltaMargin ?? 0) - (item.PositionWinLoss ?? 0)) * (-1);
|
||
item.TwoSideMargin = item.WorstCastClientPayable;
|
||
|
||
if (Math.Abs(minSpv) > Math.Abs(item.DeltaMargin ?? 0) - (item.PositionWinLoss ?? 0))
|
||
{
|
||
//更新tradeSpan,使得每笔交易的持仓预付金和客户预付金计算用的Spv组保持一致
|
||
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
|
||
{
|
||
tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = (item.DeltaMargin > 0 ? -(x.DeltaMargin ?? 0) : (x.DeltaMargin ?? 0)) + (x.PositionWinLoss ?? 0));
|
||
tradeSpansReq.ForEach(x => x.WorstCastClientPayable = (item.DeltaMargin > 0 ? -(x.DeltaMargin ?? 0) : (x.DeltaMargin ?? 0)) + (x.PositionWinLoss ?? 0));
|
||
}
|
||
}
|
||
else
|
||
{
|
||
item.WorstCastClientPayable = 0;
|
||
item.TwoSideMargin = 0;
|
||
|
||
tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = 0);
|
||
tradeSpansReq.ForEach(x => x.WorstCastClientPayable = 0);
|
||
}
|
||
}
|
||
|
||
var clientSpan = new ClientSpan
|
||
{
|
||
ClientId = clientGroup.Key,
|
||
ValueDate = req.settleDate,
|
||
Spv1 = underlyingGroup.Sum(g => g.Spv1),
|
||
Spv2 = underlyingGroup.Sum(g => g.Spv2),
|
||
Spv3 = underlyingGroup.Sum(g => g.Spv3),
|
||
Spv4 = underlyingGroup.Sum(g => g.Spv4),
|
||
DeltaMargin = underlyingGroup.Sum(g => g.DeltaMargin),
|
||
//负数代表客户应缴预付金,正数代表客户应收预付金
|
||
WorstCastClientPayable = underlyingGroup.Sum(g => g.WorstCastClientPayable),
|
||
MySideMargin = underlyingGroup.Sum(g => g.WorstCastClientPayable),
|
||
TwoSideMargin = underlyingGroup.Sum(g => g.TwoSideMargin),
|
||
OptId = req.userId,
|
||
OptName = req.userName,
|
||
OptDate = DateTime.Now,
|
||
SpanType = req.SpanType,
|
||
AdditionalWorstCastClientPayable = req.clientAdditionalMarginDic != null && req.clientAdditionalMarginDic.TryGetValue(clientGroup.Key, out var dd) ? dd : 0
|
||
};
|
||
//单笔预付金算法的交易不参与品种轧差;
|
||
var singleMarginTrade = clientGroup.Where(O => O.tradeSpan.IsSingleMargin == true).Select(O => O.tradeSpan);
|
||
clientSpan.Spv1 += singleMarginTrade.Sum(O => O.Spv1 * (-1));
|
||
clientSpan.Spv2 += singleMarginTrade.Sum(O => O.Spv2 * (-1));
|
||
clientSpan.Spv3 += singleMarginTrade.Sum(O => O.Spv3 * (-1));
|
||
clientSpan.Spv4 += singleMarginTrade.Sum(O => O.Spv4 * (-1));
|
||
clientSpan.DeltaMargin += singleMarginTrade.Sum(O => O.DeltaMargin * (-1));
|
||
clientSpan.WorstCastClientPayable += singleMarginTrade.Sum(O => O.WorstCastClientPayable * (-1));
|
||
clientSpan.MySideMargin += singleMarginTrade.Sum(O => O.WorstCastClientPayable * (-1));
|
||
clientSpan.TwoSideMargin += singleMarginTrade.Sum(O => O.TwoSideMargin);
|
||
clientSpan.MySideMargin = Math.Min(clientSpan.MySideMargin ?? 0, 0);
|
||
|
||
//交易员不支付预付金
|
||
clientSpan.WorstCastClientPayable = Math.Min(clientSpan.WorstCastClientPayable ?? 0, 0);
|
||
|
||
clientSpanNews.Add(clientSpan);
|
||
}
|
||
}
|
||
|
||
//span类型为实时删除所有实时计算的交易的预付金信息
|
||
if (req.SpanType == ClientSpan.SpanType_RealTime)
|
||
{
|
||
if (req.RefreshClientIds != null)
|
||
{
|
||
db.BulkDelete<ClientSpan>($"{nameof(ClientSpan.ClientId)} in @ids", new { ids = req.RefreshClientIds });
|
||
}
|
||
else
|
||
{
|
||
db.BulkDelete<ClientSpan>($"{nameof(ClientSpan.SpanType)}=@SpanType", new { req.SpanType });
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (req.ClientIds != null)
|
||
{
|
||
var sql = $"{nameof(ClientSpan.ClientId)} in @ids and {nameof(ClientSpan.ValueDate)}='{req.settleDate.ToSqlDate()}' and {nameof(ClientSpan.SpanType)}={req.SpanType} and {nameof(ClientSpan.ModifiedFlag)}=0";
|
||
db.BulkDelete<ClientSpan>(sql, new { ids = req.ClientIds });
|
||
}
|
||
else
|
||
{
|
||
var sql = $"{nameof(ClientSpan.ValueDate)}='{req.settleDate.ToSqlDate()}' and {nameof(ClientSpan.SpanType)}={req.SpanType} and {nameof(ClientSpan.ModifiedFlag)}=0";
|
||
db.BulkDelete<ClientSpan>(sql);
|
||
}
|
||
|
||
var clientSpanOldsWithFlag = db.client_span.Where(t => t.ValueDate == req.settleDate && t.SpanType == req.SpanType && t.ModifiedFlag)
|
||
.Select(n => new { n.ValueDate, n.ClientId }).ToList();
|
||
//筛选出可以修改的clientSpan
|
||
clientSpanNews = clientSpanNews.Where(c => !clientSpanOldsWithFlag.Any(t => t.ValueDate == c.ValueDate && t.ClientId == c.ClientId)).ToList();
|
||
}
|
||
if (clientSpanNews.Count > 0)
|
||
{
|
||
MySqlBulkExtensions.BulkInsert(db, clientSpanNews);
|
||
}
|
||
db.SaveChanges();
|
||
return req.tradeSpans;
|
||
}
|
||
}
|
||
|
||
public override double GetTradeMargin(GetTradeMarginReq req)
|
||
{
|
||
using (var db = new YLContext())
|
||
{
|
||
if (req.trade.TradeType == "结构化交易")
|
||
{
|
||
req.trade.SubTrades = db.trade.Where(x => x.ParentTradeId == req.realTradeId).ToList();
|
||
}
|
||
|
||
var marginReq = req.GetRunMarginCalculationReq();
|
||
if (req.trade.IsGroup == 1)
|
||
{
|
||
marginReq.tradeList = db.trade.Where(x => x.ParentTradeId == req.realTradeId).ToList();
|
||
}
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
}
|