790 lines
42 KiB
C#
790 lines
42 KiB
C#
using Qdp.Foundation.Implementations;
|
||
using Qdp.Pricing.Library.Options.Products.Autocall.Snowball;
|
||
using YLErp.Abstract.DataProviders;
|
||
using YLErp.BLL;
|
||
using YLErp.Commons;
|
||
using YLErp.DBModels.Consts;
|
||
using YLErp.DBModels.Helpers;
|
||
using YLErp.Modules.CalculationModule;
|
||
using YLErp.Modules.DataProviderModule;
|
||
using YLErp.Modules.SalesModule;
|
||
using YLErp.Modules.TradeModule.DealModule;
|
||
using YLErp.Modules.TradeModule.ExoticOptionModule;
|
||
|
||
namespace YLErp.Modules.TradeModule
|
||
{
|
||
public class TradeSnowballBLL : ExoticOptionModule.TradeCashServiceEx
|
||
{
|
||
public TradeSnowballBLL(YLBaseService baseService) : base(baseService)
|
||
{
|
||
}
|
||
|
||
public TradeSnowballBLL(OptUserInfo userInfo) : base(userInfo)
|
||
{
|
||
}
|
||
|
||
public void CheckStatus(DateTime valueDate, IEodPriceProviderV2 priceProvider, DateTime? startDate = null
|
||
, Action<OtcTrade, trade_snowball> afterKnowInOut = null, IEnumerable<int> clienIds = null)
|
||
{
|
||
priceProvider ??= new EodPriceProvider(valueDate);
|
||
|
||
if (startDate == null)
|
||
{
|
||
startDate = valueDate.AddYears(-5);
|
||
}
|
||
|
||
var query = from trade in DbContext.trade
|
||
join snowball in DbContext.trade_snowball on trade.id equals snowball.TradeId
|
||
join underlying in DbContext.underlying_manager on trade.UnderlyingId equals underlying.id
|
||
where trade.TradeDate > startDate.Value && trade.TradeDate <= valueDate
|
||
&& trade.ExerciseDate >= valueDate
|
||
&& ConsTrade.确认成交 == trade.TradeStatus && (trade.TradeType == "雪球期权")
|
||
&& trade.ValidState != ConsGlobal.InValid
|
||
&& (snowball.KnockInOutStatus != ConsTrade.KnockState.KnockedOut || (snowball.KnockInOutStatus == ConsTrade.KnockState.KnockedOut && snowball.KnockInOutDate >= valueDate))
|
||
&& trade.DividendDate < valueDate
|
||
select new
|
||
{
|
||
underlying = underlying,
|
||
trade = trade,
|
||
trade_snowball = snowball
|
||
};
|
||
#region 增加客户筛选 tw
|
||
if (clienIds != null)
|
||
{
|
||
query = query.Where(l => clienIds.Contains(l.trade.ClientId));
|
||
}
|
||
#endregion
|
||
//未敲出的,以及已敲出但敲出日期大于等于当前收盘日的(为了历史收盘)
|
||
var trades = query.ToList();
|
||
|
||
var tradeIds = trades.Select(x => x.trade.id).ToList();
|
||
var manuallyTradeObservationPrices = DbContext.manually_trade_observation_price
|
||
.Where(x => tradeIds.Contains(x.TradeId) && x.ValueDate == valueDate).ToDictionary(n => n.TradeId);
|
||
trades.ForEach(tr =>
|
||
{
|
||
if (tr.trade.ExerciseDate < valueDate)
|
||
{
|
||
return;//已到期交易不再观察;
|
||
}
|
||
var tradeStatus = tr.trade.TradeStatus;
|
||
|
||
double closePrice;
|
||
double? SettlementAmount = null;
|
||
|
||
if (manuallyTradeObservationPrices.TryGetValue(tr.trade.id, out var manuallyTradeObservationPrice))
|
||
{
|
||
closePrice = manuallyTradeObservationPrice.Price ?? 0;
|
||
SettlementAmount = manuallyTradeObservationPrice.SettlementAmount;
|
||
}
|
||
else if (!priceProvider.TryGetEodPrice(tr.trade.UnderlyingCode, out var eodPrice))
|
||
{
|
||
throw new Exception($"[{tr.trade.TradeType}:{tr.trade.TradeNumber},标的:{tr.trade.UnderlyingCode}]未找到结算价");
|
||
}
|
||
else
|
||
{
|
||
closePrice = eodPrice.ClosePrice;
|
||
}
|
||
|
||
var oldKnockInOutStatus = tr.trade_snowball.KnockInOutStatus;
|
||
CheckSnowballKnockInOutStatus(tr.trade, tr.trade_snowball, valueDate, closePrice, SettlementAmount);
|
||
|
||
if (oldKnockInOutStatus != tr.trade_snowball.KnockInOutStatus)
|
||
{
|
||
var KnockInOutStatus = tr.trade_snowball.KnockInOutStatus == ConsTrade.KnockState.KnockedIn ? "敲入" : "敲出";
|
||
LogFactory.GetLogger("收盘检查雪球").Info($"{tr.trade.TradeNumber}--{KnockInOutStatus}--调试");
|
||
AddTradeOperationHistoryAndSetParentTradeInfo(false, tr.trade, KnockInOutStatus, KnockInOutStatus);
|
||
}
|
||
|
||
if (tradeStatus != tr.trade.TradeStatus || oldKnockInOutStatus != tr.trade_snowball.KnockInOutStatus)
|
||
{
|
||
//删除E/Bod_Trade记录
|
||
RemoveEodTradeAndFutureInfo(false, tr.trade.id, valueDate);
|
||
}
|
||
|
||
if (afterKnowInOut != null && DbContext.Entry(tr.trade_snowball).State == EntityState.Modified)
|
||
{
|
||
afterKnowInOut(tr.trade, tr.trade_snowball);
|
||
}
|
||
});
|
||
|
||
DbContext.SaveChanges();
|
||
}
|
||
|
||
//获取雪球期权敲出时要准备的信息
|
||
public (DateTime koSettleDate, double koBarrier) GetKoSettleInfo(DateTime valueDate, OtcTradeBase otcTrade, trade_snowball tradeSnowball, SimpleSnowball snowball)
|
||
{
|
||
double koBarrier;
|
||
var koSettleDate = valueDate;
|
||
var datesStr = tradeSnowball.KOObservationSettleDates;
|
||
var KOObsSettleDates = string.IsNullOrWhiteSpace(datesStr) ? null : datesStr.Split(new char[] { ',', ';', ',', ';' }, StringSplitOptions.RemoveEmptyEntries).Select(x => DateTime.Parse(x)).ToArray();
|
||
if (snowball.CustomizedKOBarriers != null && snowball.CustomizedKOBarriers.Length > 0)
|
||
{
|
||
var index = snowball.KOObsDates.Select(x => x.DateTime).ToList().IndexOf(valueDate);
|
||
koBarrier = snowball.CustomizedKOBarriers[index];
|
||
if (KOObsSettleDates != null && KOObsSettleDates.Length > index)
|
||
{
|
||
koSettleDate = KOObsSettleDates[index];
|
||
}
|
||
}
|
||
else
|
||
{
|
||
koBarrier = tradeSnowball.KOBarrier;
|
||
if (KOObsSettleDates != null && KOObsSettleDates.Any())
|
||
{
|
||
koSettleDate = KOObsSettleDates[0];
|
||
}
|
||
}
|
||
|
||
if (tradeSnowball.KORebateType == RebateTypeEnum.AtEnd)
|
||
{
|
||
koSettleDate = otcTrade.ExerciseDate.Value;
|
||
}
|
||
else if (koSettleDate < valueDate)
|
||
{
|
||
koSettleDate = valueDate;
|
||
}
|
||
|
||
if (otcTrade.IsMoneynessOptionData)
|
||
{
|
||
koBarrier *= otcTrade.SpotPrice ?? 1.0;
|
||
}
|
||
|
||
return (koSettleDate, koBarrier);
|
||
}
|
||
|
||
public SnowballObservationResult GetDefaultAmountForSpecialSnowball(OtcTradeBase otcTrade, trade_snowball tradeSnowball, DateTime valueDate, double closePrice)
|
||
{
|
||
var tradeCashs = DbContext.trade_cash.Where(x => x.ValidState != ConsGlobal.InValid && !x.IsDeleted && x.TradeId == otcTrade.id && x.Action == "系统操作-平仓费" && x.ValueDate > valueDate && (x.ConfirmDate > valueDate || x.ConfirmDate == DateTime.MinValue) && x.UnwindNotional < x.Notional).ToList();
|
||
var tradeNotional = (ConsTrade.TradeCompleteStatus.Contains(otcTrade.TradeStatus) && otcTrade.UnWindDate <= valueDate ? 0 : otcTrade.Notional) + tradeCashs.Sum(x => x.UnwindNotional).Value;
|
||
|
||
return tradeSnowball.PrepaymentUsed
|
||
? new SpecialSnowballObservationHelper(otcTrade, tradeSnowball).GetObservationResultForTraderSide(valueDate, closePrice, tradeNotional)
|
||
: throw new ServiceFaultException("不支持非预付金形式的雪球");
|
||
}
|
||
|
||
public double GetDefaultAmount(OtcTradeBase otcTrade, trade_snowball tradeSnowball, DateTime valueDate, double closePrice)
|
||
{
|
||
var tradeCashs = DbContext.trade_cash.Where(x => x.ValidState != ConsGlobal.InValid && !x.IsDeleted && x.TradeId == otcTrade.id && x.Action == "系统操作-平仓费" && x.ValueDate > valueDate && (x.ConfirmDate > valueDate || x.ConfirmDate == DateTime.MinValue) && x.UnwindNotional < x.Notional).ToList();
|
||
var tradeNotional = (ConsTrade.TradeCompleteStatus.Contains(otcTrade.TradeStatus) && otcTrade.UnWindDate <= valueDate ? 0 : otcTrade.Notional) + tradeCashs.Sum(x => x.UnwindNotional).Value;
|
||
|
||
if (tradeSnowball.PrepaymentUsed)
|
||
{
|
||
var result = new SpecialSnowballObservationHelper(otcTrade, tradeSnowball).GetObservationResultForTraderSide(valueDate, closePrice, tradeNotional);
|
||
|
||
return result.PaymentAmount;
|
||
}
|
||
|
||
var defaultAmount = 0d;
|
||
|
||
var request = new OptionTradeParamRequest(valuedateBLL.SysRiskFreeRate())
|
||
{
|
||
ParamOverride = p => p.notional = tradeNotional
|
||
};
|
||
|
||
var optionTrade = QdpTradeBuilder.GetSnowballOptionTrade(otcTrade, tradeSnowball, request);
|
||
var snowball = (SimpleSnowball)optionTrade.Instrument;
|
||
var isCall = ConsGlobal.CallPut.IsCall(otcTrade.CallPut);
|
||
|
||
//只在敲出观察日检查敲出和票息情况
|
||
//如果交易已经是敲出状态了,不用再做票息和敲出检查
|
||
if (snowball.KOObsDates.Select(x => x.DateTime).Contains(valueDate)
|
||
&& tradeSnowball.KnockInOutStatus != ConsTrade.KnockState.KnockedOut)
|
||
{
|
||
#region 敲出检查
|
||
|
||
(var koSettleDate, var koBarrier) = GetKoSettleInfo(valueDate, otcTrade, tradeSnowball, snowball);
|
||
|
||
// 发生敲出事件(看涨 - 向上敲出支付票息,看跌 - 向下敲出支付票息)
|
||
if (isCall ? closePrice >= koBarrier : closePrice <= koBarrier)
|
||
{
|
||
tradeSnowball.KnockInOutStatus = ConsTrade.KnockState.KnockedOut;
|
||
|
||
if (snowball.UseOptionPayoffAtKO)
|
||
{
|
||
var koOptionCashflows = snowball.GetKOPayoff(new Date(valueDate), closePrice);
|
||
//koOptionCashflows[0]..PaymentAmount包含了买卖方向的处理了
|
||
defaultAmount = TradeHelper.GetAmountByPaymentAmount(koOptionCashflows[0].PaymentAmount, otcTrade.PrincipalSum(), otcTrade.BuySell);
|
||
}
|
||
else
|
||
{
|
||
var CouponPayment = snowball.CouponPayment(valueDate, includeStartDate: tradeSnowball.CouponIncludeStartDate == true && tradeSnowball.CouponDayCount != "Monthly");
|
||
defaultAmount = TradeHelper.GetAmountByPaymentAmount(CouponPayment, otcTrade.PrincipalSum(), otcTrade.BuySell);
|
||
|
||
if (tradeSnowball.AnnualizedPremiumRate.HasValue && tradeSnowball.AnnualizedPremiumRate != 0)
|
||
{
|
||
var tradePrice = (otcTrade.StockEqvNotional * otcTrade.ParticipationRate * tradeSnowball.AnnualizedPremiumRate * snowball.CouponDayCount.CalcDayCountFraction(snowball.StartDate, new Date(valueDate))) ?? 0;
|
||
|
||
if (tradePrice != 0)
|
||
{
|
||
defaultAmount += (otcTrade.BuySell == "买入" ? -1 : 1) * tradePrice;
|
||
}
|
||
}
|
||
}
|
||
|
||
return defaultAmount; //已经敲出了,不需要再继续走下去了
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
|
||
#region 敲入检查
|
||
//在当前结算日之前未敲出且未敲入:
|
||
// !((KO || KI) && KOKIDate < valueDate)
|
||
if (!((tradeSnowball.KnockInOutStatus == ConsTrade.KnockState.KnockedOut
|
||
|| tradeSnowball.KnockInOutStatus == ConsTrade.KnockState.KnockedIn) && tradeSnowball.KnockInOutDate < valueDate)
|
||
&& snowball.KIObsDates.Select(x => x.DateTime).Contains(valueDate) && tradeSnowball.KIPayoffType != KIPayoffTypeEnum.None)
|
||
{
|
||
var kiBarrier = otcTrade.IsMoneynessOptionData ? tradeSnowball.KIBarrier * otcTrade.SpotPrice : tradeSnowball.KIBarrier;
|
||
|
||
// 发生敲入事件(看涨 - 向下敲入,看跌 - 向上敲入)
|
||
if (isCall ? closePrice <= kiBarrier : closePrice >= kiBarrier)
|
||
{
|
||
// 更新观察状态
|
||
tradeSnowball.KnockInOutStatus = ConsTrade.KnockState.KnockedIn;
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region 到期检查
|
||
if (valueDate == snowball.ExerciseDates.Last().DateTime)
|
||
{
|
||
if (snowball.UseOptionPayoffAtMaturity &&
|
||
(tradeSnowball.KnockInOutStatus == ConsTrade.KnockState.KnockedIn || tradeSnowball.KIBarrier <= 0))
|
||
{
|
||
//已敲入,到期时计算期权收益
|
||
var optionPayoffPayment = snowball.GetPayoff(new double[] { closePrice });
|
||
//optionPayoffPayment[0].PaymentAmount包含了买卖方向的处理了
|
||
defaultAmount = TradeHelper.GetAmountByPaymentAmount(optionPayoffPayment[0].PaymentAmount, otcTrade.PrincipalSum(), otcTrade.BuySell);
|
||
}
|
||
else if (tradeSnowball.KnockInOutStatus != ConsTrade.KnockState.KnockedOut)
|
||
{
|
||
var startDate = tradeSnowball.CouponIncludeStartDate == true ? snowball.StartDate.AddDays(-1) : snowball.StartDate;
|
||
var maturityCouponRate = snowball.Coupon * snowball.InitialSpotPrice;
|
||
var maturityCouponPayment =
|
||
snowball.FixedCoupon ?
|
||
maturityCouponRate * snowball.Notional :
|
||
maturityCouponRate * snowball.Notional * snowball.CouponDayCount.CalcDayCountFraction(startDate, snowball.ExerciseDates.Last());
|
||
maturityCouponPayment = TradeHelper.GetAmountByPaymentAmount(maturityCouponPayment, otcTrade.PrincipalSum(), otcTrade.BuySell);
|
||
|
||
defaultAmount = maturityCouponPayment;
|
||
|
||
if (tradeSnowball.AnnualizedPremiumRate.HasValue && tradeSnowball.AnnualizedPremiumRate != 0)
|
||
{
|
||
var tradePrice = (otcTrade.StockEqvNotional * otcTrade.ParticipationRate * tradeSnowball.AnnualizedPremiumRate * snowball.CouponDayCount.CalcDayCountFraction(snowball.StartDate, new Date(valueDate))) ?? 0;
|
||
if (tradePrice != 0)
|
||
{
|
||
defaultAmount += (otcTrade.BuySell == "买入" ? -1 : 1) * tradePrice;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
return defaultAmount;
|
||
}
|
||
|
||
public void CheckSnowballKnockInOutStatus(OtcTradeBase otcTrade, trade_snowball tradeSnowball, DateTime valueDate, double closePrice, double? settlementAmount)
|
||
{
|
||
if (otcTrade is null)
|
||
{
|
||
throw new ArgumentNullException(nameof(otcTrade));
|
||
}
|
||
|
||
if (tradeSnowball is null)
|
||
{
|
||
throw new ArgumentNullException(nameof(tradeSnowball));
|
||
}
|
||
|
||
try
|
||
{
|
||
var tradeStatusOld = otcTrade.TradeStatus;
|
||
InnerCheckSnowballKnockInOutStatus(otcTrade, tradeSnowball, valueDate, closePrice, settlementAmount);
|
||
//生成确认书
|
||
if (PS.Config.IsAutoGenerateContracts && tradeStatusOld == ConsTrade.确认成交 && ConsTrade.TradeCompleteStatus.Contains(otcTrade.TradeStatus))
|
||
{
|
||
//修改销售提成的状态
|
||
new SalesCommissionDataService(this).SetCommissionVaild(otcTrade.id);
|
||
new TradeContractGenerateService(this).GenerateContractsAsync(new List<int> { otcTrade.id });
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new ServiceException($"[检查敲入敲出]交易编号:{otcTrade.TradeNumber},{ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
private void InnerCheckSnowballKnockInOutStatus(
|
||
OtcTradeBase otcTrade, trade_snowball tradeSnowball, DateTime valueDate, double closePrice, double? SettlementAmount)
|
||
{
|
||
var tradeCashs = DbContext.trade_cash.Where(x => x.ValidState != ConsGlobal.InValid && !x.IsDeleted && x.TradeId == otcTrade.id && x.Action == "系统操作-平仓费" && x.ValueDate > valueDate && (x.ConfirmDate > valueDate || x.ConfirmDate == DateTime.MinValue) && x.UnwindNotional < x.Notional).ToList();
|
||
var tradeNotional = (ConsTrade.TradeCompleteStatus.Contains(otcTrade.TradeStatus) && otcTrade.UnWindDate <= valueDate ? 0 : otcTrade.Notional) + tradeCashs.Sum(x => x.UnwindNotional).Value;
|
||
|
||
if (tradeSnowball.PrepaymentUsed)
|
||
{
|
||
ProcessSpecialSnowball(otcTrade, tradeSnowball, valueDate, closePrice, tradeNotional, SettlementAmount);
|
||
|
||
return;
|
||
}
|
||
|
||
var request = new OptionTradeParamRequest(valuedateBLL.SysRiskFreeRate())
|
||
{
|
||
ParamOverride = p => p.notional = tradeNotional
|
||
};
|
||
|
||
var optionTrade = QdpTradeBuilder.GetSnowballOptionTrade(otcTrade, tradeSnowball, request);
|
||
var snowball = (SimpleSnowball)optionTrade.Instrument;
|
||
var isCall = ConsGlobal.CallPut.IsCall(otcTrade.CallPut);
|
||
|
||
//只在敲出观察日检查敲出和票息情况
|
||
//如果交易已经是敲出状态了,不用再做票息和敲出检查
|
||
if (snowball.KOObsDates.Select(x => x.DateTime).Contains(valueDate)
|
||
&& tradeSnowball.KnockInOutStatus != ConsTrade.KnockState.KnockedOut)
|
||
{
|
||
#region 敲出检查
|
||
|
||
(var koSettleDate, var koBarrier) = GetKoSettleInfo(valueDate, otcTrade, tradeSnowball, snowball);
|
||
|
||
// 发生敲出事件(看涨 - 向上敲出支付票息,看跌 - 向下敲出支付票息)
|
||
if (isCall ? closePrice >= koBarrier : closePrice <= koBarrier)
|
||
{
|
||
// 更新观察状态
|
||
tradeSnowball.KnockInOutStatus = ConsTrade.KnockState.KnockedOut;
|
||
tradeSnowball.KnockInOutDate = valueDate;
|
||
|
||
// 更新交易状态
|
||
otcTrade.TradeStatus = ConsTrade.已平仓;
|
||
otcTrade.UnWindDate = valueDate;
|
||
|
||
if (snowball.UseOptionPayoffAtKO)
|
||
{
|
||
double paymentAmount = 0;
|
||
var koOptionCashflows = snowball.GetKOPayoff(new Date(valueDate), closePrice);
|
||
if (SettlementAmount != null)
|
||
{
|
||
paymentAmount = SettlementAmount.Value;
|
||
}
|
||
else
|
||
{
|
||
paymentAmount = TradeHelper.GetAmountByPaymentAmount(koOptionCashflows[0].PaymentAmount, otcTrade.PrincipalSum(), otcTrade.BuySell);
|
||
}
|
||
|
||
var parentTradeId = 0;
|
||
var parentTradeCashId = 0;
|
||
if (otcTrade.IsGroup == 2 && otcTrade.ParentTradeId > 0)
|
||
{
|
||
var groupAction = DbContext.trade_cash_group_action.FirstOrDefault(x => x.TradeId == otcTrade.id && x.Status != "已完成");
|
||
if (groupAction != null)
|
||
{
|
||
groupAction.Status = "已完成";
|
||
parentTradeCashId = groupAction.ParentTradeCashId;
|
||
parentTradeId = groupAction.ParentTradeId;
|
||
}
|
||
else
|
||
{
|
||
parentTradeId = otcTrade.ParentTradeId;
|
||
parentTradeCashId = SaveGroupUnwindCash(otcTrade, valueDate, paymentAmount, closePrice, out var continueTradeCashHandle).id;
|
||
}
|
||
}
|
||
|
||
SaveOptionPayoffCash(otcTrade, paymentAmount, koSettleDate, closePrice, ClientCashInCashOut.系统操作_平仓费, TradeCashExerciseWayEnum.提前终止行权, valueDate, true, parentTradeId, parentTradeCashId);
|
||
//koOptionCashflows[0].PaymentAmount包含了买卖方向的处理了
|
||
}
|
||
else
|
||
{
|
||
if (SettlementAmount != null)
|
||
{
|
||
double tradePrice = 0;
|
||
var _settlementAmount = SettlementAmount.Value;
|
||
|
||
var parentTradeId = 0;
|
||
var parentTradeCashId = 0;
|
||
if (otcTrade.IsGroup == 2 && otcTrade.ParentTradeId > 0)
|
||
{
|
||
var groupAction = DbContext.trade_cash_group_action.FirstOrDefault(x => x.TradeId == otcTrade.id && x.Status != "已完成");
|
||
if (groupAction != null)
|
||
{
|
||
groupAction.Status = "已完成";
|
||
parentTradeCashId = groupAction.ParentTradeCashId;
|
||
parentTradeId = groupAction.ParentTradeId;
|
||
}
|
||
else
|
||
{
|
||
parentTradeId = otcTrade.ParentTradeId;
|
||
parentTradeCashId = SaveGroupUnwindCash(otcTrade, valueDate, _settlementAmount, closePrice, out var continueTradeCashHandle).id;
|
||
}
|
||
}
|
||
|
||
//观察日价格页面的结算金额包含了年化期权费,该处逻辑需要先按照扣除年化期权费来算,后面逻辑会补上年化期权费,否则会重复运算
|
||
if (tradeSnowball.AnnualizedPremiumRate.HasValue && tradeSnowball.AnnualizedPremiumRate != 0)
|
||
{
|
||
tradePrice = otcTrade.StockEqvNotional * otcTrade.ParticipationRate * tradeSnowball.AnnualizedPremiumRate * snowball.CouponDayCount.CalcDayCountFraction(snowball.StartDate, new Date(valueDate)) ?? 0;
|
||
if (tradePrice != 0)
|
||
{
|
||
_settlementAmount -= (otcTrade.BuySell == "买入" ? -1 : 1) * tradePrice;
|
||
}
|
||
}
|
||
var tradeCash = SaveCouponAmountCash(otcTrade, _settlementAmount, koSettleDate, closePrice, valueDate, parentTradeId, parentTradeCashId);
|
||
if (tradePrice != 0 && otcTrade.PremiumPayDate <= valueDate)
|
||
{
|
||
SaveTradePrice(otcTrade, tradeCash, tradePrice, koSettleDate);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
var CouponPayment = snowball.CouponPayment(valueDate, includeStartDate: tradeSnowball.CouponIncludeStartDate == true && tradeSnowball.CouponDayCount != "Monthly");
|
||
var couponPayment = TradeHelper.GetAmountByPaymentAmount(CouponPayment, otcTrade.PrincipalSum(), otcTrade.BuySell);
|
||
|
||
var parentTradeId = 0;
|
||
var parentTradeCashId = 0;
|
||
var hasUnfinishedGroupAction = false;
|
||
var continueTradeCashHandle = false;
|
||
var groupTradeCash = new trade_cash();
|
||
if (otcTrade.IsGroup == 2 && otcTrade.ParentTradeId > 0)
|
||
{
|
||
var groupAction = DbContext.trade_cash_group_action.FirstOrDefault(x => x.TradeId == otcTrade.id && x.Status != "已完成");
|
||
if (groupAction != null)
|
||
{
|
||
hasUnfinishedGroupAction = true;
|
||
groupAction.Status = "已完成";
|
||
parentTradeCashId = groupAction.ParentTradeCashId;
|
||
parentTradeId = groupAction.ParentTradeId;
|
||
}
|
||
else
|
||
{
|
||
parentTradeId = otcTrade.ParentTradeId;
|
||
groupTradeCash = SaveGroupUnwindCash(otcTrade, valueDate, couponPayment, closePrice, out continueTradeCashHandle);
|
||
parentTradeCashId = groupTradeCash.id;
|
||
}
|
||
}
|
||
|
||
var tradeCash = SaveCouponAmountCash(otcTrade, couponPayment, koSettleDate, closePrice, valueDate, parentTradeId, parentTradeCashId);
|
||
|
||
if (tradeSnowball.AnnualizedPremiumRate.HasValue && tradeSnowball.AnnualizedPremiumRate != 0)
|
||
{
|
||
var tradePrice = (otcTrade.StockEqvNotional * otcTrade.ParticipationRate * tradeSnowball.AnnualizedPremiumRate * snowball.CouponDayCount.CalcDayCountFraction(snowball.StartDate, new Date(valueDate))) ?? 0;
|
||
if (tradePrice != 0 && otcTrade.PremiumPayDate <= valueDate)
|
||
{
|
||
SaveTradePrice(otcTrade, tradeCash, tradePrice, koSettleDate);
|
||
if (otcTrade.IsGroup == 2 && otcTrade.ParentTradeId > 0 && !hasUnfinishedGroupAction && continueTradeCashHandle)
|
||
{
|
||
groupTradeCash.Amount += (otcTrade.BuySell == "买入" ? -1 : 1) * tradePrice;
|
||
DbContext.SaveChanges();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
LogFactory.GetLogger("收盘检查雪球").Info($"{otcTrade.TradeNumber}--敲出");
|
||
|
||
return; //已经敲出了,不需要再继续走下去了
|
||
}
|
||
#endregion
|
||
}
|
||
|
||
#region 敲入检查
|
||
//在当前结算日之前未敲出且未敲入:
|
||
// !((KO || KI) && KOKIDate < valueDate)
|
||
if (!((tradeSnowball.KnockInOutStatus == ConsTrade.KnockState.KnockedOut
|
||
|| tradeSnowball.KnockInOutStatus == ConsTrade.KnockState.KnockedIn)
|
||
&& tradeSnowball.KnockInOutDate < valueDate)
|
||
&& snowball.KIObsDates.Select(x => x.DateTime).Contains(valueDate) && tradeSnowball.KIPayoffType != KIPayoffTypeEnum.None)
|
||
{
|
||
var kiBarrier =
|
||
otcTrade.IsMoneynessOptionData ?
|
||
tradeSnowball.KIBarrier * otcTrade.SpotPrice :
|
||
tradeSnowball.KIBarrier;
|
||
|
||
// 发生敲入事件(看涨时向下敲入,看跌时向上敲入)
|
||
if (isCall ? closePrice <= kiBarrier : closePrice >= kiBarrier)
|
||
{
|
||
// 更新观察状态
|
||
tradeSnowball.KnockInOutStatus = ConsTrade.KnockState.KnockedIn;
|
||
tradeSnowball.KnockInOutDate = valueDate;
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region 到期检查
|
||
if (valueDate == snowball.ExerciseDates.Last().DateTime)
|
||
{
|
||
if (snowball.UseOptionPayoffAtMaturity &&
|
||
(tradeSnowball.KnockInOutStatus == ConsTrade.KnockState.KnockedIn || tradeSnowball.KIBarrier <= 0))
|
||
{
|
||
otcTrade.TradeStatus = ConsTrade.已执行;
|
||
otcTrade.UnWindDate = valueDate;
|
||
double paymentAmount = 0;
|
||
//已敲入,到期时计算期权收益
|
||
var optionPayoffPayment = snowball.GetPayoff(new double[] { closePrice });
|
||
if (SettlementAmount != null)
|
||
{
|
||
paymentAmount = SettlementAmount.Value;
|
||
}
|
||
else
|
||
{
|
||
paymentAmount = TradeHelper.GetAmountByPaymentAmount(optionPayoffPayment[0].PaymentAmount, otcTrade.PrincipalSum(), otcTrade.BuySell);
|
||
}
|
||
|
||
var parentTradeId = 0;
|
||
var parentTradeCashId = 0;
|
||
if (otcTrade.IsGroup == 2 && otcTrade.ParentTradeId > 0)
|
||
{
|
||
var groupAction = DbContext.trade_cash_group_action.FirstOrDefault(x => x.TradeId == otcTrade.id && x.Status != "已完成");
|
||
if (groupAction != null)
|
||
{
|
||
groupAction.Status = "已完成";
|
||
parentTradeCashId = groupAction.ParentTradeCashId;
|
||
parentTradeId = groupAction.ParentTradeId;
|
||
}
|
||
else
|
||
{
|
||
parentTradeId = otcTrade.ParentTradeId;
|
||
parentTradeCashId = SaveGroupUnwindCash(otcTrade, valueDate, paymentAmount, closePrice, out var continueTradeCashHandle).id;
|
||
}
|
||
}
|
||
|
||
//optionPayoffPayment[0].PaymentAmount包含了买卖方向的处理了
|
||
SaveOptionPayoffCash(otcTrade, paymentAmount, valueDate, closePrice, ClientCashInCashOut.系统操作_行权费, TradeCashExerciseWayEnum.到期行权, valueDate, false, parentTradeId, parentTradeCashId);
|
||
}
|
||
else if (tradeSnowball.KnockInOutStatus != ConsTrade.KnockState.KnockedOut)
|
||
{
|
||
var startDate = tradeSnowball.CouponIncludeStartDate == true ? snowball.StartDate.AddDays(-1) : snowball.StartDate;
|
||
otcTrade.TradeStatus = ConsTrade.已到期;
|
||
otcTrade.UnWindDate = valueDate;
|
||
var maturityCouponRate = snowball.Coupon * snowball.InitialSpotPrice;
|
||
var maturityCouponPayment =
|
||
snowball.FixedCoupon ?
|
||
maturityCouponRate * snowball.Notional :
|
||
maturityCouponRate * snowball.Notional * snowball.CouponDayCount.CalcDayCountFraction(startDate, snowball.ExerciseDates.Last());
|
||
maturityCouponPayment = TradeHelper.GetAmountByPaymentAmount(maturityCouponPayment, otcTrade.PrincipalSum(), otcTrade.BuySell);
|
||
|
||
double tradePrice = 0;
|
||
if (tradeSnowball.AnnualizedPremiumRate.HasValue && tradeSnowball.AnnualizedPremiumRate != 0)
|
||
{
|
||
tradePrice = otcTrade.StockEqvNotional * otcTrade.ParticipationRate * tradeSnowball.AnnualizedPremiumRate * snowball.CouponDayCount.CalcDayCountFraction(snowball.StartDate, new Date(valueDate)) ?? 0;
|
||
}
|
||
|
||
var parentTradeId = 0;
|
||
var parentTradeCashId = 0;
|
||
var groupTradeCash = new trade_cash();
|
||
if (otcTrade.IsGroup == 2 && otcTrade.ParentTradeId > 0)
|
||
{
|
||
var groupAction = DbContext.trade_cash_group_action.FirstOrDefault(x => x.TradeId == otcTrade.id && x.Status != "已完成");
|
||
if (groupAction != null)
|
||
{
|
||
groupAction.Status = "已完成";
|
||
parentTradeCashId = groupAction.ParentTradeCashId;
|
||
parentTradeId = groupAction.ParentTradeId;
|
||
}
|
||
else
|
||
{
|
||
var paymentAmount = SettlementAmount != null ? SettlementAmount.Value : (maturityCouponPayment + ((otcTrade.BuySell == "买入" ? -1 : 1) * tradePrice));
|
||
parentTradeId = otcTrade.ParentTradeId;
|
||
parentTradeCashId = SaveGroupUnwindCash(otcTrade, valueDate, paymentAmount, closePrice, out var continueTradeCashHandle).id;
|
||
}
|
||
}
|
||
|
||
var tradeCash = new trade_cash();
|
||
if (SettlementAmount != null)
|
||
{
|
||
//观察日价格页面的结算金额包含了年化期权费,该处逻辑需要先按照扣除年化期权费来算,后面逻辑会补上年化期权费,否则会重复运算
|
||
var _settlementAmount = SettlementAmount.Value;
|
||
if (tradePrice != 0)
|
||
{
|
||
_settlementAmount -= (otcTrade.BuySell == "买入" ? -1 : 1) * tradePrice;
|
||
}
|
||
tradeCash = SaveOptionPayoffCash(otcTrade, _settlementAmount, valueDate, closePrice, ClientCashInCashOut.系统操作_行权费, TradeCashExerciseWayEnum.到期行权, valueDate, false, parentTradeId, parentTradeCashId);
|
||
}
|
||
else
|
||
{
|
||
tradeCash = SaveOptionPayoffCash(otcTrade, maturityCouponPayment, valueDate, closePrice, ClientCashInCashOut.系统操作_行权费, TradeCashExerciseWayEnum.到期行权, valueDate, false, parentTradeId, parentTradeCashId);
|
||
}
|
||
|
||
if (tradePrice != 0 && otcTrade.PremiumPayDate <= valueDate)
|
||
{
|
||
SaveTradePrice(otcTrade, tradeCash, tradePrice, valueDate);
|
||
}
|
||
}
|
||
}
|
||
#endregion
|
||
}
|
||
|
||
private trade_cash SaveOptionPayoffCash(OtcTradeBase trade, double paymentAmount, DateTime valueDate, double underlyingPrice, string cashAction, string exerciseWay, DateTime happenDate, bool isKnockOut, int parentTradeId, int parentTradeCashId)
|
||
{
|
||
return SaveCash(trade, cashAction, exerciseWay, paymentAmount, valueDate, underlyingPrice, happenDate, isKnockOut, isLastAction: true, parentTradeId: parentTradeId, parentTradeCashId: parentTradeCashId);
|
||
}
|
||
|
||
private trade_cash SaveCouponAmountCash(OtcTradeBase trade, double couponAmount, DateTime valueDate, double underlyingPrice, DateTime happenDate, int parentTradeId, int parentTradeCashId)
|
||
{
|
||
return SaveCash(trade, ClientCashInCashOut.系统操作_票息, null, couponAmount, valueDate, underlyingPrice, happenDate, true, isLastAction: true, parentTradeId: parentTradeId, parentTradeCashId: parentTradeCashId);
|
||
}
|
||
|
||
public void SaveTradePrice(OtcTradeBase trade, trade_cash tradeCash, double tradePrice, DateTime valueDate)
|
||
{
|
||
const string cashAction = ClientCashInCashOut.系统操作_期权费;
|
||
|
||
// 保存trade_cash
|
||
var tcd = new trade_cash_detail();
|
||
DbContext.trade_cash_detail.Add(tcd);
|
||
|
||
tcd.TradeId = tradeCash.TradeId;
|
||
tcd.TradeCashId = tradeCash.id;
|
||
tcd.Amount = (trade.BuySell == "买入" ? -1 : 1) * tradePrice;
|
||
tcd.Action = cashAction;
|
||
tcd.ValueDate = tradeCash.ValueDate;
|
||
tcd.OptDate = DateTime.Now;
|
||
tcd.OptId = tradeCash.OptId;
|
||
tcd.OptName = tradeCash.OptName;
|
||
|
||
var tradeCashUpDate = DbContext.trade_cash.Find(tradeCash.id);
|
||
tradeCashUpDate.Amount += tcd.Amount ?? 0;
|
||
|
||
// 保存ClientCashInCashOut
|
||
var client = DbContextFactory.GetClientDbContext(UserInfo).client.Where(n => n.id == trade.ClientId)
|
||
.Select(n => new { n.id, n.Number, n.Name }).FirstOrDefault();
|
||
|
||
if (client == null)
|
||
{
|
||
throw new Exception($"{trade.TradeType}'{trade.TradeNumber}'找不到客户信息,客户id:{trade.ClientId}");
|
||
}
|
||
|
||
var cashInOutRecord = DbContext.ClientCashInCashOut.FirstOrDefault(c => c.TradeId == trade.id && c.Action == cashAction && c.HappenDate == valueDate);
|
||
if (cashInOutRecord == null)
|
||
{
|
||
cashInOutRecord = new ClientCashInCashOut();
|
||
cashInOutRecord.CreateDate = tradeCash.OptDate;
|
||
cashInOutRecord.CreatorId = tradeCash.OptId;
|
||
cashInOutRecord.CreatorName = tradeCash.OptName;
|
||
DbContext.ClientCashInCashOut.Add(cashInOutRecord);
|
||
}
|
||
cashInOutRecord.Direction = "应收";
|
||
cashInOutRecord.Number = UniqueTimeId.GetStr();
|
||
cashInOutRecord.ClientId = client.id;
|
||
cashInOutRecord.ClientNumber = client.Number;
|
||
cashInOutRecord.ClientName = client.Name;
|
||
cashInOutRecord.Money = -tcd.Amount;
|
||
cashInOutRecord.HappenDate = valueDate;
|
||
cashInOutRecord.ValidState = "Valid";
|
||
cashInOutRecord.State = ClientCashInCashOut.已确认;
|
||
cashInOutRecord.OptDate = DateTime.Now;
|
||
cashInOutRecord.OptId = tradeCash.OptId;
|
||
cashInOutRecord.OptName = tradeCash.OptName;
|
||
cashInOutRecord.TradeId = trade.id;
|
||
cashInOutRecord.TradeCashId = tradeCash.id;
|
||
cashInOutRecord.Action = cashAction;
|
||
cashInOutRecord.TradeNumber = trade.TradeNumber;
|
||
cashInOutRecord.IsGroup = trade.IsGroup;
|
||
|
||
DbContext.SaveChanges();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理专业版雪球
|
||
/// </summary>
|
||
private void ProcessSpecialSnowball(OtcTradeBase otcTrade, trade_snowball snowball
|
||
, DateTime valueDate, double closePrice, double tradeNotional, double? settlementAmount)
|
||
{
|
||
var obResult = new SpecialSnowballObservationHelper(otcTrade, snowball)
|
||
.GetObservationResultForTraderSide(valueDate, closePrice, tradeNotional);
|
||
|
||
if (obResult.ResultType == SnowballObservationResultType.NonObservationDay
|
||
|| obResult.ResultType == SnowballObservationResultType.Monitoring)
|
||
{
|
||
return;
|
||
}
|
||
|
||
//期权敲入
|
||
if (obResult.ResultType == SnowballObservationResultType.KnockedIn)
|
||
{
|
||
// 更新期权敲入状态
|
||
if (snowball.KnockInOutDate == null)
|
||
{
|
||
snowball.KnockInOutDate = snowball.IsInitialKnockedIn ? otcTrade.StartDate.Value : valueDate;
|
||
}
|
||
snowball.KnockInOutStatus = ConsTrade.KnockState.KnockedIn;
|
||
return;
|
||
}
|
||
|
||
var paymentAmount = settlementAmount != null
|
||
? settlementAmount.Value
|
||
: TradeHelper.GetAmountByPaymentAmount(obResult.PaymentAmount, otcTrade.PrincipalSum(), otcTrade.BuySell);
|
||
|
||
if (obResult.ResultType == SnowballObservationResultType.KoPayoff)
|
||
{
|
||
// 更新期权敲出状态
|
||
snowball.KnockInOutDate = valueDate;
|
||
snowball.KnockInOutStatus = ConsTrade.KnockState.KnockedOut;
|
||
|
||
// 更新交易了结状态
|
||
otcTrade.UnWindDate = valueDate;
|
||
otcTrade.TradeStatus = ConsTrade.已平仓;
|
||
|
||
//写入资金记录
|
||
|
||
(var parentTradeId, var parentTradeCashId) = SaveGroupCash(otcTrade, obResult.PaymentDate, paymentAmount, closePrice);
|
||
|
||
SaveCouponAmountCash(otcTrade, paymentAmount, obResult.PaymentDate, closePrice, valueDate, parentTradeId, parentTradeCashId);
|
||
|
||
LogFactory.GetLogger("收盘检查雪球").Info($"{otcTrade.TradeNumber}--敲出");
|
||
}
|
||
else if (obResult.ResultType == SnowballObservationResultType.KiPayoffAtEndDate)
|
||
{
|
||
// 更新期权敲入状态
|
||
if (snowball.KnockInOutDate == null)
|
||
{
|
||
snowball.KnockInOutDate = snowball.IsInitialKnockedIn ? otcTrade.StartDate.Value : valueDate;
|
||
}
|
||
snowball.KnockInOutStatus = ConsTrade.KnockState.KnockedIn;
|
||
|
||
// 更新交易了结状态
|
||
otcTrade.UnWindDate = valueDate;
|
||
otcTrade.TradeStatus = ConsTrade.已执行;
|
||
|
||
(var parentTradeId, var parentTradeCashId) = SaveGroupCash(otcTrade, obResult.PaymentDate, paymentAmount, closePrice);
|
||
|
||
SaveOptionPayoffCash(otcTrade, paymentAmount, obResult.PaymentDate, closePrice, ClientCashInCashOut.系统操作_行权费, TradeCashExerciseWayEnum.到期行权, valueDate, false, parentTradeId, parentTradeCashId);
|
||
}
|
||
else if (obResult.ResultType == SnowballObservationResultType.NkiPayoffAtEndDate)
|
||
{
|
||
otcTrade.UnWindDate = valueDate;
|
||
otcTrade.TradeStatus = ConsTrade.已到期;
|
||
|
||
(var parentTradeId, var parentTradeCashId) = SaveGroupCash(otcTrade, obResult.PaymentDate, paymentAmount, closePrice);
|
||
|
||
SaveOptionPayoffCash(otcTrade, paymentAmount, obResult.PaymentDate, closePrice, ClientCashInCashOut.系统操作_行权费, TradeCashExerciseWayEnum.到期行权, valueDate, false, parentTradeId, parentTradeCashId);
|
||
}
|
||
else
|
||
{
|
||
throw new ServiceException("系统错误,未处理的观察结果类型:" + obResult.ResultType) { IsFaultError = true };
|
||
}
|
||
}
|
||
|
||
//保存组合交易资金
|
||
private (int parentTradeId, int parentTradeCashId) SaveGroupCash(OtcTradeBase otcTrade
|
||
, DateTime paymentDate, double paymentAmount, double closePrice)
|
||
{
|
||
var parentTradeId = 0;
|
||
var parentTradeCashId = 0;
|
||
|
||
if (otcTrade.IsGroup == 2 && otcTrade.ParentTradeId > 0)
|
||
{
|
||
var groupAction = DbContext.trade_cash_group_action.FirstOrDefault(x => x.TradeId == otcTrade.id && x.Status != "已完成");
|
||
|
||
if (groupAction != null)
|
||
{
|
||
groupAction.Status = "已完成";
|
||
parentTradeCashId = groupAction.ParentTradeCashId;
|
||
parentTradeId = groupAction.ParentTradeId;
|
||
}
|
||
else
|
||
{
|
||
parentTradeId = otcTrade.ParentTradeId;
|
||
parentTradeCashId = SaveGroupUnwindCash(otcTrade, paymentDate, paymentAmount, closePrice, out var continueTradeCashHandle).id;
|
||
}
|
||
}
|
||
|
||
return (parentTradeId, parentTradeCashId);
|
||
}
|
||
}
|
||
}
|