Files
zszq-trs/YLErpDAL/Modules/CalculationModule/HedgePnlCalc.cs
T

529 lines
21 KiB
C#

using YLErp.Abstract.DataProviders;
using YLErp.BLL.Eod;
using YLErp.BLL.Hedge;
using YLErp.DBModels.Helpers;
using YLErp.Modules.CalculationModule.Abstract;
using YLErp.Modules.VolatilityModule;
namespace YLErp.Modules.CalculationModule
{
/// <summary>
/// 对冲交易盈亏计算
/// </summary>
public class HedgePnlCalc
{
readonly IHedgePnlCalcContext _context;
readonly IUnderlyingDataProvider _unDataProvider;
readonly IPriceProvider _unPriceProvider;
readonly IPriceProvider _exchangeOptionPriceProvider;
readonly IPriceProvider _unSettlePriceProvider;
public HedgePnlCalc(IHedgePnlCalcContext context)
{
_context = CalcCheckHelper.CheckHedgePnlCalcContext(context);
_unDataProvider = context.UnderlyingDataProvider;
_unPriceProvider = context.UnderlyingPriceProvider;
_unSettlePriceProvider = context.UnderlyingSettlePriceProvider;
_exchangeOptionPriceProvider = context.ExchangeOptionPriceProvider;
}
/// <summary>
/// 计算对冲交易Pnl信息
/// </summary>
/// <param name="newHedgeTrades">当日对冲交易数据</param>
/// <param name="eodPositions">上日持仓数据</param>
public IEnumerable<HedgePnl> Calculate(IEnumerable<ExchangeTrade> newHedgeTrades, IEnumerable<EodTradePosition> eodPositions)
{
if (newHedgeTrades == null && eodPositions == null)
{
return Enumerable.Empty<HedgePnl>();
}
var pnlResults = new List<HedgePnl>();
//-----------------------------------------
// 处理昨日持仓盈亏
//-----------------------------------------
if (eodPositions != null)
{
foreach (var eodPosition in eodPositions)
{
ExchangeListOption exchangeOption = null;
if (eodPosition.TradeType == "场内期权")
{
exchangeOption = _unDataProvider.GetExchange_List_Option(eodPosition.ExchangeOptionCode);
//剔除已到期场内期权持仓
if (exchangeOption == null || exchangeOption.MaturityDate < _context.ValueDate)
{
continue;
}
}
else if (!(ConsTrade.TradeTypesForHedge.Contains(eodPosition.TradeType)||ConsTrade.BondTypeList.Contains(eodPosition.TradeType)))
{
continue;
}
var eodPnl = ProcessEodPositionHegePnl(eodPosition, exchangeOption);
pnlResults.Add(eodPnl);
}
}
//-----------------------------------------
// 处理当日对冲交易盈亏
//-----------------------------------------
if (newHedgeTrades != null && newHedgeTrades.Any())
{
//对冲交易手续费计算
var tradeCommissionDict = _context.CommissionCalc.GetTradeCommission(newHedgeTrades);
foreach (var newTrade in newHedgeTrades)
{
//手续费
var commission = tradeCommissionDict.GetTradeCommission(newTrade.id);
//标的价格
_unPriceProvider.TryGetPrice(newTrade.UnderlyingCode, out var underlyingPrice);
//计算
ProcessNewTradeHegePnlV2(pnlResults, newTrade, commission, underlyingPrice);
}
}
//-----------------------------------------
// 场内期权对冲盈利
//-----------------------------------------
var tempTradeId = 0;
foreach (var pnl in pnlResults)
{
var underlying = _unDataProvider.GetUnderlying(pnl.UnderlyingCode, out var contractSize);
if (underlying == null)
{
continue;
}
if (pnl.TradeType == "场内期权")
{
tempTradeId--;
ProcessExchangeOptionPnl(pnl, underlying, tempTradeId);
}
else
{
pnl.Delta = pnl.Notional;
pnl.DeltaCash = pnl.Pv;
pnl.DeltaInLots = pnl.Delta / contractSize;
pnl.Lots = pnl.Notional / contractSize;
}
}
return pnlResults;
}
/// <summary>
/// 昨日持仓对冲盈利
/// </summary>
private HedgePnl ProcessEodPositionHegePnl(EodTradePosition eodPosition, ExchangeListOption exchangeOption)
{
double lastPv = 0, pv = 0, dailyPnl = 0, realizedPnL = 0, cost = 0;
_unPriceProvider.TryGetPrice(eodPosition.UnderlyingCode, out var SettlePrice);
var notional = eodPosition.Amount;
if (Math.Abs(notional) > 0)
{
lastPv = eodPosition.Pv;
pv = SettlePrice * eodPosition.Amount;
dailyPnl = pv - lastPv;
realizedPnL = eodPosition.ClosedPnL;
cost = eodPosition.Cost;
}
else
{
notional = 0;
}
var uniqueCode = GetHedgeUniqueCode(eodPosition.BookId, eodPosition.TradeType, eodPosition.PositionType, eodPosition.UnderlyingCode, eodPosition.ExchangeOptionCode);
var lastTotalPnl = eodPosition.TotalPnL;
var eodPnl = new HedgePnl
{
BookId = eodPosition.BookId,
ValueDate = _context.ValueDate,
TradeType = eodPosition.TradeType,
PositionType = eodPosition.PositionType,
CallPut = TradeHelper.GetCallPut(exchangeOption?.OptionType),
BuySell = eodPosition.BuySell,
UnderlyingId = eodPosition.UnderlyingId,
UnderlyingCode = eodPosition.UnderlyingCode,
HedgeUniqueCode = uniqueCode,
Notional = notional,
LastPv = lastPv,
Pv = pv,
DailyPnL = dailyPnl,
RealizedPnL = realizedPnL,
TotalPnl = lastTotalPnl + dailyPnl,
LastTotalPnl = lastTotalPnl,
Cost = cost,
Commission = eodPosition.Commission,
Strike = exchangeOption?.Strike ?? 0,
ExchangeOptionCode = eodPosition.ExchangeOptionCode,
SettlePrice = SettlePrice,
PositionPnl = eodPosition.PositionPnL,
ExerciseDate = exchangeOption?.MaturityDate
};
if (Math.Abs(eodPnl.Strike) < 1e-7 && exchangeOption != null)
{
eodPnl.Strike = exchangeOption.Strike;
eodPnl.BuySell = "long".Equals(eodPosition.PositionType) ? "买入" : "卖出";
}
return eodPnl;
}
/// <summary>
/// 当日交易对冲盈利V2
/// </summary>
private HedgePnl ProcessNewTradeHegePnlV2(List<HedgePnl> pnlResults, ExchangeTrade newTrade, double newTradeCommission, double underlyingPrice)
{
if (newTrade.Notional < 0)
{
throw new ServiceFaultException($"[场内交易数据,id:{newTrade.id},成交份额:{newTrade.Notional}]不允许成交份额小于0的场内交易数据存在!");
}
//持仓类型 多头 空头分割
var positionType = GetHedgeLongShort(newTrade.TradeType, newTrade.TradeSide);
//对冲唯一编码(簿记账户ID_结构类型_持仓类型_合约代码)
var uniqueCode = GetHedgeUniqueCode(newTrade.AssetBookId, newTrade.TradeType, positionType, newTrade.UnderlyingCode, newTrade.OptionCode);
//获取是否存在对应uniqueCode的对冲信息
var eodPnl = pnlResults.FirstOrDefault(t => t.HedgeUniqueCode == uniqueCode);
//新的pnl(如果eodPnl存在则使用eodPnl)
var newPnl = eodPnl;
if (eodPnl == null)
{
newPnl = new HedgePnl
{
BookId = newTrade.AssetBookId,
ValueDate = _context.ValueDate,
TradeType = newTrade.TradeType,
PositionType = positionType,
CallPut = TradeHelper.GetCallPut(newTrade.OptionType),
BuySell = positionType == "long" ? "买入" : "卖出",
UnderlyingId = newTrade.UnderlyingId,
UnderlyingCode = newTrade.UnderlyingCode,
HedgeUniqueCode = uniqueCode,
Strike = newTrade.OptionStrike ?? 0,
ExchangeOptionCode = newTrade.OptionCode,
SettlePrice = underlyingPrice,
ExerciseDate = newTrade.MaturityDate
};
pnlResults.Add(newPnl);
if (newTrade.TradeType == "场内期权")
{
//场内期权的buysell不影响qdp计算
newPnl.BuySell = newTrade.TradeSide;
}
}
else
{
newPnl.LastPv = eodPnl.LastPv; //昨市值
}
//持仓符号和新交易符号
var posSign = newPnl.Notional < 0 ? -1 : 1;
var newSign = EodOperationBase.GetSign(newTrade.TradeSide);
double newNotional = newTrade.Notional, openNotional = 0d;
//平仓处理
if (newSign != posSign)
{
var closeNotional = Math.Abs(newPnl.Notional);
if (newNotional > closeNotional)
{
//平仓超出部分需要变成开仓
openNotional = newNotional - closeNotional;
}
else
{
closeNotional = newNotional;
}
if (closeNotional > 0)
{
//开仓金额(带符号)
var openAmount = closeNotional * posSign * newPnl.Cost / newPnl.Notional;
//平仓金额(带符号)
var closeAmount = closeNotional * newSign * newTrade.TradeSinglePrice;
//平仓盈亏
var closeProfit = -(openAmount + closeAmount);
//已实现盈亏
newPnl.RealizedPnL += closeProfit;
//减去持仓成本
newPnl.Cost -= newTrade.TradeAmount* newTrade.TradeSinglePrice;
//减去持仓份额
newPnl.Notional -= closeNotional * posSign;
}
}
else
{
openNotional = newNotional;
}
//开仓处理
if (openNotional > 0)
{
//转换为带符号的值
openNotional *= newSign;
//累加开仓份额
newPnl.Notional += openNotional;
//累加开仓成本
newPnl.Cost += openNotional * newTrade.TradeSinglePrice;
}
var newNotional_s = newNotional * newSign;
var newCost = (newTrade.TradeSinglePrice * newNotional_s) + newTradeCommission;
var newPv = newNotional_s * underlyingPrice;
var newDailyPnl = newPv - newCost;
newPnl.Cost += newTradeCommission; //成本加上手续费
newPnl.Commission += newTradeCommission; //累加总手续费
newPnl.DailyPnL += newDailyPnl; //当日盈亏
newPnl.TotalPnl += newDailyPnl; //总盈亏
newPnl.Pv += newPv; //总市值
newPnl.TdCost += newCost; //当日成本--主要用于下方的场内期权盈亏计算
return newPnl;
}
#region----场内期权对冲盈利----
private void ProcessExchangeOptionPnl(HedgePnl pnl, underlying_manager underlying, int tempTradeId)
{
TradeValueResult optionResult = null;
//场内期权合约信息
var exchangeOption = (_unDataProvider.GetExchange_List_Option(pnl.ExchangeOptionCode)?.Clone())
?? throw new HedgePnlCalcException($"场内期权合约[{pnl.ExchangeOptionCode}]在场内期权合约信息表中不存在!");
if (exchangeOption.MaturityDate < _context.ValueDate)
{
throw new HedgePnlCalcException($"场内期权合约'{pnl.ExchangeOptionCode}'已过期,合约到期日:{exchangeOption.MaturityDate:yyyy-MM-dd},估值日期:{_context.ValueDate:yyyy-MM-dd}");
}
if (exchangeOption.UnderlyingCode.StartsWith("IO"))
{
exchangeOption.UnderlyingCode = "000300.SH";
}
//pnl callput
pnl.CallPut = TradeHelper.GetCallPut(exchangeOption.OptionType);
//场内期权 合约乘数
var contractSize = underlying.ContractSize;
if (exchangeOption.ContractSize > 1e-6)
{
contractSize = exchangeOption.ContractSize;
}
//pnl持仓手数
pnl.Lots = pnl.Notional / contractSize;
//更新场内期权市场价格
pnl.ExOptionPrice = _exchangeOptionPriceProvider.TryGetPrice(pnl.ExchangeOptionCode, out var price) ? price : null;
//是否有持仓
var hasPosition = Math.Abs(pnl.Notional) > 1e-10;
if (hasPosition)
{
//使用曲面波动率或者隐含波动率计算期权风险
optionResult = InnerCalcExchangeOptionRisks(tempTradeId, pnl, underlying, exchangeOption);
}
if (optionResult != null && optionResult.Succeeded)
{
pnl.Vol = optionResult.Vol;
pnl.Delta = optionResult.Delta;
pnl.DeltaT1 = optionResult.DeltaT1;
pnl.SA_Delta = optionResult.SA_Delta;
pnl.Gamma = optionResult.Gamma;
pnl.Vega = optionResult.Vega;
pnl.DeltaCash = optionResult.DeltaCash;
pnl.GammaCash = optionResult.GammaCash;
pnl.DeltaInLots = optionResult.Delta / contractSize;
pnl.GammaInLots = optionResult.Gamma / contractSize;
pnl.Theta = optionResult.Theta;
pnl.Rho = optionResult.Rho * 100;
pnl.DdeltaDt = optionResult.DDeltaDt;
pnl.DdeltaDvol = optionResult.DDeltaDVol;
pnl.DvegaDt = optionResult.DVegaDt;
pnl.DvegaDvol = optionResult.DVegaDVol;
pnl.Pv = optionResult.Pv;
pnl.TimeValue = optionResult.TimeValue;
}
else
{
pnl.Pv = 0;
}
var flag = _context.ExchangeOptionPriceUseFlag;
if (PS.Config.Is光大光子)
{
flag = flag == ExchangeOptionPriceUseFlag.TrialCalclMode
? ExchangeOptionPriceUseFlag.SetExOptionPrice : ExchangeOptionPriceUseFlag.CalcPv;
}
if (flag == ExchangeOptionPriceUseFlag.CalcPv && hasPosition)
{
pnl.Pv = (pnl.ExOptionPrice * pnl.Notional) ?? 0;
var intrinsicValue = ConsGlobal.CallPut.IsCall(pnl.CallPut) ? pnl.SettlePrice - pnl.Strike : pnl.Strike - pnl.SettlePrice;
pnl.TimeValue = pnl.Pv - Math.Max(0, intrinsicValue) * pnl.Notional;
}
pnl.DailyPnL = pnl.Pv - pnl.LastPv - pnl.TdCost;
pnl.TotalPnl = pnl.LastTotalPnl + pnl.DailyPnL;
}
//场内期权估值计算,pnl在此方法中会对CallPut、Lots、ExOptionPrice赋值处理
private TradeValueResult InnerCalcExchangeOptionRisks(int tempTradeId, HedgePnl pnl, underlying_manager underlying, ExchangeListOption exchangeOption)
{
var tempTrade = new trade
{
TradeType = pnl.TradeType,
UnderlyingCode = pnl.UnderlyingCode,
UnderlyingId = pnl.UnderlyingId,
TradeDate = _context.ValueDate,
BuySell = pnl.BuySell,
StartDate = _context.ValueDate,
ExerciseDate = exchangeOption.MaturityDate,
MaturityDate = underlying.MaturityDate,
TradePrice = Math.Abs(pnl.Cost),
TradeStatus = "确认成交",
ExerciseMode = exchangeOption.ExerciseMode,
OptionType = exchangeOption.OptionType,
Strike = pnl.Strike,
Notional = pnl.Notional,
UnderlyingInstrumentType = underlying.UnderlyingInstrumentType,
ExchangeOptionCode = pnl.ExchangeOptionCode,
AssetId = pnl.BookId,
id = tempTradeId,
UnderlyingAssetClass = underlying.UnderlyingType,
//用于反算隐含波动率
StructureType = "场内期权",
TradeSinglePrice = pnl.ExOptionPrice
};
if (PS.Config.ErpElement.ExchangeOptionVolType == Configuration.Enums.ExchangeOptionVolType.ImpliedVol)
{
_unSettlePriceProvider.TryGetPrice(pnl.UnderlyingCode, out var price);
tempTrade.Vol = VolatilityHelper.GetImpliedVol(_context.ValueDate, tempTrade, null, price.Normalize(), _context.IsEodCalc);
}
var optionCalcContext = _context.CreateOptionCalculateContext();
var optionResult = TradeRiskCalcUtil.CalcTradeRisk(tempTrade, optionCalcContext, out _);
if (optionResult != null)
{
if (optionResult.FailReason == TradeValueFailReason.missingVol)
{
if (PS.Config.Is光大光子 && _context.CalcScenario == Enums.CalcScenarioEnum.EodSettlement && _context.VolType == "对冲")
{
var errmsg = $"[光子对冲收盘 {_context.ValueDate:yyyy-MM-dd}]场内期权'{pnl.ExchangeOptionCode}' 找不到波动率!";
throw new HedgePnlCalcException(errmsg);
}
}
if (!optionResult.Succeeded)
{
var errmsg = $"{_context.ValueDate:yyyy-MM-dd},场内期权'{pnl.ExchangeOptionCode}' 计算失败,{optionResult.ErrorMessage},fail reason:{optionResult.FailReason}";
if (_context.CalcScenario == Enums.CalcScenarioEnum.EodSettlement)
{
LogFactory.GetLogger(nameof(HedgePnlCalc)).Error(errmsg);
}
else
{
//其他场景可能会产生大量重复日志,为了避免这种情况使用debug方式输出
LogFactory.GetLogger(nameof(HedgePnlCalc)).Debug(errmsg);
}
}
}
else
{
var errmsg = $"{_context.ValueDate:yyyy-MM-dd},场内期权'{pnl.ExchangeOptionCode}' 获取不到计算结果";
if (_context.CalcScenario == Enums.CalcScenarioEnum.EodSettlement)
{
LogFactory.GetLogger(nameof(HedgePnlCalc)).Error(errmsg);
}
else
{
LogFactory.GetLogger(nameof(HedgePnlCalc)).Debug(errmsg);
}
}
return optionResult;
}
#endregion
/// <summary>
/// 根据对冲账号 簿记账户I 结构类型 买卖方向 标的代码 场内期权代码 编制对冲唯一编码
/// </summary>
public static string GetHedgeUniqueCode(int BookId, string TradeType, string LongShort, string UnderlyingCode, string ExchangeOptionCode = null)
{
List<string> tradeTypes=new List<string>() { "利率债", "信用债", "其它债券" };
if (tradeTypes.Contains(TradeType))
{
return $"{BookId}_{TradeType}_{UnderlyingCode}".ToUpperInvariant();
}
return $"{BookId}_{TradeType}_{LongShort}_{("场内期权".Equals(TradeType) ? ExchangeOptionCode : UnderlyingCode)}".ToUpperInvariant();
}
/// <summary>
/// 对冲交易 根据结构类型 买卖方向 看涨看跌 获取持仓long short
/// </summary>
public static string GetHedgeLongShort(string TradeType, string BuySell)
{
switch (TradeType)
{
case "商品期货":
case "商品现货":
case "场内期权":
case "利率债":
case "信用债":
case "其它债券":
return BuySell.Contains("多头") ? "long" : "short";
case "股票":
default: return "long";
}
}
}
/// <summary>
/// 对冲交易计算异常
/// </summary>
public class HedgePnlCalcException : Exception
{
public HedgePnlCalcException(string message) : base(message)
{
}
public HedgePnlCalcException(string message, Exception innerException) : base(message, innerException)
{
}
}
}