514 lines
21 KiB
C#
514 lines
21 KiB
C#
using Qdp.Pricing.Base.Implementations;
|
||
using YLErp.BLL.Calculation;
|
||
using YLErp.Enums;
|
||
using YLErp.Helpers;
|
||
using YLErp.Model.Enum;
|
||
using YLErp.Modules.CalculationModule;
|
||
using YLErp.Modules.DataProviderModule;
|
||
using YLErp.Modules.MarginModule;
|
||
|
||
namespace YLErp.BLL.MarginCalculation
|
||
{
|
||
public class GDGZMarginCalculation : MarginCalculationBase
|
||
{
|
||
// 定义一个静态变量来保存类的实例(单例模式)
|
||
public static readonly GDGZMarginCalculation Instance;
|
||
|
||
static GDGZMarginCalculation()
|
||
{
|
||
Instance = new GDGZMarginCalculation();
|
||
}
|
||
|
||
// 定义私有构造函数,使外界不能创建该类实例
|
||
private GDGZMarginCalculation()
|
||
{
|
||
|
||
}
|
||
|
||
public override List<trade_span> RunMarginCalculation(RunMarginCalculationReq req)
|
||
{
|
||
if (req.tradeList == null || !req.tradeList.Any())
|
||
{
|
||
return new List<trade_span>(0);
|
||
}
|
||
|
||
return new InnerRunMarginCalculation(req, _underlyingDataProvider).CalcMargin();
|
||
}
|
||
|
||
public override List<trade_span> CalcClientMargin(CalcClientMarginReq req)
|
||
{
|
||
var clientSpanNews = new List<ClientSpan>();
|
||
using (var db = new YLContext())
|
||
{
|
||
if (req.tradeSpans != null && req.tradeSpans.Count > 0)
|
||
{
|
||
var clientGroups = req.tradeSpans.GroupBy(t => t.ClientId);
|
||
foreach (var clientGroup in clientGroups)
|
||
{
|
||
var clientSpan = new ClientSpan
|
||
{
|
||
ClientId = clientGroup.Key ?? 0,
|
||
ValueDate = req.settleDate,
|
||
//负数代表客户应缴预付金,正数代表客户应收预付金
|
||
WorstCastClientPayable = -clientGroup.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 ?? 0, out var dd) ? dd : 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).ToList();
|
||
clientSpanNews = clientSpanNews.Where(c => !clientSpanOldsWithFlag.Any(t => t.ValueDate == c.ValueDate && t.ClientId == c.ClientId)).ToList();
|
||
}
|
||
if (clientSpanNews.Count > 0)
|
||
{
|
||
db.client_span.AddRange(clientSpanNews);
|
||
}
|
||
db.SaveChanges();
|
||
return req.tradeSpans;
|
||
}
|
||
}
|
||
|
||
public override double GetTradeMargin(GetTradeMarginReq req)
|
||
{
|
||
using (var db = new YLContext())
|
||
{
|
||
if (req.trade.TradeType == "结构化交易")
|
||
{
|
||
req.trade.SubTrades = db.trade.Where(x => x.ParentTradeId == req.trade.id).ToList();
|
||
}
|
||
}
|
||
var tradeMargin = RunMarginCalculation(req.GetRunMarginCalculationReq());
|
||
if (null != tradeMargin)
|
||
{
|
||
return tradeMargin.FirstOrDefault()?.WorstCastClientPayable ?? 0.0;
|
||
}
|
||
return 0.0;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 内部计算交易预付金类
|
||
/// </summary>
|
||
class InnerRunMarginCalculation
|
||
{
|
||
readonly RunMarginCalculationReq _req;
|
||
readonly List<trade_span> _tradeSpans;
|
||
readonly bool _isInitialMargin;
|
||
readonly RunMarginCalculationHelper _helper;
|
||
|
||
MarginParamProvider _mpProvider;
|
||
|
||
public InnerRunMarginCalculation(RunMarginCalculationReq req, UnderlyingDataProvider underlyingDataProvider)
|
||
{
|
||
_req = req;
|
||
_tradeSpans = new List<trade_span>();
|
||
_isInitialMargin = req.CalcMarginType == CalcMarginTypeEnum.InitialMargin;
|
||
_helper = new RunMarginCalculationHelper(_req, underlyingDataProvider);
|
||
}
|
||
|
||
public List<trade_span> CalcMargin()
|
||
{
|
||
_tradeSpans.Clear();
|
||
|
||
if (_req.tradeList == null || !_req.tradeList.Any())
|
||
{
|
||
return _tradeSpans;
|
||
}
|
||
|
||
//设置详细信息
|
||
_helper.SetFieldsByTradeType();
|
||
|
||
//计算一组数据的交易风险指标
|
||
var calcReq = _helper.GetCalculateRisksForTradesReq(
|
||
priceProvider: _req.PriceProvider,
|
||
addVolRateDic: null, overrideVols: null,
|
||
pricingRequest: PricingRequest.Pv | PricingRequest.Delta | PricingRequest.Gamma);
|
||
|
||
var tradeRiskResult = CalculatorHelper.CalculateRisksForTrades(calcReq);
|
||
|
||
if (tradeRiskResult.Results.Count < 1)
|
||
{
|
||
return _tradeSpans;
|
||
}
|
||
|
||
//跨式组合
|
||
var tradeStraddleList = new List<TradeRiskResultRecord>();
|
||
|
||
//结构化交易组合
|
||
var tradeStructureList = new List<TradeRiskResultRecord>();
|
||
|
||
//这个循环只处理最简单的情况
|
||
foreach (var item in tradeRiskResult.Results)
|
||
{
|
||
if (item.Trade.StructureType == "跨式组合" || item.Trade.StructureType == "宽跨式组合")
|
||
{
|
||
tradeStraddleList.Add(item);
|
||
}
|
||
else if (item.Trade.StructureType == "结构化交易")
|
||
{
|
||
tradeStructureList.Add(item);
|
||
}
|
||
else
|
||
{
|
||
ProcessSingleTrade(item);
|
||
}
|
||
}
|
||
|
||
//处理跨式交易
|
||
var groups = tradeStraddleList.GroupBy(n => n.Trade.ParentTradeId);
|
||
foreach (var grp in groups)
|
||
{
|
||
ProcessStraddleTrades(grp.ToArray());
|
||
}
|
||
|
||
//处理结构化组合交易
|
||
groups = tradeStructureList.GroupBy(n => n.Trade.ParentTradeId);
|
||
foreach (var grp in groups)
|
||
{
|
||
ProcessStructureTrades(grp.ToArray());
|
||
}
|
||
|
||
return _tradeSpans;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理单个交易
|
||
/// </summary>
|
||
private void ProcessSingleTrade(TradeRiskResultRecord item)
|
||
{
|
||
var margin = GetUpDownEffectValue(item.ValueResult, out var updownLimit);
|
||
|
||
if (!_isInitialMargin)
|
||
{
|
||
margin += item.ValueResult.Pv;
|
||
margin = GetRealMargin(margin, item.Trade.BuySell, item.Trade.ClientId, out _);
|
||
}
|
||
else if (item.Trade.BuySell == "卖出")
|
||
{
|
||
margin = -margin;
|
||
}
|
||
|
||
var tradeSpan = _helper.CreateTradeSpan(item.Trade).SetAllSpvAndWorst(margin);
|
||
|
||
tradeSpan.Comment = GetComment(item.ValueResult, updownLimit);
|
||
|
||
_tradeSpans.Add(tradeSpan);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理跨式交易
|
||
/// </summary>
|
||
private void ProcessStraddleTrades(TradeRiskResultRecord[] items)
|
||
{
|
||
var firstItem = items[0];
|
||
|
||
//组内只有一条交易
|
||
if (items.Length == 1)
|
||
{
|
||
ProcessSingleTrade(firstItem);
|
||
return;
|
||
}
|
||
|
||
//初始预付金:两腿初始预付金平分
|
||
//跨式组合的两腿交易方向是一致的
|
||
if (_isInitialMargin)
|
||
{
|
||
var spans1 = items.Select(n =>
|
||
{
|
||
var tradeSpan = _helper.CreateTradeSpan(n.Trade);
|
||
tradeSpan.WorstCastClientPayable = n.ValueResult.Pv + GetUpDownEffectValue(n.ValueResult, out var updownLimit);
|
||
tradeSpan.Comment = GetComment(n.ValueResult, updownLimit);
|
||
return tradeSpan;
|
||
}).ToArray();
|
||
|
||
var margin = spans1.Average(n => n.WorstCastClientPayable ?? 0);
|
||
|
||
if (firstItem.Trade.BuySell == "卖出")
|
||
{
|
||
margin = -margin;
|
||
}
|
||
|
||
foreach (var span in spans1)
|
||
{
|
||
span.SetAllSpvAndWorst(margin);
|
||
}
|
||
|
||
_tradeSpans.AddRange(spans1);
|
||
|
||
return;
|
||
}
|
||
|
||
//持仓预付金
|
||
//一腿预付金为期末结算收支
|
||
//另一腿预付金为期末结算收支+两腿标的涨跌停造成期权价值变化孰大者
|
||
|
||
var maxEffectVal = 0d;
|
||
var lastItem = items.Last();
|
||
InnerClient client = null;
|
||
|
||
var spans2 = items.Select(n =>
|
||
{
|
||
var effectVal = GetUpDownEffectValue(n.ValueResult, out var updownLimit);
|
||
|
||
maxEffectVal = Math.Max(maxEffectVal, effectVal);
|
||
|
||
var tradeSpan = _helper.CreateTradeSpan(n.Trade);
|
||
|
||
tradeSpan.Comment = GetComment(n.ValueResult, updownLimit);
|
||
|
||
var margin = n == lastItem ? n.ValueResult.Pv + maxEffectVal : n.ValueResult.Pv;
|
||
|
||
if (client != null && client.ClientId == n.Trade.ClientId)
|
||
{
|
||
margin = GetRealMargin(margin, n.Trade.BuySell, (MarginOptionEnum)client.MarginOptionType);
|
||
}
|
||
else
|
||
{
|
||
margin = GetRealMargin(margin, n.Trade.BuySell, n.Trade.ClientId, out client);
|
||
}
|
||
|
||
//设置预付金
|
||
tradeSpan.SetAllSpvAndWorst(margin);
|
||
|
||
return tradeSpan;
|
||
});
|
||
|
||
_tradeSpans.AddRange(spans2);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理结构化组合交易
|
||
/// </summary>
|
||
private void ProcessStructureTrades(TradeRiskResultRecord[] items)
|
||
{
|
||
var firstItem = items[0];
|
||
InnerClient client = null;
|
||
client = _helper.GetClient(firstItem.Trade.ClientId);
|
||
double margin = 0d, updownLimit = 0;
|
||
//远期:光子买的腿,初始预付金为期初权利金*2,持仓预付金计算公式与香草一致
|
||
if (firstItem.Trade.TradeNumber.Contains("FWD"))
|
||
{
|
||
foreach (var item in items)
|
||
{
|
||
if (_isInitialMargin)
|
||
{
|
||
if (item.Trade.BuySell == "买入")
|
||
{
|
||
updownLimit = 0;
|
||
margin = item.ValueResult.Pv * 2;
|
||
}
|
||
else
|
||
{
|
||
margin = -GetUpDownEffectValue(item.ValueResult, out updownLimit);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
margin = item.ValueResult.Pv + GetUpDownEffectValue(item.ValueResult, out updownLimit);
|
||
if (client != null && client.ClientId == item.Trade.ClientId)
|
||
{
|
||
margin = GetRealMargin(margin, item.Trade.BuySell, (MarginOptionEnum)client.MarginOptionType);
|
||
}
|
||
else
|
||
{
|
||
margin = GetRealMargin(margin, item.Trade.BuySell, item.Trade.ClientId, out client);
|
||
}
|
||
}
|
||
|
||
var tradeSpan = _helper.CreateTradeSpan(item.Trade).SetAllSpvAndWorst(margin);
|
||
|
||
tradeSpan.Comment = GetComment(item.ValueResult, updownLimit);
|
||
|
||
_tradeSpans.Add(tradeSpan);
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
//价差
|
||
if (firstItem.Trade.TradeNumber.Contains("SPD"))
|
||
{
|
||
//行权价 K1=4000,K2=4500,Notional=10:
|
||
//单向追保:
|
||
//1.交易员买入,预付金5000
|
||
//2.交易员卖出,预付金0
|
||
//双向追保:
|
||
//1.交易员买入,预付金5000
|
||
//2.交易员卖出,预付金 - 5000
|
||
//对手方单向追保:
|
||
//1.交易员买入,预付金0
|
||
//2.交易员卖出,预付金 - 5000
|
||
//双向追保 交易员买入 买入 + 卖出 0
|
||
// 卖出 买入 0 卖出 -
|
||
|
||
var sumBuy = items.Sum(n => n.Trade.BuySell == "买入" ? (n.Trade.TradePrice ?? 0) : 0);
|
||
var sumSell = items.Sum(n => n.Trade.BuySell == "买入" ? 0 : (n.Trade.TradePrice ?? 0));
|
||
|
||
//行权价之差的绝对值,因为取得时总和的绝对值所以在任何方向取负值都没问题
|
||
var strikeDiff = Math.Abs(items.Sum(n => (n.Trade.BuySell == "买入" ? -1 : 1) * (n.Trade.Strike ?? 0) * n.Trade.TradeAmount));
|
||
|
||
//交易员买入价差
|
||
if (sumBuy > sumSell)
|
||
{
|
||
if ((MarginOptionEnum)client.MarginOptionType == MarginOptionEnum.对手方单向追保)
|
||
{
|
||
margin = 0;
|
||
}
|
||
else
|
||
{
|
||
margin = strikeDiff;
|
||
}
|
||
//最后一笔交易员买入
|
||
var lastItem = items.LastOrDefault(n => n.Trade.BuySell == "买入");
|
||
|
||
var spans = items.Select(n =>
|
||
{
|
||
var tradeSpan = _helper.CreateTradeSpan(n.Trade);
|
||
if (n == lastItem)
|
||
{
|
||
tradeSpan.SetAllSpvAndWorst(margin);
|
||
tradeSpan.Comment = "客户卖出价差LAST";
|
||
}
|
||
else
|
||
{
|
||
tradeSpan.Comment = "客户卖出价差";
|
||
}
|
||
return tradeSpan;
|
||
}).ToArray();
|
||
_tradeSpans.AddRange(spans);
|
||
|
||
}
|
||
else
|
||
{
|
||
//最后一笔客户卖出腿/交易员买入
|
||
var lastItem = items.LastOrDefault(n => n.Trade.BuySell == "买入");
|
||
if ((MarginOptionEnum)client.MarginOptionType == MarginOptionEnum.单向追保)
|
||
{
|
||
margin = 0;
|
||
}
|
||
else
|
||
{
|
||
margin = -strikeDiff;
|
||
}
|
||
var spans = items.Select(n =>
|
||
{
|
||
var tradeSpan = _helper.CreateTradeSpan(n.Trade);
|
||
if ((n == lastItem && (MarginOptionEnum)client.MarginOptionType != MarginOptionEnum.双向追保) || (n != lastItem && (MarginOptionEnum)client.MarginOptionType == MarginOptionEnum.双向追保))
|
||
{
|
||
tradeSpan.SetAllSpvAndWorst(margin);
|
||
tradeSpan.Comment = "客户卖出价差LAST";
|
||
}
|
||
else
|
||
{
|
||
tradeSpan.Comment = "客户卖出价差";
|
||
}
|
||
return tradeSpan;
|
||
}).ToArray();
|
||
|
||
_tradeSpans.AddRange(spans);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
foreach (var item in items)
|
||
{
|
||
ProcessSingleTrade(item);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取涨跌停造成期权价值变化
|
||
/// </summary>
|
||
private double GetUpDownEffectValue(TradeValueResult ValueResult, out double updownLimit)
|
||
{
|
||
if (_mpProvider == null)
|
||
{
|
||
_mpProvider = new MarginParamProvider(_req.UserInfo, _req.settleDate);
|
||
}
|
||
|
||
if (!_mpProvider.TryGetUpdownLimit(ValueResult.UnderlyingCode, out updownLimit, out _))
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
var spotPrice = ValueResult.SpotPrice ?? 0;
|
||
|
||
var spotPriceLimit = spotPrice * updownLimit;
|
||
|
||
return Math.Abs(ValueResult.Delta * spotPriceLimit) + Math.Abs(ValueResult.Gamma * spotPriceLimit * spotPriceLimit) / 2;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据买卖方向和客户追保方向获取预付金实际取值(只用于持仓预付金)
|
||
/// </summary>
|
||
private double GetRealMargin(double margin, string buysell, int clientId, out InnerClient client)
|
||
{
|
||
client = _helper.GetClient(clientId);
|
||
|
||
if (client != null)
|
||
{
|
||
return GetRealMargin(margin, buysell, (MarginOptionEnum)client.MarginOptionType);
|
||
}
|
||
|
||
return margin;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据买卖方向和客户追保方向获取预付金实际取值(只用于持仓预付金)
|
||
/// </summary>
|
||
private static double GetRealMargin(double margin, string buysell, MarginOptionEnum marginOptionType)
|
||
{
|
||
if (marginOptionType == MarginOptionEnum.单向追保)
|
||
{
|
||
return buysell == "卖出" ? 0 : Math.Max(margin, 0);
|
||
}
|
||
|
||
if (marginOptionType == MarginOptionEnum.对手方单向追保)
|
||
{
|
||
return buysell == "卖出" ? Math.Min(margin, 0) : 0;
|
||
}
|
||
|
||
return margin;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取交易预付金备注
|
||
/// </summary>
|
||
private static string GetComment(TradeValueResult ValueResult, double updownLimit)
|
||
{
|
||
return $"Delta:{ValueResult.Delta},closePrice:{ValueResult.SpotPrice},Gamma:{ValueResult.Gamma},updownLimit:{updownLimit:0.00%}";
|
||
}
|
||
}
|
||
}
|
||
}
|