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