496 lines
18 KiB
C#
496 lines
18 KiB
C#
using Qdp.Pricing.Base.Utilities;
|
||
using YLErp.DBModels;
|
||
using YLErp.QdpModule;
|
||
|
||
namespace YLErp.Modules.TradeModule.ExoticOptionModule
|
||
{
|
||
/// <summary>
|
||
/// 专业版雪球观察帮助类
|
||
/// </summary>
|
||
public class SpecialSnowballObservationHelper
|
||
{
|
||
private readonly OtcTradeBase _otcTrade;
|
||
private readonly trade_snowball _snowball;
|
||
|
||
public SpecialSnowballObservationHelper(OtcTradeBase otcTrade, trade_snowball snowball)
|
||
{
|
||
_otcTrade = otcTrade ?? throw new ArgumentNullException(nameof(otcTrade));
|
||
_snowball = snowball ?? throw new ArgumentNullException(nameof(snowball));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取雪球观察结果(买方)
|
||
/// </summary>
|
||
public SnowballObservationResult GetObservationResultForBuySide(DateTime valueDate, double closePrice, double tradeNotional)
|
||
{
|
||
System.Diagnostics.Debug.Assert(_snowball.KnockInOutStatus != ConsTrade.KnockState.KnockedOut);
|
||
|
||
//敲出检查
|
||
|
||
var koResult = GetKoPayoff(valueDate, closePrice, tradeNotional, out var isKoObservationDay, out var koBarrier);
|
||
|
||
if (koResult != null)
|
||
{
|
||
return koResult;
|
||
}
|
||
|
||
var resultType = _snowball.IsInitialKnockedIn || _snowball.KnockInOutStatus == ConsTrade.KnockState.KnockedIn
|
||
? SnowballObservationResultType.KnockedIn
|
||
: (isKoObservationDay ? SnowballObservationResultType.Monitoring : SnowballObservationResultType.NonObservationDay);
|
||
|
||
//敲入检查
|
||
if (resultType != SnowballObservationResultType.KnockedIn && IsNeedCheckKnockIn(valueDate))
|
||
{
|
||
var kiBarrier = _otcTrade.IsMoneynessOptionData ? _snowball.KIBarrier * (_otcTrade.SpotPrice ?? 0) : _snowball.KIBarrier;
|
||
|
||
// 发生敲入事件(看涨 - 向下敲入,看跌 - 向上敲入)
|
||
|
||
resultType = (ConsGlobal.CallPut.IsCall(_otcTrade.CallPut) ? closePrice <= kiBarrier : closePrice >= kiBarrier)
|
||
? SnowballObservationResultType.KnockedIn
|
||
: SnowballObservationResultType.Monitoring;
|
||
}
|
||
|
||
SnowballObservationResult result;
|
||
|
||
//到期检查
|
||
if (valueDate == _otcTrade.ExerciseDate.Value)
|
||
{
|
||
result = resultType == SnowballObservationResultType.KnockedIn
|
||
? GetKiPayoffAtEndDate(closePrice, tradeNotional) : GetNKiPayoffAtEndDate(tradeNotional);
|
||
}
|
||
else
|
||
{
|
||
result = new SnowballObservationResult { ResultType = resultType };
|
||
}
|
||
|
||
result.KoBarrier = koBarrier ??
|
||
(_otcTrade.IsMoneynessOptionData ? _snowball.KOBarrier * (_otcTrade.SpotPrice ?? 0) : _snowball.KOBarrier);
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取雪球观察结果(交易员角度)
|
||
/// </summary>
|
||
public SnowballObservationResult GetObservationResultForTraderSide(DateTime valueDate, double closePrice, double tradeNotional)
|
||
{
|
||
var result = GetObservationResultForBuySide(valueDate, closePrice, tradeNotional);
|
||
|
||
if (_otcTrade.BuySell == "卖出")
|
||
{
|
||
result.PaymentAmount = -result.PaymentAmount;
|
||
|
||
result.Payoff?.InvertSign();
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取累积票息(tradeNotional为负值时是卖方角度)
|
||
/// </summary>
|
||
public PrepaymentSnowballKoPayoff GetEffectiveObservation(DateTime valueDate, double tradeNotional)
|
||
{
|
||
var koObservationParseResult = ParseKoObservation(valueDate);
|
||
|
||
//非敲出观察日
|
||
if (koObservationParseResult == null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
var payoff = new PrepaymentSnowballKoPayoff
|
||
{
|
||
CouponStartDate = _otcTrade.StartDate.Value,
|
||
CouponEndDate = valueDate,
|
||
CouponRate = koObservationParseResult.KoRebate,
|
||
//票息支付金额
|
||
CouponPaymentAmount = koObservationParseResult.KoRebate * tradeNotional * (_otcTrade.SpotPrice ?? 0)
|
||
};
|
||
|
||
//票息计息年化方式处理
|
||
if (!string.IsNullOrWhiteSpace(_snowball.CouponDayCount))
|
||
{
|
||
//首日是否计息
|
||
var startDate = _snowball.CouponIncludeStartDate == true
|
||
? _otcTrade.StartDate.Value.AddDays(-1) : _otcTrade.StartDate.Value;
|
||
|
||
//是否支付日计息
|
||
var endDate = valueDate;
|
||
if (_snowball.CouponUsePaymentDate == true)
|
||
{
|
||
endDate = GetKoPaymentDate(_snowball.KOObservationSettleDates, koObservationParseResult.KoDateIndex, valueDate);
|
||
}
|
||
|
||
//终日是否计息
|
||
if (_snowball.CouponIncludeEndDate == false)
|
||
{
|
||
endDate = endDate.AddDays(-1);
|
||
}
|
||
|
||
//票息年化计息
|
||
var couponDayCount = _snowball.CouponDayCount.ToDayCountImpl();
|
||
var dayFraction = couponDayCount.CalcDayCountFraction(startDate, endDate);
|
||
payoff.CouponPaymentAmount *= dayFraction;
|
||
}
|
||
|
||
return payoff;
|
||
}
|
||
|
||
#region----到期日敲入收益----
|
||
|
||
/// <summary>
|
||
/// 获取到期日敲入收益
|
||
/// </summary>
|
||
private SnowballObservationResult GetKiPayoffAtEndDate(double closePrice, double tradeNotional)
|
||
{
|
||
var strike = _otcTrade.IsMoneynessOption == "是"
|
||
? (_snowball.SpreadStrikeAtMaturity1 ?? 0) * (_otcTrade.SpotPrice ?? 0)
|
||
: _snowball.SpreadStrikeAtMaturity1 ?? 0;
|
||
|
||
//敲入转香草期权,买卖方向需要反一下,通过notional体现
|
||
var notional = -tradeNotional * (_snowball.KIParticipationRate ?? 1);
|
||
|
||
var isCall = ConsGlobal.CallPut.IsCall(_otcTrade.OptionType);
|
||
|
||
var payoff = new PrepaymentSnowballKiPayoff();
|
||
|
||
//期权支付
|
||
|
||
if (_snowball.PrincipalProtectionRate.HasValue && _snowball.PrincipalProtectionRate.Value != 0)
|
||
{
|
||
var spread = (1 - _snowball.PrincipalProtectionRate.Value) * (_otcTrade.SpotPrice ?? 0);
|
||
|
||
if (isCall)
|
||
{
|
||
//熊市价差
|
||
payoff.OptionPaymentAmount = Math.Min(Math.Max(strike - closePrice, 0), spread) * notional;
|
||
}
|
||
else
|
||
{
|
||
//牛市价差
|
||
payoff.OptionPaymentAmount = Math.Min(Math.Max(closePrice - strike, 0), spread) * notional;
|
||
}
|
||
}
|
||
else if (isCall)
|
||
{
|
||
//看跌期权
|
||
payoff.OptionPaymentAmount = Math.Max(strike - closePrice, 0) * notional;
|
||
}
|
||
else
|
||
{
|
||
//看涨期权
|
||
payoff.OptionPaymentAmount = Math.Max(closePrice - strike, 0) * notional;
|
||
}
|
||
|
||
//预付金返还
|
||
if (_snowball.PrepaymentRatio.HasValue)
|
||
{
|
||
payoff.PrepaymentAmount = tradeNotional * (_otcTrade.SpotPrice ?? 0) * _snowball.PrepaymentRatio.Value;
|
||
|
||
//利息金额
|
||
if (_snowball.PrepaymentInterestRate.HasValue)
|
||
{
|
||
payoff.InterestAmount = payoff.PrepaymentAmount * _snowball.PrepaymentInterestRate.Value;
|
||
|
||
//年化计息
|
||
if (!string.IsNullOrWhiteSpace(_snowball.CouponDayCount))
|
||
{
|
||
//首日是否计息
|
||
var startDate = _snowball.CouponIncludeStartDate == true
|
||
? _otcTrade.StartDate.Value.AddDays(-1) : _otcTrade.StartDate.Value;
|
||
|
||
//终日是否计息
|
||
var endDate = _snowball.CouponIncludeEndDate == true
|
||
? _otcTrade.ExerciseDate.Value : _otcTrade.ExerciseDate.Value.AddDays(-1);
|
||
|
||
var couponDayCount = _snowball.CouponDayCount.ToDayCountImpl();
|
||
|
||
var dayFraction = couponDayCount.CalcDayCountFraction(startDate, endDate);
|
||
|
||
payoff.InterestAmount *= dayFraction;
|
||
}
|
||
}
|
||
}
|
||
|
||
return new SnowballObservationResult
|
||
{
|
||
Payoff = payoff,
|
||
PaymentAmount = payoff.TotalAmount(),
|
||
PaymentDate = _otcTrade.ExerciseDate.Value,
|
||
ResultType = SnowballObservationResultType.KiPayoffAtEndDate
|
||
};
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region----获取到期日未敲入收益----
|
||
|
||
/// <summary>
|
||
/// 获取到期日未敲入收益
|
||
/// </summary>
|
||
private SnowballObservationResult GetNKiPayoffAtEndDate(double tradeNotional)
|
||
{
|
||
var notionalValue = tradeNotional * (_otcTrade.SpotPrice ?? 0);
|
||
|
||
var payoff = new PrepaymentSnowballNkiPayoff();
|
||
|
||
//红利票息支付金额
|
||
payoff.CouponPaymentAmount = _snowball.Coupon * notionalValue;
|
||
|
||
//预付金返还
|
||
if (_snowball.PrepaymentRatio.HasValue)
|
||
{
|
||
payoff.PrepaymentAmount = notionalValue * _snowball.PrepaymentRatio.Value;
|
||
|
||
//利息金额
|
||
if (_snowball.PrepaymentInterestRate.HasValue)
|
||
{
|
||
payoff.InterestAmount = payoff.PrepaymentAmount * _snowball.PrepaymentInterestRate.Value;
|
||
}
|
||
}
|
||
|
||
//年化计息
|
||
if (!string.IsNullOrWhiteSpace(_snowball.CouponDayCount))
|
||
{
|
||
//首日是否计息
|
||
var startDate = _snowball.CouponIncludeStartDate == true
|
||
? _otcTrade.StartDate.Value.AddDays(-1) : _otcTrade.StartDate.Value;
|
||
|
||
//终日是否计息
|
||
var endDate = _snowball.CouponIncludeEndDate == true
|
||
? _otcTrade.ExerciseDate.Value : _otcTrade.ExerciseDate.Value.AddDays(-1);
|
||
|
||
var couponDayCount = _snowball.CouponDayCount.ToDayCountImpl();
|
||
|
||
var dayFraction = couponDayCount.CalcDayCountFraction(startDate, endDate);
|
||
|
||
payoff.InterestAmount *= dayFraction;
|
||
|
||
payoff.CouponPaymentAmount *= dayFraction;
|
||
}
|
||
|
||
return new SnowballObservationResult
|
||
{
|
||
Payoff = payoff,
|
||
PaymentAmount = payoff.TotalAmount(),
|
||
PaymentDate = _otcTrade.ExerciseDate.Value,
|
||
ResultType = SnowballObservationResultType.NkiPayoffAtEndDate
|
||
};
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region----获取敲出收益----
|
||
|
||
/// <summary>
|
||
/// 获取敲出收益
|
||
/// </summary>
|
||
private SnowballObservationResult GetKoPayoff(DateTime valueDate, double closePrice, double tradeNotional
|
||
, out bool isObservationDay, out double? koBarrier)
|
||
{
|
||
var koObservationParseResult = ParseKoObservation(valueDate);
|
||
|
||
koBarrier = koObservationParseResult?.KoBarrier;
|
||
|
||
isObservationDay = koObservationParseResult != null;
|
||
|
||
//非敲出观察日
|
||
if (!isObservationDay)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
var isCall = ConsGlobal.CallPut.IsCall(_otcTrade.CallPut);
|
||
|
||
// 发生敲出事件(看涨 - 向上敲出支付票息,看跌 - 向下敲出支付票息)
|
||
|
||
if (isCall ? closePrice < koObservationParseResult.KoBarrier : closePrice > koObservationParseResult.KoBarrier)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
var payoff = new PrepaymentSnowballKoPayoff
|
||
{
|
||
CouponStartDate = _otcTrade.StartDate.Value,
|
||
CouponEndDate = valueDate,
|
||
CouponRate = koObservationParseResult.KoRebate
|
||
};
|
||
|
||
var notionalValue = tradeNotional * (_otcTrade.SpotPrice ?? 0);
|
||
|
||
//增强收益支付金额
|
||
if (_snowball.EnhancedParticipationRate.HasValue)
|
||
{
|
||
payoff.EnhancedPaymentAmount = (closePrice - koObservationParseResult.KoBarrier) * tradeNotional * _snowball.EnhancedParticipationRate.Value;
|
||
if (!isCall)
|
||
{
|
||
payoff.EnhancedPaymentAmount = -payoff.EnhancedPaymentAmount;
|
||
}
|
||
}
|
||
|
||
//票息支付金额
|
||
payoff.CouponPaymentAmount = koObservationParseResult.KoRebate * notionalValue;
|
||
|
||
//预付金返还
|
||
if (_snowball.PrepaymentRatio.HasValue)
|
||
{
|
||
payoff.PrepaymentAmount = notionalValue * _snowball.PrepaymentRatio.Value;
|
||
|
||
//利息金额
|
||
if (_snowball.PrepaymentInterestRate.HasValue)
|
||
{
|
||
payoff.InterestAmount = payoff.PrepaymentAmount * _snowball.PrepaymentInterestRate.Value;
|
||
}
|
||
}
|
||
|
||
//票息支付日
|
||
var paymentDate = GetKoPaymentDate(_snowball.KOObservationSettleDates, koObservationParseResult.KoDateIndex, valueDate);
|
||
|
||
//票息计息年化方式处理
|
||
if (!string.IsNullOrWhiteSpace(_snowball.CouponDayCount))
|
||
{
|
||
//首日是否计息
|
||
var startDate = _snowball.CouponIncludeStartDate == true
|
||
? _otcTrade.StartDate.Value.AddDays(-1) : _otcTrade.StartDate.Value;
|
||
|
||
//是否支付日计息
|
||
var endDate = valueDate;
|
||
if (_snowball.CouponUsePaymentDate == true)
|
||
{
|
||
endDate = paymentDate;
|
||
}
|
||
|
||
//终日是否计息
|
||
if (_snowball.CouponIncludeEndDate == false)
|
||
{
|
||
endDate = endDate.AddDays(-1);
|
||
}
|
||
|
||
payoff.CouponEndDate = endDate;
|
||
|
||
//票息年化计息
|
||
var couponDayCount = _snowball.CouponDayCount.ToDayCountImpl();
|
||
var dayFraction = couponDayCount.CalcDayCountFraction(startDate, endDate);
|
||
payoff.CouponPaymentAmount *= dayFraction;
|
||
|
||
//年化利息金额
|
||
payoff.InterestAmount *= dayFraction;
|
||
}
|
||
|
||
//返回敲出观察结果
|
||
var result = new SnowballObservationResult
|
||
{
|
||
Payoff = payoff,
|
||
PaymentDate = paymentDate,
|
||
ResultType = SnowballObservationResultType.KoPayoff,
|
||
PaymentAmount = payoff.TotalAmount(),
|
||
KoBarrier = koObservationParseResult.KoBarrier
|
||
};
|
||
|
||
if (_snowball.KORebateType == RebateTypeEnum.AtEnd)
|
||
{
|
||
result.PaymentDate = _otcTrade.ExerciseDate.Value;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 解析敲出观察频率
|
||
/// </summary>
|
||
private KoObservationParseResult ParseKoObservation(DateTime valueDate)
|
||
{
|
||
(var koDates, var koBarries, var koCoupons)
|
||
= QdpHelper.ParseAutocallCustomizedInfo(_snowball.KOObservationDates);
|
||
|
||
koDates ??= QdpObservationHelper.GetDefaultKoObservationDatesForSnowbalV2(_otcTrade.StartDate.Value, _otcTrade.ExerciseDate.Value);
|
||
|
||
var koDateIndex = koDates.Select(x => x.DateTime).ToList().IndexOf(valueDate);
|
||
|
||
if (koDateIndex < 0)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
var result = new KoObservationParseResult
|
||
{
|
||
KoDateIndex = koDateIndex,
|
||
KoRebate = koCoupons != null && koCoupons.Length > koDateIndex ? koCoupons[koDateIndex] : _snowball.KORebate,
|
||
KoBarrier = koBarries != null && koBarries.Length > koDateIndex ? koBarries[koDateIndex] : _snowball.KOBarrier
|
||
};
|
||
|
||
if (_otcTrade.IsMoneynessOptionData)
|
||
{
|
||
result.KoBarrier *= _otcTrade.SpotPrice ?? 1.0;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取票息支付日
|
||
/// </summary>
|
||
private static DateTime GetKoPaymentDate(string paymentDatesStr, int koDateIndex, DateTime koObservationDate)
|
||
{
|
||
var paymentDates = string.IsNullOrWhiteSpace(paymentDatesStr) ? null
|
||
: paymentDatesStr.Split(new char[] { ',', ';', ',', ';' }).Select(x => DateTime.Parse(x)).ToArray();
|
||
|
||
if (paymentDates != null && paymentDates.Length > koDateIndex)
|
||
{
|
||
var paymentDate = paymentDates[koDateIndex];
|
||
|
||
//预防错误的支付日数据
|
||
if (paymentDate < koObservationDate)
|
||
{
|
||
paymentDate = koObservationDate;
|
||
}
|
||
|
||
return paymentDate;
|
||
}
|
||
|
||
return koObservationDate;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否需要观察敲入
|
||
/// </summary>
|
||
private bool IsNeedCheckKnockIn(DateTime observationDate)
|
||
{
|
||
//已在观察日之前敲入则不需要观察是否敲入
|
||
if (_snowball.KnockInOutStatus == ConsTrade.KnockState.KnockedIn && _snowball.KnockInOutDate < observationDate)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
//仅在到期日观察
|
||
if (_snowball.KIObservationType == KIObservationType.OnlyEndDate)
|
||
{
|
||
return observationDate == _otcTrade.ExerciseDate;
|
||
}
|
||
|
||
//每日观察
|
||
return true;
|
||
}
|
||
|
||
class KoObservationParseResult
|
||
{
|
||
/// <summary>
|
||
/// 观察日在敲出观察频率中的索引
|
||
/// </summary>
|
||
public int KoDateIndex { get; set; }
|
||
|
||
/// <summary>
|
||
/// 敲出障碍价格
|
||
/// </summary>
|
||
public double KoBarrier { get; set; }
|
||
|
||
/// <summary>
|
||
/// 敲出支付票息率
|
||
/// </summary>
|
||
public double KoRebate { get; set; }
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|