- trade_cashController.checkEodPrice 是前端'执行到期'按钮的前置 gate,
原用 EodPriceQueryService.TryGetEodPrice(仅查期货+股票两表),
债券标的(如 GLMS-20260715-0002 / 180011.IB)必然取不到价而报
'交易日{date}的结算价或收盘价未找到',挡在 tradeExpireConfirm 之前,
导致层1 对 tradeExpireInner/MultipleTradeExpireConfirm 的修复被绕过。
- 改为与层1 一致的 EodPriceQueryService.TryGetSettlementEodPrice
(债券走中债估值、期货/股票走原路径),三处到期取价口径统一。
950 lines
40 KiB
C#
950 lines
40 KiB
C#
using NPOI.SS.Formula.Functions;
|
|
using YLErp.BLL.Calculation.V2;
|
|
using YLErp.BLL.EodSettlement;
|
|
using YLErp.Commons;
|
|
using YLErp.CustomizedBizLogic;
|
|
using YLErp.DBModels.Consts;
|
|
using YLErp.DBModels.Helpers;
|
|
using YLErp.Enums;
|
|
using YLErp.Modules.CalculationModule;
|
|
using YLErp.Modules.DataProviderModule;
|
|
using YLErp.Modules.TradeDalModule;
|
|
using YLErp.Modules.TradeModule;
|
|
using YLErp.Modules.TradeModule.AccumulatorOptionModule;
|
|
using YLErp.Modules.TradeModule.DealModule;
|
|
using YLErp.Modules.TradeModule.OrderModule;
|
|
using YLErp.Modules.VolatilityModule;
|
|
using YLErp.QdpModule;
|
|
|
|
namespace YLErp.Web.Controllers
|
|
{
|
|
public class trade_cashController : BaseController
|
|
{
|
|
readonly YLContext db = new YLContext();
|
|
|
|
public ActionResult executionTradeCash(string enid, bool isexpire = false, bool isUseApproval = false)
|
|
{
|
|
ViewBag.Title = "现金交割交易 | 执行交割";
|
|
var intid = DecryptInt(enid);
|
|
var td = db.trade.Find(intid);
|
|
if (td == null)
|
|
{
|
|
return Content("找不到现金交割交易");
|
|
}
|
|
if (td.Warning)
|
|
{
|
|
new TradeDalService(CurUser).RollbackToBeforeSettle(td, valuedateBLL.ValueDate);
|
|
}
|
|
ViewBag.Trade = td;
|
|
|
|
tradeBLL.SetFieldsByTradeType(td);
|
|
|
|
if (td.TradeType == "亚式期权" && td.trade_asian_option != null
|
|
&& !EodPriceQueryService.CheckDbExists(td.trade_asian_option.AveragingPeriodStartDate ?? td.TradeDate.Value,
|
|
valuedateBLL.ValueDate, td.UnderlyingInstrumentType, td.UnderlyingCode))
|
|
{
|
|
return Content("该亚式期权没有获取到均价起算日之后的收盘价列表");
|
|
}
|
|
|
|
var r = new TradeCashService(CurUser).ExecutionTradeCash(td);
|
|
|
|
//r.SpotPrice = EodPriceQueryService.TryGetEodPrice(r.ValueDate, td.UnderlyingCode, out var eodPrice)
|
|
// ? eodPrice.GetPrice(td.SettlementType) : DataCacheProvider.GetUnderlyingDataSource().GetPrice(r.UnderlyingId ?? 0);
|
|
|
|
//需要审批或者复核的交易都会显示行权审核提交按钮
|
|
ViewBag.IsShowReCheckClose = ((valuedateBLL.SystemDate.CloseReCheck == 1) ||
|
|
(valuedateBLL.SystemDate.CloseReApprove == 1 && HasTradeProcess())) && td.TradeStatus != ConsTrade.行权待复核;
|
|
ViewBag.IsReCheckConfirm = td.TradeStatus == ConsTrade.行权待复核;
|
|
ViewBag.isUseApproval = isUseApproval;
|
|
|
|
//复核时隐藏下面的字段,让用户手动输入验证
|
|
if (td.TradeStatus == ConsTrade.行权待复核)
|
|
{
|
|
r.ExtraAmount = null;
|
|
r.FinalPrice = null;
|
|
var tradeCash = db.trade_cash.Where(x => x.TradeId == td.id && (x.Action == "系统操作-行权费" || x.Action == "系统操作-平仓费") && x.ValidState == "InValid" && !x.IsDeleted).OrderByDescending(x => x.OptDate)?.FirstOrDefault();
|
|
r.IsExpire = tradeCash != null && tradeCash.ExerciseWay == TradeCashExerciseWayEnum.到期行权;
|
|
if (td.TradeType == "亚式期权")
|
|
{
|
|
r.Strike = tradeCash.Strike;
|
|
}
|
|
if (isUseApproval)
|
|
{
|
|
r.FinalPrice = tradeCash.FinalPrice;
|
|
r.Strike = tradeCash.Strike;
|
|
r.UnwindType = tradeCash.UnwindType;
|
|
r.UnwindNotional = tradeCash.UnwindNotional;
|
|
r.UnwindPercentRate = tradeCash.UnwindPercentRate * 100;
|
|
r.UnwindStockEqvNotional = tradeCash.UnwindStockEqvNotional;
|
|
r.UnwindTradeAmount = tradeCash.UnwindTradeAmount;
|
|
r.ExtraAmount = tradeCash.ExtraAmount;
|
|
r.UnwindPrice = tradeCash.UnwindPrice;
|
|
r.Amount = tradeCash.Amount;
|
|
r.InitialAmount = r.Amount - (r.ExtraAmount ?? 0);
|
|
}
|
|
if (r.IsExpire == true)
|
|
{
|
|
r.UnwindType = "全部行权";
|
|
}
|
|
ViewBag.IsExpire = r.IsExpire;
|
|
}
|
|
else
|
|
{
|
|
r.UnwindType = "全部行权";
|
|
r.IsExpire = isexpire;
|
|
ViewBag.IsExpire = isexpire;
|
|
}
|
|
if (!string.IsNullOrEmpty(td.PairTrade))
|
|
{
|
|
var pid = Convert.ToInt32(td.PairTrade);
|
|
ViewBag.PairTrade = db.trade.Find(pid);
|
|
}
|
|
if (r != null)
|
|
{
|
|
r.InitialSpotPrice = r.InitialSpotPrice.OtcFormatValue(OtcFormatFlag.umprice);
|
|
r.ValidStrike = r.ValidStrike.OtcFormatValue(OtcFormatFlag.umprice);
|
|
|
|
r.OriginalStockEqvNotional = td.StockEqvNotionalReal;
|
|
r.StockEqvNotional = TradeHelper.GetStockEqvNotionalReal(td.StockEqvNotional, td.ParticipationRate, td.AnnualizeFactor);
|
|
r.UnwindStockEqvNotional = r.StockEqvNotional;
|
|
r.IsUnwindStockEqvNotional = false;
|
|
}
|
|
|
|
return View(r);
|
|
}
|
|
|
|
public JsonResult checkEodPrice(string enid)
|
|
{
|
|
var intid = DecryptInt(enid);
|
|
|
|
var td = db.trade.Find(intid);
|
|
|
|
if (td == null)
|
|
{
|
|
return JsonError("找不到交易");
|
|
}
|
|
|
|
var valueDate = valuedateBLL.ValueDate;
|
|
if (PS.Config.Company == Configuration.CompanyEnum.厦门象屿 && td.SettlementType == SettlementTypeEnum.ReferencePrice)
|
|
{
|
|
valueDate = Modules.SpecialModule.XiaMenXiangYuHelper.GetRefernceValueDate(valueDate);
|
|
}
|
|
|
|
if (td.TradeDate > valueDate)
|
|
{
|
|
return JsonError("执行日期在交易起始日之前,操作失败");
|
|
}
|
|
|
|
if (td.ExerciseMode == "European" && td.ExerciseDate > valueDate)
|
|
{
|
|
return JsonError("执行日期在交易到期日之前,操作失败");
|
|
}
|
|
|
|
if (td.ExerciseDate != null && valueDate > td.ExerciseDate)
|
|
{
|
|
valueDate = td.ExerciseDate.Value;
|
|
}
|
|
if (QdpCalendarHelper.IsHoliday(valueDate))
|
|
{
|
|
valueDate = QdpCalendarHelper.GetNonHolidayDefore(valueDate);
|
|
}
|
|
if (!EodPriceQueryService.TryGetSettlementEodPrice(valueDate, td.UnderlyingCode, out _))
|
|
{
|
|
return JsonError($"交易日{valueDate:yyyy-MM-dd}的结算价或收盘价未找到!");
|
|
}
|
|
|
|
return JsonSuccess();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算到期收益
|
|
/// </summary>
|
|
[HttpPost]
|
|
public JsonResult CalcAmount(int enid, double finalPrice, double notional, bool isRefreshFinalPrice = false, double? strike = null)
|
|
{
|
|
var td = db.trade.Find(enid);
|
|
var r = new trade_cash();
|
|
if (td == null)
|
|
{
|
|
return JsonError("计算错误");
|
|
}
|
|
else
|
|
{
|
|
r.IsPossibleExec = true;
|
|
var um = db.underlying_manager.FirstOrDefault(t => t.id == td.UnderlyingId);
|
|
if (notional < td.Notional)
|
|
{
|
|
td.Notional = notional;
|
|
td.StockEqvNotionalReal = (notional * td.SpotPrice) ?? 0;
|
|
td.StockEqvNotional = TradeHelper.GetStockEqvNotional(td.StockEqvNotionalReal, td.ParticipationRate, td.AnnualizeFactor);
|
|
}
|
|
|
|
var valueDate = valuedateBLL.ValueDate;
|
|
if (td.ExerciseDate != null && valueDate > td.ExerciseDate.Value)
|
|
{
|
|
valueDate = td.ExerciseDate.Value;
|
|
}
|
|
r.ValueDate = valueDate;
|
|
|
|
SettlementCalcCommons.SetPossibleExec(r, td, finalPrice, isRefreshFinalPrice, strike: strike, finalPriceSettleDate: r.ValueDate);
|
|
|
|
return JsonSuccess("计算成功", new { r.IsPossibleExec, r.Amount, r.FinalPrice, r.UnwindPrice });
|
|
}
|
|
}
|
|
|
|
[HttpPost]
|
|
public JsonResult CalcMultyTradeAmount(int enid, double finalPrice)
|
|
{
|
|
var td = db.trade.Find(enid);
|
|
|
|
if (td == null)
|
|
{
|
|
return JsonError("计算错误");
|
|
}
|
|
else
|
|
{
|
|
double amount = 0;
|
|
var childTrades = db.trade.Where(x => x.ParentTradeId == td.id).ToList();
|
|
foreach (var trade in childTrades)
|
|
{
|
|
var r = new trade_cash();
|
|
tradeBLL.SetFieldsByTradeType(trade);
|
|
SettlementCalcCommons.SetPossibleExec(r, trade, finalPrice);
|
|
|
|
amount += r.Amount;
|
|
}
|
|
|
|
return JsonSuccess("计算成功", new { IsPossibleExec = true, Amount = amount });
|
|
}
|
|
}
|
|
|
|
public double? CalcAmountOnly(int enid, double tradePrice)
|
|
{
|
|
var td = db.trade.Find(enid);
|
|
var r = new trade_cash();
|
|
if (td != null)
|
|
{
|
|
tradeBLL.SetFieldsByTradeType(td);
|
|
r.IsPossibleExec = true;
|
|
var um = db.underlying_manager.FirstOrDefault(t => t.id == td.UnderlyingId);
|
|
SettlementCalcCommons.SetPossibleExec(r, td, tradePrice);
|
|
return r.Amount;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行合约/复核执行合约
|
|
/// </summary>
|
|
[HttpPost]
|
|
public JsonResult executionTradeCashJson(TradeCashReq req, string _Version)
|
|
{
|
|
if (req is null)
|
|
{
|
|
return JsonError("数据不能为空");
|
|
}
|
|
var tc = new TradeCashService(CurUser).GetLastSettleInfo(req.TradeId);
|
|
if (tc != null && tc.ValueDate > req.ValueDate)
|
|
{
|
|
return JsonError($"提交失败,了结日期不应早于{tc.ValueDate:yyyy-MM-dd},请重新操作");
|
|
}
|
|
|
|
var result = new TradeCashService(CurUser).ExecutionTradeCash(req);
|
|
|
|
setTradeExtensionInfo(result.Trade);
|
|
|
|
if (!result.Success)
|
|
{
|
|
return JsonError(result.Message, result.Trade);
|
|
}
|
|
|
|
if (result.TradeCash != null && _Version == "V2")
|
|
{
|
|
var trade = result.Trade;
|
|
new TradeExtendService(CurUser).SetTradeExtend(new[] { trade });
|
|
var text = TradeTerminationAbstractService.GetAbstractInfoText(result.TradeCash, trade, false);
|
|
return JsonSuccess(result.Message, text);
|
|
}
|
|
return JsonSuccess(result.Message, result.Trade);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 日终执行批量行权到期
|
|
/// </summary>
|
|
[HttpPost]
|
|
public async Task<JsonResult> ExeExpireTradeCashJson(TradeCashReq req)
|
|
{
|
|
var tc = new trade_cash();
|
|
await TryUpdateModelAsync(tc);
|
|
new TradeExpireService(CurUser).ExeExpireTradeCash(req, tc);
|
|
return JsonSuccess("执行成功", null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 拒绝平仓审核
|
|
/// </summary>
|
|
[MyAuthorize("交易管理-交易平仓审核")]
|
|
[HttpPost]
|
|
public JsonResult DenyReCheck(TradeCashReq req)
|
|
{
|
|
new TradeProcessService(CurUser).DenyReCheck(req);
|
|
return JsonSuccess("提前终止拒绝成功");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 平仓待复核提交
|
|
/// </summary>
|
|
[HttpPost]
|
|
public JsonResult CloseReCheck(TradeCashReq req, string _Version, bool isFromExercise = false)
|
|
{
|
|
var tc = new TradeCashService(CurUser).GetLastSettleInfo(req.TradeId);
|
|
if (tc != null && tc.ValueDate > req.ValueDate)
|
|
{
|
|
return JsonError($"提交失败,了结日期不应早于{tc.ValueDate:yyyy-MM-dd},请重新操作");
|
|
}
|
|
var result = new TradeCloseService(CurUser).CloseReCheck(req, isFromExercise);
|
|
setTradeExtensionInfo(result.Trade);
|
|
if (_Version == "V2")
|
|
{
|
|
var trade = result.Trade;
|
|
new TradeExtendService(CurUser).SetTradeExtend(new[] { trade });
|
|
var text = TradeTerminationAbstractService.GetAbstractInfoText(result.TradeCash, trade, true);
|
|
return JsonSuccess("提交审核成功", text);
|
|
}
|
|
return JsonSuccess("提交审核成功", result.Trade);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 行权待复核提交
|
|
/// </summary>
|
|
[HttpPost]
|
|
public JsonResult applyExerciseRecheck(TradeCashReq req, string _Version)
|
|
{
|
|
var tc = new TradeCashService(CurUser).GetLastSettleInfo(req.TradeId);
|
|
if (tc != null && tc.ValueDate > req.ValueDate)
|
|
{
|
|
return JsonError($"提交失败,了结日期不应早于{tc.ValueDate:yyyy-MM-dd},请重新操作");
|
|
}
|
|
var result = new TradeExerciseService(CurUser).ApplyExerciseRecheck(req);
|
|
setTradeExtensionInfo(result.Trade);
|
|
if (_Version == "V2")
|
|
{
|
|
var trade = result.Trade;
|
|
new TradeExtendService(CurUser).SetTradeExtend(new[] { trade });
|
|
var text = TradeTerminationAbstractService.GetAbstractInfoText(result.TradeCash, trade, false);
|
|
return JsonSuccess("提交审核成功", text);
|
|
}
|
|
return JsonSuccess("提交审核成功", result.Trade);
|
|
}
|
|
|
|
//交易平仓复核/交易平仓
|
|
[MyAuthorize("交易管理-交易平仓,交易管理-交易平仓审核")]
|
|
[HttpPost]
|
|
public JsonResult UnwindTradeJson(TradeCashReq req, string _Version)
|
|
{
|
|
try
|
|
{
|
|
var tc = new TradeCashService(CurUser).GetLastSettleInfo(req.TradeId);
|
|
if (tc != null && tc.ValueDate > req.ValueDate)
|
|
{
|
|
return JsonError($"提交失败,了结日期不应早于{tc.ValueDate:yyyy-MM-dd},请重新操作");
|
|
}
|
|
var result = new TradeUnwindService(CurUser).UnwindTrade(req);
|
|
setTradeExtensionInfo(result.Trade);
|
|
tradeBLL.SetFieldsByTradeType(result.Trade);
|
|
if (result.ApprovalProcess)
|
|
{
|
|
return JsonSuccess("复核审批成功", result.Trade);
|
|
}
|
|
if (result.HasPairTrade)
|
|
{
|
|
return JsonSuccess("执行成功,配对交易平仓" + (result.PairTradeUnwindSuccess ? "执行成功" : "执行失败"), result.Trade);
|
|
}
|
|
if (_Version == "V2")
|
|
{
|
|
var trade = result.Trade;
|
|
new TradeExtendService(CurUser).SetTradeExtend(new[] { trade });
|
|
var text = TradeTerminationAbstractService.GetAbstractInfoText(result.TradeCash, trade, true);
|
|
return JsonSuccess("执行成功", text);
|
|
}
|
|
return JsonSuccess("执行成功", result.Trade);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogFactory.GetLogger("平仓失败").Error("UnwindTradeJson", ex);
|
|
return JsonError("平仓失败" + ex.Message);
|
|
}
|
|
}
|
|
|
|
[HttpPost]
|
|
public JsonResult GetPositionVol(TradeCashReq req)
|
|
{
|
|
var td = db.trade.Find(req.TradeId);
|
|
if (td == null)
|
|
{
|
|
return JsonError("系统中没有此交易!");
|
|
}
|
|
|
|
if (!req.ValueDate.HasValue)
|
|
{
|
|
req.ValueDate = valuedateBLL.ValueDate;
|
|
}
|
|
|
|
var vol = new VolatilityService(CurUser).GetTradeVol(td, req.ValueDate.Value);
|
|
|
|
if (!PS.Config.IsTradeVol)
|
|
{
|
|
var um = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
|
|
var variety = DataCacheProvider.GetVariety(td.UnderlyingCode);
|
|
var singleVol = new SingleVolReq()
|
|
{
|
|
VolType = "交易",
|
|
Strike = td.Strike ?? 0,
|
|
SpotPrice = req.SpotPrice ?? 0,
|
|
TradeDate = req.ValueDate.Value,
|
|
ExerciseDate = td.ExerciseDate ?? DateTime.Today,
|
|
IsMoneynessOption = td.IsMoneynessOption,
|
|
CallPut = td.CallPut == "看涨" ? "Call" : "Put",
|
|
UnderlyingId = um.id,
|
|
UnderlyingCode = um.UnderlyingCode,
|
|
UnderlyingName = um.UnderlyingName,
|
|
UnderlyingTypeId = variety.id,
|
|
BaseVol = null,
|
|
BidVar = null,
|
|
AskVar = null,
|
|
};
|
|
if (PS.Config.Is润和)
|
|
{
|
|
singleVol.TradeDate = req.ValueDate ?? DateTime.Today;
|
|
}
|
|
vol = SingleVolService.GetSingleVol(singleVol, UserId);
|
|
}
|
|
|
|
return JsonSuccess("", vol);
|
|
|
|
}
|
|
|
|
[HttpPost]
|
|
public JsonResult GetImpliedUnWindVol(TradeCashReq req)
|
|
{
|
|
if (req == null)
|
|
{
|
|
return JsonError("实体不能为空!");
|
|
}
|
|
|
|
if (!req.UnwindPrice.HasValue)
|
|
{
|
|
return JsonError("平仓单价不能为空");
|
|
}
|
|
|
|
if (!req.FinalPrice.HasValue)
|
|
{
|
|
return JsonError("标的价格不能为空");
|
|
}
|
|
var td = db.trade.Find(req.TradeId);
|
|
if (td == null)
|
|
{
|
|
return JsonError("系统中没有此交易!");
|
|
}
|
|
if (td.TradeType != "香草期权")
|
|
{
|
|
return JsonError("只支持香草期权获取波动率");
|
|
}
|
|
|
|
if (!req.ValueDate.HasValue)
|
|
{
|
|
req.ValueDate = valuedateBLL.ValueDate;
|
|
}
|
|
if (req.TTM.HasValue)
|
|
{
|
|
td.TTMDays = req.TTM;
|
|
}
|
|
var vol = Modules.PricingModule.ReverseCalcService.GetImpliedUnWindVol(td, req.ValueDate.Value, req.UnwindPrice.Value, req.FinalPrice.Value, valuedateBLL.ValueDate > req.ValueDate);
|
|
if (!vol.HasValue)
|
|
{
|
|
return JsonError("获取隐含波动率失败");
|
|
}
|
|
|
|
return JsonSuccess("", vol);
|
|
}
|
|
|
|
public JsonResult tradeUnwindCalc(TradeCashReq req)
|
|
{
|
|
Dictionary<string, double> resultDict = null;
|
|
var tradeBLL = new tradeBLL();
|
|
var userId = CurUser.UserId.ToString();
|
|
//修改交易主表信息交易状态为已执行
|
|
var td = db.trade.AsNoTracking().FirstOrDefault(t => t.id == req.TradeId);
|
|
if (td == null)
|
|
{
|
|
return JsonError("计算失败,找不到交易!", null);
|
|
}
|
|
if (!GlobalDicionary.SupportTradeTypes.Contains(td.TradeType))
|
|
{
|
|
return JsonError(string.Format("计算失败,当前服务不支持{0}的计算!", td.TradeType), null);
|
|
}
|
|
if (td.Warning)
|
|
{
|
|
new TradeDalService(CurUser).RollbackToBeforeSettle(td, req.ValueDate ?? valuedateBLL.ValueDate);
|
|
}
|
|
try
|
|
{
|
|
if (!req.ValueDate.HasValue)
|
|
{
|
|
req.ValueDate = valuedateBLL.ValueDate;
|
|
}
|
|
tradeBLL.SetFieldsByTradeType(td); //这个方法会慢
|
|
|
|
resultDict = GetTradeUnwindCalc(req, td.Clone());
|
|
|
|
if (PS.Config.Is润和)
|
|
{
|
|
req.UnwindNotional = req.ActualUnwindNotional ?? 1;
|
|
Dictionary<string, double> resultDictVolPV = GetTradeUnwindCalc(req, td.Clone());
|
|
req.UnwindVol = req.MidVol;
|
|
Dictionary<string, double> resultDictMidPV = GetTradeUnwindCalc(req, td.Clone());
|
|
|
|
resultDict.Add("UnwindVolPV", resultDictVolPV["PV"]);
|
|
resultDict.Add("MidPV", resultDictMidPV["PV"]);
|
|
}
|
|
|
|
return JsonSuccess("计算成功!", resultDict);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
LogFactory.GetLogger("tradeUnwindCalc出错").Error(e);
|
|
return JsonError("计算出错!");
|
|
}
|
|
}
|
|
|
|
public Dictionary<string, double> GetTradeUnwindCalc(TradeCashReq req, trade td)
|
|
{
|
|
Dictionary<string, double> resultDict = null;
|
|
var userId = CurUser.UserId.ToString();
|
|
|
|
if (req.UnwindNotional < td.Notional)
|
|
{
|
|
//平仓时都计算的平仓单价,将凤凰期权累积的票息金额处理成单份对应的票息金额
|
|
//20210728:涉及到部分平仓的话这个算法是有问题的
|
|
List<autocall_observation> HappenedObservations = null;
|
|
switch (td.TradeType)
|
|
{
|
|
case "凤凰期权":
|
|
HappenedObservations = td.trade_autocall.HappenedObservations;
|
|
break;
|
|
case "区间累积期权":
|
|
HappenedObservations = td.trade_rangeaccrual.HappenedObservations;
|
|
break;
|
|
}
|
|
|
|
if (HappenedObservations != null)
|
|
{
|
|
var spotPrice = Math.Abs(td.SpotPrice ?? 0);
|
|
|
|
foreach (var x in HappenedObservations)
|
|
{
|
|
var notional = td.Notional;
|
|
|
|
if (spotPrice > 1e-8)
|
|
{
|
|
notional = x.StockEqvNotional / spotPrice;
|
|
}
|
|
else if (td.StockEqvNotionalReal > 0)
|
|
{
|
|
notional = (td.OriginalNotional ?? 0) * x.StockEqvNotional / td.StockEqvNotionalReal;
|
|
}
|
|
|
|
if (notional > 0)
|
|
{
|
|
x.PaymentAmount /= notional;
|
|
}
|
|
}
|
|
}
|
|
|
|
td.Notional = req.UnwindNotional;
|
|
//现金流交易在平仓时的份额即名义本金,现金流没有份额的概念
|
|
if (td.TradeType == "现金流交易")
|
|
{
|
|
td.StockEqvNotional = req.UnwindNotional;
|
|
}
|
|
}
|
|
var udm = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
|
|
if (td.TradeType == "现金流交易")
|
|
{
|
|
udm = new underlying_manager();
|
|
}
|
|
if (req.FinalPrice.HasValue)
|
|
{
|
|
udm.Price = req.FinalPrice;
|
|
}
|
|
|
|
udm.QuotationDate = req.ValueDate;
|
|
|
|
//应该不需要这段逻辑,先注释掉:OTC-13391
|
|
//td.TradeDate = req.ValueDate;
|
|
//td.StartDate = req.ValueDate;
|
|
|
|
string fixing = null;
|
|
//亚式期权
|
|
if (td.TradeType == "亚式期权" || td.StructureType == "亚式熊市价差")
|
|
{
|
|
fixing = AsianOptionFixingService.GetFixingString(req.ValueDate.Value, td);
|
|
fixing = FixingService.AddOrReplaceLastDateSpotPrice(fixing, req.ValueDate.Value, udm.Price ?? 0.0);
|
|
|
|
if (PS.Config.Is润和 && DateTime.Now.Hour < 15)
|
|
{
|
|
var index = fixing.IndexOf(req.ValueDate.Value.ToString("yyyy-MM-dd"));
|
|
if (index >= 0)
|
|
{
|
|
fixing = fixing.Remove(index).TrimEnd(';');
|
|
}
|
|
}
|
|
}
|
|
|
|
var ttm = req.TTM;
|
|
if (!ttm.HasValue)
|
|
{
|
|
if (PS.Config.Is厦门象屿 && td.SettlementType == SettlementTypeEnum.ReferencePrice)
|
|
{
|
|
ttm = TradeCalcHelper.CalculateTTMDaysForXiangYu(req.ValueDate.Value, td.ExerciseDate.Value, udm.UnderlyingTypeId, td.ExerciseDate.Value == valuedateBLL.ValueDate);
|
|
}
|
|
else
|
|
{
|
|
ttm = TradeCalcHelper.CalculateTTMDays(req.ValueDate.Value, td.ExerciseDate.Value, udm.UnderlyingTypeId, td.ExerciseDate.Value == valuedateBLL.ValueDate);
|
|
}
|
|
}
|
|
|
|
if (req.ValueDate < valuedateBLL.ValueDate)
|
|
{
|
|
new TradeHisDataService(CurUser).UpdateTradeWithHisData(td, req.ValueDate.Value);
|
|
}
|
|
|
|
try
|
|
{
|
|
if (td.TradeType == "累计期权")
|
|
{
|
|
var request = new OptionValueCalcRequest(valuedateBLL.SysRiskFreeRate())
|
|
{
|
|
vols = new[] { req.UnwindVol ?? 0.0 },
|
|
spotPrices = new[] { udm.Price ?? 0.0 },
|
|
engineName = null,
|
|
preciseTimeMode = req.ValueDate == valuedateBLL.ValueDate.Date,//与定价计算统一
|
|
pricingRequest = QdpPricingRequest.BASIC_PRICING,
|
|
quadratureFastMode = true,
|
|
timeToMaturityDays = ttm.Value,
|
|
calcScenario = CalcScenarioEnum.Pricing
|
|
};
|
|
|
|
var OptionValue = OptionCalculatorV2.GetOptionValueResult(req.ValueDate.Value, td, request, out _);
|
|
|
|
//累计期权怕平仓时剩余的平仓观察日,需要根据已观察日进行区分,不依赖平仓日期来判断
|
|
var observations = db.autocall_observation.Where(x => x.TradeId == td.id);
|
|
var startDate = td.TradeDate.Value;
|
|
if (observations.Any())
|
|
{
|
|
startDate = observations.Max(x => x.EndDate).AddDays(1);
|
|
}
|
|
|
|
//累计期权计算的pv是总额,所以这里要转换为单价
|
|
var dayCount = new TradeAccumulatorChangeService(CurUser).GetAccTradeUnwindDayCount(td.trade_accumulator_option.KOObservationDates, startDate, td.TradeDate, td.ExerciseDate);
|
|
|
|
var count = td.trade_accumulator_option.AccumuTradeAmount * dayCount;
|
|
if (count != 0)
|
|
{
|
|
OptionValue.Pv = OptionValue.Pv / count;
|
|
}
|
|
OptionValue.RoundedPv = OtcFormatHelper.FormatValue(OptionValue.Pv, 2);
|
|
if (Math.Abs(OptionValue.DeltaInLots) < 1e-6 && count != 0)
|
|
{
|
|
OptionValue.DeltaInLots = OptionValue.Delta / count / udm.ContractSize;
|
|
}
|
|
resultDict = new Dictionary<string, double> { { "PV", OptionValue.Pv }, { "DeltaInLots", OptionValue.DeltaInLots } };
|
|
}
|
|
else if (td.TradeType == "自定义交易")
|
|
{
|
|
if (PS.Config.ErpElement.ExternalAPIForCustomCalcEnable)
|
|
{
|
|
td.StartDate = req.ValueDate.Value;
|
|
var positionVol = VolatilityHelper.GetTradeVol(td, req.ValueDate.Value);
|
|
var tempResult = TradeRiskCalcUtil.GetManualOptionValue(req.ValueDate.Value, td, udm.Price ?? 0, positionVol, true, CalcScenarioEnum.Pricing);
|
|
if (tempResult.manual != null)
|
|
{
|
|
var underlying = Modules.DataCacheModule.DataCacheManager.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
|
|
var deltaInLots = TradeLotsCalc.CalcDeltaInLots(tempResult.optionValue.Delta, null, underlying);
|
|
resultDict = new Dictionary<string, double> { { "PV", tempResult.optionValue.Pv }, { "DeltaInLots", deltaInLots } };
|
|
}
|
|
else
|
|
{
|
|
throw new Exception(PS.Config.ErpElement.ExternalAPIForCustomCalcEnable ? "失败:接口计算失败" : "失败:无法调用计算接口,请开启相应配置");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new ServiceException("计算失败,不支持自定义交易计算");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (td.TradeType == "结构化产品")
|
|
{
|
|
using (var db = DbContextFactory.GetYLDbContext())
|
|
{
|
|
td.MetaDic = db.TradeMeta.Where(p => p.TradeId == td.id).ToDictionary(g => g.MetaKey, g => g.MetaValue);
|
|
}
|
|
}
|
|
var OptionValue = ValueCalculator.GetOptionValueResultV2(
|
|
userId: userId,
|
|
underlying: udm,
|
|
trade: td,
|
|
vols: new double[] { req.UnwindVol ?? 0.0 },
|
|
spotPrices: new double[] { udm.Price ?? 0.0 },
|
|
fixing: fixing,
|
|
timeToMaturityDays: ttm.Value,
|
|
preciseTimeMode: req.ValueDate == valuedateBLL.ValueDate.Date,//OTC-4198,与定价计算统一
|
|
request: QdpPricingRequest.BASIC_PRICING,
|
|
calcScenario: CalcScenarioEnum.Pricing);
|
|
|
|
if (td.TradeType == "雪球期权" && td.trade_snowball.PrepaymentUsed)
|
|
{
|
|
//OptionValue.Pv是根据td.notional算出来的
|
|
OptionValue.Pv -= td.Notional * (td.SpotPrice ?? 0) * (td.trade_snowball?.PrepaymentRatio ?? 0) * (td.BuySell == "卖出" ? -1 : 1);
|
|
}
|
|
|
|
resultDict = new Dictionary<string, double> { { "PV", OptionValue.Pv }, { "DeltaInLots", OptionValue.DeltaInLots } };
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
resultDict = new Dictionary<string, double> { { "PV", 0 }, { "DeltaInLots", 0 } };
|
|
}
|
|
|
|
return resultDict;
|
|
}
|
|
/// <summary>
|
|
/// 获取累计期权平仓定价日的观察日期
|
|
/// </summary>
|
|
/// <param name="kOObservationDates">观察日期</param>
|
|
/// <param name="valueDate">定价日期或平仓日期</param>
|
|
/// <param name="tradeDate">交易日期</param>
|
|
/// <param name="exerciseDate">到期日期</param>
|
|
/// <returns></returns>
|
|
public JsonResult GetAccTradeUnwindDayCount(string kOObservationDates, DateTime? valueDate, DateTime tradeDate, DateTime exerciseDate)
|
|
{
|
|
var service = new TradeAccumulatorChangeService(CurUser);
|
|
var dayCount = service.GetAccTradeUnwindDayCount(kOObservationDates, valueDate, tradeDate, exerciseDate);
|
|
return JsonSuccess("", dayCount);
|
|
}
|
|
|
|
public JsonResult tradeSwapUnwindCalcGet(TradeCashReq req)
|
|
{
|
|
Dictionary<string, double> resultDict = null;
|
|
var tradeBLL = new tradeBLL();
|
|
|
|
//修改交易主表信息交易状态为已执行
|
|
var td = db.trade.AsNoTracking().FirstOrDefault(t => t.id == req.TradeId);
|
|
if (td == null)
|
|
{
|
|
return JsonError("计算失败,找不到交易!", null);
|
|
}
|
|
try
|
|
{
|
|
if (!req.ValueDate.HasValue)
|
|
{
|
|
req.ValueDate = valuedateBLL.ValueDate;
|
|
}
|
|
tradeBLL.SetFieldsByTradeType(td); //这个方法会慢
|
|
|
|
var tradeCashs = db.trade_cash.Where(y => y.TradeId == td.id && y.Action == "系统操作-互换" && y.ValidState != "InValid" && !y.IsDeleted && y.ValueDate <= req.ValueDate);
|
|
var tradeCashIds = tradeCashs.Select(x => x.id);
|
|
var tradeCash = tradeCashs.OrderByDescending(y => y.id).FirstOrDefault();
|
|
var cashSwaps = db.trade_cash_swap.Where(x => x.TradeId == td.id && tradeCashIds.Contains(x.TradeCashId)).ToArray();
|
|
var tradeCashSwap = tradeCash != null ? cashSwaps.FirstOrDefault(x => x.TradeCashId == tradeCash.id) : null;
|
|
//取最后一次手动收益;
|
|
var lastManualCashSwap = cashSwaps.OrderByDescending(o => o.StartDate).FirstOrDefault(x => !x.IsAuto);
|
|
var lastManualCash = lastManualCashSwap != null ? tradeCashs.FirstOrDefault(x => x.id == lastManualCashSwap.TradeCashId) : null;
|
|
var initialAmountGet = PayoffSwapCalcService.GetInitialAmountSwapGet(td, td.trade_swap, tradeCashSwap?.GetFinalPrice ?? td.trade_swap.GetSpotPrice ?? 0
|
|
, req.FinalPrice ?? 0, (td.OriginalStockEqvNotional ?? 0) * req.UnwindPercentRate, req.ValueDate.Value, tradeCash?.ValueDate);
|
|
DateTime endDate;
|
|
var preSwapDate = PayoffSwapCalcService.GetSwapRateStartDate(td, td.trade_swap, req.ValueDate.Value, tradeCash, lastManualCash, td.trade_swap.IsPayFloatingProfit, out endDate);
|
|
var extraAmountPay = PayoffSwapCalcService.GetExtraAmountBySwapRate(td.ClientId, td.TradeDate, td.trade_swap.PaySwapTimeAndRate, preSwapDate, endDate, td.trade_swap.AnnualDays ?? 0, (td.OriginalStockEqvNotional ?? 0) * req.UnwindPercentRate);
|
|
|
|
resultDict = new Dictionary<string, double> { { "InitialAmount", initialAmountGet }, { "ExtraAmount", extraAmountPay } };
|
|
|
|
return JsonSuccess("计算成功!", resultDict);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
LogFactory.GetLogger("tradeSwapUnwindCalc").Error(e);
|
|
return JsonError("计算出错!");
|
|
}
|
|
}
|
|
|
|
public JsonResult tradeSwapUnwindCalcPay(TradeCashReq req)
|
|
{
|
|
Dictionary<string, double> resultDict = null;
|
|
var tradeBLL = new tradeBLL();
|
|
|
|
//修改交易主表信息交易状态为已执行
|
|
var td = db.trade.AsNoTracking().FirstOrDefault(t => t.id == req.TradeId);
|
|
if (td == null)
|
|
{
|
|
return JsonError("计算失败,找不到交易!", null);
|
|
}
|
|
try
|
|
{
|
|
if (!req.ValueDate.HasValue)
|
|
{
|
|
req.ValueDate = valuedateBLL.ValueDate;
|
|
}
|
|
tradeBLL.SetFieldsByTradeType(td); //这个方法会慢
|
|
|
|
var tradeCashs = db.trade_cash.Where(y => y.TradeId == td.id && y.Action == "系统操作-互换" && y.ValidState != "InValid" && !y.IsDeleted && y.ValueDate <= req.ValueDate);
|
|
var tradeCashIds = tradeCashs.Select(x => x.id);
|
|
var tradeCash = tradeCashs.OrderByDescending(y => y.id).FirstOrDefault();
|
|
var cashSwaps = db.trade_cash_swap.Where(x => x.TradeId == td.id && tradeCashIds.Contains(x.TradeCashId)).ToArray();
|
|
var tradeCashSwap = tradeCash != null ? cashSwaps.FirstOrDefault(x => x.TradeCashId == tradeCash.id) : null;
|
|
//取最后一次手动收益;
|
|
var lastManualCashSwap = cashSwaps.OrderByDescending(o => o.StartDate).FirstOrDefault(x => !x.IsAuto);
|
|
var lastManualCash = lastManualCashSwap != null ? tradeCashs.FirstOrDefault(x => x.id == lastManualCashSwap.TradeCashId) : null;
|
|
var initialAmountPay = PayoffSwapCalcService.GetInitialAmountSwapPay(td, td.trade_swap, tradeCashSwap?.PayFinalPrice ?? td.trade_swap.PaySpotPrice ?? 0
|
|
, req.FinalPrice ?? 0, (td.OriginalStockEqvNotional ?? 0) * req.UnwindPercentRate, req.ValueDate.Value, tradeCash?.ValueDate);
|
|
DateTime endDate;
|
|
var preSwapDate = PayoffSwapCalcService.GetSwapRateStartDate(td, td.trade_swap, req.ValueDate.Value, tradeCash, lastManualCash, td.trade_swap.IsGetFloatingProfit, out endDate);
|
|
var extraAmountGet = PayoffSwapCalcService.GetExtraAmountBySwapRate(td.ClientId, td.TradeDate, td.trade_swap.GetSwapTimeAndRate, preSwapDate, endDate, td.trade_swap.AnnualDays ?? 0, (td.OriginalStockEqvNotional ?? 0) * req.UnwindPercentRate);
|
|
|
|
resultDict = new Dictionary<string, double> { { "InitialAmount", initialAmountPay }, { "ExtraAmount", extraAmountGet } };
|
|
|
|
return JsonSuccess("计算成功!", resultDict);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
LogFactory.GetLogger("tradeSwapUnwindCalc").Error(e);
|
|
return JsonError("计算出错!");
|
|
}
|
|
}
|
|
|
|
public JsonResult ApplyUnwindSwapTrade(TradeCashReq req)
|
|
{
|
|
_ = new TradeUnwindService(CurUser).ApplyUnwindSwapTrade(req);
|
|
|
|
return JsonSuccess("提交平仓审批成功!");
|
|
}
|
|
|
|
public JsonResult UnwindSwapTrade(TradeCashReq req)
|
|
{
|
|
_ = new TradeUnwindService(CurUser).UnwindSwapTrade(req);
|
|
|
|
return JsonSuccess("平仓成功!");
|
|
}
|
|
|
|
public JsonResult ApplySwapTrade(TradeCashReq req)
|
|
{
|
|
_ = new TradeUnwindService(CurUser).ApplySwapTradeSettlement(req);
|
|
|
|
return JsonSuccess("提交互换审批成功!");
|
|
}
|
|
|
|
public JsonResult SwapTrade(TradeCashReq req)
|
|
{
|
|
_ = new TradeUnwindService(CurUser).SwapTrade(req);
|
|
|
|
return JsonSuccess("互换成功!");
|
|
}
|
|
|
|
public JsonResult SaveGroupTradeCash(SaveGroupTradeCashReq req)
|
|
{
|
|
new TradeUnwindService(CurUser).SaveGroupTradeCash(req);
|
|
|
|
return JsonSuccess("保存成功!");
|
|
}
|
|
//交易平仓复核/交易平仓
|
|
[MyAuthorize("交易管理-交易平仓,交易管理-交易平仓审核")]
|
|
[HttpPost]
|
|
public JsonResult UnwindParentTrade(SaveGroupTradeCashReq req)
|
|
{
|
|
new TradeUnwindService(CurUser).UnwindParentTrade(req);
|
|
|
|
return JsonSuccess("复核成功!");
|
|
}
|
|
public JsonResult DenyReCheckByParent(int tradeId)
|
|
{
|
|
new TradeProcessService(CurUser).DenyReCheckByParent(tradeId);
|
|
return JsonSuccess("提前终止拒绝成功");
|
|
}
|
|
[HttpPost]
|
|
public JsonResult CheckFinishGroupTradeCash(int TcId)
|
|
{
|
|
var isFinished = !db.trade_cash_group_action.Any(x => x.ParentTradeCashId == TcId && x.Status == "待完成");
|
|
|
|
return Json(isFinished);
|
|
}
|
|
|
|
[HttpPost]
|
|
public JsonResult FinishGroupTradeCash(int TcId)
|
|
{
|
|
new TradeUnwindService(CurUser).FinishGroupTradeCash(TcId);
|
|
|
|
return Json("已完成");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 导入了结(部分了结)交易
|
|
/// </summary>
|
|
[MyAuthorize("交易管理-批量了结")]
|
|
public ActionResult UploadFinishedTrades()
|
|
{
|
|
if (Request.Form.Files.Count < 1)
|
|
{
|
|
return JsonError("未获取上传文件");
|
|
}
|
|
|
|
var file = Request.Form.Files[0];
|
|
var fileExt = Path.GetExtension(file.FileName);
|
|
if (fileExt.ToLowerInvariant() != ".xlsx")
|
|
{
|
|
return JsonError("目前只支持xlsx格式的文件");
|
|
}
|
|
|
|
using var stream = file.OpenReadStream();
|
|
new OtcTradeCloseService(CurUser).BatchCloseByImport(stream, out _, out _);
|
|
|
|
return JsonSuccess();
|
|
}
|
|
|
|
private void setTradeExtensionInfo(trade td)
|
|
{
|
|
if (td is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//生成简要时需要的信息补全
|
|
if (td.TradeType == "合成价差期权")
|
|
{
|
|
td.SyntheticUnderlyingTipsInfo = synthetic_underlyingBLL.GetUnderlyingTipsInfo(td.UnderlyingCode);
|
|
}
|
|
var varietyQuery = from um in db.underlying_manager
|
|
join va in db.variety on um.UnderlyingTypeId equals va.id
|
|
where um.id == td.UnderlyingId
|
|
select new VarietyDto
|
|
{
|
|
QuoteUnit = va.QuoteUnit
|
|
};
|
|
var variety = varietyQuery.FirstOrDefault();
|
|
if (variety != null)
|
|
{
|
|
td.QuoteUnit = variety.QuoteUnit;
|
|
td.QuoteUnitSingle = variety.QuoteUnitSingle;
|
|
}
|
|
}
|
|
|
|
private bool HasTradeProcess()
|
|
{
|
|
return db.approvalprocess.Any(t => t.processType == "TradeProcess");
|
|
}
|
|
}
|
|
}
|