依据 outputs/zszq-trs-pricing-structure-dz-死代码分析报告.md 第一轮零风险清理。 国联分支下 Is润和 恒为 false,Structure_DZ 无任何菜单/代码入口。 单独 revert 本 commit 即可整体找回。
950 lines
38 KiB
C#
950 lines
38 KiB
C#
using Qdp.Foundation.Implementations;
|
|
using Qdp.Pricing.Base.Enums;
|
|
using Qdp.Pricing.Base.Implementations;
|
|
using Qdp.Pricing.Library.Equity.Engines.Analytical;
|
|
using System.Text.RegularExpressions;
|
|
using YLErp.BLL.MarginCalculation;
|
|
using YLErp.Configuration;
|
|
using YLErp.DBModels;
|
|
using YLErp.DBModels.Consts;
|
|
using YLErp.Enums;
|
|
using YLErp.Modules.CalculationModule;
|
|
using YLErp.Modules.DataProviderModule;
|
|
using YLErp.Modules.PricingModule;
|
|
using YLErp.Modules.PricingModule.Models;
|
|
using YLErp.Modules.StructureModule;
|
|
using YLErp.Modules.SystemModule;
|
|
using YLErp.Modules.TagModule;
|
|
using YLErp.Modules.TradeModule;
|
|
using YLErp.Modules.TradeModule.OrderModule;
|
|
using YLErp.Modules.UnderlyingModule;
|
|
using YLErp.Modules.VolatilityModule;
|
|
using YLErp.Office;
|
|
using YLErp.QdpModule;
|
|
|
|
namespace YLErp.Web.Controllers
|
|
{
|
|
public class PricingController : BaseController
|
|
{
|
|
/// <summary>
|
|
/// 组合报价
|
|
/// </summary>
|
|
[MyAuthorize("报价管理-结构化交易定价")]
|
|
public ActionResult Structure()
|
|
{
|
|
var otcTrade = new OtcOptionTradeFull()
|
|
{
|
|
TraderId = CurUser.UserId,
|
|
TraderName = CurUser.UserName,
|
|
BuySell = "卖出",
|
|
VolType = "交易",
|
|
TradeType = "香草期权",
|
|
OptionType = "看涨",
|
|
ExerciseMode = "European",
|
|
TradeDate = valuedateBLL.ValueDate,
|
|
UnderlyingInstrumentType = AppHelper.OtcConfig.StockFirst ? "Stock" : "CommodityFutures",
|
|
SettlementType = (int)SettlementTypeEnum.ClosePrice,
|
|
NoRiskRate = valuedateBLL.SystemDate.RiskFreeRate / 100,
|
|
ParticipationRate = 1,
|
|
AnnualizeFactor = 1,
|
|
MarginTemplateName = "系统默认",
|
|
CouponIncludeStartDate = false,
|
|
CouponUsePaymentDate = false
|
|
};
|
|
|
|
var model = new Models.PricingModel(CurUser, UserBLL.IsTradeOfCurrentLogin(CurUser.UserId)) { Trade = otcTrade };
|
|
|
|
if (model.NumOfSmoothingDaysCfg == "ONE")
|
|
{
|
|
model.Trade.NumOfSmoothingDays = 1;
|
|
}
|
|
//获取自定义结构信息
|
|
var structureTypes =
|
|
new StructureService(CurUser)
|
|
.QueryStructureMap(StructureRangeEnum.BALCK_TRADE);
|
|
var structureTypeMap = new Dictionary<string, List<Structure_Details>>() {
|
|
{ "气囊结构",new List<Structure_Details>() }
|
|
};
|
|
foreach (var item in structureTypes)
|
|
{
|
|
structureTypeMap[item.Key] = item.Value;
|
|
}
|
|
ViewBag.StructureTypeMap = structureTypeMap;
|
|
var map = new StructureService(CurUser)
|
|
.QueryStructureMap(StructureRangeEnum.EXTEND_INFO);
|
|
ViewBag.ExtendInfoMap = new Dictionary<string, List<Structure_Details>>();
|
|
ViewBag.ExtendInfoMap[""] = new List<Structure_Details>();
|
|
foreach (var item in map)
|
|
{
|
|
ViewBag.ExtendInfoMap[item.Key] = item.Value;
|
|
}
|
|
using (var tagService = new TagService(CurUser))
|
|
{
|
|
ViewBag.TagList = tagService.GetTagListByType(TagTypeEnum.Trade);
|
|
}
|
|
return View(model);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 组合报价导入
|
|
/// </summary>
|
|
public ActionResult StructureImport(string EncryptId)
|
|
{
|
|
var tradeId = DecryptInt(EncryptId);
|
|
|
|
var otcTrade = new TradeDetailsQueryService(CurUser).GetOtcTradeFull(tradeId);
|
|
|
|
if (otcTrade == null)
|
|
{
|
|
return ShowError("没有找到交易数据");
|
|
}
|
|
|
|
if (!otcTrade.IsOption() && otcTrade.TradeType != "结构化产品")
|
|
{
|
|
return ShowError("期权定价只支持期权交易");
|
|
}
|
|
|
|
ResetOtcOptionTrade(otcTrade);
|
|
|
|
otcTrade.ValueDate = valuedateBLL.ValueDate;
|
|
if (PS.Config.IsTradeVol && PS.Config.Company == CompanyEnum.厦门象屿)
|
|
{
|
|
var daycountMode = PS.Config.ErpElement.SmoothingDaycountMode == Configuration.Enums.SmoothingDaycountMode.CalendarDay
|
|
? DayCountMode.CalendarDay : DayCountMode.TradingDay;
|
|
|
|
//新增交易当天的持仓波动率需要划掉一天,修改后的持仓波动率不需要再划一天
|
|
var vol = AnalyticalOptionTradeVolInterp.tradeVolLinearInterp(
|
|
valuationDate: new Qdp.Foundation.Implementations.Date(otcTrade.ValueDate),
|
|
tradeOpenVol: otcTrade.TradeOpenVolatility ?? 0,
|
|
tradeCloseVol: otcTrade.TradeCloseVolatility ?? 0,
|
|
startDate: new Qdp.Foundation.Implementations.Date(otcTrade.StartDate.Value),
|
|
maturityDate: new Qdp.Foundation.Implementations.Date(otcTrade.ExerciseDate.Value),
|
|
numOfSmoothingDays: otcTrade.NumOfSmoothingDays ?? 0,
|
|
dayCountMode: daycountMode,
|
|
calendar: CalendarImpl.Get("chn"),
|
|
includeStartDate: false);
|
|
otcTrade.TradeOpenVolatility = otcTrade.TradeCloseVolatility = vol;
|
|
}
|
|
|
|
var model = new Models.PricingModel(CurUser, UserBLL.IsTradeOfCurrentLogin(CurUser.UserId)) { IsImport = true, Trade = otcTrade };
|
|
|
|
//获取自定义结构信息
|
|
var structureTypes =
|
|
new StructureService(CurUser)
|
|
.QueryStructureMap(StructureRangeEnum.BALCK_TRADE);
|
|
var structureTypeMap = new Dictionary<string, List<Structure_Details>>() {
|
|
{ "气囊结构",new List<Structure_Details>() }
|
|
};
|
|
var dict = new Dictionary<string, List<Structure_Details>>();
|
|
foreach (var item in structureTypes)
|
|
{
|
|
structureTypeMap[item.Key] = item.Value;
|
|
}
|
|
ViewBag.StructureTypeMap = structureTypeMap;
|
|
var map = new StructureService(CurUser)
|
|
.QueryStructureMap(StructureRangeEnum.EXTEND_INFO);
|
|
ViewBag.ExtendInfoMap = new Dictionary<string, List<Structure_Details>>();
|
|
ViewBag.ExtendInfoMap[""] = new List<Structure_Details>();
|
|
foreach (var item in map)
|
|
{
|
|
ViewBag.ExtendInfoMap[item.Key] = item.Value;
|
|
}
|
|
|
|
return View(nameof(Structure), model);
|
|
}
|
|
|
|
#region----定价模板----
|
|
|
|
/// <summary>
|
|
/// 加载定价模板
|
|
/// </summary>
|
|
public JsonResult AjaxGetTemplateList()
|
|
{
|
|
var datas = new SysUserConfigService(CurUser).GetConfigInfos();
|
|
return JsonSuccessData(datas);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 加载定价模板
|
|
/// </summary>
|
|
public JsonResult AjaxGetTemplate(string name, string TemplateType)
|
|
{
|
|
var t_Type = SysUserConfigType.PricingTemplateV2;
|
|
if (TemplateType == "公共模板")
|
|
{
|
|
t_Type = SysUserConfigType.CommonTemplate;
|
|
}
|
|
var configData = new SysUserConfigService(CurUser).GetConfigData(t_Type, name);
|
|
if (string.IsNullOrWhiteSpace(configData))
|
|
{
|
|
return JsonError("模板数据不存在");
|
|
}
|
|
return JsonSuccessData(configData);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 删除定价模板
|
|
/// </summary>
|
|
public JsonResult AjaxRemoveTemplate(int sysUserConfigId)
|
|
{
|
|
if (sysUserConfigId == 0)
|
|
{
|
|
return JsonError("请求参数为空");
|
|
}
|
|
new SysUserConfigService(CurUser).RemoveData(sysUserConfigId, SysUserConfigType.PricingTemplateV2, Server.CacheProvider);
|
|
return JsonSuccess("成功删除");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保存定价模板
|
|
/// </summary>
|
|
/// <param name="name"></param>
|
|
/// <param name="dataJson"></param>
|
|
/// <param name="_override">是否可以覆盖</param>
|
|
/// <param name="_CommonTemplate">公共模板</param>
|
|
/// <returns></returns>
|
|
public JsonResult AjaxSaveTemplate(string name, string dataJson, bool _override = true, bool _CommonTemplate = false)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(name))
|
|
{
|
|
return JsonError("错误参数:name");
|
|
}
|
|
|
|
try
|
|
{
|
|
new SysUserConfigService(CurUser).SaveData(SysUserConfigType.PricingTemplateV2, name, dataJson, Server.CacheProvider, enableOverride: _override, enableCommtemplate: _CommonTemplate);
|
|
}
|
|
catch (ServiceException ex)
|
|
{
|
|
if (ex.Message.Contains("覆盖"))
|
|
{
|
|
return JsonError("模板已经存在,请填写其它名称或着选择覆盖已存在的模板");
|
|
}
|
|
}
|
|
|
|
return JsonSuccess("保存成功");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 管理模板列表
|
|
/// </summary>
|
|
public ActionResult TemplateList()
|
|
{
|
|
return View();
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// 根据报价或定价的参数,获取一个波动率的值,只在Normal模式下使用。
|
|
/// SkewMap模式使用GetBaseVolValue
|
|
/// </summary>
|
|
public JsonResult AjaxGetVol(SingleVolReq req)
|
|
{
|
|
var userGroup = UserBLL.GetUserGroup(UserId);
|
|
if (PS.Config.ErpElement.SkewMapVolConstruction)
|
|
{
|
|
try
|
|
{
|
|
if (req.IsMoneynessOption == "是")
|
|
{
|
|
req.Strike = req.SpotPrice * req.Strike;
|
|
}
|
|
|
|
var volType = req.VolType;
|
|
|
|
req.Vols = VolatilityHelper.GetVol(req.TradeDate, "交易", req.UnderlyingCode, userGroup);
|
|
if (req.Vols == null || string.IsNullOrEmpty(req.Vols.VolSurfaceMode))
|
|
{
|
|
throw new InvalidOperationException($"找不到波动率曲面{req.UnderlyingCode}");
|
|
}
|
|
|
|
if (req.BaseVol == null || req.BaseVol <= 0)
|
|
{
|
|
req.BaseVol = SkewMapVolHelper.GetSkewMapBaseVolForTrade(req.TradeDate, req.UnderlyingCode, req.Vols, req.UnderlyingTypeId, req.ExerciseDate);
|
|
}
|
|
|
|
int? varValue = null;
|
|
|
|
switch (volType)
|
|
{
|
|
case "报价Ask":
|
|
varValue = req.AskVar;
|
|
break;
|
|
case "报价Bid":
|
|
varValue = req.BidVar;
|
|
break;
|
|
}
|
|
|
|
if (!varValue.HasValue)
|
|
{
|
|
var vol = VolatilityHelper.GetVol(req.TradeDate, "交易", req.UnderlyingCode, CurUser.UserGroup);
|
|
if (vol == null)
|
|
{
|
|
return JsonError("找不到波动率:" + req.UnderlyingCode);
|
|
}
|
|
switch (volType)
|
|
{
|
|
case "报价Ask":
|
|
req.AskVar = varValue = (int)(vol.GetAskVar() ?? 0);
|
|
break;
|
|
case "报价Bid":
|
|
req.BidVar = varValue = (int)(vol.GetBidVar() ?? 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
req.VolType = volType;
|
|
|
|
var TargetVol = SingleVolService.GetSingleVol(req, UserId).ToString();
|
|
|
|
return JsonSuccessData(new
|
|
{
|
|
var = varValue,
|
|
vol = TargetVol,
|
|
baseVol = req.BaseVol
|
|
});
|
|
}
|
|
catch (ArgumentException ex)
|
|
{
|
|
if (Regex.IsMatch(ex.Message, @"列.\d+.不属于表"))
|
|
{
|
|
return JsonError("Var值超出SkewMap可选范围");
|
|
}
|
|
return JsonError(ex.Message);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return JsonError(ex.Message);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ConsUserGroup.HasGroup && string.IsNullOrWhiteSpace(userGroup))
|
|
{
|
|
return JsonSuccessData(new { vol = 0 });
|
|
}
|
|
var vol = SingleVolService.GetSingleVol(req, UserId);
|
|
if (PS.Config.Is润和)
|
|
{
|
|
req.VolType = "交易";
|
|
var Midvol = SingleVolService.GetSingleVol(req, UserId);
|
|
return JsonSuccessData(new { vol = vol, Midvol = Midvol });
|
|
}
|
|
return JsonSuccessData(new { vol });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取一个标的信息
|
|
/// </summary>
|
|
public JsonResult AjaxGetUnderlying(UnderlyingGetRequest req)
|
|
{
|
|
underlying_manager underlying = null;
|
|
|
|
if (!string.IsNullOrWhiteSpace(req.UnderlyingCode))
|
|
{
|
|
underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(req.UnderlyingCode);
|
|
}
|
|
else
|
|
{
|
|
var query = DataCacheProvider.GetUnderlyingDataSource().AsQueryable()
|
|
.Where(n => n.LaunchState == "1" && n.UnderlyingState != "Matured");
|
|
if (!string.IsNullOrWhiteSpace(req.InstrumentType))
|
|
{
|
|
underlying = query.FirstOrDefault(n => n.UnderlyingInstrumentType == req.InstrumentType);
|
|
}
|
|
else if (req.VarietyId > 0)
|
|
{
|
|
underlying = query.FirstOrDefault(n => n.UnderlyingTypeId == req.VarietyId);
|
|
}
|
|
}
|
|
|
|
if (underlying != null)
|
|
{
|
|
// 获取不超过到期日的实行日
|
|
// 1个月,2周,1周,到日期这样的规则向前计算
|
|
if (!req.TradeDate.HasValue)
|
|
{
|
|
req.TradeDate = valuedateBLL.ValueDate;
|
|
}
|
|
var MaturityDate = underlying.MaturityDate ?? DateTime.Today;
|
|
if (underlying.CalcTypeIsStock() || underlying.IsCommoditySpot())
|
|
{
|
|
MaturityDate = DateTime.Today.AddYears(3);
|
|
}
|
|
underlying.ExerciseDate = QdpCalendarHelper.GetUnderlyingExerciseDate(MaturityDate, req.TradeDate.Value);
|
|
SyntheticPriceModel synthetic = null;
|
|
if (underlying.CommodityCode == "组合标的")
|
|
{
|
|
//返回组合标的中组成标的的现价
|
|
synthetic = new SyntheticUnderlyingPriceService(CurUser).GetPriceModel(underlying.UnderlyingCode);
|
|
underlying.Price = synthetic.Price;
|
|
}
|
|
|
|
var variety = DataCacheProvider.GetVariety(underlying.UnderlyingCode);
|
|
underlying.CountRatio = variety != null ? variety.CountRatio : 1;
|
|
return JsonSuccessData(new { underlying, synthetic });
|
|
}
|
|
|
|
return JsonError("标的信息缺失");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取组合标的价格模型
|
|
/// </summary>
|
|
public JsonResult AjaxGetSyntheticPriceModel(string underlyingCode, DateTime? tradeDate = null)
|
|
{
|
|
var synthetic = new SyntheticUnderlyingPriceService(CurUser).GetPriceModel(underlyingCode);
|
|
if (synthetic != null && tradeDate != null && tradeDate != valuedateBLL.ValueDate
|
|
&& EodPriceQueryService.TryGetEodPrice(tradeDate.Value, underlyingCode, out var eodPrice))
|
|
{
|
|
synthetic.Price = eodPrice.ClosePrice;
|
|
}
|
|
return JsonSuccessData(synthetic);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取系统设定的无风险利率
|
|
/// </summary>
|
|
public JsonResult AjaxGetNoRiskRate(DateTime? startDate, DateTime? endDate)
|
|
{
|
|
var riskFreeRate = valuedateBLL.GetRiskFreeRateFromCurve(startDate, endDate);
|
|
return JsonSuccessData(riskFreeRate / 100);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取系统设定的股票分红率
|
|
/// </summary>
|
|
public JsonResult AjaxGetDividendRate(string underlyingCode, DateTime tradeDate, string tradetype, string optiontype)
|
|
{
|
|
if (string.IsNullOrEmpty(underlyingCode))
|
|
{
|
|
return JsonSuccessData(0);
|
|
}
|
|
|
|
var list = yldb.dividendrate_record.Where(x => x.TradeType.Contains(tradetype) && tradeDate >= x.ValueDate && (x.OptionType == optiontype || x.OptionType == "全部")).ToList();
|
|
var record = list.Where(x => x.UnderlyingCode.Split(',').Any(code => code == underlyingCode)).OrderByDescending(x => x.OptDate).OrderByDescending(x => x.ValueDate);
|
|
if (record.Any())
|
|
{
|
|
return JsonSuccessData(record.FirstOrDefault()?.DividendRate ?? 0);
|
|
}
|
|
|
|
tradeDate = tradeDate.Date;
|
|
|
|
var query = from n in yldb.underlying_manager
|
|
join m in yldb.UnderlyingDividend.Where(a => a.ValueDate >= tradeDate)
|
|
on n.id equals m.UnderlyingId into ms
|
|
from m in ms.DefaultIfEmpty()
|
|
where n.UnderlyingCode == underlyingCode
|
|
select m == null ? n.DividendRate : m.DividendRate;
|
|
|
|
return JsonSuccessData(query.FirstOrDefault() ?? 0);
|
|
}
|
|
|
|
public JsonResult AjaxGetExchangeOptionPrice(string optionCode)
|
|
{
|
|
var provider = new ExchangeOptionPriceProvider();
|
|
var price = provider.GetPrice(optionCode);
|
|
|
|
return JsonSuccessData(price);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取标的价格
|
|
/// </summary>
|
|
public JsonResult AjaxGetUnderlyingPrice(string underlyingCode, DateTime? tradeDate = null)
|
|
{
|
|
double price = 0;
|
|
double netPrice = 0;
|
|
var udm = DataCacheProvider.GetUnderlyingDataSource().GetData(underlyingCode);
|
|
|
|
SyntheticPriceModel synthetic = null;
|
|
|
|
if (udm != null)
|
|
{
|
|
if (udm.CommodityCode == "组合标的")
|
|
{
|
|
using var syntheService = new SyntheticUnderlyingPriceService(CurUser);
|
|
if (tradeDate != null)
|
|
{
|
|
synthetic = syntheService.GetPriceModel(underlyingCode, Convert.ToDateTime(tradeDate));
|
|
if (synthetic != null)
|
|
{
|
|
price = synthetic.Price;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
synthetic = syntheService.GetPriceModel(underlyingCode);
|
|
price = synthetic.Price;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
price = udm.Price ?? 0;
|
|
netPrice = price;
|
|
if (udm.IsBond())
|
|
{
|
|
if (EodPriceQueryService.TryGetBondEodPrice(valuedateBLL.ValueDate, underlyingCode, out var eodPrice))
|
|
{
|
|
price = eodPrice.ClosePrice;
|
|
netPrice = eodPrice.SettlePrice;
|
|
}
|
|
else
|
|
{
|
|
price = price * Convert.ToDouble(ConsGlobal.bondPriceMultiple);
|
|
netPrice = price;
|
|
}
|
|
}
|
|
|
|
}
|
|
if (tradeDate != null && tradeDate != valuedateBLL.ValueDate)
|
|
{
|
|
var date = Convert.ToDateTime(tradeDate);
|
|
if (udm.IsBond())
|
|
{
|
|
if (EodPriceQueryService.TryGetBondEodPrice(date, underlyingCode, out var eodPrice))
|
|
{
|
|
price = eodPrice.ClosePrice;
|
|
netPrice = eodPrice.SettlePrice;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (EodPriceQueryService.TryGetEodPrice(date, underlyingCode, out var eodPrice))
|
|
{
|
|
price = eodPrice.ClosePrice;
|
|
netPrice = eodPrice.SettlePrice;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return JsonSuccessData(new { price, synthetic, netPrice });
|
|
}
|
|
|
|
//期权定价计算(带预付金计算)
|
|
public JsonResult AjaxCalcPrices(IEnumerable<OtcOptionTradeFull> trades, bool calcMargin, bool calcAutocallGreeks = false)
|
|
{
|
|
if (trades is null || !trades.Any())
|
|
{
|
|
return JsonSuccessData(Enumerable.Empty<TradeValueResult>());
|
|
}
|
|
var service = new PriceCalcService(CurUser);
|
|
var resultList = service.CalcOptionPrice(trades, calcMargin
|
|
, CalcScenarioEnum.Pricing, td => (td.TradeType != "凤凰期权" && td.TradeType != "雪球期权") || calcAutocallGreeks
|
|
, calcVersion: Request.Form["version"]);
|
|
return JsonSuccessData(resultList);
|
|
}
|
|
|
|
//期权定价计算(带预付金计算)
|
|
public JsonResult AjaxCalcPrice(OtcOptionTradeFull trade)
|
|
{
|
|
if (trade is null)
|
|
{
|
|
return JsonError("错误:请求参数为空");
|
|
}
|
|
var result = new PriceCalcService(CurUser).CalcOptionPrice(trade, false, CalcScenarioEnum.Pricing, false);
|
|
|
|
result.calcResult.Pv = result.calcResult.Pv - (trade.StockEqvNotional * (trade.PrepaymentRatio ?? 0) * (trade.BuySell == "卖出" ? -1 : 1));
|
|
result.calcResult.RoundedPv = result.calcResult.RoundedPv - (trade.StockEqvNotional * (trade.PrepaymentRatio ?? 0) * (trade.BuySell == "卖出" ? -1 : 1));
|
|
|
|
return JsonSuccessData(new { result.calcResult, result.Day1Pnl });
|
|
}
|
|
|
|
public JsonResult AjaxCalcTrades(CalcTradesRequest request)
|
|
{
|
|
var result = new PriceCalcService(CurUser).CalcTrades(request);
|
|
|
|
return JsonSuccessData(result);
|
|
}
|
|
|
|
public JsonResult AjaxCalcSwapTrades(CalcTradesRequest request)
|
|
{
|
|
var result = new PriceCalcService(CurUser).CalcSwapTrades(request);
|
|
|
|
return JsonSuccessData(result);
|
|
}
|
|
|
|
//保本雪球计算年化期权费率
|
|
public JsonResult AjaxCalcSnowballAnnualPremium(OtcOptionTradeFull trade)
|
|
{
|
|
if (trade is null)
|
|
{
|
|
return JsonError("错误:请求参数为空");
|
|
}
|
|
var result = new PriceCalcService(CurUser).CalcSnowballAnnualPremium(trade);
|
|
return JsonSuccessData(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据权利金反算雪球票息
|
|
/// </summary>
|
|
public JsonResult AjaxCalcSnowballKORebate(OtcOptionTradeFull trade)
|
|
{
|
|
if (trade is null)
|
|
{
|
|
return JsonError("错误:请求参数为空");
|
|
}
|
|
var result = new PriceCalcService(CurUser).CalcSnowballKORebate(trade);
|
|
return JsonSuccessData(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据权利金反算凤凰票息
|
|
/// </summary>
|
|
/// <param name="trade"></param>
|
|
/// <returns></returns>
|
|
public JsonResult AjaxCalcPhoenixCouponRate(OtcOptionTradeFull trade)
|
|
{
|
|
if (trade is null)
|
|
{
|
|
return JsonError("错误:请求参数为空");
|
|
}
|
|
var result = new PriceCalcService(CurUser).CalcPhoenixCouponRate(trade);
|
|
return JsonSuccessData(result);
|
|
}
|
|
|
|
//获取初始预付金
|
|
public JsonResult AjaxGetInitialMargin(OtcOptionTradeFull trade)
|
|
{
|
|
if (trade is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(trade));
|
|
}
|
|
|
|
try
|
|
{
|
|
var tdConv = TradeConverter.ConvertOptionTrade(trade);
|
|
var realTradeId = tdConv.id;
|
|
tdConv.id = 0;
|
|
tdConv.OptId = UserId;
|
|
tdConv.VolType = "报价Bid";
|
|
tdConv.TradeCloseVolatility = null;//不设置为null会影响计算结果
|
|
if (!tdConv.TTMDays.HasValue)
|
|
{
|
|
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(tdConv.UnderlyingCode);
|
|
tdConv.TTMDays = TradeCalcHelper.CalculateTTMDays(tdConv.TradeDate.Value, tdConv.ExerciseDate.Value,
|
|
underlying?.UnderlyingTypeId ?? 0, PS.Config.ErpElement.PrecisionOfMinuteInQuote);
|
|
}
|
|
var initialMargin = MarginDefault.GetInitialMargin(tdConv, realTradeId, true);
|
|
return JsonSuccessData(initialMargin);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogFactory.GetLogger("AjaxGetInitialMargin").Error(ex);
|
|
return JsonError("获取初始预付金失败:" + ex.Message);
|
|
}
|
|
}
|
|
|
|
//保存交易
|
|
public JsonResult AjaxSaveTrades(IEnumerable<OtcOptionTradeFull> trades)
|
|
{
|
|
if (trades is null || !trades.Any())
|
|
{
|
|
return JsonError("没有交易数据");
|
|
}
|
|
if (PS.Config.Is润和)
|
|
{
|
|
foreach (var x in trades)
|
|
{
|
|
x.MetaDic["MidVol"] = x.MidVol.ToString();
|
|
x.MetaDic["Day1Pnl"] = x.Day1Pnl.ToString();
|
|
}
|
|
}
|
|
|
|
var dbTrades = new OtcTradeSaveService(CurUser).SaveOptionTradesFromPricing(trades);
|
|
var items = dbTrades.SelectMany(n =>
|
|
{
|
|
return n.TradeType == "结构化交易" ? n.SubTrades.Select(m => new { m.EncryptId }) : new[] { new { n.EncryptId } };
|
|
});
|
|
return JsonSuccess("录入交易成功", items);
|
|
}
|
|
|
|
//保存分组交易
|
|
public JsonResult AjaxSaveGroupTrade(trade trade, IEnumerable<OtcOptionTradeFull> subTrades)
|
|
{
|
|
var dbTrade = new OtcTradeSaveService(CurUser).SaveGroupTradeFromPricing(trade, subTrades);
|
|
return JsonSuccess("录入交易成功", dbTrade);
|
|
}
|
|
|
|
//计算组合的到期收益曲线
|
|
public JsonResult GetTradesPayoffLine(IEnumerable<OtcOptionTradeFull> trades)
|
|
{
|
|
var service = new OptionTradeAnalysisService(CurUser);
|
|
return Json(service.GetTradesPayoffLine(trades));
|
|
}
|
|
|
|
//计算组合的Pv曲线
|
|
public JsonResult GetTradesPvLine(IEnumerable<OtcOptionTradeFull> trades)
|
|
{
|
|
var service = new OptionTradeAnalysisService(CurUser);
|
|
return Json(service.GetTradesPvLine(trades));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算某笔交易的Pv曲线
|
|
/// </summary>
|
|
public JsonResult GetTradesPvLine2(string enTradeId)
|
|
{
|
|
var intid = DecryptInt(enTradeId);
|
|
var service = new OptionTradeAnalysisService(CurUser);
|
|
return Json(service.GetTradesPvLine2(intid));
|
|
}
|
|
|
|
//同时返回到期收益、Pv、以及在一半ttm时的Pv,三条曲线
|
|
public JsonResult GetTradesLifePvLine(IEnumerable<OtcOptionTradeFull> trades)
|
|
{
|
|
var service = new OptionTradeAnalysisService(CurUser);
|
|
return Json(service.GetTradePvLifeLine(trades));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算组合Greeks随时间变化的曲线
|
|
/// </summary>
|
|
/// <param name="trades"></param>
|
|
/// <returns></returns>
|
|
public JsonResult GetTradesGreeksForLifetime(IEnumerable<OtcOptionTradeFull> trades)
|
|
{
|
|
var service = new OptionTradeAnalysisService(CurUser);
|
|
return Json(service.GetTradesGreeksForLifetime(trades));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据交易编号获取otctradefull
|
|
/// </summary>
|
|
public JsonResult AjaxGetOtcTradeFull(string tradeNumber)
|
|
{
|
|
var otcTrade = new TradeDetailsQueryService(CurUser).GetOtcTradeFull(tradeNumber);
|
|
ResetOtcOptionTrade(otcTrade);
|
|
return JsonSuccessData(otcTrade);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取不超过到期日的执行日
|
|
/// 1个月,2周,1周,到日期这样的规则向前计算
|
|
/// </summary>
|
|
public JsonResult AjaxGetExerciseDate(DateTime? underlyingMaturityDate, DateTime? tradeDate = null)
|
|
{
|
|
if (!tradeDate.HasValue)
|
|
{
|
|
tradeDate = valuedateBLL.ValueDate;
|
|
}
|
|
var ExerciseDate = QdpCalendarHelper.GetUnderlyingExerciseDate(underlyingMaturityDate ?? DateTime.Today.AddYears(1), tradeDate.Value);
|
|
return JsonSuccessData(new { ExerciseDate });
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取隐含波动率
|
|
/// </summary>
|
|
public JsonResult GetImpliedVol(OtcOptionTradeFull trade)
|
|
{
|
|
if (trade == null)
|
|
{
|
|
return JsonError("所传数据不能为空");
|
|
}
|
|
if (string.IsNullOrEmpty(trade.UnderlyingCode))
|
|
{
|
|
return JsonError("请选择标的代码");
|
|
}
|
|
if (string.IsNullOrWhiteSpace(trade.TradeType))
|
|
{
|
|
return JsonError("请填写结构类型");
|
|
}
|
|
if (trade.TradeType != "香草期权")
|
|
{
|
|
return JsonError("暂时只支持香草期权类型");
|
|
}
|
|
if (string.IsNullOrWhiteSpace(trade.OptionType))
|
|
{
|
|
return JsonError("请填写看涨看跌");
|
|
}
|
|
if (!trade.TradeDate.HasValue)
|
|
{
|
|
return JsonError("请填写交易日期");
|
|
}
|
|
if (!trade.ExerciseDate.HasValue)
|
|
{
|
|
return JsonError("请填写到期日期");
|
|
}
|
|
if (!trade.Strike.HasValue)
|
|
{
|
|
return JsonError("请填写行权价");
|
|
}
|
|
if (!trade.IsMoneynessOptionData && !trade.SpotPrice.HasValue)
|
|
{
|
|
return JsonError("请填写期初标的价格");
|
|
}
|
|
if (!trade.TradeSinglePrice.HasValue)
|
|
{
|
|
return JsonError("请填写权利金");
|
|
}
|
|
if (trade.ExerciseDate <= trade.TradeDate)
|
|
{
|
|
return JsonError("到期日期不能小于成交日期");
|
|
}
|
|
var sysDate = valuedateBLL.ValueDate;
|
|
var valDate = trade.ValueDate ?? trade.TradeDate ?? valuedateBLL.ValueDate;
|
|
trade.VolType = "交易";
|
|
var impliedVol = VolatilityHelper.GetImpliedVol(valDate, trade, trade.TTMDays, trade.UnderlyingPrice ?? trade.SpotPrice ?? 0, sysDate > valDate);
|
|
return JsonSuccessData(Math.Abs(impliedVol));
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
private void ResetOtcOptionTrade(OtcOptionTradeFull otcTrade)
|
|
{
|
|
if (otcTrade is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
otcTrade.id = 0;
|
|
otcTrade.TradeNumber = string.Empty;
|
|
otcTrade.Notional = otcTrade.OriginalNotional ?? 0;
|
|
otcTrade.StockEqvNotional = otcTrade.OriginalStockEqvNotional ?? 0;
|
|
var um = DataCacheProvider.GetUnderlyingDataSource().GetData(otcTrade.UnderlyingCode);
|
|
otcTrade.TradeAmount = otcTrade.Notional / (um?.CountRatio ?? 1);
|
|
otcTrade.ParentTradeId = 0;
|
|
otcTrade.IsGroup = 0;
|
|
otcTrade.UnWindDate = null;
|
|
otcTrade.FinalPrice = null;
|
|
otcTrade.UnWindNotional = null;
|
|
otcTrade.HasPartialUnWind = null;
|
|
otcTrade.KnockInOutDate = null;
|
|
otcTrade.KnockInOutStatus = null;
|
|
otcTrade.CheckStatus = null;
|
|
otcTrade.ProcessOrderId = 0;
|
|
otcTrade.ProcessOptDate = null;
|
|
otcTrade.ProcessStatus = null;
|
|
otcTrade.DividendDate = new DateTime(2000, 1, 1);
|
|
otcTrade.StructureType = string.Empty;
|
|
otcTrade.StructureIntroduction = string.Empty;
|
|
otcTrade.TraderId = CurUser.UserId;
|
|
otcTrade.TraderName = CurUser.UserName;
|
|
|
|
otcTrade.StockEqvNotional = otcTrade.StockEqvNotional.OtcFormatValue(OtcFormatFlag.StockEqvNotional);
|
|
otcTrade.StockEqvNotionalReal = otcTrade.StockEqvNotionalReal.OtcFormatValue(OtcFormatFlag.StockEqvNotional);
|
|
|
|
otcTrade.CouponIncludeStartDate ??= false;
|
|
|
|
otcTrade.CouponUsePaymentDate ??= false;
|
|
otcTrade.IsApproval = false;
|
|
otcTrade.CountRatio = DataCacheProvider.GetUnderlyingDataSource().GetData(otcTrade.UnderlyingCode)?.CountRatio ?? 1;
|
|
}
|
|
|
|
public ActionResult HistoricalBacktest(bool isLayer = false)
|
|
{
|
|
var datas =
|
|
new SysUserConfigService(CurUser).GetConfigInfos()
|
|
.GroupBy(O => O.ConfigType)
|
|
.ToDictionary(
|
|
K => K.Key == SysUserConfigType.CommonTemplate ? "公共定价模板" : "个人定价模板",
|
|
V => V.Select(O => new { O.EncryptId, O.ConfigName }));
|
|
|
|
ViewBag.isLayer = isLayer;
|
|
ViewBag.pageObj = new
|
|
{
|
|
dataSourceList = datas,
|
|
dataSourceTypeList = datas.Keys,
|
|
defaultType = datas.Keys.FirstOrDefault() ?? "",
|
|
calcRange = new[] { "估值日期", "标的名称", "时间序列-标的价格" },
|
|
calcIndex = new[] { "delta", "deltaCash", "deltaInLots", "gamma", "vega", "rho", "theta" },
|
|
};
|
|
return View();
|
|
}
|
|
|
|
public JsonResult ConfigInfos(string encryptId)
|
|
{
|
|
var id = DecryptInt(encryptId);
|
|
var obj = new SysUserConfigService(CurUser).GetConfigInfos(id);
|
|
return JsonSuccess("ok", obj);
|
|
}
|
|
|
|
public JsonResult UploadPrice(IFormFile file)
|
|
{
|
|
try
|
|
{
|
|
using var stream = file.OpenReadStream();
|
|
var priceList = HistoricalBacktestService.AnalysisPrice(stream);
|
|
if (priceList.Any(O => O.TimeSeries == default))
|
|
{
|
|
throw new ServiceException("文件中存在错误的日期格式");
|
|
}
|
|
if (priceList.Any(O => string.IsNullOrWhiteSpace(O.UnderlyingCode)))
|
|
{
|
|
throw new ServiceException("标的代码不应为空");
|
|
}
|
|
if (priceList.Any(O => double.IsNaN(O.Price)))
|
|
{
|
|
throw new ServiceException("价格不应为空");
|
|
}
|
|
var codes = priceList.Select(O => O.UnderlyingCode).ToHashSet();
|
|
var list = new List<string>();
|
|
foreach (var item in codes)
|
|
{
|
|
if (DataCacheProvider.GetUnderlyingDataSource().GetData(item) == null)
|
|
{
|
|
list.Add(item);
|
|
}
|
|
}
|
|
if (list.Count > 0)
|
|
{
|
|
return JsonError($"导入失败:标的 {string.Join(",", list)} 不存在");
|
|
}
|
|
Server.CacheProvider.Remove("backtest_" + UserId);
|
|
Server.CacheProvider.Set("backtest_" + UserId, priceList, DateTime.Now.AddHours(6));
|
|
return JsonSuccess("导入成功");
|
|
}
|
|
catch (ServiceException)
|
|
{
|
|
throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogFactory.GetLogger("HistoricalBacktest").Error(ex);
|
|
return JsonError("导入失败");
|
|
}
|
|
}
|
|
|
|
public FileResult OutputHistoricalBacktest(HistoricalBacktestReq req)
|
|
{
|
|
var priceInfos = Server.CacheProvider.Get("backtest_" + UserId) as List<HistoricalBacktestPriceModel>;
|
|
if (priceInfos == null)
|
|
{
|
|
throw new ServiceException("请先导入要计算的维度序列");
|
|
}
|
|
var id = DecryptInt(req.encryptId);
|
|
var result = new HistoricalBacktestService(CurUser).Execute(id, priceInfos);
|
|
|
|
var modelDict = new Dictionary<string, object>
|
|
{
|
|
["Sheet1"] = new
|
|
{
|
|
InfoList = result
|
|
}
|
|
};
|
|
var sourcePath = OtcAppContext.MapPath("~/App_Docs/导出模板/");
|
|
var settleDocName = "历史回测导出模板.xlsx";
|
|
var sourceFileName = Path.Combine(sourcePath, settleDocName);
|
|
var buffer = new ExcelTemplateGenerator().SetTemplateFile(sourceFileName).SetTemplateData(modelDict).Output();
|
|
return File(buffer, "application/ms-excel", $"历史回测-{DateTime.Now:yyyy-MM-dd}.xlsx");
|
|
}
|
|
|
|
public JsonResult calcHistoricalBacktest(HistoricalBacktestReq req)
|
|
{
|
|
var priceInfos = Server.CacheProvider.Get("backtest_" + UserId) as List<HistoricalBacktestPriceModel>;
|
|
if (priceInfos == null)
|
|
{
|
|
return JsonError("请先导入要计算的维度序列");
|
|
}
|
|
var id = DecryptInt(req.encryptId);
|
|
var result = new HistoricalBacktestService(CurUser).Execute(id, priceInfos);
|
|
return JsonSuccess("计算成功", result);
|
|
}
|
|
|
|
}
|
|
} |