从山证v2.3.0拷贝
This commit is contained in:
@@ -0,0 +1,411 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
using Qdp.Foundation.Implementations;
|
||||
using Qdp.Pricing.Base.Implementations;
|
||||
using Qdp.Pricing.Base.Utilities;
|
||||
using YLErp.Abstract.DataProviders;
|
||||
using YLErp.BLL;
|
||||
using YLErp.BLL.Eod;
|
||||
using YLErp.DBModels.Consts;
|
||||
using YLErp.DBModels.Enums;
|
||||
using YLErp.Modules.DataProviderModule;
|
||||
using YLErp.Modules.TradeModule.ExoticOptionModule;
|
||||
using YLErp.QdpModule;
|
||||
|
||||
namespace YLErp.Modules.TradeModule.DealModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 二元期权操作
|
||||
/// 迁移自:trade_binary_optionBLL
|
||||
/// </summary>
|
||||
public class BinaryOptionDealService : TradeCashServiceEx
|
||||
{
|
||||
public BinaryOptionDealService(YLBaseService baseService) : base(baseService)
|
||||
{
|
||||
}
|
||||
|
||||
public BinaryOptionDealService(OptUserInfo userInfo) : base(userInfo)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public void CheckTouchStatus(DateTime valueDate, IEodPriceProviderV2 priceProvider, DateTime? startDate = null,
|
||||
Action<OtcTrade, trade_binary_option> afterKnowInOut = null, System.Collections.Generic.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_binary_option on td.id equals tb.TradeId
|
||||
where td.TradeDate > startDate.Value && td.TradeDate <= valueDate
|
||||
&& td.ExerciseDate >= valueDate
|
||||
&& ConsTrade.确认成交 == td.TradeStatus
|
||||
&& td.ValidState != ConsGlobal.InValid
|
||||
&& td.TradeType == "二元期权" && td.ExerciseMode == "American"
|
||||
&& td.DividendDate < valueDate
|
||||
select new
|
||||
{
|
||||
trade = td,
|
||||
tradeBinary = 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;
|
||||
}
|
||||
|
||||
// 美式二元检查
|
||||
|
||||
foreach (var tr in trades)
|
||||
{
|
||||
if (tr.trade.ExerciseDate < valueDate)
|
||||
{
|
||||
continue;//已到期交易不再观察;
|
||||
}
|
||||
var tradeStatus = tr.trade.TradeStatus;
|
||||
|
||||
if (!priceProvider.TryGetEodPrice(tr.trade.UnderlyingCode, out var eodprice))
|
||||
{
|
||||
throw new Exception($"[{tr.trade.TradeType}:{tr.trade.TradeNumber}]标的:{tr.trade.UnderlyingCode} 未找到结算价");
|
||||
}
|
||||
|
||||
double? upPrice = 0, lowPrice = 0;
|
||||
var isObservationDate = false;
|
||||
|
||||
//根据是否为离散观察来确定用来比较的价格
|
||||
//如果是离散观察,只用收盘价比较
|
||||
//如果是连续观察,使用最高价和最低价
|
||||
if (tr.tradeBinary.IsDiscreteMonitored)
|
||||
{
|
||||
var observationDates = QdpHelper.GetObservationDatesFromString(tr.tradeBinary.ObservationDates);
|
||||
if (observationDates == null || observationDates.Contains(valueDate))
|
||||
{
|
||||
isObservationDate = true;
|
||||
upPrice = lowPrice = eodprice.ClosePrice;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
isObservationDate = true;
|
||||
upPrice = eodprice.HighPrice;
|
||||
lowPrice = eodprice.LowPrice;
|
||||
}
|
||||
|
||||
var strike = tr.trade.IsMoneynessOptionData ? tr.trade.Strike * tr.trade.SpotPrice : tr.trade.Strike;
|
||||
|
||||
var UpperBarrier = new Lazy<double?>(() =>
|
||||
tr.trade.IsMoneynessOptionData ? tr.tradeBinary.UpperBarrier * tr.trade.SpotPrice : tr.tradeBinary.UpperBarrier);
|
||||
if (isObservationDate)
|
||||
{
|
||||
switch (tr.tradeBinary.PayoffType)
|
||||
{
|
||||
case "UpOneTouch":
|
||||
if (upPrice > strike)
|
||||
{
|
||||
//触碰,买方获得盈利,交易结束
|
||||
var cash = GetCashOrNothingAmount(tr.trade, tr.tradeBinary, valueDate, useHighAmount: false);
|
||||
SaveBinarySettleCash(tr.trade, tr.tradeBinary, valueDate, cash, true, upPrice);
|
||||
tr.trade.TradeStatus = ConsTrade.已执行;
|
||||
tr.trade.UnWindDate = valueDate;
|
||||
}
|
||||
else if (tr.trade.ExerciseDate <= valueDate)
|
||||
{
|
||||
SaveBinarySettleCash(tr.trade, tr.tradeBinary, valueDate, 0, false, upPrice);
|
||||
}
|
||||
break;
|
||||
case "DownOneTouch":
|
||||
if (lowPrice < strike)
|
||||
{
|
||||
//触碰,买方获得盈利,交易结束
|
||||
var cash = GetCashOrNothingAmount(tr.trade, tr.tradeBinary, valueDate, useHighAmount: false);
|
||||
SaveBinarySettleCash(tr.trade, tr.tradeBinary, valueDate, cash, true, lowPrice);
|
||||
tr.trade.TradeStatus = ConsTrade.已执行;
|
||||
tr.trade.UnWindDate = valueDate;
|
||||
}
|
||||
else if (tr.trade.ExerciseDate <= valueDate)
|
||||
{
|
||||
SaveBinarySettleCash(tr.trade, tr.tradeBinary, valueDate, 0, false, lowPrice);
|
||||
}
|
||||
break;
|
||||
case "UpNoTouch":
|
||||
if (upPrice > strike)
|
||||
{
|
||||
//触碰,买方无盈利,交易结束
|
||||
SaveBinarySettleCash(tr.trade, tr.tradeBinary, valueDate, 0, false, upPrice);
|
||||
tr.trade.TradeStatus = ConsTrade.已到期;
|
||||
tr.trade.UnWindDate = valueDate;
|
||||
}
|
||||
break;
|
||||
case "DownNoTouch":
|
||||
if (lowPrice < strike)
|
||||
{
|
||||
//触碰,买方无盈利,交易结束
|
||||
SaveBinarySettleCash(tr.trade, tr.tradeBinary, valueDate, 0, false, lowPrice);
|
||||
tr.trade.TradeStatus = ConsTrade.已到期;
|
||||
tr.trade.UnWindDate = valueDate;
|
||||
}
|
||||
break;
|
||||
case "DoubleOneTouch":
|
||||
if (upPrice >= UpperBarrier.Value || lowPrice <= strike)
|
||||
{
|
||||
var breachHighBarrier = upPrice >= UpperBarrier.Value;
|
||||
var price = breachHighBarrier ? upPrice : lowPrice;
|
||||
var cash = GetCashOrNothingAmount(tr.trade, tr.tradeBinary, valueDate, useHighAmount: breachHighBarrier);
|
||||
//触碰上限或下限,买方获得盈利,交易结束
|
||||
SaveBinarySettleCash(tr.trade, tr.tradeBinary, valueDate, cash, true, price);
|
||||
tr.trade.TradeStatus = ConsTrade.已执行;
|
||||
tr.trade.UnWindDate = valueDate;
|
||||
}
|
||||
else if (tr.trade.ExerciseDate <= valueDate)
|
||||
{
|
||||
SaveBinarySettleCash(tr.trade, tr.tradeBinary, valueDate, 0, false, eodprice.ClosePrice);
|
||||
}
|
||||
break;
|
||||
case "DoubleNoTouch":
|
||||
if (upPrice > UpperBarrier.Value || lowPrice < strike)
|
||||
{
|
||||
var price = upPrice >= tr.tradeBinary.UpperBarrier ? upPrice : lowPrice;
|
||||
//触碰上限或下限,买方无盈利,交易结束
|
||||
SaveBinarySettleCash(tr.trade, tr.tradeBinary, valueDate, 0, false, price);
|
||||
tr.trade.TradeStatus = ConsTrade.已到期;
|
||||
tr.trade.UnWindDate = valueDate;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//OneTouch类型,到期仍未触碰,则交易结束
|
||||
if (tr.tradeBinary.PayoffType.Contains("OneTouch") && tr.trade.ExerciseDate <= valueDate)
|
||||
{
|
||||
//SaveBinarySettleCash(tr.trade, tr.tradeBinary, valueDate, 0, false, eodprice.ClosePrice);
|
||||
tr.trade.TradeStatus = ConsTrade.已到期;
|
||||
tr.trade.UnWindDate = valueDate;
|
||||
}
|
||||
|
||||
//NoTouch类型,到期仍未触碰,买方获得盈利,交易结束
|
||||
if (tr.tradeBinary.PayoffType.Contains("NoTouch") && tr.trade.ExerciseDate <= valueDate && !ConsTrade.TradeCompleteStatus.Contains(tr.trade.TradeStatus))
|
||||
{
|
||||
var cash = GetCashOrNothingAmount(tr.trade, tr.tradeBinary, valueDate, useHighAmount: false);
|
||||
SaveBinarySettleCash(tr.trade, tr.tradeBinary, valueDate, cash, false, eodprice.ClosePrice);
|
||||
tr.trade.TradeStatus = ConsTrade.已执行;
|
||||
tr.trade.UnWindDate = valueDate;
|
||||
}
|
||||
|
||||
if (tradeStatus != tr.trade.TradeStatus)
|
||||
{
|
||||
//删除E/Bod_Trade记录
|
||||
RemoveEodTradeAndFutureInfo(false, tr.trade.id, valueDate);
|
||||
}
|
||||
|
||||
if (afterKnowInOut != null && DbContext.Entry(tr.tradeBinary).State == EntityState.Modified)
|
||||
{
|
||||
afterKnowInOut(tr.trade, tr.tradeBinary);
|
||||
}
|
||||
|
||||
//更新,不能放到循环外,黑箱交易的子交易相互有依赖关系
|
||||
DbContext.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static double GetCashOrNothingAmount(OtcTradeBase trade, trade_binary_option tradeBinary, DateTime settleDate, bool useHighAmount = false)
|
||||
{
|
||||
var result = (trade.Notional / trade.OriginalNotional * (trade.OriginalPrincipalSum ?? 0)) ?? 0;
|
||||
double refund;
|
||||
if (trade.IsUsePremiumRate == true)
|
||||
{
|
||||
var rate = useHighAmount ? tradeBinary.CashOrNothingAmountHighRate : tradeBinary.CashOrNothingAmountRate;
|
||||
refund = Math.Abs((rate ?? 0) * (trade.SpotPrice ?? 0) * trade.Notional);
|
||||
}
|
||||
else
|
||||
{
|
||||
var amount = useHighAmount ? tradeBinary.CashOrNothingAmountHigh : tradeBinary.CashOrNothingAmount;
|
||||
refund = Math.Abs((amount ?? 0) * trade.Notional);
|
||||
}
|
||||
if (tradeBinary.RebateAnnualizedAtKO)
|
||||
{
|
||||
var rebateDayCountImpl = string.IsNullOrWhiteSpace(tradeBinary.RebateDayCount) ? new Act365() : tradeBinary.RebateDayCount.ToDayCountImpl();
|
||||
var fraction = rebateDayCountImpl.CalcDayCountFraction(new Date(trade.StartDate.Value), new Date(settleDate));
|
||||
refund *= fraction;
|
||||
refund += Math.Abs((trade.TradePrice - trade.TradePrice * fraction) ?? 0);
|
||||
}
|
||||
return result + refund;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 美式二元期权触碰后,买方获得盈利,记录资金信息
|
||||
/// </summary>
|
||||
public trade_cash SaveBinarySettleCash(OtcTradeBase td, trade_binary_option tradeBinary, DateTime valuedate, double cash, bool earlyExecute, double? price, bool saveChanges = true)
|
||||
{
|
||||
var tradeCash = new trade_cash
|
||||
{
|
||||
ValidState = "Valid",
|
||||
OptDate = DateTime.Now,
|
||||
OptId=UserId,
|
||||
OptName=UserName,
|
||||
Action = ClientCashInCashOut.系统操作_行权费,
|
||||
IsLastAction = true,
|
||||
ValueDate = valuedate,
|
||||
Strike = td.Strike,
|
||||
CallPut = td.CallPut,
|
||||
Amount = cash * EodOperationBase.GetSign(td.BuySell),
|
||||
UnwindPrice = cash / td.Notional,
|
||||
UnwindPricePercentRate = cash / td.OriginalStockEqvNotional,
|
||||
Status = TradeCashStatusEnum.已执行,
|
||||
TradeType = td.BuySell,
|
||||
TradeId = td.id,
|
||||
Notional = td.Notional,
|
||||
TradeAmount = td.TradeAmount,
|
||||
UnwindPercentRate = td.OriginalNotional > 0 ? td.Notional / td.OriginalNotional : 0,
|
||||
FinalPrice = price,
|
||||
ExerciseWay = earlyExecute ? TradeCashExerciseWayEnum.提前终止行权 : TradeCashExerciseWayEnum.到期行权
|
||||
};
|
||||
|
||||
if (tradeBinary.RebateType == "AtEnd")
|
||||
{
|
||||
tradeCash.ValueDate = td.ExerciseDate.Value;
|
||||
tradeCash.HappenedDate = valuedate;
|
||||
}
|
||||
|
||||
if (saveChanges)
|
||||
{
|
||||
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 = "已完成";
|
||||
tradeCash.ParentTradeCashId = groupAction.ParentTradeCashId;
|
||||
tradeCash.ParentTradeId = groupAction.ParentTradeId;
|
||||
}
|
||||
else
|
||||
{
|
||||
tradeCash.ParentTradeId = td.ParentTradeId;
|
||||
tradeCash.ParentTradeCashId = SaveGroupUnwindCash(td, tradeCash.ValueDate, tradeCash.Amount, price ?? 0, out bool continueTradeCashHandle).id;
|
||||
}
|
||||
}
|
||||
DbContext.trade_cash.Add(tradeCash);
|
||||
DbContext.SaveChanges();
|
||||
|
||||
SaveTradeCashDetail(tradeCash);
|
||||
|
||||
new ClientCashinCashoutBLL(this).CloseTrade_ClientCashInCashOutSave(td, tradeCash, tradeCash.ValueDate);
|
||||
}
|
||||
|
||||
return tradeCash;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
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_double_sharkfin_optionBLL
|
||||
/// </summary>
|
||||
public class DoubleSharkOptionKnockoutService : TradeCashServiceEx
|
||||
{
|
||||
public DoubleSharkOptionKnockoutService(YLBaseService baseService) : base(baseService)
|
||||
{
|
||||
}
|
||||
|
||||
public DoubleSharkOptionKnockoutService(OptUserInfo userInfo) : base(userInfo)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检出敲入敲出
|
||||
/// </summary>
|
||||
public void CheckKnockoutStatus(DateTime valueDate, IEodPriceProviderV2 priceProvider, DateTime? startDate = null
|
||||
, Action<OtcTrade, trade_double_sharkfin_option> afterKnowInOut = null, System.Collections.Generic.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_double_sharkfin_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,
|
||||
tradeDbShark = 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;//已到期交易不再观察;
|
||||
}
|
||||
var tradeStatus = tr.trade.TradeStatus;
|
||||
var observationDates = QdpHelper.GetObservationDatesFromString(tr.tradeDbShark.ObservationDates);
|
||||
//每日观察或者当前结算日是观察日的时候,才检查是否会敲敲出
|
||||
if (observationDates == null || observationDates.Contains(valueDate) || !tr.tradeDbShark.IsDiscrete)
|
||||
{
|
||||
//根据是否为离散观察来确定用来比较的价格
|
||||
//如果是离散观察,只用收盘价比较
|
||||
//如果是连续观察,使用最高价和最低价
|
||||
double? upPrice, lowPrice;
|
||||
|
||||
if (manuallyTradeObservationPrices.TryGetValue(tr.trade.id, out var manuallyTradeObservationPrice))
|
||||
{
|
||||
upPrice = 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 if (tr.tradeDbShark.IsDiscrete)
|
||||
{
|
||||
upPrice = eodprice.ClosePrice;
|
||||
lowPrice = eodprice.ClosePrice;
|
||||
}
|
||||
else
|
||||
{
|
||||
upPrice = eodprice.HighPrice;
|
||||
lowPrice = eodprice.LowPrice;
|
||||
}
|
||||
|
||||
var oldKnockInOutStatus = tr.tradeDbShark.KnockInOutStatus;
|
||||
CheckDoubleSharkFinKnockOutStatus(tr.trade, tr.tradeDbShark, valueDate, upPrice, lowPrice);
|
||||
|
||||
if (oldKnockInOutStatus != tr.tradeDbShark.KnockInOutStatus)
|
||||
{
|
||||
var KnockInOutStatus = tr.tradeDbShark.KnockInOutStatus == ConsTrade.KnockState.KnockedIn ? "敲入" : "敲出";
|
||||
AddTradeOperationHistoryAndSetParentTradeInfo(false, tr.trade, KnockInOutStatus, KnockInOutStatus);
|
||||
}
|
||||
|
||||
if (tradeStatus != tr.trade.TradeStatus|| oldKnockInOutStatus != tr.tradeDbShark.KnockInOutStatus)
|
||||
{
|
||||
//删除E/Bod_Trade记录
|
||||
RemoveEodTradeAndFutureInfo(false, tr.trade.id, valueDate);
|
||||
}
|
||||
|
||||
if (afterKnowInOut != null && DbContext.Entry(tr.tradeDbShark).State == EntityState.Modified)
|
||||
{
|
||||
afterKnowInOut(tr.trade, tr.tradeDbShark);
|
||||
}
|
||||
}
|
||||
|
||||
//更新,不能放到循环外,黑箱交易的子交易相互有依赖关系
|
||||
DbContext.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckDoubleSharkFinKnockOutStatus(OtcTradeBase td, trade_double_sharkfin_option tradeDbShark, DateTime valuedate, double? upPrice, double? lowPrice)
|
||||
{
|
||||
var barrierHigh = td.IsMoneynessOptionData ? tradeDbShark.BarrierHigh * td.SpotPrice : tradeDbShark.BarrierHigh;
|
||||
var barrierLow = td.IsMoneynessOptionData ? tradeDbShark.BarrierLow * td.SpotPrice : tradeDbShark.BarrierLow;
|
||||
if (upPrice >= barrierHigh || lowPrice <= barrierLow)
|
||||
{
|
||||
var useRebate = lowPrice <= barrierLow;
|
||||
var price = upPrice >= barrierHigh ? upPrice : lowPrice;
|
||||
tradeDbShark.KnockInOutStatus = ConsTrade.KnockState.KnockedOut;
|
||||
tradeDbShark.KnockInOutDate = valuedate;
|
||||
td.TradeStatus = ConsTrade.已平仓;
|
||||
td.UnWindDate = valuedate;
|
||||
SaveDoubleSharkFinRebateCash(td, tradeDbShark, price, valuedate, useRebate);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 双鲨期权敲出,应获得rebate,保存相应的资金信息
|
||||
/// </summary>
|
||||
public trade_cash SaveDoubleSharkFinRebateCash(OtcTradeBase td, trade_double_sharkfin_option tradeDbShark, double? closePrice, DateTime valueDate, bool useRebate, bool saveChanges = true)
|
||||
{
|
||||
double rebate, rebateRate;
|
||||
|
||||
var spotPrice = td.SpotPrice ?? 0;
|
||||
|
||||
if (td.IsUsePremiumRate == true)
|
||||
{
|
||||
rebateRate = (useRebate ? tradeDbShark.RebateRate : tradeDbShark.RebateHighRate) ?? 0;
|
||||
rebate = rebateRate * spotPrice;
|
||||
}
|
||||
else
|
||||
{
|
||||
rebate = (useRebate ? tradeDbShark.Rebate : tradeDbShark.RebateHigh) ?? 0;
|
||||
rebateRate = spotPrice > 0 ? rebate / spotPrice : 0;
|
||||
}
|
||||
|
||||
var req = new TradeCashReq
|
||||
{
|
||||
UnwindNotional = td.Notional,
|
||||
UnwindPercentRate = td.OriginalNotional > 0 ? td.Notional / td.OriginalNotional.Value : 0,
|
||||
FinalPrice = closePrice,
|
||||
UnwindPrice = rebate,
|
||||
UnwindPricePercentRate = rebateRate,
|
||||
Notional = td.Notional,
|
||||
TradeAmount = td.TradeAmount,
|
||||
ValueDate = valueDate,
|
||||
HappenedDate = valueDate,
|
||||
BarrierPrice = useRebate ? tradeDbShark.BarrierLow : tradeDbShark.BarrierHigh
|
||||
};
|
||||
|
||||
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: false);
|
||||
tc.ValidState = ConsGlobal.Valid;
|
||||
tc.ExerciseWay = TradeCashExerciseWayEnum.提前终止行权;
|
||||
if (tradeDbShark.RebateType == "AtEnd")
|
||||
{
|
||||
tc.ValueDate = td.ExerciseDate.Value;
|
||||
tc.HappenedDate = valueDate;
|
||||
}
|
||||
//加入平仓份额和平仓日期
|
||||
td.UnWindDate = tc.ValueDate;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (saveChanges)
|
||||
{
|
||||
SaveTradeCashDetail(tc);
|
||||
|
||||
//增加出入金记录
|
||||
new ClientCashinCashoutBLL(this).CloseTrade_ClientCashInCashOutSave(td, tc, tc.ValueDate);
|
||||
}
|
||||
return tc;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
namespace YLErp.Modules.TradeModule.ExoticOptionModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 预付金雪球赔付
|
||||
/// </summary>
|
||||
public abstract class PrepaymentSnowballPayoff
|
||||
{
|
||||
/// <summary>
|
||||
/// 利息金额(买方角度)
|
||||
/// </summary>
|
||||
public double InterestAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 预付金金额(买方角度)
|
||||
/// </summary>
|
||||
public double PrepaymentAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 倒置符号(将正值倒置为负值,负值倒置为正值)
|
||||
/// </summary>
|
||||
public virtual void InvertSign()
|
||||
{
|
||||
InterestAmount = -InterestAmount;
|
||||
PrepaymentAmount = -PrepaymentAmount;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 雪球敲出赔付
|
||||
/// </summary>
|
||||
public class PrepaymentSnowballKoPayoff : PrepaymentSnowballPayoff
|
||||
{
|
||||
/// <summary>
|
||||
/// 票息金额(买方金额)
|
||||
/// </summary>
|
||||
public double CouponPaymentAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 增强收益金额(买方金额)
|
||||
/// </summary>
|
||||
public double EnhancedPaymentAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 敲出票息开始日
|
||||
/// </summary>
|
||||
public DateTime CouponStartDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 敲出票息结束日
|
||||
/// </summary>
|
||||
public DateTime CouponEndDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 票息率
|
||||
/// </summary>
|
||||
public double CouponRate { get; set; }
|
||||
|
||||
public double TotalAmount()
|
||||
{
|
||||
return CouponPaymentAmount + EnhancedPaymentAmount + InterestAmount;
|
||||
}
|
||||
|
||||
public override void InvertSign()
|
||||
{
|
||||
base.InvertSign();
|
||||
|
||||
CouponPaymentAmount = -CouponPaymentAmount;
|
||||
EnhancedPaymentAmount = -EnhancedPaymentAmount;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 雪球到期日敲入赔付
|
||||
/// </summary>
|
||||
public class PrepaymentSnowballKiPayoff : PrepaymentSnowballPayoff
|
||||
{
|
||||
/// <summary>
|
||||
/// 期权赔付金额(买方角度)
|
||||
/// </summary>
|
||||
public double OptionPaymentAmount { get; set; }
|
||||
|
||||
public double TotalAmount()
|
||||
{
|
||||
return OptionPaymentAmount + InterestAmount;
|
||||
}
|
||||
|
||||
public override void InvertSign()
|
||||
{
|
||||
base.InvertSign();
|
||||
|
||||
OptionPaymentAmount = -OptionPaymentAmount;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 雪球到期日未敲入赔付
|
||||
/// </summary>
|
||||
public class PrepaymentSnowballNkiPayoff : PrepaymentSnowballPayoff
|
||||
{
|
||||
/// <summary>
|
||||
/// 红利票息赔付金额(买方角度)
|
||||
/// </summary>
|
||||
public double CouponPaymentAmount { get; set; }
|
||||
|
||||
public double TotalAmount()
|
||||
{
|
||||
return CouponPaymentAmount + InterestAmount;
|
||||
}
|
||||
|
||||
public override void InvertSign()
|
||||
{
|
||||
base.InvertSign();
|
||||
|
||||
CouponPaymentAmount = -CouponPaymentAmount;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 雪球观察结果类型
|
||||
/// </summary>
|
||||
public enum SnowballObservationResultType
|
||||
{
|
||||
/// <summary>
|
||||
/// 非观察日
|
||||
/// </summary>
|
||||
NonObservationDay,
|
||||
|
||||
/// <summary>
|
||||
/// 观察中
|
||||
/// </summary>
|
||||
Monitoring,
|
||||
|
||||
/// <summary>
|
||||
/// 敲入状态
|
||||
/// </summary>
|
||||
KnockedIn,
|
||||
|
||||
/// <summary>
|
||||
/// 敲出赔付
|
||||
/// </summary>
|
||||
KoPayoff,
|
||||
|
||||
/// <summary>
|
||||
/// 到期日敲入赔付
|
||||
/// </summary>
|
||||
KiPayoffAtEndDate,
|
||||
|
||||
/// <summary>
|
||||
/// 到期日非敲入赔付
|
||||
/// </summary>
|
||||
NkiPayoffAtEndDate
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 雪球观察结果
|
||||
/// </summary>
|
||||
public class SnowballObservationResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 观察结果类型
|
||||
/// </summary>
|
||||
public SnowballObservationResultType ResultType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付日期
|
||||
/// </summary>
|
||||
public DateTime PaymentDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付总金额(买方角度)
|
||||
/// </summary>
|
||||
public double PaymentAmount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 敲出障碍价格
|
||||
/// </summary>
|
||||
public double KoBarrier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 赔付明细
|
||||
/// </summary>
|
||||
public PrepaymentSnowballPayoff Payoff { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,495 @@
|
||||
using Qdp.Pricing.Base.Utilities;
|
||||
using YLErp.DBModels;
|
||||
using YLErp.QdpModule;
|
||||
|
||||
namespace YLErp.Modules.TradeModule.ExoticOptionModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 专业版雪球观察帮助类
|
||||
/// </summary>
|
||||
public class SpecialSnowballObservationHelper
|
||||
{
|
||||
private readonly OtcTradeBase _otcTrade;
|
||||
private readonly trade_snowball _snowball;
|
||||
|
||||
public SpecialSnowballObservationHelper(OtcTradeBase otcTrade, trade_snowball snowball)
|
||||
{
|
||||
_otcTrade = otcTrade ?? throw new ArgumentNullException(nameof(otcTrade));
|
||||
_snowball = snowball ?? throw new ArgumentNullException(nameof(snowball));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取雪球观察结果(买方)
|
||||
/// </summary>
|
||||
public SnowballObservationResult GetObservationResultForBuySide(DateTime valueDate, double closePrice, double tradeNotional)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(_snowball.KnockInOutStatus != ConsTrade.KnockState.KnockedOut);
|
||||
|
||||
//敲出检查
|
||||
|
||||
var koResult = GetKoPayoff(valueDate, closePrice, tradeNotional, out var isKoObservationDay, out var koBarrier);
|
||||
|
||||
if (koResult != null)
|
||||
{
|
||||
return koResult;
|
||||
}
|
||||
|
||||
var resultType = _snowball.IsInitialKnockedIn || _snowball.KnockInOutStatus == ConsTrade.KnockState.KnockedIn
|
||||
? SnowballObservationResultType.KnockedIn
|
||||
: (isKoObservationDay ? SnowballObservationResultType.Monitoring : SnowballObservationResultType.NonObservationDay);
|
||||
|
||||
//敲入检查
|
||||
if (resultType != SnowballObservationResultType.KnockedIn && IsNeedCheckKnockIn(valueDate))
|
||||
{
|
||||
var kiBarrier = _otcTrade.IsMoneynessOptionData ? _snowball.KIBarrier * (_otcTrade.SpotPrice ?? 0) : _snowball.KIBarrier;
|
||||
|
||||
// 发生敲入事件(看涨 - 向下敲入,看跌 - 向上敲入)
|
||||
|
||||
resultType = (ConsGlobal.CallPut.IsCall(_otcTrade.CallPut) ? closePrice <= kiBarrier : closePrice >= kiBarrier)
|
||||
? SnowballObservationResultType.KnockedIn
|
||||
: SnowballObservationResultType.Monitoring;
|
||||
}
|
||||
|
||||
SnowballObservationResult result;
|
||||
|
||||
//到期检查
|
||||
if (valueDate == _otcTrade.ExerciseDate.Value)
|
||||
{
|
||||
result = resultType == SnowballObservationResultType.KnockedIn
|
||||
? GetKiPayoffAtEndDate(closePrice, tradeNotional) : GetNKiPayoffAtEndDate(tradeNotional);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = new SnowballObservationResult { ResultType = resultType };
|
||||
}
|
||||
|
||||
result.KoBarrier = koBarrier ??
|
||||
(_otcTrade.IsMoneynessOptionData ? _snowball.KOBarrier * (_otcTrade.SpotPrice ?? 0) : _snowball.KOBarrier);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取雪球观察结果(交易员角度)
|
||||
/// </summary>
|
||||
public SnowballObservationResult GetObservationResultForTraderSide(DateTime valueDate, double closePrice, double tradeNotional)
|
||||
{
|
||||
var result = GetObservationResultForBuySide(valueDate, closePrice, tradeNotional);
|
||||
|
||||
if (_otcTrade.BuySell == "卖出")
|
||||
{
|
||||
result.PaymentAmount = -result.PaymentAmount;
|
||||
|
||||
result.Payoff?.InvertSign();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取累积票息(tradeNotional为负值时是卖方角度)
|
||||
/// </summary>
|
||||
public PrepaymentSnowballKoPayoff GetEffectiveObservation(DateTime valueDate, double tradeNotional)
|
||||
{
|
||||
var koObservationParseResult = ParseKoObservation(valueDate);
|
||||
|
||||
//非敲出观察日
|
||||
if (koObservationParseResult == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var payoff = new PrepaymentSnowballKoPayoff
|
||||
{
|
||||
CouponStartDate = _otcTrade.StartDate.Value,
|
||||
CouponEndDate = valueDate,
|
||||
CouponRate = koObservationParseResult.KoRebate,
|
||||
//票息支付金额
|
||||
CouponPaymentAmount = koObservationParseResult.KoRebate * tradeNotional * (_otcTrade.SpotPrice ?? 0)
|
||||
};
|
||||
|
||||
//票息计息年化方式处理
|
||||
if (!string.IsNullOrWhiteSpace(_snowball.CouponDayCount))
|
||||
{
|
||||
//首日是否计息
|
||||
var startDate = _snowball.CouponIncludeStartDate == true
|
||||
? _otcTrade.StartDate.Value.AddDays(-1) : _otcTrade.StartDate.Value;
|
||||
|
||||
//是否支付日计息
|
||||
var endDate = valueDate;
|
||||
if (_snowball.CouponUsePaymentDate == true)
|
||||
{
|
||||
endDate = GetKoPaymentDate(_snowball.KOObservationSettleDates, koObservationParseResult.KoDateIndex, valueDate);
|
||||
}
|
||||
|
||||
//终日是否计息
|
||||
if (_snowball.CouponIncludeEndDate == false)
|
||||
{
|
||||
endDate = endDate.AddDays(-1);
|
||||
}
|
||||
|
||||
//票息年化计息
|
||||
var couponDayCount = _snowball.CouponDayCount.ToDayCountImpl();
|
||||
var dayFraction = couponDayCount.CalcDayCountFraction(startDate, endDate);
|
||||
payoff.CouponPaymentAmount *= dayFraction;
|
||||
}
|
||||
|
||||
return payoff;
|
||||
}
|
||||
|
||||
#region----到期日敲入收益----
|
||||
|
||||
/// <summary>
|
||||
/// 获取到期日敲入收益
|
||||
/// </summary>
|
||||
private SnowballObservationResult GetKiPayoffAtEndDate(double closePrice, double tradeNotional)
|
||||
{
|
||||
var strike = _otcTrade.IsMoneynessOption == "是"
|
||||
? (_snowball.SpreadStrikeAtMaturity1 ?? 0) * (_otcTrade.SpotPrice ?? 0)
|
||||
: _snowball.SpreadStrikeAtMaturity1 ?? 0;
|
||||
|
||||
//敲入转香草期权,买卖方向需要反一下,通过notional体现
|
||||
var notional = -tradeNotional * (_snowball.KIParticipationRate ?? 1);
|
||||
|
||||
var isCall = ConsGlobal.CallPut.IsCall(_otcTrade.OptionType);
|
||||
|
||||
var payoff = new PrepaymentSnowballKiPayoff();
|
||||
|
||||
//期权支付
|
||||
|
||||
if (_snowball.PrincipalProtectionRate.HasValue && _snowball.PrincipalProtectionRate.Value != 0)
|
||||
{
|
||||
var spread = (1 - _snowball.PrincipalProtectionRate.Value) * (_otcTrade.SpotPrice ?? 0);
|
||||
|
||||
if (isCall)
|
||||
{
|
||||
//熊市价差
|
||||
payoff.OptionPaymentAmount = Math.Min(Math.Max(strike - closePrice, 0), spread) * notional;
|
||||
}
|
||||
else
|
||||
{
|
||||
//牛市价差
|
||||
payoff.OptionPaymentAmount = Math.Min(Math.Max(closePrice - strike, 0), spread) * notional;
|
||||
}
|
||||
}
|
||||
else if (isCall)
|
||||
{
|
||||
//看跌期权
|
||||
payoff.OptionPaymentAmount = Math.Max(strike - closePrice, 0) * notional;
|
||||
}
|
||||
else
|
||||
{
|
||||
//看涨期权
|
||||
payoff.OptionPaymentAmount = Math.Max(closePrice - strike, 0) * notional;
|
||||
}
|
||||
|
||||
//预付金返还
|
||||
if (_snowball.PrepaymentRatio.HasValue)
|
||||
{
|
||||
payoff.PrepaymentAmount = tradeNotional * (_otcTrade.SpotPrice ?? 0) * _snowball.PrepaymentRatio.Value;
|
||||
|
||||
//利息金额
|
||||
if (_snowball.PrepaymentInterestRate.HasValue)
|
||||
{
|
||||
payoff.InterestAmount = payoff.PrepaymentAmount * _snowball.PrepaymentInterestRate.Value;
|
||||
|
||||
//年化计息
|
||||
if (!string.IsNullOrWhiteSpace(_snowball.CouponDayCount))
|
||||
{
|
||||
//首日是否计息
|
||||
var startDate = _snowball.CouponIncludeStartDate == true
|
||||
? _otcTrade.StartDate.Value.AddDays(-1) : _otcTrade.StartDate.Value;
|
||||
|
||||
//终日是否计息
|
||||
var endDate = _snowball.CouponIncludeEndDate == true
|
||||
? _otcTrade.ExerciseDate.Value : _otcTrade.ExerciseDate.Value.AddDays(-1);
|
||||
|
||||
var couponDayCount = _snowball.CouponDayCount.ToDayCountImpl();
|
||||
|
||||
var dayFraction = couponDayCount.CalcDayCountFraction(startDate, endDate);
|
||||
|
||||
payoff.InterestAmount *= dayFraction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new SnowballObservationResult
|
||||
{
|
||||
Payoff = payoff,
|
||||
PaymentAmount = payoff.TotalAmount(),
|
||||
PaymentDate = _otcTrade.ExerciseDate.Value,
|
||||
ResultType = SnowballObservationResultType.KiPayoffAtEndDate
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region----获取到期日未敲入收益----
|
||||
|
||||
/// <summary>
|
||||
/// 获取到期日未敲入收益
|
||||
/// </summary>
|
||||
private SnowballObservationResult GetNKiPayoffAtEndDate(double tradeNotional)
|
||||
{
|
||||
var notionalValue = tradeNotional * (_otcTrade.SpotPrice ?? 0);
|
||||
|
||||
var payoff = new PrepaymentSnowballNkiPayoff();
|
||||
|
||||
//红利票息支付金额
|
||||
payoff.CouponPaymentAmount = _snowball.Coupon * notionalValue;
|
||||
|
||||
//预付金返还
|
||||
if (_snowball.PrepaymentRatio.HasValue)
|
||||
{
|
||||
payoff.PrepaymentAmount = notionalValue * _snowball.PrepaymentRatio.Value;
|
||||
|
||||
//利息金额
|
||||
if (_snowball.PrepaymentInterestRate.HasValue)
|
||||
{
|
||||
payoff.InterestAmount = payoff.PrepaymentAmount * _snowball.PrepaymentInterestRate.Value;
|
||||
}
|
||||
}
|
||||
|
||||
//年化计息
|
||||
if (!string.IsNullOrWhiteSpace(_snowball.CouponDayCount))
|
||||
{
|
||||
//首日是否计息
|
||||
var startDate = _snowball.CouponIncludeStartDate == true
|
||||
? _otcTrade.StartDate.Value.AddDays(-1) : _otcTrade.StartDate.Value;
|
||||
|
||||
//终日是否计息
|
||||
var endDate = _snowball.CouponIncludeEndDate == true
|
||||
? _otcTrade.ExerciseDate.Value : _otcTrade.ExerciseDate.Value.AddDays(-1);
|
||||
|
||||
var couponDayCount = _snowball.CouponDayCount.ToDayCountImpl();
|
||||
|
||||
var dayFraction = couponDayCount.CalcDayCountFraction(startDate, endDate);
|
||||
|
||||
payoff.InterestAmount *= dayFraction;
|
||||
|
||||
payoff.CouponPaymentAmount *= dayFraction;
|
||||
}
|
||||
|
||||
return new SnowballObservationResult
|
||||
{
|
||||
Payoff = payoff,
|
||||
PaymentAmount = payoff.TotalAmount(),
|
||||
PaymentDate = _otcTrade.ExerciseDate.Value,
|
||||
ResultType = SnowballObservationResultType.NkiPayoffAtEndDate
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region----获取敲出收益----
|
||||
|
||||
/// <summary>
|
||||
/// 获取敲出收益
|
||||
/// </summary>
|
||||
private SnowballObservationResult GetKoPayoff(DateTime valueDate, double closePrice, double tradeNotional
|
||||
, out bool isObservationDay, out double? koBarrier)
|
||||
{
|
||||
var koObservationParseResult = ParseKoObservation(valueDate);
|
||||
|
||||
koBarrier = koObservationParseResult?.KoBarrier;
|
||||
|
||||
isObservationDay = koObservationParseResult != null;
|
||||
|
||||
//非敲出观察日
|
||||
if (!isObservationDay)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var isCall = ConsGlobal.CallPut.IsCall(_otcTrade.CallPut);
|
||||
|
||||
// 发生敲出事件(看涨 - 向上敲出支付票息,看跌 - 向下敲出支付票息)
|
||||
|
||||
if (isCall ? closePrice < koObservationParseResult.KoBarrier : closePrice > koObservationParseResult.KoBarrier)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var payoff = new PrepaymentSnowballKoPayoff
|
||||
{
|
||||
CouponStartDate = _otcTrade.StartDate.Value,
|
||||
CouponEndDate = valueDate,
|
||||
CouponRate = koObservationParseResult.KoRebate
|
||||
};
|
||||
|
||||
var notionalValue = tradeNotional * (_otcTrade.SpotPrice ?? 0);
|
||||
|
||||
//增强收益支付金额
|
||||
if (_snowball.EnhancedParticipationRate.HasValue)
|
||||
{
|
||||
payoff.EnhancedPaymentAmount = (closePrice - koObservationParseResult.KoBarrier) * tradeNotional * _snowball.EnhancedParticipationRate.Value;
|
||||
if (!isCall)
|
||||
{
|
||||
payoff.EnhancedPaymentAmount = -payoff.EnhancedPaymentAmount;
|
||||
}
|
||||
}
|
||||
|
||||
//票息支付金额
|
||||
payoff.CouponPaymentAmount = koObservationParseResult.KoRebate * notionalValue;
|
||||
|
||||
//预付金返还
|
||||
if (_snowball.PrepaymentRatio.HasValue)
|
||||
{
|
||||
payoff.PrepaymentAmount = notionalValue * _snowball.PrepaymentRatio.Value;
|
||||
|
||||
//利息金额
|
||||
if (_snowball.PrepaymentInterestRate.HasValue)
|
||||
{
|
||||
payoff.InterestAmount = payoff.PrepaymentAmount * _snowball.PrepaymentInterestRate.Value;
|
||||
}
|
||||
}
|
||||
|
||||
//票息支付日
|
||||
var paymentDate = GetKoPaymentDate(_snowball.KOObservationSettleDates, koObservationParseResult.KoDateIndex, valueDate);
|
||||
|
||||
//票息计息年化方式处理
|
||||
if (!string.IsNullOrWhiteSpace(_snowball.CouponDayCount))
|
||||
{
|
||||
//首日是否计息
|
||||
var startDate = _snowball.CouponIncludeStartDate == true
|
||||
? _otcTrade.StartDate.Value.AddDays(-1) : _otcTrade.StartDate.Value;
|
||||
|
||||
//是否支付日计息
|
||||
var endDate = valueDate;
|
||||
if (_snowball.CouponUsePaymentDate == true)
|
||||
{
|
||||
endDate = paymentDate;
|
||||
}
|
||||
|
||||
//终日是否计息
|
||||
if (_snowball.CouponIncludeEndDate == false)
|
||||
{
|
||||
endDate = endDate.AddDays(-1);
|
||||
}
|
||||
|
||||
payoff.CouponEndDate = endDate;
|
||||
|
||||
//票息年化计息
|
||||
var couponDayCount = _snowball.CouponDayCount.ToDayCountImpl();
|
||||
var dayFraction = couponDayCount.CalcDayCountFraction(startDate, endDate);
|
||||
payoff.CouponPaymentAmount *= dayFraction;
|
||||
|
||||
//年化利息金额
|
||||
payoff.InterestAmount *= dayFraction;
|
||||
}
|
||||
|
||||
//返回敲出观察结果
|
||||
var result = new SnowballObservationResult
|
||||
{
|
||||
Payoff = payoff,
|
||||
PaymentDate = paymentDate,
|
||||
ResultType = SnowballObservationResultType.KoPayoff,
|
||||
PaymentAmount = payoff.TotalAmount(),
|
||||
KoBarrier = koObservationParseResult.KoBarrier
|
||||
};
|
||||
|
||||
if (_snowball.KORebateType == RebateTypeEnum.AtEnd)
|
||||
{
|
||||
result.PaymentDate = _otcTrade.ExerciseDate.Value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析敲出观察频率
|
||||
/// </summary>
|
||||
private KoObservationParseResult ParseKoObservation(DateTime valueDate)
|
||||
{
|
||||
(var koDates, var koBarries, var koCoupons)
|
||||
= QdpHelper.ParseAutocallCustomizedInfo(_snowball.KOObservationDates);
|
||||
|
||||
koDates ??= QdpObservationHelper.GetDefaultKoObservationDatesForSnowbalV2(_otcTrade.StartDate.Value, _otcTrade.ExerciseDate.Value);
|
||||
|
||||
var koDateIndex = koDates.Select(x => x.DateTime).ToList().IndexOf(valueDate);
|
||||
|
||||
if (koDateIndex < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = new KoObservationParseResult
|
||||
{
|
||||
KoDateIndex = koDateIndex,
|
||||
KoRebate = koCoupons != null && koCoupons.Length > koDateIndex ? koCoupons[koDateIndex] : _snowball.KORebate,
|
||||
KoBarrier = koBarries != null && koBarries.Length > koDateIndex ? koBarries[koDateIndex] : _snowball.KOBarrier
|
||||
};
|
||||
|
||||
if (_otcTrade.IsMoneynessOptionData)
|
||||
{
|
||||
result.KoBarrier *= _otcTrade.SpotPrice ?? 1.0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取票息支付日
|
||||
/// </summary>
|
||||
private static DateTime GetKoPaymentDate(string paymentDatesStr, int koDateIndex, DateTime koObservationDate)
|
||||
{
|
||||
var paymentDates = string.IsNullOrWhiteSpace(paymentDatesStr) ? null
|
||||
: paymentDatesStr.Split(new char[] { ',', ';', ',', ';' }).Select(x => DateTime.Parse(x)).ToArray();
|
||||
|
||||
if (paymentDates != null && paymentDates.Length > koDateIndex)
|
||||
{
|
||||
var paymentDate = paymentDates[koDateIndex];
|
||||
|
||||
//预防错误的支付日数据
|
||||
if (paymentDate < koObservationDate)
|
||||
{
|
||||
paymentDate = koObservationDate;
|
||||
}
|
||||
|
||||
return paymentDate;
|
||||
}
|
||||
|
||||
return koObservationDate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否需要观察敲入
|
||||
/// </summary>
|
||||
private bool IsNeedCheckKnockIn(DateTime observationDate)
|
||||
{
|
||||
//已在观察日之前敲入则不需要观察是否敲入
|
||||
if (_snowball.KnockInOutStatus == ConsTrade.KnockState.KnockedIn && _snowball.KnockInOutDate < observationDate)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//仅在到期日观察
|
||||
if (_snowball.KIObservationType == KIObservationType.OnlyEndDate)
|
||||
{
|
||||
return observationDate == _otcTrade.ExerciseDate;
|
||||
}
|
||||
|
||||
//每日观察
|
||||
return true;
|
||||
}
|
||||
|
||||
class KoObservationParseResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 观察日在敲出观察频率中的索引
|
||||
/// </summary>
|
||||
public int KoDateIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 敲出障碍价格
|
||||
/// </summary>
|
||||
public double KoBarrier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 敲出支付票息率
|
||||
/// </summary>
|
||||
public double KoRebate { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
using YLErp.Abstract.DataProviders;
|
||||
using YLErp.Modules.DataProviderModule;
|
||||
|
||||
namespace YLErp.Modules.TradeModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 气囊结构服务
|
||||
/// </summary>
|
||||
public class TradeAirbagService : TradeServiceBase
|
||||
{
|
||||
public TradeAirbagService(OptUserInfo userInfo) : base(userInfo)
|
||||
{
|
||||
}
|
||||
|
||||
public TradeAirbagService(YLBaseService baseService) : base(baseService)
|
||||
{
|
||||
}
|
||||
|
||||
public void SetKnockIn(DateTime valueDate, IEodPriceProviderV2 priceProvider, DateTime? startDate = null
|
||||
, Action<OtcTrade, trade_airbag> afterKnowInOut = null, System.Collections.Generic.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 airbag in DbContext.trade_airbag on trade.id equals airbag.TradeId
|
||||
where trade.TradeDate > startDate.Value && trade.TradeDate <= valueDate
|
||||
&& string.IsNullOrEmpty(airbag.KnockInOutStatus)
|
||||
&& trade.TradeType == "气囊结构"
|
||||
&& ConsTrade.确认成交 == trade.TradeStatus
|
||||
&& trade.ValidState != ConsGlobal.InValid
|
||||
&& trade.DividendDate < valueDate
|
||||
select new
|
||||
{
|
||||
trade,
|
||||
tradeAirbag = airbag
|
||||
};
|
||||
#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).ToArray();
|
||||
|
||||
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;
|
||||
|
||||
double? closePrice;
|
||||
|
||||
if (manuallyTradeObservationPrices.TryGetValue(tr.trade.id, out var manuallyTradeObservationPrice))
|
||||
{
|
||||
closePrice = 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;
|
||||
}
|
||||
|
||||
var oldKnockInOutStatus = tr.tradeAirbag.KnockInOutStatus;
|
||||
CheckAirbagKnockInStatus(tr.trade, tr.tradeAirbag, valueDate, closePrice);
|
||||
|
||||
if (oldKnockInOutStatus != tr.tradeAirbag.KnockInOutStatus)
|
||||
{
|
||||
var KnockInOutStatus = tr.tradeAirbag.KnockInOutStatus == ConsTrade.KnockState.KnockedIn ? "敲入" : "敲出";
|
||||
AddTradeOperationHistoryAndSetParentTradeInfo(false, tr.trade, KnockInOutStatus, KnockInOutStatus);
|
||||
}
|
||||
|
||||
if (tradeStatus != tr.trade.TradeStatus|| oldKnockInOutStatus != tr.tradeAirbag.KnockInOutStatus)
|
||||
{
|
||||
//删除E/Bod_Trade记录
|
||||
RemoveEodTradeAndFutureInfo(false, tr.trade.id, valueDate);
|
||||
}
|
||||
|
||||
if (afterKnowInOut != null && DbContext.Entry(tr.tradeAirbag).State == EntityState.Modified)
|
||||
{
|
||||
afterKnowInOut(tr.trade, tr.tradeAirbag);
|
||||
}
|
||||
}
|
||||
|
||||
//更新
|
||||
DbContext.SaveChanges();
|
||||
}
|
||||
|
||||
public void CheckAirbagKnockInStatus(OtcTradeBase td, trade_airbag tradeAirbag, DateTime valueDate, double? closePrice)
|
||||
{
|
||||
//气囊结构暂时都是每日连续观察,不用考虑观察周期问题
|
||||
//气囊结构暂时都是向下敲入
|
||||
if (closePrice <= (td.IsMoneynessOptionData ? tradeAirbag.Barrier * td.SpotPrice : tradeAirbag.Barrier))
|
||||
{
|
||||
tradeAirbag.KnockInOutStatus = ConsTrade.KnockState.KnockedIn;
|
||||
tradeAirbag.KnockInOutDate = valueDate;
|
||||
tradeAirbag.KnockInOutNotional = td.Notional;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置敲入敲出状态
|
||||
/// </summary>
|
||||
public trade_airbag SaveKnockInOutStatus(int tradeId, DateTime? KnockInOutDate, string KnockInOutStatus)
|
||||
{
|
||||
var airbag = DbContext.trade_airbag.FirstOrDefault(x => x.TradeId == tradeId);
|
||||
|
||||
if (airbag != null)
|
||||
{
|
||||
airbag.KnockInOutDate = KnockInOutDate;
|
||||
airbag.KnockInOutStatus = KnockInOutStatus;
|
||||
airbag.OptId = UserId;
|
||||
airbag.OptName = UserName;
|
||||
airbag.OptDate = DateTime.Now;
|
||||
DbContext.SaveChanges();
|
||||
|
||||
var td = DbContext.trade.Find(tradeId);
|
||||
|
||||
SaveTradeOperationHistory(td, airbag.KnockInOutStatusCn);
|
||||
}
|
||||
|
||||
return airbag;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
using Qdp.Foundation.Implementations;
|
||||
using Qdp.Pricing.Base.Utilities;
|
||||
using YLErp.Models;
|
||||
using YLErp.Modules;
|
||||
using YLErp.Modules.CalculationModule;
|
||||
|
||||
namespace YLErp.BLL
|
||||
{
|
||||
public class trade_asian_optionBLL
|
||||
{
|
||||
/// <summary>
|
||||
/// 计算亚式期权Floating类型交易的浮动行权价
|
||||
/// </summary>
|
||||
public static double? GetAsianStrikePrice(DateTime valueDate, trade trade)
|
||||
{
|
||||
var baseReq = AsianOptionFixingService.GetRequest(valueDate, trade);
|
||||
var strikeReq = new AsianOptionStrikeRequest(baseReq)
|
||||
{
|
||||
IsMoneynessOption = trade.IsMoneynessOptionData,
|
||||
SpotPrice = trade.SpotPrice,
|
||||
Strike = trade.Strike,
|
||||
};
|
||||
tradeBLL.SetFieldsByTradeType(trade);
|
||||
return GetAsianStrikePrice(strikeReq, trade.trade_asian_option);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取亚式期权行权价
|
||||
/// </summary>
|
||||
public static double? GetAsianStrikePrice(AsianOptionStrikeRequest request, trade_asian_option asianOption)
|
||||
{
|
||||
if (request is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (asianOption is null)
|
||||
{
|
||||
using (var db = DbContextFactory.GetYLDbContext())
|
||||
{
|
||||
asianOption = request.TradeId > 0 ? db.trade_asian_option.AsNoTracking().FirstOrDefault(n => n.TradeId == request.TradeId) : null;
|
||||
}
|
||||
|
||||
if (asianOption is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if ("Floating".Equals(asianOption.StrikeType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var fixings = AsianOptionFixingService.GetFixingString(request, asianOption);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(fixings))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var fixingValues = fixings.Split(QdpConsts.Semilicon).Select(x =>
|
||||
{
|
||||
var splits = x.Split(QdpConsts.Comma);
|
||||
return Tuple.Create(splits[0].ToDate(), double.Parse(splits[1]));
|
||||
}).ToDictionary(x => x.Item1, x => x.Item2);
|
||||
if ("GeometricAverage".Equals(asianOption.PayoffType))
|
||||
{
|
||||
var n = fixingValues.Count;
|
||||
return Math.Pow(fixingValues.Select(x => x.Value).Aggregate(func: (result, item) => result * item), 1.0 / n);
|
||||
}
|
||||
else if ("ArithmeticAverage".Equals(asianOption.PayoffType)
|
||||
|| "DiscreteArithmeticAverage".Equals(asianOption.PayoffType)
|
||||
|| "EnhancedArithmeticAverage".Equals(asianOption.PayoffType))
|
||||
{
|
||||
return fixingValues.Select(x => x.Value).Average();
|
||||
}
|
||||
}
|
||||
|
||||
return request.IsMoneynessOption ? (request.SpotPrice * request.Strike) : request.Strike;
|
||||
}
|
||||
|
||||
//TODO:除权除息
|
||||
|
||||
/// <summary>
|
||||
/// 计算亚式期权Fix类型交易的浮动行权价
|
||||
/// </summary>
|
||||
public static double? GetAsianFinalPrice(trade trade, DateTime? valueDate = null)
|
||||
{
|
||||
if (trade is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (valueDate == null)
|
||||
{
|
||||
valueDate = valuedateBLL.ValueDate;
|
||||
}
|
||||
|
||||
tradeBLL.SetFieldsByTradeType(trade);
|
||||
|
||||
var asianOption = trade.trade_asian_option;
|
||||
|
||||
if (asianOption is null)
|
||||
{
|
||||
using (var db = DbContextFactory.GetYLDbContext())
|
||||
{
|
||||
trade.trade_asian_option = asianOption = db.trade_asian_option.AsNoTracking().FirstOrDefault(n => n.TradeId == trade.id);
|
||||
}
|
||||
|
||||
if (asianOption is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if ("Fixed".Equals(asianOption.StrikeType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var fixings = AsianOptionFixingService.GetFixingString(valueDate.Value, trade);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(fixings))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var fixingValues = fixings.Split(QdpConsts.Semilicon).Select(x =>
|
||||
{
|
||||
var splits = x.Split(QdpConsts.Comma);
|
||||
return Tuple.Create(splits[0].ToDate(), double.Parse(splits[1]));
|
||||
}).ToDictionary(x => x.Item1, x => x.Item2);
|
||||
if ("GeometricAverage".Equals(asianOption.PayoffType))
|
||||
{
|
||||
var n = fixingValues.Count;
|
||||
return Math.Pow(fixingValues.Select(x => x.Value).Aggregate(func: (result, item) => result * item), 1.0 / n);
|
||||
}
|
||||
else if ("ArithmeticAverage".Equals(asianOption.PayoffType)
|
||||
|| "DiscreteArithmeticAverage".Equals(asianOption.PayoffType))
|
||||
{
|
||||
return fixingValues.Select(x => x.Value).Average();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取亚式期权 均价起算日之后的均价 未到均价起算日则返回Null
|
||||
/// </summary>
|
||||
public static double? GetAsianAveragePrice(trade trade)
|
||||
{
|
||||
if (trade.trade_asian_option == null)
|
||||
{
|
||||
tradeBLL.SetFieldsByTradeType(trade);
|
||||
}
|
||||
|
||||
if (null != trade.trade_asian_option)
|
||||
{
|
||||
var fixings = AsianOptionFixingService.GetFixingString(valuedateBLL.ValueDate, trade);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(fixings))
|
||||
{
|
||||
var fixingValues = string.IsNullOrEmpty(fixings)
|
||||
? new Dictionary<Date, double>() :
|
||||
fixings.Split(QdpConsts.Semilicon)
|
||||
.Select(x =>
|
||||
{
|
||||
var splits = x.Split(QdpConsts.Comma);
|
||||
return Tuple.Create(splits[0].ToDate(), double.Parse(splits[1]));
|
||||
}).ToDictionary(x => x.Item1, x => x.Item2);
|
||||
if ("GeometricAverage".Equals(trade.trade_asian_option.PayoffType))
|
||||
{
|
||||
var n = fixingValues.Count;
|
||||
return Math.Pow(fixingValues.Select(x => x.Value).Aggregate(func: (result, item) => result * item), 1.0 / n);
|
||||
}
|
||||
else if ("ArithmeticAverage".Equals(trade.trade_asian_option.PayoffType) || "DiscreteArithmeticAverage".Equals(trade.trade_asian_option.PayoffType))
|
||||
{
|
||||
return fixingValues.Select(x => x.Value).Average();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class AsianOptionStrikeRequest : AsianFixingRequest
|
||||
{
|
||||
public AsianOptionStrikeRequest(FixingRequestBase baseReq) : base(baseReq)
|
||||
{
|
||||
}
|
||||
|
||||
public AsianOptionStrikeRequest(DateTime valueDate, int tradeId, string instrumentType, string underlyingCode, DateTime exerciseDate, SettlementTypeEnum settlementType)
|
||||
: base(valueDate, tradeId, instrumentType, underlyingCode, exerciseDate, settlementType)
|
||||
{
|
||||
}
|
||||
|
||||
public bool IsMoneynessOption { get; set; }
|
||||
|
||||
public double? Strike { get; set; }
|
||||
|
||||
public double? SpotPrice { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,753 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,534 @@
|
||||
using Org.BouncyCastle.Ocsp;
|
||||
using Qdp.Foundation.Utilities;
|
||||
using YLErp.Commons;
|
||||
using YLErp.CustomizedBizLogic;
|
||||
using YLErp.DBModels.Consts;
|
||||
using YLErp.DBModels.Enums;
|
||||
using YLErp.DBModels.Helpers;
|
||||
using YLErp.Modules.ClientModule;
|
||||
using YLErp.Modules.TradeModule.DealModule;
|
||||
using static NPOI.HSSF.Util.HSSFColor;
|
||||
|
||||
namespace YLErp.Modules.TradeModule.ExoticOptionModule
|
||||
{
|
||||
public class TradeCashServiceEx : TradeCashService
|
||||
{
|
||||
public TradeCashServiceEx(YLBaseService baseService) : base(baseService)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public TradeCashServiceEx(OptUserInfo userInfo) : base(userInfo)
|
||||
{
|
||||
}
|
||||
|
||||
public trade_cash SaveGroupUnwindCash(OtcTradeBase otcTrade, DateTime valueDate, double paymentAmount, double closePrice, out bool continueTradeCashHandle)
|
||||
{
|
||||
var childrenTrades = DbContext.trade.Where(x => x.ParentTradeId == otcTrade.ParentTradeId && x.ValidState != "InValid");
|
||||
//子交易观察调用该方法处理主交易时,当前子交易会了结,但还没存数据库,所以只需要判断其他子交易是否已了结即可
|
||||
var isAllCompleted = childrenTrades.Where(x => x.id != otcTrade.id).All(x => ConsTrade.TradeCompleteStatus.Contains(x.TradeStatus));
|
||||
var parentTrade = DbContext.trade.Find(otcTrade.ParentTradeId);
|
||||
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(parentTrade.UnderlyingCode);
|
||||
var tradeCashIds = DbContext.trade_cash.Where(x => x.TradeId == otcTrade.ParentTradeId && x.ValueDate == valueDate).Select(x => x.id);
|
||||
var tradeCashGroupAction = DbContext.trade_cash_group_action.FirstOrDefault(x => tradeCashIds.Contains(x.ParentTradeCashId) && x.IsEodSettle);
|
||||
var tradeCash = new trade_cash();
|
||||
if (tradeCashGroupAction != null)
|
||||
{
|
||||
tradeCash = DbContext.trade_cash.Find(tradeCashGroupAction.ParentTradeCashId);
|
||||
|
||||
var action = DbContext.trade_cash_group_action.FirstOrDefault(x => x.ParentTradeCashId == tradeCashGroupAction.ParentTradeCashId && x.TradeId == otcTrade.id);
|
||||
if (action != null)
|
||||
{
|
||||
if (!action.IsFinishedUnwindPercent)
|
||||
{
|
||||
tradeCash.Amount += paymentAmount;
|
||||
tradeCash.OptId = UserId;
|
||||
tradeCash.OptName = UserName;
|
||||
tradeCash.OptDate = DateTime.Now;
|
||||
if (isAllCompleted)
|
||||
{
|
||||
tradeCash.IsLastAction = true;
|
||||
}
|
||||
|
||||
action.IsFinishedUnwindPercent = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
continueTradeCashHandle = false;
|
||||
return tradeCash;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tradeCash.Amount += paymentAmount;
|
||||
tradeCash.OptId = UserId;
|
||||
tradeCash.OptName = UserName;
|
||||
tradeCash.OptDate = DateTime.Now;
|
||||
if (isAllCompleted)
|
||||
{
|
||||
tradeCash.IsLastAction = true;
|
||||
tradeCash.UnwindNotional = tradeCash.Notional;
|
||||
tradeCash.UnwindTradeAmount = tradeCash.TradeAmount;
|
||||
tradeCash.UnwindPercentRate = 1;
|
||||
}
|
||||
|
||||
tradeCashGroupAction = new trade_cash_group_action()
|
||||
{
|
||||
IsEodSettle = true,
|
||||
TradeId = otcTrade.id,
|
||||
ParentTradeId = otcTrade.ParentTradeId,
|
||||
ParentTradeCashId = tradeCash.id,
|
||||
Status = "已完成",
|
||||
OptId = UserId,
|
||||
OptName = UserName,
|
||||
OptDate = DateTime.Now,
|
||||
IsFinishedUnwindPercent = true
|
||||
};
|
||||
DbContext.trade_cash_group_action.Add(tradeCashGroupAction);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tradeCash = new trade_cash()
|
||||
{
|
||||
Action = ClientCashInCashOut.系统操作_票息,
|
||||
IsLastAction = false,
|
||||
TradeType = parentTrade.BuySell,
|
||||
Notional = parentTrade.Notional,
|
||||
TradeAmount = parentTrade.TradeAmount,
|
||||
TradeId = otcTrade.ParentTradeId,
|
||||
HappenedDate = valueDate,
|
||||
ValueDate = valueDate,
|
||||
FinalPrice = closePrice,
|
||||
Status = TradeCashStatusEnum.已执行,
|
||||
OptId = UserId,
|
||||
OptName = UserName,
|
||||
OptDate = DateTime.Now
|
||||
};
|
||||
|
||||
tradeCash.Amount += paymentAmount;
|
||||
if (isAllCompleted)
|
||||
{
|
||||
tradeCash.IsLastAction = true;
|
||||
tradeCash.UnwindNotional = tradeCash.Notional;
|
||||
tradeCash.UnwindTradeAmount = tradeCash.TradeAmount;
|
||||
tradeCash.UnwindPercentRate = 1;
|
||||
}
|
||||
|
||||
DbContext.trade_cash.Add(tradeCash);
|
||||
DbContext.SaveChanges();
|
||||
|
||||
#region---存入ClientCashInCashOut---
|
||||
|
||||
var cl = ClientDataQueryService.GetClient(parentTrade.ClientId, true);
|
||||
var ee = new ClientCashInCashOut
|
||||
{
|
||||
Direction = "应收",
|
||||
Number = UniqueTimeId.GetStr(),
|
||||
ClientId = cl.id,
|
||||
ClientNumber = cl.Number,
|
||||
ClientName = cl.Name,
|
||||
HappenDate = tradeCash.ValueDate,
|
||||
State = ClientCashInCashOut.已确认,
|
||||
OptId = tradeCash.OptId,
|
||||
OptName = tradeCash.OptName,
|
||||
OptDate = tradeCash.OptDate,
|
||||
CreatorId = tradeCash.OptId,
|
||||
CreatorName = tradeCash.OptName,
|
||||
CreateDate = tradeCash.OptDate,
|
||||
TradeId = tradeCash.TradeId,
|
||||
TradeCashId = tradeCash.id,
|
||||
Action = tradeCash.Action,
|
||||
TradeNumber = tradeCash.TradeNumber,
|
||||
IsGroup = parentTrade.IsGroup
|
||||
};
|
||||
DbContext.ClientCashInCashOut.Add(ee);
|
||||
|
||||
#endregion
|
||||
|
||||
tradeCashGroupAction = new trade_cash_group_action()
|
||||
{
|
||||
IsEodSettle = true,
|
||||
TradeId = otcTrade.id,
|
||||
ParentTradeId = otcTrade.ParentTradeId,
|
||||
ParentTradeCashId = tradeCash.id,
|
||||
Status = "已完成",
|
||||
OptId = UserId,
|
||||
OptName = UserName,
|
||||
OptDate = DateTime.Now,
|
||||
IsFinishedUnwindPercent = true
|
||||
};
|
||||
DbContext.trade_cash_group_action.Add(tradeCashGroupAction);
|
||||
}
|
||||
|
||||
if (isAllCompleted)
|
||||
{
|
||||
parentTrade.UnWindNotional = parentTrade.Notional;
|
||||
parentTrade.UnWindDate = tradeCash.ValueDate;
|
||||
parentTrade.FinalPrice = tradeCash.FinalPrice;
|
||||
}
|
||||
parentTrade.OptId = UserId;
|
||||
parentTrade.OptName = UserName;
|
||||
parentTrade.OptDate = DateTime.Now;
|
||||
|
||||
DbContext.SaveChanges();
|
||||
if (BLL.valuedateBLL.SystemDate.CloseReApprove != 1 || !HasTradeProcess())
|
||||
{
|
||||
if (PS.Config.SalesCommissionCalculation == "公式1")
|
||||
{
|
||||
//销售提成
|
||||
new SalesModule.SalesCommissionDetailDataService(UserInfo).CalcuSalesCommissionDetail(tradeCash);
|
||||
}
|
||||
if (PS.Config.Company == Configuration.CompanyEnum.招证)
|
||||
{
|
||||
new BizLogicZhaoZheng().GenerateZhaoZhengDealNumber(parentTrade, tradeCash);
|
||||
}
|
||||
if (PS.Config.Company == Configuration.CompanyEnum.物产中大)
|
||||
{
|
||||
new BizLogicWCZD().GenerateWCZDNumber(DbContext, parentTrade, tradeCash.ValueDate, tradeCash.id);
|
||||
}
|
||||
}
|
||||
if (tradeCash.IsLastAction)
|
||||
{
|
||||
var tradeIds = DbContext.trade_cash_group_action.Where(x => x.ParentTradeCashId == tradeCash.id).Select(x => x.TradeId).ToList();
|
||||
var tradeStatusList = DbContext.trade.Where(x => tradeIds.Contains(x.id)).Select(x => x.TradeStatus).ToList();
|
||||
if (tradeStatusList.Contains(ConsTrade.已执行))
|
||||
{
|
||||
parentTrade.TradeStatus = ConsTrade.已执行;
|
||||
}
|
||||
else if (tradeStatusList.Contains(ConsTrade.已到期))
|
||||
{
|
||||
parentTrade.TradeStatus = ConsTrade.已到期;
|
||||
}
|
||||
else
|
||||
{
|
||||
parentTrade.TradeStatus = ConsTrade.已平仓;
|
||||
}
|
||||
}
|
||||
DbContext.SaveChanges();
|
||||
|
||||
continueTradeCashHandle = true;
|
||||
return tradeCash;
|
||||
}
|
||||
|
||||
public int SaveGroupCouponCash(OtcTradeBase trade, DateTime happenDate, double paymentAmount, double underlyingPrice)
|
||||
{
|
||||
var parentTradeCashId = 0;
|
||||
|
||||
var childrenTrades = DbContext.trade.Where(x => x.ParentTradeId == trade.ParentTradeId && x.ValidState != "InValid");
|
||||
var parentTrade = DbContext.trade.Find(trade.ParentTradeId);
|
||||
var tradeCashIds = DbContext.trade_cash.Where(x => x.TradeId == trade.ParentTradeId && x.ValueDate == happenDate).Select(x => x.id);
|
||||
var tradeCashGroupAction = DbContext.trade_cash_group_action.FirstOrDefault(x => tradeCashIds.Contains(x.ParentTradeCashId) && x.IsEodSettle);
|
||||
var tradeCash = new trade_cash();
|
||||
if (tradeCashGroupAction != null)
|
||||
{
|
||||
parentTradeCashId = tradeCashGroupAction.ParentTradeCashId;
|
||||
|
||||
if (DbContext.trade_cash_group_action.Any(x => x.ParentTradeCashId == tradeCashGroupAction.ParentTradeCashId && x.TradeId == trade.id))
|
||||
{
|
||||
return parentTradeCashId;
|
||||
}
|
||||
else
|
||||
{
|
||||
tradeCash = DbContext.trade_cash.Find(tradeCashGroupAction.ParentTradeCashId);
|
||||
tradeCash.Amount += paymentAmount;
|
||||
tradeCash.OptId = UserId;
|
||||
tradeCash.OptName = UserName;
|
||||
tradeCash.OptDate = DateTime.Now;
|
||||
|
||||
tradeCashGroupAction = new trade_cash_group_action()
|
||||
{
|
||||
IsEodSettle = true,
|
||||
TradeId = trade.id,
|
||||
ParentTradeId = trade.ParentTradeId,
|
||||
ParentTradeCashId = tradeCash.id,
|
||||
Status = "已完成",
|
||||
OptId = UserId,
|
||||
OptName = UserName,
|
||||
OptDate = DateTime.Now,
|
||||
IsFinishedUnwindPercent = false
|
||||
};
|
||||
DbContext.trade_cash_group_action.Add(tradeCashGroupAction);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tradeCash = new trade_cash()
|
||||
{
|
||||
Action = ClientCashInCashOut.系统操作_票息,
|
||||
IsLastAction = false,
|
||||
TradeType = parentTrade.BuySell,
|
||||
Notional = parentTrade.Notional,
|
||||
TradeAmount = parentTrade.TradeAmount,
|
||||
Amount = paymentAmount,
|
||||
TradeId = trade.ParentTradeId,
|
||||
HappenedDate = happenDate,
|
||||
ValueDate = happenDate,
|
||||
FinalPrice = underlyingPrice,
|
||||
Status = TradeCashStatusEnum.已执行,
|
||||
OptId = UserId,
|
||||
OptName = UserName,
|
||||
OptDate = DateTime.Now
|
||||
};
|
||||
|
||||
DbContext.trade_cash.Add(tradeCash);
|
||||
DbContext.SaveChanges();
|
||||
|
||||
parentTradeCashId = tradeCash.id;
|
||||
|
||||
tradeCashGroupAction = new trade_cash_group_action()
|
||||
{
|
||||
IsEodSettle = true,
|
||||
TradeId = trade.id,
|
||||
ParentTradeId = trade.ParentTradeId,
|
||||
ParentTradeCashId = tradeCash.id,
|
||||
Status = "已完成",
|
||||
OptId = UserId,
|
||||
OptName = UserName,
|
||||
OptDate = DateTime.Now,
|
||||
IsFinishedUnwindPercent = false
|
||||
};
|
||||
DbContext.trade_cash_group_action.Add(tradeCashGroupAction);
|
||||
DbContext.SaveChanges();
|
||||
|
||||
#region---存入ClientCashInCashOut---
|
||||
|
||||
var cl = ClientDataQueryService.GetClient(parentTrade.ClientId, true);
|
||||
var ee = new ClientCashInCashOut
|
||||
{
|
||||
Direction = "应收",
|
||||
Number = UniqueTimeId.GetStr(),
|
||||
ClientId = cl.id,
|
||||
ClientNumber = cl.Number,
|
||||
ClientName = cl.Name,
|
||||
HappenDate = tradeCash.ValueDate,
|
||||
State = ClientCashInCashOut.已确认,
|
||||
OptId = tradeCash.OptId,
|
||||
OptName = tradeCash.OptName,
|
||||
OptDate = tradeCash.OptDate,
|
||||
CreatorId = tradeCash.OptId,
|
||||
CreatorName = tradeCash.OptName,
|
||||
CreateDate = tradeCash.OptDate,
|
||||
TradeId = tradeCash.TradeId,
|
||||
TradeCashId = tradeCash.id,
|
||||
Action = tradeCash.Action,
|
||||
TradeNumber = tradeCash.TradeNumber,
|
||||
IsGroup = parentTrade.IsGroup
|
||||
};
|
||||
DbContext.ClientCashInCashOut.Add(ee);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
parentTrade.OptId = UserId;
|
||||
parentTrade.OptName = UserName;
|
||||
parentTrade.OptDate = DateTime.Now;
|
||||
|
||||
DbContext.SaveChanges();
|
||||
if (BLL.valuedateBLL.SystemDate.CloseReApprove != 1 || !HasTradeProcess())
|
||||
{
|
||||
if (PS.Config.SalesCommissionCalculation == "公式1")
|
||||
{
|
||||
//销售提成
|
||||
new SalesModule.SalesCommissionDetailDataService(UserInfo).CalcuSalesCommissionDetail(tradeCash);
|
||||
}
|
||||
if (PS.Config.Company == Configuration.CompanyEnum.招证)
|
||||
{
|
||||
new BizLogicZhaoZheng().GenerateZhaoZhengDealNumber(parentTrade, tradeCash);
|
||||
}
|
||||
if (PS.Config.Company == Configuration.CompanyEnum.物产中大)
|
||||
{
|
||||
new BizLogicWCZD().GenerateWCZDNumber(DbContext, parentTrade, tradeCash.ValueDate, tradeCash.id);
|
||||
}
|
||||
}
|
||||
return parentTradeCashId;
|
||||
}
|
||||
|
||||
//如果两个票息的结算日设为同一天的情况下,用这个方法保存票息有问题
|
||||
public trade_cash SaveCash(OtcTradeBase trade, string cashAction, string exerciseWay, double paymentAmount, DateTime valueDate, double underlyingPrice, DateTime happenDate, bool isKnockOut = false, bool isLastAction = false, int parentTradeId = 0, int parentTradeCashId = 0)
|
||||
{
|
||||
var notional = trade.Notional;
|
||||
var tradeAmount = trade.TradeAmount;
|
||||
// 保存trade_cash
|
||||
var tc = DbContext.trade_cash.FirstOrDefault(t => t.TradeId == trade.id && t.Action == cashAction && !t.IsDeleted && t.HappenedDate == happenDate);
|
||||
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(trade.UnderlyingCode);
|
||||
//暂时只应用于累计期权的观察部分行权处理
|
||||
trade_accumulator_option accumulator = new trade_accumulator_option();
|
||||
if (trade.TradeType == "累计期权")
|
||||
{
|
||||
accumulator = DbContext.trade_accumulator_option.FirstOrDefault(x => x.TradeId == trade.id);
|
||||
tc = DbContext.trade_cash.FirstOrDefault(t => t.TradeId == trade.id && t.Action == "系统操作-平仓费" && t.UnwindType == "部分行权" && !t.IsDeleted && t.HappenedDate == happenDate);
|
||||
if (tc == null)
|
||||
{
|
||||
trade.TradeAmount -= accumulator.AccumuTradeAmount;
|
||||
trade.Notional = trade.TradeAmount * underlying.CountRatio;
|
||||
trade.StockEqvNotional = TradeHelper.GetStockEqvNotional(trade.Notional * trade.SpotPrice, trade.ParticipationRate, trade.AnnualizeFactor);
|
||||
}
|
||||
}
|
||||
if (tc == null)
|
||||
{
|
||||
tc = new trade_cash();
|
||||
DbContext.trade_cash.Add(tc);
|
||||
tc.Notional = notional;
|
||||
tc.TradeAmount = tradeAmount;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (paymentAmount == 0)
|
||||
{
|
||||
//如果是最后一笔资金记录但是查出来的tc没有设置最后一笔资金标识则重新设置
|
||||
if (isLastAction && !tc.IsLastAction)
|
||||
{
|
||||
tc.IsLastAction = true;
|
||||
tc.UnwindNotional = trade.Notional;
|
||||
tc.UnwindTradeAmount = trade.TradeAmount;
|
||||
tc.UnwindPercentRate = trade.Notional / trade.OriginalNotional;
|
||||
DbContext.SaveChanges();
|
||||
}
|
||||
|
||||
return tc;
|
||||
}
|
||||
|
||||
if (trade.TradeType != "累计期权")
|
||||
{
|
||||
tc.Notional = notional;
|
||||
tc.TradeAmount = tradeAmount;
|
||||
}
|
||||
}
|
||||
tc.ValidState = null;
|
||||
tc.OptId = UserId;
|
||||
tc.OptName = UserName;
|
||||
tc.OptDate = DateTime.Now;
|
||||
tc.ExceciseType = "现金";
|
||||
tc.TradeType = trade.BuySell;
|
||||
tc.CallPut = trade.CallPut;
|
||||
tc.Amount = paymentAmount;
|
||||
|
||||
tc.TradeId = trade.id;
|
||||
tc.ParentTradeId = parentTradeId;
|
||||
tc.ParentTradeCashId = parentTradeCashId;
|
||||
tc.HappenedDate = happenDate;
|
||||
|
||||
if (trade.TradeType == "累计期权" && (cashAction == "部分行权" || cashAction == "到期行权"))
|
||||
{
|
||||
if (cashAction == "部分行权")
|
||||
{
|
||||
tc.Action = ClientCashInCashOut.系统操作_平仓费;
|
||||
tc.UnwindType = "部分行权";
|
||||
tc.ExerciseWay = TradeCashExerciseWayEnum.提前终止行权;
|
||||
}
|
||||
else if (cashAction == "到期行权")
|
||||
{
|
||||
tc.Action = ClientCashInCashOut.系统操作_行权费;
|
||||
tc.UnwindType = "到期";
|
||||
tc.ExerciseWay = TradeCashExerciseWayEnum.到期行权;
|
||||
}
|
||||
|
||||
tc.UnwindTradeAmount = accumulator.AccumuTradeAmount;
|
||||
tc.UnwindNotional = accumulator.AccumuTradeAmount * underlying.CountRatio;
|
||||
tc.UnwindPrice = Math.Abs(accumulator.AccumuTradeAmount != 0 ? paymentAmount / accumulator.AccumuTradeAmount : 0);
|
||||
tc.UnwindPricePercentRate = Math.Abs(accumulator.AccumuTradeAmount != 0 && trade.SpotPrice != null && trade.SpotPrice != 0 ? paymentAmount / accumulator.AccumuTradeAmount / trade.SpotPrice.Value : 0);
|
||||
tc.UnwindPercentRate = accumulator.AccumuTradeAmount * underlying.CountRatio / trade.OriginalNotional;
|
||||
tc.NotionalPercentRate = tc.UnwindPercentRate;
|
||||
}
|
||||
else
|
||||
{
|
||||
tc.Action = cashAction;
|
||||
tc.ExerciseWay = exerciseWay;
|
||||
tc.UnwindPrice = Math.Abs(trade.Notional != 0 ? paymentAmount / trade.Notional : 0);
|
||||
tc.UnwindPricePercentRate = Math.Abs(trade.Notional != 0 && trade.SpotPrice != null && trade.SpotPrice != 0 ? paymentAmount / trade.Notional / trade.SpotPrice.Value : 0);
|
||||
tc.NotionalPercentRate = trade.Notional / trade.OriginalNotional;
|
||||
}
|
||||
tc.IsLastAction = isLastAction;
|
||||
|
||||
if (exerciseWay == "到期行权")
|
||||
{
|
||||
tc.UnwindType = "到期";
|
||||
if (cashAction == ClientCashInCashOut.系统操作_票息 && tc.Amount == 0)
|
||||
{
|
||||
tc.Action = ClientCashInCashOut.系统操作_行权费;
|
||||
}
|
||||
}
|
||||
tc.Status = TradeCashStatusEnum.已执行;
|
||||
tc.ValueDate = valueDate;
|
||||
tc.FinalPrice = underlyingPrice;
|
||||
if (isKnockOut)
|
||||
{
|
||||
tc.BarrierPrice = trade.IsMoneynessOptionData && trade.SpotPrice != 0 ? (underlyingPrice / trade.SpotPrice) : underlyingPrice;
|
||||
}
|
||||
|
||||
if (isLastAction && !(cashAction == "部分行权" && trade.TradeType == "累计期权"))
|
||||
{
|
||||
tc.UnwindNotional = notional;
|
||||
tc.UnwindTradeAmount = tradeAmount;
|
||||
tc.UnwindPercentRate = notional / trade.OriginalNotional;
|
||||
}
|
||||
|
||||
tc.NotionalPercentRate = trade.Notional / trade.OriginalNotional;
|
||||
DbContext.SaveChanges();
|
||||
|
||||
var tradeCashDetails = DbContext.trade_cash_detail.Where(x => x.TradeCashId == tc.id);
|
||||
DbContext.trade_cash_detail.RemoveRange(tradeCashDetails);
|
||||
|
||||
SaveTradeCashDetail(tc);
|
||||
|
||||
if ((BLL.valuedateBLL.SystemDate.CloseReApprove != 1 || !HasTradeProcess()) || isKnockOut || isLastAction)
|
||||
{
|
||||
if (PS.Config.SalesCommissionCalculation == "公式1")
|
||||
{
|
||||
//销售提成
|
||||
new SalesModule.SalesCommissionDetailDataService(UserInfo).CalcuSalesCommissionDetail(tc);
|
||||
}
|
||||
|
||||
if (PS.Config.Company == Configuration.CompanyEnum.招证)
|
||||
{
|
||||
new BizLogicZhaoZheng().GenerateZhaoZhengDealNumber(trade, tc);
|
||||
}
|
||||
if (PS.Config.Company == Configuration.CompanyEnum.物产中大 && cashAction!=ClientCashInCashOut.系统操作_期权费)
|
||||
{
|
||||
new BizLogicWCZD().GenerateWCZDNumber(DbContext, trade, tc.ValueDate, tc.id);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存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.TradeCashId == tc.id);
|
||||
if (cashInOutRecord == null)
|
||||
{
|
||||
cashInOutRecord = new ClientCashInCashOut();
|
||||
cashInOutRecord.CreateDate = tc.OptDate;
|
||||
cashInOutRecord.CreatorId = tc.OptId;
|
||||
cashInOutRecord.CreatorName = tc.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 = -tc.Amount;
|
||||
cashInOutRecord.HappenDate = valueDate;
|
||||
cashInOutRecord.State = ClientCashInCashOut.已确认;
|
||||
cashInOutRecord.OptDate = tc.OptDate;
|
||||
cashInOutRecord.OptId = tc.OptId;
|
||||
cashInOutRecord.OptName = tc.OptName;
|
||||
cashInOutRecord.TradeId = trade.id;
|
||||
cashInOutRecord.TradeCashId = tc.id;
|
||||
cashInOutRecord.Action = tc.Action;
|
||||
cashInOutRecord.TradeNumber = trade.TradeNumber;
|
||||
cashInOutRecord.IsGroup = trade.IsGroup;
|
||||
|
||||
DbContext.SaveChanges();
|
||||
|
||||
return tc;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
using Qdp.Foundation.Implementations;
|
||||
using Qdp.Pricing.Base.Implementations;
|
||||
using YLErp.Abstract.DataProviders;
|
||||
using YLErp.Modules.DataProviderModule;
|
||||
using YLErp.Modules.SalesModule;
|
||||
using YLErp.Modules.TradeModule.DealModule;
|
||||
using YLErp.QdpModule;
|
||||
|
||||
namespace YLErp.Modules.TradeModule
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class TradeRangeAccrualService : ExoticOptionModule.TradeCashServiceEx
|
||||
{
|
||||
public TradeRangeAccrualService(OptUserInfo userInfo) : base(userInfo)
|
||||
{
|
||||
}
|
||||
|
||||
public TradeRangeAccrualService(YLBaseService baseService) : base(baseService)
|
||||
{
|
||||
}
|
||||
|
||||
public void CheckStatus(DateTime valueDate, IEodPriceProviderV2 priceProvider, DateTime? startDate = null
|
||||
, Action<OtcTrade, trade_rangeaccrual> 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 rangeaccrual in DbContext.trade_rangeaccrual on trade.id equals rangeaccrual.TradeId
|
||||
where trade.TradeDate > startDate.Value && trade.TradeDate <= valueDate && trade.ExerciseDate >= valueDate
|
||||
&& (trade.TradeType == "区间累积期权")
|
||||
&& ConsTrade.确认成交 == trade.TradeStatus
|
||||
&& trade.ValidState != ConsGlobal.InValid
|
||||
&& trade.DividendDate < valueDate
|
||||
select new
|
||||
{
|
||||
trade,
|
||||
tradeRange = rangeaccrual
|
||||
};
|
||||
#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).ToArray();
|
||||
|
||||
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;
|
||||
|
||||
double closePrice;
|
||||
|
||||
if (manuallyTradeObservationPrices.TryGetValue(tr.trade.id, out var manuallyTradeObservationPrice))
|
||||
{
|
||||
closePrice = manuallyTradeObservationPrice.Price ?? 0;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
CheckRangeAccrualBonus(tr.trade, tr.tradeRange, valueDate, closePrice);
|
||||
|
||||
if (tradeStatus != tr.trade.TradeStatus)
|
||||
{
|
||||
//删除E/Bod_Trade记录
|
||||
RemoveEodTradeAndFutureInfo(false, tr.trade.id, valueDate);
|
||||
}
|
||||
|
||||
if (afterKnowInOut != null && DbContext.Entry(tr.tradeRange).State == EntityState.Modified)
|
||||
{
|
||||
afterKnowInOut(tr.trade, tr.tradeRange);
|
||||
}
|
||||
}
|
||||
|
||||
DbContext.SaveChanges();
|
||||
}
|
||||
|
||||
public void CheckRangeAccrualBonus(OtcTradeBase trade, trade_rangeaccrual tradeRange, DateTime valueDate, double closePrice)
|
||||
{
|
||||
var tcQuery = from x in DbContext.trade_cash
|
||||
where x.TradeId == trade.id && x.ValidState != ConsGlobal.InValid
|
||||
&& !x.IsDeleted && x.Action == "系统操作-平仓费"
|
||||
&& x.ValueDate > valueDate && (x.ConfirmDate > valueDate || x.ConfirmDate == DateTime.MinValue)
|
||||
&& x.UnwindNotional < x.Notional
|
||||
select x;
|
||||
|
||||
var notional = (ConsTrade.TradeCompleteStatus.Contains(trade.TradeStatus) && trade.UnWindDate <= valueDate
|
||||
? 0 : trade.Notional) + (tcQuery.Sum(x => x.UnwindNotional) ?? 0);
|
||||
|
||||
if (CheckRangeAccrualBonus(trade, tradeRange, valueDate, closePrice, notional, out var couponCash))
|
||||
{
|
||||
SaveObservation(trade, tradeRange, valueDate, couponCash, notional);
|
||||
//这里要保存,否则下面查询的时候,查不到最后一天的票息记录.
|
||||
DbContext.SaveChanges();
|
||||
}
|
||||
|
||||
//到期日生成票息资金记录
|
||||
if (valueDate.Date == trade.ExerciseDate.Value.Date)
|
||||
{
|
||||
trade.UnWindDate = valueDate.Date;
|
||||
trade.TradeStatus = ConsTrade.已到期;
|
||||
|
||||
var happenedObservations = DbContext.autocall_observation.Where(o => o.TradeId == trade.id).ToList();
|
||||
|
||||
var totalPaymentAmount = happenedObservations?.Sum(x => x.PaymentAmount) ?? 0;
|
||||
totalPaymentAmount += trade.PrincipalSum() * (trade.BuySell == "卖出" ? -1 : 1);
|
||||
|
||||
var parentTradeId = 0;
|
||||
var parentTradeCashId = 0;
|
||||
if (trade.IsGroup == 2 && trade.ParentTradeId > 0)
|
||||
{
|
||||
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;
|
||||
parentTradeCashId = SaveGroupUnwindCash(trade, valueDate, totalPaymentAmount, closePrice, out bool continueTradeCashHandle).id;
|
||||
}
|
||||
}
|
||||
|
||||
SaveCash(trade, ClientCashInCashOut.系统操作_票息, null, totalPaymentAmount, valueDate, closePrice, valueDate, isLastAction: true, parentTradeId: parentTradeId, parentTradeCashId: parentTradeCashId);
|
||||
|
||||
//生成确认书
|
||||
if (PS.Config.IsAutoGenerateContracts && ConsTrade.TradeCompleteStatus.Contains(trade.TradeStatus))
|
||||
{
|
||||
//修改销售提成的状态
|
||||
new SalesCommissionDataService(this).SetCommissionVaild(trade.id);
|
||||
new TradeContractGenerateService(this).GenerateContractsAsync(new List<int> { trade.id });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool CheckRangeAccrualBonus(OtcTradeBase trade, trade_rangeaccrual tradeRange, DateTime valueDate, double closePrice, double notional, out double couponCash)
|
||||
{
|
||||
couponCash = 0;
|
||||
|
||||
if (trade is null || tradeRange is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var observDates = QdpHelper.ParseObservationDate(tradeRange.ObservationDates)?.ToArray();
|
||||
|
||||
if (observDates == null)
|
||||
{
|
||||
observDates = CalendarImpl.Get("chn").BizDaysBetweenDatesInclEndDay(trade.TradeDate.Value, trade.ExerciseDate.Value).ToArray();
|
||||
}
|
||||
|
||||
if (observDates != null && observDates.Contains(new Date(valueDate)))
|
||||
{
|
||||
var upperRange = trade.IsMoneynessOptionData ? tradeRange.UpperRange * trade.SpotPrice.Value : tradeRange.UpperRange;
|
||||
var lowerRange = trade.IsMoneynessOptionData ? tradeRange.LowerRange * trade.SpotPrice.Value : tradeRange.LowerRange;
|
||||
|
||||
//有区间收益
|
||||
if (closePrice < upperRange && closePrice > lowerRange)
|
||||
{
|
||||
couponCash = tradeRange.BonusRate * notional * (trade.SpotPrice ?? 0) / observDates.Length * (trade.BuySell == "卖出" ? -1 : 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public autocall_observation SaveObservation(OtcTradeBase trade, trade_rangeaccrual rangeaccrual, DateTime valueDate, double couponCash, double notional, bool saveChanges = true)
|
||||
{
|
||||
var observationRecord = DbContext.autocall_observation.FirstOrDefault(o => o.TradeId == trade.id && o.EndDate == valueDate.Date);
|
||||
if (observationRecord == null)
|
||||
{
|
||||
observationRecord = new autocall_observation()
|
||||
{
|
||||
TradeId = trade.id,
|
||||
StartDate = valueDate.Date,
|
||||
EndDate = valueDate.Date,
|
||||
CouponRate = rangeaccrual.BonusRate,
|
||||
StockEqvNotional = notional * (trade.SpotPrice ?? 0),
|
||||
PaymentAmount = couponCash,
|
||||
PaymentDate = trade.ExerciseDate.Value.Date
|
||||
};
|
||||
if (saveChanges)
|
||||
{
|
||||
DbContext.autocall_observation.Add(observationRecord);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
observationRecord.StartDate = valueDate.Date;
|
||||
observationRecord.EndDate = valueDate.Date;
|
||||
observationRecord.CouponRate = rangeaccrual.BonusRate;
|
||||
observationRecord.StockEqvNotional = notional * (trade.SpotPrice ?? 0);
|
||||
observationRecord.PaymentAmount = couponCash;
|
||||
observationRecord.PaymentDate = trade.ExerciseDate.Value.Date;
|
||||
}
|
||||
return observationRecord;
|
||||
}
|
||||
|
||||
public double GetRangeCoupon(int tradeId, DateTime valueDate, double price)
|
||||
{
|
||||
var trade = DbContext.trade.AsNoTracking().FirstOrDefault(t => t.id == tradeId);
|
||||
var tradeRangeAccrual = DbContext.trade_rangeaccrual.AsNoTracking().FirstOrDefault(t => t.TradeId == tradeId);
|
||||
return GetRangeCoupon(trade, tradeRangeAccrual, valueDate, price);
|
||||
}
|
||||
|
||||
public double GetRangeCoupon(OtcTradeBase trade, trade_rangeaccrual tradeRangeAccrual, DateTime valueDate, double price)
|
||||
{
|
||||
if (trade == null || tradeRangeAccrual == null)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
var tradeCashs = DbContext.trade_cash.AsNoTracking().Where(x => x.ValidState != ConsGlobal.InValid && !x.IsDeleted && x.TradeId == trade.id && x.Action == "系统操作-平仓费" && (x.ValueDate > valueDate && (x.ConfirmDate > valueDate || x.ConfirmDate == DateTime.MinValue)) && x.UnwindNotional < x.Notional).ToList();
|
||||
var notional = (ConsTrade.TradeCompleteStatus.Contains(trade.TradeStatus) && trade.UnWindDate <= valueDate ? 0 : trade.Notional) + tradeCashs.Sum(x => x.UnwindNotional).Value;
|
||||
|
||||
var observDates = QdpHelper.ParseObservationDate(tradeRangeAccrual.ObservationDates)?.ToArray();
|
||||
if (observDates == null)
|
||||
{
|
||||
observDates = CalendarImpl.Get("chn").BizDaysBetweenDatesInclEndDay(
|
||||
new Date(trade.TradeDate.Value), new Date(trade.ExerciseDate.Value)).ToArray();
|
||||
}
|
||||
|
||||
var upperRange = trade.IsMoneynessOptionData ? tradeRangeAccrual.UpperRange * (trade.SpotPrice ?? 0) : tradeRangeAccrual.UpperRange;
|
||||
var lowerRange = trade.IsMoneynessOptionData ? tradeRangeAccrual.LowerRange * (trade.SpotPrice ?? 0) : tradeRangeAccrual.LowerRange;
|
||||
|
||||
if (price < upperRange && price > lowerRange)
|
||||
{
|
||||
return (observDates != null && observDates.Length > 0) ?
|
||||
tradeRangeAccrual.BonusRate * notional * (trade.SpotPrice ?? 0) / observDates.Length :
|
||||
tradeRangeAccrual.BonusRate * notional * (trade.SpotPrice ?? 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,789 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using NPOI.SS.Formula.Functions;
|
||||
using Qdp.Foundation.Implementations;
|
||||
using Qdp.Pricing.Library.Options.Products.Autocall.Snowball;
|
||||
using YLErp.Modules.CalculationModule;
|
||||
using YLErp.Modules.TradeModule.ExoticOptionModule;
|
||||
|
||||
namespace YLErp.Modules.TradeModule
|
||||
{
|
||||
public class TradeSnowballService : YLBaseService
|
||||
{
|
||||
public TradeSnowballService(OptUserInfo userInfo) : base(userInfo)
|
||||
{
|
||||
}
|
||||
|
||||
public TradeSnowballService(YLBaseService baseService) : base(baseService)
|
||||
{
|
||||
}
|
||||
|
||||
public List<autocall_observation> GetObservations(int tradeId, DateTime valueDate)
|
||||
{
|
||||
var trade = DbContext.trade.FirstOrDefault(t => t.id == tradeId);
|
||||
var tradeSnowball = DbContext.trade_snowball.FirstOrDefault(t => t.TradeId == tradeId);
|
||||
if (trade == null || tradeSnowball == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 如果是敲出转期权,则不支付票息
|
||||
if (tradeSnowball.KOPayoffType != KOPayoffTypeEnum.Rebate)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (trade.TradeStatus == ConsTrade.已到期 || trade.TradeStatus == ConsTrade.已执行 || trade.TradeStatus.Contains("待确认"))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var optionTrade = QdpTradeBuilder.GetSnowballOptionTrade(trade, tradeSnowball);
|
||||
|
||||
if (optionTrade == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var snowball = (SimpleSnowball)optionTrade.Instrument;
|
||||
|
||||
var observationEndDate =
|
||||
tradeSnowball.KnockInOutStatus == ConsTrade.KnockState.KnockedOut ?
|
||||
tradeSnowball.KnockInOutDate.Value :
|
||||
valueDate;
|
||||
var qdpEndDate = new Date(observationEndDate);
|
||||
qdpEndDate = snowball.KOObsDates.Where(d => d <= qdpEndDate).Max();
|
||||
|
||||
var observation = snowball.GetEffectiveObservation(qdpEndDate, tradeSnowball.CouponIncludeStartDate ?? false);
|
||||
|
||||
if (tradeSnowball.PrepaymentUsed)
|
||||
{
|
||||
var payoff = new SpecialSnowballObservationHelper(trade, tradeSnowball).GetEffectiveObservation(qdpEndDate, optionTrade.Notional);
|
||||
|
||||
observation = payoff == null ? null : new Qdp.Pricing.Library.Options.Products.Autocall.Phoenix.ObservationPayment
|
||||
{
|
||||
CouponRate = payoff.CouponRate,
|
||||
EndDate = payoff.CouponEndDate,
|
||||
PaymentAmount = payoff.CouponPaymentAmount,
|
||||
PaymentDate = qdpEndDate,
|
||||
StartDate = payoff.CouponStartDate,
|
||||
Notional = Math.Abs(optionTrade.Notional * (trade.SpotPrice ?? 0))
|
||||
};
|
||||
}
|
||||
if (observation == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var tradeCashCoupon = DbContext.trade_cash.FirstOrDefault(t => t.TradeId == tradeId && t.Action == "系统操作-票息" && t.ValidState != "InValid" && !t.IsDeleted);
|
||||
var tradeCashDetial = tradeCashCoupon != null ? DbContext.trade_cash_detail.FirstOrDefault(x => x.TradeCashId == tradeCashCoupon.id && x.Action == "系统操作-期权费") : null;
|
||||
return new List<autocall_observation>()
|
||||
{
|
||||
new autocall_observation {
|
||||
StartDate = observation.StartDate.DateTime,
|
||||
EndDate = observation.EndDate.DateTime,
|
||||
CouponRate = observation.CouponRate,
|
||||
StockEqvNotional = Math.Abs(observation.Notional),
|
||||
PaymentAmount = observation.PaymentAmount,
|
||||
AnnualizedPremiumRate = tradeSnowball.AnnualizedPremiumRate,
|
||||
AnnualizedTradePrice = tradeCashDetial?.Amount ?? 0,
|
||||
PaymentDate =
|
||||
tradeSnowball.KnockInOutStatus == ConsTrade.KnockState.KnockedOut ?
|
||||
tradeCashCoupon?.ValueDate ?? observationEndDate:
|
||||
observation.PaymentDate.DateTime
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public double GetKOOptionPayoff(int tradeId, DateTime valueDate, double price)
|
||||
{
|
||||
var trade = DbContext.trade.AsNoTracking().FirstOrDefault(t => t.id == tradeId);
|
||||
var tradeSnowball = DbContext.trade_snowball.AsNoTracking().FirstOrDefault(t => t.TradeId == tradeId);
|
||||
return GetKOOptionPayoff(trade, tradeSnowball, valueDate, price);
|
||||
}
|
||||
|
||||
public double GetKOOptionPayoff(OtcTradeBase trade, trade_snowball tradeSnowball, DateTime valueDate, double price)
|
||||
{
|
||||
if (trade == null || tradeSnowball == null)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
if (tradeSnowball.KOPayoffType == KOPayoffTypeEnum.Rebate)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
var optionTrade = QdpTradeBuilder.GetSnowballOptionTrade(trade, tradeSnowball);
|
||||
var snowball = (SimpleSnowball)optionTrade.Instrument;
|
||||
var payoffs = snowball.GetKOPayoff(new Date(valueDate), price);
|
||||
if (payoffs != null && payoffs.Length > 0)
|
||||
{
|
||||
return payoffs[0].PaymentAmount;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
using YLErp.BLL;
|
||||
using YLErp.CustomizedBizLogic;
|
||||
using YLErp.DBModels.Consts;
|
||||
using YLErp.Enums;
|
||||
using YLErp.Modules.EodModule;
|
||||
using YLErp.Modules.TradeModule.ExoticOptionModule;
|
||||
|
||||
namespace YLErp.Modules.TradeModule.DealModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 二元期权操作
|
||||
/// 迁移自:trade_binary_optionBLL
|
||||
/// </summary>
|
||||
public class TradeSwapService : TradeCashServiceEx
|
||||
{
|
||||
public TradeSwapService(YLBaseService baseService) : base(baseService)
|
||||
{
|
||||
}
|
||||
|
||||
public TradeSwapService(OptUserInfo userInfo) : base(userInfo)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public void HandleSwapTradeCashPre(DateTime valueDate, System.Collections.Generic.IEnumerable<int> clienIds)
|
||||
{
|
||||
#region #region 新增客户筛选 tw
|
||||
var tradeCashPres = DbContext.trade_cash_pre.Where(x => x.ValueDate == valueDate && x.ValidState != "InValid" && !x.IsFinished).ToList();
|
||||
var trades = new List<trade>();
|
||||
var tradeIds = new List<int>();
|
||||
if (clienIds != null)
|
||||
{
|
||||
trades = DbContext.trade.Where(l => clienIds.Contains(l.ClientId)).ToList();
|
||||
tradeIds = trades.Select(l => l.id).ToList();
|
||||
tradeCashPres = tradeCashPres.Where(l => tradeIds.Contains(l.TradeId)).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
tradeIds = tradeCashPres.Select(x => x.TradeId).ToList();
|
||||
trades = DbContext.trade.Where(x => tradeIds.Contains(x.id)).ToList();
|
||||
tradeIds = trades.Select(x => x.id).ToList();
|
||||
tradeCashPres = tradeCashPres.Where(l => tradeIds.Contains(l.TradeId)).ToList();
|
||||
}
|
||||
#endregion
|
||||
tradeCashPres.ForEach(x =>
|
||||
{
|
||||
var trade = trades.FirstOrDefault(y => y.id == x.TradeId);
|
||||
if (trade.ExerciseDate < valueDate)
|
||||
{
|
||||
return;//已到期交易不再观察;
|
||||
}
|
||||
var client = DataCacheProvider.GetClientDataSource().GetData(trade.ClientId);
|
||||
var currencyRate = new EodCurrencyRateService(UserInfo).GetCurrencyRate(trade.QuoteCurrency, trade.SettlementCurrency, x.ValueDate);
|
||||
var currencyRateTradeDate = new EodCurrencyRateService(UserInfo).GetCurrencyRate(trade.QuoteCurrency, trade.SettlementCurrency, trade.TradeDate.Value);
|
||||
var tradeCashDetails = DbContext.trade_cash_detail.Where(y => y.TradeCashPreId == x.id).ToList();
|
||||
var costFeeGet = 0.0;
|
||||
var costTradePriceGet = 0.0;
|
||||
var coupon = 0.0;
|
||||
var winloss = 0.0;
|
||||
tradeCashDetails.ForEach(y =>
|
||||
{
|
||||
if (y.TradeCashType == TradeCashTypeEnum.利息.ToString())
|
||||
{
|
||||
y.Amount = y.QuoteAmount * (PS.Config.Company == Configuration.CompanyEnum.中金 && client.BoundSide == BoundSideEnum.南向 ? currencyRateTradeDate : currencyRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
y.Amount = y.QuoteAmount * currencyRate;
|
||||
}
|
||||
|
||||
if (y.TradeCashType == TradeCashTypeEnum.开仓手续费.ToString())
|
||||
{
|
||||
costTradePriceGet = y.Amount ?? 0;
|
||||
}
|
||||
else if (y.TradeCashType == TradeCashTypeEnum.了结手续费.ToString())
|
||||
{
|
||||
costFeeGet = y.Amount ?? 0;
|
||||
}
|
||||
else if (y.TradeCashType == TradeCashTypeEnum.利息.ToString())
|
||||
{
|
||||
coupon = y.Amount ?? 0;
|
||||
}
|
||||
else if (y.TradeCashType == TradeCashTypeEnum.浮动收益.ToString())
|
||||
{
|
||||
winloss = y.Amount ?? 0;
|
||||
}
|
||||
});
|
||||
|
||||
var tradeCashSwap = DbContext.trade_cash_swap.FirstOrDefault(y => y.TradeCashPreId == x.id);
|
||||
tradeCashSwap.PayInitialAmount = -winloss;
|
||||
tradeCashSwap.PayAmount = -winloss;
|
||||
tradeCashSwap.GetExtraAmount = coupon;
|
||||
tradeCashSwap.GetCostFee = costFeeGet + costTradePriceGet;
|
||||
tradeCashSwap.GetAmount = costFeeGet + costTradePriceGet + coupon;
|
||||
|
||||
var tradeCash = new trade_cash()
|
||||
{
|
||||
OptId = x.OptId,
|
||||
OptName = x.OptName,
|
||||
OptDate = DateTime.Now,
|
||||
ExceciseType = x.ExceciseType,
|
||||
TradeType = x.TradeType,
|
||||
CallPut = x.CallPut,
|
||||
Notional = x.Notional,
|
||||
TradeAmount = x.TradeAmount,
|
||||
IsLastAction = x.IsLastAction,
|
||||
TradeId = x.TradeId,
|
||||
FinalPrice = x.FinalPrice,
|
||||
UnwindType = x.UnwindType,
|
||||
UnwindNotional = x.UnwindNotional,
|
||||
UnwindTradeAmount = x.UnwindTradeAmount,
|
||||
UnwindPercentRate = x.UnwindPercentRate,
|
||||
NotionalPercentRate = x.NotionalPercentRate,
|
||||
Number = x.Number,
|
||||
Amount = winloss + coupon + costFeeGet + costTradePriceGet,
|
||||
QuoteAmount = tradeCashDetails.Sum(y => y.QuoteAmount ?? 0),
|
||||
CurrencyRate = currencyRate,
|
||||
Action = x.Action,
|
||||
Status = x.Status,
|
||||
ValueDate = x.ValueDate,
|
||||
HappenedDate = x.HappenedDate,
|
||||
ValidState = "Valid",
|
||||
ExerciseWay = TradeCashExerciseWayEnum.到期行权
|
||||
};
|
||||
DbContext.trade_cash.Add(tradeCash);
|
||||
|
||||
x.IsFinished = true;
|
||||
|
||||
DbContext.SaveChanges();
|
||||
if (PS.Config.Company == Configuration.CompanyEnum.招证)
|
||||
{
|
||||
new BizLogicZhaoZheng().GenerateZhaoZhengDealNumber(trade, tradeCash);
|
||||
}
|
||||
if (PS.Config.Company == Configuration.CompanyEnum.物产中大)
|
||||
{
|
||||
new BizLogicWCZD().GenerateWCZDNumber(DbContext, trade, tradeCash.ValueDate, tradeCash.id);
|
||||
}
|
||||
tradeCashDetails.ForEach(y => y.TradeCashId = tradeCash.id);
|
||||
tradeCashSwap.TradeCashId = tradeCash.id;
|
||||
|
||||
//增加出入金记录
|
||||
new ClientCashinCashoutBLL(this).CloseTrade_ClientCashInCashOutSave(trade, tradeCash, tradeCash.ValueDate);
|
||||
});
|
||||
|
||||
//更新数据库
|
||||
DbContext.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user