412 lines
20 KiB
C#
412 lines
20 KiB
C#
using Qdp.Foundation.Implementations;
|
|
using Qdp.Pricing.Base.Implementations;
|
|
using Qdp.Pricing.Base.Utilities;
|
|
using YLErp.Abstract.DataProviders;
|
|
using YLErp.BLL;
|
|
using YLErp.DBModels.Consts;
|
|
using YLErp.Model;
|
|
using YLErp.Modules.DataProviderModule;
|
|
using YLErp.QdpModule;
|
|
|
|
namespace YLErp.Modules.TradeModule.ExoticOptionModule
|
|
{
|
|
/// <summary>
|
|
/// 障碍期权敲入敲出操作
|
|
/// 迁移自:trade_barrier_optionBLL
|
|
/// </summary>
|
|
public class BarrierOptionKnockioService : TradeCashServiceEx
|
|
{
|
|
public BarrierOptionKnockioService(YLBaseService baseService) : base(baseService)
|
|
{
|
|
}
|
|
|
|
public BarrierOptionKnockioService(OptUserInfo userInfo) : base(userInfo)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置障碍期权敲入敲出 ,返回error
|
|
/// </summary>
|
|
public void SetKnockInOut(DateTime valueDate, IEodPriceProviderV2 priceProvider, DateTime? startDate = null,
|
|
Action<OtcTrade, trade_barrier_option> afterKnowInOut = null, IEnumerable<int> clienIds = null)
|
|
{
|
|
if (priceProvider is null)
|
|
{
|
|
priceProvider = new EodPriceProvider(valueDate);
|
|
}
|
|
|
|
if (startDate == null)
|
|
{
|
|
startDate = valueDate.AddYears(-5);
|
|
}
|
|
var query = from td in DbContext.trade
|
|
join tb in DbContext.trade_barrier_option on td.id equals tb.TradeId
|
|
where td.TradeDate > startDate.Value && td.TradeDate <= valueDate
|
|
&& string.IsNullOrEmpty(tb.KnockInOutStatus)
|
|
&& td.TradeType == "障碍期权"
|
|
&& ConsTrade.确认成交 == td.TradeStatus
|
|
&& td.ValidState != ConsGlobal.InValid
|
|
&& td.DividendDate < valueDate
|
|
select new
|
|
{
|
|
trade = td,
|
|
tradeBarrier = tb
|
|
};
|
|
#region 增加客户筛选 tw
|
|
if (clienIds != null)
|
|
{
|
|
query = query.Where(l => clienIds.Contains(l.trade.ClientId));
|
|
}
|
|
#endregion
|
|
var trades = query.ToList();
|
|
|
|
if (trades == null || !trades.Any())
|
|
{
|
|
return;
|
|
}
|
|
|
|
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;//已到期交易不再观察;
|
|
}
|
|
double? closePrice, highPrice, lowPrice;
|
|
|
|
if (manuallyTradeObservationPrices.TryGetValue(tr.trade.id, out var manuallyTradeObservationPrice))
|
|
{
|
|
closePrice = manuallyTradeObservationPrice.Price;
|
|
highPrice = manuallyTradeObservationPrice.Price;
|
|
lowPrice = manuallyTradeObservationPrice.Price;
|
|
}
|
|
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;
|
|
highPrice = eodprice.HighPrice;
|
|
lowPrice = eodprice.LowPrice;
|
|
}
|
|
|
|
var tradeStatus = tr.trade.TradeStatus;
|
|
var oldKnockInOutStatus = tr.tradeBarrier.KnockInOutStatus;
|
|
|
|
CheckBarrierKnockInOutStatus(tr.trade, tr.tradeBarrier, valueDate, closePrice, highPrice, lowPrice);
|
|
|
|
if (oldKnockInOutStatus != tr.tradeBarrier.KnockInOutStatus)
|
|
{
|
|
var KnockInOutStatus = tr.tradeBarrier.KnockInOutStatus == ConsTrade.KnockState.KnockedIn ? "敲入" : "敲出";
|
|
AddTradeOperationHistoryAndSetParentTradeInfo(false, tr.trade, KnockInOutStatus, KnockInOutStatus);
|
|
}
|
|
|
|
//到期仍未敲入的情况
|
|
if (tr.tradeBarrier.BarrierType.Contains("敲入")
|
|
&& (string.IsNullOrWhiteSpace(tr.tradeBarrier.KnockInOutStatus) || ConsTrade.KnockState.IsMonitoring(tr.tradeBarrier.KnockInOutStatus))
|
|
&& tr.trade.ExerciseDate <= valueDate)
|
|
{
|
|
tr.trade.TradeStatus = ConsTrade.已到期;
|
|
tr.trade.UnWindDate = valueDate;
|
|
SaveBarrierRebateCash(tr.trade, tr.tradeBarrier, valueDate, closePrice, knockOutBarrierPrice: null);
|
|
}
|
|
|
|
if (tradeStatus != tr.trade.TradeStatus || oldKnockInOutStatus != tr.tradeBarrier.KnockInOutStatus)
|
|
{
|
|
//删除E/Bod_Trade记录
|
|
RemoveEodTradeAndFutureInfo(false, tr.trade.id, valueDate);
|
|
}
|
|
|
|
if (afterKnowInOut != null && DbContext.Entry(tr.tradeBarrier).State == EntityState.Modified)
|
|
{
|
|
afterKnowInOut(tr.trade, tr.tradeBarrier);
|
|
}
|
|
|
|
//更新,不能放到循环外,黑箱交易的子交易相互有依赖关系
|
|
DbContext.SaveChanges();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public void CheckBarrierKnockInOutStatus(OtcTradeBase td, trade_barrier_option tradeBarrier
|
|
, DateTime valuedate, double? closePrice, double? highPrice, double? lowPrice)
|
|
{
|
|
var BarrierPrice = td.IsMoneynessOptionData ? tradeBarrier.BarrierPrice * td.SpotPrice : tradeBarrier.BarrierPrice;
|
|
var UpperBarrierPrice = td.IsMoneynessOptionData ? tradeBarrier.UpperBarrierPrice * td.SpotPrice : tradeBarrier.UpperBarrierPrice;
|
|
|
|
switch (tradeBarrier.Discrete)
|
|
{
|
|
case "离散":
|
|
var observationDates = QdpHelper.GetObservationDatesFromString(tradeBarrier.ObservationDates);
|
|
//每日观察或者当前结算日是观察日的时候,才检查是否会敲入敲出
|
|
if (observationDates == null || observationDates.Contains(valuedate))
|
|
{
|
|
switch (tradeBarrier.BarrierType)
|
|
{
|
|
case "上升敲入":
|
|
if (closePrice >= BarrierPrice)
|
|
{
|
|
tradeBarrier.KnockInOutStatus = ConsTrade.KnockState.KnockedIn;
|
|
tradeBarrier.KnockInOutDate = valuedate;
|
|
tradeBarrier.KnockInOutNotional = td.Notional;
|
|
}
|
|
break;
|
|
case "上升敲出":
|
|
if (closePrice >= BarrierPrice)
|
|
{
|
|
tradeBarrier.KnockInOutStatus = ConsTrade.KnockState.KnockedOut;
|
|
tradeBarrier.KnockInOutDate = valuedate;
|
|
tradeBarrier.KnockInOutNotional = td.Notional;
|
|
td.TradeStatus = ConsTrade.已平仓;
|
|
td.UnWindDate = valuedate;
|
|
SaveBarrierRebateCash(td, tradeBarrier, valuedate, closePrice, tradeBarrier.BarrierPrice);
|
|
}
|
|
break;
|
|
case "下降敲入":
|
|
if (closePrice <= BarrierPrice)
|
|
{
|
|
tradeBarrier.KnockInOutStatus = ConsTrade.KnockState.KnockedIn;
|
|
tradeBarrier.KnockInOutDate = valuedate;
|
|
tradeBarrier.KnockInOutNotional = td.Notional;
|
|
}
|
|
break;
|
|
case "下降敲出":
|
|
if (closePrice <= BarrierPrice)
|
|
{
|
|
tradeBarrier.KnockInOutStatus = ConsTrade.KnockState.KnockedOut;
|
|
tradeBarrier.KnockInOutDate = valuedate;
|
|
tradeBarrier.KnockInOutNotional = td.Notional;
|
|
td.TradeStatus = ConsTrade.已平仓;
|
|
td.UnWindDate = valuedate;
|
|
SaveBarrierRebateCash(td, tradeBarrier, valuedate, closePrice, tradeBarrier.BarrierPrice);
|
|
}
|
|
break;
|
|
case "双障碍敲出":
|
|
if (closePrice >= UpperBarrierPrice || closePrice <= BarrierPrice)
|
|
{
|
|
var barrierPrice = closePrice >= UpperBarrierPrice ?
|
|
tradeBarrier.UpperBarrierPrice : tradeBarrier.BarrierPrice;
|
|
tradeBarrier.KnockInOutStatus = ConsTrade.KnockState.KnockedOut;
|
|
tradeBarrier.KnockInOutDate = valuedate;
|
|
tradeBarrier.KnockInOutNotional = td.Notional;
|
|
td.TradeStatus = ConsTrade.已平仓;
|
|
td.UnWindDate = valuedate;
|
|
var upDown = closePrice >= UpperBarrierPrice ? true : false;
|
|
SaveBarrierRebateCash(td, tradeBarrier, valuedate, closePrice, barrierPrice, upDown);
|
|
}
|
|
break;
|
|
case "双障碍敲入":
|
|
if (closePrice >= UpperBarrierPrice || closePrice <= BarrierPrice)
|
|
{
|
|
tradeBarrier.KnockInOutStatus = ConsTrade.KnockState.KnockedIn;
|
|
tradeBarrier.KnockInOutDate = valuedate;
|
|
tradeBarrier.KnockInOutNotional = td.Notional;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case "连续":
|
|
switch (tradeBarrier.BarrierType)
|
|
{
|
|
case "上升敲入":
|
|
if (highPrice >= BarrierPrice)
|
|
{
|
|
tradeBarrier.KnockInOutStatus = ConsTrade.KnockState.KnockedIn;
|
|
tradeBarrier.KnockInOutDate = valuedate;//UpdateDate
|
|
tradeBarrier.KnockInOutNotional = td.Notional;
|
|
}
|
|
break;
|
|
case "上升敲出":
|
|
if (highPrice >= BarrierPrice)
|
|
{
|
|
tradeBarrier.KnockInOutStatus = ConsTrade.KnockState.KnockedOut;
|
|
tradeBarrier.KnockInOutDate = valuedate;
|
|
tradeBarrier.KnockInOutNotional = td.Notional;
|
|
td.TradeStatus = ConsTrade.已平仓;
|
|
td.UnWindDate = valuedate;
|
|
SaveBarrierRebateCash(td, tradeBarrier, valuedate, highPrice, tradeBarrier.BarrierPrice);
|
|
}
|
|
break;
|
|
case "下降敲入":
|
|
if (lowPrice <= BarrierPrice)
|
|
{
|
|
tradeBarrier.KnockInOutStatus = ConsTrade.KnockState.KnockedIn;
|
|
tradeBarrier.KnockInOutDate = valuedate;
|
|
tradeBarrier.KnockInOutNotional = td.Notional;
|
|
}
|
|
break;
|
|
case "下降敲出":
|
|
if (lowPrice <= BarrierPrice)
|
|
{
|
|
tradeBarrier.KnockInOutStatus = ConsTrade.KnockState.KnockedOut;
|
|
tradeBarrier.KnockInOutDate = valuedate;
|
|
tradeBarrier.KnockInOutNotional = td.Notional;
|
|
td.TradeStatus = ConsTrade.已平仓;
|
|
td.UnWindDate = valuedate;
|
|
SaveBarrierRebateCash(td, tradeBarrier, valuedate, lowPrice, tradeBarrier.BarrierPrice);
|
|
}
|
|
break;
|
|
case "双障碍敲出":
|
|
if (highPrice >= UpperBarrierPrice || lowPrice <= BarrierPrice)
|
|
{
|
|
var price = highPrice >= UpperBarrierPrice ? highPrice : lowPrice;
|
|
var barrierPrice = highPrice >= UpperBarrierPrice ?
|
|
tradeBarrier.UpperBarrierPrice : tradeBarrier.BarrierPrice;
|
|
|
|
tradeBarrier.KnockInOutStatus = ConsTrade.KnockState.KnockedOut;
|
|
tradeBarrier.KnockInOutDate = valuedate;
|
|
tradeBarrier.KnockInOutNotional = td.Notional;
|
|
td.TradeStatus = ConsTrade.已平仓;
|
|
td.UnWindDate = valuedate;
|
|
var upDown = highPrice >= UpperBarrierPrice ? true : false;
|
|
SaveBarrierRebateCash(td, tradeBarrier, valuedate, price, barrierPrice, upDown);
|
|
}
|
|
break;
|
|
case "双障碍敲入":
|
|
if (highPrice >= UpperBarrierPrice || lowPrice <= BarrierPrice)
|
|
{
|
|
tradeBarrier.KnockInOutStatus = ConsTrade.KnockState.KnockedIn;
|
|
tradeBarrier.KnockInOutDate = valuedate;
|
|
tradeBarrier.KnockInOutNotional = td.Notional;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 障碍期权到期时仍未敲入,或者已经敲出,应获得rebate,保存相应的资金信息
|
|
/// </summary>
|
|
public void SaveBarrierRebateCash(OtcTradeBase td, trade_barrier_option tradeBarrier, DateTime settleDate, double? closePrice, double? knockOutBarrierPrice, bool upDown = false)
|
|
{
|
|
var tc = SetTradeCash(td, tradeBarrier, settleDate, closePrice, knockOutBarrierPrice, upDown);
|
|
|
|
SaveTradeCashDetail(tc);
|
|
//增加出入金记录
|
|
new ClientCashinCashoutBLL(this).CloseTrade_ClientCashInCashOutSave(td, tc, tc.ValueDate);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 生成TradeCash
|
|
/// </summary>
|
|
/// <param name="td">交易</param>
|
|
/// <param name="tradeBarrier">子对象</param>
|
|
/// <param name="settleDate">敲出/了结日期</param>
|
|
/// <param name="closePrice">收盘价</param>
|
|
/// <param name="knockOutBarrierPrice">障碍价</param>
|
|
/// <param name="saveChanges">是否保存</param>
|
|
/// <returns></returns>
|
|
public trade_cash SetTradeCash(OtcTradeBase td, trade_barrier_option tradeBarrier, DateTime settleDate, double? closePrice, double? knockOutBarrierPrice, bool upDown = false, bool saveChanges = true)
|
|
{
|
|
var spotPrice = td.SpotPrice ?? 0;
|
|
|
|
double rebate, rebateRate;
|
|
if (upDown)
|
|
{
|
|
if (td.IsUsePremiumRate == true)
|
|
{
|
|
rebateRate = tradeBarrier.RebateHighRate ?? 0;
|
|
rebate = rebateRate * spotPrice;
|
|
}
|
|
else
|
|
{
|
|
rebate = tradeBarrier.RebateHigh ?? 0;
|
|
rebateRate = spotPrice > 0 ? rebate / spotPrice : 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (td.IsUsePremiumRate == true)
|
|
{
|
|
rebateRate = tradeBarrier.RebateRate ?? 0;
|
|
rebate = rebateRate * spotPrice;
|
|
}
|
|
else
|
|
{
|
|
rebate = tradeBarrier.Rebate ?? 0;
|
|
rebateRate = spotPrice > 0 ? rebate / spotPrice : 0;
|
|
}
|
|
}
|
|
|
|
if (tradeBarrier.RebateAnnualizedAtKO)
|
|
{
|
|
var rebateDayCountImpl = string.IsNullOrWhiteSpace(tradeBarrier.RebateDayCount) ? new Act365() : tradeBarrier.RebateDayCount.ToDayCountImpl();
|
|
var fraction = rebateDayCountImpl.CalcDayCountFraction(new Date(td.StartDate.Value), new Date(settleDate));
|
|
rebate *= fraction;
|
|
rebate += Math.Abs((td.TradeSinglePrice - td.TradeSinglePrice * fraction) ?? 0);
|
|
}
|
|
|
|
var req = new TradeCashReq
|
|
{
|
|
UnwindNotional = td.Notional,
|
|
UnwindPrice = rebate,
|
|
UnwindPricePercentRate = rebateRate,
|
|
FinalPrice = closePrice,
|
|
UnwindPercentRate = td.OriginalNotional > 0 ? td.Notional / td.OriginalNotional.Value : 0,
|
|
Notional = td.Notional,
|
|
TradeAmount = td.TradeAmount,
|
|
ValueDate = settleDate,
|
|
HappenedDate = settleDate,//记录流水记录的结算日
|
|
BarrierPrice = knockOutBarrierPrice
|
|
};
|
|
|
|
req.UnwindFee = req.UnwindNotional * (req.UnwindPrice ?? 0) + ((req.UnwindNotional / td.OriginalNotional * td.OriginalPrincipalSum) ?? 0);
|
|
|
|
if (valuedateBLL.SystemDate.UnwindAmountAngle == 1)
|
|
{
|
|
req.UnwindFee = req.UnwindFee * (td.BuySell == "卖出" ? -1 : 1);
|
|
}
|
|
if (valuedateBLL.SystemDate.UnwindSinglePriceAngle == 1)
|
|
{
|
|
req.UnwindPrice = req.UnwindPrice * (td.BuySell == "卖出" ? -1 : 1);
|
|
}
|
|
var tc = CloseTrade_TradeCashSave(td, req, isFromRecheckOrKO: true, isLastAction: true, saveChanges: saveChanges);
|
|
|
|
if (tradeBarrier.RebateType == "AtEnd")
|
|
{
|
|
tc.ValueDate = td.ExerciseDate.Value;
|
|
tc.HappenedDate = settleDate;
|
|
}
|
|
|
|
if (td.IsGroup == 2 && td.ParentTradeId > 0)
|
|
{
|
|
var groupAction = DbContext.trade_cash_group_action.FirstOrDefault(x => x.TradeId == td.id && x.Status != "已完成");
|
|
if (groupAction != null)
|
|
{
|
|
groupAction.Status = "已完成";
|
|
tc.ParentTradeCashId = groupAction.ParentTradeCashId;
|
|
tc.ParentTradeId = groupAction.ParentTradeId;
|
|
}
|
|
else
|
|
{
|
|
tc.ParentTradeId = td.ParentTradeId;
|
|
tc.ParentTradeCashId = SaveGroupUnwindCash(td, tc.ValueDate, tc.Amount, closePrice ?? 0, out var continueTradeCashHandle).id;
|
|
}
|
|
}
|
|
|
|
tc.ValidState = "Valid";
|
|
|
|
//敲出价格为null,代表该交易为敲入类型的交易,但未敲入,到期后需要返还补偿金额的一条tradecash记录,归为到期行为
|
|
if (knockOutBarrierPrice == null)
|
|
{
|
|
tc.ExerciseWay = TradeCashExerciseWayEnum.到期行权;
|
|
}
|
|
else
|
|
{
|
|
tc.ExerciseWay = TradeCashExerciseWayEnum.提前终止行权;
|
|
}
|
|
|
|
return tc;
|
|
}
|
|
}
|
|
}
|