495 lines
21 KiB
C#
495 lines
21 KiB
C#
using YLErp.BLL.Calculation;
|
|
using YLErp.Commons;
|
|
using YLErp.Enums;
|
|
using YLErp.Helpers;
|
|
using YLErp.Model.Enum;
|
|
using YLErp.Modules;
|
|
using YLErp.Modules.MarginModule;
|
|
using YLErp.QdpModule;
|
|
|
|
namespace YLErp.BLL.MarginCalculation
|
|
{
|
|
/// <summary>
|
|
/// 兴证预付金计算
|
|
/// </summary>
|
|
public class XingZhengMarginCalculation : MarginCalculationBase
|
|
{
|
|
// 定义一个静态变量来保存类的实例
|
|
public static readonly XingZhengMarginCalculation Instance;
|
|
|
|
static XingZhengMarginCalculation()
|
|
{
|
|
Instance = new XingZhengMarginCalculation();
|
|
}
|
|
|
|
// 定义私有构造函数,使外界不能创建该类实例
|
|
protected XingZhengMarginCalculation()
|
|
{
|
|
}
|
|
|
|
public override List<trade_span> RunMarginCalculation(RunMarginCalculationReq req)
|
|
{
|
|
if (req?.tradeList == null || !req.tradeList.Any())
|
|
{
|
|
return new List<trade_span>();
|
|
}
|
|
|
|
var resultMap = new Dictionary<int, trade_span>();
|
|
|
|
var helper = new RunMarginCalculationHelper(req, _underlyingDataProvider);
|
|
var underlyingCodes = req.tradeList.Select(t => t.UnderlyingCode).ToHashSet();
|
|
var mpProvider = helper.GetMarginParamProvider(MarginParamTypeEnum.MarginRate | MarginParamTypeEnum.UpDownLimit);
|
|
|
|
helper.SetFieldsByTradeType();
|
|
|
|
var addVolRateDic = req.tradeList.Select(t =>
|
|
{
|
|
var volRate = 0d;
|
|
var client = helper.GetClient(t);
|
|
if (client?.QuestionnaireScore == null || client.QuestionnaireScore < 60)
|
|
{
|
|
volRate = 0.03;
|
|
}
|
|
return (t.id, volRate);
|
|
}).Where(d => d.volRate > 0).ToDictionary(d => d.id, d => d.volRate);
|
|
|
|
helper.GetUpDownLimitPrices(out var upLimitPrices, out var downLimitPrices);
|
|
|
|
var userIdNew = UniqueTimeId.Get().ToString();
|
|
|
|
var calcTradeList = req.tradeList.Where(t => t.TradeType != "雪球期权").ToArray();
|
|
var prices = new[] { ("up", upLimitPrices), ("down", downLimitPrices), ("normal", req.PriceProvider) };
|
|
|
|
foreach (var price in prices)
|
|
{
|
|
var key = price.Item1;
|
|
var calcReq = helper.GetCalculateRisksForTradesReq(priceProvider: price.Item2, addVolRateDic: addVolRateDic, overrideVols: null, pricingRequest: QdpPricingRequest.BASIC_PRICING);
|
|
calcReq.tradeList = calcTradeList;
|
|
|
|
var tradeRiskResult = CalculatorHelper.CalculateRisksForTrades(calcReq);
|
|
|
|
if (tradeRiskResult.Results == null || tradeRiskResult.Results.Count < 1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (var item in tradeRiskResult.Results)
|
|
{
|
|
var pv = double.IsNaN(item.ValueResult.Pv) ? 0 : item.ValueResult.Pv;
|
|
|
|
var hasSpecialMargin = helper.GetSpecialMargin(item.Trade, pv, out var value);
|
|
if (!hasSpecialMargin)
|
|
{
|
|
var client = helper.GetClient(item.Trade);
|
|
value = pv * (client == null ? 1d : (req.CalcMarginType == CalcMarginTypeEnum.InitialMargin ? client.Ratio1 : client.Ratio) ?? 0);
|
|
}
|
|
|
|
var contains = resultMap.TryGetValue(item.Trade.id, out var tempTradeSpan);
|
|
|
|
if (!contains)
|
|
{
|
|
resultMap[item.Trade.id] = tempTradeSpan = helper.CreateTradeSpan(item.Trade);
|
|
tempTradeSpan.UnderlyingPrice = req.PriceProvider.GetPrice(item.Trade.UnderlyingCode);
|
|
}
|
|
|
|
switch (key)
|
|
{
|
|
case "up":
|
|
tempTradeSpan.Spv1 = value;
|
|
break;
|
|
case "down":
|
|
tempTradeSpan.Spv2 = value;
|
|
break;
|
|
case "normal":
|
|
tempTradeSpan.Spv = value;
|
|
tempTradeSpan.Delta = item.ValueResult.Delta;
|
|
mpProvider.TryGetMarginRate(item.ValueResult.UnderlyingCode, out var marginRate);
|
|
tempTradeSpan.DeltaMargin = (tempTradeSpan.Delta * tempTradeSpan.UnderlyingPrice * marginRate) ?? 0;
|
|
break;
|
|
}
|
|
|
|
if (contains)
|
|
{
|
|
tempTradeSpan.SetWorstCastClientPayable();
|
|
}
|
|
}
|
|
}
|
|
|
|
var snowballMargin = new SnowballMarginService();
|
|
|
|
foreach (var td in req.tradeList)
|
|
{
|
|
if (td.TradeType != "雪球期权")
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var um = helper.GetUnderlying(td.UnderlyingCode);
|
|
var spotPrice = req.PriceProvider.GetPrice(td.UnderlyingCode);
|
|
|
|
trade_span tempTradeSpan;
|
|
resultMap[td.id] = tempTradeSpan = helper.CreateTradeSpan(td, um);
|
|
tempTradeSpan.UnderlyingPrice = spotPrice;
|
|
|
|
var margin = snowballMargin.CalcMargin(req.settleDate, td, td.trade_snowball, um, spotPrice);
|
|
|
|
tempTradeSpan.Spv1 = tempTradeSpan.Spv2
|
|
= tempTradeSpan.Spv3 = tempTradeSpan.Spv4
|
|
= tempTradeSpan.WorstCastClientPayable = margin;
|
|
}
|
|
|
|
return resultMap.Values.ToList();
|
|
}
|
|
|
|
public override List<trade_span> CalcClientMargin(CalcClientMarginReq req)
|
|
{
|
|
var clientSpanNews = new List<ClientSpan>();
|
|
|
|
using (var db = DbContextFactory.GetYLDbContext())
|
|
{
|
|
//删除
|
|
if (req.tradeSpans != null && req.tradeSpans.Count > 0)
|
|
{
|
|
var tradeIds = req.tradeSpans.Select(t => t.TradeId).ToList();
|
|
|
|
var tradeList = db.trade.AsNoTracking().Where(t => tradeIds.Contains(t.id))
|
|
.Select(n => new { n.id, n.ClientId, n.UnderlyingId }).ToList();
|
|
|
|
var tradeSpanInfos = (from tradeSpan in req.tradeSpans
|
|
join trade in tradeList on tradeSpan.TradeId equals trade.id
|
|
where tradeSpan.ValueDate == req.settleDate
|
|
select new { trade, tradeSpan }).ToList();
|
|
|
|
var clientGroups = tradeSpanInfos.GroupBy(t => t.trade.ClientId);
|
|
|
|
var clientIds = clientGroups.Select(n => n.Key).ToArray();
|
|
|
|
var dbTradeSpans = db.trade_span.Where(x => x.ValueDate == req.settleDate && x.ClientId != null && clientIds.Contains(x.ClientId.Value)).ToArray();
|
|
|
|
foreach (var clientGroup in clientGroups)
|
|
{
|
|
#region Span Margin Method
|
|
|
|
var client = DataCacheProvider.GetClientDataSource().GetData(clientGroup.Key);
|
|
|
|
var underlyingGroup = clientGroup.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),
|
|
Spv2 = -t.Sum(g => g.tradeSpan.Spv2),
|
|
OptId = req.userId,
|
|
OptName = req.userName,
|
|
OptDate = DateTime.Now,
|
|
SpanType = req.SpanType,
|
|
DeltaMargin = -t.Sum(g => g.tradeSpan.DeltaMargin)
|
|
}).ToList();
|
|
|
|
foreach (var item in underlyingGroup)
|
|
{
|
|
item.WorstCastClientPayable = Math.Min(item.Spv1 ?? 0, item.Spv2 ?? 0);
|
|
|
|
#region 更新tradeSpan,使得每笔交易的持仓预付金和客户预付金计算用的Spv组保持一致
|
|
|
|
var tradeIdList = clientGroup.Select(x => x.tradeSpan.TradeId);
|
|
var tradeSpansUpdate = dbTradeSpans.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);
|
|
}
|
|
|
|
#endregion
|
|
|
|
if (client.MarginOptionType == (int)MarginOptionEnum.单向追保)
|
|
{
|
|
item.WorstCastClientPayable = Math.Min(item.WorstCastClientPayable.Value, 0);
|
|
}
|
|
else if (client.MarginOptionType == (int)MarginOptionEnum.对手方单向追保)
|
|
{
|
|
item.WorstCastClientPayable = Math.Max(item.WorstCastClientPayable.Value, 0);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
var clientSpan = new ClientSpan
|
|
{
|
|
ClientId = clientGroup.Key,
|
|
ValueDate = req.settleDate,
|
|
Spv1 = underlyingGroup.Sum(g => g.Spv1),
|
|
Spv2 = underlyingGroup.Sum(g => g.Spv2),
|
|
DeltaMargin = underlyingGroup.Sum(g => g.DeltaMargin),
|
|
//负数代表客户应缴预付金,正数代表客户应收预付金
|
|
WorstCastClientPayable = underlyingGroup.Sum(g => g.WorstCastClientPayable),
|
|
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
|
|
};
|
|
|
|
if (clientSpan.WorstCastClientPayable > clientSpan.DeltaMargin)
|
|
{
|
|
clientSpan.WorstCastClientPayable = clientSpan.DeltaMargin;
|
|
|
|
//取delta预付金作为最终预付金
|
|
var tradeSpansUpdate = dbTradeSpans.Where(x => x.ClientId == clientGroup.Key && x.ValueDate == req.settleDate).ToList();
|
|
|
|
tradeSpansUpdate.ForEach(x => x.WorstCastClientPayable = x.DeltaMargin);
|
|
}
|
|
|
|
if (!HasTwoSideMargin(clientGroup.Key))
|
|
{
|
|
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)
|
|
{
|
|
var trade = req.trade;
|
|
using (var db = new YLContext())
|
|
{
|
|
if (trade.TradeType == "结构化交易")
|
|
{
|
|
trade.SubTrades = db.trade.Where(x => x.ParentTradeId == trade.id).ToList();
|
|
}
|
|
|
|
var tradeMargin = RunMarginCalculation(req.GetRunMarginCalculationReq());
|
|
if (tradeMargin != null && tradeMargin.FirstOrDefault() != null && trade.BuySell == "买入")
|
|
{
|
|
var margin = tradeMargin.FirstOrDefault().WorstCastClientPayable ?? 0.0;
|
|
var mpProvider = new MarginParamProvider(OptUserInfo.SystemUser, valuedateBLL.ValueDate.Date).Initialize(new List<string> { trade.UnderlyingCode }.ToHashSet(), MarginParamTypeEnum.MarginRate);
|
|
mpProvider.TryGetMarginRate(trade.UnderlyingCode, out var marginRate);
|
|
var deltaMargin = Math.Abs(tradeMargin.FirstOrDefault().Delta ?? 0) * (trade.SpotPrice ?? 0) * marginRate;
|
|
|
|
//单笔交易的初始预付金 = 维持预付金 × 105%(初始预付金是为避免客户频繁转账,在盘中对当天收盘时的维持预付金的预估,该比例不低于105%,可根据不同客户情况进行调整)
|
|
var client = GetClientInfo(trade.ClientId);
|
|
var ratio = client == null ? 1 : (client.Ratio1 ?? 1);
|
|
return (margin > deltaMargin ? margin : deltaMargin) * ratio;
|
|
}
|
|
}
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 雪球期权预付金计算
|
|
/// </summary>
|
|
class SnowballMarginService : YLBaseService
|
|
{
|
|
const string MK_MarginSpot1 = "MarginSpot1";
|
|
const string MK_MarginSpot2 = "MarginSpot2";
|
|
|
|
public SnowballMarginService() : base(OptUserInfo.SystemUser)
|
|
{
|
|
|
|
}
|
|
|
|
public double CalcMargin(DateTime settleDate, OtcTradeBase td, trade_snowball snowball, underlying_manager um, double spotPrice)
|
|
{
|
|
if (GetObSpots(td, out var spot1, out var spot2))
|
|
{
|
|
var obDates = GetObDates(snowball.ObservationDates, td.TradeDate.Value, settleDate);
|
|
|
|
if (td.id > 0 && !IsCalcEnable(obDates, um, spot1))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
var marginRate = 1d;
|
|
|
|
if (spotPrice < spot2)
|
|
{
|
|
var perUnit = (td.SpotPrice ?? 0) * 0.05;//不考虑为0的情况了太累
|
|
marginRate += Math.Floor((spot2 - spotPrice) / perUnit);
|
|
}
|
|
|
|
return marginRate * td.StockEqvNotional * 0.05;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//获取是否追保处理
|
|
private bool IsCalcEnable(List<DateTime> obDates, underlying_manager um, double spot1)
|
|
{
|
|
if (ConsGlobal.InstrumentType.CalcTypeIsFutures(um.UnderlyingCode) || ConsGlobal.InstrumentType.IsCommoditySpot(um.UnderlyingCode))
|
|
{
|
|
var query = from t in DbContext.eod_commodity_future_price
|
|
where obDates.Contains(t.ValueDate)
|
|
&& t.UnderlyingCode == um.UnderlyingCode && t.SettlePrice < spot1
|
|
select t;
|
|
return query.Any();
|
|
}
|
|
else
|
|
{
|
|
var query = from t in DbContext.eod_stock_price
|
|
where obDates.Contains(t.ValueDate)
|
|
&& t.UnderlyingCode == um.UnderlyingCode && t.ClosePrice < spot1
|
|
select t;
|
|
return query.Any();
|
|
}
|
|
}
|
|
|
|
//获取追保价格
|
|
private bool GetObSpots(OtcTradeBase td, out double spot1, out double spot2)
|
|
{
|
|
spot1 = 0;
|
|
spot2 = 0;
|
|
|
|
TradeMetaDto[] metas;
|
|
|
|
if (td.MetaDic.ContainsKey(MK_MarginSpot1))
|
|
{
|
|
metas = new[] { new TradeMetaDto { MetaKey = MK_MarginSpot1, MetaValue = td.MetaDic[MK_MarginSpot1] } };
|
|
|
|
if (td.MetaDic.ContainsKey(MK_MarginSpot2))
|
|
{
|
|
metas = new[] {
|
|
metas[0],
|
|
new TradeMetaDto { MetaKey = MK_MarginSpot2, MetaValue = td.MetaDic[MK_MarginSpot2] }
|
|
};
|
|
}
|
|
}
|
|
else if (td.id > 0)
|
|
{
|
|
var metaKeys = new[] { MK_MarginSpot1, MK_MarginSpot2 };
|
|
|
|
metas = DbContext.TradeMeta.Where(n => n.TradeId == td.id && metaKeys.Contains(n.MetaKey))
|
|
.Select(n => new TradeMetaDto { MetaKey = n.MetaKey, MetaValue = n.MetaValue }).ToArray();
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!metas.Any())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (var m in metas)
|
|
{
|
|
if (MK_MarginSpot1.Equals(m.MetaKey, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
NumberHelper.TryParse(m.MetaValue, out spot1, out _);
|
|
}
|
|
else
|
|
{
|
|
NumberHelper.TryParse(m.MetaValue, out spot2, out _);
|
|
}
|
|
}
|
|
|
|
if (td.IsMoneynessOption == "是")
|
|
{
|
|
spot1 *= td.SpotPrice ?? 0;
|
|
spot2 *= td.SpotPrice ?? 0;
|
|
}
|
|
|
|
if (spot1 < 1e-2)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//获取敲入观察日列表
|
|
private List<DateTime> GetObDates(string ObservationDates, DateTime startDate, DateTime endDate)
|
|
{
|
|
var dateList = new List<DateTime>();
|
|
|
|
if (string.IsNullOrWhiteSpace(ObservationDates))
|
|
{
|
|
var obDate = startDate;
|
|
|
|
for (var i = 0; i < 10000; i++)
|
|
{
|
|
obDate = QdpCalendarHelper.GetNonHoliday(obDate);
|
|
|
|
if (obDate > endDate)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
dateList.Add(obDate);
|
|
}
|
|
|
|
obDate = obDate.AddDays(1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var obStrs = ObservationDates.Split(',');
|
|
|
|
foreach (var str in obStrs)
|
|
{
|
|
if (DateTime.TryParse(str, out var obDate) && obDate >= startDate && obDate <= endDate)
|
|
{
|
|
obDate = QdpCalendarHelper.GetNonHoliday(obDate);
|
|
dateList.Add(obDate);
|
|
}
|
|
}
|
|
}
|
|
|
|
return dateList;
|
|
}
|
|
}
|
|
}
|
|
}
|