Files
zszq-trs/YLErpDAL/Modules/TradeModule/ObservationModule/OptionObservationService.cs
T
2024-05-09 14:06:26 +08:00

537 lines
22 KiB
C#

using YLErp.BLL;
using YLErp.Modules.TradeModule.AccumulatorOptionModule;
using YLErp.Modules.TradeModule.ExoticOptionModule;
using YLErp.QdpModule;
namespace YLErp.Modules.TradeModule.ObservationModule
{
/// <summary>
/// 期权交易观察操作
/// </summary>
public partial class OptionObservationService : TradeServiceBase
{
public OptionObservationService(YLBaseService baseService) : base(baseService)
{
}
public OptionObservationService(OptUserInfo userInfo) : base(userInfo)
{
}
/// <summary>
/// 检查观察状态(from tradeController.GetIsObservationDay)
/// </summary>
public CheckObservationResult CheckObservation(CheckObservationRequest req)
{
var td = DbContext.trade.AsNoTracking().FirstOrDefault(n => n.id == req.tradeId);
if (td == null)
{
throw new ServiceException("找不到交易信息");
}
return CheckObservation(td, req.valueDate, req.price);
}
/// <summary>
/// 检查观察状态(from tradeController.GetIsObservationDay)
/// </summary>
public CheckObservationResult CheckObservation(trade td, DateTime valueDate, double price)
{
if (td is null)
{
throw new ArgumentNullException(nameof(td));
}
tradeBLL.SetFieldsByTradeType(td);
CheckObservationResult result;
switch (td.TradeType)
{
case "障碍期权":
result = CheckBarrieKioStatus(td, valueDate, price);
break;
case "双鲨期权":
result = CheckDbSharkKioStatus(td, valueDate, price);
break;
case "区间累积期权":
result = CheckRangeAccKioStatus(td, valueDate, price);
break;
case "凤凰期权":
result = CheckAutoCallKioStatus(td, valueDate, price);
break;
case "雪球期权":
result = CheckSnowballKioStatus(td, valueDate, price);
break;
case "气囊结构":
result = CheckAirbagKioStatus(td, valueDate, price);
break;
case "累计期权":
result = CheckAccumulatorObservation(td, valueDate, price);
break;
default:
throw new ServiceException("不支持此交易类型:" + td.TradeType);
}
return result;
}
private static (bool blKI, bool blKO) IsObservationDay(OtcTrade td, DateTime valueDate, string observationDateStr, string koObservationDateStr)
{
if (valueDate < td.TradeDate.Value || valueDate > td.ExerciseDate.Value)
{
return (false, false);
}
var observationDates = QdpHelper.GetObservationDatesFromString(observationDateStr);
var observationDatesKO = QdpHelper.GetObservationDatesFromString(koObservationDateStr);
var blKI = observationDates == null ? valueDate > td.StartDate.Value : observationDates.Contains(valueDate);
if (ConsGlobal.TradeType.RangeAcc.Equals(td.TradeType))
{
blKI = observationDates == null ? valueDate >= td.StartDate.Value : observationDates.Contains(valueDate);
}
var blKO = observationDatesKO == null || observationDatesKO.Contains(valueDate);
return (blKI, blKO);
}
/// <summary>
/// 与IsObservationDay区别 增加票息日期处理
/// </summary>
private static (bool blKI, bool blKO, bool blKOCoupon) IsObservationDayV2(OtcTrade td, DateTime valueDate, string observationDateStr, string koObservationDateStr, string CouponBarrierStr)
{
if (valueDate < td.TradeDate.Value || valueDate > td.ExerciseDate.Value)
{
return (false, false, false);
}
var observationDates = QdpHelper.GetObservationDatesFromString(observationDateStr);
var observationDatesKO = QdpHelper.GetObservationDatesFromString(koObservationDateStr);
var CouponBarrierKO = QdpHelper.GetObservationDatesFromString(CouponBarrierStr);
var blKI = observationDates == null ? valueDate > td.StartDate.Value : observationDates.Contains(valueDate);
var blKO = observationDatesKO == null || observationDatesKO.Contains(valueDate);
var blKOCoupon = CouponBarrierKO == null || CouponBarrierKO.Contains(valueDate);
return (blKI, blKO, blKOCoupon);
}
//障碍期权
private CheckObservationResult CheckBarrieKioStatus(trade td, DateTime valueDate, double price)
{
var result = new CheckObservationResult { UnderlyingCode = td.UnderlyingCode };
var tdEx = td.trade_barrier_option;
result.IsObservationDay = IsObservationDay(td, valueDate, tdEx.ObservationDates, null).blKI;
if (result.IsObservationDay)
{
result.KnockInOutStatusObservation = "观察中";
var barrier = td.IsMoneynessOptionData ? tdEx.BarrierPrice * td.SpotPrice : tdEx.BarrierPrice;
var upperBarrier = new Lazy<double?>(() => td.IsMoneynessOptionData ? tdEx.UpperBarrierPrice * td.SpotPrice : tdEx.UpperBarrierPrice);
if (tdEx.BarrierType == "上升敲出")
{
if (price >= barrier)
{
result.KnockInOutStatusObservation = "敲出";
}
}
else if (tdEx.BarrierType == "上升敲入")
{
if (price >= barrier)
{
result.KnockInOutStatusObservation = "敲入";
}
}
else if (tdEx.BarrierType == "下降敲出")
{
if (price <= barrier)
{
result.KnockInOutStatusObservation = "敲出";
}
}
else if (tdEx.BarrierType == "下降敲入")
{
if (price <= barrier)
{
result.KnockInOutStatusObservation = "敲入";
}
}
else if (tdEx.BarrierType == "双障碍敲出")
{
if (price <= barrier || price >= upperBarrier.Value)
{
result.KnockInOutStatusObservation = "敲出";
}
}
else if (tdEx.BarrierType == "双障碍敲入")
{
if (price <= barrier || price >= upperBarrier.Value)
{
result.KnockInOutStatusObservation = "敲入";
}
}
}
return result;
}
//双鲨期权
private CheckObservationResult CheckDbSharkKioStatus(trade td, DateTime valueDate, double price)
{
var result = new CheckObservationResult { UnderlyingCode = td.UnderlyingCode };
var tdEx = td.trade_double_sharkfin_option;
result.IsObservationDay = IsObservationDay(td, valueDate, tdEx.ObservationDates, null).blKI;
if (result.IsObservationDay)
{
result.KnockInOutStatusObservation = "观察中";
var barrierLow = td.IsMoneynessOptionData ? tdEx.BarrierLow * td.SpotPrice : tdEx.BarrierLow;
var barrierHigh = new Lazy<double?>(() => td.IsMoneynessOptionData ? tdEx.BarrierHigh * td.SpotPrice : tdEx.BarrierHigh);
if (price <= barrierLow || price >= barrierHigh.Value)
{
result.KnockInOutStatusObservation = "敲出";
}
}
return result;
}
//凤凰期权
private CheckObservationResult CheckAutoCallKioStatus(trade td, DateTime valueDate, double price)
{
var result = new CheckObservationResult { UnderlyingCode = td.UnderlyingCode };
var tdEx = td.trade_autocall;
var ko = TradeObservationHelper.GetAutocallKOObservationAndCoupon(tdEx.KOObservationDates, tdEx.CouponBarrier);
var (blKI, blKO, blKOCoupon) = IsObservationDayV2(td, valueDate, tdEx.ObservationDates, ko.Item2, ko.Item1);
result.IsObservationDay = blKI || blKO || blKOCoupon;
//若所有条件都未满足的默认值处理
if (tdEx.KnockInOutStatusCn == "已敲入")
{
result.KnockInOutStatusObservation = "敲入";
}
else if (result.IsObservationDay)
{
result.KnockInOutStatusObservation = "观察中";
}
//当前周期是否计息
result.CheckData["autocall_NeedCoupon"] = "否";
if (result.IsObservationDay)
{
var isCall = ConsGlobal.CallPut.IsCall(td.CallPut);
result.KOBarrier = TradeObservationHelper.GetPriceByDateV2(ko.Item2, valueDate) ?? tdEx.KOBarrier;
if (td.IsMoneynessOptionData)
{
result.KOBarrier *= td.SpotPrice ?? 0;
}
if (blKO && (isCall ? price >= result.KOBarrier.Value : price <= result.KOBarrier.Value))
{
result.KnockInOutStatusObservation = "敲出";
}
else if (blKI)
{
var kiBarrier = td.IsMoneynessOptionData ? tdEx.KIBarrier * td.SpotPrice : tdEx.KIBarrier;
if (isCall ? price <= kiBarrier : price >= kiBarrier)
{
result.KnockInOutStatusObservation = "敲入";
}
}
if (blKOCoupon)
{
//满足敲入到期不支付票息的情况时,当前周期是否计息应该为否
if (!tdEx.IncludeCouponAfterKI && result.KnockInOutStatusObservation == "敲入")
{
result.CheckData["autocall_NeedCoupon"] = "否";
}
else if (isCall ? price >= (td.IsMoneynessOptionData ? tdEx.CouponBarrier * td.SpotPrice : tdEx.CouponBarrier) : price <= (td.IsMoneynessOptionData ? tdEx.CouponBarrier * td.SpotPrice : tdEx.CouponBarrier))
{
result.CheckData["autocall_NeedCoupon"] = "是";
}
}
}
else
{
result.KOBarrier = tdEx.KOBarrier;
if (td.IsMoneynessOptionData)
{
result.KOBarrier *= td.SpotPrice ?? 0;
}
}
//结算金额
var defaultAmount = new TradeAutocallBLL(this).GetDefaultAmount(td, tdEx, valueDate, price);
result.CheckData["结算金额"] = defaultAmount.OtcFormatMoney();
return result;
}
//气囊结构
private CheckObservationResult CheckAirbagKioStatus(trade td, DateTime valueDate, double price)
{
var result = new CheckObservationResult
{
UnderlyingCode = td.UnderlyingCode,
IsObservationDay = true
};
result.KnockInOutStatusObservation = "观察中";
//若所有条件都未满足的默认值处理
if (td.trade_airbag.KnockInOutStatusCn == "已敲入")
{
result.KnockInOutStatusObservation = "敲入";
}
else if (price <= (td.IsMoneynessOptionData ? td.trade_airbag.Barrier * td.SpotPrice : td.trade_airbag.Barrier))
{
result.KnockInOutStatusObservation = "敲入";
}
return result;
}
//区间累计
private CheckObservationResult CheckRangeAccKioStatus(trade td, DateTime valueDate, double price)
{
var result = new CheckObservationResult { UnderlyingCode = td.UnderlyingCode };
var tdEx = td.trade_rangeaccrual;
result.IsObservationDay = IsObservationDay(td, valueDate, tdEx.ObservationDates, null).blKI;
if (result.IsObservationDay)
{
var rangeCoupon = new TradeRangeAccrualService(this).GetRangeCoupon(td, td.trade_rangeaccrual, valueDate, price);
//观察后区间收益
result.CheckData["rangeacc_BonusObservation"] = rangeCoupon.OtcFormatMoney();
}
return result;
}
//累计期权
private CheckObservationResult CheckAccumulatorObservation(trade td, DateTime valueDate, double price)
{
new OptionTradeActionRestoreService(this).RestoreTradeDataToSpecialDay(td, valueDate);
var tdEx = td.trade_accumulator_option;
var checkResult = TradeAccumulatorService.CheckAccumulatorPayoff(td, tdEx, valueDate, price, tdEx.AccumuTradeAmount * (td.CountRatio ?? 1));
var result = new CheckObservationResult
{
UnderlyingCode = td.UnderlyingCode,
IsObservationDay = checkResult != null
};
if (result.IsObservationDay)
{
result.KnockInOutStatusObservation = "观察中";
result.KOBarrier = checkResult.Barrier;
if (valueDate == td.ExerciseDate.Value.Date && tdEx.SettlementMode == "现金期末"
&& (checkResult.SettlementMode != "敲出" || !tdEx.EarlyTerminate))
{
checkResult.PaymentNotional = checkResult.PaymentAmount += DbContext.autocall_observation.Where(n => n.TradeId == td.id && n.SettlementMode == "现金期末")
.Select(n => n.PaymentAmount).AsEnumerable().DefaultIfEmpty(0).Sum();
checkResult.PaymentAmount *= ConsGlobal.CallPut.IsCall(td.OptionType) ? price - checkResult.Strike : checkResult.Strike - price;
checkResult.SettlementMode = "现金当日";
if (td.BuySell != "买入")
{
checkResult.PaymentAmount = -checkResult.PaymentAmount;
}
}
result.CheckData["累计结算数量"] = "0.00";
result.CheckData["累计结算金额"] = "0.00";
if (checkResult.SettlementMode == "票息" || checkResult.SettlementMode == "现金当日" || checkResult.SettlementMode == "现金当日&转远期")
{
result.CheckData["累计结算金额"] = checkResult.PaymentAmount.OtcFormatMoney();
result.CheckData["累计结算数量"] = (checkResult.PaymentNotional / checkResult.CountRatio).OtcFormatFlex(2);
}
else
{
result.CheckData["累计结算数量"] = (checkResult.PaymentNotional / checkResult.CountRatio).OtcFormatFlex(2);
if (checkResult.SettlementMode == "敲出")
{
result.CheckData["累计结算金额"] = checkResult.PaymentAmount.OtcFormatMoney();
}
}
result.KnockInOutStatusObservation = checkResult.SettlementMode;
}
else
{
result.KOBarrier = tdEx.KOBarrier;
if (td.IsMoneynessOptionData)
{
result.KOBarrier *= td.SpotPrice ?? 0;
}
}
return result;
}
/// <summary>
/// 结算金额
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
/// <exception cref="ServiceException"></exception>
public double GetTradeObservationCoupon(CheckObservationRequest req)
{
if (req.price < 0)
{
throw new ServiceException("观察标的价格不合法!");
}
var defaultAmount = 0d;
var td = DbContext.trade.Find(req.tradeId);
switch (td.TradeType)
{
case "凤凰期权":
var trade_autocall = DbContext.trade_autocall.FirstOrDefault(x => x.TradeId == req.tradeId);
defaultAmount = new TradeAutocallBLL(this).GetDefaultAmount(td, trade_autocall, req.valueDate, req.price);
break;
case "雪球期权":
var trade_snowball = DbContext.trade_snowball.FirstOrDefault(x => x.TradeId == req.tradeId);
defaultAmount = new TradeSnowballBLL(this).GetDefaultAmount(td, trade_snowball, req.valueDate, req.price);
break;
default:
throw new ServiceException("不支持的交易类型:" + td.TradeType);
}
return defaultAmount;
}
/// <summary>
/// 保存自定义观察日
/// </summary>
public void SaveManuallyTradeObservationPrice(SaveManuallyTradeObservationRequest req)
{
//if (req.price < 0)
//{
// throw new ServiceException("观察标的价格不合法!");
//}
trade td = null;
if (req.tradeId > 0)
{
td = DbContext.trade.Find(req.tradeId);
}
else
{
td = DbContext.trade.FirstOrDefault(n => n.TradeNumber == req.tradeNumber);
req.tradeId = td.id;
}
if (td == null)
{
throw new ServiceException("交易信息不存在:" + (req.tradeId > 0 ? req.tradeId.ToString() : req.tradeNumber));
}
new TradeExtendService(this).SetTradeExtend(new[] { td }, tracking: true);
var tradeStatus = td.TradeStatus;
switch (td.TradeType)
{
case "障碍期权":
if (!string.IsNullOrEmpty(td.trade_barrier_option.KnockInOutStatus))
{
throw new ServiceException("请确认该交易的敲入敲出状态");
}
if (td.TradeStatus != ConsTrade.确认成交)
{
throw new ServiceException("该交易非确认成交状态,无法设置");
}
new BarrierOptionKnockioService(this).CheckBarrierKnockInOutStatus(td, td.trade_barrier_option, req.valueDate, req.price, req.price, req.price);
break;
case "双鲨期权":
if (!string.IsNullOrEmpty(td.trade_double_sharkfin_option.KnockInOutStatus))
{
throw new ServiceException("请确认该交易的敲入敲出状态");
}
if (td.TradeStatus != ConsTrade.确认成交)
{
throw new ServiceException("该交易非确认成交状态,无法设置");
}
new DoubleSharkOptionKnockoutService(this).CheckDoubleSharkFinKnockOutStatus(td, td.trade_double_sharkfin_option, req.valueDate, req.price, req.price);
break;
case "区间累积期权":
new TradeRangeAccrualService(this).CheckRangeAccrualBonus(td, td.trade_rangeaccrual, req.valueDate, req.price);
break;
case "凤凰期权":
new TradeAutocallBLL(this).CheckAutocallKnockInOutStatus(td, td.trade_autocall, req.valueDate, req.price, req.SettlementAmount);
break;
case "雪球期权":
new TradeSnowballBLL(this).CheckSnowballKnockInOutStatus(td, td.trade_snowball, req.valueDate, req.price, req.SettlementAmount);
break;
case "气囊结构":
new TradeAirbagService(this).CheckAirbagKnockInStatus(td, td.trade_airbag, req.valueDate, req.price);
break;
case "累计期权":
new TradeAccumulatorService(this).CheckAccumulatorKnockInStatus(td, td.trade_accumulator_option, req.valueDate, req.price);
break;
default:
throw new ServiceException("期权类型不支持:" + td.TradeType);
}
var records = DbContext.manually_trade_observation_price.Where(x => x.TradeId == req.tradeId && x.ValueDate == req.valueDate);
DbContext.manually_trade_observation_price.RemoveRange(records);
var r = new manually_trade_observation_price
{
SettlementAmount = req.SettlementAmount,
Price = req.price,
TradeId = req.tradeId,
ValueDate = req.valueDate,
OptDate = DateTime.Now,
OptId = UserId,
OptName = UserName
};
DbContext.manually_trade_observation_price.Add(r);
AddTradeOperationHistoryAndSetParentTradeInfo(false, td, "设置观察价格");
if (tradeStatus != td.TradeStatus)
{
//删除E/Bod_Trade记录
RemoveEodTradeAndFutureInfo(false, td.id, req.valueDate);
}
DbContext.SaveChanges();
}
}
}