537 lines
22 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|