230 lines
10 KiB
C#
230 lines
10 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|