909 lines
39 KiB
C#
909 lines
39 KiB
C#
using Qdp.ComputeServiceV2.Data.CommonModels.TradeInfos;
|
|
using Qdp.Foundation.Utilities;
|
|
using Qdp.Pricing.Library.Common.Interfaces;
|
|
using Qdp.Pricing.Library.Options.Products.Asian;
|
|
using System.Data;
|
|
using System.Globalization;
|
|
using YLErp.BLL;
|
|
using YLErp.BLL.Eod;
|
|
using YLErp.Commons;
|
|
using YLErp.CustomizedBizLogic;
|
|
using YLErp.DBModels.Consts;
|
|
using YLErp.DBModels.Enums;
|
|
using YLErp.DBModels.Helpers;
|
|
using YLErp.Model.Enum;
|
|
using YLErp.Modules.CalculationModule;
|
|
using YLErp.Modules.ClientModule;
|
|
|
|
namespace YLErp.Modules.TradeModule.DealModule
|
|
{
|
|
/// <summary>
|
|
/// 交易了结处理服务
|
|
/// </summary>
|
|
public class OtcTradeCloseService : TradeCashService
|
|
{
|
|
public OtcTradeCloseService(OptUserInfo userInfo) : base(userInfo)
|
|
{
|
|
|
|
}
|
|
|
|
public OtcTradeCloseService(YLBaseService baseService) : base(baseService)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// 导入批量了结
|
|
/// </summary>
|
|
/// <param name="streamIn"></param>
|
|
/// <param name="totalNum">当前文件中的目标期权总条数</param>
|
|
/// <param name="successNum">成功入库的数量</param>
|
|
public void BatchCloseByImport(Stream streamIn, out int totalNum, out int successNum)
|
|
{
|
|
totalNum = 0;
|
|
successNum = 0;
|
|
|
|
var rowIndex = 0;
|
|
|
|
try
|
|
{
|
|
var ds = Office.ExcelHelper.ReadExcelAsDataSet(streamIn, new[] { 0 }, 0);
|
|
|
|
if (ds.Tables.Count < 1 || ds.Tables[0].Rows.Count < 2)
|
|
{
|
|
throw new ServiceException("读取导入数据失败:数据为空") { Tag = "111" };
|
|
}
|
|
|
|
var table = ds.Tables[0];
|
|
var reader = new DataRowReader(table);
|
|
|
|
totalNum = table.Rows.Count;
|
|
rowIndex = 1;
|
|
|
|
foreach (var row in table.Rows.Cast<DataRow>().Skip(1))
|
|
{
|
|
rowIndex++;
|
|
|
|
if (row.ItemArray.All(n => string.IsNullOrWhiteSpace(n?.ToString())))
|
|
{
|
|
totalNum--;
|
|
continue;
|
|
}
|
|
|
|
reader.SetDataRow(row);
|
|
|
|
var model = new TradeCloseRequestModel
|
|
{
|
|
ImportFlag = "批量了结导入",
|
|
SkipWorkflow = true,
|
|
TradeNumber = reader.GetString("交易编号", true),
|
|
CloseType = reader.GetString("了结方式", true),
|
|
CloseDate = reader.GetDate("了结日期", true).Value,
|
|
CloseTradeAmount = reader.GetDouble("了结数量"),
|
|
CloseTradeAmountRate = reader.GetPercent("了结数量比例"),
|
|
UnderlyingPrice = reader.GetDouble("了结标的价格", true).Value
|
|
};
|
|
|
|
if (model.CloseType == "平仓")
|
|
{
|
|
model.UnwindTotalFee = reader.GetDouble("平仓总额", "了结总额");
|
|
model.UnwindPrice = reader.GetDouble("平仓单价", "了结单价");
|
|
model.UnwindPriceRate = reader.GetPercent("平仓单价比例", "了结单价比例");
|
|
model.UnwindVolatility = reader.GetPercent("平仓波动率");
|
|
}
|
|
if (model.CloseType == "到期")
|
|
{
|
|
model.UnwindTotalFee = reader.GetDouble("平仓总额", "了结总额");
|
|
}
|
|
ExecuteClose(model);
|
|
successNum++;
|
|
}
|
|
}
|
|
catch (ServiceException se)
|
|
{
|
|
if (se.Tag != null)
|
|
{
|
|
throw;
|
|
}
|
|
|
|
throw new ServiceException($"已成功了结{successNum}条,第{rowIndex}行,{se.Message}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogFactory.GetLogger("批量了结导入").Error(ex);
|
|
throw new ServiceException($"已成功了结{successNum}条,第{rowIndex}行,发生错误:{ex.Message}", ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行了结(适用于交易导入、批量了结、api了结,如果用作其它用途需要注意IsApproval的赋值)
|
|
/// </summary>
|
|
public TradeCloseResult ExecuteClose(TradeCloseRequestModel req, bool isAveragePriceOverride = false)
|
|
{
|
|
if (req is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(req));
|
|
}
|
|
|
|
var dbTrade = DbContext.trade.FirstOrDefault(t => t.TradeNumber == req.TradeNumber && t.ValidState != "InValid");
|
|
if (dbTrade == null)
|
|
{
|
|
throw new ServiceException("交易数据 不存在,交易编号:" + req.TradeNumber);
|
|
}
|
|
|
|
CheckRequest(dbTrade, req);
|
|
if (dbTrade.Warning)
|
|
{
|
|
new TradeDalModule.TradeDalService(OptUser).RollbackToBeforeSettle(dbTrade, req.CloseDate);
|
|
}
|
|
CalcForClose(dbTrade, req, isAveragePriceOverride, out var countRatio, out var closeNotional);
|
|
|
|
var isPartialClose = Math.Abs(closeNotional - dbTrade.Notional) > 1e-4;
|
|
|
|
if (isPartialClose && (req.CloseType == "行权" || req.CloseType == "到期"))
|
|
{
|
|
throw new ServiceException("了结方式为'行权'或'到期'时,了结数量必须等于持仓数量,交易编号:" + dbTrade.TradeNumber);
|
|
}
|
|
|
|
#region---添加trade_cash---
|
|
|
|
var tradeCash = new trade_cash
|
|
{
|
|
ValidState = ConsGlobal.Valid,
|
|
OptId = OptUser.UserId,
|
|
OptName = OptUser.UserName,
|
|
OptDate = OptDate,
|
|
ExceciseType = "现金",
|
|
TradeType = dbTrade.BuySell,
|
|
CallPut = dbTrade.CallPut,
|
|
Strike = dbTrade.IsMoneynessOptionData ? dbTrade.Strike * (dbTrade.SpotPrice ?? 0.0) : dbTrade.Strike,
|
|
Notional = dbTrade.Notional,
|
|
Amount = req.UnwindTotalFee ?? 0,
|
|
ExtraAmount = req.ExtraAmount,
|
|
TradeId = dbTrade.id,
|
|
FinalPrice = req.UnderlyingAvgPrice ?? req.UnderlyingPrice,
|
|
VolType = "交易",
|
|
TradeAmount = dbTrade.TradeAmount,
|
|
Status = TradeCashStatusEnum.已执行,
|
|
ValueDate = req.CloseDate,
|
|
Comments = "导入了结数据",
|
|
SpotPrice = req.UnderlyingPrice,
|
|
ExerciseWay = "",
|
|
UnwindType = "",//部分平仓/行权、全部平仓/行权/到期
|
|
Action = (req.CloseType == "行权" || req.CloseType == "到期") ? ClientCashInCashOut.系统操作_行权费 : ClientCashInCashOut.系统操作_平仓费
|
|
};
|
|
|
|
DbContext.trade_cash.Add(tradeCash);
|
|
DbContext.SaveChanges();
|
|
|
|
tradeCash.UnwindNotional = closeNotional;
|
|
tradeCash.UnwindTradeAmount = req.CloseTradeAmount;
|
|
tradeCash.UnwindPercentRate = req.CloseTradeAmountRate;
|
|
tradeCash.NotionalPercentRate = req.CloseTradeAmountRate;
|
|
tradeCash.UnwindPrice = req.UnwindPrice;
|
|
tradeCash.UnwindPricePercentRate = req.UnwindPriceRate;
|
|
|
|
if (req.CloseType == "平仓")
|
|
{
|
|
tradeCash.UnwindVol = req.UnwindVolatility;
|
|
tradeCash.UnwindType = isPartialClose ? "部分平仓" : "全部平仓";
|
|
tradeCash.Action = ClientCashInCashOut.系统操作_平仓费;
|
|
tradeCash.ExerciseWay = TradeCashExerciseWayEnum.提前终止行权;
|
|
tradeCash.IsLastAction = !isPartialClose;
|
|
}
|
|
else if (req.CloseType == "提前行权")
|
|
{
|
|
if (!PS.Config.TradeElement.IsSingleExecutionTemplate && dbTrade.ExerciseMode != "European")
|
|
{
|
|
tradeCash.ExerciseWay = TradeCashExerciseWayEnum.提前终止行权;
|
|
}
|
|
else
|
|
{
|
|
tradeCash.ExerciseWay = TradeCashExerciseWayEnum.到期行权;
|
|
}
|
|
|
|
if (isPartialClose)
|
|
{
|
|
tradeCash.UnwindType = "部分行权";
|
|
tradeCash.Action = ClientCashInCashOut.系统操作_平仓费;
|
|
}
|
|
else
|
|
{
|
|
tradeCash.UnwindType = "全部行权";
|
|
tradeCash.Action = ClientCashInCashOut.系统操作_行权费;
|
|
}
|
|
|
|
tradeCash.IsLastAction = !isPartialClose;
|
|
}
|
|
else
|
|
{
|
|
tradeCash.Action = ClientCashInCashOut.系统操作_行权费;
|
|
tradeCash.ExerciseWay = TradeCashExerciseWayEnum.到期行权;
|
|
tradeCash.UnwindType = req.CloseType == "到期" ? "到期" : "全部行权";
|
|
tradeCash.IsLastAction = true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
//审批流程
|
|
if (!req.SkipWorkflow && HasTradeProcess())
|
|
{
|
|
var isUnwind = tradeCash.Action.Contains("平仓");
|
|
|
|
tradeCash.ValidState = ConsGlobal.InValid;
|
|
|
|
dbTrade.TradeStatus = isUnwind ? ConsTrade.平仓待复核 : ConsTrade.行权待复核;
|
|
|
|
dbTrade.CheckStatus = Convert.ToInt32(TradeCheckEnum.StatusOfOld);
|
|
|
|
//检查是否有交易审批流程
|
|
if (valuedateBLL.SystemDate.CloseReApprove == 1)
|
|
{
|
|
// 如果有审批组
|
|
InitTradeProcessOrder(dbTrade, UserId);
|
|
}
|
|
|
|
AddTradeOperationHistoryAndSetParentTradeInfo(false, dbTrade, optType: isUnwind ? "平仓审核提交" : "行权审核提交", comments: req.ImportFlag);
|
|
}
|
|
else
|
|
{
|
|
//SaveTradeCashDetail(2021-11-23:如果进入审批流程则在审批后才增加tradecashDetail)
|
|
SaveTradeCashDetail(tradeCash, false);
|
|
|
|
#region---存入ClientCashInCashOut---
|
|
|
|
var cl = ClientDataQueryService.GetClient(dbTrade.ClientId, true);
|
|
var ee = new ClientCashInCashOut
|
|
{
|
|
Direction = "应收",
|
|
Number = UniqueTimeId.GetStr(),
|
|
ClientId = cl.id,
|
|
ClientNumber = cl.Number,
|
|
ClientName = cl.Name,
|
|
Money = tradeCash.Amount * -1,
|
|
HappenDate = req.CloseDate,
|
|
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 = dbTrade.TradeNumber,
|
|
IsGroup = dbTrade.IsGroup
|
|
};
|
|
DbContext.ClientCashInCashOut.Add(ee);
|
|
|
|
#endregion
|
|
|
|
#region---更新trade---
|
|
|
|
dbTrade.UnWindNotional = tradeCash.UnwindNotional;
|
|
|
|
if (req.CloseType == "平仓" || req.CloseType == "提前行权")
|
|
{
|
|
dbTrade.Notional -= tradeCash.UnwindNotional ?? 0;
|
|
|
|
if (isPartialClose)
|
|
{
|
|
dbTrade.HasPartialUnWind = 1;
|
|
}
|
|
else
|
|
{
|
|
dbTrade.Notional = 0;
|
|
|
|
if (req.CloseType == "平仓")
|
|
{
|
|
dbTrade.TradeStatus = ConsTrade.已平仓;
|
|
}
|
|
else
|
|
{
|
|
dbTrade.TradeStatus = ConsTrade.已执行;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dbTrade.Notional = 0;
|
|
dbTrade.TradeStatus = req.CloseType == "到期" ? ConsTrade.已到期 : ConsTrade.已执行;
|
|
}
|
|
|
|
dbTrade.UnWindDate = req.CloseDate;
|
|
dbTrade.TradeAmount = dbTrade.Notional / countRatio;
|
|
if (dbTrade.TradeType == "远期" && PS.Config.ErpElement.ForwardTradePriceModel == 0 && !string.IsNullOrWhiteSpace(dbTrade.BasisUnderlyingCode))
|
|
{
|
|
dbTrade.StockEqvNotional -= TradeHelper.GetStockEqvNotional(tradeCash.UnwindNotional / dbTrade.OriginalNotional * dbTrade.OriginalStockEqvNotional, 1, 1);
|
|
}
|
|
else
|
|
{
|
|
dbTrade.StockEqvNotional = TradeHelper.GetStockEqvNotional(dbTrade.Notional * (dbTrade.SpotPrice ?? 0.0), dbTrade.ParticipationRate, dbTrade.AnnualizeFactor);
|
|
}
|
|
dbTrade.FinalPrice = tradeCash.FinalPrice;
|
|
|
|
#endregion
|
|
|
|
//删除E/Bod数据
|
|
RemoveEodTradeAndFutureInfo(false, dbTrade.id, tradeCash.ValueDate);
|
|
|
|
AddTradeOperationHistoryAndSetParentTradeInfo(false, dbTrade, optType: "批量了结-" + req.CloseType, comments: req.ImportFlag);
|
|
|
|
//当前这个方法只会被导入了结交易和api了结调用,所以直接记录提成就行;
|
|
if (PS.Config.SalesCommissionCalculation == "公式1")
|
|
{
|
|
//销售提成
|
|
new SalesModule.SalesCommissionDetailDataService(this).CalcuSalesCommissionDetail(tradeCash);
|
|
}
|
|
if (PS.Config.Company == Configuration.CompanyEnum.招证)
|
|
{
|
|
new BizLogicZhaoZheng().GenerateZhaoZhengDealNumber(dbTrade, tradeCash);
|
|
}
|
|
if (PS.Config.Company == Configuration.CompanyEnum.物产中大)
|
|
{
|
|
new BizLogicWCZD().GenerateWCZDNumber(DbContext, dbTrade, tradeCash.ValueDate, tradeCash.id);
|
|
}
|
|
}
|
|
|
|
dbTrade.OptId = OptUser.UserId;
|
|
dbTrade.OptName = OptUser.UserName;
|
|
dbTrade.OptDate = OptDate;
|
|
dbTrade.IsApproval = dbTrade.ParentTradeId > 0 && dbTrade.IsGroup == 2;
|
|
|
|
//保存更改
|
|
DbContext.SaveChanges();
|
|
|
|
return new TradeCloseResult
|
|
{
|
|
Trade = dbTrade,
|
|
TradeCash = tradeCash
|
|
};
|
|
}
|
|
|
|
//检查请求数据
|
|
private void CheckRequest(trade td, TradeCloseRequestModel req)
|
|
{
|
|
if (td.TradeType == "结构化交易")
|
|
{
|
|
throw new ServiceException("结构化交易主交易不允许了结操作,交易编号:" + req.TradeNumber);
|
|
}
|
|
|
|
if (td.TradeStatus != ConsTrade.确认成交 && td.TradeStatus != ConsTrade.提前终止拒绝)
|
|
{
|
|
throw new ServiceException("只有确认成交或者提前终止拒绝的交易可以导入,交易编号:" + req.TradeNumber);
|
|
}
|
|
|
|
//----------------------------------------------
|
|
// 了结方式
|
|
//----------------------------------------------
|
|
|
|
switch (req.CloseType)
|
|
{
|
|
case "平仓":
|
|
break;
|
|
case "行权":
|
|
case "提前行权":
|
|
case "到期":
|
|
if (td.TradeType == "自定义交易")
|
|
{
|
|
throw new ServiceException("了结方式 填写错误,结构类型为'自定义交易'时仅支持'平仓',交易编号:" + td.TradeNumber);
|
|
}
|
|
break;
|
|
default:
|
|
if (string.IsNullOrWhiteSpace(req.CloseType))
|
|
{
|
|
throw new ServiceException("了结方式 必须填写,交易编号:" + td.TradeNumber);
|
|
}
|
|
else
|
|
{
|
|
throw new ServiceException("了结方式 填写错误,不支持:" + req.CloseType + ",交易编号:" + td.TradeNumber);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------
|
|
// 了结日期
|
|
//----------------------------------------------
|
|
|
|
if (req.CloseDate > SystemValueDate)
|
|
{
|
|
throw new ServiceException("了结日期 不能大于 系统日期,交易编号:" + td.TradeNumber);
|
|
}
|
|
|
|
if (req.CloseDate < td.TradeDate)
|
|
{
|
|
throw new ServiceException("了结日期不能早于交易日期,交易编号:" + td.TradeNumber);
|
|
}
|
|
|
|
if (req.CloseDate > td.ExerciseDate)
|
|
{
|
|
throw new ServiceException("了结日期不能大于到期日期,交易编号:" + td.TradeNumber);
|
|
}
|
|
|
|
if (td.ExerciseMode != "American" && req.CloseType == "提前行权")
|
|
{
|
|
throw new ServiceException("只有美式期权的了结方式可以填写'提前行权',交易编号:" + td.TradeNumber);
|
|
}
|
|
|
|
if ((req.CloseType == "到期" || req.CloseType == "行权") && req.CloseDate != td.ExerciseDate)
|
|
{
|
|
throw new ServiceException("了结日期应该为交易的到期日期,交易编号:" + td.TradeNumber);
|
|
}
|
|
|
|
var maxDate = DbContext.trade_cash.Where(n => n.TradeId == td.id && n.ValidState != ConsGlobal.InValid && !n.IsDeleted)
|
|
.Max(n => (DateTime?)n.ValueDate);
|
|
if (maxDate.HasValue && maxDate.Value > req.CloseDate)
|
|
{
|
|
throw new ServiceException($"了结日期 填写错误,因为{maxDate:yyyy-MM-dd}已存在了结操作,必须大于等于此日期,交易编号:{td.TradeNumber}");
|
|
}
|
|
|
|
|
|
//----------------------------------------------
|
|
// 了结数量
|
|
//----------------------------------------------
|
|
|
|
if ((req.CloseTradeAmount ?? 0) <= 0 && td.IsUsePremiumRate == false)
|
|
{
|
|
throw new ServiceException("请填写了结数量,交易编号:" + td.TradeNumber);
|
|
}
|
|
|
|
if ((req.CloseTradeAmountRate ?? 0) <= 0 && td.IsUsePremiumRate == true)
|
|
{
|
|
throw new ServiceException("请填写了结数量比例,交易编号:" + td.TradeNumber);
|
|
}
|
|
//----------------------------------------------
|
|
// 了结金额
|
|
//----------------------------------------------
|
|
|
|
if (req.CloseType == "平仓")
|
|
{
|
|
if (td.IsUsePremiumRate == true)
|
|
{
|
|
if (!req.UnwindPriceRate.HasValue && !req.UnwindTotalFee.HasValue)
|
|
{
|
|
throw new ServiceException("名义本金成交方式时平仓总额和平仓单价比例至少有一个不能为空,交易编号:" + td.TradeNumber);
|
|
}
|
|
}
|
|
else if (!req.UnwindPrice.HasValue && !req.UnwindTotalFee.HasValue)
|
|
{
|
|
throw new ServiceException("数量成交方式的交易了结单价和了结总额至少有一个不能为空,交易编号:" + td.TradeNumber);
|
|
}
|
|
|
|
//用户页面操作模式下平仓波动率不能为空
|
|
if (!req.UnwindVolatility.HasValue && string.IsNullOrEmpty(req.ImportFlag) && td.TradeType != "自定义交易")
|
|
{
|
|
throw new ServiceException("平仓波动率不能为空,交易编号:" + td.TradeNumber);
|
|
}
|
|
}
|
|
}
|
|
|
|
//互算加计算了结收益
|
|
private void CalcForClose(trade td, TradeCloseRequestModel req, bool isAveragePriceOverride, out int countRatio, out double closeNotional)
|
|
{
|
|
//-------------------------------------
|
|
// 了结数量和了结数量比例互算
|
|
//-------------------------------------
|
|
|
|
closeNotional = 0;
|
|
countRatio = UnderlyingDataProvider.GetCountRatio(td.UnderlyingCode);
|
|
|
|
|
|
if (td.IsUsePremiumRate == true || req.CloseTradeAmount == null)
|
|
{
|
|
closeNotional = (td.OriginalNotional * req.CloseTradeAmountRate) ?? 0;
|
|
req.CloseTradeAmount = closeNotional / countRatio;
|
|
}
|
|
var diff = req.CloseTradeAmount.Value - td.TradeAmount;
|
|
//比较了结数量和持仓数量
|
|
if (diff > 1e-4)
|
|
{
|
|
throw new ServiceException("了结数量超过了持仓数量,交易编号:" + td.TradeNumber);
|
|
}
|
|
else if (Math.Abs(diff) < 1e-4)
|
|
{
|
|
req.CloseTradeAmount = td.TradeAmount;
|
|
}
|
|
|
|
if (!td.IsUsePremiumRate.GetValueOrDefault())
|
|
{
|
|
closeNotional = (req.CloseTradeAmount ?? 0) * countRatio;
|
|
req.CloseTradeAmountRate = closeNotional / td.OriginalNotional;
|
|
}
|
|
|
|
//-------------------------------------
|
|
// 了结单价和了结总额互算
|
|
//-------------------------------------
|
|
|
|
if (req.CloseType == "平仓")
|
|
{
|
|
var StockEqvNotional = td.OriginalStockEqvNotional * req.CloseTradeAmountRate;
|
|
|
|
if (req.UnwindTotalFee.HasValue)
|
|
{
|
|
req.UnwindTotalFee = (req.UnwindTotalFee ?? 0) * (valuedateBLL.SystemDate.UnwindAmountAngle == 1 ? 1 : EodOperationBase.GetSign(td.BuySell));
|
|
|
|
req.UnwindPriceRate = TradeHelper.GetPremiumRateByTradePrice(req.UnwindTotalFee, StockEqvNotional, td.ParticipationRate, td.PrincipalSum(), td.AnnualizeFactor, td.BuySell, td.TradeType, false);
|
|
req.UnwindPrice = TradeHelper.GetTradeSinglePriceByTradePrice(req.UnwindTotalFee, closeNotional, td.PrincipalSum(), td.BuySell, td.TradeType, false);
|
|
|
|
req.UnwindPriceRate = (req.UnwindPriceRate ?? 0) * (td.TradeType == "远期" || ConsTrade.HasMinusValueOptions.Contains(td.TradeType) ? 1 : EodOperationBase.GetSign(td.BuySell));
|
|
req.UnwindPrice = (req.UnwindPrice ?? 0) * (td.TradeType == "远期" || ConsTrade.HasMinusValueOptions.Contains(td.TradeType) ? 1 : EodOperationBase.GetSign(td.BuySell));
|
|
}
|
|
else
|
|
{
|
|
bool ActualHasMinusValueOptions = valuedateBLL.SystemDate.UnwindSinglePriceAngle == 1 ? ConsTrade.HasMinusValueOptions.Contains(td.TradeType) : !ConsTrade.HasMinusValueOptions.Contains(td.TradeType);
|
|
int UnwindpriceAndMoneyIsAgreement = td.BuySell == "买入" || valuedateBLL.SystemDate.UnwindSinglePriceAngle == 1 || ConsTrade.HasMinusValueOptions.Contains(td.TradeType) || td.TradeType == "远期" ? 1 : -1;
|
|
if (td.IsUsePremiumRate == true)
|
|
{
|
|
req.UnwindPriceRate = (req.UnwindPriceRate ?? 0) * (td.TradeType == "远期" || ActualHasMinusValueOptions ? 1 : EodOperationBase.GetSign(td.BuySell));
|
|
req.UnwindPrice = TradeHelper.GetTradeSinglePriceByPremiumRate(req.UnwindPriceRate, td.SpotPrice);
|
|
req.UnwindTotalFee = TradeHelper.GetTradePriceByPremiumRate(req.UnwindPriceRate * UnwindpriceAndMoneyIsAgreement, StockEqvNotional, td.ParticipationRate, td.PrincipalSum(), td.AnnualizeFactor, td.BuySell, td.TradeType, false) * UnwindpriceAndMoneyIsAgreement;
|
|
}
|
|
else
|
|
{
|
|
req.UnwindPrice = (req.UnwindPrice ?? 0) * (td.TradeType == "远期" || ActualHasMinusValueOptions ? 1 : EodOperationBase.GetSign(td.BuySell));
|
|
req.UnwindPriceRate = TradeHelper.GetPremiumRateByTradeSinglePrice(req.UnwindPrice, td.SpotPrice);
|
|
req.UnwindTotalFee = TradeHelper.GetTradePriceBySinglePrice(req.UnwindPrice * UnwindpriceAndMoneyIsAgreement, closeNotional, td.PrincipalSum(), td.BuySell, td.TradeType, false) * UnwindpriceAndMoneyIsAgreement;
|
|
}
|
|
req.UnwindTotalFee = (req.UnwindTotalFee ?? 0) * (td.TradeType == "远期" || ConsTrade.HasMinusValueOptions.Contains(td.TradeType) ? 1 : EodOperationBase.GetSign(td.BuySell));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tradeBLL.SetFieldsByTradeType(td);
|
|
|
|
if (td.TradeType == "亚式期权" && req.UnderlyingAvgPrice.HasValue)
|
|
{
|
|
var strike = td.Strike ?? 0;
|
|
var finalPrice = req.UnderlyingPrice;
|
|
if (td.trade_asian_option.StrikeType == "Floating")
|
|
{
|
|
strike = req.UnderlyingAvgPrice.Value;
|
|
}
|
|
else
|
|
{
|
|
finalPrice = req.UnderlyingAvgPrice.Value;
|
|
}
|
|
|
|
req.UnwindPrice = 0;
|
|
|
|
if (td.OptionType == "看涨")
|
|
{
|
|
if (finalPrice > strike)
|
|
{
|
|
req.UnwindPrice = finalPrice - strike;
|
|
}
|
|
}
|
|
else if (td.OptionType == "看跌")
|
|
{
|
|
if (finalPrice < strike)
|
|
{
|
|
req.UnwindPrice = strike - finalPrice;
|
|
}
|
|
}
|
|
|
|
if (td.BuySell != "买入" && req.UnwindPrice > 0)
|
|
{
|
|
req.UnwindPrice = -req.UnwindPrice;
|
|
}
|
|
if (!req.UnwindTotalFee.HasValue)
|
|
{
|
|
if (td.IsUsePremiumRate == true)
|
|
{
|
|
req.UnwindTotalFee = TradeHelper.GetTradePriceBySinglePrice(req.UnwindPrice, td.OriginalNotional * req.CloseTradeAmountRate, td.PrincipalSum(), td.BuySell, td.TradeType, false);
|
|
}
|
|
else
|
|
{
|
|
req.UnwindTotalFee = TradeHelper.GetTradePriceBySinglePrice(req.UnwindPrice, closeNotional, td.PrincipalSum(), td.BuySell, td.TradeType, false);
|
|
}
|
|
}
|
|
|
|
req.UnwindPriceRate = TradeHelper.GetPremiumRateByTradeSinglePrice(req.UnwindPrice, td.SpotPrice);
|
|
}
|
|
else if (td.TradeType == "Risky期权")
|
|
{
|
|
var finalPrice = req.UnderlyingPrice;
|
|
var UnwindPrice = 0d;
|
|
var UnwindTotalFee = 0d;
|
|
var QdpTrades = GetToQdpOptionRisk(td, countRatio);
|
|
foreach (var item in QdpTrades)
|
|
{
|
|
if (item != null && item.Instrument != null && item.Instrument is IOption optitem)
|
|
{
|
|
var cashFlows = optitem.GetPayoff(new double[] { finalPrice });
|
|
if (!(cashFlows != null && (cashFlows[0].PaymentAmount == 0 || double.IsNaN(cashFlows[0].PaymentAmount))))
|
|
{
|
|
var perAmount = cashFlows[0].PaymentAmount / td.Notional;
|
|
UnwindPrice += perAmount;
|
|
UnwindTotalFee += OtcFormatHelper.FormatValue(cashFlows[0].PaymentAmount, 2);
|
|
}
|
|
}
|
|
}
|
|
req.UnwindPrice = UnwindPrice;
|
|
//risky保底是一个总的值
|
|
var amount = TradeHelper.GetAmountByPaymentAmount(0, td.PrincipalSum(), td.BuySell);
|
|
UnwindTotalFee += OtcFormatHelper.FormatValue(amount, 2);
|
|
|
|
if (req.UnwindTotalFee != null)
|
|
{
|
|
req.ExtraAmount = req.UnwindTotalFee - UnwindTotalFee;
|
|
}
|
|
else
|
|
{
|
|
req.UnwindTotalFee = UnwindTotalFee;
|
|
}
|
|
req.UnwindPriceRate = TradeHelper.GetPremiumRateByTradeSinglePrice(req.UnwindPrice, td.SpotPrice);
|
|
}
|
|
else
|
|
{
|
|
var finalPrice = req.UnderlyingPrice;
|
|
var QdpTrade = TradeCalcHelper.GetQdpTrade(td);
|
|
if (QdpTrade != null && QdpTrade.Instrument != null && QdpTrade.Instrument is IOption opt)
|
|
{
|
|
if (td.TradeType == "亚式期权" && td.trade_asian_option != null)
|
|
{
|
|
if (isAveragePriceOverride && finalPrice > 0)
|
|
{
|
|
req.UnderlyingAvgPrice = finalPrice;
|
|
}
|
|
else
|
|
{
|
|
var asianOpt = opt as AsianOption;
|
|
if (td.trade_asian_option.StrikeType != "Floating")
|
|
{
|
|
//分段式亚式期权,行权时的默认标的价格需要通过QDP计算得出,而不是标的现价,再根据该价格算出行权收益
|
|
req.UnderlyingAvgPrice = finalPrice = asianOpt.FinalPrice();
|
|
}
|
|
else
|
|
{
|
|
req.UnderlyingAvgPrice = asianOpt.Strike;
|
|
}
|
|
}
|
|
}
|
|
|
|
var cashFlows = opt.GetPayoff(new double[] { finalPrice });
|
|
var perAmount = cashFlows[0].PaymentAmount / td.Notional;
|
|
req.UnwindPrice = perAmount;
|
|
}
|
|
if (req.UnwindTotalFee != null)
|
|
{
|
|
if (td.IsUsePremiumRate == true)
|
|
{
|
|
var UnwindTotalFee = TradeHelper.GetTradePriceBySinglePrice(req.UnwindPrice, td.OriginalNotional * req.CloseTradeAmountRate, td.PrincipalSum(), td.BuySell, td.TradeType, false);
|
|
req.ExtraAmount = req.UnwindTotalFee - UnwindTotalFee;
|
|
}
|
|
else
|
|
{
|
|
var UnwindTotalFee = TradeHelper.GetTradePriceBySinglePrice(req.UnwindPrice, closeNotional, td.PrincipalSum(), td.BuySell, td.TradeType, false);
|
|
|
|
req.ExtraAmount = req.UnwindTotalFee - UnwindTotalFee;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (td.IsUsePremiumRate == true)
|
|
{
|
|
req.UnwindTotalFee = TradeHelper.GetTradePriceBySinglePrice(req.UnwindPrice, td.OriginalNotional * req.CloseTradeAmountRate, td.PrincipalSum(), td.BuySell, td.TradeType, false);
|
|
}
|
|
else
|
|
{
|
|
req.UnwindTotalFee = TradeHelper.GetTradePriceBySinglePrice(req.UnwindPrice, closeNotional, td.PrincipalSum(), td.BuySell, td.TradeType, false);
|
|
}
|
|
}
|
|
req.UnwindPriceRate = TradeHelper.GetPremiumRateByTradeSinglePrice(req.UnwindPrice, td.SpotPrice);
|
|
}
|
|
}
|
|
|
|
if (td.TradeType != "亚式期权")
|
|
{
|
|
req.UnderlyingAvgPrice = null;
|
|
}
|
|
}
|
|
|
|
public static List<TradeBase> GetToQdpOptionRisk(trade trade, int countRatio)
|
|
{
|
|
var options = new List<TradeBase>();
|
|
var tradeclone = trade.Clone();
|
|
tradeclone.TradeAmount = tradeclone.TradeAmount = TradeCalcHelper.GetTradeAmountV(trade, trade.TradeAmount, 1);
|
|
tradeclone.Notional = tradeclone.Notional = TradeCalcHelper.GetTradeAmountV(trade, trade.Notional, countRatio);
|
|
|
|
var td1 = tradeclone.Clone();
|
|
td1.Strike = trade.trade_risky_option.Strike1;
|
|
td1.ParticipationRate = trade.trade_risky_option.ParticipationRate1;
|
|
td1.TradeAmount = TradeCalcHelper.GetTradeAmount(td1, td1.TradeAmount, 1);
|
|
td1.Notional = TradeCalcHelper.GetTradeAmount(td1, td1.Notional, countRatio);
|
|
td1.OptionType = "看跌";
|
|
td1.BuySell = trade.BuySell == "买入" ? "卖出" : "买入";
|
|
var option1 = TradeCalcHelper.GetQdpTrade(td1);
|
|
if (option1 != null)
|
|
{
|
|
options.Add(option1);
|
|
}
|
|
|
|
var td2 = tradeclone.Clone();
|
|
td2.Strike = trade.trade_risky_option.Strike2;
|
|
td2.ParticipationRate = trade.trade_risky_option.ParticipationRate2;
|
|
td2.TradeAmount = TradeCalcHelper.GetTradeAmount(td2, td2.TradeAmount, 1);
|
|
td2.Notional = TradeCalcHelper.GetTradeAmount(td2, td2.Notional, countRatio);
|
|
|
|
var option2 = TradeCalcHelper.GetQdpTrade(td2);
|
|
if (option2 != null)
|
|
{
|
|
options.Add(option2);
|
|
}
|
|
|
|
var td3 = tradeclone.Clone();
|
|
td3.Strike = trade.trade_risky_option.Strike3;
|
|
//decimal 为了解决精度问题: 0.2-0.3=0.0999999999
|
|
var participationRate3 = (decimal)trade.trade_risky_option.ParticipationRate2 - (decimal)trade.trade_risky_option.ParticipationRate3;
|
|
td3.ParticipationRate = (double?)Math.Abs(participationRate3);
|
|
td3.TradeAmount = TradeCalcHelper.GetTradeAmount(td3, td3.TradeAmount, 1);
|
|
td3.Notional = TradeCalcHelper.GetTradeAmount(td3, td3.Notional, countRatio);
|
|
if (participationRate3 < 0)
|
|
{
|
|
td3.BuySell = trade.BuySell == "买入" ? "卖出" : "买入";
|
|
}
|
|
|
|
var option3 = TradeCalcHelper.GetQdpTrade(td3);
|
|
if (option3 != null)
|
|
{
|
|
options.Add(option3);
|
|
}
|
|
|
|
return options;
|
|
}
|
|
|
|
#region----DataRowReader----
|
|
|
|
class DataRowReader
|
|
{
|
|
DataRow _row;
|
|
|
|
readonly Dictionary<string, int> _colMap;
|
|
|
|
public DataRowReader(DataTable table)
|
|
{
|
|
var colCount = table.Columns.Count;
|
|
|
|
_colMap = new Dictionary<string, int>(colCount, StringComparer.OrdinalIgnoreCase);
|
|
|
|
var row = table.Rows[0];
|
|
|
|
for (var index = 0; index < colCount; index++)
|
|
{
|
|
var col = row[index]?.ToString()?.Trim();
|
|
if (string.IsNullOrEmpty(col))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
_colMap[col.Replace("%", "")] = index;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置datarow
|
|
/// </summary>
|
|
public void SetDataRow(DataRow row)
|
|
{
|
|
_row = row;
|
|
}
|
|
|
|
public string GetString(string fieldName, bool required = false)
|
|
{
|
|
var str = _colMap.TryGetValue(fieldName, out var colIndex) ? _row[colIndex]?.ToString()?.Trim() : null;
|
|
|
|
if (required && string.IsNullOrEmpty(str))
|
|
{
|
|
throw new ServiceException($"{fieldName} 必须填写");
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
public double? GetDouble(string fieldName, bool required = false)
|
|
{
|
|
var str = GetString(fieldName, required);
|
|
|
|
if (!required && string.IsNullOrEmpty(str))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return double.TryParse(str, out var num) ? num : throw new ServiceException($"{fieldName} 填写错误:{str}");
|
|
}
|
|
|
|
//为了兼容模板修改导致的字段名称改变问题
|
|
public double? GetDouble(string fieldName, string fieldName2, bool required = false)
|
|
{
|
|
var str = GetString(fieldName, false) ?? GetString(fieldName2, false);
|
|
|
|
if (string.IsNullOrEmpty(str))
|
|
{
|
|
return required ? throw new ServiceException($"{fieldName} 必须填写") : (double?)null;
|
|
}
|
|
return double.TryParse(str, out var num) ? num : throw new ServiceException($"{fieldName} 填写错误:{str}");
|
|
}
|
|
|
|
public double? GetPercent(string fieldName, bool required = false)
|
|
{
|
|
var str = GetString(fieldName, required);
|
|
|
|
if (!required && string.IsNullOrEmpty(str))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var percent = str.EndsWith("%");
|
|
if (percent)
|
|
{
|
|
str = str.TrimEnd('%');
|
|
}
|
|
|
|
return double.TryParse(str, out var num) ? (percent ? num / 100 : num) : throw new ServiceException($"{fieldName} 填写错误:{str}");
|
|
}
|
|
|
|
//为了兼容模板修改导致的字段名称改变问题
|
|
public double? GetPercent(string fieldName, string fieldName2, bool required = false)
|
|
{
|
|
var str = GetString(fieldName, false) ?? GetString(fieldName2, false);
|
|
|
|
if (string.IsNullOrEmpty(str))
|
|
{
|
|
return required ? throw new ServiceException($"{fieldName} 必须填写") : (double?)null;
|
|
}
|
|
|
|
var percent = str.EndsWith("%");
|
|
if (percent)
|
|
{
|
|
str = str.TrimEnd('%');
|
|
}
|
|
|
|
return double.TryParse(str, out var num) ? (percent ? num / 100 : num) : throw new ServiceException($"{fieldName} 填写错误:{str}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取日期(不包括时间)
|
|
/// </summary>
|
|
public DateTime? GetDate(string fieldName, bool required = false)
|
|
{
|
|
var str = GetString(fieldName, required);
|
|
|
|
if (!required && string.IsNullOrEmpty(str))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (str.Length == 8)
|
|
{
|
|
return DateTime.TryParseExact(str, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt2) ? dt2 : throw new ServiceException($"{fieldName} 填写错误:{str}");
|
|
}
|
|
|
|
return DateTime.TryParse(str, out var dt) ? dt.Date : throw new ServiceException($"{fieldName} 填写错误:{str}");
|
|
}
|
|
|
|
public int? GetInt32(string fieldName, bool required = false)
|
|
{
|
|
var str = GetString(fieldName, required);
|
|
|
|
if (!required && string.IsNullOrEmpty(str))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return int.TryParse(str, out var num) ? num : throw new ServiceException($"{fieldName} 填写错误:{str}");
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// 交易了结结果
|
|
/// </summary>
|
|
public class TradeCloseResult
|
|
{
|
|
public trade Trade { get; set; }
|
|
|
|
public trade_cash TradeCash { get; set; }
|
|
}
|
|
}
|