1636 lines
62 KiB
C#
1636 lines
62 KiB
C#
using FluentFTP.Helpers;
|
|
using Microsoft.EntityFrameworkCore.Storage;
|
|
using NPOI.SS.Formula.Functions;
|
|
using Org.BouncyCastle.Ocsp;
|
|
using Qdp.Pricing.Base.Utilities;
|
|
using YLErp.BLL.Calculation;
|
|
using YLErp.Configuration.Enums;
|
|
using YLErp.Helpers;
|
|
using YLErp.Model.Enum;
|
|
using YLErp.Modules.CalculationModule;
|
|
using YLErp.Modules.SystemModule;
|
|
using YLErp.Modules.TagModule;
|
|
using static iTextSharp.text.pdf.AcroFields;
|
|
|
|
namespace YLErp.Modules.TradeModule.OrderModule
|
|
{
|
|
/// <summary>
|
|
/// 衍生品交易录入服务
|
|
/// </summary>
|
|
public class OtcTradeSaveService : YLBaseService
|
|
{
|
|
public OtcTradeSaveService(OptUserInfo userInfo) : base(userInfo)
|
|
{
|
|
|
|
}
|
|
|
|
public OtcTradeSaveService(YLBaseService baseService) : base(baseService)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保存来自API或导入的交易
|
|
/// </summary>
|
|
public trade SaveOptionTradeFromApiOrImport(OtcOptionTradeFullEx tdReq, TradeSourceEnum dataSource, IDbContextTransaction trans = null)
|
|
{
|
|
if (tdReq is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(tdReq));
|
|
}
|
|
|
|
if (dataSource != TradeSourceEnum.WebApiV2 && dataSource != TradeSourceEnum.导入交易)
|
|
{
|
|
throw new NotSupportedException("不支持:" + dataSource.ToString());
|
|
}
|
|
|
|
if (tdReq._Update)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(tdReq.TradeNumber))
|
|
{
|
|
throw new ServiceException("交易编号 必须填写");
|
|
}
|
|
|
|
var td = DbContext.trade.Where(n => n.TradeNumber == tdReq.TradeNumber)
|
|
.Select(n => new
|
|
{
|
|
n.id,
|
|
n.TradeType,
|
|
n.IsGroup,
|
|
n.TradeDate,
|
|
n.ClientId,
|
|
n.SpotPrice,
|
|
n.TradePrice,
|
|
n.ParentTradeId,
|
|
n.StructureType,
|
|
n.TradeStatus
|
|
}).FirstOrDefault();
|
|
|
|
if (td == null)
|
|
{
|
|
throw new ServiceException("未找到对应的交易数据:" + tdReq.TradeNumber);
|
|
}
|
|
|
|
//国君需求,如果有更多update需求,更改tdReq._Update处理
|
|
if (td.TradeStatus != ConsTrade.新增待确认)
|
|
{
|
|
throw new ServiceException("交易状态必须是'新增待确认'状态才允许修改");
|
|
}
|
|
|
|
tdReq.id = td.id;
|
|
|
|
//扩展支持组合交易主交易的修改
|
|
if (td.IsGroup == 1 && td.TradeType == "结构化交易")
|
|
{
|
|
tdReq.IsGroup = 1;
|
|
tdReq.TradeType = "结构化交易";
|
|
tdReq.ClientId = td.ClientId;
|
|
tdReq.TradeDate = td.TradeDate;
|
|
|
|
if (!tdReq.SpotPrice.HasValue)
|
|
{
|
|
tdReq.SpotPrice = td.SpotPrice;
|
|
}
|
|
|
|
tdReq.TradePrice = td.TradePrice;
|
|
|
|
CheckApiOrImportSave(tdReq, dataSource);
|
|
return new TradeSaveService(this).SaveGroupTrade(tdReq, null);
|
|
}
|
|
else if (td.IsGroup == 2 && td.ParentTradeId > 0)
|
|
{
|
|
tdReq.IsGroup = 2;
|
|
tdReq.ParentTradeId = td.ParentTradeId;
|
|
tdReq.StructureType = td.StructureType;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tdReq.id = 0;
|
|
}
|
|
|
|
var saveExt = CheckApiOrImportSave(tdReq, dataSource);
|
|
|
|
var dbtrade = TradeConverter.ConvertOptionTrade(tdReq);
|
|
|
|
dbtrade.SaveExt = saveExt;
|
|
|
|
return new TradeSaveService(this).SaveTrade(dbtrade, dataSource, trans);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保存来自API或导入的结构化组合交易
|
|
/// </summary>
|
|
public trade SaveStructureOptionTradeFromApiOrImport(StructureOrderRequest request, TradeSourceEnum dataSource, IDbContextTransaction trans = null)
|
|
{
|
|
if (request is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(request));
|
|
}
|
|
|
|
if (dataSource != TradeSourceEnum.WebApiV2 && dataSource != TradeSourceEnum.导入交易)
|
|
{
|
|
throw new NotSupportedException("不支持:" + dataSource.ToString());
|
|
}
|
|
|
|
switch (request.StructureType)
|
|
{
|
|
case "牛市价差":
|
|
case "熊市价差":
|
|
case "亚式熊市价差":
|
|
case "跨式组合":
|
|
case "宽跨式组合":
|
|
case "复制标的资产":
|
|
case "蝶式组合":
|
|
case "飞鹰式组合":
|
|
case "比例价差":
|
|
case "日历价差":
|
|
case "箱式价差":
|
|
case "风险逆转":
|
|
case "三领口组合":
|
|
case "结构化交易":
|
|
break;
|
|
default:
|
|
if (string.IsNullOrWhiteSpace(request.StructureType))
|
|
{
|
|
throw new ServiceException("结构化类型 必须填写");
|
|
}
|
|
throw new ServiceException("结构化类型 不可辨识:" + request.StructureType);
|
|
}
|
|
|
|
if (request.Trades == null || request.Trades.Count() < 2)
|
|
{
|
|
throw new ServiceException("结构化交易组合必须有两条腿及以上");
|
|
}
|
|
|
|
var dbtrades = request.Trades.Select(td =>
|
|
{
|
|
td.id = 0;
|
|
td.StructureType = request.StructureType;
|
|
var saveExt = CheckApiOrImportSave(td, dataSource);
|
|
var trad = TradeConverter.ConvertOptionTrade(td);
|
|
trad.SaveExt = saveExt;
|
|
return trad;
|
|
}).ToArray();
|
|
|
|
var struTrade = dbtrades[0].Clone();
|
|
struTrade.TradeType = "结构化交易";
|
|
struTrade.SubTrades = dbtrades;
|
|
|
|
return new TradeSaveService(OptUser).SaveTrade(struTrade, dataSource, trans);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保存来自API或导入的组合交易
|
|
/// </summary>
|
|
public trade SaveGroupOptionTradeFromApiOrImport(StructureOrderRequest request, TradeSourceEnum dataSource, IDbContextTransaction trans = null)
|
|
{
|
|
if (request is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(request));
|
|
}
|
|
|
|
if (dataSource != TradeSourceEnum.WebApiV2 && dataSource != TradeSourceEnum.导入交易)
|
|
{
|
|
throw new NotSupportedException("不支持:" + dataSource.ToString());
|
|
}
|
|
|
|
if (request.Trades == null || request.Trades.Count() <= 2)
|
|
{
|
|
throw new ServiceException("组合交易必须有两条腿及以上");
|
|
}
|
|
|
|
var dbtrades = request.Trades.Select(td =>
|
|
{
|
|
td.id = 0;
|
|
if (td.IsGroup == 1)
|
|
{
|
|
td.StructureType = td.TradeType;
|
|
td.TradeType = "结构化交易";
|
|
}
|
|
var saveExt = CheckApiOrImportSave(td, dataSource);
|
|
var trad = TradeConverter.ConvertOptionTrade(td);
|
|
trad.SaveExt = saveExt;
|
|
return trad;
|
|
}).ToArray();
|
|
|
|
var struTrade = dbtrades.FirstOrDefault(x => x.IsGroup == 1);
|
|
if (struTrade == null)
|
|
{
|
|
throw new Exception("未找到组合主交易,组合子交易必须和主交易配对出现");
|
|
}
|
|
struTrade.SubTrades = dbtrades.Where(x => x.IsGroup != 1);
|
|
|
|
return new TradeSaveService(OptUser).SaveTrade(struTrade, dataSource, trans);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保存组合报价的交易
|
|
/// </summary>
|
|
public List<trade> SaveOptionTradesFromPricing(IEnumerable<OtcOptionTradeFull> trades)
|
|
{
|
|
var tdGroups = trades.GroupBy(n => n.CalcId.Split('-')[0]).ToArray();
|
|
var dbTrades = new List<trade>();
|
|
var saveChecker = new OtcOptionSaveChecker(this);
|
|
var tags = (trades != null && trades.Any()) ? trades.First().Tags : null;
|
|
try
|
|
{
|
|
foreach (var tdGroup in tdGroups)
|
|
{
|
|
var tdList = tdGroup.Select(td =>
|
|
{
|
|
saveChecker.CheckBasic(td).CheckAnnualizeFactorForEditSave(td);
|
|
saveChecker.CheckPrincipalRate(td).CheckParticipationRate(td);
|
|
saveChecker.CheckExoticFields(td);
|
|
return TradeConverter.ConvertOptionTrade(td);
|
|
}).ToArray();
|
|
|
|
var tdConv = tdList.Where(O => O.TradeType != "现金流交易").FirstOrDefault() ?? tdList[0];
|
|
if (tdList.Length > 1)
|
|
{
|
|
tdConv = tdConv.Clone();
|
|
tdConv.TradeType = "结构化交易";
|
|
tdConv.SubTrades = tdList;
|
|
}
|
|
|
|
if (PS.Config.Is浙期 && IsCodesExistsCommoditySpot(new string[] { tdConv.UnderlyingCode }))
|
|
{
|
|
tdConv.MetaDic["ActualExerciseDate"] = tdConv.ExerciseDate.Value.ToString("yyyy-MM-dd"); //没有录入
|
|
}
|
|
|
|
var dbTrade = new TradeSaveService(this).SaveTrade(tdConv, TradeSourceEnum.系统交易);
|
|
using (var tagService = new TagService(OptUser))
|
|
{
|
|
tagService.SetTradeTagForTradeEdit(new TagModule.Dto.SetTradeTagForTradeEditRequest
|
|
{
|
|
TradeId = dbTrade.id,
|
|
Tags = tags
|
|
});
|
|
if ("结构化交易".Equals(dbTrade.TradeType))
|
|
{
|
|
if (dbTrade.SubTrades != null && dbTrade.SubTrades.Any())
|
|
{
|
|
dbTrade.SubTrades.ToList().ForEach(p =>
|
|
{
|
|
if (p.id > 0)
|
|
{
|
|
tagService.SetTradeTagForTradeEdit(new TagModule.Dto.SetTradeTagForTradeEditRequest
|
|
{
|
|
TradeId = p.id,
|
|
Tags = tags
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
}
|
|
var variety = UnderlyingDataProvider.GetVariety(dbTrade.UnderlyingCode);
|
|
if (variety != null)
|
|
{
|
|
dbTrade.QuoteUnit = variety.QuoteUnit;
|
|
dbTrade.QuoteUnitSingle = variety.QuoteUnitSingle;
|
|
}
|
|
dbTrades.Add(dbTrade);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
if (dbTrades.Count > 0)
|
|
{
|
|
var ids = dbTrades.Select(n => n.id.ToString()).ToArray();
|
|
var sql = "UPDATE trade SET ValidState='InValid', TradeNumber='XX', ClientId=0, ChangeMsg='因组合报价录入交易部分失败所以无效' WHERE id in ({0}) OR ParentTradeId in ({0});";
|
|
sql = string.Format(sql, string.Join(",", ids));
|
|
var changes = DbContext.Database.ExecuteSqlRaw(sql);
|
|
}
|
|
throw;
|
|
}
|
|
|
|
return dbTrades;
|
|
}
|
|
|
|
public trade SaveOptionForwardTradeFromApi(OtcTradeForward req, TradeSourceEnum dataSource)
|
|
{
|
|
req.TradeType = "远期";
|
|
var otcOptionTradeFullEx = new OtcOptionTradeFullEx { ClientName = req.ClientName };
|
|
new OtcOptionSaveChecker(this).CheckAssetBook(req).CheckTrader(req).CheckClient(otcOptionTradeFullEx);
|
|
req.ClientId = otcOptionTradeFullEx.ClientId;
|
|
var um = DataCacheProvider.GetUnderlyingDataSource().GetData(req.UnderlyingCode);
|
|
if (um == null)
|
|
{
|
|
throw new ServiceException("系统中不存在,传入的标的1代码");
|
|
}
|
|
else if (!string.IsNullOrEmpty(req.BasisUnderlyingCode))
|
|
{
|
|
if (DataCacheProvider.GetUnderlyingDataSource().GetData(req.BasisUnderlyingCode) == null)
|
|
{
|
|
throw new ServiceException("系统中不存在,传入的标的2代码");
|
|
}
|
|
|
|
}
|
|
if (req.IsSupplyForwardValue == 0)
|
|
{
|
|
req.MetaDic["IsSupplyForwardValue"] = "0";
|
|
}
|
|
else if (req.IsSupplyForwardValue == -1)
|
|
{
|
|
req.MetaDic["IsSupplyForwardValue"] = "-1";
|
|
}
|
|
else if (req.IsSupplyForwardValue == 1)
|
|
{
|
|
req.MetaDic["IsSupplyForwardValue"] = "1";
|
|
}
|
|
else
|
|
{
|
|
throw new ServiceException("是否补偿远期价值,传入值错误,只能为0,1,-1;");
|
|
}
|
|
|
|
if (req.PriceModel == 0)
|
|
{
|
|
if ((PS.Config.ErpElement.ForwardTradePriceModel == ForwardTradePriceModel.STANDARD || PS.Config.ErpElement.ForwardTradePriceModel == ForwardTradePriceModel.HOOK_PRICE))
|
|
{
|
|
if (PS.Config.ErpElement.ForwardTradePriceModel == ForwardTradePriceModel.STANDARD)
|
|
{
|
|
var spList = new List<string>
|
|
{
|
|
req.SpotPrice.OtcFormatUmPrice()
|
|
};
|
|
if (!string.IsNullOrEmpty(req.BasisUnderlyingCode))
|
|
{
|
|
|
|
if (req.SpotPrice2 == null)
|
|
{
|
|
throw new ServiceException("填写标的2代码,则必须填写标的2期初价格");
|
|
}
|
|
else
|
|
{
|
|
spList.Add((req.SpotPrice2 ?? 0).OtcFormatUmPrice());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new ServiceException("请填写标的2代码");
|
|
}
|
|
|
|
req.SpotPrice = (double)((decimal)req.SpotPrice - (decimal)(req.SpotPrice2 ?? 0));
|
|
req.MetaDic["期初信息"] = spList.ToJson();
|
|
}
|
|
new ForwardModule.ForwardTradeImportService(UserInfo).GetSpotPrice(req);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var spList = new List<string>
|
|
{
|
|
req.SpotPrice.OtcFormatUmPrice()
|
|
};
|
|
req.MetaDic["期初信息"] = spList.ToJson();
|
|
}
|
|
new ForwardModule.ForwardTradeImportService(UserInfo).GetCountRatio(req);
|
|
new ForwardModule.ForwardTradeImportService(UserInfo).GetNotional_TradeAmount_Lots(req, um);
|
|
new ForwardModule.ForwardTradeImportService(UserInfo).GetForwardValue(req);
|
|
new ForwardModule.ForwardTradeImportService(UserInfo).GetOpenCommission_TradePrice(req, null, req.OpenCommission, req.TradePrice);
|
|
var td = TradeConverter.ConvertForward(req);
|
|
|
|
td = new TradeSaveService(this).SaveTrade(td, dataSource);
|
|
var variety = UnderlyingDataProvider.GetVariety(td.UnderlyingCode);
|
|
if (variety != null)
|
|
{
|
|
td.QuoteUnit = variety.QuoteUnit;
|
|
td.QuoteUnitSingle = variety.QuoteUnitSingle;
|
|
}
|
|
return td;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保存分组交易
|
|
/// </summary>
|
|
public trade SaveGroupTradeFromPricing(trade trade, IEnumerable<OtcOptionTradeFull> subTrades)
|
|
{
|
|
var saveChecker = new OtcOptionSaveChecker(this);
|
|
|
|
subTrades.ToList().ForEach(x =>
|
|
{
|
|
saveChecker.CheckBasic(x).CheckAnnualizeFactorForEditSave(x);
|
|
saveChecker.CheckPrincipalRate(x).CheckParticipationRate(x);
|
|
saveChecker.CheckExoticFields(x);
|
|
});
|
|
|
|
trade.TradeType = "结构化交易";
|
|
trade.IsGroup = 1;
|
|
trade.SubTrades = subTrades.Select(x => TradeConverter.ConvertOptionTrade(x)).ToArray();
|
|
trade.UnderlyingCode = trade.SubTrades.FirstOrDefault(x => !string.IsNullOrEmpty(x.UnderlyingCode)).UnderlyingCode;
|
|
var dbTrade = new TradeSaveService(this).SaveTrade(trade, TradeSourceEnum.系统交易);
|
|
using (var tagService = new TagService(OptUser))
|
|
{
|
|
tagService.SetTradeTagForTradeEdit(new TagModule.Dto.SetTradeTagForTradeEditRequest
|
|
{
|
|
TradeId = dbTrade.id,
|
|
Tags = trade.Tags
|
|
});
|
|
}
|
|
return dbTrade;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保存交易编辑的交易
|
|
/// </summary>
|
|
public trade SaveOptionTradesFromTradeEdit(OtcOptionTradeFull td)
|
|
{
|
|
var saveChecker = new OtcOptionSaveChecker(this);
|
|
|
|
saveChecker.CheckBasic(td).CheckAnnualizeFactorForEditSave(td);
|
|
saveChecker.CheckPrincipalRate(td).CheckParticipationRate(td);
|
|
saveChecker.CheckExoticFields(td);
|
|
|
|
var tdConv = TradeConverter.ConvertOptionTrade(td);
|
|
|
|
var dbTrade = new TradeSaveService(this).SaveTrade(tdConv, TradeSourceEnum.系统交易);
|
|
var variety = UnderlyingDataProvider.GetVariety(dbTrade.UnderlyingCode);
|
|
if (variety != null)
|
|
{
|
|
dbTrade.QuoteUnit = variety.QuoteUnit;
|
|
dbTrade.QuoteUnitSingle = variety.QuoteUnitSingle;
|
|
}
|
|
using (var tagService = new TagService(OptUser))
|
|
{
|
|
tagService.SetTradeTagForTradeEdit(new TagModule.Dto.SetTradeTagForTradeEditRequest
|
|
{
|
|
TradeId = dbTrade.id,
|
|
Tags = td.Tags
|
|
});
|
|
}
|
|
return dbTrade;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 判断标的是否为现货
|
|
/// </summary>
|
|
/// <param name="codes"></param>
|
|
/// <returns></returns>
|
|
public bool IsCodesExistsCommoditySpot(string[] codes)
|
|
{
|
|
return UndelyingHelper.IsCodesExistsCommoditySpot(codes, (um) =>
|
|
{
|
|
return um.UnderlyingInstrumentType == "CommoditySpot" ? true : false;
|
|
});
|
|
|
|
}
|
|
|
|
//检查导入和API数据
|
|
private TradeSaveExt CheckApiOrImportSave(OtcOptionTradeFullEx td, TradeSourceEnum dataSource)
|
|
{
|
|
if (dataSource != TradeSourceEnum.WebApiV2 && dataSource != TradeSourceEnum.导入交易)
|
|
{
|
|
throw new NotSupportedException("不支持:" + dataSource.ToString());
|
|
}
|
|
|
|
var saveChecker = new OtcOptionSaveChecker(this).CheckBasic(td);
|
|
|
|
//簿记、交易员、交易对手
|
|
saveChecker.CheckAssetBook(td).CheckTrader(td).CheckClient(td);
|
|
|
|
//年化系数
|
|
saveChecker.CheckAnnualizeFactorForApiOrImport(td);
|
|
|
|
//保底收益率、参与率
|
|
saveChecker.CheckPrincipalRate(td).CheckParticipationRate(td);
|
|
|
|
TradingPremiumFlag premiumFlag;
|
|
var notionalFlag = TradingNotionalFlag.None;
|
|
|
|
// 权利金单价/期权费率/权利金总额
|
|
// 20200622: 权利金总额/单价/费率、名义本金/份额导入时可能存在为负值的情况
|
|
// 20210425: 国海凤凰雪球期权资金记录方向问题,需要权利金可以为负
|
|
if (dataSource == TradeSourceEnum.导入交易)
|
|
{
|
|
if ((td.HasFlag & OtcOptionTradeFullHasFlag.TradeSinglePrice) > 0)
|
|
{
|
|
premiumFlag = TradingPremiumFlag.TradeSinglePrice;
|
|
td.TradeSinglePrice = td.TradeSinglePrice.Value;
|
|
}
|
|
else if ((td.HasFlag & OtcOptionTradeFullHasFlag.TradePrice) > 0)
|
|
{
|
|
premiumFlag = TradingPremiumFlag.TradePrice;
|
|
td.TradePrice = td.TradePrice.Value;
|
|
}
|
|
else if ((td.HasFlag & OtcOptionTradeFullHasFlag.PremiumRate) > 0)
|
|
{
|
|
premiumFlag = TradingPremiumFlag.PremiumRate;
|
|
td.PremiumRate = td.PremiumRate.Value;
|
|
}
|
|
else
|
|
{
|
|
//暂时不支持小于0
|
|
td.TradeSinglePrice = td.PremiumRate = td.TradePrice = 0;
|
|
premiumFlag = TradingPremiumFlag.AllZero;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (td.TradeSinglePrice.HasValue)
|
|
{
|
|
premiumFlag = TradingPremiumFlag.TradeSinglePrice;
|
|
}
|
|
else if (td.PremiumRate.HasValue)
|
|
{
|
|
premiumFlag = TradingPremiumFlag.PremiumRate;
|
|
}
|
|
else
|
|
{
|
|
//TradePrice在20200803被修改后一直有值,已经不具备优先使用的条件
|
|
premiumFlag = TradingPremiumFlag.TradePrice;
|
|
}
|
|
}
|
|
|
|
// 有效名义本金/名义本金/交易份额/交易数量
|
|
|
|
if (notionalFlag == TradingNotionalFlag.None)
|
|
{
|
|
if (Math.Abs(td.Notional) > 0)
|
|
{
|
|
notionalFlag = TradingNotionalFlag.Notional;
|
|
td.Notional = Math.Abs(td.Notional);
|
|
}
|
|
else if (Math.Abs(td.TradeAmount) > 0)
|
|
{
|
|
notionalFlag = TradingNotionalFlag.TradeAmount;
|
|
td.TradeAmount = Math.Abs(td.TradeAmount);
|
|
}
|
|
else if (td.TradeType == "累计期权")
|
|
{
|
|
throw new ServiceException("有效交易份额/有效交易数量 两个字段中必须有一个值大于0");
|
|
}
|
|
else if (Math.Abs(td.StockEqvNotional) > 0)
|
|
{
|
|
notionalFlag = TradingNotionalFlag.StockEqvNotional;
|
|
td.StockEqvNotional = Math.Abs(td.StockEqvNotional);
|
|
}
|
|
else if (Math.Abs(td.StockEqvNotionalReal) > 0)
|
|
{
|
|
notionalFlag = TradingNotionalFlag.StockEqvNotionalReal;
|
|
td.StockEqvNotionalReal = Math.Abs(td.StockEqvNotionalReal);
|
|
}
|
|
else
|
|
{
|
|
//交易数量
|
|
throw new ServiceException("有效交易份额/有效交易数量/有效名义本金/名义本金 四个字段中必须有一个值大于0");
|
|
}
|
|
}
|
|
|
|
if (!(td.TradeType == "结构化交易" && td.IsGroup == 1))
|
|
{
|
|
td.IsUsePremiumRate = premiumFlag == TradingPremiumFlag.PremiumRate
|
|
|| premiumFlag == TradingPremiumFlag.TradePrice && (notionalFlag == TradingNotionalFlag.StockEqvNotionalReal || notionalFlag == TradingNotionalFlag.StockEqvNotional);
|
|
}
|
|
|
|
// 组合标的(使用最大名义本金)
|
|
if (saveChecker.CheckSyntheticUnderlying(td, td.SyntheticPrices, out var maxSpotPrice) && maxSpotPrice > 0)
|
|
{
|
|
//暂时不再支持最大名义本金方式导入处理
|
|
//var spotPrice = Math.Abs(td.SpotPrice.Value);
|
|
//var spotPriceRate = spotPrice / maxSpotPrice;
|
|
//switch (notionalFlag)
|
|
//{
|
|
// case TradingNotionalFlag.StockEqvNotionalReal:
|
|
// td.StockEqvNotionalMax = TradeHelper.GetStockEqvNotional(td.StockEqvNotionalReal, td.ParticipationRate, td.AnnualizeFactor);
|
|
// td.StockEqvNotionalReal *= spotPriceRate;
|
|
// break;
|
|
// case TradingNotionalFlag.StockEqvNotional:
|
|
// td.StockEqvNotionalMax = td.StockEqvNotional;
|
|
// td.StockEqvNotional *= spotPriceRate;
|
|
// break;
|
|
// case TradingNotionalFlag.Notional:
|
|
// td.StockEqvNotionalMax = td.Notional * maxSpotPrice;
|
|
// break;
|
|
// case TradingNotionalFlag.TradeAmount:
|
|
// //组合标的没有数量和份额的乘积因子
|
|
// td.StockEqvNotionalMax = td.TradeAmount * maxSpotPrice;
|
|
// break;
|
|
// default:
|
|
// throw new SystemException("[NotionalFlag]系统程序错误");
|
|
//}
|
|
}
|
|
|
|
if (td.Comments?.Length > 100)
|
|
{
|
|
throw new ServiceException("交易备注 最多100个字");
|
|
}
|
|
|
|
// 奇异期权检查
|
|
saveChecker.CheckExoticFields(td);
|
|
|
|
return new TradeSaveExt
|
|
{
|
|
PremiumFlag = premiumFlag,
|
|
NotionalFlag = notionalFlag
|
|
};
|
|
}
|
|
}
|
|
|
|
class OtcOptionSaveChecker : YLBaseService
|
|
{
|
|
public OtcOptionSaveChecker(YLBaseService baseService) : base(baseService)
|
|
{
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// 检查基础信息(结构类型/标的代码/初始预付金)
|
|
/// </summary>
|
|
public OtcOptionSaveChecker CheckBasic(OtcTradeBase td)
|
|
{
|
|
if (!string.IsNullOrEmpty(td.TradeNumber))
|
|
{
|
|
var exclueStr = new List<string> { "+","/","?","%","#","&","=" };
|
|
exclueStr.ForEach(x =>
|
|
{
|
|
if (td.TradeNumber.Contains(x))
|
|
{
|
|
throw new ServiceException("交易编号不合法,不能包含以下字符:"+ exclueStr.Join(","));
|
|
}
|
|
});
|
|
}
|
|
// 结构类型
|
|
if (string.IsNullOrWhiteSpace(td.TradeType))
|
|
{
|
|
throw new ServiceException("结构类型 必须填写");
|
|
}
|
|
if (td.TradeType == "现金流交易" || td.TradeType == "结构化交易" && td.IsGroup == 1)
|
|
{
|
|
return this;
|
|
}
|
|
// 标的代码
|
|
if (string.IsNullOrWhiteSpace(td.UnderlyingCode))
|
|
{
|
|
throw new ServiceException("标的代码 必须填写");
|
|
}
|
|
|
|
var um = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
|
|
|
|
// 标的代码
|
|
if (um == null)
|
|
{
|
|
throw new ServiceException("标的代码 必须在系统中存在:" + td.UnderlyingCode);
|
|
}
|
|
if (um.UnderlyingInstrumentType == "Stock")
|
|
{
|
|
var limitStatus = new UnderlyingModule.StockBlackWhiteService(UserInfo).GetStockBlackWhiteList(Configuration.Enums.LimitRangeEnum.Option, out var Codes);
|
|
if (Codes != null)
|
|
{
|
|
if (limitStatus == Codes.Contains(um.UnderlyingCode))
|
|
{
|
|
if (limitStatus)
|
|
{
|
|
throw new ServiceException("标的代码 填写错误,存在于黑名单中,请求值:" + td.UnderlyingCode);
|
|
}
|
|
else
|
|
{
|
|
throw new ServiceException("标的代码 填写错误,不存在于白名单中,请求值:" + td.UnderlyingCode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 簿记账户
|
|
/// </summary>
|
|
public OtcOptionSaveChecker CheckAssetBook(OtcTradeBase td)
|
|
{
|
|
if (td.AssetId > 0)
|
|
{
|
|
return this;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(td.AssetBookName))
|
|
{
|
|
throw new ServiceException("簿记账户 必须填写");
|
|
}
|
|
var assetUnit = DbContext.assetunit.Where(au => au.Name == td.AssetBookName).Select(n => new { n.id }).FirstOrDefault();
|
|
if (assetUnit == null)
|
|
{
|
|
throw new ServiceException("簿记账户不存在:" + td.AssetBookName);
|
|
}
|
|
td.AssetId = assetUnit.id;
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 交易员赋值及验证逻辑
|
|
/// </summary>
|
|
public OtcOptionSaveChecker CheckTrader(OtcTradeBase td)
|
|
{
|
|
if (td.TraderId > 0)
|
|
{
|
|
return this;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(td.TraderName))
|
|
{
|
|
throw new ServiceException("交易员名称 必须填写");
|
|
}
|
|
|
|
using (var sysdb = DbContextFactory.GetErpBaseContext())
|
|
{
|
|
var systemUser = sysdb.SystemUsers.Where(su => su.LoginName == td.TraderName)
|
|
.Select(n => new { n.Id, n.Name }).FirstOrDefault()
|
|
?? sysdb.SystemUsers.Where(su => su.Name == td.TraderName)
|
|
.Select(n => new { n.Id, n.Name }).FirstOrDefault();
|
|
if (systemUser == null)
|
|
{
|
|
throw new ServiceException("交易员不存在:" + td.TraderName);
|
|
}
|
|
else
|
|
{
|
|
td.TraderId = systemUser.Id;
|
|
td.TraderName = systemUser.Name;
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 交易对手方
|
|
/// </summary>
|
|
public OtcOptionSaveChecker CheckClient(OtcOptionTradeFullEx td)
|
|
{
|
|
if (td.ClientId > 0)
|
|
{
|
|
return this;
|
|
}
|
|
|
|
var clientQuery = DbContextFactory.GetClientDbContext(OptUser).client.AsQueryable();
|
|
|
|
if (!string.IsNullOrWhiteSpace(td.ClientNumber))
|
|
{
|
|
clientQuery = clientQuery.Where(c => c.Number == td.ClientNumber);
|
|
}
|
|
else if (!string.IsNullOrWhiteSpace(td.ClientName))
|
|
{
|
|
clientQuery = clientQuery.Where(c => c.Name == td.ClientName);
|
|
}
|
|
else
|
|
{
|
|
throw new ServiceException("交易对手方名称或编号 必须填写");
|
|
}
|
|
|
|
var client = clientQuery.Select(n => new { n.id }).FirstOrDefault();
|
|
|
|
if (client == null)
|
|
{
|
|
throw new ServiceException($"交易对手方不存在,客户名称:[{td.ClientName}],交易对手方编号:[{td.ClientNumber}]");
|
|
}
|
|
|
|
td.ClientId = client.id;
|
|
|
|
if (!td.MetaDic.ContainsKey("主协议编号"))
|
|
{
|
|
var mainProtocal = ClientDataModelV1.getMainProtocols(td.ClientId).FirstOrDefault();
|
|
if (mainProtocal != null)
|
|
{
|
|
td.MetaDic.Add("主协议编号", mainProtocal.Value);
|
|
}
|
|
}
|
|
|
|
if (!td.MetaDic.ContainsKey("补充协议编号") && td.MetaDic.ContainsKey("主协议编号"))
|
|
{
|
|
var sideProtocal = ClientDataModelV1.getSideProtocols(td.MetaDic["主协议编号"]).FirstOrDefault();
|
|
if (sideProtocal != null)
|
|
{
|
|
td.MetaDic.Add("补充协议编号", sideProtocal);
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 年化系数(API或导入的时候使用的是trade的字段所以雪球和凤凰需要转换为自己的字段)
|
|
/// </summary>
|
|
public OtcOptionSaveChecker CheckAnnualizeFactorForApiOrImport(OtcOptionTradeFull td)
|
|
{
|
|
//非期权年化
|
|
if (!td.IsAnnualized)
|
|
{
|
|
td.AnnualizeFactor = 1;
|
|
if (td.TradeType == "雪球期权" || td.TradeType == "凤凰期权")
|
|
{
|
|
td.IsAnnualized2 = false;
|
|
td.AnnualizeFactor2 = 1;
|
|
}
|
|
td.MetaDic[nameof(td.AnnualizeFactor)] = null;
|
|
td.MetaDic[nameof(td.AnnualizeFactor2)] = null;
|
|
return this;
|
|
}
|
|
|
|
//期权年化
|
|
if (!td.MetaDic.TryGetValue(nameof(td.AnnualizeFactor), out var str) || string.IsNullOrWhiteSpace(str))
|
|
{
|
|
if (td.AnnualizeFactor > 0)
|
|
{
|
|
var daysInYear = CalculatorHelper.GetTradeDayCount().ToDayCountImpl().DaysInYear();
|
|
var ttmdays = td.AnnualizeFactor.Value * daysInYear;
|
|
td.MetaDic[nameof(td.AnnualizeFactor)] = $"{ttmdays}/{daysInYear}";
|
|
}
|
|
else
|
|
{
|
|
var un = UnderlyingDataProvider.GetUnderlying(td.UnderlyingCode);
|
|
var ttmdays = TradeCalcHelper.CalculateTTMDays(td.TradeDate.Value, td.SettlementDate ?? td.ExerciseDate.Value, un?.UnderlyingTypeId ?? 0, false);
|
|
var daysInYear = CalculatorHelper.GetTradeDayCount().ToDayCountImpl().DaysInYear();
|
|
td.AnnualizeFactor = ttmdays / daysInYear;
|
|
td.MetaDic[nameof(td.AnnualizeFactor)] = $"{ttmdays}/{daysInYear}";
|
|
}
|
|
}
|
|
|
|
//雪球和凤凰使用自己的年化处理方式
|
|
if (td.TradeType == "雪球期权" || td.TradeType == "凤凰期权")
|
|
{
|
|
td.IsAnnualized2 = true;
|
|
td.AnnualizeFactor2 = td.AnnualizeFactor;
|
|
td.MetaDic[nameof(td.AnnualizeFactor2)] = td.MetaDic[nameof(td.AnnualizeFactor)];
|
|
|
|
td.IsAnnualized = false;
|
|
td.AnnualizeFactor = 1;
|
|
td.MetaDic[nameof(td.AnnualizeFactor)] = null;
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 检查年化系数(组合报价或编辑保存时使用)
|
|
/// </summary>
|
|
public OtcOptionSaveChecker CheckAnnualizeFactorForEditSave(OtcOptionTradeFull td)
|
|
{
|
|
var IsAnnualized2 = td.TradeType == "雪球期权" || td.TradeType == "凤凰期权";
|
|
|
|
if (IsAnnualized2)
|
|
{
|
|
td.IsAnnualized = false;
|
|
td.AnnualizeFactor = 1;
|
|
td.MetaDic[nameof(td.AnnualizeFactor)] = null;
|
|
|
|
if (!td.IsAnnualized2)
|
|
{
|
|
td.AnnualizeFactor2 = 1;
|
|
td.MetaDic[nameof(td.AnnualizeFactor2)] = null;
|
|
return this;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
td.IsAnnualized2 = false;
|
|
td.AnnualizeFactor2 = 1;
|
|
td.MetaDic[nameof(td.AnnualizeFactor2)] = null;
|
|
|
|
if (!td.IsAnnualized)
|
|
{
|
|
td.AnnualizeFactor = 1;
|
|
td.MetaDic[nameof(td.AnnualizeFactor)] = null;
|
|
return this;
|
|
}
|
|
}
|
|
|
|
var AnnualizeFactorField = IsAnnualized2 ? nameof(td.AnnualizeFactor2) : nameof(td.AnnualizeFactor);
|
|
|
|
if (!td.MetaDic.TryGetValue(AnnualizeFactorField, out var str) || string.IsNullOrWhiteSpace(str))
|
|
{
|
|
throw new ServiceException("年化系数 必须填写");
|
|
}
|
|
|
|
var index = str.IndexOf('/');
|
|
|
|
if (index < 0)
|
|
{
|
|
throw new ServiceException("年化系数 填写错误,格式必须为:天数/年天数,填写值为:" + str);
|
|
}
|
|
|
|
if (!double.TryParse(str.Substring(0, index).Trim(), out var ttmDays))
|
|
{
|
|
throw new ServiceException("年化系数 填写错误,天数解析失败,填写值为:" + str);
|
|
}
|
|
|
|
if (ttmDays < 0)
|
|
{
|
|
throw new ServiceException("年化系数 填写错误,格式必须为:天数/年天数,天数必须大于等于0,填写值为:" + str);
|
|
}
|
|
|
|
if (!double.TryParse(str.Substring(index + 1).Trim(), out var daysOfYear))
|
|
{
|
|
throw new ServiceException("年化系数 填写错误,格式必须为:天数/年天数,年天数解析失败,填写值为:" + str);
|
|
}
|
|
|
|
if (daysOfYear < 1)
|
|
{
|
|
throw new ServiceException("年化系数 填写错误,格式必须为:天数/年天数,年天数必须大于0,填写值为:" + str);
|
|
}
|
|
|
|
if (IsAnnualized2)
|
|
{
|
|
td.AnnualizeFactor2 = ttmDays / daysOfYear;
|
|
}
|
|
else
|
|
{
|
|
td.AnnualizeFactor = ttmDays / daysOfYear;
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保底收益率
|
|
/// </summary>
|
|
public OtcOptionSaveChecker CheckPrincipalRate(OtcTradeBase td)
|
|
{
|
|
if (!td.PrincipalRate.HasValue)
|
|
{
|
|
td.PrincipalRate = 0;
|
|
}
|
|
else if (td.PrincipalRate.Value < 0)
|
|
{
|
|
throw new ServiceException("保底收益率 填写错误,必须大于等于0,请求值:" + td.PrincipalRate.Value);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 参与率
|
|
/// </summary>
|
|
public OtcOptionSaveChecker CheckParticipationRate(OtcTradeBase td)
|
|
{
|
|
if (!td.ParticipationRate.HasValue)
|
|
{
|
|
td.ParticipationRate = 1;
|
|
}
|
|
else if (td.ParticipationRate.Value < 0)
|
|
{
|
|
throw new ServiceException("参与率 填写错误,必须大于等于0,请求值:" + td.ParticipationRate.Value);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
//组合标的
|
|
public bool CheckSyntheticUnderlying(OtcTradeBase td, IEnumerable<UnderlyingPriceModel> syntheticPrices, out double maxSpotPrice)
|
|
{
|
|
maxSpotPrice = 0;
|
|
|
|
var synthetic = DataCacheProvider.GetUnderlyingDataSource().GetSyntheticUnderlying(td.UnderlyingCode);
|
|
|
|
if (synthetic == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (syntheticPrices == null)
|
|
{
|
|
throw new ServiceException("[组合标的]必须填写每个组成标的的期初价格");
|
|
}
|
|
|
|
var model = synthetic.GetSyntheticPriceModel();
|
|
var codeSet = model.SuList.Select(n => n.UnderlyingCode).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
|
|
foreach (var item in syntheticPrices)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(item.UnderlyingCode))
|
|
{
|
|
continue;
|
|
}
|
|
if (!codeSet.Remove(item.UnderlyingCode))
|
|
{
|
|
throw new ServiceException($"[组合标的]填写错误,组合标的中不存在此标的:{item.UnderlyingCode}");
|
|
}
|
|
model.SuList.First(n => n.UnderlyingCode.Equals(item.UnderlyingCode, StringComparison.OrdinalIgnoreCase)).Price = item.Price;
|
|
}
|
|
|
|
if (codeSet.Any())
|
|
{
|
|
throw new ServiceException("[组合标的]标的价格未填写完整,缺少:" + string.Join(",", codeSet));
|
|
}
|
|
|
|
//组合标的有独特的名义本金处理方式
|
|
|
|
maxSpotPrice = model.SuList.Max(n => Math.Abs(n.Coefficient * n.Price));
|
|
if (maxSpotPrice < 1e-6)
|
|
{
|
|
throw new ServiceException("[组合标的]标的价格 填写错误");
|
|
}
|
|
|
|
td.SpotPrice = model.Price = model.SuList.Sum(n => n.Coefficient * n.Price) + model.Constant;
|
|
|
|
td.MetaDic["组合标的"] = JsonHelper.ToJson(model);
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 奇异期权字段检查
|
|
/// </summary>
|
|
public OtcOptionSaveChecker CheckExoticFields(OtcOptionTradeFull td)
|
|
{
|
|
switch (td.TradeType)
|
|
{
|
|
case "香草期权":
|
|
case "自定义交易":
|
|
case "结构化产品":
|
|
case "合成价差期权": break;
|
|
case "障碍期权":
|
|
CheckBarrierTrade(td); break;
|
|
case "Risky期权":
|
|
CheckRiskyTrade(td); break;
|
|
case "二元期权":
|
|
CheckBinaryTrade(td); break;
|
|
case "亚式期权":
|
|
CheckAisanTrade(td); break;
|
|
case "双鲨期权":
|
|
CheckDoubleSharkTrade(td); break;
|
|
case "区间累积期权":
|
|
CheckRangeAccTrade(td); break;
|
|
case "凤凰期权":
|
|
CheckAutoCallTrade(td); break;
|
|
case "雪球期权":
|
|
CheckSnowballTrade(td); break;
|
|
case "气囊结构":
|
|
CheckAirbagTrade(td); break;
|
|
case "收益增强结构":
|
|
CheckUnderlyigEnhanceTrade(td); break;
|
|
case "结构化交易":
|
|
case "现金流交易": break;
|
|
case "累计期权":
|
|
CheckAccumulatorTrade(td); break;
|
|
case "彩虹期权":
|
|
case "价差期权":
|
|
throw new ServiceException("暂不支持");
|
|
default:
|
|
throw new ServiceException("不可辨识的结构类型:" + td.TradeType);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
//障碍期权
|
|
private void CheckBarrierTrade(OtcOptionTradeFull td)
|
|
{
|
|
//---------------------------------------------
|
|
// 障碍类型
|
|
//---------------------------------------------
|
|
|
|
if (string.IsNullOrWhiteSpace(td.BarrierType))
|
|
{
|
|
throw new ServiceException("[障碍期权]障碍类型 必须填写");
|
|
}
|
|
|
|
switch (td.BarrierType)
|
|
{
|
|
default:
|
|
throw new ServiceException("[障碍期权]障碍类型 不支持:" + td.BarrierType);
|
|
case "上升敲入":
|
|
case "上升敲出":
|
|
case "下降敲入":
|
|
case "下降敲出":
|
|
case "双障碍敲入":
|
|
case "双障碍敲出":
|
|
break;
|
|
}
|
|
|
|
//障碍价格BarrierLow 高障碍价格BarrierHigh
|
|
|
|
if (td.BarrierType.Contains("双障碍"))
|
|
{
|
|
if (!td.BarrierHigh.HasValue)
|
|
{
|
|
throw new ServiceException($"[障碍期权][障碍类型为{td.BarrierType}时]高障碍价格 必须填写");
|
|
}
|
|
|
|
if (td.BarrierHigh < td.BarrierLow)
|
|
{
|
|
throw new ServiceException("[障碍期权]高障碍价格 必须大于障碍价格");
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------
|
|
// 观察方式
|
|
//---------------------------------------------
|
|
if (string.IsNullOrWhiteSpace(td.MonitorType))
|
|
{
|
|
td.MonitorType = "离散";
|
|
}
|
|
else if (td.MonitorType != "离散" && td.MonitorType != "连续")
|
|
{
|
|
throw new ServiceException("[障碍期权]观察方式 填写错误,当前支持的类型为:离散、连续,请求值为:" + td.MonitorType);
|
|
}
|
|
|
|
//---------------------------------------------
|
|
// 补偿支付方式
|
|
//---------------------------------------------
|
|
if (string.IsNullOrWhiteSpace(td.RebateType))
|
|
{
|
|
td.RebateType = "AtHit";
|
|
}
|
|
else if (td.RebateType != "AtHit" && td.RebateType != "AtEnd")
|
|
{
|
|
throw new ServiceException("[障碍期权]补偿支付方式 填写错误,当前支持的类型为:AtHit、AtEnd,请求值为:" + td.RebateType);
|
|
}
|
|
|
|
//补偿金额(根据权利金数据格式进行处理)
|
|
if (td.IsUsePremiumRate == true)
|
|
{
|
|
td.Rebate = 0;
|
|
}
|
|
else
|
|
{
|
|
td.RebateRate = 0;
|
|
}
|
|
|
|
//高障碍补偿金额(根据权利金数据格式进行处理)
|
|
if (td.IsUsePremiumRate == true)
|
|
{
|
|
td.RebateHigh = 0;
|
|
}
|
|
else
|
|
{
|
|
td.RebateHighRate = 0;
|
|
}
|
|
|
|
//补偿金额是否年化选否的时候,清空DayCount
|
|
if (!td.RebateAnnualizedAtKO)
|
|
{
|
|
td.RebateDayCount = "";
|
|
}
|
|
|
|
//障碍偏移
|
|
if (!td.BarrierShift.HasValue)
|
|
{
|
|
td.BarrierShift = 0;
|
|
}
|
|
}
|
|
|
|
//二元期权
|
|
private void CheckBinaryTrade(OtcOptionTradeFull td)
|
|
{
|
|
//---------------------------------------------
|
|
// 二元类型
|
|
//---------------------------------------------
|
|
|
|
if (string.IsNullOrWhiteSpace(td.PayoffType))
|
|
{
|
|
throw new ServiceException("[二元期权]二元类型 必须填写");
|
|
}
|
|
|
|
switch (td.PayoffType)
|
|
{
|
|
default:
|
|
throw new ServiceException("[二元期权]二元类型 不支持:" + td.PayoffType);
|
|
case "CashOrNothing":
|
|
case "AssetOrNothing":
|
|
if (td.ExerciseMode != "European")
|
|
{
|
|
throw new ServiceException("[二元期权]二元类型 填写错误,所选行权方式不支持:" + td.PayoffType);
|
|
}
|
|
break;
|
|
case "UpOneTouch":
|
|
case "DownOneTouch":
|
|
case "UpNoTouch":
|
|
case "DownNoTouch":
|
|
case "DoubleOneTouch":
|
|
case "DoubleNoTouch":
|
|
if (td.ExerciseMode != "American")
|
|
{
|
|
throw new ServiceException("[二元期权]二元类型 填写错误,所选行权方式不支持:" + td.PayoffType);
|
|
}
|
|
break;
|
|
}
|
|
|
|
//补偿金额,高障碍补偿金额(根据权利金数据格式进行处理)
|
|
if (td.IsUsePremiumRate == true)
|
|
{
|
|
td.CashOrNothingAmount = 0;
|
|
td.CashOrNothingAmountHigh = 0;
|
|
}
|
|
else
|
|
{
|
|
td.CashOrNothingAmountRate = 0;
|
|
td.CashOrNothingAmountHighRate = 0;
|
|
}
|
|
|
|
//补偿金额是否年化选否的时候,清空DayCount
|
|
if (!td.RebateAnnualizedAtKO)
|
|
{
|
|
td.RebateDayCount = "";
|
|
}
|
|
|
|
if (td.ExerciseMode == "American")
|
|
{
|
|
if (td.PayoffType.Contains("Double"))
|
|
{
|
|
if (!td.BarrierHigh.HasValue)
|
|
{
|
|
throw new ServiceException($"[二元期权][美式行权,二元类型为{td.PayoffType}时]高障碍价格 必须填写");
|
|
}
|
|
|
|
//高障碍价格BarrierHigh
|
|
}
|
|
|
|
//---------------------------------------------
|
|
// 观察方式
|
|
//---------------------------------------------
|
|
if (string.IsNullOrWhiteSpace(td.MonitorType))
|
|
{
|
|
td.MonitorType = "离散";
|
|
}
|
|
else if (td.MonitorType != "离散" && td.MonitorType != "连续")
|
|
{
|
|
throw new ServiceException("[二元期权]观察方式 填写错误,当前支持的类型为:离散、连续,请求值为:" + td.MonitorType);
|
|
}
|
|
|
|
//---------------------------------------------
|
|
// 补偿支付方式
|
|
//---------------------------------------------
|
|
if (string.IsNullOrWhiteSpace(td.RebateType))
|
|
{
|
|
td.RebateType = "AtHit";
|
|
}
|
|
else if (td.RebateType != "AtHit" && td.RebateType != "AtEnd")
|
|
{
|
|
throw new ServiceException("[二元期权]补偿支付方式 填写错误,当前支持的类型为:AtHit、AtEnd,请求值为:" + td.RebateType);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
td.MonitorType = "";
|
|
td.RebateType = "";
|
|
}
|
|
}
|
|
|
|
//亚式期权
|
|
private void CheckAisanTrade(OtcOptionTradeFull td)
|
|
{
|
|
if (!td.AveragingPeriodStartDate.HasValue)
|
|
{
|
|
td.AveragingPeriodStartDate = td.TradeDate.Value;
|
|
}
|
|
else if (td.AveragingPeriodStartDate.Value < td.TradeDate.Value || td.AveragingPeriodStartDate >= td.ExerciseDate.Value)
|
|
{
|
|
throw new ServiceException("[亚式期权]均价起算日 必须在交易开始日和到期日之间");
|
|
}
|
|
|
|
switch (td.PayoffType)
|
|
{
|
|
default:
|
|
if (!string.IsNullOrWhiteSpace(td.PayoffType))
|
|
{
|
|
throw new ServiceException("[亚式期权]均价计算方式 不支持:" + td.PayoffType);
|
|
}
|
|
td.PayoffType = td.StrikeType == "Segmented" ? "EnhancedArithmeticAverage" : "ArithmeticAverage";
|
|
break;
|
|
case "ArithmeticAverage":
|
|
case "GeometricAverage":
|
|
case "DiscreteArithmeticAverage":
|
|
case "EnhancedArithmeticAverage":
|
|
break;
|
|
}
|
|
|
|
switch (td.StrikeType)
|
|
{
|
|
default:
|
|
if (!string.IsNullOrWhiteSpace(td.StrikeType))
|
|
{
|
|
throw new ServiceException("[亚式期权]行权价类型 不支持:" + td.StrikeType);
|
|
}
|
|
td.StrikeType = "Fixed";
|
|
break;
|
|
case "Fixed":
|
|
break;
|
|
case "Segmented":
|
|
if (td.PayoffType != "EnhancedArithmeticAverage")
|
|
{
|
|
throw new ServiceException("[亚式期权]行权价类型为'分段式'时,均价计算类型只支持'增强算术平均'");
|
|
}
|
|
break;
|
|
case "Floating":
|
|
td.Strike = null;
|
|
break;
|
|
}
|
|
|
|
if (!td.StrikeGearingFactor.HasValue)
|
|
{
|
|
td.StrikeGearingFactor = 1;
|
|
}
|
|
else if (td.StrikeGearingFactor < 0)
|
|
{
|
|
throw new ServiceException("[亚式期权]杠杆率 必须大于等于0");
|
|
}
|
|
}
|
|
|
|
//双鲨期权
|
|
private void CheckDoubleSharkTrade(OtcOptionTradeFull td)
|
|
{
|
|
//BarrierLow 障碍价格,BarrierHigh 高障碍价格
|
|
|
|
if (!td.BarrierHigh.HasValue)
|
|
{
|
|
throw new ServiceException("[双鲨期权]高障碍价格 必须填写");
|
|
}
|
|
|
|
if (td.BarrierHigh <= td.BarrierLow)
|
|
{
|
|
throw new ServiceException("[双鲨期权]高障碍价格 必须大于障碍价格");
|
|
}
|
|
|
|
//补偿金额+高障碍补偿金额(根据权利金数据格式进行处理)
|
|
if (td.IsUsePremiumRate == true)
|
|
{
|
|
td.Rebate = 0;
|
|
td.RebateHigh = 0;
|
|
}
|
|
else
|
|
{
|
|
td.RebateRate = 0;
|
|
td.RebateHighRate = 0;
|
|
}
|
|
|
|
//---------------------------------------------
|
|
// 观察方式
|
|
//---------------------------------------------
|
|
if (string.IsNullOrWhiteSpace(td.MonitorType))
|
|
{
|
|
td.MonitorType = "离散";
|
|
}
|
|
else if (td.MonitorType != "离散" && td.MonitorType != "连续")
|
|
{
|
|
throw new ServiceException("[双鲨期权]观察方式 填写错误,当前支持的类型为:离散、连续,请求值为:" + td.MonitorType);
|
|
}
|
|
|
|
//---------------------------------------------
|
|
// 补偿支付方式
|
|
//---------------------------------------------
|
|
if (string.IsNullOrWhiteSpace(td.RebateType))
|
|
{
|
|
td.RebateType = "AtHit";
|
|
}
|
|
else if (td.RebateType != "AtHit" && td.RebateType != "AtEnd")
|
|
{
|
|
throw new ServiceException("[双鲨期权]补偿支付方式 填写错误,当前支持的类型为:AtHit、AtEnd,请求值为:" + td.RebateType);
|
|
}
|
|
}
|
|
|
|
//区间累积期权
|
|
private void CheckRangeAccTrade(OtcOptionTradeFull td)
|
|
{
|
|
//区间下限 LowerRange,区间上限 UpperRange
|
|
|
|
if (td.UpperRange < td.LowerRange)
|
|
{
|
|
throw new ServiceException("[区间累积期权]区间下限 必须小于 区间上限");
|
|
}
|
|
|
|
if (td.BonusRate < 0)
|
|
{
|
|
throw new ServiceException("[区间累积期权]区间收益 必须大于等于0");
|
|
}
|
|
}
|
|
|
|
//凤凰期权
|
|
private void CheckAutoCallTrade(OtcOptionTradeFull td)
|
|
{
|
|
//敲出障碍价格 KOBarrier,敲入障碍价格:KIBarrier,派息障碍价格 CouponBarrier
|
|
//敲入期权行权价2 SpreadStrike,敲入期权行权价1 SpreadStrike1
|
|
|
|
if (td.Coupon < 0)
|
|
{
|
|
throw new ServiceException("[凤凰期权]票息/敲出补偿 必须大于等于0");
|
|
}
|
|
}
|
|
|
|
//雪球期权
|
|
private void CheckSnowballTrade(OtcOptionTradeFull td)
|
|
{
|
|
if (td.KORebate < 0)
|
|
{
|
|
throw new ServiceException("[雪球期权]票息/敲出补偿 必须大于等于0");
|
|
}
|
|
|
|
if (td.Coupon < 0)
|
|
{
|
|
throw new ServiceException("[雪球期权]未敲出补偿 必须大于等于0");
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(td.KOObservationDates) && !td.KOObservationDates.All(n => n == ';') && !td.KOObservationDates.Contains(td.ExerciseDate.Value.ToString("yyyy-MM-dd")))
|
|
{
|
|
//throw new ServiceException($"[雪球期权]敲出观察频率 必须包含到期日({td.ExerciseDate.Value:yyyy-MM-dd})");
|
|
throw new ServiceException($"[雪球期权]到期日和最后一个敲出观察日不一致,请修改敲出观察日列表");
|
|
}
|
|
}
|
|
|
|
//气囊结构
|
|
private void CheckAirbagTrade(OtcOptionTradeFull td)
|
|
{
|
|
//敲入参与率
|
|
if (td.KIParticipationRate < 0)
|
|
{
|
|
throw new ServiceException("[气囊结构]敲入参与率 必须大于等于0");
|
|
}
|
|
|
|
//障碍价格 BarrierLow,收益封顶价格 HighStrike
|
|
}
|
|
|
|
//收益增强结构
|
|
private void CheckUnderlyigEnhanceTrade(OtcOptionTradeFull td)
|
|
{
|
|
if (td.AnnualizedEnhanceRate < 0)
|
|
{
|
|
throw new ServiceException("[收益增强结构]年化增强收益 必须大于等于0");
|
|
}
|
|
}
|
|
|
|
//累计期权
|
|
private void CheckAccumulatorTrade(OtcOptionTradeFull td)
|
|
{
|
|
|
|
switch (td.PayoffType)
|
|
{
|
|
default:
|
|
if (!string.IsNullOrWhiteSpace(td.PayoffType))
|
|
{
|
|
throw new ServiceException("[累计期权]上端收益类型 不支持:" + td.PayoffType);
|
|
}
|
|
td.PayoffType = "浮动";
|
|
break;
|
|
case "固定":
|
|
case "浮动": break;
|
|
}
|
|
|
|
switch (td.SettlementMode)
|
|
{
|
|
default:
|
|
if (!string.IsNullOrWhiteSpace(td.SettlementMode))
|
|
{
|
|
throw new ServiceException("[累计期权]结算方式 不支持:" + td.SettlementMode);
|
|
}
|
|
td.SettlementMode = "现金当日";
|
|
break;
|
|
case "现金当日":
|
|
case "现金期末":
|
|
case "实物交割":
|
|
case "现金实物合并结算":
|
|
break;
|
|
case "现金结算(当日)":
|
|
td.SettlementMode = "现金当日"; break;
|
|
case "现金结算(期末)":
|
|
td.SettlementMode = "现金期末"; break;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(td.CouponDayCount))
|
|
{
|
|
td.CouponDayCount = "Act365";
|
|
}
|
|
else
|
|
{
|
|
var daycounts = Enum.GetNames(typeof(Qdp.Pricing.Base.Enums.DayCount));
|
|
if (!daycounts.Any(n => n.Equals(td.CouponDayCount, StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
throw new ServiceException("[累计期权]票息日历规则 不支持:" + td.CouponDayCount);
|
|
}
|
|
}
|
|
if (td.AccumulatorStructureType== AccumulatorStructureTypeEnum.Segmented)
|
|
{
|
|
switch (td.SettlementMode2)
|
|
{
|
|
default:
|
|
if (!string.IsNullOrWhiteSpace(td.SettlementMode2))
|
|
{
|
|
throw new ServiceException("[累计期权]结算方式 不支持:" + td.SettlementMode2);
|
|
}
|
|
td.SettlementMode2 = "现金当日";
|
|
break;
|
|
case "现金当日":
|
|
case "现金期末":
|
|
case "实物交割": break;
|
|
case "现金实物合并结算":
|
|
break;
|
|
case "现金结算(当日)":
|
|
td.SettlementMode2 = "现金当日"; break;
|
|
}
|
|
|
|
switch (td.SettlementMode3)
|
|
{
|
|
default:
|
|
if (!string.IsNullOrWhiteSpace(td.SettlementMode3))
|
|
{
|
|
throw new ServiceException("[累计期权]结算方式 不支持:" + td.SettlementMode3);
|
|
}
|
|
td.SettlementMode3 = "现金当日";
|
|
break;
|
|
case "现金当日":
|
|
case "现金期末":
|
|
case "实物交割": break;
|
|
case "现金实物合并结算":
|
|
break;
|
|
case "现金结算(当日)":
|
|
td.SettlementMode3 = "现金当日"; break;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
//Risky期权
|
|
private void CheckRiskyTrade(OtcOptionTradeFull td)
|
|
{
|
|
if (td.Strike1 == null)
|
|
{
|
|
throw new ServiceException("[Risky期权]执行价格1 必须填写");
|
|
}
|
|
if (td.Strike2 == null)
|
|
{
|
|
throw new ServiceException("[Risky期权]执行价格2 必须填写");
|
|
}
|
|
if (td.Strike3 == null)
|
|
{
|
|
throw new ServiceException("[Risky期权]执行价格3 必须填写");
|
|
}
|
|
if (td.ParticipationRate1 == null)
|
|
{
|
|
throw new ServiceException("[Risky期权]参与率1 必须填写");
|
|
}
|
|
if (td.ParticipationRate2 == null)
|
|
{
|
|
throw new ServiceException("[Risky期权]参与率2 必须填写");
|
|
}
|
|
if (td.ParticipationRate3 == null)
|
|
{
|
|
throw new ServiceException("[Risky期权]参与率3 必须填写");
|
|
}
|
|
if (td.Strike1 > td.Strike2)
|
|
{
|
|
throw new ServiceException("[Risky期权]执行价格1 必须小于等于执行价格2");
|
|
}
|
|
if (td.Strike2 > td.Strike3)
|
|
{
|
|
throw new ServiceException("[Risky期权]执行价格2 必须小于等于执行价格3");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 为了解决导入时某些可用null类型判断的属性被人为设定成必然不为null,比如TradePrice
|
|
/// </summary>
|
|
enum OtcOptionTradeFullHasFlag
|
|
{
|
|
None = 0,
|
|
|
|
PremiumRate = 1 << 0,
|
|
|
|
TradeSinglePrice = 1 << 1,
|
|
|
|
TradePrice = 1 << 2
|
|
}
|
|
|
|
/// <summary>
|
|
/// 场外期权全量(API和导入用到)
|
|
/// </summary>
|
|
public class OtcOptionTradeFullEx : OtcOptionTradeFull
|
|
{
|
|
/// <summary>
|
|
/// 是否更新已有数据
|
|
/// </summary>
|
|
public bool _Update { get; set; }
|
|
|
|
/// <summary>
|
|
/// 了结信息
|
|
/// </summary>
|
|
public TradeCloseRequestModel CloseRequest { get; set; }
|
|
|
|
/// <summary>
|
|
/// 组合标的价格列表
|
|
/// </summary>
|
|
public IEnumerable<UnderlyingPriceModel> SyntheticPrices { get; set; }
|
|
|
|
/// <summary>
|
|
/// HasFlag
|
|
/// </summary>
|
|
internal OtcOptionTradeFullHasFlag HasFlag { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 组合交易下单请求
|
|
/// </summary>
|
|
public class StructureOrderRequest
|
|
{
|
|
public string StructureType { get; set; }
|
|
|
|
public IEnumerable<OtcOptionTradeFullEx> Trades { get; set; }
|
|
}
|
|
}
|
|
|
|
//if (td.IsUsePremiumRate == true)
|
|
//{
|
|
// td.StockEqvNotionalMax = TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor);
|
|
//}
|
|
//else
|
|
//{
|
|
// td.StockEqvNotionalReal = spotPrice * td.Notional;
|
|
// td.StockEqvNotional = TradeHelper.GetStockEqvNotional(td.StockEqvNotionalReal, td.ParticipationRate, td.AnnualizeFactor);
|
|
//} |