Files
zszq-trs/YLErpDAL/Modules/TradeModule/OptionTradeKnockoutService.cs
T
2024-05-09 14:06:26 +08:00

335 lines
12 KiB
C#

using Microsoft.EntityFrameworkCore.Storage;
using Qdp.Foundation.Utilities;
using YLErp.Commons;
using YLErp.CustomizedBizLogic;
using YLErp.DBModels.Consts;
using YLErp.DBModels.Enums;
namespace YLErp.Modules.TradeModule
{
/// <summary>
/// 期权交易敲出操作服务
/// </summary>
public class OptionTradeKnockoutService : YLBaseService
{
public OptionTradeKnockoutService(YLBaseService baseService) : base(baseService)
{
}
/// <summary>
/// 保存敲出操作资金记录
/// </summary>
public OptionTradeKnockoutResult Execute(OtcTradeBase td, trade_accumulator_option tdAcc, OptionTradeKnockoutRequest req, IDbContextTransaction trans = null)
{
var innerTrans = trans == null;
try
{
if (innerTrans)
{
trans = DbContext.Database.BeginTransaction();
}
var result = InnerExecute(td, tdAcc, req);
//执行事务
if (innerTrans)
{
trans.Commit();
}
return result;
}
finally
{
if (innerTrans)
{
trans?.Dispose();
}
}
}
private OptionTradeKnockoutResult InnerExecute(OtcTradeBase td, trade_accumulator_option tdAcc, OptionTradeKnockoutRequest req)
{
const string tcAction = ClientCashInCashOut.系统操作_平仓费;
//trade action
var action = CheckAndAddAction(td, req, out var needResettle);
//client info
var client = DbContextFactory.GetClientDbContext(UserInfo).client.Where(n => n.id == td.ClientId)
.Select(n => new { n.id, n.Number, n.Name }).FirstOrDefault();
if (client == null)
{
throw new ServiceException($"{td.TradeType}'{td.TradeNumber}'找不到客户信息,客户id:{td.ClientId}");
}
var tradeCashs = DbContext.trade_cash.Where(t => t.TradeId == td.id && t.Action == "系统操作-平仓费" && t.UnwindType == "部分行权" && !t.IsDeleted && t.HappenedDate == req.ValueDate);
DbContext.trade_cash.RemoveRange(tradeCashs);
//trade cash
var tc = new trade_cash
{
TradeId = td.id,
ValueDate = req.ValueDate,
HappenedDate = req.ValueDate,
Action = tcAction,
IsLastAction = true,
ExerciseWay = TradeCashExerciseWayEnum.提前终止行权,
Amount = req.PaymentAmount,
ValidState = null,
ExceciseType = "现金",
TradeType = td.BuySell,
CallPut = td.CallPut,
Notional = td.Notional,
TradeAmount = td.TradeAmount,
UnwindPrice = Math.Abs(td.Notional != 0 ? req.PaymentAmount / td.Notional : 0),
UnwindPricePercentRate = Math.Abs(td.Notional != 0 && td.SpotPrice != null && td.SpotPrice != 0 ? req.PaymentAmount / td.Notional / td.SpotPrice.Value : 0),
OptId = UserId,
OptName = UserName,
OptDate = DateTime.Now,
ParentTradeId = 0,
ParentTradeCashId = 0,
Status = TradeCashStatusEnum.已执行,
FinalPrice = req.UnderlyingPrice,
BarrierPrice = tdAcc.KOBarrier,
UnwindType = null,
UnwindNotional = td.Notional,
UnwindTradeAmount = td.TradeAmount,
UnwindPercentRate = td.Notional / td.OriginalNotional,
NotionalPercentRate = td.Notional / td.OriginalNotional,
AdvanceMoney = 0,
IsDeleted = false,
SettleDate = req.ValueDate,
Comments = null,
ConfirmDate = DateTime.MinValue,
CurrencyRate = null,
ExtraAmount = null,
Strike = td.Strike,
SpotPrice = td.SpotPrice,
TradePremium = 0,
QuoteAmount = 0,
UnwindVol = null,
VolType = null,
Number = null
};
DbContext.trade_cash.Add(tc);
DbContext.SaveChanges();
if (PS.Config.SalesCommissionCalculation == "公式1")
{
//销售提成
new SalesModule.SalesCommissionDetailDataService(UserInfo).CalcuSalesCommissionDetail(tc);
}
if (PS.Config.Company == Configuration.CompanyEnum.招证)
{
new BizLogicZhaoZheng().GenerateZhaoZhengDealNumber(td, tc);
}
if (PS.Config.Company == Configuration.CompanyEnum.物产中大)
{
new BizLogicWCZD().GenerateWCZDNumber(DbContext, td, tc.ValueDate, tc.id);
}
//trade_cash_detail
var tcd = new trade_cash_detail
{
TradeId = tc.TradeId,
TradeCashId = tc.id,
Action = tc.Action,
Amount = tc.Amount,
ExtraAmount = tc.ExtraAmount,
SinglePrice = tc.UnwindPrice,
SinglePricePercentRate = tc.UnwindPricePercentRate,
ValueDate = tc.ValueDate,
OptId = tc.OptId,
OptName = tc.OptName,
OptDate = DateTime.Now
};
DbContext.trade_cash_detail.Add(tcd);
//client cash
var cashInOutRecord = new ClientCashInCashOut
{
Direction = "应收",
Number = UniqueTimeId.GetStr(),
ClientId = client.id,
ClientNumber = client.Number,
ClientName = client.Name,
Money = -tc.Amount,
HappenDate = req.ValueDate,
State = ClientCashInCashOut.已确认,
OptDate = tc.OptDate,
OptId = tc.OptId,
OptName = tc.OptName,
CreateDate = tc.OptDate,
CreatorId = tc.OptId,
CreatorName = tc.OptName,
TradeId = td.id,
TradeCashId = tc.id,
Action = tcAction,
TradeNumber = td.TradeNumber,
IsGroup = td.IsGroup
};
DbContext.ClientCashInCashOut.Add(cashInOutRecord);
//end
action.CashId = tc.id;
DbContext.SaveChanges();
return new OptionTradeKnockoutResult
{
NeedResettle = needResettle,
TradeCash = tc,
TradeAction = action
};
}
//检查已有的action并在数据库上下文中添加敲出action(此时action还没有cashid)
private TradeAction CheckAndAddAction(OtcTradeBase td, OptionTradeKnockoutRequest req, out bool needResettle)
{
var tradeId = td.id;
var sysDate = BLL.valuedateBLL.ValueDate;
var actions = DbContext.TradeAction.Where(n => n.TradeId == tradeId && n.IsValid).ToArray();
//存在未审核通过的操作
if (actions.Any(n => n.CheckFlag > 0 && n.CheckFlag < byte.MaxValue))
{
throw new ServiceException($"{td.TradeType}交易'{td.TradeNumber}'存在未审核通过的操作,执行敲出操作失败");
}
//在这里敲出了结已经被检查
if (actions.Any(n => n.ActionFlag == TradeActionHelper.Flag_FullClosed))
{
throw new ServiceException($"{td.TradeType}交易'{td.TradeNumber}'已完全了结,执行敲出操作失败");
}
//存在后续的资金记录(观察日资金或者了结资金)
if (!req.IgnoreLaterCashRecords && actions.Any(n => n.ValueDate > req.ValueDate && n.CashId > 0))
{
throw new ServiceException($"{td.TradeType}交易'{td.TradeNumber}'存在后续的资金记录,执行敲出操作失败");
}
needResettle = false;
//如果观察日存在转远期的操作行为,这里暂时不进行处理
//检查观察日是否已经存在资金记录,如果有的话说明曾进行过观察,此时需要进行无效处理
var tdObActions = actions.Where(n => n.ValueDate == sysDate && n.CashId > 0).ToArray();
if (tdObActions.Any())
{
var existsTradeCashs = new Lazy<IEnumerable<trade_cash>>(
() => DbContext.trade_cash.Where(t => t.TradeId == tradeId && t.ValueDate == req.ValueDate && t.ValidState != ConsGlobal.InValid && !t.IsDeleted));
var existsTradeCashDetails = new Lazy<IEnumerable<trade_cash_detail>>(
() => DbContext.trade_cash_detail.Where(t => t.TradeId == tradeId && t.ValueDate == req.ValueDate));
var existsClientCashs = new Lazy<IEnumerable<ClientCashInCashOut>>(
() => DbContext.ClientCashInCashOut.Where(t => t.ClientId == td.ClientId && t.TradeId == tradeId && t.HappenDate >= req.ValueDate && t.ValidState != ConsGlobal.InValid));
foreach (var a in tdObActions)
{
if ((a.ActionType == TradeActionType.ObCoupon || a.ActionType == TradeActionType.ObCash)
&& a.ActionFlag == TradeActionHelper.Flag_CashAtHit)
{
a.IsValid = false;
var tc = existsTradeCashs.Value.FirstOrDefault(n => n.id == a.CashId.Value);
if (tc != null)
{
needResettle = true;
tc.ValidState = ConsGlobal.InValid;
//client cash
var cc = existsClientCashs.Value.FirstOrDefault(n => n.TradeCashId == tc.id);
if (cc != null)
{
cc.ValidState = ConsGlobal.InValid;
}
//trade_cash_detail
var details = existsTradeCashDetails.Value.Where(n => n.TradeCashId == tc.id).ToArray();
DbContext.trade_cash_detail.RemoveRange(details);
}
}
}
}
return DbContext.TradeAction.Add(new TradeAction
{
TradeId = tradeId,
ValueDate = req.ValueDate,
ActionRemark = "敲出",
ActionFlag = TradeActionHelper.Flag_FullClosed,
ActionType = TradeActionType.KnockOut,
CashId = 0,
CreateTime = DateTime.Now,
IsValid = true,
OptId = UserId,
ActionData = null,
BackId = null,
CheckFlag = 0
}).Entity;
}
}
/// <summary>
/// 期权交易敲出执行请求
/// </summary>
public class OptionTradeKnockoutRequest
{
/// <summary>
/// 操作日期
/// </summary>
public DateTime ValueDate { get; set; }
/// <summary>
/// 标的价格
/// </summary>
public double UnderlyingPrice { get; set; }
/// <summary>
/// 支付金额
/// </summary>
public double PaymentAmount { get; set; }
/// <summary>
/// 是否忽略后续资金记录的检查
/// </summary>
public bool IgnoreLaterCashRecords { get; set; }
}
/// <summary>
/// 期权交易敲出执行结果
/// </summary>
public class OptionTradeKnockoutResult
{
/// <summary>
/// 是否需要重新收盘
/// </summary>
public bool NeedResettle { get; set; }
/// <summary>
///
/// </summary>
public TradeAction TradeAction { get; set; }
/// <summary>
///
/// </summary>
public trade_cash TradeCash { get; set; }
}
}