272 lines
12 KiB
C#
272 lines
12 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|