319 lines
13 KiB
C#
319 lines
13 KiB
C#
using YLErp.BLL.Calculation;
|
|
using YLErp.Enums;
|
|
using YLErp.Modules.MarginModule;
|
|
using YLErp.Modules.VolatilityModule;
|
|
using YLErp.QdpModule;
|
|
|
|
namespace YLErp.BLL.MarginCalculation
|
|
{
|
|
public class MaoChuanMarginCalculation : MarginCalculationBase
|
|
{
|
|
// 定义一个静态变量来保存类的实例(单例模式)
|
|
public static readonly MaoChuanMarginCalculation Instance;
|
|
|
|
static MaoChuanMarginCalculation()
|
|
{
|
|
Instance = new MaoChuanMarginCalculation();
|
|
}
|
|
|
|
// 定义私有构造函数,使外界不能创建该类实例
|
|
private MaoChuanMarginCalculation()
|
|
{
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算香草期权和亚式期权预付金
|
|
/// </summary>
|
|
private List<trade_span> CalculationNormalOptionMargin(RunMarginCalculationReq req)
|
|
{
|
|
var resultMap = new Dictionary<int, trade_span>();
|
|
|
|
var codes = req.tradeList.Select(O => O.UnderlyingCode).ToHashSet();
|
|
var mpProvider = new MarginParamProvider(req.UserInfo, req.settleDate)
|
|
.Initialize(codes, MarginParamTypeEnum.MarginRate);
|
|
//客户要求用AskVol计算预付金,所以先把所有交易方向改为卖出并把VolType赋值为 BidAskVol;
|
|
//Dictionary<int, string> buySellDict = tradeList.ToDictionary(K => K.id, V => V.BuySell);
|
|
//tradeList.ForEach(t => t.BuySell = "卖出");
|
|
var vols = new Dictionary<int, double>();
|
|
foreach (var t in req.tradeList)
|
|
{
|
|
var vType = t.VolType;
|
|
t.VolType = "报价Ask";
|
|
var um = _underlyingDataProvider.GetUnderlying(t.UnderlyingCode);
|
|
if (um == null)
|
|
{
|
|
throw new MarginCalcException($"[预付金计算]找不到标的数据(交易编号:{t.TradeNumber},标的代码:{t.UnderlyingCode})");
|
|
}
|
|
var vol = SingleVolService.GetSingleVol(new SingleVolReq(t, um), req.userId, isEodCalc: req.CalcMarginType == CalcMarginTypeEnum.EodMargin);
|
|
vols[t.id] = vol;
|
|
t.VolType = vType;
|
|
}
|
|
|
|
var tradeRiskResult = CalculatorHelper.CalculateRisksForTrades(
|
|
valueDate: req.settleDate,
|
|
tradeList: req.tradeList,
|
|
calcScenario: CalcScenarioEnum.EodSettlement,
|
|
priceProvider: req.PriceProvider,
|
|
pricingRequest: QdpPricingRequest.BASIC_PRICING,
|
|
addVolRateDic: null,
|
|
volType: null,
|
|
overrideVolsForTrade: vols,
|
|
isUseTradeVol: PS.Config.IsTradeVol,
|
|
preciseTimeMode: req.CalcMarginType != CalcMarginTypeEnum.EodMargin);
|
|
|
|
//tradeList.ForEach(t => t.BuySell = buySellDict[t.id]);
|
|
|
|
foreach (var r in tradeRiskResult.Results)
|
|
{
|
|
if (!mpProvider.TryGetMarginRate(r.Trade.UnderlyingCode, out var marginRate))
|
|
{
|
|
throw new MarginCalcException($"{r.Trade.UnderlyingCode} 预付金比率不存在");
|
|
}
|
|
|
|
var trade = req.tradeList.FirstOrDefault(t => t.id == r.Trade.id);
|
|
|
|
if (!req.PriceProvider.TryGetPrice(r.Trade.UnderlyingCode, out var price))
|
|
{
|
|
price = r.Trade.SpotPrice ?? 0;
|
|
}
|
|
|
|
logger.Info($"茂川标准预付金计算:[{r.Trade.TradeType}_{r.Trade.BuySell}_{r.Trade.OptionType}_{r.Trade.Strike}] => Vol:{r.ValueResult.Vol};Delta:{r.ValueResult.Delta}");
|
|
//预付金= Delta*标的期货预付金比率*标的期货价格*持仓规模 (qdp计算出的Delta已经乘过数量了,所以这里不用再乘了);
|
|
var value = Math.Abs(r.ValueResult.Delta) * marginRate * price;// * r.Trade.Notional;
|
|
if (r.Trade.BuySell == "买入") { value = Math.Abs(value); } else { value = 0 - Math.Abs(value); }
|
|
var tempTradeSpan = new trade_span
|
|
{
|
|
TradeId = r.Trade.id,
|
|
ClientId = trade.ClientId,
|
|
ValueDate = req.settleDate,
|
|
UnderlyingId = trade.UnderlyingId,
|
|
UnderlyingCode = trade.UnderlyingCode,
|
|
OptId = req.userId,
|
|
OptName = req.userName,
|
|
OptDate = DateTime.Now,
|
|
Spv1 = value,
|
|
Spv2 = value,
|
|
Spv3 = value,
|
|
Spv4 = value,
|
|
WorstCastClientPayable = value
|
|
};
|
|
resultMap[r.Trade.id] = tempTradeSpan;
|
|
}
|
|
|
|
return resultMap.Values.ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算其他期权预付金
|
|
/// </summary>
|
|
private List<trade_span> CalculationOtherOptionMargin(RunMarginCalculationReq req)
|
|
{
|
|
var resultMap = new Dictionary<int, trade_span>();
|
|
var tempTradeList = new List<trade>();
|
|
var vols = new Dictionary<int, double>();
|
|
foreach (var t in req.tradeList)
|
|
{
|
|
var vType = t.VolType;
|
|
t.VolType = "报价Ask";
|
|
var um = _underlyingDataProvider.GetUnderlying(t.UnderlyingCode);
|
|
var vol = SingleVolService.GetSingleVol(new SingleVolReq(t, um), req.userId, req.CalcMarginType == CalcMarginTypeEnum.EodMargin);
|
|
vols[t.id] = vol;
|
|
t.VolType = vType;
|
|
tempTradeList.Add(t.Clone());
|
|
}
|
|
var tradeRiskResult = CalculatorHelper.CalculateRisksForTrades(
|
|
valueDate: req.settleDate,
|
|
tradeList: tempTradeList,
|
|
calcScenario: CalcScenarioEnum.EodSettlement,
|
|
priceProvider: req.PriceProvider,
|
|
pricingRequest: QdpPricingRequest.BASIC_PRICING,
|
|
addVolRateDic: null,
|
|
volType: null,
|
|
overrideVolsForTrade: vols,
|
|
isUseTradeVol: PS.Config.IsTradeVol,
|
|
preciseTimeMode: req.CalcMarginType != CalcMarginTypeEnum.EodMargin);
|
|
|
|
//if (priceDict == null)
|
|
//{
|
|
// priceDict = new Dictionary<int, double>();
|
|
// Dictionary<int, string[]> rateDict = base.GetUpDownLimitRate(req.tradeList.Select(O => O.UnderlyingId).ToArray());
|
|
// tempTradeList.ForEach(t =>
|
|
// {
|
|
// double[] arr = base.GetUpDownLimitPrice(rateDict[t.id], (t.SpotPrice ?? 0), double.Parse(rateDict[t.id][2]));
|
|
// priceDict[t.UnderlyingId] = t.OptionType == "看涨" ? arr[1] : arr[0];
|
|
// });
|
|
//}
|
|
//else
|
|
//{
|
|
// Dictionary<int, double[]> dict = base.GetUpDownLimitPrice(priceDict);
|
|
// tempTradeList.ForEach(t =>
|
|
// priceDict[t.UnderlyingId] = t.OptionType == "看涨" ? dict[t.id][1] : dict[t.id][0]);
|
|
//}
|
|
|
|
var newTradeRiskResult = CalculatorHelper.CalculateRisksForTrades(
|
|
valueDate: req.settleDate,
|
|
tradeList: tempTradeList,
|
|
calcScenario: CalcScenarioEnum.EodSettlement,
|
|
priceProvider: req.PriceProvider,
|
|
pricingRequest: QdpPricingRequest.BASIC_PRICING,
|
|
addVolRateDic: null,
|
|
volType: null,
|
|
overrideVolsForTrade: vols,
|
|
isUseTradeVol: PS.Config.IsTradeVol,
|
|
preciseTimeMode: req.CalcMarginType != CalcMarginTypeEnum.EodMargin);
|
|
|
|
foreach (var t in tempTradeList)
|
|
{
|
|
var cPv = tradeRiskResult.Results.FirstOrDefault(O => O.ValueResult.TradeId == t.id).ValueResult.Pv;
|
|
var nPv = newTradeRiskResult.Results.FirstOrDefault(O => O.ValueResult.TradeId == t.id).ValueResult.Pv;
|
|
//预付金= 次日最大亏损;
|
|
var value = t.OptionType == "看涨" ? cPv - nPv : cPv + nPv;
|
|
logger.Info($"茂川奇异预付金计算:[{t.TradeType}_{t.BuySell}_{t.OptionType}_{t.Strike}] => 当日Pv:{cPv};次日Pv:{nPv}");
|
|
var tempTradeSpan = new trade_span
|
|
{
|
|
TradeId = t.id,
|
|
ClientId = t.ClientId,
|
|
ValueDate = req.settleDate,
|
|
UnderlyingId = t.UnderlyingId,
|
|
UnderlyingCode = t.UnderlyingCode,
|
|
OptId = req.userId,
|
|
OptName = req.userName,
|
|
OptDate = DateTime.Now,
|
|
Spv1 = value,
|
|
Spv2 = value,
|
|
Spv3 = value,
|
|
Spv4 = value,
|
|
WorstCastClientPayable = value
|
|
};
|
|
resultMap[t.id] = tempTradeSpan;
|
|
}
|
|
|
|
return resultMap.Values.ToList();
|
|
}
|
|
|
|
private List<trade_span> CalculationStructureOptionMargin(RunMarginCalculationReq req)
|
|
{
|
|
var resultMap = new List<trade_span>();
|
|
foreach (var td in req.tradeList)
|
|
{
|
|
var tempId = int.MaxValue;
|
|
td.SubTrades.ToList().ForEach(t => t.id = tempId--);
|
|
var childrenSpans = RunMarginCalculation(req.Clone(td.SubTrades.ToList()));
|
|
|
|
double margin = 0;
|
|
switch (td.StructureType)
|
|
{
|
|
case "牛市价差":
|
|
case "熊市价差":
|
|
case "亚式熊市价差":
|
|
case "三领口组合":
|
|
for (var j = 0; j < childrenSpans.Count; j++)
|
|
{
|
|
margin += childrenSpans[j].WorstCastClientPayable ?? 0;
|
|
}
|
|
break;
|
|
case "跨式组合":
|
|
case "宽跨式组合":
|
|
for (var j = 0; j < childrenSpans.Count; j++)
|
|
{
|
|
var absMargin = Math.Abs(childrenSpans[j].WorstCastClientPayable ?? 0);
|
|
if (margin < absMargin)
|
|
{
|
|
margin = absMargin;
|
|
}
|
|
}
|
|
break;
|
|
case "复制标的资产":
|
|
case "蝶式组合":
|
|
case "飞鹰式组合":
|
|
case "比例价差":
|
|
case "日历价差":
|
|
case "箱式价差":
|
|
case "风险逆转":
|
|
default:
|
|
break;
|
|
}
|
|
var tempTradeSpan = new trade_span
|
|
{
|
|
TradeId = td.id,
|
|
ClientId = td.ClientId,
|
|
ValueDate = req.settleDate,
|
|
UnderlyingId = td.UnderlyingId,
|
|
UnderlyingCode = td.UnderlyingCode,
|
|
OptId = req.userId,
|
|
OptName = req.userName,
|
|
OptDate = DateTime.Now,
|
|
Spv1 = margin,
|
|
Spv2 = margin,
|
|
Spv3 = margin,
|
|
Spv4 = margin,
|
|
WorstCastClientPayable = margin
|
|
};
|
|
resultMap.Add(tempTradeSpan);
|
|
}
|
|
return resultMap;
|
|
}
|
|
|
|
public override List<trade_span> RunMarginCalculation(RunMarginCalculationReq req)
|
|
{
|
|
var resultMap = new List<trade_span>();
|
|
|
|
if (req.tradeList == null || !req.tradeList.Any())
|
|
{
|
|
return resultMap;
|
|
}
|
|
|
|
if (req.forOtherSide)
|
|
{
|
|
return RunMarginCalculationOtherSide(req);
|
|
}
|
|
|
|
var tList = req.tradeList.Where(O => O.TradeType == "结构化交易").ToList();
|
|
|
|
if (tList.Count > 0)
|
|
{
|
|
resultMap.AddRange(CalculationStructureOptionMargin(req.Clone(tList)));
|
|
}
|
|
|
|
tList = req.tradeList.Where(O => O.TradeType == "香草期权" || O.TradeType == "亚式期权").ToList();
|
|
|
|
if (tList.Count > 0)
|
|
{
|
|
resultMap.AddRange(CalculationNormalOptionMargin(req.Clone(tList)));
|
|
}
|
|
|
|
tList = req.tradeList.Where(O => O.TradeType != "结构化交易" && O.TradeType != "香草期权" && O.TradeType != "亚式期权").ToList();
|
|
|
|
if (tList.Count > 0)
|
|
{
|
|
resultMap.AddRange(CalculationOtherOptionMargin(req.Clone(tList)));
|
|
}
|
|
|
|
return resultMap;
|
|
}
|
|
|
|
public override double GetTradeMargin(GetTradeMarginReq req)
|
|
{
|
|
var trade = req.trade;
|
|
|
|
if (trade.TradeType == "结构化交易" && (trade.SubTrades == null || trade.SubTrades.Count() == 0))
|
|
{
|
|
using (var db = new YLContext())
|
|
{
|
|
trade.SubTrades = db.trade.Where(x => x.ParentTradeId == trade.id).ToList();
|
|
}
|
|
}
|
|
|
|
var tradeMargin = RunMarginCalculation(req.GetRunMarginCalculationReq());
|
|
|
|
if (null != tradeMargin)
|
|
{
|
|
return tradeMargin.FirstOrDefault()?.WorstCastClientPayable ?? 0.0;
|
|
}
|
|
|
|
return 0.0;
|
|
}
|
|
}
|
|
}
|