754 lines
39 KiB
C#
754 lines
39 KiB
C#
using Qdp.Pricing.Library.Options.Products.Autocall.Phoenix;
|
|
using YLErp.Abstract.DataProviders;
|
|
using YLErp.BLL;
|
|
using YLErp.Commons;
|
|
using YLErp.DBModels.Consts;
|
|
using YLErp.DBModels.Enums;
|
|
using YLErp.DBModels.Helpers;
|
|
using YLErp.Modules.CalculationModule;
|
|
using YLErp.Modules.DataProviderModule;
|
|
|
|
namespace YLErp.Modules.TradeModule
|
|
{
|
|
public class TradeAutocallBLL : ExoticOptionModule.TradeCashServiceEx
|
|
{
|
|
public TradeAutocallBLL(OptUserInfo userInfo) : base(userInfo)
|
|
{
|
|
|
|
}
|
|
|
|
public TradeAutocallBLL(YLBaseService baseService) : base(baseService)
|
|
{
|
|
}
|
|
|
|
public void CheckStatus(DateTime valueDate, IEodPriceProviderV2 priceProvider, DateTime? startDate = null
|
|
, Action<OtcTrade, trade_autocall> afterKnowInOut = null, IEnumerable<int> clienIds = null)
|
|
{
|
|
if (priceProvider is null)
|
|
{
|
|
priceProvider = new EodPriceProvider(valueDate);
|
|
}
|
|
|
|
if (startDate == null)
|
|
{
|
|
startDate = valueDate.AddYears(-5);
|
|
}
|
|
|
|
//未敲出的,以及已敲出但敲出日期大于等于当前收盘日的(为了历史收盘)
|
|
|
|
var query = from trade in DbContext.trade
|
|
join autocall in DbContext.trade_autocall on trade.id equals autocall.TradeId
|
|
join underlying in DbContext.underlying_manager on trade.UnderlyingId equals underlying.id
|
|
where trade.TradeDate > startDate.Value && trade.TradeDate <= valueDate && trade.ExerciseDate >= valueDate
|
|
&& (trade.TradeType == "凤凰期权")
|
|
&& (ConsTrade.确认成交 == trade.TradeStatus)
|
|
&& trade.ValidState != ConsGlobal.InValid
|
|
&& (autocall.KnockInOutStatus != ConsTrade.KnockState.KnockedOut || (autocall.KnockInOutStatus == ConsTrade.KnockState.KnockedOut && autocall.KnockInOutDate >= valueDate))
|
|
&& trade.DividendDate < valueDate
|
|
select new
|
|
{
|
|
underlying = underlying,
|
|
trade = trade,
|
|
trade_autocall = autocall
|
|
};
|
|
#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);
|
|
|
|
foreach (var tr in trades)
|
|
{
|
|
if (tr.trade.ExerciseDate < valueDate)
|
|
{
|
|
continue;//已到期交易不再观察;
|
|
}
|
|
var tradeStatus = tr.trade.TradeStatus;
|
|
var knockInOutStatus = tr.trade_autocall.KnockInOutStatus;
|
|
|
|
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;
|
|
}
|
|
|
|
CheckAutocallKnockInOutStatus(tr.trade, tr.trade_autocall, valueDate, closePrice, SettlementAmount);
|
|
|
|
if (tradeStatus != tr.trade.TradeStatus || knockInOutStatus != tr.trade_autocall.KnockInOutStatus)
|
|
{
|
|
//删除E/Bod_Trade记录
|
|
RemoveEodTradeAndFutureInfo(false, tr.trade.id, valueDate);
|
|
}
|
|
|
|
if (afterKnowInOut != null && DbContext.Entry(tr.trade_autocall).State == EntityState.Modified)
|
|
{
|
|
afterKnowInOut(tr.trade, tr.trade_autocall);
|
|
}
|
|
}
|
|
|
|
DbContext.SaveChanges();
|
|
}
|
|
|
|
public double GetDefaultAmount(OtcTradeBase otcTrade, trade_autocall tradeAutoCall, DateTime valueDate, double closePrice)
|
|
{
|
|
var defaultAmount = 0d;
|
|
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 notional = (ConsTrade.TradeCompleteStatus.Contains(otcTrade.TradeStatus) && otcTrade.UnWindDate <= valueDate ? 0 : otcTrade.Notional) + tradeCashs.Sum(x => x.UnwindNotional).Value;
|
|
var optionTrade = QdpTradeBuilder.GetAutocallOptionTrade(otcTrade, tradeAutoCall,
|
|
new OptionTradeParamRequest(valuedateBLL.SysRiskFreeRate()) { ParamOverride = x => { x.notional = notional; } });
|
|
var autocall = (AutoCall)optionTrade.Instrument;
|
|
var isCall = ConsGlobal.CallPut.IsCall(otcTrade.CallPut);
|
|
|
|
//只在敲出观察日检查敲出和票息情况
|
|
//如果交易已经是敲出状态了,不用再做票息和敲出检查
|
|
if (autocall.KOObsDates.Select(x => x.DateTime).Contains(valueDate)
|
|
&& tradeAutoCall.KnockInOutStatus != ConsTrade.KnockState.KnockedOut)
|
|
{
|
|
double koBarrier;
|
|
if (autocall.CustomizedKOBarriers != null && autocall.CustomizedKOBarriers.Length > 0)
|
|
{
|
|
var index = autocall.KOObsDates.Select(x => x.DateTime).ToList().IndexOf(valueDate);
|
|
koBarrier = autocall.CustomizedKOBarriers[index];
|
|
}
|
|
else
|
|
{
|
|
koBarrier = tradeAutoCall.KOBarrier;
|
|
}
|
|
|
|
if (otcTrade.IsMoneynessOptionData)
|
|
{
|
|
koBarrier *= otcTrade.SpotPrice ?? 1.0;
|
|
}
|
|
|
|
#region 票息检查
|
|
var couponBarrier =
|
|
otcTrade.IsMoneynessOptionData ?
|
|
tradeAutoCall.CouponBarrier * otcTrade.SpotPrice :
|
|
tradeAutoCall.CouponBarrier;
|
|
|
|
//看涨 - 向上敲出,看跌 - 向下敲出
|
|
var isKnockedOut = isCall ? closePrice >= koBarrier : closePrice <= koBarrier;
|
|
|
|
//有票息
|
|
if (isCall ? closePrice >= couponBarrier : closePrice <= couponBarrier)
|
|
{
|
|
//利息计算时,当autocall的Notional包含了符号,则CouponPayment考虑了买卖方向了
|
|
defaultAmount = autocall.CouponPayment(valueDate, includeTradeStartDate: tradeAutoCall.CouponIncludeStartDate == true && tradeAutoCall.CouponDayCount != "Monthly");
|
|
if (isKnockedOut)
|
|
{
|
|
tradeAutoCall.KnockInOutStatus = ConsTrade.KnockState.KnockedOut;
|
|
defaultAmount = TradeHelper.GetAmountByPaymentAmount(defaultAmount, otcTrade.PrincipalSum(), otcTrade.BuySell);
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
#region 敲入检查
|
|
//在当前结算日之前未敲出且未敲入:
|
|
if (!((tradeAutoCall.KnockInOutStatus == ConsTrade.KnockState.KnockedOut
|
|
|| tradeAutoCall.KnockInOutStatus == ConsTrade.KnockState.KnockedIn)
|
|
&& tradeAutoCall.KnockInOutDate < valueDate)
|
|
&& autocall.KIObsDates.Select(x => x.DateTime).Contains(valueDate))
|
|
{
|
|
var kiBarrier =
|
|
otcTrade.IsMoneynessOptionData ?
|
|
tradeAutoCall.KIBarrier * otcTrade.SpotPrice :
|
|
tradeAutoCall.KIBarrier;
|
|
|
|
//看涨 - 向下敲入,看跌 - 向上敲入
|
|
var knockedin = isCall ? closePrice <= kiBarrier : closePrice >= kiBarrier;
|
|
|
|
// 发生敲入事件
|
|
if (knockedin)
|
|
{
|
|
// 更新观察状态
|
|
tradeAutoCall.KnockInOutStatus = ConsTrade.KnockState.KnockedIn;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region 到期检查
|
|
if (valueDate == autocall.ExerciseDates.Last().DateTime)
|
|
{
|
|
if (tradeAutoCall.KnockInOutStatus == ConsTrade.KnockState.KnockedIn)
|
|
{
|
|
//已敲入,到期时计算期权收益
|
|
var optionPayoffPayment = autocall.GetPayoff(new double[] { closePrice });
|
|
//敲入是否支付票息
|
|
if (tradeAutoCall.IncludeCouponAfterKI)
|
|
{
|
|
//optionPayoffPayment[0].PaymentAmount包含了买卖方向的处理了
|
|
defaultAmount += TradeHelper.GetAmountByPaymentAmount(optionPayoffPayment[0].PaymentAmount, otcTrade.PrincipalSum(), otcTrade.BuySell);
|
|
}
|
|
else
|
|
{
|
|
//optionPayoffPayment[0].PaymentAmount包含了买卖方向的处理了
|
|
defaultAmount = TradeHelper.GetAmountByPaymentAmount(optionPayoffPayment[0].PaymentAmount, otcTrade.PrincipalSum(), otcTrade.BuySell);
|
|
}
|
|
}
|
|
else if(tradeAutoCall.KnockInOutStatus != ConsTrade.KnockState.KnockedOut)
|
|
{
|
|
defaultAmount = TradeHelper.GetAmountByPaymentAmount(defaultAmount, otcTrade.PrincipalSum(), otcTrade.BuySell);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
return defaultAmount;
|
|
}
|
|
|
|
public void CheckAutocallKnockInOutStatus(OtcTradeBase otcTrade, trade_autocall tradeAutoCall, DateTime valueDate, double closePrice, double? SettlementAmount)
|
|
{
|
|
try
|
|
{
|
|
var oldKnockInOutStatus = tradeAutoCall.KnockInOutStatus;
|
|
InnerCheckAutocallKnockInOutStatus(otcTrade, tradeAutoCall, valueDate, closePrice, SettlementAmount);
|
|
|
|
if (oldKnockInOutStatus != tradeAutoCall.KnockInOutStatus)
|
|
{
|
|
var KnockInOutStatus = tradeAutoCall.KnockInOutStatus == ConsTrade.KnockState.KnockedIn ? "敲入" : "敲出";
|
|
AddTradeOperationHistoryAndSetParentTradeInfo(false, otcTrade, KnockInOutStatus, KnockInOutStatus);
|
|
if (KnockInOutStatus == "敲出" && tradeAutoCall.CouponPayType == CouponPayTypeEnum.AtKnockout)
|
|
{
|
|
var observations = DbContext.autocall_observation.Where(n => n.TradeId == otcTrade.id).ToArray();
|
|
foreach (var item in observations)
|
|
{
|
|
item.PaymentDate = valueDate;
|
|
}
|
|
DbContext.SaveChanges();
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new ServiceException($"[检查敲入敲出]交易编号:{otcTrade.TradeNumber},{ex.Message}", ex);
|
|
}
|
|
}
|
|
|
|
private void InnerCheckAutocallKnockInOutStatus(OtcTradeBase otcTrade, trade_autocall tradeAutoCall, DateTime valueDate, double closePrice, double? SettlementAmount)
|
|
{
|
|
var hasUseSettlementAmount = false;
|
|
double notional = 0;
|
|
var eodTrade = DbContext.eod_trade.FirstOrDefault(x => x.TradeId == otcTrade.id && x.ValueDate == valueDate);
|
|
if (eodTrade != null)
|
|
{
|
|
notional = eodTrade.trade.Notional;
|
|
}
|
|
else
|
|
{
|
|
var bodTrade = DbContext.bod_Trade.FirstOrDefault(x => x.TradeId == otcTrade.id && x.ValueDate == valueDate);
|
|
notional = bodTrade != null ? bodTrade.trade.Notional : otcTrade.Notional;
|
|
var tradeCashs = DbContext.trade_cash.Where(x => x.ValidState != ConsGlobal.InValid && !x.IsDeleted && x.TradeId == otcTrade.id && x.Action != "系统操作-行权费" && x.Action != "系统操作-票息" && x.ValueDate == valueDate).ToList();
|
|
notional -= tradeCashs.Sum(x => x.UnwindNotional ?? 0);
|
|
}
|
|
|
|
//敲出到期支付,或者敲入到期支付票息时,交易可能已经敲出了或者到起执行了,这个时候到期日收盘,需要补上到期票息,这里处理该票息对应的持仓份额
|
|
if (tradeAutoCall.CouponPayType == CouponPayTypeEnum.AtMaturity && valueDate == otcTrade.ExerciseDate && ConsTrade.TradeCompleteStatus.Contains(otcTrade.TradeStatus))
|
|
{
|
|
var tradeCash = DbContext.trade_cash.Where(x => x.ValidState != ConsGlobal.InValid && !x.IsDeleted && x.TradeId == otcTrade.id && x.IsLastAction).FirstOrDefault();
|
|
if (tradeCash != null)
|
|
{
|
|
notional = tradeCash.Notional;
|
|
}
|
|
}
|
|
|
|
//敲入转期权和到期支付票息同时存在时,若设置观察价格页面设置了结算金额,作为票息处理,敲入了结金额维持系统计算逻辑不变
|
|
var optionTrade = QdpTradeBuilder.GetAutocallOptionTrade(otcTrade, tradeAutoCall,
|
|
new OptionTradeParamRequest(valuedateBLL.SysRiskFreeRate()) { ParamOverride = x => { x.notional = notional; } });
|
|
var autocall = (AutoCall)optionTrade.Instrument;
|
|
var kiBarrier = otcTrade.IsMoneynessOptionData ? tradeAutoCall.KIBarrier * otcTrade.SpotPrice : tradeAutoCall.KIBarrier;
|
|
var isCall = ConsGlobal.CallPut.IsCall(otcTrade.CallPut);
|
|
|
|
//只在敲出观察日检查敲出和票息情况
|
|
//如果交易已经是敲出状态了,不用再做票息和敲出检查
|
|
if (autocall.KOObsDates.Select(x => x.DateTime).Contains(valueDate)
|
|
&& tradeAutoCall.KnockInOutStatus != ConsTrade.KnockState.KnockedOut)
|
|
{
|
|
double koBarrier;
|
|
if (autocall.CustomizedKOBarriers != null && autocall.CustomizedKOBarriers.Length > 0)
|
|
{
|
|
var index = autocall.KOObsDates.Select(x => x.DateTime).ToList().IndexOf(valueDate);
|
|
koBarrier = autocall.CustomizedKOBarriers[index];
|
|
}
|
|
else
|
|
{
|
|
koBarrier = tradeAutoCall.KOBarrier;
|
|
}
|
|
|
|
if (otcTrade.IsMoneynessOptionData)
|
|
{
|
|
koBarrier *= otcTrade.SpotPrice ?? 1.0;
|
|
}
|
|
|
|
#region 票息检查
|
|
var couponBarrier =
|
|
otcTrade.IsMoneynessOptionData ?
|
|
tradeAutoCall.CouponBarrier * otcTrade.SpotPrice :
|
|
tradeAutoCall.CouponBarrier;
|
|
|
|
//看涨 - 向上敲出,看跌 - 向下敲出
|
|
var isKnockedOut = isCall ? closePrice >= koBarrier : closePrice <= koBarrier;
|
|
|
|
//有票息
|
|
if (isCall ? closePrice >= couponBarrier : closePrice <= couponBarrier)
|
|
{
|
|
//利息计算时,当autocall的Notional包含了符号,则GetEffectiveObservation考虑了买卖方向了
|
|
var observation = autocall.GetEffectiveObservation(valueDate, includeTradeStartDate: tradeAutoCall.CouponIncludeStartDate == true && tradeAutoCall.CouponDayCount != "Monthly");
|
|
|
|
if (observation != null)
|
|
{
|
|
//otcTrade.trade_autocall = tradeAutocall;
|
|
var _settlementAmount = SettlementAmount;
|
|
//观察日页面设置的结算金额
|
|
if (SettlementAmount != null)
|
|
{
|
|
hasUseSettlementAmount = true;
|
|
//到期敲入且未敲出情况
|
|
if (valueDate == autocall.ExerciseDates.Last().DateTime && !isKnockedOut)
|
|
{
|
|
//当前满足敲入或者已经敲入了
|
|
if (autocall.KIObsDates.Select(x => x.DateTime).Contains(valueDate) && closePrice <= kiBarrier || tradeAutoCall.KnockInOutStatus == ConsTrade.KnockState.KnockedIn)
|
|
{
|
|
tradeAutoCall.KnockInOutStatus = ConsTrade.KnockState.KnockedIn;
|
|
var optionPayoffPayment = autocall.GetPayoff(new double[] { closePrice });
|
|
var paymentAmount = TradeHelper.GetAmountByPaymentAmount(optionPayoffPayment[0].PaymentAmount, otcTrade.PrincipalSum(), otcTrade.BuySell);
|
|
//在记录票息时将敲入部分的payoff先减掉,在后面到期处理时会再添加一笔敲入的资金记录
|
|
_settlementAmount -= paymentAmount;
|
|
}
|
|
}
|
|
}
|
|
|
|
SaveCouponObservation(otcTrade, tradeAutoCall, observation, valueDate, closePrice, isKnockedOut, _settlementAmount);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region 敲出检查
|
|
|
|
// 发生敲出事件
|
|
if (isKnockedOut)
|
|
{
|
|
// 更新观察状态
|
|
tradeAutoCall.KnockInOutStatus = ConsTrade.KnockState.KnockedOut;
|
|
tradeAutoCall.KnockInOutDate = valueDate;
|
|
|
|
// 更新交易状态
|
|
otcTrade.TradeStatus = ConsTrade.已平仓;
|
|
otcTrade.UnWindDate = valueDate;
|
|
|
|
var parentTradeId = 0;
|
|
var parentTradeCashId = 0;
|
|
//敲出时支付的票息在敲出日写入资金记录
|
|
if (tradeAutoCall.CouponPayType != CouponPayTypeEnum.AtCreated)
|
|
{
|
|
var hasUnfinishedGroupAction = false;
|
|
var continueTradeCashHandle = false;
|
|
var tradeCash = 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;
|
|
tradeCash = SaveGroupUnwindCash(otcTrade, valueDate, 0, closePrice, out continueTradeCashHandle);
|
|
parentTradeCashId = tradeCash.id;
|
|
}
|
|
}
|
|
|
|
var amount = SaveCouponCashOnEnd(otcTrade, tradeAutoCall.CouponPayType == CouponPayTypeEnum.AtMaturity ? otcTrade.ExerciseDate.Value : valueDate, parentTradeId, parentTradeCashId, closePrice);
|
|
|
|
if (otcTrade.IsGroup == 2 && otcTrade.ParentTradeId > 0 && !hasUnfinishedGroupAction && continueTradeCashHandle)
|
|
{
|
|
tradeCash.Amount += amount;
|
|
DbContext.SaveChanges();
|
|
}
|
|
}
|
|
|
|
SaveCash(otcTrade, ClientCashInCashOut.系统操作_票息, null, 0, tradeAutoCall.CouponPayType == CouponPayTypeEnum.AtMaturity ? otcTrade.ExerciseDate.Value : valueDate, closePrice, valueDate, true, isLastAction: true, parentTradeId: parentTradeId, parentTradeCashId: parentTradeCashId);
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
#region 敲入检查
|
|
//在当前结算日之前未敲出且未敲入:
|
|
if (!((tradeAutoCall.KnockInOutStatus == ConsTrade.KnockState.KnockedOut
|
|
|| tradeAutoCall.KnockInOutStatus == ConsTrade.KnockState.KnockedIn)
|
|
&& tradeAutoCall.KnockInOutDate < valueDate)
|
|
&& autocall.KIObsDates.Select(x => x.DateTime).Contains(valueDate))
|
|
{
|
|
//看涨 - 向下敲入,看跌 - 向上敲入
|
|
var knockedin = isCall ? closePrice <= kiBarrier : closePrice >= kiBarrier;
|
|
|
|
// 发生敲入事件
|
|
if (knockedin)
|
|
{
|
|
// 更新观察状态
|
|
tradeAutoCall.KnockInOutStatus = ConsTrade.KnockState.KnockedIn;
|
|
tradeAutoCall.KnockInOutDate = valueDate;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region 到期检查
|
|
if (valueDate == autocall.ExerciseDates.Last().DateTime)
|
|
{
|
|
if (tradeAutoCall.KnockInOutStatus == ConsTrade.KnockState.KnockedIn)
|
|
{
|
|
otcTrade.TradeStatus = ConsTrade.已执行;
|
|
otcTrade.UnWindDate = valueDate;
|
|
if (!autocall.IncludeCouponAfterKI)
|
|
{
|
|
// 敲入不支付票息,则要将之前累积的票息删除掉
|
|
RemoveAccumulatedCoupon(otcTrade.id);
|
|
hasUseSettlementAmount = false;
|
|
}
|
|
double paymentAmount = 0;
|
|
|
|
if (SettlementAmount != null && !hasUseSettlementAmount)
|
|
{
|
|
paymentAmount = SettlementAmount ?? 0;
|
|
}
|
|
else
|
|
{
|
|
//已敲入,到期时计算期权收益
|
|
var optionPayoffPayment = autocall.GetPayoff(new double[] { closePrice });
|
|
//optionPayoffPayment[0].PaymentAmount包含了买卖方向的处理了
|
|
paymentAmount = TradeHelper.GetAmountByPaymentAmount(optionPayoffPayment[0].PaymentAmount, otcTrade.PrincipalSum(), otcTrade.BuySell);
|
|
|
|
}
|
|
|
|
var parentTradeId = 0;
|
|
var parentTradeCashId = 0;
|
|
var hasUnfinishedGroupAction = false;
|
|
var continueTradeCashHandle = false;
|
|
var tradeCash = 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;
|
|
tradeCash = SaveGroupUnwindCash(otcTrade, valueDate, paymentAmount, closePrice, out continueTradeCashHandle);
|
|
parentTradeCashId = tradeCash.id;
|
|
}
|
|
}
|
|
|
|
SaveOptionPayoffCash(otcTrade, paymentAmount, valueDate, closePrice, valueDate, parentTradeId, parentTradeCashId);
|
|
|
|
//期末支付的票息在到期日写入资金记录
|
|
if (tradeAutoCall.CouponPayType == CouponPayTypeEnum.AtMaturity && tradeAutoCall.IncludeCouponAfterKI)
|
|
{
|
|
var amount = SaveCouponCashOnEnd(otcTrade, valueDate, parentTradeId, parentTradeCashId, closePrice);
|
|
if (otcTrade.IsGroup == 2 && otcTrade.ParentTradeId > 0 && !hasUnfinishedGroupAction && continueTradeCashHandle)
|
|
{
|
|
tradeCash.Amount += amount;
|
|
DbContext.SaveChanges();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (tradeAutoCall.KnockInOutStatus != ConsTrade.KnockState.KnockedOut)
|
|
{
|
|
// 更新交易状态
|
|
otcTrade.TradeStatus = ConsTrade.已到期;
|
|
otcTrade.UnWindDate = valueDate;
|
|
}
|
|
|
|
var parentTradeId = 0;
|
|
var parentTradeCashId = 0;
|
|
var hasUnfinishedGroupAction = false;
|
|
var continueTradeCashHandle = false;
|
|
var tradeCash = 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;
|
|
tradeCash = SaveGroupUnwindCash(otcTrade, valueDate, 0, closePrice, out continueTradeCashHandle);
|
|
parentTradeCashId = tradeCash.id;
|
|
}
|
|
}
|
|
|
|
//期末支付的票息在到期日写入资金记录
|
|
if (tradeAutoCall.CouponPayType != CouponPayTypeEnum.AtCreated && tradeAutoCall.KnockInOutStatus != ConsTrade.KnockState.KnockedOut)
|
|
{
|
|
var amount = SaveCouponCashOnEnd(otcTrade, valueDate, parentTradeId, parentTradeCashId, closePrice);
|
|
if (otcTrade.IsGroup == 2 && otcTrade.ParentTradeId > 0 && !hasUnfinishedGroupAction && continueTradeCashHandle)
|
|
{
|
|
tradeCash.Amount += amount;
|
|
DbContext.SaveChanges();
|
|
}
|
|
}
|
|
|
|
if (tradeAutoCall.KnockInOutStatus != ConsTrade.KnockState.KnockedOut)
|
|
{
|
|
SaveCash(otcTrade, ClientCashInCashOut.系统操作_票息, "到期行权", 0, valueDate, closePrice, valueDate, false, isLastAction: true, parentTradeId: parentTradeId, parentTradeCashId: parentTradeCashId);
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// 当票息在到期支付时,从autocall_observation表记录的票息累积数据生成对应的资金记录
|
|
/// </summary>
|
|
private double SaveCouponCashOnEnd(OtcTradeBase trade, DateTime valueDate, int parentTradeId, int parentTradeCashId, double closePrice)
|
|
{
|
|
double amount = 0;
|
|
var tradeCashes = DbContext.trade_cash.Where(t => t.TradeId == trade.id && !t.IsDeleted
|
|
&& t.Action == ClientCashInCashOut.系统操作_票息).ToList();
|
|
if (tradeCashes != null)
|
|
{
|
|
DbContext.trade_cash.RemoveRange(tradeCashes);
|
|
var tradeCashIds = tradeCashes.Select(x => x.id);
|
|
var tradeCashDetials = DbContext.trade_cash_detail.Where(x => tradeCashIds.Contains(x.TradeCashId));
|
|
DbContext.trade_cash_detail.RemoveRange(tradeCashDetials);
|
|
}
|
|
|
|
var clientCashes = DbContext.ClientCashInCashOut.Where(c => c.TradeId == trade.id
|
|
&& c.Action == ClientCashInCashOut.系统操作_票息).ToList();
|
|
if (clientCashes != null)
|
|
{
|
|
DbContext.ClientCashInCashOut.RemoveRange(clientCashes);
|
|
}
|
|
|
|
var observations = DbContext.autocall_observation.Where(o => o.TradeId == trade.id).OrderBy(x => x.EndDate).ToList();
|
|
var maxEndDate = observations.Max(x => (DateTime?)x.EndDate) ?? DateTime.MinValue;
|
|
observations.ForEach(o =>
|
|
{
|
|
amount += o.PaymentAmount;
|
|
// 保存trade_cash
|
|
var tc = new trade_cash
|
|
{
|
|
OptId = UserId,
|
|
OptName = UserName,
|
|
OptDate = DateTime.Now,
|
|
ExceciseType = "现金",
|
|
TradeType = trade.BuySell,
|
|
CallPut = trade.CallPut,
|
|
Notional = trade.Notional,
|
|
TradeAmount = trade.TradeAmount,
|
|
UnwindNotional = maxEndDate == o.EndDate ? trade.Notional : 0,
|
|
UnwindTradeAmount = maxEndDate == o.EndDate ? trade.TradeAmount : 0,
|
|
UnwindPercentRate = maxEndDate == o.EndDate ? (trade.OriginalNotional != 0 ? trade.Notional / trade.OriginalNotional : 0) : 0,
|
|
Amount = o.PaymentAmount,
|
|
UnwindPrice = Math.Abs(trade.Notional != 0 ? o.PaymentAmount / trade.Notional : 0),
|
|
UnwindPricePercentRate = Math.Abs(trade.Notional != 0 && trade.SpotPrice != null && trade.SpotPrice != 0 ? o.PaymentAmount / trade.Notional / trade.SpotPrice.Value : 0),
|
|
FinalPrice = closePrice,
|
|
TradeId = trade.id,
|
|
HappenedDate = o.EndDate,
|
|
Action = ClientCashInCashOut.系统操作_票息,
|
|
ExerciseWay = "到期行权",
|
|
Status = TradeCashStatusEnum.已执行,
|
|
ValueDate = valueDate,
|
|
ParentTradeId = parentTradeId,
|
|
ParentTradeCashId = parentTradeCashId
|
|
};
|
|
DbContext.trade_cash.Add(tc);
|
|
DbContext.SaveChanges();
|
|
|
|
SaveTradeCashDetail(tc);
|
|
|
|
// 保存ClientCashInCashOut
|
|
var client = DataCacheProvider.GetClientDataSource().GetData(trade.ClientId);
|
|
if (client != null)
|
|
{
|
|
var cashInOutRecord = new ClientCashInCashOut
|
|
{
|
|
Direction = "应收",
|
|
Number = UniqueTimeId.GetStr(),
|
|
ClientId = client.id,
|
|
ClientName = client.Name,
|
|
ClientNumber = client.Number,
|
|
Money = -tc.Amount,
|
|
HappenDate = valueDate,
|
|
State = ClientCashInCashOut.已确认,
|
|
OptDate = tc.OptDate,
|
|
OptId = tc.OptId,
|
|
CreatorName = tc.OptName,
|
|
CreateDate = tc.OptDate,
|
|
CreatorId = tc.OptId,
|
|
OptName = tc.OptName,
|
|
TradeId = trade.id,
|
|
TradeCashId = tc.id,
|
|
Action = ClientCashInCashOut.系统操作_票息,
|
|
TradeNumber = trade.TradeNumber,
|
|
IsGroup = trade.IsGroup
|
|
};
|
|
|
|
DbContext.ClientCashInCashOut.Add(cashInOutRecord);
|
|
}
|
|
});
|
|
|
|
return amount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 适用于票息到期支付,但敲入后不支付票息的情况下,将已经累计的票息信息删除
|
|
/// </summary>
|
|
private void RemoveAccumulatedCoupon(int tradeId)
|
|
{
|
|
var observations = DbContext.autocall_observation.Where(o => o.TradeId == tradeId).ToList();
|
|
if (observations.Count > 0)
|
|
{
|
|
DbContext.autocall_observation.RemoveRange(observations);
|
|
}
|
|
}
|
|
|
|
private trade_cash SaveOptionPayoffCash(OtcTradeBase trade, double amount, DateTime valueDate, double underlyingPrice, DateTime happenDate, int parentTradeId, int parentTradeCashId)
|
|
{
|
|
return SaveCash(trade, ClientCashInCashOut.系统操作_行权费, TradeCashExerciseWayEnum.到期行权, amount, valueDate, underlyingPrice, happenDate, false, isLastAction: true, parentTradeId: parentTradeId, parentTradeCashId: parentTradeCashId);
|
|
}
|
|
|
|
public autocall_observation SaveCouponObservation(OtcTradeBase trade, trade_autocall tradeAutoCall
|
|
, ObservationPayment observation, DateTime happenDate, double underlyingPrice, bool isKnockedOut, double? SettlementAmount, bool saveChanges = true)
|
|
{
|
|
//保存autocall_observation
|
|
var observationRecord = DbContext.autocall_observation.FirstOrDefault(o => o.TradeId == trade.id && o.EndDate == happenDate);
|
|
if (observationRecord == null)
|
|
{
|
|
observationRecord = new autocall_observation()
|
|
{
|
|
TradeId = trade.id,
|
|
StartDate = observation.StartDate.DateTime.Date,
|
|
EndDate = observation.EndDate.DateTime.Date,
|
|
CouponRate = observation.CouponRate,
|
|
StockEqvNotional = observation.Notional,
|
|
PaymentAmount = SettlementAmount == null ? observation.PaymentAmount : SettlementAmount.Value,
|
|
PaymentDate = observation.PaymentDate.DateTime.Date
|
|
};
|
|
DbContext.autocall_observation.Add(observationRecord);
|
|
}
|
|
else
|
|
{
|
|
observationRecord.StartDate = observation.StartDate.DateTime.Date;
|
|
observationRecord.EndDate = observation.EndDate.DateTime.Date;
|
|
observationRecord.CouponRate = observation.CouponRate;
|
|
observationRecord.StockEqvNotional = observation.Notional;
|
|
if (SettlementAmount != null)
|
|
{
|
|
observationRecord.PaymentAmount = SettlementAmount.Value;
|
|
}
|
|
else
|
|
{
|
|
// 系统操作_票息 修改 功能暂时禁掉
|
|
//bool isupdate = true;
|
|
//var trade_cash = DbContext.trade_cash.Where(x => x.id == observationRecord.CashId && x.ValidState != "InValid" && x.Action == ClientCashInCashOut.系统操作_票息).FirstOrDefault();
|
|
//if (trade_cash != null)
|
|
//{
|
|
// var cashInOutRecord = DbContext.ClientCashInCashOut.FirstOrDefault(c => c.TradeCashId == trade_cash.id);
|
|
// if (cashInOutRecord != null)
|
|
// {
|
|
// if (DbContext.clientcashincashout_update.Where(x => x.ClientcashincashoutId == cashInOutRecord.id && x.ValidState != "InValid" && x.State == "已确认").Any())
|
|
// {
|
|
// isupdate = false;
|
|
// }
|
|
// }
|
|
//}
|
|
//if (isupdate)
|
|
//{
|
|
// observationRecord.PaymentAmount = observation.PaymentAmount;
|
|
//}
|
|
observationRecord.PaymentAmount = observation.PaymentAmount;
|
|
}
|
|
observationRecord.PaymentDate = observation.PaymentDate.DateTime.Date;
|
|
}
|
|
if (saveChanges)
|
|
{
|
|
// 票息当期付,立即产生资金记录
|
|
if (tradeAutoCall.CouponPayType == CouponPayTypeEnum.AtCreated)
|
|
{
|
|
var parentTradeId = 0;
|
|
var parentTradeCashId = 0;
|
|
if (trade.IsGroup == 2 && trade.ParentTradeId > 0)
|
|
{
|
|
//已经存在票息记录的说明该票息已经和组合主交易有关联了,不需要再做处理
|
|
if (DbContext.trade_cash.Any(t => t.TradeId == trade.id && t.Action == ClientCashInCashOut.系统操作_票息 && !t.IsDeleted && t.HappenedDate == happenDate))
|
|
{
|
|
return observationRecord;
|
|
}
|
|
|
|
var groupAction = DbContext.trade_cash_group_action.FirstOrDefault(x => x.TradeId == trade.id && x.Status != "已完成");
|
|
if (groupAction != null)
|
|
{
|
|
groupAction.Status = "已完成";
|
|
parentTradeCashId = groupAction.ParentTradeCashId;
|
|
parentTradeId = groupAction.ParentTradeId;
|
|
}
|
|
else
|
|
{
|
|
parentTradeId = trade.ParentTradeId;
|
|
var paymentAmount = SettlementAmount != null ? SettlementAmount.Value : observation.PaymentAmount;
|
|
parentTradeCashId = SaveGroupCouponCash(trade, happenDate, paymentAmount, underlyingPrice);
|
|
}
|
|
}
|
|
bool isLastAction = isKnockedOut || (happenDate == trade.ExerciseDate && tradeAutoCall.KnockInOutStatus != ConsTrade.KnockState.KnockedIn);
|
|
var tradeCash = new trade_cash();
|
|
if (SettlementAmount != null)
|
|
{
|
|
tradeCash = SaveCash(trade, ClientCashInCashOut.系统操作_票息, null, SettlementAmount ?? 0, happenDate, underlyingPrice, happenDate, isKnockedOut, isLastAction: isLastAction, parentTradeId: parentTradeId, parentTradeCashId: parentTradeCashId);
|
|
}
|
|
else
|
|
{
|
|
var paymentAmount = observation.PaymentAmount;
|
|
if (isKnockedOut)
|
|
{
|
|
paymentAmount = TradeHelper.GetAmountByPaymentAmount(paymentAmount, trade.PrincipalSum(), trade.BuySell);
|
|
}
|
|
tradeCash = SaveCash(trade, ClientCashInCashOut.系统操作_票息, happenDate == trade.ExerciseDate ? "到期行权" : null, paymentAmount, happenDate, underlyingPrice, happenDate, isKnockedOut, isLastAction: isLastAction, parentTradeId: parentTradeId, parentTradeCashId: parentTradeCashId);
|
|
}
|
|
observationRecord.CashId = tradeCash.id;
|
|
}
|
|
DbContext.SaveChanges();
|
|
}
|
|
return observationRecord;
|
|
}
|
|
|
|
public List<autocall_observation> QueryHappenedObservations(int tradeId, DateTime valueDate)
|
|
{
|
|
using (var db = new YLContext())
|
|
{
|
|
return db.autocall_observation.AsNoTracking().Where(o => o.TradeId == tradeId && o.EndDate <= valueDate).ToList();
|
|
}
|
|
}
|
|
}
|
|
}
|