从山证v2.3.0拷贝
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
namespace YLErp.Modules.CalculationModule.Abstract
|
||||
{
|
||||
/// <summary>
|
||||
/// 场内交易佣金计算接口
|
||||
/// </summary>
|
||||
public interface IExchangeTradeCommissionCalc
|
||||
{
|
||||
/// <summary>
|
||||
/// 交易佣金计算
|
||||
/// </summary>
|
||||
/// <param name="tradeList">交易列表</param>
|
||||
/// <param name="isActualTrade">是否真实交易</param>
|
||||
/// <returns></returns>
|
||||
ITradeCommissionCalcResult GetTradeCommission(IEnumerable<ExchangeTrade> tradeList, bool isActualTrade = true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 交易佣金计算结果接口
|
||||
/// </summary>
|
||||
public interface ITradeCommissionCalcResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据交易ID返回对应的交易佣金计算结果,如果未找到返回0
|
||||
/// </summary>
|
||||
double GetTradeCommission(int tradeId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using YLErp.Abstract;
|
||||
using YLErp.Abstract.DataProviders;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule.Abstract
|
||||
{
|
||||
/// <summary>
|
||||
/// 持仓风险对冲计算上下文接口
|
||||
/// </summary>
|
||||
public interface IHedgePnlCalcContext
|
||||
{
|
||||
/// <summary>
|
||||
/// 估值日
|
||||
/// </summary>
|
||||
DateTime ValueDate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 计算场景
|
||||
/// </summary>
|
||||
Enums.CalcScenarioEnum CalcScenario { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 波动率类型
|
||||
/// </summary>
|
||||
string VolType { get; }
|
||||
|
||||
bool IsEodCalc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 标的数据提供者
|
||||
/// </summary>
|
||||
IUnderlyingDataProvider UnderlyingDataProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 标的价格提供者
|
||||
/// </summary>
|
||||
IPriceProvider UnderlyingPriceProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 标的结算价格提供者
|
||||
/// </summary>
|
||||
IPriceProvider UnderlyingSettlePriceProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 场内期权价格提供者
|
||||
/// </summary>
|
||||
IPriceProvider ExchangeOptionPriceProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误处理接口
|
||||
/// </summary>
|
||||
IErrorHandler ErrorHandler { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 对冲交易佣金计算接口
|
||||
/// </summary>
|
||||
IExchangeTradeCommissionCalc CommissionCalc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 场内期权价格使用选项
|
||||
/// 注意:在计算类中光子将忽略此项并固定为CalcPv
|
||||
/// </summary>
|
||||
ExchangeOptionPriceUseFlag ExchangeOptionPriceUseFlag { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建期权计算上下文对象
|
||||
/// </summary>
|
||||
IOtcTradeValueCalcContext CreateOptionCalculateContext();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场内期权价格使用选项
|
||||
/// </summary>
|
||||
public enum ExchangeOptionPriceUseFlag
|
||||
{
|
||||
/// <summary>
|
||||
/// 不使用
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 只设置HedgePnl的ExOptionPrice属性值
|
||||
/// </summary>
|
||||
SetExOptionPrice = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 用来计算PV(此选项涵盖了SetExOptionPrice选项)
|
||||
/// </summary>
|
||||
CalcPv = SetExOptionPrice + 1,
|
||||
|
||||
/// <summary>
|
||||
/// 价格试算模式
|
||||
/// </summary>
|
||||
TrialCalclMode = 11
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using YLErp.Abstract;
|
||||
using YLErp.Abstract.DataProviders;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule.Abstract
|
||||
{
|
||||
/// <summary>
|
||||
/// 期权估值计算数据提供者
|
||||
/// </summary>
|
||||
public interface IOptionCalcDataProvider
|
||||
{
|
||||
IPriceProvider UnderlyingPriceProvider { get; }
|
||||
|
||||
IUnderlyingDataProvider UnderlyingDataProvider { get; }
|
||||
|
||||
ITradeExtendDataProvider TradeExtendDataProvider { get; }
|
||||
|
||||
IVolatilityDataProvider VolatilityDataProvider { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using Qdp.Pricing.Base.Implementations;
|
||||
using YLErp.Abstract;
|
||||
using YLErp.Commons;
|
||||
using YLErp.Enums;
|
||||
using YLErp.QdpModule;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule.Abstract
|
||||
{
|
||||
/// <summary>
|
||||
/// 场外交易估值计算上下文接口
|
||||
/// </summary>
|
||||
public interface IOtcTradeValueCalcContext : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 计算场景枚举
|
||||
/// </summary>
|
||||
CalcScenarioEnum CalcScenario { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户组
|
||||
/// </summary>
|
||||
string UserGroup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 估值日期
|
||||
/// </summary>
|
||||
DateTime ValueDate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 波动率类型
|
||||
/// </summary>
|
||||
string VolType { get; }
|
||||
|
||||
bool IsEodCalc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 系统全局无风险利率
|
||||
/// </summary>
|
||||
double SysRiskFreeRate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否精确时间模式
|
||||
/// </summary>
|
||||
bool IsPreciseTimeMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 增加波动率比率
|
||||
/// </summary>
|
||||
double AddingVolRate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// QDP市场代理
|
||||
/// </summary>
|
||||
MarketProxy MarketProxy { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据提供者(接口实现中不能为null)
|
||||
/// </summary>
|
||||
IOptionCalcDataProvider DataProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误处理接口
|
||||
/// </summary>
|
||||
IErrorHandler ErrorHandler { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否计算T+1日Delta
|
||||
/// </summary>
|
||||
bool CalcDeltaT1 { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 追踪
|
||||
/// </summary>
|
||||
TraceWrap Trace { get; }
|
||||
|
||||
//----------------------------------------
|
||||
//方法
|
||||
//----------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// 根据交易品种ID获取此交易品种是否存在夜盘
|
||||
/// </summary>
|
||||
bool HasNightMarket(int varietyId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取标的关联性
|
||||
/// </summary>
|
||||
double GetCorrelation(int underlyingId1, int underlyingId2);
|
||||
|
||||
/// <summary>
|
||||
/// 为计算准备波动率
|
||||
/// </summary>
|
||||
bool PrepareVolatility(string qdpTradeId, OtcTradeBase trade, double spotPrice, out string[] volsurfaceNames);
|
||||
|
||||
/// <summary>
|
||||
/// 获取交易的无风险利率
|
||||
/// </summary>
|
||||
double GetRiskFreeRate(OtcTradeBase trade);
|
||||
|
||||
/// <summary>
|
||||
/// 获取交易的分红率
|
||||
/// </summary>
|
||||
double GetDividendRate(OtcTradeBase trade);
|
||||
|
||||
/// <summary>
|
||||
/// 获取期权计算模式
|
||||
/// </summary>
|
||||
PricingRequest GetPricingRequest(OtcTradeBase trade);
|
||||
|
||||
/// <summary>
|
||||
/// 为亚式期权交易获取fixing数据
|
||||
/// </summary>
|
||||
string GetFixingString(OtcTradeBase trade, trade_asian_option asianOption, double spotPrice);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using YLErp.Modules.TradeModule.KnockOutModule.Dto;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule.Abstract
|
||||
{
|
||||
/// <summary>
|
||||
/// 交易敲出 收益计算 服务
|
||||
/// </summary>
|
||||
public interface ITradeKnockOutPayoffCalcService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取交易敲出收益
|
||||
/// </summary>
|
||||
/// <param name="td"></param>
|
||||
/// <param name="underlyingPrice">实时标的价格</param>
|
||||
/// <returns></returns>
|
||||
GetKnockOutPayoffResult GetKnockOutPayoff(trade td, double underlyingPrice);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
//using YLErp.DBModels;
|
||||
|
||||
//namespace YLErp.Modules.CalculationModule.Abstract
|
||||
//{
|
||||
|
||||
// /// <summary>
|
||||
// /// 期权标的数据提供者
|
||||
// /// </summary>
|
||||
// public interface IUnderlyingRelProvider
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// 获取品种信息
|
||||
// /// </summary>
|
||||
// Variety GetVariety(int varietyId);
|
||||
|
||||
// /// <summary>
|
||||
// /// 根据标的ID获取标的
|
||||
// /// </summary>
|
||||
// underlying_manager GetUnderlying(int underlyingId);
|
||||
|
||||
// /// <summary>
|
||||
// /// 根据标的代码获取标的
|
||||
// /// </summary>
|
||||
// underlying_manager GetUnderlying(string underlyingCode);
|
||||
|
||||
// /// <summary>
|
||||
// /// 根据标的代码获取场内期权标的
|
||||
// /// </summary>
|
||||
// ExchangeListOption GetExchange_List_Option(string ContractCode);
|
||||
|
||||
// /// <summary>
|
||||
// /// 获取相关性
|
||||
// /// </summary>
|
||||
// CorrelationTable GetCorrelation(int underlyingId1, int underlyingId2);
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,35 @@
|
||||
using YLErp.Abstract;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule.Abstract
|
||||
{
|
||||
/// <summary>
|
||||
/// 波动率提供接口
|
||||
/// </summary>
|
||||
public interface IVolatilityDataProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取标的波动率
|
||||
/// </summary>
|
||||
IVolatility GetUnderlyingVol(DateTime valueDate, string voltype, string contractCode, string userGroup);
|
||||
|
||||
/// <summary>
|
||||
/// 获取场外期权持仓波动率
|
||||
/// </summary>
|
||||
IOtcTradeVolatility GetOtcPositionVol(int tradeId, DateTime valueDate);
|
||||
|
||||
/// <summary>
|
||||
/// 获取场外期权对冲波动率
|
||||
/// </summary>
|
||||
double? GetOtcHedgingVol(int tradeId, DateTime valueDate);
|
||||
|
||||
/// <summary>
|
||||
/// 获取场外期权到期结算波动率
|
||||
/// </summary>
|
||||
double? GetOtcEodOverrideVol(int tradeId, DateTime valueDate);
|
||||
|
||||
/// <summary>
|
||||
/// 获取场内期权保存的波动率
|
||||
/// </summary>
|
||||
double? GetExOptionSavedVol(string optionCode, DateTime valueDate);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace YLErp.Modules.CalculationModule.Abstract
|
||||
{
|
||||
class TradeCalcExpception : Exception
|
||||
{
|
||||
public TradeCalcExpception(int tradeId, string message)
|
||||
: base($"(trade id:{tradeId}){message}")
|
||||
{
|
||||
TradeId = tradeId;
|
||||
}
|
||||
|
||||
public int TradeId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using Qdp.Pricing.Library.Options.Products.Asian;
|
||||
using YLErp.BLL;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 亚式期权计算帮助类
|
||||
/// </summary>
|
||||
public static class AsianOptionCalcHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取亚式期权均价
|
||||
/// </summary>
|
||||
public static double GetAveragePrice(OtcTradeBase trade, trade_asian_option asiaOption, double price, DateTime valueDate, out int fixingCount)
|
||||
{
|
||||
fixingCount = 0;
|
||||
|
||||
var startDate = asiaOption?.AveragingPeriodStartDate ?? trade.TradeDate.Value;
|
||||
|
||||
//均价起算日大于结算日的情况下取计值日
|
||||
if (startDate > valueDate)
|
||||
{
|
||||
return price;
|
||||
}
|
||||
|
||||
if (startDate == valueDate)
|
||||
{
|
||||
fixingCount = 1;
|
||||
return price;
|
||||
}
|
||||
|
||||
var fixings = AsianOptionFixingService.GetFixingString(valueDate, trade, asiaOption);
|
||||
|
||||
if (string.IsNullOrEmpty(fixings))
|
||||
{
|
||||
return price;
|
||||
}
|
||||
|
||||
var req = new OptionTradeParamRequest(valuedateBLL.SysRiskFreeRate())
|
||||
{
|
||||
tradeId = trade.TradeNumber,
|
||||
fixings = fixings,
|
||||
hasNightMarket = false,
|
||||
maturityShift = 0,
|
||||
ParamOverride = null,
|
||||
preciseTimeMode = false,
|
||||
timeToMaturityDays = double.NaN,
|
||||
volSurfaceNames = null
|
||||
};
|
||||
|
||||
var QdpTrade = QdpTradeBuilder.GetAsianOptionTrade(trade, asiaOption, req);
|
||||
|
||||
if (QdpTrade != null && QdpTrade.Instrument != null && QdpTrade.Instrument is AsianOption asianOpt)
|
||||
{
|
||||
fixingCount = asianOpt.Fixings.Count;
|
||||
|
||||
return asiaOption.StrikeType == "Floating" ? asianOpt.ActualStrike : asianOpt.FinalPrice();
|
||||
}
|
||||
|
||||
return price;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
using YLErp.Abstract.DataProviders;
|
||||
using YLErp.BLL;
|
||||
using YLErp.BLL.Calculation;
|
||||
using YLErp.Modules.DataProviderModule;
|
||||
using YLErp.QdpModule;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
public class CCRService : YLBaseService
|
||||
{
|
||||
public CCRService(OptUserInfo userInfo) : base(userInfo)
|
||||
{
|
||||
}
|
||||
|
||||
public CCRService(YLBaseService baseService) : base(baseService)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否实时计算
|
||||
/// <para>实时计算时传参不一样</para>
|
||||
/// </summary>
|
||||
public bool IsRealtime { get; set; }
|
||||
|
||||
private DateTime? _settlementDate = null;
|
||||
/// <summary>
|
||||
/// 结算日期
|
||||
/// </summary>
|
||||
public DateTime SettlementDate
|
||||
{
|
||||
get
|
||||
{
|
||||
return _settlementDate ?? SystemValueDate;
|
||||
}
|
||||
set
|
||||
{
|
||||
_settlementDate = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 要忽略计算的交易类型
|
||||
/// </summary>
|
||||
private readonly List<string> ignoreTradeTypes = new List<string>()
|
||||
{
|
||||
"结构化交易",
|
||||
"自定义交易"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 执行ccr计算
|
||||
/// <para>原公式:CCR=MTM+k*(ADD-ON);最新的更改为:CCR=MTM+(ADD-ON)</para>
|
||||
/// <para>MTM:持仓浮动盈亏</para>
|
||||
/// <para>ADD-ON:假定未来盈亏</para>
|
||||
/// </summary>
|
||||
/// <param name="trades">要计算的交易;一般为某个客户的所有持仓交易</param>
|
||||
/// <param name="j">CCR算法必要参数</param>
|
||||
/// <param name="N">CCR算法必要参数</param>
|
||||
/// <returns>Key:交易Id:Value:CCR值</returns>
|
||||
public Dictionary<int, double> CalculationCCR(List<trade> trades, double j, double N)
|
||||
{
|
||||
var result = new Dictionary<int, double>();
|
||||
var add_onVal = CalculationADD_ON(trades, (int)(j * 244), (int)N, out var mtmVal);
|
||||
if (add_onVal.Count != mtmVal.Count)
|
||||
{
|
||||
throw new Exception($"计算出错,结果数量不匹配:\r\nadd_on:{add_onVal.ToJson()}\r\nmtm:{mtmVal.ToJson()}");
|
||||
}
|
||||
foreach (var item in add_onVal)
|
||||
{
|
||||
result[item.Key] = mtmVal[item.Key] + item.Value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算ADD_ON
|
||||
/// <para>算法由国海提供</para>
|
||||
/// </summary>
|
||||
/// <param name="trades">要计算的交易;一般为某个客户的所有持仓交易</param>
|
||||
/// <param name="dayCount">计算要覆盖的交易日数量;一般为244的倍数</param>
|
||||
/// <param name="interval">计算时滚动价格的间隔;为国海算法中的N</param>
|
||||
/// <param name="mtmVal">当前持仓交易持仓部分累计浮动盈亏;</param>
|
||||
/// <returns>Key:交易Id:Value:CCR值</returns>
|
||||
private Dictionary<int, double> CalculationADD_ON(List<trade> trades, int dayCount, int interval, out Dictionary<int, double> mtmVal)
|
||||
{
|
||||
if (trades is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(trades));
|
||||
}
|
||||
|
||||
var calcTrades = trades.Where(O => !ignoreTradeTypes.Contains(O.TradeType)).ToList();
|
||||
if (calcTrades.Count == 0)
|
||||
{
|
||||
throw new ServiceException("不存在可计算的交易");
|
||||
}
|
||||
var underlyingCodes = calcTrades.Select(O => O.UnderlyingCode);
|
||||
var dateList = QdpCalendarHelper.AllBizDays(QdpCalendarHelper.BizDayShift(SettlementDate, -dayCount), SettlementDate);
|
||||
dateList.Add(SettlementDate);
|
||||
var settlementType = ConsGlobal.SettlePriceMode.GetSettlementType(valuedateBLL.SystemDate.EodSettlePriceMode);
|
||||
var latestPriceProvider = IsRealtime ? (IPriceProvider)DataCacheProvider.GetUnderlyingDataSource() : new EodPriceProvider(SettlementDate).GetPriceProvider(settlementType);
|
||||
var latestRiskResult = CalculatorHelper.CalculateRisksForTrades(SettlementDate, calcTrades, Enums.CalcScenarioEnum.EodSettlement, latestPriceProvider, QdpPricingRequest.PV_ONLY, settlementType: settlementType, isUseTradeVol: PS.Config.IsTradeVol, preciseTimeMode: false, canUseManual: true);
|
||||
var priceProviderDict = new Dictionary<DateTime, EodPriceProvider>();
|
||||
var latestPvMap = new Dictionary<int, double>();
|
||||
// 日期维度 合计值 交易编号 浮动盈亏
|
||||
var pvList = new List<KeyValuePair<double, Dictionary<int, double>>>();
|
||||
//var pricessss = new List<string>();
|
||||
|
||||
for (int i = 0; i < dateList.Count; i++)
|
||||
{
|
||||
TradeRiskResult riskResult = null;
|
||||
if (dateList[i].Date.Equals(SettlementDate.Date))
|
||||
{
|
||||
riskResult = latestRiskResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
var priceProvider = new EodPriceProvider(dateList[i]);
|
||||
priceProviderDict[dateList[i]] = priceProvider;
|
||||
if (i < interval) { continue; }
|
||||
var calcPriceProvider = new ManualPriceProvider();
|
||||
//pricessss.Add("");
|
||||
|
||||
foreach (var code in underlyingCodes)
|
||||
{
|
||||
var currentPrice = priceProviderDict[dateList[i]].GetPrice(code, SettlementTypeEnum.ClosePrice);
|
||||
var lastPrice = priceProviderDict[dateList[i - interval]].GetPrice(code, SettlementTypeEnum.ClosePrice);
|
||||
var price = latestPriceProvider.GetPrice(code) * (currentPrice / lastPrice).Normalize();
|
||||
//pricessss[i - 1] += $"{dateList[i].ToString("yyyy-MM-dd")}\t{code}\t{currentPrice}\t{lastPrice}\t{price}\t";
|
||||
calcPriceProvider.SetPrice(code, price);
|
||||
}
|
||||
riskResult = CalculatorHelper.CalculateRisksForTrades(SystemValueDate, calcTrades, IsRealtime ? Enums.CalcScenarioEnum.Pricing : Enums.CalcScenarioEnum.EodSettlement, calcPriceProvider, QdpPricingRequest.PV_ONLY, settlementType: settlementType, isUseTradeVol: PS.Config.IsTradeVol, preciseTimeMode: false);
|
||||
}
|
||||
//ADD-ON合计规则
|
||||
var pv = riskResult.Results.ToDictionary(
|
||||
K => K.Trade.id,
|
||||
V =>
|
||||
{
|
||||
if (!(V.Trade.MetaDic.TryGetValue(YLErp.DBModels.Consts.ConsTradeMetaKey.CCR_K, out var strK) && double.TryParse(strK, out var k)))
|
||||
{
|
||||
k = 1;
|
||||
}
|
||||
if (!latestPvMap.ContainsKey(V.Trade.id))
|
||||
{
|
||||
latestPvMap[V.Trade.id] = latestRiskResult.Results.Where(B => B.Trade.id == V.Trade.id).FirstOrDefault()?.ValueResult.Pv ?? 0;
|
||||
}
|
||||
return k * (V.ValueResult.Pv - latestPvMap[V.Trade.id]);
|
||||
});
|
||||
//System.Diagnostics.Debug.WriteLine(((double)i / dateList.Count).ToString("0.##%"));
|
||||
pvList.Add(new KeyValuePair<double, Dictionary<int, double>>(pv.Values.Sum(), pv));
|
||||
}
|
||||
//LogFactory.GetLogger<CCRService>().Info($"计算次数:{pvList.Count}\r\n{pvList.Select(O => O.Value).ToJson()}");
|
||||
mtmVal = latestRiskResult.Results
|
||||
.ToDictionary(
|
||||
K => K.Trade.id,
|
||||
V => V.ValueResult.Pv);
|
||||
return pvList.OrderByDescending(O => O.Key).FirstOrDefault().Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using YLErp.Modules.CalculationModule.Abstract;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 计算检查帮助类
|
||||
/// </summary>
|
||||
public static class CalcCheckHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 检查IOptionCalcDataProvider接口实现
|
||||
/// </summary>
|
||||
public static T CheckOptionCalcDataProvider<T>(T dataProvider) where T : class, IOptionCalcDataProvider
|
||||
{
|
||||
if (dataProvider is null)
|
||||
{
|
||||
throw new ArgumentException("接口实现类 不能为null", nameof(IOptionCalcDataProvider));
|
||||
}
|
||||
|
||||
if (dataProvider.UnderlyingDataProvider is null)
|
||||
{
|
||||
throw new ArgumentException("UnderlyingDataProvider 不能为null", nameof(IOptionCalcDataProvider));
|
||||
}
|
||||
|
||||
if (dataProvider.UnderlyingPriceProvider is null)
|
||||
{
|
||||
throw new ArgumentException("UnderlyingPriceProvider 不能为null", nameof(IOptionCalcDataProvider));
|
||||
}
|
||||
|
||||
if (dataProvider.VolatilityDataProvider is null)
|
||||
{
|
||||
throw new ArgumentException("VolatilityDataProvider 不能为null", nameof(IOptionCalcDataProvider));
|
||||
}
|
||||
|
||||
if (dataProvider.TradeExtendDataProvider is null)
|
||||
{
|
||||
throw new ArgumentException("TradeExtendDataProvider 不能为null", nameof(IOptionCalcDataProvider));
|
||||
}
|
||||
|
||||
return dataProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查IHedgePnlCalcContext接口实现
|
||||
/// </summary>
|
||||
public static T CheckHedgePnlCalcContext<T>(T context) where T : class, IHedgePnlCalcContext
|
||||
{
|
||||
if (context is null)
|
||||
{
|
||||
throw new ArgumentException("接口实现类 不能为null", nameof(IHedgePnlCalcContext));
|
||||
}
|
||||
|
||||
if (context.UnderlyingDataProvider is null)
|
||||
{
|
||||
throw new ArgumentException("UnderlyingDataProvider 不能为null", nameof(IHedgePnlCalcContext));
|
||||
}
|
||||
|
||||
if (context.UnderlyingPriceProvider is null)
|
||||
{
|
||||
throw new ArgumentException("UnderlyingPriceProvider 不能为null", nameof(IHedgePnlCalcContext));
|
||||
}
|
||||
|
||||
if (context.UnderlyingSettlePriceProvider is null)
|
||||
{
|
||||
throw new ArgumentException("UnderlyingSettlePriceProvider 不能为null", nameof(IHedgePnlCalcContext));
|
||||
}
|
||||
|
||||
if (context.ExchangeOptionPriceProvider is null)
|
||||
{
|
||||
throw new ArgumentException("ExchangePriceProvider 不能为null", nameof(IHedgePnlCalcContext));
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
using YLErp.DBModels.Enums;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 场内交易手续费计算帮助类
|
||||
/// </summary>
|
||||
public static class CommissionCalcHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 计算某交易日某合约商品期货交易的手续费,
|
||||
/// 因为手续费计算涉及到是否为平当日开仓,因此需要将当日的交易合并计算才准确
|
||||
/// </summary>
|
||||
public static Dictionary<int, double> GetCommissionForTrade(underlying_manager underlying, IEnumerable<ExchangeTrade> trades)
|
||||
{
|
||||
var CommissionDict = new Dictionary<int, double>();
|
||||
if (underlying == null)
|
||||
{
|
||||
return CommissionDict;
|
||||
}
|
||||
var variety = DataCacheProvider.GetVarietyDataSource().GetData(underlying.UnderlyingTypeId);
|
||||
return GetCommissionForFutureTrades(variety, trades);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算某交易日某合约商品期货交易的手续费,
|
||||
/// 因为手续费计算涉及到是否为平当日开仓,因此需要将当日的交易合并计算才准确
|
||||
/// </summary>
|
||||
public static Dictionary<int, double> GetCommissionForFutureTrades(Variety variety, IEnumerable<ExchangeTrade> trades)
|
||||
{
|
||||
var CommissionDict = new Dictionary<int, double>();
|
||||
|
||||
if (variety == null)
|
||||
{
|
||||
return CommissionDict;
|
||||
}
|
||||
|
||||
var todayOpenVolume = trades.Where(x => x.CommissionType == CommissionType.系统计算 && x.TradeSide.IndexOf("开仓") > 0).Sum(y => y.Notional);
|
||||
|
||||
foreach (var trade in trades)
|
||||
{
|
||||
if (trade.CommissionType == CommissionType.不收取)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trade.CommissionType == CommissionType.手动录入)
|
||||
{
|
||||
CommissionDict[trade.id] = trade.Commission;
|
||||
continue;
|
||||
}
|
||||
|
||||
//老数据还是自动去算。
|
||||
if (trade.TradeSide.IndexOf("开仓") > 0)
|
||||
{
|
||||
CommissionDict[trade.id] = GetRegularCommissionForFutureTrade(variety, trade.UnderlyingCode, trade.Notional, trade.TradeSinglePrice);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (trade.Notional <= todayOpenVolume)
|
||||
{
|
||||
CommissionDict[trade.id] = GetCloseTodayCommissionForFutrueTrade(variety, trade.UnderlyingCode, trade.Notional, trade.TradeSinglePrice);
|
||||
todayOpenVolume -= trade.Notional;
|
||||
}
|
||||
else
|
||||
{
|
||||
CommissionDict[trade.id] = GetCloseTodayCommissionForFutrueTrade(variety, trade.UnderlyingCode, todayOpenVolume, trade.TradeSinglePrice)
|
||||
+ GetRegularCommissionForFutureTrade(variety, trade.UnderlyingCode, trade.Notional - todayOpenVolume, trade.TradeSinglePrice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return CommissionDict;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算通常的商品期货交易手续费
|
||||
/// </summary>
|
||||
private static double GetRegularCommissionForFutureTrade(Variety variety, string underlyingCode, double notional, double price)
|
||||
{
|
||||
//如果有平今仓手续费合约规则
|
||||
if (!string.IsNullOrWhiteSpace(variety.CloseTodayContractRule))
|
||||
{
|
||||
var contractMonthList = variety.CloseTodayContractRule.Split(new char[] { ',' }).Select(x => Int32.Parse(x));
|
||||
if (contractMonthList.Contains(int.Parse(underlyingCode.Substring(underlyingCode.Length - 2))))
|
||||
{
|
||||
return variety.CloseTodayCommissionType == ConsCommissionType.Ratio
|
||||
? (variety.CloseTodayCommission ?? 0.0) * notional * price
|
||||
: (variety.CloseTodayCommission ?? 0.0) * (int)(notional / variety.TradeUnitValue ?? 1.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
return variety.CommissionType == ConsCommissionType.Ratio
|
||||
? (variety.Commission ?? 0.0) * notional * price
|
||||
: (variety.Commission ?? 0.0) * (int)(notional / variety.TradeUnitValue ?? 1.0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return variety.CommissionType == ConsCommissionType.Ratio
|
||||
? (variety.Commission ?? 0.0) * notional * price
|
||||
: (variety.Commission ?? 0.0) * (int)(notional / variety.TradeUnitValue ?? 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算场内期权交易的手续费,
|
||||
/// </summary>
|
||||
public static Dictionary<int, double> GetCommissionForOptionTrade(IEnumerable<ExchangeTrade> trades)
|
||||
{
|
||||
var CommissionDict = new Dictionary<int, double>();
|
||||
|
||||
var umProvider = DataCacheProvider.GetUnderlyingDataSource();
|
||||
|
||||
foreach (var trade in trades)
|
||||
{
|
||||
if (trade.CommissionType == CommissionType.不收取)
|
||||
{
|
||||
CommissionDict[trade.id] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trade.CommissionType == CommissionType.手动录入)
|
||||
{
|
||||
CommissionDict[trade.id] = trade.Commission;
|
||||
continue;
|
||||
}
|
||||
|
||||
var um = umProvider.GetData(trade.UnderlyingId);
|
||||
if (um == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var variety = DataCacheProvider.GetVarietyDataSource().GetData(um.UnderlyingTypeId);
|
||||
if (variety == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
CommissionDict[trade.id] = GetRegularCommissionForOptionTrade(variety, trade.Notional, trade.TradeSinglePrice);
|
||||
}
|
||||
return CommissionDict;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算通常的场内期权交易手续费
|
||||
/// </summary>
|
||||
public static double GetRegularCommissionForOptionTrade(Variety variety, double notional, double price)
|
||||
{
|
||||
return variety.TradedOptionCommissionType == ConsCommissionType.Ratio
|
||||
? (variety.TradedOptionCommission ?? 0.0) * notional * price
|
||||
: (variety.TradedOptionCommission ?? 0.0) * (int)(notional / variety.TradeUnitValue ?? 1.0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算通常的场内期权交易手续费
|
||||
/// </summary>
|
||||
public static double GetRegularCommissionForOptionTrade(Variety variety, double notional, double price,double contractSize)
|
||||
{
|
||||
if (contractSize == 0)
|
||||
{
|
||||
contractSize = variety.TradeUnitValue ?? 1.0;
|
||||
}
|
||||
return variety.TradedOptionCommissionType == ConsCommissionType.Ratio
|
||||
? (variety.TradedOptionCommission ?? 0.0) * notional * price
|
||||
: (variety.TradedOptionCommission ?? 0.0) * (notional / contractSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算平今仓的商品期货期权手续费
|
||||
/// </summary>
|
||||
private static double GetCloseTodayCommissionForFutrueTrade(Variety variety, string underlyingCode, double notional, double price)
|
||||
{
|
||||
//平今仓手续费类型缺失,或者为“正常”,则按正常手续费计算
|
||||
if (string.IsNullOrWhiteSpace(variety.CloseTodayCommissionType)
|
||||
|| variety.CloseTodayCommissionType == ConsCommissionType.Regular)
|
||||
{
|
||||
return GetRegularCommissionForFutureTrade(variety, underlyingCode, notional, price);
|
||||
}
|
||||
else
|
||||
{
|
||||
//如果有平今仓手续费合约规则
|
||||
if (!string.IsNullOrWhiteSpace(variety.CloseTodayContractRule))
|
||||
{
|
||||
var contractMonthList = variety.CloseTodayContractRule.Split(new char[] { ',' }).Select(x => Int32.Parse(x));
|
||||
if (contractMonthList.Contains(Int32.Parse(underlyingCode.Substring(underlyingCode.Length - 2))))
|
||||
{
|
||||
return variety.CloseTodayCommissionType == ConsCommissionType.Ratio
|
||||
? (variety.CloseTodayCommission ?? 0.0) * notional * price
|
||||
: (variety.CloseTodayCommission ?? 0.0) * (int)(notional / variety.TradeUnitValue ?? 1.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetRegularCommissionForFutureTrade(variety, underlyingCode, notional, price);
|
||||
}
|
||||
}
|
||||
else //没有平今仓手续费合约规则,则统一按平今仓手续费类型计算
|
||||
{
|
||||
return variety.CloseTodayCommissionType == ConsCommissionType.Ratio
|
||||
? (variety.CloseTodayCommission ?? 0.0) * notional * price
|
||||
: (variety.CloseTodayCommission ?? 0.0) * (int)(notional / variety.TradeUnitValue ?? 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
using YLErp.DBModels.Enums;
|
||||
using YLErp.Modules.CalculationModule.Abstract;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 交易佣金计算
|
||||
/// </summary>
|
||||
public class ExchangeTradeCommissionCalc : IExchangeTradeCommissionCalc
|
||||
{
|
||||
/// <summary>
|
||||
/// 计算交易手续费
|
||||
/// </summary>
|
||||
public ITradeCommissionCalcResult GetTradeCommission(IEnumerable<ExchangeTrade> tradeList, bool actualTrade = true)
|
||||
{
|
||||
if (tradeList == null || !tradeList.Any())
|
||||
{
|
||||
return TradeCommissionResult.Empty;
|
||||
}
|
||||
|
||||
var dic = new Dictionary<int, double>();
|
||||
|
||||
//当日成交的对冲交易计算
|
||||
var futureTradeDict = tradeList.Where(t => t.TradeType == "商品期货").ToLookup(t => t.UnderlyingId);
|
||||
|
||||
foreach (var item in futureTradeDict)
|
||||
{
|
||||
var trads = item.ToList();
|
||||
var underlying = GetUnderlying(item.Key);
|
||||
if (underlying == null) { continue; }
|
||||
var vareity = GetVariety(underlying.UnderlyingTypeId);
|
||||
var commissionDict = CommissionCalcHelper.GetCommissionForFutureTrades(vareity, trads);
|
||||
if (commissionDict != null && commissionDict.Count > 0)
|
||||
{
|
||||
foreach (var tempItem in commissionDict)
|
||||
{
|
||||
dic[tempItem.Key] = tempItem.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var insiteTradeList = tradeList.Where(t => t.TradeType == "场内期权").ToList();
|
||||
Dictionary<string, double> insiteTradeContractSizeDic = null;
|
||||
if (insiteTradeList != null&& insiteTradeList.Count>0)
|
||||
{
|
||||
insiteTradeContractSizeDic = GetInsiteTradeContractSize(insiteTradeList.Select(p => p.OptionCode).Distinct().ToList());
|
||||
}
|
||||
if (insiteTradeContractSizeDic == null)
|
||||
{
|
||||
insiteTradeContractSizeDic = new Dictionary<string, double>();
|
||||
}
|
||||
|
||||
foreach (var td in tradeList)
|
||||
{
|
||||
if (td.TradeType == "商品期货")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (td.CommissionType == CommissionType.手动录入)
|
||||
{
|
||||
dic[td.id] = td.Commission;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (td.TradeType == "场内期权") //当日成交的场内期权交易计算手续费
|
||||
{
|
||||
var underlying = GetUnderlying(td.UnderlyingId);
|
||||
if (underlying == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var variety = GetVariety(underlying.UnderlyingTypeId);
|
||||
if (variety == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
dic[td.id] = CommissionCalcHelper.GetRegularCommissionForOptionTrade(variety, td.Notional, td.TradeSinglePrice, insiteTradeContractSizeDic.ContainsKey(td.OptionCode) ? insiteTradeContractSizeDic[td.OptionCode] : 0);
|
||||
}
|
||||
else if (td.TradeType == "股票") //股票交易交易费计算
|
||||
{
|
||||
var stockCommissionConfig = GetStock_Commission_Config(td.ExchangeAccountId);
|
||||
if (stockCommissionConfig != null && stockCommissionConfig.Enabled == 1)
|
||||
{
|
||||
dic[td.id] = StockTradeCalcHelper.GetStockAllTradeExpenses(td, stockCommissionConfig, actualTrade);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new TradeCommissionResult(dic);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取场内期权合约乘数
|
||||
/// </summary>
|
||||
/// <param name="contractCodes"></param>
|
||||
/// <returns></returns>
|
||||
private Dictionary<string, double> GetInsiteTradeContractSize(List<string> contractCodes)
|
||||
{
|
||||
using (var db = DbContextFactory.GetYLDbContext())
|
||||
{
|
||||
return db.exchange_list_option.AsNoTracking().Where(p => contractCodes.Contains(p.ContractCode)).ToDictionary(p => p.ContractCode, p => p.ContractSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected virtual StockCommissionConfig GetStock_Commission_Config(int exchangeAccountId)
|
||||
{
|
||||
|
||||
// 同步簿记账户 对应 多个 对冲账户的情况下,可能出现异常问题,取不到正确的值,需产品重新规划
|
||||
var account = DataCacheProvider.GetExchangeAccountDataSource().AsQueryable().FirstOrDefault(n => n.id == exchangeAccountId);
|
||||
return account == null ? null : DataCacheProvider.GetStockCommissionDataSource().AsQueryable().FirstOrDefault(n => n.ExchangeAccountCode == account.AccountCode);
|
||||
}
|
||||
|
||||
protected virtual underlying_manager GetUnderlying(int underlyingId)
|
||||
{
|
||||
return DataCacheProvider.GetUnderlyingDataSource().GetData(underlyingId);
|
||||
}
|
||||
|
||||
protected virtual Variety GetVariety(int varietyId)
|
||||
{
|
||||
return DataCacheProvider.GetVarietyDataSource().GetData(varietyId);
|
||||
}
|
||||
|
||||
class TradeCommissionResult : ITradeCommissionCalcResult
|
||||
{
|
||||
readonly Dictionary<int, double> _dic;
|
||||
|
||||
public TradeCommissionResult(Dictionary<int, double> dic)
|
||||
{
|
||||
_dic = dic;
|
||||
}
|
||||
|
||||
public double GetTradeCommission(int tradeId)
|
||||
{
|
||||
if (_dic != null && _dic.TryGetValue(tradeId, out double dd))
|
||||
{
|
||||
return dd;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static readonly TradeCommissionResult Empty;
|
||||
|
||||
static TradeCommissionResult()
|
||||
{
|
||||
Empty = new TradeCommissionResult(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
using Qdp.Pricing.Base.Implementations;
|
||||
using YLErp.BLL;
|
||||
using YLErp.Models;
|
||||
using YLErp.Modules.TradeModule.DealModule;
|
||||
using YLErp.QdpModule;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取fixing的服务
|
||||
/// </summary>
|
||||
public static class FixingService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取fixing数据
|
||||
/// </summary>
|
||||
public static string GetFixingString(FixingRequest request)
|
||||
{
|
||||
if (request is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (request.ValueDate == default)
|
||||
{
|
||||
request.ValueDate = valuedateBLL.ValueDate;
|
||||
}
|
||||
|
||||
if (request.StartDate == default)
|
||||
{
|
||||
request.StartDate = request.ValueDate;
|
||||
}
|
||||
else if (request.ValueDate < request.StartDate)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
//估值日期大于到期日期则估值日期变为到期日期
|
||||
if (request.ValueDate > request.ExerciseDate)
|
||||
{
|
||||
request.ValueDate = request.ExerciseDate;
|
||||
}
|
||||
|
||||
Dictionary<DateTime, double> priceDic = null;
|
||||
|
||||
HashSet<DateTime> observationDates = null;
|
||||
//仅选出观察日列表中的价格作为fixing
|
||||
if (!string.IsNullOrWhiteSpace(request.ObservationDates))
|
||||
{
|
||||
observationDates = QdpHelper.ParseObservationDate(request.ObservationDates).Select(x => x.DateTime).ToHashSet();
|
||||
}
|
||||
else
|
||||
{
|
||||
observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(request.StartDate, request.ValueDate).Select(x => x.DateTime).ToHashSet();
|
||||
observationDates.Add(request.StartDate);
|
||||
observationDates.Add(request.ValueDate);
|
||||
}
|
||||
|
||||
var useReferencePrice = request.SettlementType == SettlementTypeEnum.ReferencePrice;
|
||||
|
||||
using (var db = DbContextFactory.GetYLDbContext())
|
||||
{
|
||||
//日终股票价格
|
||||
if (ConsGlobal.InstrumentType.IsStock(request.InstrumentType))
|
||||
{
|
||||
var query = from e in db.eod_stock_price
|
||||
where e.UnderlyingCode == request.UnderlyingCode
|
||||
&& e.ValueDate >= request.StartDate && e.ValueDate <= request.ValueDate
|
||||
orderby e.ValueDate
|
||||
select new
|
||||
{
|
||||
e.ValueDate,
|
||||
Price = useReferencePrice ? (e.ReferencePrice ?? e.ClosePrice) : e.ClosePrice
|
||||
};
|
||||
|
||||
priceDic = query.ToDictionary(n => n.ValueDate, n => n.Price);
|
||||
|
||||
ExdividenProcess(request, priceDic);
|
||||
}
|
||||
else
|
||||
{
|
||||
var query = from e in db.eod_commodity_future_price
|
||||
where e.UnderlyingCode == request.UnderlyingCode
|
||||
&& e.ValueDate >= request.StartDate && e.ValueDate <= request.ValueDate
|
||||
orderby e.ValueDate
|
||||
select new EodPrice
|
||||
{
|
||||
ValueDate = e.ValueDate,
|
||||
ClosePrice = e.ClosePrice,
|
||||
ReferencePrice = e.ReferencePrice,
|
||||
SettlePrice = e.SettlePrice
|
||||
};
|
||||
|
||||
priceDic = query.ToDictionary(e => e.ValueDate, e => e.GetPrice(request.SettlementType));
|
||||
}
|
||||
|
||||
//厦门象屿使用参考价的交易在上午10点15分进入下一交易日,所以会出现导入参考价而没有导入收盘价和结算价的情况
|
||||
//如果取到的收盘价或结算价为0则可能是异常价格,受制于数据库结构不好修改,所以只能简单处理下
|
||||
if (priceDic != null && !useReferencePrice && PS.Config.Company == Configuration.CompanyEnum.厦门象屿
|
||||
&& request.ValueDate == valuedateBLL.ValueDate && DateTime.Now.TimeOfDay < GlobalConfig.EodStartTime
|
||||
&& priceDic.TryGetValue(request.ValueDate, out var price) && Math.Abs(price) < 1e-6)
|
||||
{
|
||||
priceDic.Remove(request.ValueDate);
|
||||
}
|
||||
|
||||
if (request.ValueDate <= request.ExerciseDate && request.ValueDate >= request.StartDate)
|
||||
{
|
||||
if (priceDic == null)
|
||||
{
|
||||
priceDic = new Dictionary<DateTime, double>();
|
||||
}
|
||||
|
||||
var valueDatePrice = DataCacheProvider.GetUnderlyingDataSource().GetData(request.UnderlyingCode).Price ?? 0;
|
||||
if (priceDic.Count < 1 && !QdpCalendarHelper.IsHoliday(request.ValueDate))
|
||||
{
|
||||
foreach (var observationDate in observationDates.OrderBy(x => x))
|
||||
{
|
||||
if(request.StartDate <= observationDate && observationDate <= request.ValueDate)
|
||||
{
|
||||
priceDic.Add(observationDate, valueDatePrice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (priceDic.Any() && priceDic.Last().Key != request.ValueDate && !QdpCalendarHelper.IsHoliday(request.ValueDate))
|
||||
{
|
||||
priceDic.Add(request.ValueDate, valueDatePrice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (priceDic != null && priceDic.Count > 0)
|
||||
{
|
||||
var em = priceDic.Where(n => observationDates.Contains(n.Key)).Select(p => $"{p.Key:yyyy-MM-dd},{p.Value}");
|
||||
|
||||
return string.Join(";", em);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取fixing数据
|
||||
/// </summary>
|
||||
/// <param name="valueDate">估值日期</param>
|
||||
/// <param name="otcTrade">场外交易</param>
|
||||
/// <param name="startDate">开始日期</param>
|
||||
/// <param name="observationDates">观察日</param>
|
||||
public static string GetFixingString(DateTime valueDate, OtcTradeBase otcTrade, DateTime startDate, string observationDates)
|
||||
{
|
||||
if (otcTrade is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetFixingString(new FixingRequest(
|
||||
tradeId: otcTrade.id,
|
||||
valueDate: valueDate,
|
||||
startDate: startDate,
|
||||
exerciseDate: otcTrade.ExerciseDate ?? valueDate,
|
||||
observationDates: observationDates,
|
||||
instrumentType: otcTrade.UnderlyingInstrumentType,
|
||||
underlyingCode: otcTrade.UnderlyingCode,
|
||||
settlementType: otcTrade.SettlementType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 日终价格进行除权处理
|
||||
/// </summary>
|
||||
private static void ExdividenProcess(FixingRequest request, Dictionary<DateTime, double> priceDic)
|
||||
{
|
||||
if (priceDic == null || priceDic.Count < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var service = new DividendService(OptUserInfo.SystemUser);
|
||||
|
||||
//获取按照除权除息日期正序排列的数组,估值日不需要除权处理(O.ExDividendDate < request.ValueDate)
|
||||
var exDividendInfos = service.GetExDividendInfos(request.UnderlyingCode)
|
||||
.Where(O => O.ExDividendDate >= request.StartDate && O.ExDividendDate < request.ValueDate)
|
||||
.OrderBy(n => n.ExDividendDate).ToArray();
|
||||
|
||||
if (!exDividendInfos.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var ratioDict = new DbRecordChangesService<TradeChanges>(OptUserInfo.SystemUser).GetValue(
|
||||
changeType: DBModels.Consts.ConsInfoChangeType.Dividend,
|
||||
recordId: request.TradeId,
|
||||
fieldName: nameof(trade.DividendRatio),
|
||||
optDateStart: request.StartDate,
|
||||
optDateEnd: request.ValueDate)
|
||||
.ToDictionary(K => K.OptDate, V => { return double.TryParse(V.NewValue, out var tempValue) ? (double?)tempValue : null; });
|
||||
|
||||
//日终价格和除权除息信息都按照正序排列
|
||||
//获取除权价格则使用大于日终价格日期的除权信息除权
|
||||
//循环日终价格,如果除权日期小于价格日期则被排除掉
|
||||
|
||||
var startIndex = 0;
|
||||
|
||||
foreach (var kv in priceDic)
|
||||
{
|
||||
(var date, var price) = (kv.Key, kv.Value);
|
||||
|
||||
for (var i = startIndex; i < exDividendInfos.Length; i++)
|
||||
{
|
||||
var dividenInfo = exDividendInfos[i];
|
||||
|
||||
//除权日当天的收盘价也需要处理
|
||||
|
||||
if (date <= dividenInfo.ExDividendDate.Value)
|
||||
{
|
||||
ratioDict.TryGetValue(date, out var ratio);
|
||||
price = service.GetPrice(price, dividenInfo, ratio);
|
||||
}
|
||||
else
|
||||
{
|
||||
startIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
priceDic[date] = price;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取fixing请求参数
|
||||
/// </summary>
|
||||
/// <param name="valueDate"></param>
|
||||
/// <param name="otcTrade"></param>
|
||||
/// <returns></returns>
|
||||
public static FixingRequestBase GetRequestBase(DateTime valueDate, OtcTradeBase otcTrade)
|
||||
{
|
||||
if (otcTrade is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new FixingRequestBase(
|
||||
tradeId: otcTrade.id,
|
||||
valueDate: valueDate,
|
||||
exerciseDate: otcTrade.ExerciseDate ?? valueDate,
|
||||
instrumentType: otcTrade.UnderlyingInstrumentType,
|
||||
underlyingCode: otcTrade.UnderlyingCode,
|
||||
settlementType: otcTrade.SettlementType
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加或替换最后一天的fixing价格,仅fixing有值的情况下进行处理
|
||||
/// </summary>
|
||||
public static string AddOrReplaceLastDateSpotPrice(string fixing, DateTime lastDate, double spotPrice)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(fixing))
|
||||
{
|
||||
var span = fixing.AsSpan().Trim(';');
|
||||
var lastIndex = span.LastIndexOf(';');
|
||||
if (span.Slice(lastIndex + 1).StartsWith(lastDate.ToString("yyyy-MM-dd").AsSpan()))
|
||||
{
|
||||
fixing = span.Slice(0, lastIndex + 1).ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
fixing += ";";
|
||||
}
|
||||
|
||||
fixing += $"{lastDate:yyyy-MM-dd},{spotPrice}";
|
||||
}
|
||||
|
||||
return fixing;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 亚式期权fixing数据服务
|
||||
/// </summary>
|
||||
public static class AsianOptionFixingService
|
||||
{
|
||||
private static trade_asian_option GetAsianOption(int tradeId)
|
||||
{
|
||||
if (tradeId > 0)
|
||||
{
|
||||
using (var db = DbContextFactory.GetYLDbContext())
|
||||
{
|
||||
return db.trade_asian_option.AsNoTracking().FirstOrDefault(n => n.TradeId == tradeId);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为亚式期权交易获取fixing数据
|
||||
/// </summary>
|
||||
public static string GetFixingString(DateTime valueDate, trade trade, bool setFixingBeforeAPdStartDate = false)
|
||||
{
|
||||
if (trade.trade_asian_option == null)
|
||||
{
|
||||
trade.trade_asian_option = GetAsianOption(trade.id);
|
||||
if (trade.trade_asian_option == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
var fixingReq = GetRequest(valueDate, trade, setFixingBeforeAPdStartDate);
|
||||
|
||||
return GetFixingString(fixingReq, trade.trade_asian_option);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为亚式期权交易获取fixing数据
|
||||
/// </summary>
|
||||
public static string GetFixingString(DateTime valueDate, OtcTradeBase trade, trade_asian_option asianOption)
|
||||
{
|
||||
if (trade is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (asianOption == null || asianOption.TradeId != trade.id)
|
||||
{
|
||||
asianOption = GetAsianOption(trade.id);
|
||||
|
||||
if (asianOption == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
return FixingService.GetFixingString(valueDate, trade
|
||||
, asianOption.AveragingPeriodStartDate ?? trade.TradeDate ?? valueDate, asianOption.ObservationDates);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为亚式期权交易获取fixing数据
|
||||
/// </summary>
|
||||
public static string GetFixingString(AsianFixingRequest request, trade_asian_option asianOption)
|
||||
{
|
||||
if (request is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (asianOption == null)
|
||||
{
|
||||
asianOption = GetAsianOption(request.TradeId);
|
||||
|
||||
if (asianOption == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
var AveragingPeriodStartDate = asianOption.AveragingPeriodStartDate ?? request.ValueDate;
|
||||
|
||||
//这段逻辑从方顿Logic中抽取,但应该是通用逻辑
|
||||
if (request.ValueDate < AveragingPeriodStartDate)
|
||||
{
|
||||
if (request.SetFixingBeforeAPdStartDate)
|
||||
{
|
||||
var umPrice = DataCacheProvider.GetUnderlyingDataSource().GetData(request.UnderlyingCode).Price;
|
||||
asianOption.Fixings = $"{request.ValueDate:yyyy-MM-dd},{umPrice}";
|
||||
}
|
||||
else
|
||||
{
|
||||
asianOption.Fixings = string.Empty;
|
||||
}
|
||||
|
||||
return asianOption.Fixings;
|
||||
}
|
||||
|
||||
return FixingService.GetFixingString(new FixingRequest(request, AveragingPeriodStartDate, asianOption.ObservationDates));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取亚式期权fixing请求参数
|
||||
/// </summary>
|
||||
/// <param name="valueDate">估值日期</param>
|
||||
/// <param name="otcTrade">场外交易对象</param>
|
||||
/// <param name="onlyFixed">是否只在固定行权价时获取</param>
|
||||
/// <param name="setFixingBeforeAPdStartDate"></param>
|
||||
public static AsianFixingRequest GetRequest(DateTime valueDate, OtcTradeBase otcTrade, bool setFixingBeforeAPdStartDate = false)
|
||||
{
|
||||
if (otcTrade is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new AsianFixingRequest(
|
||||
tradeId: otcTrade.id,
|
||||
valueDate: valueDate,
|
||||
exerciseDate: otcTrade.ExerciseDate ?? valueDate,
|
||||
instrumentType: otcTrade.UnderlyingInstrumentType,
|
||||
underlyingCode: otcTrade.UnderlyingCode,
|
||||
settlementType: otcTrade.SettlementType)
|
||||
{
|
||||
SetFixingBeforeAPdStartDate = setFixingBeforeAPdStartDate
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查fixings是否需要填充
|
||||
/// </summary>
|
||||
public static string CheckAsiaFixings(OtcTradeBase tr, trade_asian_option asianOption, string fixings, double spotPrice)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(fixings) && asianOption.PayoffType == "EnhancedArithmeticAverage" && asianOption.StrikeType != "Segmented")
|
||||
{
|
||||
return $"{tr.StartDate.Value:yyyy-MM-dd},{spotPrice}";
|
||||
}
|
||||
return fixings;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 亚式期权fixing请求model
|
||||
/// </summary>
|
||||
public class AsianFixingRequest : FixingRequestBase
|
||||
{
|
||||
public AsianFixingRequest(FixingRequestBase baseReq) : base(baseReq)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public AsianFixingRequest(DateTime valueDate, int tradeId, string instrumentType, string underlyingCode, DateTime exerciseDate, SettlementTypeEnum settlementType)
|
||||
: base(valueDate, tradeId, instrumentType, underlyingCode, exerciseDate, settlementType)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 来源于以前的方顿逻辑暂时保留
|
||||
/// </summary>
|
||||
public bool SetFixingBeforeAPdStartDate { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 自定义期权交易计算服务
|
||||
/// </summary>
|
||||
public class ForwardradeCalcService
|
||||
{
|
||||
/// <summary>
|
||||
/// 计算PV/Risk(交易员角度)
|
||||
/// </summary>
|
||||
public static TradeValueResult CalcValue(OtcTradeBase trade, double spotPrice)
|
||||
{
|
||||
if (trade is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(trade));
|
||||
}
|
||||
|
||||
return CalcValue(trade.Strike ?? 0, spotPrice, trade.Notional, trade.CallPut, trade.BuySell);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算PV/Risk(交易员角度)
|
||||
/// </summary>
|
||||
public static TradeValueResult CalcValue(double strike, double spotPrice, double notional, string callput, string buysell)
|
||||
{
|
||||
var isCall = callput == "Call";
|
||||
var pv = (spotPrice - strike) * notional;
|
||||
|
||||
//买入看跌和卖出看涨取负值
|
||||
var flag = (TradeCalcHelper.IsBuy(buysell) ? 1 : 2) | (isCall ? 1 : 2);
|
||||
|
||||
TradeValueResult result;
|
||||
|
||||
if (flag == 3)
|
||||
{
|
||||
result = new TradeValueResult
|
||||
{
|
||||
Pv = -pv,
|
||||
Delta = -notional,
|
||||
DeltaCash = -notional * spotPrice
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
result = new TradeValueResult
|
||||
{
|
||||
Pv = pv,
|
||||
Delta = notional,
|
||||
DeltaCash = notional * spotPrice
|
||||
};
|
||||
}
|
||||
|
||||
result.RoundedPv = result.Pv;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算远期价值(客户角度)
|
||||
/// </summary>
|
||||
public static double CalcForwardValue(OtcTradeBase trade)
|
||||
{
|
||||
if (trade is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(trade));
|
||||
}
|
||||
|
||||
if (trade.TradeType != "远期")
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var forwardValue = ((trade.SpotPrice ?? 0) - (trade.Strike ?? 0)) * trade.Notional;
|
||||
|
||||
return trade.OptionType == "看涨" || trade.OptionType == "多头" ? forwardValue : -forwardValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,520 @@
|
||||
using YLErp.Abstract.DataProviders;
|
||||
using YLErp.BLL.Eod;
|
||||
using YLErp.BLL.Hedge;
|
||||
using YLErp.DBModels.Helpers;
|
||||
using YLErp.Modules.CalculationModule.Abstract;
|
||||
using YLErp.Modules.VolatilityModule;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 对冲交易盈亏计算
|
||||
/// </summary>
|
||||
public class HedgePnlCalc
|
||||
{
|
||||
readonly IHedgePnlCalcContext _context;
|
||||
readonly IUnderlyingDataProvider _unDataProvider;
|
||||
readonly IPriceProvider _unPriceProvider;
|
||||
readonly IPriceProvider _exchangeOptionPriceProvider;
|
||||
readonly IPriceProvider _unSettlePriceProvider;
|
||||
|
||||
public HedgePnlCalc(IHedgePnlCalcContext context)
|
||||
{
|
||||
_context = CalcCheckHelper.CheckHedgePnlCalcContext(context);
|
||||
_unDataProvider = context.UnderlyingDataProvider;
|
||||
_unPriceProvider = context.UnderlyingPriceProvider;
|
||||
_unSettlePriceProvider = context.UnderlyingSettlePriceProvider;
|
||||
_exchangeOptionPriceProvider = context.ExchangeOptionPriceProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算对冲交易Pnl信息
|
||||
/// </summary>
|
||||
/// <param name="newHedgeTrades">当日对冲交易数据</param>
|
||||
/// <param name="eodPositions">上日持仓数据</param>
|
||||
public IEnumerable<HedgePnl> Calculate(IEnumerable<ExchangeTrade> newHedgeTrades, IEnumerable<EodTradePosition> eodPositions)
|
||||
{
|
||||
if (newHedgeTrades == null && eodPositions == null)
|
||||
{
|
||||
return Enumerable.Empty<HedgePnl>();
|
||||
}
|
||||
|
||||
var pnlResults = new List<HedgePnl>();
|
||||
|
||||
//-----------------------------------------
|
||||
// 处理昨日持仓盈亏
|
||||
//-----------------------------------------
|
||||
|
||||
if (eodPositions != null)
|
||||
{
|
||||
foreach (var eodPosition in eodPositions)
|
||||
{
|
||||
ExchangeListOption exchangeOption = null;
|
||||
if (eodPosition.TradeType == "场内期权")
|
||||
{
|
||||
exchangeOption = _unDataProvider.GetExchange_List_Option(eodPosition.ExchangeOptionCode);
|
||||
//剔除已到期场内期权持仓
|
||||
if (exchangeOption == null || exchangeOption.MaturityDate < _context.ValueDate)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (!(ConsTrade.TradeTypesForHedge.Contains(eodPosition.TradeType)||ConsTrade.BondTypeList.Contains(eodPosition.TradeType)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var eodPnl = ProcessEodPositionHegePnl(eodPosition, exchangeOption);
|
||||
pnlResults.Add(eodPnl);
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------
|
||||
// 处理当日对冲交易盈亏
|
||||
//-----------------------------------------
|
||||
|
||||
if (newHedgeTrades != null && newHedgeTrades.Any())
|
||||
{
|
||||
//对冲交易手续费计算
|
||||
var tradeCommissionDict = _context.CommissionCalc.GetTradeCommission(newHedgeTrades);
|
||||
foreach (var newTrade in newHedgeTrades)
|
||||
{
|
||||
//手续费
|
||||
var commission = tradeCommissionDict.GetTradeCommission(newTrade.id);
|
||||
//标的价格
|
||||
_unPriceProvider.TryGetPrice(newTrade.UnderlyingCode, out var underlyingPrice);
|
||||
//计算
|
||||
ProcessNewTradeHegePnlV2(pnlResults, newTrade, commission, underlyingPrice);
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------
|
||||
// 场内期权对冲盈利
|
||||
//-----------------------------------------
|
||||
var tempTradeId = 0;
|
||||
foreach (var pnl in pnlResults)
|
||||
{
|
||||
var underlying = _unDataProvider.GetUnderlying(pnl.UnderlyingCode, out var contractSize);
|
||||
if (underlying == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pnl.TradeType == "场内期权")
|
||||
{
|
||||
tempTradeId--;
|
||||
ProcessExchangeOptionPnl(pnl, underlying, tempTradeId);
|
||||
}
|
||||
else
|
||||
{
|
||||
pnl.Delta = pnl.Notional;
|
||||
pnl.DeltaCash = pnl.Pv;
|
||||
pnl.DeltaInLots = pnl.Delta / contractSize;
|
||||
pnl.Lots = pnl.Notional / contractSize;
|
||||
}
|
||||
}
|
||||
|
||||
return pnlResults;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 昨日持仓对冲盈利
|
||||
/// </summary>
|
||||
private HedgePnl ProcessEodPositionHegePnl(EodTradePosition eodPosition, ExchangeListOption exchangeOption)
|
||||
{
|
||||
double lastPv = 0, pv = 0, dailyPnl = 0, realizedPnL = 0, cost = 0;
|
||||
|
||||
_unPriceProvider.TryGetPrice(eodPosition.UnderlyingCode, out var SettlePrice);
|
||||
|
||||
var notional = eodPosition.Amount;
|
||||
|
||||
if (Math.Abs(notional) > 0)
|
||||
{
|
||||
lastPv = eodPosition.Pv;
|
||||
pv = SettlePrice * eodPosition.Amount;
|
||||
dailyPnl = pv - lastPv;
|
||||
realizedPnL = eodPosition.ClosedPnL;
|
||||
cost = eodPosition.Cost;
|
||||
}
|
||||
else
|
||||
{
|
||||
notional = 0;
|
||||
}
|
||||
|
||||
var uniqueCode = GetHedgeUniqueCode(eodPosition.BookId, eodPosition.TradeType, eodPosition.PositionType, eodPosition.UnderlyingCode, eodPosition.ExchangeOptionCode);
|
||||
var lastTotalPnl = eodPosition.TotalPnL;
|
||||
|
||||
var eodPnl = new HedgePnl
|
||||
{
|
||||
BookId = eodPosition.BookId,
|
||||
ValueDate = _context.ValueDate,
|
||||
TradeType = eodPosition.TradeType,
|
||||
PositionType = eodPosition.PositionType,
|
||||
CallPut = TradeHelper.GetCallPut(exchangeOption?.OptionType),
|
||||
BuySell = eodPosition.BuySell,
|
||||
UnderlyingId = eodPosition.UnderlyingId,
|
||||
UnderlyingCode = eodPosition.UnderlyingCode,
|
||||
HedgeUniqueCode = uniqueCode,
|
||||
Notional = notional,
|
||||
LastPv = lastPv,
|
||||
Pv = pv,
|
||||
DailyPnL = dailyPnl,
|
||||
RealizedPnL = realizedPnL,
|
||||
TotalPnl = lastTotalPnl + dailyPnl,
|
||||
LastTotalPnl = lastTotalPnl,
|
||||
Cost = cost,
|
||||
Commission = eodPosition.Commission,
|
||||
Strike = exchangeOption?.Strike ?? 0,
|
||||
ExchangeOptionCode = eodPosition.ExchangeOptionCode,
|
||||
SettlePrice = SettlePrice,
|
||||
PositionPnl = eodPosition.PositionPnL,
|
||||
ExerciseDate = exchangeOption?.MaturityDate
|
||||
};
|
||||
if (Math.Abs(eodPnl.Strike) < 1e-7 && exchangeOption != null)
|
||||
{
|
||||
eodPnl.Strike = exchangeOption.Strike;
|
||||
eodPnl.BuySell = "long".Equals(eodPosition.PositionType) ? "买入" : "卖出";
|
||||
}
|
||||
return eodPnl;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当日交易对冲盈利V2
|
||||
/// </summary>
|
||||
private HedgePnl ProcessNewTradeHegePnlV2(List<HedgePnl> pnlResults, ExchangeTrade newTrade, double newTradeCommission, double underlyingPrice)
|
||||
{
|
||||
if (newTrade.Notional < 0)
|
||||
{
|
||||
throw new ServiceFaultException($"[场内交易数据,id:{newTrade.id},成交份额:{newTrade.Notional}]不允许成交份额小于0的场内交易数据存在!");
|
||||
}
|
||||
|
||||
//持仓类型 多头 空头分割
|
||||
var positionType = GetHedgeLongShort(newTrade.TradeType, newTrade.TradeSide);
|
||||
//对冲唯一编码(簿记账户ID_结构类型_持仓类型_合约代码)
|
||||
var uniqueCode = GetHedgeUniqueCode(newTrade.AssetBookId, newTrade.TradeType, positionType, newTrade.UnderlyingCode, newTrade.OptionCode);
|
||||
//获取是否存在对应uniqueCode的对冲信息
|
||||
var eodPnl = pnlResults.FirstOrDefault(t => t.HedgeUniqueCode == uniqueCode);
|
||||
|
||||
//新的pnl(如果eodPnl存在则使用eodPnl)
|
||||
var newPnl = eodPnl;
|
||||
if (eodPnl == null)
|
||||
{
|
||||
newPnl = new HedgePnl
|
||||
{
|
||||
BookId = newTrade.AssetBookId,
|
||||
ValueDate = _context.ValueDate,
|
||||
TradeType = newTrade.TradeType,
|
||||
PositionType = positionType,
|
||||
CallPut = TradeHelper.GetCallPut(newTrade.OptionType),
|
||||
BuySell = positionType == "long" ? "买入" : "卖出",
|
||||
UnderlyingId = newTrade.UnderlyingId,
|
||||
UnderlyingCode = newTrade.UnderlyingCode,
|
||||
HedgeUniqueCode = uniqueCode,
|
||||
Strike = newTrade.OptionStrike ?? 0,
|
||||
ExchangeOptionCode = newTrade.OptionCode,
|
||||
SettlePrice = underlyingPrice,
|
||||
ExerciseDate = newTrade.MaturityDate
|
||||
};
|
||||
|
||||
pnlResults.Add(newPnl);
|
||||
|
||||
if (newTrade.TradeType == "场内期权")
|
||||
{
|
||||
//场内期权的buysell不影响qdp计算
|
||||
newPnl.BuySell = newTrade.TradeSide;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newPnl.LastPv = eodPnl.LastPv; //昨市值
|
||||
}
|
||||
|
||||
//持仓符号和新交易符号
|
||||
var posSign = newPnl.Notional < 0 ? -1 : 1;
|
||||
var newSign = EodOperationBase.GetSign(newTrade.TradeSide);
|
||||
|
||||
double newNotional = newTrade.Notional, openNotional = 0d;
|
||||
|
||||
//平仓处理
|
||||
if (newSign != posSign)
|
||||
{
|
||||
var closeNotional = Math.Abs(newPnl.Notional);
|
||||
|
||||
if (newNotional > closeNotional)
|
||||
{
|
||||
//平仓超出部分需要变成开仓
|
||||
openNotional = newNotional - closeNotional;
|
||||
}
|
||||
else
|
||||
{
|
||||
closeNotional = newNotional;
|
||||
}
|
||||
|
||||
if (closeNotional > 0)
|
||||
{
|
||||
//开仓金额(带符号)
|
||||
var openAmount = closeNotional * posSign * newPnl.Cost / newPnl.Notional;
|
||||
|
||||
//平仓金额(带符号)
|
||||
var closeAmount = closeNotional * newSign * newTrade.TradeSinglePrice;
|
||||
|
||||
//平仓盈亏
|
||||
var closeProfit = -(openAmount + closeAmount);
|
||||
|
||||
//已实现盈亏
|
||||
newPnl.RealizedPnL += closeProfit;
|
||||
|
||||
//减去持仓成本
|
||||
newPnl.Cost -= openAmount;
|
||||
|
||||
//减去持仓份额
|
||||
newPnl.Notional -= closeNotional * posSign;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
openNotional = newNotional;
|
||||
}
|
||||
|
||||
//开仓处理
|
||||
if (openNotional > 0)
|
||||
{
|
||||
//转换为带符号的值
|
||||
openNotional *= newSign;
|
||||
|
||||
//累加开仓份额
|
||||
newPnl.Notional += openNotional;
|
||||
|
||||
//累加开仓成本
|
||||
newPnl.Cost += openNotional * newTrade.TradeSinglePrice;
|
||||
}
|
||||
|
||||
var newNotional_s = newNotional * newSign;
|
||||
var newCost = (newTrade.TradeSinglePrice * newNotional_s) + newTradeCommission;
|
||||
var newPv = newNotional_s * underlyingPrice;
|
||||
var newDailyPnl = newPv - newCost;
|
||||
|
||||
newPnl.Cost += newTradeCommission; //成本加上手续费
|
||||
newPnl.Commission += newTradeCommission; //累加总手续费
|
||||
newPnl.DailyPnL += newDailyPnl; //当日盈亏
|
||||
newPnl.TotalPnl += newDailyPnl; //总盈亏
|
||||
newPnl.Pv += newPv; //总市值
|
||||
newPnl.TdCost += newCost; //当日成本--主要用于下方的场内期权盈亏计算
|
||||
|
||||
return newPnl;
|
||||
}
|
||||
|
||||
#region----场内期权对冲盈利----
|
||||
|
||||
private void ProcessExchangeOptionPnl(HedgePnl pnl, underlying_manager underlying, int tempTradeId)
|
||||
{
|
||||
TradeValueResult optionResult = null;
|
||||
|
||||
//场内期权合约信息
|
||||
var exchangeOption = (_unDataProvider.GetExchange_List_Option(pnl.ExchangeOptionCode)?.Clone())
|
||||
?? throw new HedgePnlCalcException($"场内期权合约[{pnl.ExchangeOptionCode}]在场内期权合约信息表中不存在!");
|
||||
|
||||
if (exchangeOption.MaturityDate < _context.ValueDate)
|
||||
{
|
||||
throw new HedgePnlCalcException($"场内期权合约'{pnl.ExchangeOptionCode}'已过期,合约到期日:{exchangeOption.MaturityDate:yyyy-MM-dd},估值日期:{_context.ValueDate:yyyy-MM-dd}");
|
||||
}
|
||||
|
||||
if (exchangeOption.UnderlyingCode.StartsWith("IO"))
|
||||
{
|
||||
exchangeOption.UnderlyingCode = "000300.SH";
|
||||
}
|
||||
|
||||
//pnl callput
|
||||
pnl.CallPut = TradeHelper.GetCallPut(exchangeOption.OptionType);
|
||||
|
||||
//场内期权 合约乘数
|
||||
var contractSize = underlying.ContractSize;
|
||||
if (exchangeOption.ContractSize > 1e-6)
|
||||
{
|
||||
contractSize = exchangeOption.ContractSize;
|
||||
}
|
||||
|
||||
//pnl持仓手数
|
||||
pnl.Lots = pnl.Notional / contractSize;
|
||||
|
||||
//更新场内期权市场价格
|
||||
pnl.ExOptionPrice = _exchangeOptionPriceProvider.TryGetPrice(pnl.ExchangeOptionCode, out var price) ? price : null;
|
||||
|
||||
//是否有持仓
|
||||
var hasPosition = Math.Abs(pnl.Notional) > 1e-10;
|
||||
|
||||
if (hasPosition)
|
||||
{
|
||||
//使用曲面波动率或者隐含波动率计算期权风险
|
||||
optionResult = InnerCalcExchangeOptionRisks(tempTradeId, pnl, underlying, exchangeOption);
|
||||
}
|
||||
|
||||
if (optionResult != null && optionResult.Succeeded)
|
||||
{
|
||||
pnl.Vol = optionResult.Vol;
|
||||
pnl.Delta = optionResult.Delta;
|
||||
pnl.DeltaT1 = optionResult.DeltaT1;
|
||||
pnl.SA_Delta = optionResult.SA_Delta;
|
||||
pnl.Gamma = optionResult.Gamma;
|
||||
pnl.Vega = optionResult.Vega;
|
||||
pnl.DeltaCash = optionResult.DeltaCash;
|
||||
pnl.GammaCash = optionResult.GammaCash;
|
||||
pnl.DeltaInLots = optionResult.Delta / contractSize;
|
||||
pnl.GammaInLots = optionResult.Gamma / contractSize;
|
||||
pnl.Theta = optionResult.Theta;
|
||||
pnl.Rho = optionResult.Rho * 100;
|
||||
pnl.DdeltaDt = optionResult.DDeltaDt;
|
||||
pnl.DdeltaDvol = optionResult.DDeltaDVol;
|
||||
pnl.DvegaDt = optionResult.DVegaDt;
|
||||
pnl.DvegaDvol = optionResult.DVegaDVol;
|
||||
pnl.Pv = optionResult.Pv;
|
||||
pnl.TimeValue = optionResult.TimeValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
pnl.Pv = 0;
|
||||
}
|
||||
|
||||
var flag = _context.ExchangeOptionPriceUseFlag;
|
||||
|
||||
if (PS.Config.Is光大光子)
|
||||
{
|
||||
flag = flag == ExchangeOptionPriceUseFlag.TrialCalclMode
|
||||
? ExchangeOptionPriceUseFlag.SetExOptionPrice : ExchangeOptionPriceUseFlag.CalcPv;
|
||||
}
|
||||
|
||||
if (flag == ExchangeOptionPriceUseFlag.CalcPv && hasPosition)
|
||||
{
|
||||
pnl.Pv = (pnl.ExOptionPrice * pnl.Notional) ?? 0;
|
||||
var intrinsicValue = ConsGlobal.CallPut.IsCall(pnl.CallPut) ? pnl.SettlePrice - pnl.Strike : pnl.Strike - pnl.SettlePrice;
|
||||
pnl.TimeValue = pnl.Pv - Math.Max(0, intrinsicValue) * pnl.Notional;
|
||||
}
|
||||
|
||||
pnl.DailyPnL = pnl.Pv - pnl.LastPv - pnl.TdCost;
|
||||
pnl.TotalPnl = pnl.LastTotalPnl + pnl.DailyPnL;
|
||||
}
|
||||
|
||||
//场内期权估值计算,pnl在此方法中会对CallPut、Lots、ExOptionPrice赋值处理
|
||||
private TradeValueResult InnerCalcExchangeOptionRisks(int tempTradeId, HedgePnl pnl, underlying_manager underlying, ExchangeListOption exchangeOption)
|
||||
{
|
||||
var tempTrade = new trade
|
||||
{
|
||||
TradeType = pnl.TradeType,
|
||||
UnderlyingCode = pnl.UnderlyingCode,
|
||||
UnderlyingId = pnl.UnderlyingId,
|
||||
TradeDate = _context.ValueDate,
|
||||
BuySell = pnl.BuySell,
|
||||
StartDate = _context.ValueDate,
|
||||
ExerciseDate = exchangeOption.MaturityDate,
|
||||
MaturityDate = underlying.MaturityDate,
|
||||
TradePrice = Math.Abs(pnl.Cost),
|
||||
TradeStatus = "确认成交",
|
||||
ExerciseMode = exchangeOption.ExerciseMode,
|
||||
OptionType = exchangeOption.OptionType,
|
||||
Strike = pnl.Strike,
|
||||
Notional = pnl.Notional,
|
||||
UnderlyingInstrumentType = underlying.UnderlyingInstrumentType,
|
||||
ExchangeOptionCode = pnl.ExchangeOptionCode,
|
||||
AssetId = pnl.BookId,
|
||||
id = tempTradeId,
|
||||
UnderlyingAssetClass = underlying.UnderlyingType,
|
||||
|
||||
//用于反算隐含波动率
|
||||
StructureType = "场内期权",
|
||||
TradeSinglePrice = pnl.ExOptionPrice
|
||||
};
|
||||
|
||||
if (PS.Config.ErpElement.ExchangeOptionVolType == Configuration.Enums.ExchangeOptionVolType.ImpliedVol)
|
||||
{
|
||||
_unSettlePriceProvider.TryGetPrice(pnl.UnderlyingCode, out var price);
|
||||
tempTrade.Vol = VolatilityHelper.GetImpliedVol(_context.ValueDate, tempTrade, null, price.Normalize(), _context.IsEodCalc);
|
||||
}
|
||||
|
||||
var optionCalcContext = _context.CreateOptionCalculateContext();
|
||||
|
||||
var optionResult = TradeRiskCalcUtil.CalcTradeRisk(tempTrade, optionCalcContext, out _);
|
||||
|
||||
if (optionResult != null)
|
||||
{
|
||||
if (optionResult.FailReason == TradeValueFailReason.missingVol)
|
||||
{
|
||||
if (PS.Config.Is光大光子 && _context.CalcScenario == Enums.CalcScenarioEnum.EodSettlement && _context.VolType == "对冲")
|
||||
{
|
||||
var errmsg = $"[光子对冲收盘 {_context.ValueDate:yyyy-MM-dd}]场内期权'{pnl.ExchangeOptionCode}' 找不到波动率!";
|
||||
throw new HedgePnlCalcException(errmsg);
|
||||
}
|
||||
}
|
||||
|
||||
if (!optionResult.Succeeded)
|
||||
{
|
||||
var errmsg = $"{_context.ValueDate:yyyy-MM-dd},场内期权'{pnl.ExchangeOptionCode}' 计算失败,{optionResult.ErrorMessage},fail reason:{optionResult.FailReason}";
|
||||
|
||||
if (_context.CalcScenario == Enums.CalcScenarioEnum.EodSettlement)
|
||||
{
|
||||
LogFactory.GetLogger(nameof(HedgePnlCalc)).Error(errmsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
//其他场景可能会产生大量重复日志,为了避免这种情况使用debug方式输出
|
||||
LogFactory.GetLogger(nameof(HedgePnlCalc)).Debug(errmsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var errmsg = $"{_context.ValueDate:yyyy-MM-dd},场内期权'{pnl.ExchangeOptionCode}' 获取不到计算结果";
|
||||
|
||||
if (_context.CalcScenario == Enums.CalcScenarioEnum.EodSettlement)
|
||||
{
|
||||
LogFactory.GetLogger(nameof(HedgePnlCalc)).Error(errmsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogFactory.GetLogger(nameof(HedgePnlCalc)).Debug(errmsg);
|
||||
}
|
||||
}
|
||||
|
||||
return optionResult;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 根据对冲账号 簿记账户I 结构类型 买卖方向 标的代码 场内期权代码 编制对冲唯一编码
|
||||
/// </summary>
|
||||
public static string GetHedgeUniqueCode(int BookId, string TradeType, string LongShort, string UnderlyingCode, string ExchangeOptionCode = null)
|
||||
{
|
||||
return $"{BookId}_{TradeType}_{LongShort}_{("场内期权".Equals(TradeType) ? ExchangeOptionCode : UnderlyingCode)}".ToUpperInvariant();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对冲交易 根据结构类型 买卖方向 看涨看跌 获取持仓long short
|
||||
/// </summary>
|
||||
public static string GetHedgeLongShort(string TradeType, string BuySell)
|
||||
{
|
||||
switch (TradeType)
|
||||
{
|
||||
case "商品期货":
|
||||
case "商品现货":
|
||||
case "场内期权":
|
||||
return BuySell.Contains("多头") ? "long" : "short";
|
||||
case "股票":
|
||||
default: return "long";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对冲交易计算异常
|
||||
/// </summary>
|
||||
public class HedgePnlCalcException : Exception
|
||||
{
|
||||
public HedgePnlCalcException(string message) : base(message)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public HedgePnlCalcException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
using YLErp.Abstract;
|
||||
using YLErp.Abstract.DataProviders;
|
||||
using YLErp.BLL;
|
||||
using YLErp.Enums;
|
||||
using YLErp.Modules.CalculationModule.Abstract;
|
||||
using YLErp.Modules.DataProviderModule;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 持仓风险对冲计算上下文
|
||||
/// </summary>
|
||||
public class HedgePnlCalcContext : IHedgePnlCalcContext
|
||||
{
|
||||
#region----属性定义----
|
||||
|
||||
/// <summary>
|
||||
/// 估值日
|
||||
/// </summary>
|
||||
public DateTime ValueDate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 计算场景
|
||||
/// </summary>
|
||||
public CalcScenarioEnum CalcScenario { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 波动率类型
|
||||
/// </summary>
|
||||
public string VolType { get; }
|
||||
|
||||
public bool IsEodCalc { get; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public IPriceProvider UnderlyingPriceProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public IPriceProvider UnderlyingSettlePriceProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 场内标的价格提供者
|
||||
/// </summary>
|
||||
public IPriceProvider ExchangeOptionPriceProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public IUnderlyingDataProvider UnderlyingDataProvider { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 对冲交易佣金计算接口
|
||||
/// </summary>
|
||||
public IExchangeTradeCommissionCalc CommissionCalc { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误处理接口
|
||||
/// </summary>
|
||||
public IErrorHandler ErrorHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public OptUserInfo OptUser { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 场内期权价格使用选项(默认SetExOptionPrice)
|
||||
/// 注意:在计算类中光子将忽略此项并固定为CalcPv
|
||||
/// </summary>
|
||||
public ExchangeOptionPriceUseFlag ExchangeOptionPriceUseFlag { get; set; } = ExchangeOptionPriceUseFlag.SetExOptionPrice;
|
||||
|
||||
#endregion
|
||||
|
||||
#region----构造函数----
|
||||
|
||||
public HedgePnlCalcContext(CalcScenarioEnum calcScenario, DateTime valueDate, string volType, bool isEodCalc
|
||||
, IPriceProvider underlyingPriceProvider, IPriceProvider underlyingSettlePriceProvider,
|
||||
IPriceProvider exchangeOptionPriceProvider, OptUserInfo optUser
|
||||
, IExchangeTradeCommissionCalc commissionCalc = null
|
||||
, IUnderlyingDataProvider underlyingDataProvider = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(volType))
|
||||
{
|
||||
throw new ArgumentException("volType不能为空", nameof(volType));
|
||||
}
|
||||
|
||||
VolType = volType;
|
||||
IsEodCalc = isEodCalc;
|
||||
ValueDate = valueDate;
|
||||
CalcScenario = calcScenario;
|
||||
OptUser = optUser ?? throw new ArgumentNullException(nameof(optUser));
|
||||
UnderlyingPriceProvider = underlyingPriceProvider ?? throw new ArgumentNullException(nameof(underlyingPriceProvider));
|
||||
UnderlyingSettlePriceProvider = underlyingSettlePriceProvider ?? throw new ArgumentNullException(nameof(underlyingPriceProvider));
|
||||
ExchangeOptionPriceProvider = exchangeOptionPriceProvider ?? throw new ArgumentNullException(nameof(exchangeOptionPriceProvider));
|
||||
|
||||
CommissionCalc = commissionCalc ?? new ExchangeTradeCommissionCalc();
|
||||
UnderlyingDataProvider = underlyingDataProvider ?? new UnderlyingDataProvider();
|
||||
}
|
||||
|
||||
public HedgePnlCalcContext(IOtcTradeValueCalcContext optionCalcContext,
|
||||
IExchangeTradeCommissionCalc tradeCommissionCalc, IPriceProvider exchangeOptionPriceProvider)
|
||||
{
|
||||
if (optionCalcContext is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(optionCalcContext));
|
||||
}
|
||||
|
||||
CommissionCalc = tradeCommissionCalc ?? throw new ArgumentNullException(nameof(tradeCommissionCalc));
|
||||
ExchangeOptionPriceProvider = exchangeOptionPriceProvider ?? throw new ArgumentNullException(nameof(exchangeOptionPriceProvider));
|
||||
|
||||
var dataProvider = CalcCheckHelper.CheckOptionCalcDataProvider(optionCalcContext.DataProvider);
|
||||
UnderlyingDataProvider = dataProvider.UnderlyingDataProvider;
|
||||
UnderlyingPriceProvider = dataProvider.UnderlyingPriceProvider;
|
||||
UnderlyingSettlePriceProvider = dataProvider.UnderlyingPriceProvider;
|
||||
|
||||
VolType = optionCalcContext.VolType;
|
||||
ValueDate = optionCalcContext.ValueDate;
|
||||
CalcScenario = optionCalcContext.CalcScenario;
|
||||
ErrorHandler = optionCalcContext.ErrorHandler;
|
||||
|
||||
OptUser = OptUserInfo.SystemUser;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 创建期权计算上下文对象
|
||||
/// </summary>
|
||||
public virtual IOtcTradeValueCalcContext CreateOptionCalculateContext()
|
||||
{
|
||||
var sysRiskFreeRate = valuedateBLL.RiskFreeRate * 0.01;
|
||||
var dataProvider = new InnerOptionCalcDataProvider(this);
|
||||
return new OptionValueCalcContext(VolType, IsEodCalc, ValueDate, sysRiskFreeRate, dataProvider)
|
||||
{
|
||||
AddingVolRate = 0,
|
||||
ErrorHandler = ErrorHandler,
|
||||
CalcScenario = CalcScenario
|
||||
};
|
||||
}
|
||||
|
||||
#region----InnerOptionCalcDataProvider----
|
||||
|
||||
class InnerOptionCalcDataProvider : IOptionCalcDataProvider
|
||||
{
|
||||
public InnerOptionCalcDataProvider(HedgePnlCalcContext context)
|
||||
{
|
||||
UnderlyingDataProvider = context.UnderlyingDataProvider;
|
||||
UnderlyingPriceProvider = context.UnderlyingPriceProvider;
|
||||
UnderlyingSettlePriceProvider = context.UnderlyingSettlePriceProvider;
|
||||
VolatilityDataProvider = new VolatilityDataProvider(context.ValueDate);
|
||||
TradeExtendDataProvider = new TradeExtendDataProvider(context.OptUser);
|
||||
}
|
||||
|
||||
public IPriceProvider UnderlyingPriceProvider { get; }
|
||||
public IPriceProvider UnderlyingSettlePriceProvider { get; }
|
||||
|
||||
public IUnderlyingDataProvider UnderlyingDataProvider { get; }
|
||||
|
||||
public ITradeExtendDataProvider TradeExtendDataProvider { get; }
|
||||
|
||||
public IVolatilityDataProvider VolatilityDataProvider { get; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public HedgePnlCalc GetHedgePnlCalc()
|
||||
{
|
||||
return new HedgePnlCalc(this);
|
||||
}
|
||||
public MaturityOptionHedgePnlCalc GetOptionHedgePnlCalc()
|
||||
{
|
||||
return new MaturityOptionHedgePnlCalc(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
using Qdp.ComputeService.Data.CommonModels.ValuationParams.Equity;
|
||||
using Qdp.Foundation.Implementations;
|
||||
using YLErp.Abstract;
|
||||
using YLErp.BLL.Calculation;
|
||||
using YLErp.QdpModule;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 隐含波动率计算服务
|
||||
/// </summary>
|
||||
public class ImpliedVolCalcService
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据权利金计算隐含波动率
|
||||
/// </summary>
|
||||
/// <param name="premium">期权单价</param>
|
||||
/// <param name="valueDate">计算日期</param>
|
||||
public static double ImpliedVolFromPremium(
|
||||
double premium,
|
||||
DateTime valueDate,
|
||||
string underlyingTicker,
|
||||
string underlyingInstrumentType,
|
||||
double strike,
|
||||
DateTime startDate,
|
||||
DateTime endDate,
|
||||
string optionType,
|
||||
string exerciseType,
|
||||
double spotPrice,
|
||||
double notional,
|
||||
double riskFreeRate,
|
||||
string tradeType,
|
||||
DateTime exerciseDate,
|
||||
double participationRate,
|
||||
double principalRate,
|
||||
bool isAnnualized,
|
||||
double annualizeFactor,
|
||||
double dividendRate = 0.0,
|
||||
bool isMoneynessOption = false,
|
||||
double initialSpotPrice = 0.0,
|
||||
Dictionary<Date, double> dividends = null,
|
||||
bool hasNightMarket = false,
|
||||
bool preciseTimeMode = false,
|
||||
double ttmDays = double.NaN, IVolatility volatility = null)
|
||||
{
|
||||
var optionTradeParam = new VanillaOptionTradeParam
|
||||
{
|
||||
annualizedFactor = annualizeFactor,
|
||||
buysell = tradeType,
|
||||
preciseTimeMode = preciseTimeMode,
|
||||
dividendRate = dividendRate,
|
||||
dividends = dividends,
|
||||
endDate = endDate,
|
||||
exerciseDate = exerciseDate,
|
||||
exerciseType = exerciseType,
|
||||
hasNightMarket = hasNightMarket,
|
||||
initialSpotPrice = initialSpotPrice,
|
||||
isAnnualized = isAnnualized,
|
||||
isMoneynessOption = isMoneynessOption,
|
||||
notional = notional,
|
||||
optionType = QdpConverter.ConvertOptionType(optionType),
|
||||
participationRate = participationRate,
|
||||
principalRate = principalRate,
|
||||
riskFreeRate = riskFreeRate,
|
||||
settlementDate = exerciseDate,
|
||||
startDate = startDate,
|
||||
strike = strike,
|
||||
timeToMaturityDays = ttmDays,
|
||||
tradeDate = startDate,
|
||||
underlyingInstrumentType = underlyingInstrumentType,
|
||||
underlyingTickers = new[] { underlyingTicker },
|
||||
tradeId = null,
|
||||
volSurfaceNames = null,
|
||||
isForwardTrade = false
|
||||
};
|
||||
|
||||
return ImpliedVolFromPremium(premium, valueDate, optionTradeParam, spotPrice, volatility);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据权利金计算隐含波动率
|
||||
/// </summary>
|
||||
/// <param name="premium">期权单价</param>
|
||||
/// <param name="valueDate">计算日期</param>
|
||||
/// <param name="optionTradeParam">期权要素</param>
|
||||
/// <param name="spotPrice">标的现价</param>
|
||||
public static double ImpliedVolFromPremium(double premium, DateTime valueDate
|
||||
, VanillaOptionTradeParam optionTradeParam, double spotPrice, IVolatility volatility = null)
|
||||
{
|
||||
//在计算ImpliedVol时,提前处理strike,然后都当做非MoneynessOption处理
|
||||
if (optionTradeParam.isMoneynessOption)
|
||||
{
|
||||
optionTradeParam.isMoneynessOption = false;
|
||||
optionTradeParam.strike *= optionTradeParam.initialSpotPrice;
|
||||
}
|
||||
|
||||
if (optionTradeParam.volSurfaceNames == null || !optionTradeParam.volSurfaceNames.Any())
|
||||
{
|
||||
optionTradeParam.volSurfaceNames = new[] { Guid.NewGuid().ToString() };
|
||||
}
|
||||
|
||||
var volSurfaceName = optionTradeParam.volSurfaceNames[0];
|
||||
|
||||
if (optionTradeParam.underlyingTickers == null || !optionTradeParam.underlyingTickers.Any())
|
||||
{
|
||||
throw new Exception("缺少标的代码");
|
||||
}
|
||||
|
||||
var underlyingTicker = optionTradeParam.underlyingTickers[0];
|
||||
|
||||
optionTradeParam.buysell = "买入";
|
||||
var optionTrade = QdpTradeBuilder.GetVanillaOptionTrade(optionTradeParam);
|
||||
|
||||
using (var marketProxy = new MarketProxy(valueDate, optionTradeParam.riskFreeRate))
|
||||
{
|
||||
//设置标的价格
|
||||
marketProxy.SetStockPrice(underlyingTicker, spotPrice);
|
||||
marketProxy.SetVolSurface(volSurfaceName, volatility ?? QdpVolHelper.GetDefaultVolatility(0.3));
|
||||
|
||||
OptionValuationParameters parameters;
|
||||
|
||||
if (optionTradeParam.underlyingInstrumentType == ConsGlobal.InstrumentType.Stock)
|
||||
{
|
||||
//设置DividendCurve
|
||||
var dividendCurveName = Guid.NewGuid().ToString();
|
||||
var dividendCurve = CalculatorHelper.CreateConstantRiskFreeCurve(dividendCurveName, optionTradeParam.dividendRate);
|
||||
marketProxy.SetYieldCurve(dividendCurveName, dividendCurve);
|
||||
parameters = new OptionValuationParameters(marketProxy.DiscountCurveName, dividendCurveName, volSurfaceName, underlyingTicker);
|
||||
}
|
||||
else
|
||||
{
|
||||
parameters = new OptionValuationParameters(marketProxy.DiscountCurveName, MarketProxy.ConstantZeroCurve, volSurfaceName, underlyingTicker);
|
||||
}
|
||||
|
||||
return optionTrade.ImpliedVolFromPremium(premium, marketProxy.QdpMarket, parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using YLErp.Abstract.DataProviders;
|
||||
using YLErp.BLL.Eod;
|
||||
using YLErp.BLL.Hedge;
|
||||
using YLErp.Modules.CalculationModule.Abstract;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 场内期权到期pnl计算
|
||||
/// </summary>
|
||||
public class MaturityOptionHedgePnlCalc
|
||||
{
|
||||
readonly IHedgePnlCalcContext _context;
|
||||
readonly IUnderlyingDataProvider _unDataProvider;
|
||||
readonly IPriceProvider _unPriceProvider;
|
||||
public MaturityOptionHedgePnlCalc(IHedgePnlCalcContext context)
|
||||
{
|
||||
_context = CalcCheckHelper.CheckHedgePnlCalcContext(context);
|
||||
_unDataProvider = context.UnderlyingDataProvider;
|
||||
_unPriceProvider = context.UnderlyingPriceProvider;
|
||||
}
|
||||
/// <summary>
|
||||
/// 到期hedgePnl计算
|
||||
/// </summary>
|
||||
/// <param name="hedgePnl"></param>
|
||||
/// <returns></returns>
|
||||
public HedgePnl Calculate(HedgePnl hedgePnl, OptUserInfo UserInfo)
|
||||
{
|
||||
if (hedgePnl.TradeType == "场内期权" && hedgePnl.Notional != 0)
|
||||
{
|
||||
var exchangeOption = _unDataProvider.GetExchange_List_Option(hedgePnl.ExchangeOptionCode)?.Clone();
|
||||
if (exchangeOption == null)
|
||||
{
|
||||
throw new Exception(String.Format("未找到合约代码为【{0}】的场内期权信息", hedgePnl.ExchangeOptionCode));
|
||||
}
|
||||
if (exchangeOption != null && exchangeOption.MaturityDate == _context.ValueDate)
|
||||
{
|
||||
var tempUm = _unDataProvider.GetUnderlying(hedgePnl.UnderlyingCode);
|
||||
var cost = (double)hedgePnl.Cost;
|
||||
var exchangeTrade = new ExchangeTrade()
|
||||
{
|
||||
TradeType = hedgePnl.TradeType,
|
||||
UnderlyingCode = hedgePnl.UnderlyingCode,
|
||||
UnderlyingId = hedgePnl.UnderlyingId,
|
||||
OptionCode = hedgePnl.ExchangeOptionCode,
|
||||
AssetBookId = hedgePnl.BookId,
|
||||
TradeDate = _context.ValueDate,
|
||||
TradeLots = Math.Abs(hedgePnl.Lots),
|
||||
Notional = Math.Abs(hedgePnl.Notional),
|
||||
TradeAmount = Math.Abs(hedgePnl.Notional) / tempUm.CountRatio,
|
||||
TradeSide = hedgePnl.PositionType == "long" ? "多头平仓" : "空头平仓",
|
||||
TradeSinglePrice = 0,
|
||||
InstrumentType = tempUm.UnderlyingInstrumentType,
|
||||
CreateTime = DateTime.Now,
|
||||
MaturityDate = exchangeOption.MaturityDate,
|
||||
OptionStrike = exchangeOption.Strike,
|
||||
OptionType = exchangeOption.OptionType,
|
||||
ExerciseMode = exchangeOption.ExerciseMode.TrimToNull() ?? "European",
|
||||
IsValid = true,
|
||||
OptDate = DateTime.Now,
|
||||
OptId = UserInfo.UserId,
|
||||
OptName = UserInfo.UserName,
|
||||
TradeSource = "系统交易",
|
||||
TradeNumber = DateTime.Now.ToString("yyyyMMddHHmmssfff")
|
||||
};
|
||||
var realPnl = cost * EodOperationBase.GetSign(exchangeTrade.TradeSide);
|
||||
hedgePnl.RealizedPnL += realPnl;
|
||||
hedgePnl.DailyPnL = -hedgePnl.LastPv - hedgePnl.TdCost;//重新计算
|
||||
hedgePnl.TotalPnl = hedgePnl.LastTotalPnl + hedgePnl.DailyPnL;
|
||||
hedgePnl.Cost = 0;
|
||||
hedgePnl.Notional = 0;
|
||||
hedgePnl.Pv = 0;
|
||||
hedgePnl.Delta = 0;
|
||||
hedgePnl.DeltaCash = 0;
|
||||
hedgePnl.Gamma = 0;
|
||||
hedgePnl.GammaCash = 0;
|
||||
hedgePnl.Vega = 0;
|
||||
hedgePnl.Theta = 0;
|
||||
hedgePnl.Rho = 0;
|
||||
hedgePnl.Vol = 0;
|
||||
using (var db = DbContextFactory.GetYLDbContext())
|
||||
{
|
||||
db.ExchangeTrade.Add(exchangeTrade);
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
return hedgePnl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using Qdp.Pricing.Base.Implementations;
|
||||
using YLErp.Abstract.DataProviders;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class CalculateRisksForTradesReq
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public DateTime valueDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public IEnumerable<trade> tradeList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public IPriceProvider priceProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public PricingRequest pricingRequest { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public Dictionary<int, double> addVolRateDic { get; set; }
|
||||
|
||||
public bool isMarginCalc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否收盘处理
|
||||
/// </summary>
|
||||
public bool isEodCalc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 波动率类型,默认:交易
|
||||
/// </summary>
|
||||
public string volType { get; set; } = "交易";
|
||||
|
||||
public SettlementTypeEnum settlementType { get; set; } = SettlementTypeEnum.ClosePrice;
|
||||
|
||||
/// <summary>
|
||||
/// 用户自定义的交易波动率
|
||||
/// </summary>
|
||||
public Dictionary<int, double> overrideVolsForTrade { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 使用交易波动率(默认false)
|
||||
/// </summary>
|
||||
public bool isUseTradeVol { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 精确时间模式(默认true)
|
||||
/// </summary>
|
||||
public bool PreciseTimeMode { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 默认true
|
||||
/// </summary>
|
||||
public bool isAddVolPercent { get; set; } = true;
|
||||
|
||||
public Enums.CalcScenarioEnum calcScenario { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否使用手动维护的风险值
|
||||
/// </summary>
|
||||
public bool canUseManual { get; set; } = false;
|
||||
|
||||
public CalculateRisksForTradesReq Clone(IEnumerable<trade> tradeList, IPriceProvider priceProvider = null)
|
||||
{
|
||||
var clone = (CalculateRisksForTradesReq)MemberwiseClone();
|
||||
clone.tradeList = tradeList;
|
||||
if (priceProvider != null)
|
||||
{
|
||||
clone.priceProvider = priceProvider;
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace YLErp.BLL.Hedge
|
||||
{
|
||||
/// <summary>
|
||||
/// 对冲交易盈亏
|
||||
/// </summary>
|
||||
public class HedgePnl
|
||||
{
|
||||
[DisplayName("估值日")]
|
||||
public DateTime ValueDate { get; set; }
|
||||
|
||||
[DisplayName("簿记账户信息")]
|
||||
public int BookId { get; set; }
|
||||
|
||||
[DisplayName("结构类型")]
|
||||
public string TradeType { get; set; }
|
||||
|
||||
[DisplayName("持仓类型")]
|
||||
public string PositionType { get; set; }
|
||||
|
||||
[DisplayName("看涨看跌")]
|
||||
public string CallPut { get; set; }
|
||||
|
||||
[DisplayName("买入卖出")]
|
||||
public string BuySell { get; set; }
|
||||
|
||||
[DisplayName("标的ID")]
|
||||
public int UnderlyingId { get; set; }
|
||||
|
||||
[DisplayName("标的代码")]
|
||||
public string UnderlyingCode { get; set; }
|
||||
|
||||
[DisplayName("对冲交易唯一编码")]
|
||||
public string HedgeUniqueCode { get; set; }
|
||||
|
||||
[DisplayName("份额")]
|
||||
public double Notional { get; set; }
|
||||
|
||||
[DisplayName("是否有新增交易")]
|
||||
public bool HasNewTrade { get; set; }
|
||||
|
||||
[DisplayName("昨日价值")]
|
||||
public double LastPv { get; set; }
|
||||
|
||||
[DisplayName("当日价值")]
|
||||
public double Pv { get; set; }
|
||||
|
||||
[DisplayName("时间价值")]
|
||||
public double TimeValue { get; set; }
|
||||
|
||||
[DisplayName("当日盈亏")]
|
||||
public double DailyPnL { get; set; }
|
||||
|
||||
[DisplayName("已实现盈亏")]
|
||||
public double RealizedPnL { get; set; }
|
||||
|
||||
[DisplayName("总盈亏")]
|
||||
public double TotalPnl { get; set; }
|
||||
|
||||
[DisplayName("昨日总盈亏")]
|
||||
public double LastTotalPnl { get; set; }
|
||||
|
||||
[DisplayName("成本")]
|
||||
public double Cost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当日成本
|
||||
/// </summary>
|
||||
public double TdCost { get; set; }
|
||||
|
||||
[DisplayName("手续费")]
|
||||
public double Commission { get; set; }
|
||||
|
||||
[DisplayName("场内期权合约号")]
|
||||
public string ExchangeOptionCode { get; set; }
|
||||
|
||||
[DisplayName("行权价")]
|
||||
public double Strike { get; set; }
|
||||
|
||||
[DisplayName("结算价格")]
|
||||
public double SettlePrice { get; set; }
|
||||
|
||||
[DisplayName("信用风险敞口")]
|
||||
public int CreditExposure { get; set; }
|
||||
/// <summary>
|
||||
/// 场内期权价格(20200927新增)
|
||||
/// </summary>
|
||||
public double? ExOptionPrice { get; set; }
|
||||
|
||||
#region 风险参数 场内期权使用
|
||||
|
||||
[DisplayName("Delta")]
|
||||
public double Delta { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// T+1日Delta
|
||||
/// </summary>
|
||||
public double? DeltaT1 { get; set; }
|
||||
|
||||
[DisplayName("SA_Delta")]
|
||||
public double SA_Delta { get; set; }
|
||||
|
||||
[DisplayName("Gamma")]
|
||||
public double Gamma { get; set; }
|
||||
|
||||
[DisplayName("Vega")]
|
||||
public double Vega { get; set; }
|
||||
|
||||
[DisplayName("Delta")]
|
||||
public double DeltaCash { get; set; }
|
||||
|
||||
[DisplayName("Delta")]
|
||||
public double GammaCash { get; set; }
|
||||
|
||||
[DisplayName("可对冲Delta")]
|
||||
public double DeltaInLots { get; set; }
|
||||
|
||||
[DisplayName("可对冲Gamma手数")]
|
||||
public double GammaInLots { get; set; }
|
||||
|
||||
[DisplayName("Theta")]
|
||||
public double Theta { get; set; }
|
||||
|
||||
[DisplayName("Rho")]
|
||||
public double Rho { get; set; }
|
||||
|
||||
[DisplayName("DdeltaDt")]
|
||||
public double DdeltaDt { get; set; }
|
||||
|
||||
[DisplayName("DdeltaDvol")]
|
||||
public double DdeltaDvol { get; set; }
|
||||
|
||||
[DisplayName("DvegaDt")]
|
||||
public double DvegaDt { get; set; }
|
||||
|
||||
[DisplayName("DvegaDvol")]
|
||||
public double DvegaDvol { get; set; }
|
||||
|
||||
[DisplayName("Vol")]
|
||||
public double Vol { get; set; }
|
||||
|
||||
[DisplayName("手数")]
|
||||
public double Lots { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 行权日(20190328新增)
|
||||
/// </summary>
|
||||
public DateTime? ExerciseDate { get; set; }
|
||||
[DisplayName("持仓盈亏")]
|
||||
public double PositionPnl { get; internal set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(HedgeUniqueCode) ?
|
||||
$"{TradeType}-{UnderlyingCode}-{Notional}-HasNewTrade:{HasNewTrade}" : HedgeUniqueCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
public class TradeRiskResult
|
||||
{
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
public List<TradeRiskResultRecord> Results { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public class TradeRiskResultRecord
|
||||
{
|
||||
public trade Trade { get; set; }
|
||||
|
||||
public TradeValueResult ValueResult { get; set; }
|
||||
|
||||
public underlying_manager[] Underlyings { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,545 @@
|
||||
using Qdp.Pricing.Base.Interfaces;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 计算成功以后的结果
|
||||
/// </summary>
|
||||
public class TradeValueResult
|
||||
{
|
||||
public TradeValueResult()
|
||||
{
|
||||
Succeeded = true;
|
||||
}
|
||||
|
||||
public TradeValueResult(bool succeeded)
|
||||
{
|
||||
Succeeded = succeeded;
|
||||
}
|
||||
|
||||
public TradeValueResult(IPricingResult result)
|
||||
{
|
||||
if (result != null)
|
||||
{
|
||||
Pv = result.Pv;
|
||||
Delta = result.Delta;
|
||||
Gamma = result.Gamma;
|
||||
Vega = result.Vega;
|
||||
CalendarDayTheta = result.Theta;
|
||||
TradingDayTheta = result.ThetaPnL;
|
||||
Rho = result.Rho;
|
||||
DeltaCash = result.DeltaCash;
|
||||
GammaCash = result.GammaCash;
|
||||
VegaCash = result.VegaCash;
|
||||
Vol = result.PricingVol;
|
||||
|
||||
DDeltaDVol = result.DDeltaDvol;
|
||||
DDeltaDt = result.DDeltaDt;
|
||||
DVegaDVol = result.DVegaDvol;
|
||||
DVegaDt = result.DVegaDt;
|
||||
StoppingTime = result.StoppingTime;
|
||||
SA_Delta = result.SA_Delta;
|
||||
TimeValue = result.TimeValue;
|
||||
|
||||
Succeeded = result.Succeeded;
|
||||
ErrorMessage = result.ErrorMessage;
|
||||
|
||||
PricingT = result.PricingT;
|
||||
}
|
||||
else
|
||||
{
|
||||
Succeeded = false;
|
||||
ErrorMessage = "空的IPricingResult对象传入";
|
||||
}
|
||||
}
|
||||
|
||||
public string UnderlyingCode { get; set; }
|
||||
|
||||
public double Pv { get => pv; set => pv = value.Normalize(); }
|
||||
|
||||
public double NPv { get => npv; set => npv = value.Normalize(); }
|
||||
|
||||
/// <summary>
|
||||
/// 根据某些机构财务需要,将期权单价保留两位小数之后,再乘以份额得到的总Pv值
|
||||
/// </summary>
|
||||
public double RoundedPv { get => roundedPv; set => roundedPv = value.Normalize(); }
|
||||
|
||||
public double NRoundedPv { get => nRoundedPv; set => nRoundedPv = value.Normalize(); }
|
||||
|
||||
public TradeValueResultExtend ExtendInfo { get; set; } = new TradeValueResultExtend();
|
||||
|
||||
public double Delta { get => delta; set => delta = value.Normalize(); }
|
||||
|
||||
/// <summary>
|
||||
/// 计算申万跨式组合预付金时会用到
|
||||
/// </summary>
|
||||
internal double DeltaMax { get => deltaMax; set => deltaMax = value.Normalize(); }
|
||||
|
||||
/// <summary>
|
||||
/// 亚式Delta
|
||||
/// </summary>
|
||||
public double SA_Delta { get => sA_Delta; set => sA_Delta = value.Normalize(); }
|
||||
|
||||
/// <summary>
|
||||
/// 时间价值
|
||||
/// </summary>
|
||||
public double TimeValue { get => timeValue; set => timeValue = value.Normalize(); }
|
||||
|
||||
public double Gamma { get => gamma; set => gamma = value.Normalize(); }
|
||||
|
||||
private double _vega;
|
||||
|
||||
public double Vega
|
||||
{
|
||||
get
|
||||
{
|
||||
return _vega;
|
||||
}
|
||||
set
|
||||
{
|
||||
var temp = value.Normalize();
|
||||
if (temp != 0 && VegaCash.Normalize() == 0)
|
||||
{
|
||||
VegaCash = SpotPrice.Normalize() * temp;
|
||||
}
|
||||
_vega = temp;
|
||||
}
|
||||
}
|
||||
|
||||
public double Rho { get => rho; set => rho = value.Normalize(); }
|
||||
|
||||
public double Vol { get => vol; set => vol = value.Normalize(); }
|
||||
|
||||
public double DeltaCash { get => deltaCash; set => deltaCash = value.Normalize(); }
|
||||
|
||||
public double GammaCash { get => gammaCash; set => gammaCash = value.Normalize(); }
|
||||
|
||||
public double VegaCash { get => vegaCash; set => vegaCash = value.Normalize(); }
|
||||
|
||||
public double DDeltaDVol { get => dDeltaDVol; set => dDeltaDVol = value.Normalize(); }
|
||||
|
||||
public double DDeltaDt { get => dDeltaDt; set => dDeltaDt = value.Normalize(); }
|
||||
|
||||
public double DVegaDVol { get => dVegaDVol; set => dVegaDVol = value.Normalize(); }
|
||||
|
||||
public double DVegaDt { get => dVegaDt; set => dVegaDt = value.Normalize(); }
|
||||
|
||||
public double StoppingTime { get => stoppingTime; set => stoppingTime = value.Normalize(); }
|
||||
|
||||
/// <summary>
|
||||
/// 日历日Theta
|
||||
/// </summary>
|
||||
public double CalendarDayTheta { get => calendarDayTheta; set => calendarDayTheta = value.Normalize(); }
|
||||
|
||||
/// <summary>
|
||||
/// 交易日Theta
|
||||
/// </summary>
|
||||
public double TradingDayTheta { get => tradingDayTheta; set => tradingDayTheta = value.Normalize(); }
|
||||
|
||||
public double Theta
|
||||
{
|
||||
get
|
||||
{
|
||||
return BLL.valuedateBLL.SystemDate?.ThetaType != "日历日Theta" ? TradingDayTheta : CalendarDayTheta;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
// 多标的期权
|
||||
//----------------------------------------
|
||||
|
||||
public string UnderlyingCode2 { get; set; }
|
||||
|
||||
public double? Delta2 { get => delta2; set => delta2 = value.Normalize(); }
|
||||
|
||||
public double? Gamma2 { get => gamma2; set => gamma2 = value.Normalize(); }
|
||||
|
||||
public double? Vega2 { get => vega2; set => vega2 = value.Normalize(); }
|
||||
|
||||
public double? DeltaCash2 { get => deltaCash2; set => deltaCash2 = value.Normalize(); }
|
||||
|
||||
public double? GammaCash2 { get => gammaCash2; set => gammaCash2 = value.Normalize(); }
|
||||
public double? ThetaCash2 { get => thetaCash2; set => thetaCash2 = value.Normalize(); }
|
||||
|
||||
public double CrossGamma { get => crossGamma; set => crossGamma = value.Normalize(); }
|
||||
|
||||
public double CrossVogga { get => crossVogga; set => crossVogga = value.Normalize(); }
|
||||
|
||||
public double CorrVega { get => corrVega; set => corrVega = value.Normalize(); }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 报价 卖
|
||||
/// </summary>
|
||||
public double TradePriceAsk { get => tradePriceAsk; set => tradePriceAsk = value.Normalize(); }
|
||||
|
||||
/// <summary>
|
||||
/// 单价四舍五入后的TradePriceAsk
|
||||
/// </summary>
|
||||
public double RoundedTradePriceAsk { get => roundedTradePriceAsk; set => roundedTradePriceAsk = value.Normalize(); }
|
||||
|
||||
/// <summary>
|
||||
/// 买
|
||||
/// </summary>
|
||||
public double TradePriceBid { get => tradePriceBid; set => tradePriceBid = value.Normalize(); }
|
||||
|
||||
/// <summary>
|
||||
/// 单价四舍五入后的TradePriceBid
|
||||
/// </summary>
|
||||
public double RoundedTradePriceBid { get => roundedTradePriceBid; set => roundedTradePriceBid = value.Normalize(); }
|
||||
|
||||
/// <summary>
|
||||
/// delta手数
|
||||
/// </summary>
|
||||
public double DeltaInLots { get => deltaInLots; set => deltaInLots = value.Normalize(); }
|
||||
|
||||
public int? UnderlyingId { get; set; }
|
||||
|
||||
public double? Strike { get => strike; set => strike = value.Normalize(); }
|
||||
|
||||
|
||||
private double? _spotPrice;
|
||||
private double? pricingT;
|
||||
private double? deltaT1;
|
||||
private double? vega4;
|
||||
private double? vega3;
|
||||
private double? gammaCash4;
|
||||
private double? gammaCash3;
|
||||
private double? gamma4;
|
||||
private double? gamma3;
|
||||
private double? deltaCash4;
|
||||
private double? deltaCash3;
|
||||
private double? delta4;
|
||||
private double? delta3;
|
||||
private double? strike;
|
||||
private double deltaInLots;
|
||||
private double roundedTradePriceBid;
|
||||
private double tradePriceBid;
|
||||
private double roundedTradePriceAsk;
|
||||
private double tradePriceAsk;
|
||||
private double corrVega;
|
||||
private double crossVogga;
|
||||
private double crossGamma;
|
||||
private double? gammaCash2;
|
||||
private double? deltaCash2;
|
||||
private double? thetaCash2;
|
||||
private double? thetaCash3;
|
||||
private double? thetaCash4;
|
||||
private double? vega2;
|
||||
private double? gamma2;
|
||||
private double? delta2;
|
||||
private double tradingDayTheta;
|
||||
private double calendarDayTheta;
|
||||
private double stoppingTime;
|
||||
private double dVegaDt;
|
||||
private double dVegaDVol;
|
||||
private double dDeltaDt;
|
||||
private double dDeltaDVol;
|
||||
private double vegaCash;
|
||||
private double gammaCash;
|
||||
private double deltaCash;
|
||||
private double vol;
|
||||
private double rho;
|
||||
private double gamma;
|
||||
private double timeValue;
|
||||
private double sA_Delta;
|
||||
private double deltaMax;
|
||||
private double delta;
|
||||
private double roundedPv;
|
||||
private double nRoundedPv;
|
||||
private double pv;
|
||||
private double npv;
|
||||
|
||||
public double? SpotPrice
|
||||
{
|
||||
get { return _spotPrice; }
|
||||
set
|
||||
{
|
||||
var temp = value.Normalize();
|
||||
if (temp != 0 && VegaCash.Normalize() == 0)
|
||||
{
|
||||
VegaCash = Vega.Normalize() * temp;
|
||||
}
|
||||
_spotPrice = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string UnderlyingCode3 { get; set; }
|
||||
|
||||
public string UnderlyingCode4 { get; set; }
|
||||
|
||||
public double? Delta3 { get => delta3; set => delta3 = value.Normalize(); }
|
||||
|
||||
public double? Delta4 { get => delta4; set => delta4 = value.Normalize(); }
|
||||
|
||||
public double? DeltaCash3 { get => deltaCash3; set => deltaCash3 = value.Normalize(); }
|
||||
|
||||
public double? DeltaCash4 { get => deltaCash4; set => deltaCash4 = value.Normalize(); }
|
||||
|
||||
public double? Gamma3 { get => gamma3; set => gamma3 = value.Normalize(); }
|
||||
|
||||
public double? Gamma4 { get => gamma4; set => gamma4 = value.Normalize(); }
|
||||
|
||||
public double? GammaCash3 { get => gammaCash3; set => gammaCash3 = value.Normalize(); }
|
||||
|
||||
public double? GammaCash4 { get => gammaCash4; set => gammaCash4 = value.Normalize(); }
|
||||
|
||||
public double? ThetaCash3 { get => thetaCash3; set => thetaCash3 = value.Normalize(); }
|
||||
public double? ThetaCash4 { get => thetaCash4; set => thetaCash4 = value.Normalize(); }
|
||||
public double? Vega3 { get => vega3; set => vega3 = value.Normalize(); }
|
||||
|
||||
public double? Vega4 { get => vega4; set => vega4 = value.Normalize(); }
|
||||
|
||||
/// <summary>
|
||||
/// T+1日Delta
|
||||
/// </summary>
|
||||
public double? DeltaT1 { get => deltaT1; set => deltaT1 = value.Normalize(); }
|
||||
|
||||
// 第i+1个标的的delta
|
||||
public double GetDelta(int i)
|
||||
{
|
||||
double? result;
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
result = Delta;
|
||||
break;
|
||||
case 1:
|
||||
result = Delta2;
|
||||
break;
|
||||
case 2:
|
||||
result = Delta3;
|
||||
break;
|
||||
case 3:
|
||||
result = Delta4;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"不合法的标的索引${i}");
|
||||
}
|
||||
|
||||
return double.IsNaN(result ?? 0.0) ? 0.0 : result ?? 0.0;
|
||||
}
|
||||
|
||||
public double GetGamma(int i)
|
||||
{
|
||||
double? result;
|
||||
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
result = Gamma;
|
||||
break;
|
||||
case 1:
|
||||
result = Gamma2;
|
||||
break;
|
||||
case 2:
|
||||
result = Gamma3;
|
||||
break;
|
||||
case 3:
|
||||
result = Gamma4;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"不合法的标的索引${i}");
|
||||
}
|
||||
|
||||
return double.IsNaN(result ?? 0.0) ? 0.0 : result ?? 0.0;
|
||||
}
|
||||
|
||||
public double GetVega(int i)
|
||||
{
|
||||
double? result;
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
result = Vega;
|
||||
break;
|
||||
case 1:
|
||||
result = Vega2;
|
||||
break;
|
||||
case 2:
|
||||
result = Vega3;
|
||||
break;
|
||||
case 3:
|
||||
result = Vega4;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"不合法的标的索引${i}");
|
||||
}
|
||||
|
||||
return double.IsNaN(result ?? 0.0) ? 0.0 : result ?? 0.0;
|
||||
}
|
||||
|
||||
public double GetDeltaCash(int i)
|
||||
{
|
||||
double? result;
|
||||
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
result = DeltaCash;
|
||||
break;
|
||||
case 1:
|
||||
result = DeltaCash2;
|
||||
break;
|
||||
case 2:
|
||||
result = DeltaCash3;
|
||||
break;
|
||||
case 3:
|
||||
result = DeltaCash4;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"不合法的标的索引${i}");
|
||||
}
|
||||
|
||||
return double.IsNaN(result ?? 0.0) ? 0.0 : result ?? 0.0;
|
||||
}
|
||||
|
||||
public double GetGammaCash(int i)
|
||||
{
|
||||
double? result;
|
||||
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
result = GammaCash;
|
||||
break;
|
||||
case 1:
|
||||
result = GammaCash2;
|
||||
break;
|
||||
case 2:
|
||||
result = GammaCash3;
|
||||
break;
|
||||
case 3:
|
||||
result = GammaCash4;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"不合法的标的索引${i}");
|
||||
}
|
||||
|
||||
return double.IsNaN(result ?? 0.0) ? 0.0 : result ?? 0.0;
|
||||
}
|
||||
|
||||
//------------------------------------------
|
||||
|
||||
public int TradeId { get; set; }
|
||||
|
||||
public string BuySell { get; set; }
|
||||
|
||||
public bool Succeeded { get; set; }
|
||||
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误原因
|
||||
/// </summary>
|
||||
internal TradeValueFailReason FailReason { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否来自手动风险维护
|
||||
/// </summary>
|
||||
internal bool FromManual { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 预付金
|
||||
/// </summary>
|
||||
internal double? Margin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设置错误信息,设置后Succeeded变为false
|
||||
/// </summary>
|
||||
/// <param name="errorMessage"></param>
|
||||
public TradeValueResult SetError(string errorMessage)
|
||||
{
|
||||
Succeeded = false;
|
||||
ErrorMessage = errorMessage;
|
||||
return this;
|
||||
}
|
||||
|
||||
public double? PricingT { get => pricingT; set => pricingT = value.Normalize(); }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Succeeded ? $"[{TradeId}]{UnderlyingCode},pv:{Pv}" : ErrorMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否敲出
|
||||
/// </summary>
|
||||
public bool IsKnockOut { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 敲出收益
|
||||
/// </summary>
|
||||
public double KnockOutPayoff { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// PV* 如果敲出则为 敲出收益KnockOutPayoff,未敲出 则为PV
|
||||
/// </summary>
|
||||
public double PvContainsKnockOut { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Delta* 如果敲出则为0 未敲出 则等于Delta
|
||||
/// </summary>
|
||||
public double DeltaContainsKnockOut { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gamma* 如果敲出则为0 未敲出 则等于Delta
|
||||
/// </summary>
|
||||
public double GammaContainsKnockOut { get; set; }
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Theta(轧差)
|
||||
/// </summary>
|
||||
public double ThetaNet { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// dv01值
|
||||
/// </summary>
|
||||
public double DV01 { get; set; }
|
||||
}
|
||||
|
||||
public class TradeValueResultExtend
|
||||
{
|
||||
public double QuotePv { get; set; }
|
||||
|
||||
public double FloatingWinLoss { get; set; }
|
||||
|
||||
public double QuoteFloatingWinLoss { get; set; }
|
||||
|
||||
public double Commission { get; set; }
|
||||
|
||||
public double QuoteCommission { get; set; }
|
||||
|
||||
public double AnnualFee { get; set; }
|
||||
|
||||
public double QuoteAnnualFee { get; set; }
|
||||
|
||||
public double IM { get; set; }
|
||||
|
||||
public double QuoteIM { get; set; }
|
||||
|
||||
public double PFE { get; set; }
|
||||
|
||||
public double QuotePFE { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public enum TradeValueFailReason
|
||||
{
|
||||
none,
|
||||
|
||||
/// <summary>
|
||||
/// 缺少交易数据
|
||||
/// </summary>
|
||||
missingTrade,
|
||||
|
||||
/// <summary>
|
||||
/// 缺少波动率
|
||||
/// </summary>
|
||||
missingVol
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 观察信息服务
|
||||
/// </summary>
|
||||
public static class ObservationDataService
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="tradId"></param>
|
||||
/// <param name="valueDate"></param>
|
||||
/// <returns></returns>
|
||||
public static List<autocall_observation> QueryDatas(int tradId, DateTime valueDate, bool includeValueDate = true)
|
||||
{
|
||||
using (var db = DbContextFactory.GetYLDbContext())
|
||||
{
|
||||
var predicate = PredicateBuilder.Create<autocall_observation>(n => n.TradeId == tradId);
|
||||
if (includeValueDate)
|
||||
{
|
||||
predicate = predicate.And(n => n.EndDate <= valueDate);
|
||||
}
|
||||
else
|
||||
{
|
||||
predicate = predicate.And(n => n.EndDate < valueDate);
|
||||
}
|
||||
return db.autocall_observation.AsNoTracking().Where(predicate).OrderBy(n => n.PaymentDate).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,578 @@
|
||||
using YLErp.BLL;
|
||||
using YLErp.Enums;
|
||||
using YLErp.Modules.CalculationModule.Abstract;
|
||||
using YLErp.QdpModule;
|
||||
using YLErp.ThirdParty.CaculatePrice.DongZheng;
|
||||
using YLErp.ThirdParty.CaculatePrice.DongZheng.Dto;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 只用于计算期权PV
|
||||
/// </summary>
|
||||
public class OptionCalculatorV2
|
||||
{
|
||||
|
||||
private static readonly IYcLogger log = LogFactory.GetLogger(nameof(OptionCalculatorV2));
|
||||
|
||||
/// <summary>
|
||||
/// 只用于计算期权PV
|
||||
/// </summary>
|
||||
public static TradeValueResult GetOptionValueResult(DateTime valueDate, trade td, OptionValueCalcRequest request, out underlying_manager[] underlyings)
|
||||
{
|
||||
if (td is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(td));
|
||||
}
|
||||
|
||||
if (request is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
|
||||
using var marketProxy = new MarketProxy(valueDate, request.sysRiskFreeRate);
|
||||
return GetOptionValueResult(marketProxy, td, request, out underlyings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 只用于计算期权PV
|
||||
/// </summary>
|
||||
public static TradeValueResult GetOptionValueResult(MarketProxy marketProxy, trade td, OptionValueCalcRequest request, out underlying_manager[] underlyings)
|
||||
{
|
||||
if (marketProxy is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(marketProxy));
|
||||
}
|
||||
|
||||
if (td is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(td));
|
||||
}
|
||||
|
||||
if (request is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
|
||||
if (request.spotPrices == null || !request.spotPrices.Any())
|
||||
{
|
||||
throw new Exception("期权计算缺少标的现价");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(td.UnderlyingCode) && td.HasUnderlying())
|
||||
{
|
||||
throw new Exception("缺少标的代码");
|
||||
}
|
||||
|
||||
TradeValueResult result;
|
||||
|
||||
//象屿最后一个交易日实时计算时TTM需要和平仓时算法一致
|
||||
if (PS.Config.Is厦门象屿
|
||||
&& (request.calcScenario == CalcScenarioEnum.RealtimePosition || request.calcScenario == CalcScenarioEnum.RealtimeRisk)
|
||||
&& td.SettlementType == SettlementTypeEnum.ReferencePrice)
|
||||
{
|
||||
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
|
||||
if (underlying != null)
|
||||
{
|
||||
request.timeToMaturityDays = TradeCalcHelper.CalculateTTMDaysForXiangYu(valuedateBLL.ValueDate, td.ExerciseDate.Value, underlying.UnderlyingTypeId, false);
|
||||
//20210706:支持厦门象屿参考价相关交易(这类交易不需要支持精确模式)
|
||||
request.preciseTimeMode = false;
|
||||
}
|
||||
}
|
||||
|
||||
//东证润和是精确时间模式参与计算,收盘的话,刚好是整数天,所以不需要特殊处理
|
||||
|
||||
if (PS.Config.Is润和 && !request.isEodCalc && (request.timeToMaturityDays == null || double.IsNaN(request.timeToMaturityDays.Value)))
|
||||
{
|
||||
request.timeToMaturityDays = TradeCalcHelper.CalculateTTMDays(marketProxy.ValueDate, td.ExerciseDate.Value, 0, false);
|
||||
}
|
||||
|
||||
var tpReq = PrepareCalc(marketProxy, td, request, out underlyings, out var getAsianFixings);
|
||||
|
||||
switch (td.TradeType)
|
||||
{
|
||||
case "香草期权":
|
||||
{
|
||||
var tradeParam = QdpTradeBuilder.GetVanillaOptionTradeParam(td, tpReq, false);
|
||||
result = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
|
||||
}
|
||||
break;
|
||||
case "Risky期权":
|
||||
{
|
||||
result = GetOptionRisky(td, request, underlyings[0], tpReq, marketProxy);
|
||||
}
|
||||
break;
|
||||
case "场内期权":
|
||||
{
|
||||
var tradeParam = QdpTradeBuilder.GetVanillaOptionTradeParam(td, tpReq, true);
|
||||
result = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
|
||||
}
|
||||
break;
|
||||
case "合成价差期权":
|
||||
{
|
||||
var tradeParam = QdpTradeBuilder.GetSSpreadOptionTradeParam(td, tpReq);
|
||||
result = TradeRiskCalcUtil.GetSSpreadOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
|
||||
}
|
||||
break;
|
||||
case "亚式期权":
|
||||
{
|
||||
if (!PS.Config.Is润和 && !request.isEodCalc && (request.timeToMaturityDays == null || double.IsNaN(request.timeToMaturityDays.Value)))
|
||||
{
|
||||
tpReq.timeToMaturityDays = TradeCalcHelper.CalculateTTMDays(marketProxy.ValueDate, td.ExerciseDate.Value, 0, false);
|
||||
}
|
||||
tpReq.fixings = getAsianFixings();
|
||||
var tradeParam = QdpTradeBuilder.GetAsianOptionTradeParam(td, td.trade_asian_option, tpReq);
|
||||
result = TradeRiskCalcUtil.GetAsianOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
|
||||
}
|
||||
break;
|
||||
case "亚式合成价差期权":
|
||||
{
|
||||
tpReq.fixings = getAsianFixings();
|
||||
var tradeParam = QdpTradeBuilder.GetAsianOptionTradeParam(td, td.trade_asian_option, tpReq);
|
||||
result = TradeRiskCalcUtil.GetAsianSSpreadOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
|
||||
}
|
||||
break;
|
||||
case "障碍期权":
|
||||
{
|
||||
var tradeParam = QdpTradeBuilder.GetBarrierOptionTradeParam(td, td.trade_barrier_option, tpReq);
|
||||
result = TradeRiskCalcUtil.GetBarrierOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
|
||||
}
|
||||
break;
|
||||
case "二元期权":
|
||||
{
|
||||
var tradeParam = QdpTradeBuilder.GetBinaryOptionTradeParam(td, td.trade_binary_option, tpReq);
|
||||
result = TradeRiskCalcUtil.GetBinaryOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
|
||||
}
|
||||
break;
|
||||
case "双鲨期权":
|
||||
{
|
||||
var tradeParam = QdpTradeBuilder.GetDoubleSharkFinOptionTradeParam(td, td.trade_double_sharkfin_option, tpReq);
|
||||
result = TradeRiskCalcUtil.GetDoubleSharkFinOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
|
||||
}
|
||||
break;
|
||||
case "凤凰期权":
|
||||
{
|
||||
if (td.trade_autocall == null)
|
||||
{
|
||||
throw new ServiceException("缺少奇异期权数据,交易编号:" + td.TradeNumber);
|
||||
}
|
||||
if (td.trade_autocall.HappenedObservations == null && td.id > 0)
|
||||
{
|
||||
td.trade_autocall.HappenedObservations = ObservationDataService.QueryDatas(td.id, marketProxy.ValueDate);
|
||||
}
|
||||
var tradeParam = QdpTradeBuilder.GetAutocallOptionTradeParam(td, td.trade_autocall, tpReq);
|
||||
try
|
||||
{
|
||||
result = TradeRiskCalcUtil.GetAutocallOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (string.IsNullOrEmpty(td.TradeNumber))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
throw new Exception($"凤凰期权'{td.TradeNumber}'计算出错:{ex.Message}");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "雪球期权":
|
||||
{
|
||||
if (td.IsSnowballSpecialist())
|
||||
{
|
||||
var snowballSpecialistOptionCalculator = new SnowballSpecialistOptionCalculator();
|
||||
|
||||
if (td.trade_snowball.PrepaymentRatio > 0)
|
||||
{
|
||||
var specialSnowballTrade = snowballSpecialistOptionCalculator.GetSpecialTrade(td);
|
||||
var specialSnowballResult = snowballSpecialistOptionCalculator.CalcOptionValue(marketProxy.ValueDate, request.spotPrices[0], request.vols[0], request.calcScenario, specialSnowballTrade);
|
||||
|
||||
var breakevenSnowballTrade = snowballSpecialistOptionCalculator.GetBreakevenTrade(td);
|
||||
var tradeParam = QdpTradeBuilder.GetSnowballTradeParam(breakevenSnowballTrade, breakevenSnowballTrade.trade_snowball, tpReq);
|
||||
tradeParam.riskFreeRate = breakevenSnowballTrade.NoRiskRate ?? 0;
|
||||
var breakevenSnowballResult = TradeRiskCalcUtil.GetSnowballOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
|
||||
|
||||
result = snowballSpecialistOptionCalculator.MergeTradeValueResult(specialSnowballResult, breakevenSnowballResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = snowballSpecialistOptionCalculator.CalcOptionValue(marketProxy.ValueDate, request.spotPrices[0], request.vols[0], request.calcScenario, td);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// 普通雪球
|
||||
var tradeParam = QdpTradeBuilder.GetSnowballTradeParam(td, td.trade_snowball, tpReq);
|
||||
result = TradeRiskCalcUtil.GetSnowballOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "区间累积期权":
|
||||
{
|
||||
if (td.trade_rangeaccrual == null)
|
||||
{
|
||||
throw new ServiceException("缺少奇异期权数据,交易编号:" + td.TradeNumber);
|
||||
}
|
||||
if (td.trade_rangeaccrual.HappenedObservations == null && td.id > 0)
|
||||
{
|
||||
td.trade_rangeaccrual.HappenedObservations = ObservationDataService.QueryDatas(td.id, marketProxy.ValueDate, includeValueDate: false);
|
||||
}
|
||||
tpReq.fixings = request.fixings;
|
||||
if (string.IsNullOrWhiteSpace(tpReq.fixings)
|
||||
&& (td.trade_rangeaccrual.HappenedObservations == null || td.trade_rangeaccrual.HappenedObservations.Count == 0))
|
||||
{
|
||||
//非日终时不将当天的价格加入fixing中
|
||||
var valueDate = request.calcScenario == CalcScenarioEnum.EodMargin
|
||||
|| request.calcScenario == CalcScenarioEnum.EodSettlement
|
||||
|| request.calcScenario == CalcScenarioEnum.ScenarioCalc
|
||||
? marketProxy.ValueDate : marketProxy.ValueDate.AddDays(-1);
|
||||
tpReq.fixings = FixingService.GetFixingString(valueDate: valueDate, otcTrade: td, startDate: td.StartDate ?? td.TradeDate.Value, observationDates: td.trade_rangeaccrual.ObservationDates);
|
||||
}
|
||||
var tradeParam = QdpTradeBuilder.GetRangeAccrualTradeParam(td, td.trade_rangeaccrual, tpReq);
|
||||
result = TradeRiskCalcUtil.GetRangeAccrualValue(marketProxy, GetOptionCalcParam(tradeParam, request));
|
||||
}
|
||||
break;
|
||||
case "气囊结构":
|
||||
{
|
||||
var tradeParam = QdpTradeBuilder.GetAirbagOptionTradeParam(td, td.trade_airbag, tpReq);
|
||||
result = TradeRiskCalcUtil.GetAirbagOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request));
|
||||
}
|
||||
break;
|
||||
case "收益增强结构":
|
||||
{
|
||||
var tradeParam = QdpTradeBuilder.GetUnderlyingEnhanceTradeParam(td, td.trade_underlying_enhance, tpReq);
|
||||
result = TradeRiskCalcUtil.GetUnderlyingEnhanceValue(marketProxy, GetOptionCalcParam(tradeParam, request));
|
||||
}
|
||||
break;
|
||||
case "累计期权":
|
||||
{
|
||||
var tradeParam = QdpTradeBuilder.GetAccumulatorOptionTradeParam(td, td.trade_accumulator_option, tpReq);
|
||||
result = TradeRiskCalcUtil.GetAccumulatorOptionValue(marketProxy, GetOptionCalcParam(tradeParam, request), td.Notional);
|
||||
}
|
||||
break;
|
||||
case "现金流交易":
|
||||
{
|
||||
var tradeParam = QdpTradeBuilder.GetCashFlowTradeParam(td, td.trade_cashflow, tpReq);
|
||||
result = TradeRiskCalcUtil.GetCashFlowValue(marketProxy, GetOptionCalcParam(tradeParam, request), td.StockEqvNotional);
|
||||
}
|
||||
break;
|
||||
case "结构化产品":
|
||||
var structProductParm = QdpTradeBuilder.GetStructProductTradeParam(request, marketProxy, td);
|
||||
StructureResult spResult;
|
||||
log.Info("定价请求信息:" + JsonHelper.Serialize(structProductParm.Request));
|
||||
if (DongZhengPriceApi.StructureProduct(structProductParm.Request, out spResult, structProductParm.VolSurface))
|
||||
{
|
||||
result = new TradeValueResult
|
||||
{
|
||||
Pv = spResult.pv,
|
||||
Delta = spResult.delta,
|
||||
Gamma = spResult.gamma,
|
||||
Vega = spResult.vegaPercentage,
|
||||
TradingDayTheta = spResult.thetaPerDay,
|
||||
Rho = spResult.rhoPercentage
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("东证结构化产品定价计算失败" + spResult.message);
|
||||
}
|
||||
log.Info("定价请求结果:" + JsonHelper.Serialize(spResult));
|
||||
break;
|
||||
default:
|
||||
throw new Exception("不支持这种类型交易的期权计算:" + td.TradeType);
|
||||
}
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
result.Strike = td.Strike ?? 0;
|
||||
result.SpotPrice = request.spotPrices[0];
|
||||
result.VegaCash = result.VegaCash.IsNormalize() ? result.VegaCash : (result.Vega * result.SpotPrice).Normalize();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static TradeValueResult GetOptionRisky(
|
||||
trade td,
|
||||
OptionValueCalcRequest request,
|
||||
underlying_manager underlyings,
|
||||
OptionTradeParamRequest tpReq,
|
||||
MarketProxy marketProxy)
|
||||
{
|
||||
var result = new TradeValueResult();
|
||||
var tradeclone = td.Clone();
|
||||
tradeclone.TradeAmount = tradeclone.TradeAmount = TradeCalcHelper.GetTradeAmountV(td, td.TradeAmount, 1);
|
||||
tradeclone.Notional = tradeclone.Notional = TradeCalcHelper.GetTradeAmountV(td, td.Notional, underlyings.CountRatio);
|
||||
|
||||
var td1 = tradeclone.Clone();
|
||||
var td2 = tradeclone.Clone();
|
||||
if (td.trade_risky_option.ParticipationRate2 != 0)
|
||||
{
|
||||
td2.Strike = td.trade_risky_option.Strike2;
|
||||
td2.ParticipationRate = td.trade_risky_option.ParticipationRate2;
|
||||
td2.TradeAmount = TradeCalcHelper.GetTradeAmount(td2, td2.TradeAmount, 1);
|
||||
td2.Notional = TradeCalcHelper.GetTradeAmount(td2, td2.Notional, underlyings.CountRatio);
|
||||
var tradeParam2 = QdpTradeBuilder.GetVanillaOptionTradeParam(td2, tpReq, false);
|
||||
result = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, GetOptionCalcParam(tradeParam2, request));
|
||||
}
|
||||
|
||||
if (td.trade_risky_option.ParticipationRate1 != 0)
|
||||
{
|
||||
td1.Strike = td.trade_risky_option.Strike1;
|
||||
td1.ParticipationRate = td.trade_risky_option.ParticipationRate1;
|
||||
td1.TradeAmount = TradeCalcHelper.GetTradeAmount(td1, td1.TradeAmount, 1);
|
||||
td1.Notional = TradeCalcHelper.GetTradeAmount(td1, td1.Notional, underlyings.CountRatio);
|
||||
td1.OptionType = "看跌";
|
||||
var tradeParam1 = QdpTradeBuilder.GetVanillaOptionTradeParam(td1, tpReq, false);
|
||||
var singleresult1 = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, GetOptionCalcParam(tradeParam1, request));
|
||||
result.Pv -= singleresult1.Pv;
|
||||
result.Delta -= singleresult1.Delta;
|
||||
result.Gamma -= singleresult1.Gamma;
|
||||
result.Vega -= singleresult1.Vega;
|
||||
result.CalendarDayTheta -= singleresult1.CalendarDayTheta;
|
||||
result.TradingDayTheta -= singleresult1.TradingDayTheta;
|
||||
result.Rho -= singleresult1.Rho;
|
||||
result.DeltaInLots -= singleresult1.DeltaInLots;
|
||||
result.DeltaCash -= singleresult1.DeltaCash;
|
||||
result.GammaCash -= singleresult1.GammaCash;
|
||||
result.VegaCash -= singleresult1.VegaCash;
|
||||
result.RoundedPv -= singleresult1.RoundedPv;
|
||||
result.DDeltaDVol -= singleresult1.DDeltaDVol;
|
||||
result.DDeltaDt -= singleresult1.DDeltaDt;
|
||||
result.DVegaDVol -= singleresult1.DVegaDVol;
|
||||
result.DVegaDt -= singleresult1.DVegaDt;
|
||||
result.DeltaT1 -= singleresult1.DeltaT1;
|
||||
result.ErrorMessage += singleresult1.ErrorMessage;
|
||||
|
||||
result.Vol = singleresult1.Vol;
|
||||
result.Succeeded = singleresult1.Succeeded && result.Succeeded;
|
||||
}
|
||||
|
||||
var td3 = tradeclone.Clone();
|
||||
td3.Strike = td.trade_risky_option.Strike3;
|
||||
//decimal 为了解决精度问题: 0.2-0.3=0.0999999999
|
||||
var participationRate3 = (decimal)td.trade_risky_option.ParticipationRate3 - (decimal)td.trade_risky_option.ParticipationRate2;
|
||||
if (participationRate3 != 0)
|
||||
{
|
||||
td3.ParticipationRate = (double?)Math.Abs(participationRate3);
|
||||
td3.TradeAmount = TradeCalcHelper.GetTradeAmount(td3, td3.TradeAmount, 1);
|
||||
td3.Notional = TradeCalcHelper.GetTradeAmount(td3, td3.Notional, underlyings.CountRatio);
|
||||
var tradeParam3 = QdpTradeBuilder.GetVanillaOptionTradeParam(td3, tpReq, false);
|
||||
var singleresult3 = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, GetOptionCalcParam(tradeParam3, request));
|
||||
if (participationRate3 < 0)
|
||||
{
|
||||
result.Pv -= singleresult3.Pv;
|
||||
result.Delta -= singleresult3.Delta;
|
||||
result.Gamma -= singleresult3.Gamma;
|
||||
result.Vega -= singleresult3.Vega;
|
||||
result.CalendarDayTheta -= singleresult3.CalendarDayTheta;
|
||||
result.TradingDayTheta -= singleresult3.TradingDayTheta;
|
||||
result.Rho -= singleresult3.Rho;
|
||||
result.DeltaInLots -= singleresult3.DeltaInLots;
|
||||
result.DeltaCash -= singleresult3.DeltaCash;
|
||||
result.GammaCash -= singleresult3.GammaCash;
|
||||
result.VegaCash -= singleresult3.VegaCash;
|
||||
result.RoundedPv -= singleresult3.RoundedPv;
|
||||
result.DDeltaDVol -= singleresult3.DDeltaDVol;
|
||||
result.DDeltaDt -= singleresult3.DDeltaDt;
|
||||
result.DVegaDVol -= singleresult3.DVegaDVol;
|
||||
result.DVegaDt -= singleresult3.DVegaDt;
|
||||
result.DeltaT1 -= singleresult3.DeltaT1;
|
||||
result.ErrorMessage += singleresult3.ErrorMessage;
|
||||
|
||||
result.Vol = singleresult3.Vol;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Pv += singleresult3.Pv;
|
||||
result.Delta += singleresult3.Delta;
|
||||
result.Gamma += singleresult3.Gamma;
|
||||
result.Vega += singleresult3.Vega;
|
||||
result.CalendarDayTheta += singleresult3.CalendarDayTheta;
|
||||
result.TradingDayTheta += singleresult3.TradingDayTheta;
|
||||
result.Rho += singleresult3.Rho;
|
||||
result.DeltaInLots += singleresult3.DeltaInLots;
|
||||
result.DeltaCash += singleresult3.DeltaCash;
|
||||
result.GammaCash += singleresult3.GammaCash;
|
||||
result.VegaCash += singleresult3.VegaCash;
|
||||
result.RoundedPv += singleresult3.RoundedPv;
|
||||
result.DDeltaDVol += singleresult3.DDeltaDVol;
|
||||
result.DDeltaDt += singleresult3.DDeltaDt;
|
||||
result.DVegaDVol += singleresult3.DVegaDVol;
|
||||
result.DVegaDt += singleresult3.DVegaDt;
|
||||
result.DeltaT1 += singleresult3.DeltaT1;
|
||||
result.ErrorMessage += singleresult3.ErrorMessage;
|
||||
|
||||
result.Vol = singleresult3.Vol;
|
||||
}
|
||||
result.Succeeded = singleresult3.Succeeded && result.Succeeded;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取期权计算参数
|
||||
/// </summary>
|
||||
private static OptionCalcParam<T> GetOptionCalcParam<T>(T tradeParam, OptionValueCalcRequest request) where T : OptionTradeParamBase
|
||||
{
|
||||
return new OptionCalcParam<T>(tradeParam)
|
||||
{
|
||||
engineName = request.engineName,
|
||||
pricingRequest = request.pricingRequest,
|
||||
spotPrices = request.spotPrices,
|
||||
calcScenario = request.calcScenario,
|
||||
quadratureFastMode = request.quadratureFastMode,
|
||||
CalcDeltaT1 = request.calcDeltaT1
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 准备计算
|
||||
/// </summary>
|
||||
private static OptionTradeParamRequest PrepareCalc(MarketProxy marketProxy, trade td
|
||||
, OptionValueCalcRequest request, out underlying_manager[] underlyings, out Func<string> getAsianFixings)
|
||||
{
|
||||
getAsianFixings = new Func<string>(() =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(request.fixings))
|
||||
{
|
||||
return request.fixings;
|
||||
}
|
||||
var fixings = AsianOptionFixingService.GetFixingString(marketProxy.ValueDate, td);
|
||||
fixings = FixingService.AddOrReplaceLastDateSpotPrice(fixings, marketProxy.ValueDate, request.spotPrices[0]);
|
||||
|
||||
if (PS.Config.Is润和 && DateTime.Now.Hour < 15)
|
||||
{
|
||||
var index = fixings.IndexOf(marketProxy.ValueDate.ToString("yyyy-MM-dd"));
|
||||
if (index >= 0)
|
||||
{
|
||||
fixings = fixings.Remove(index).TrimEnd(';');
|
||||
}
|
||||
}
|
||||
|
||||
return AsianOptionFixingService.CheckAsiaFixings(td, td.trade_asian_option, fixings, request.spotPrices[0]);
|
||||
});
|
||||
|
||||
var tpReq = new OptionTradeParamRequest(request.sysRiskFreeRate)
|
||||
{
|
||||
hasNightMarket = false,
|
||||
maturityShift = request.maturityShift,
|
||||
ParamOverride = request.ParamOverride,
|
||||
preciseTimeMode = request.preciseTimeMode,
|
||||
timeToMaturityDays = request.timeToMaturityDays ?? double.NaN,
|
||||
tradeId = marketProxy.NextRequestId() + "_",
|
||||
volSurfaceNames = null,
|
||||
fixings = null
|
||||
};
|
||||
|
||||
tradeBLL.SetFieldsByTradeType(td);
|
||||
|
||||
switch (td.TradeType)
|
||||
{
|
||||
case "彩虹期权":
|
||||
//var underlyingCodes = new[] { trade.UnderlyingCode, trade.trade_rainbow_option.UnderlyingAssetCode2 };
|
||||
//request.volSurfaceNames = PrepareVols(marketProxy, tradeId.ToString(), underlyingCodes, request.vols);
|
||||
throw new Exception("不支持彩虹期权计算");
|
||||
case "价差期权":
|
||||
//var underlyingCodes = trade.trade_spread_option.UnderlyingAssetCodes();
|
||||
//request.volSurfaceNames = PrepareVols(marketProxy, tradeId.ToString(), underlyingCodes, request.vols);
|
||||
throw new Exception("不支持价差期权计算");
|
||||
case ConsGlobal.TradeType.CashFlow:
|
||||
break;
|
||||
default:
|
||||
tpReq.volSurfaceNames = PrepareVols(marketProxy, tpReq.tradeId, new string[] { td.UnderlyingCode }, request.vols);
|
||||
if (td.StructureType == "亚式熊市价差")
|
||||
{
|
||||
tpReq.fixings = getAsianFixings();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
|
||||
|
||||
if (underlying == null)
|
||||
{
|
||||
if (td.HasUnderlying())
|
||||
{
|
||||
throw new TradeCalcExpception(td.id, "没有找到标的数据:" + td.UnderlyingCode);
|
||||
}
|
||||
}
|
||||
else if (underlying.IsFutures())
|
||||
{
|
||||
var variety = DataCacheProvider.GetVarietyDataSource().GetData(underlying.UnderlyingTypeId);
|
||||
tpReq.hasNightMarket = variety != null && variety.HasNightMarket;
|
||||
}
|
||||
|
||||
underlyings = new[] { underlying ?? new underlying_manager() };
|
||||
|
||||
tpReq.tradeId += td.id > 0 ? td.id.ToString() : string.IsNullOrWhiteSpace(td.ExchangeOptionCode) ? td.UnderlyingCode : td.ExchangeOptionCode;
|
||||
|
||||
return tpReq;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 准备波动率
|
||||
/// </summary>
|
||||
private static string[] PrepareVols(MarketProxy marketProxy, string tradeId, string[] underlyingCodes, double[] vols)
|
||||
{
|
||||
if (string.IsNullOrEmpty(tradeId))
|
||||
{
|
||||
throw new ArgumentException($"“{nameof(tradeId)}”不能是 Null 或为空。", nameof(tradeId));
|
||||
}
|
||||
|
||||
if (underlyingCodes == null || !underlyingCodes.Any())
|
||||
{
|
||||
throw new Exception("缺少标的代码");
|
||||
}
|
||||
|
||||
if (vols == null || !vols.Any() || vols.Length < underlyingCodes.Length)
|
||||
{
|
||||
throw new Exception("缺少波动率");
|
||||
}
|
||||
|
||||
var volSurfaceNames = new string[underlyingCodes.Length];
|
||||
|
||||
for (var i = 0; i < underlyingCodes.Length; i++)
|
||||
{
|
||||
var volatility = QdpVolHelper.GetDefaultVolatility(vols[i]);
|
||||
volSurfaceNames[i] = QdpVolHelper.GetVolSurfaceName(tradeId, i > 0 ? underlyingCodes[i] : null);
|
||||
marketProxy.SetVolSurface(volSurfaceNames[i], volatility);
|
||||
}
|
||||
|
||||
return volSurfaceNames;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算交叉GAMMA
|
||||
/// </summary>
|
||||
public static double[] CalcSSpreadCrossGammas(DateTime valueDate, trade td, OptionValueCalcRequest request, double[] coefficients)
|
||||
{
|
||||
if (td is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(td));
|
||||
}
|
||||
|
||||
if (request is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
|
||||
if (request.spotPrices == null || !request.spotPrices.Any())
|
||||
{
|
||||
throw new Exception("期权计算缺少标的现价");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(td.UnderlyingCode) && td.HasUnderlying())
|
||||
{
|
||||
throw new Exception("缺少标的代码");
|
||||
}
|
||||
|
||||
using var marketProxy = new MarketProxy(valueDate, request.sysRiskFreeRate);
|
||||
var tpReq = PrepareCalc(marketProxy, td, request, out _, out _);
|
||||
var tradeParam = QdpTradeBuilder.GetSSpreadOptionTradeParam(td, tpReq, coefficients);
|
||||
return TradeRiskCalcUtil.CalcSSpreadCrossGammas(marketProxy, GetOptionCalcParam(tradeParam, request), coefficients);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,685 @@
|
||||
using Qdp.Foundation.Implementations;
|
||||
using Qdp.Pricing.Base.Implementations;
|
||||
using Qdp.Pricing.Base.Interfaces;
|
||||
using Qdp.Pricing.Base.Utilities;
|
||||
using Qdp.Pricing.Library.Common.Base;
|
||||
using Qdp.Pricing.Library.Options.Products.Accumulator;
|
||||
using Qdp.Pricing.Library.Options.Products.Airbag;
|
||||
using Qdp.Pricing.Library.Options.Products.Asian;
|
||||
using Qdp.Pricing.Library.Options.Products.AsianSyntheticSpread;
|
||||
using Qdp.Pricing.Library.Options.Products.Autocall.Phoenix;
|
||||
using Qdp.Pricing.Library.Options.Products.Autocall.Snowball;
|
||||
using Qdp.Pricing.Library.Options.Products.Barrier;
|
||||
using Qdp.Pricing.Library.Options.Products.Binary;
|
||||
using Qdp.Pricing.Library.Options.Products.DoubleSharkFin;
|
||||
using Qdp.Pricing.Library.Options.Products.PayoffEnhance;
|
||||
using Qdp.Pricing.Library.Options.Products.Rainbow;
|
||||
using Qdp.Pricing.Library.Options.Products.RangeAccrual;
|
||||
using Qdp.Pricing.Library.Options.Products.Spread;
|
||||
using Qdp.Pricing.Library.Options.Products.SyntheticSpread;
|
||||
using Qdp.Pricing.Library.Options.Products.Vanilla;
|
||||
using YLErp.BLL;
|
||||
using YLErp.BLL.Calculation.V2;
|
||||
using YLErp.Modules.TradeModule;
|
||||
using YLErp.Modules.VolatilityModule;
|
||||
using YLErp.QdpModule;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
public class OptionTradeAnalysisService : YLBaseService
|
||||
{
|
||||
public OptionTradeAnalysisService(OptUserInfo userInfo) : base(userInfo)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为一组期权交易计算到期时在不同价格条件下的总体payoff情况
|
||||
/// </summary>
|
||||
/// <param name="optionTrades"></param>
|
||||
/// <returns></returns>
|
||||
public List<CurvePoint> GetTradesPayoffLine(IEnumerable<OtcOptionTradeFull> optionTrades)
|
||||
{
|
||||
if (optionTrades == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var curvePoints = new List<CurvePoint>();
|
||||
var keyPricePoints = new List<double>();
|
||||
|
||||
if (optionTrades.Count(t => t.TradeType == "雪球期权" || t.TradeType == "凤凰期权") > 0)
|
||||
{
|
||||
throw new Exception($"暂时不支持Autocall的分析");
|
||||
}
|
||||
|
||||
var underlyingIds = optionTrades.Select(n => n.UnderlyingId).ToHashSet();
|
||||
if (underlyingIds.Count > 1)
|
||||
{
|
||||
throw new Exception($"待分析的组合交易需要有相同的标的资产");
|
||||
}
|
||||
|
||||
var underlying = DbContext.underlying_manager.AsNoTracking().FirstOrDefault(n => underlyingIds.Contains(n.id));
|
||||
if (underlying == null)
|
||||
{
|
||||
throw new Exception($"找不到标的资产{optionTrades.First().UnderlyingCode}的信息,id为{optionTrades.First().UnderlyingId}");
|
||||
}
|
||||
|
||||
var options = new List<OptionBase>();
|
||||
foreach (var otcTrade in optionTrades)
|
||||
{
|
||||
var trade = TradeConverter.ConvertOptionTrade(otcTrade);
|
||||
|
||||
if (otcTrade.TradeType == "Risky期权")
|
||||
{
|
||||
var _options = GetToQdpOptionRisk(trade, underlying);
|
||||
if (_options != null)
|
||||
{
|
||||
options.AddRange(_options);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var option = ToQdpOption(trade, underlying);
|
||||
if (option != null)
|
||||
{
|
||||
options.Add(option);
|
||||
}
|
||||
}
|
||||
var points = GetKeyPoints(trade);
|
||||
if (points != null)
|
||||
{
|
||||
keyPricePoints.AddRange(points);
|
||||
}
|
||||
}
|
||||
|
||||
keyPricePoints = keyPricePoints.Distinct().ToList();
|
||||
keyPricePoints.Sort();
|
||||
keyPricePoints.Insert(0, keyPricePoints.First() * 0.8);
|
||||
keyPricePoints.Add(keyPricePoints.Last() * 1.2);
|
||||
|
||||
var prices = new double[] { 0.0 };
|
||||
foreach (var price in keyPricePoints)
|
||||
{
|
||||
prices[0] = price;
|
||||
curvePoints.Add(new CurvePoint()
|
||||
{
|
||||
X = price,
|
||||
Y = options.Sum(x =>
|
||||
{
|
||||
if (x is BarrierOption barrier)
|
||||
{
|
||||
//障碍期权在GetPayoff方法中会更改BarrierStatus值,所以需要重置
|
||||
barrier.BarrierStatus = Qdp.Pricing.Base.Enums.BarrierStatus.Monitoring;
|
||||
}
|
||||
return x.GetPayoff(prices)[0].PaymentAmount;
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return curvePoints;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为一组期权交易计算不同价格条件下的Pv
|
||||
/// </summary>
|
||||
public List<CurvePoint> GetTradesPvLine(IEnumerable<OtcOptionTradeFull> optionTrades)
|
||||
{
|
||||
if (optionTrades == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!optionTrades.Any())
|
||||
{
|
||||
return new List<CurvePoint>(0);
|
||||
}
|
||||
|
||||
var curvePoints = new List<CurvePoint>();
|
||||
var keyPricePoints = new List<double>();
|
||||
|
||||
if (optionTrades.Count(t => t.TradeType == "雪球期权" || t.TradeType == "凤凰期权") > 0)
|
||||
{
|
||||
throw new Exception($"暂时不支持Autocall的分析");
|
||||
}
|
||||
|
||||
var underlyingCount = optionTrades.Select(n => n.UnderlyingCode?.ToLowerInvariant()).Distinct().Count();
|
||||
if (underlyingCount > 1)
|
||||
{
|
||||
throw new Exception($"待分析的组合交易需要有相同的标的资产");
|
||||
}
|
||||
|
||||
var trades = new List<trade>();
|
||||
|
||||
foreach (var otcTrade in optionTrades)
|
||||
{
|
||||
var trade = TradeConverter.ConvertOptionTrade(otcTrade);
|
||||
if (trade != null)
|
||||
{
|
||||
trades.Add(trade);
|
||||
}
|
||||
|
||||
var points = GetKeyPoints(trade);
|
||||
if (points != null)
|
||||
{
|
||||
keyPricePoints.AddRange(points);
|
||||
}
|
||||
}
|
||||
|
||||
return GetTradesPvLineForKeyPoints(optionTrades.First().TradeDate.Value, trades, keyPricePoints);
|
||||
}
|
||||
|
||||
private List<CurvePoint> GetTradesPvLineForKeyPoints(DateTime valueDate, IEnumerable<trade> trades, List<double> keyPoints)
|
||||
{
|
||||
var curvePoints = new List<CurvePoint>();
|
||||
|
||||
if (trades == null || trades.Count() == 0)
|
||||
{
|
||||
return curvePoints;
|
||||
}
|
||||
|
||||
var startPrice = keyPoints.Min() > 0 ? keyPoints.Min() * 0.8 : keyPoints.Min() * 1.2;
|
||||
var endPrice = keyPoints.Min() > 0 ? keyPoints.Max() * 1.2 : keyPoints.Max() * 0.8;
|
||||
var step = (endPrice - startPrice) / 20.0;
|
||||
|
||||
var price = startPrice;
|
||||
|
||||
var calcReq = new OptionValueCalcRequest(valuedateBLL.SysRiskFreeRate())
|
||||
{
|
||||
correlations = null,
|
||||
engineName = null,
|
||||
maturityShift = 0,
|
||||
ParamOverride = null,
|
||||
preciseTimeMode = false,
|
||||
pricingRequest = PricingRequest.Pv
|
||||
};
|
||||
|
||||
while (price < endPrice + step)
|
||||
{
|
||||
var pv = 0.0;
|
||||
|
||||
foreach (var trade in trades)
|
||||
{
|
||||
//场内期权交易不会有开仓波动率,因此根据其交易价格计算出隐含波动率
|
||||
if (trade.TradeType == "场内期权" && !trade.TradeOpenVolatility.HasValue)
|
||||
{
|
||||
trade.VolType = "交易";
|
||||
trade.TradeOpenVolatility = VolatilityHelper.GetImpliedVol(trade.TradeDate ?? valuedateBLL.ValueDate, trade, trade.TTMDays, trade.SpotPrice ?? 0, true);
|
||||
}
|
||||
|
||||
if (!PS.Config.IsTradeVol && !trade.TradeOpenVolatility.HasValue)
|
||||
{
|
||||
trade.TradeOpenVolatility = trade.Vol;
|
||||
}
|
||||
|
||||
if (!trade.TradeOpenVolatility.HasValue || double.IsNaN(trade.TradeOpenVolatility.Value))
|
||||
{
|
||||
throw new Exception("无法获取开仓波动率");
|
||||
}
|
||||
|
||||
calcReq.spotPrices = new[] { price };
|
||||
calcReq.vols = new[] { trade.TradeOpenVolatility.Value };
|
||||
|
||||
var result = OptionCalculatorV2.GetOptionValueResult(valueDate, trade, calcReq, out _);
|
||||
|
||||
if (double.IsNaN(result.Pv))
|
||||
{
|
||||
throw new Exception($"计算此交易失败:{trade.UnderlyingCode},{trade.TradeType}");
|
||||
}
|
||||
|
||||
pv += result.Pv;
|
||||
}
|
||||
|
||||
curvePoints.Add(new CurvePoint()
|
||||
{
|
||||
X = price,
|
||||
Y = pv
|
||||
});
|
||||
|
||||
price += step;
|
||||
}
|
||||
|
||||
return curvePoints;
|
||||
}
|
||||
|
||||
public List<CurvePoint> GetTradesPvLine2(int tradeId)
|
||||
{
|
||||
var trade = DbContext.trade.FirstOrDefault(t => t.id == tradeId);
|
||||
if (trade == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<trade> trades;
|
||||
if (trade.TradeType == "结构化交易")
|
||||
{
|
||||
trades = DbContext.trade.Where(t => t.ParentTradeId == trade.id).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
trades = new List<trade> { trade };
|
||||
}
|
||||
|
||||
var tradebll = new tradeBLL();
|
||||
trades.ForEach(t => tradeBLL.SetFieldsByTradeType(t));
|
||||
|
||||
return GetTradesPvLine2(valuedateBLL.ValueDate, trades);
|
||||
}
|
||||
|
||||
private List<CurvePoint> GetTradesPvLine2(DateTime valueDate, List<trade> optionTrades)
|
||||
{
|
||||
if (optionTrades == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (optionTrades.Count(t => t.TradeType == "雪球期权" || t.TradeType == "凤凰期权") > 0)
|
||||
{
|
||||
throw new Exception($"暂时不支持Autocall的分析");
|
||||
}
|
||||
|
||||
var underlyingCount = optionTrades.Select(n => n.UnderlyingCode.ToLowerInvariant()).Distinct().Count();
|
||||
if (underlyingCount > 1)
|
||||
{
|
||||
throw new Exception($"待分析的组合交易需要有相同的标的资产");
|
||||
}
|
||||
|
||||
var keyPricePoints = new List<double>();
|
||||
|
||||
optionTrades.ForEach(t =>
|
||||
{
|
||||
keyPricePoints.AddRange(GetKeyPoints(t));
|
||||
});
|
||||
|
||||
return GetTradesPvLineForKeyPoints(valueDate, optionTrades, keyPricePoints);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算交易在一半ttm时候的Pv曲线
|
||||
/// </summary>
|
||||
public List<CurvePoint> GetTradesHalflifePvLine(IEnumerable<OtcOptionTradeFull> optionTrades)
|
||||
{
|
||||
var dayCount = valuedateBLL.TradeDayCount.ToDayCountImpl();
|
||||
foreach (var trade in optionTrades)
|
||||
{
|
||||
trade.ExerciseDate = AdjustToHalfLifeMaturity(dayCount, trade.TradeDate, trade.ExerciseDate);
|
||||
trade.TTMDays = double.NaN;
|
||||
}
|
||||
|
||||
return GetTradesPvLine(optionTrades);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回当前、一半ttm、以及在到期时的Pv曲线
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<List<CurvePoint>> GetTradePvLifeLine(IEnumerable<OtcOptionTradeFull> optionTrades)
|
||||
{
|
||||
var results = new List<List<CurvePoint>>();
|
||||
|
||||
var pvLine = GetTradesPvLine(optionTrades);
|
||||
results.Add(pvLine);
|
||||
|
||||
var payoffPoints = GetTradesPayoffLine(optionTrades);
|
||||
//按Pv曲线的X点对齐
|
||||
//int start = -1, end = 0;
|
||||
//var payoffLine = new List<CurvePoint>();
|
||||
//for (var i = 0; i < pvLine.Count; ++i)
|
||||
//{
|
||||
// if (pvLine[i].X == payoffPoints[end].X)
|
||||
// {
|
||||
// payoffLine.Add(payoffPoints[end]);
|
||||
// ++start;
|
||||
// ++end;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// payoffLine.Add(new CurvePoint() { X = pvLine[i].X, Y = simpleInterpolate(payoffPoints[start].X, payoffPoints[end].X, payoffPoints[start].Y, payoffPoints[end].Y, pvLine[i].X) });
|
||||
// }
|
||||
//}
|
||||
results.Add(payoffPoints);
|
||||
results.Add(GetTradesHalflifePvLine(optionTrades));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private DateTime? AdjustToHalfLifeMaturity(IDayCount dayCount, DateTime? tradeDate, DateTime? maturityDate)
|
||||
{
|
||||
var qdpStart = new Date(tradeDate.Value);
|
||||
var qdpEnd = new Date(maturityDate.Value);
|
||||
var half = dayCount.CalcDayCountFraction(qdpStart, qdpEnd) / 2.0;
|
||||
qdpEnd = dayCount.CalcEndDateFromDayCountFraction(qdpStart, half, null, null);
|
||||
return qdpEnd.DateTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为一组期权交易计算随时间变化的希腊字母变化
|
||||
/// </summary>
|
||||
/// <param name="optionTrades"></param>
|
||||
/// <returns></returns>
|
||||
public Dictionary<string, List<CurvePoint>> GetTradesGreeksForLifetime(IEnumerable<OtcOptionTradeFull> optionTrades)
|
||||
{
|
||||
if (optionTrades == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var curvePoints = new List<CurvePoint>();
|
||||
|
||||
if (optionTrades.Count(t => t.TradeType == "雪球期权" || t.TradeType == "凤凰期权") > 0)
|
||||
{
|
||||
throw new Exception($"暂时不支持Autocall的分析");
|
||||
}
|
||||
|
||||
var underlyingIds = optionTrades.Select(n => n.UnderlyingId).ToHashSet();
|
||||
if (underlyingIds.Count > 1)
|
||||
{
|
||||
throw new Exception($"待分析的组合交易需要有相同的标的资产");
|
||||
}
|
||||
|
||||
var underlying = DbContext.underlying_manager.AsNoTracking().FirstOrDefault(n => underlyingIds.Contains(n.id));
|
||||
if (underlying == null)
|
||||
{
|
||||
throw new Exception($"找不到标的资产{optionTrades.First().UnderlyingCode}的信息,id为{optionTrades.First().UnderlyingId}");
|
||||
}
|
||||
var options = new List<OptionBase>();
|
||||
var trades = new List<trade>();
|
||||
foreach (var otcTrade in optionTrades)
|
||||
{
|
||||
var trade = TradeConverter.ConvertOptionTrade(otcTrade);
|
||||
if (trade != null)
|
||||
{
|
||||
trades.Add(trade);
|
||||
}
|
||||
}
|
||||
|
||||
var minStart = trades.Min(t => t.TradeDate.Value);
|
||||
//避开最后一天的计算,在到期日当天会出现一些跟时间相关的结果,会混淆曲线的整体趋势
|
||||
var maxMaturity = CalendarImpl.Get("chn").PrevBizDay(new Date(trades.Max(t => t.ExerciseDate.Value)));
|
||||
|
||||
var valueDates = CalendarImpl.Get("chn").BizDaysBetweenDatesInclEndDay(new Date(minStart), new Date(maxMaturity));
|
||||
var userId = Guid.NewGuid().ToString();
|
||||
var results = new Dictionary<string, List<CurvePoint>>();
|
||||
results["Pv"] = new List<CurvePoint>();
|
||||
results["Delta"] = new List<CurvePoint>();
|
||||
results["Gamma"] = new List<CurvePoint>();
|
||||
results["Vega"] = new List<CurvePoint>();
|
||||
results["Theta"] = new List<CurvePoint>();
|
||||
for (var i = 0; i < valueDates.Count; ++i)
|
||||
{
|
||||
double pv = 0.0, delta = 0.0, gamma = 0.0, vega = 0.0, theta = 0.0;
|
||||
foreach (var trade in trades)
|
||||
{
|
||||
trade.TradeDate = valueDates[i].DateTime;
|
||||
trade.TTMDays = double.NaN;
|
||||
underlying.QuotationDate = trade.TradeDate;
|
||||
|
||||
if (!PS.Config.IsTradeVol && !trade.TradeOpenVolatility.HasValue)
|
||||
{
|
||||
trade.TradeOpenVolatility = trade.Vol;
|
||||
}
|
||||
if (!trade.TradeOpenVolatility.HasValue || double.IsNaN(trade.TradeOpenVolatility.Value))
|
||||
{
|
||||
throw new Exception("无法获取开仓波动率");
|
||||
}
|
||||
|
||||
var result = ValueCalculator.GetOptionValueResultV2(
|
||||
userId,
|
||||
underlying,
|
||||
trade,
|
||||
new double[] { trade.TradeOpenVolatility.Value },
|
||||
new double[] { trade.SpotPrice.Value },
|
||||
request: QdpPricingRequest.BASIC_GREEKS);
|
||||
|
||||
if (double.IsNaN(result.Pv))
|
||||
{
|
||||
throw new Exception($"计算此交易失败:{trade.UnderlyingCode},{trade.TradeType}");
|
||||
}
|
||||
pv += result.Pv;
|
||||
delta += result.Delta;
|
||||
gamma += result.Gamma;
|
||||
vega += result.Vega;
|
||||
theta += result.Theta;
|
||||
}
|
||||
|
||||
results["Pv"].Add(new CurvePoint() { X = i, Y = pv });
|
||||
results["Delta"].Add(new CurvePoint() { X = i, Y = delta });
|
||||
results["Gamma"].Add(new CurvePoint() { X = i, Y = gamma });
|
||||
results["Vega"].Add(new CurvePoint() { X = i, Y = vega });
|
||||
results["Theta"].Add(new CurvePoint() { X = i, Y = theta });
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获得一笔期权影响payoff的价格点,如行权价、障碍价格等
|
||||
/// </summary>
|
||||
/// <param name="trade"></param>
|
||||
/// <returns></returns>
|
||||
private IEnumerable<double> GetKeyPoints(trade trade)
|
||||
{
|
||||
switch (trade.TradeType)
|
||||
{
|
||||
case "香草期权":
|
||||
case "场内期权":
|
||||
case "亚式期权":
|
||||
case "彩虹期权":
|
||||
case "合成价差期权":
|
||||
case "亚式合成价差期权":
|
||||
case "收益增强结构":
|
||||
case "区间累积期权":
|
||||
case "价差期权":
|
||||
case "凤凰期权":
|
||||
case "雪球期权":
|
||||
case "气囊结构":
|
||||
return new double[] {
|
||||
trade.IsMoneynessOptionData ? trade.Strike.Value * trade.SpotPrice.Value : trade.Strike.Value
|
||||
};
|
||||
case "累计期权":
|
||||
if (trade.trade_accumulator_option.AccumulatorStructureType == AccumulatorStructureTypeEnum.Segmented)
|
||||
{
|
||||
return new double[] {
|
||||
trade.IsMoneynessOptionData ? trade.Strike.Value * trade.SpotPrice.Value : trade.Strike.Value,
|
||||
trade.IsMoneynessOptionData ? trade.trade_accumulator_option.Strike2.Value * trade.SpotPrice.Value : trade.trade_accumulator_option.Strike2.Value,
|
||||
trade.IsMoneynessOptionData ? trade.trade_accumulator_option.Strike3.Value * trade.SpotPrice.Value : trade.trade_accumulator_option.Strike3.Value,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return new double[] {
|
||||
trade.IsMoneynessOptionData ? trade.Strike.Value * trade.SpotPrice.Value : trade.Strike.Value
|
||||
};
|
||||
}
|
||||
case "障碍期权":
|
||||
if (trade.trade_barrier_option.BarrierTypeEn.StartsWith("Double"))
|
||||
{
|
||||
return new double[] {
|
||||
trade.IsMoneynessOptionData ? trade.Strike.Value * trade.SpotPrice.Value : trade.Strike.Value,
|
||||
trade.IsMoneynessOptionData ? trade.trade_barrier_option.BarrierPrice.Value* trade.SpotPrice.Value : trade.trade_barrier_option.BarrierPrice.Value,
|
||||
trade.IsMoneynessOptionData ? trade.trade_barrier_option.BarrierPrice.Value* trade.SpotPrice.Value + 0.01 : trade.trade_barrier_option.BarrierPrice.Value + 0.01,
|
||||
trade.IsMoneynessOptionData ? trade.trade_barrier_option.UpperBarrierPrice.Value * trade.SpotPrice.Value : trade.trade_barrier_option.UpperBarrierPrice.Value,
|
||||
trade.IsMoneynessOptionData ? trade.trade_barrier_option.UpperBarrierPrice.Value * trade.SpotPrice.Value - 0.01 : trade.trade_barrier_option.UpperBarrierPrice.Value - 0.01
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (trade.trade_barrier_option.BarrierTypeEn.StartsWith("Up"))
|
||||
{
|
||||
return new double[] {
|
||||
trade.IsMoneynessOptionData ? trade.Strike.Value * trade.SpotPrice.Value : trade.Strike.Value,
|
||||
trade.IsMoneynessOptionData ? trade.trade_barrier_option.BarrierPrice.Value * trade.SpotPrice.Value : trade.trade_barrier_option.BarrierPrice.Value,
|
||||
trade.IsMoneynessOptionData ? trade.trade_barrier_option.BarrierPrice.Value * trade.SpotPrice.Value - 0.01 : trade.trade_barrier_option.BarrierPrice.Value - 0.01
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return new double[] {
|
||||
trade.IsMoneynessOptionData ? trade.Strike.Value * trade.SpotPrice.Value : trade.Strike.Value,
|
||||
trade.IsMoneynessOptionData ? trade.trade_barrier_option.BarrierPrice.Value * trade.SpotPrice.Value : trade.trade_barrier_option.BarrierPrice.Value,
|
||||
trade.IsMoneynessOptionData ? trade.trade_barrier_option.BarrierPrice.Value * trade.SpotPrice.Value + 0.01 : trade.trade_barrier_option.BarrierPrice.Value + 0.01
|
||||
};
|
||||
}
|
||||
}
|
||||
case "二元期权":
|
||||
if (trade.ExerciseMode == "American" && trade.trade_binary_option.PayoffType.StartsWith("Double"))
|
||||
{
|
||||
var low = trade.IsMoneynessOptionData ? trade.Strike.Value * trade.SpotPrice.Value : trade.Strike.Value;
|
||||
var high = trade.IsMoneynessOptionData ? trade.trade_binary_option.UpperBarrier.Value * trade.SpotPrice.Value : trade.trade_binary_option.UpperBarrier.Value;
|
||||
return new double[] {
|
||||
low - 0.01,
|
||||
low,
|
||||
low + 0.01,
|
||||
high - 0.01,
|
||||
high,
|
||||
high + 0.01
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
var low = trade.IsMoneynessOptionData ? trade.Strike.Value * trade.SpotPrice.Value : trade.Strike.Value;
|
||||
return new double[] {
|
||||
low - 0.01,
|
||||
low,
|
||||
low + 0.01
|
||||
};
|
||||
}
|
||||
case "双鲨期权":
|
||||
return new double[] {
|
||||
trade.IsMoneynessOptionData ? trade.Strike.Value * trade.SpotPrice.Value : trade.Strike.Value,
|
||||
trade.IsMoneynessOptionData ? trade.trade_double_sharkfin_option.StrikeHigh.Value * trade.SpotPrice.Value : trade.trade_double_sharkfin_option.StrikeHigh.Value,
|
||||
trade.IsMoneynessOptionData ? trade.trade_double_sharkfin_option.BarrierHigh * trade.SpotPrice.Value : trade.trade_double_sharkfin_option.BarrierHigh,
|
||||
trade.IsMoneynessOptionData ? trade.trade_double_sharkfin_option.BarrierHigh * trade.SpotPrice.Value - 0.01 : trade.trade_double_sharkfin_option.BarrierHigh - 0.01,
|
||||
trade.IsMoneynessOptionData ? trade.trade_double_sharkfin_option.BarrierLow * trade.SpotPrice.Value : trade.trade_double_sharkfin_option.BarrierLow,
|
||||
trade.IsMoneynessOptionData ? trade.trade_double_sharkfin_option.BarrierLow * trade.SpotPrice.Value + 0.01 : trade.trade_double_sharkfin_option.BarrierLow + 0.01,
|
||||
};
|
||||
case "Risky期权":
|
||||
return new double[] {
|
||||
trade.IsMoneynessOptionData ? trade.trade_risky_option.Strike1.Value * trade.SpotPrice.Value : trade.trade_risky_option.Strike1.Value,
|
||||
trade.IsMoneynessOptionData ? trade.trade_risky_option.Strike2.Value * trade.SpotPrice.Value : trade.trade_risky_option.Strike2.Value,
|
||||
trade.IsMoneynessOptionData ? trade.trade_risky_option.Strike3.Value * trade.SpotPrice.Value : trade.trade_risky_option.Strike3.Value,
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将trade对象转换为Qdp对应的期权类型
|
||||
/// </summary>
|
||||
private OptionBase ToQdpOption(trade trade, underlying_manager underlying)
|
||||
{
|
||||
underlying = underlying.Clone();
|
||||
|
||||
underlying.UnderlyingInstrumentType = ConsGlobal.InstrumentType.ConvertCalcType(underlying.UnderlyingInstrumentType);
|
||||
|
||||
switch (trade.TradeType)
|
||||
{
|
||||
case "香草期权":
|
||||
return QdpTradeBuilder.GetVanillaOptionTrade(trade, null, false)?.Instrument as VanillaOption;
|
||||
case "场内期权":
|
||||
return QdpTradeBuilder.GetVanillaOptionTrade(trade, null, true)?.Instrument as VanillaOption;
|
||||
case "障碍期权":
|
||||
return QdpTradeBuilder.GetBarrierOptionTrade(trade, trade.trade_barrier_option, null)?.Instrument as BarrierOption;
|
||||
case "亚式期权":
|
||||
return QdpTradeBuilder.GetAsianOptionTrade(trade, trade.trade_asian_option, null)?.Instrument as AsianOption;
|
||||
case "二元期权":
|
||||
return QdpTradeBuilder.GetBinaryOptionTrade(trade, trade.trade_binary_option, null)?.Instrument as BinaryOption;
|
||||
case "彩虹期权":
|
||||
return QdpTradeBuilder.GetRainbowOptionTrade(trade, trade.trade_rainbow_option, null)?.Instrument as RainbowOption;
|
||||
case "价差期权":
|
||||
return QdpTradeBuilder.GetSpreadOptionTrade(trade, trade.trade_spread_option, null, null)?.Instrument as SpreadOption;
|
||||
case "合成价差期权":
|
||||
return QdpTradeBuilder.GetSSpreadOptionTrade(trade)?.Instrument as SyntheticNormalSpreadOption;
|
||||
case "亚式合成价差期权":
|
||||
return QdpTradeBuilder.GetAsianSSpreadOptionTrade(trade, trade.trade_asian_option, null)?.Instrument as AsianSyntheticNormalSpreadOption;
|
||||
case "双鲨期权":
|
||||
return QdpTradeBuilder.GetDoubleSharkFinOptionTrade(trade, trade.trade_double_sharkfin_option, null)?.Instrument as DoubleSharkFinOption;
|
||||
case "凤凰期权":
|
||||
return QdpTradeBuilder.GetAutocallOptionTrade(trade, trade.trade_autocall)?.Instrument as AutoCall;
|
||||
case "雪球期权":
|
||||
return QdpTradeBuilder.GetSnowballOptionTrade(trade, trade.trade_snowball)?.Instrument as SimpleSnowball;
|
||||
case "区间累积期权":
|
||||
return QdpTradeBuilder.GetRangeAccrualTrade(trade, trade.trade_rangeaccrual, null)?.Instrument as RangeAccrual;
|
||||
case "累积期权":
|
||||
case "累计期权":
|
||||
return QdpTradeBuilder.GetAccumulatorOptionTrade(trade, trade.trade_accumulator_option, null)?.Instrument as AccumulatorOption;
|
||||
case "气囊结构":
|
||||
return QdpTradeBuilder.GetAirbagOptionTrade(trade, trade.trade_airbag, null)?.Instrument as Airbag;
|
||||
case "收益增强结构":
|
||||
return QdpTradeBuilder.GetUnderlyingEnhanceTrade(trade, trade.trade_underlying_enhance, null)?.Instrument as UnderlyingPayoffEnhance;
|
||||
case "Risky期权":
|
||||
return QdpTradeBuilder.GetVanillaOptionTrade(trade, null, false)?.Instrument as VanillaOption;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public List<OptionBase> GetToQdpOptionRisk(trade trade, underlying_manager underlying)
|
||||
{
|
||||
var options = new List<OptionBase>();
|
||||
var tradeclone = trade.Clone();
|
||||
tradeclone.TradeAmount = tradeclone.TradeAmount = TradeCalcHelper.GetTradeAmountV(trade, trade.TradeAmount, 1);
|
||||
tradeclone.Notional = tradeclone.Notional = TradeCalcHelper.GetTradeAmountV(trade, trade.Notional, underlying.CountRatio);
|
||||
|
||||
var td1 = tradeclone.Clone();
|
||||
if (trade.trade_risky_option.ParticipationRate1 != 0)
|
||||
{
|
||||
td1.Strike = trade.trade_risky_option.Strike1;
|
||||
td1.ParticipationRate = trade.trade_risky_option.ParticipationRate1;
|
||||
td1.TradeAmount = TradeCalcHelper.GetTradeAmount(td1, td1.TradeAmount, 1);
|
||||
td1.Notional = TradeCalcHelper.GetTradeAmount(td1, td1.Notional, underlying.CountRatio);
|
||||
td1.OptionType = "看跌";
|
||||
td1.BuySell = trade.BuySell == "买入" ? "卖出" : "买入";
|
||||
var option1 = ToQdpOption(td1, underlying);
|
||||
if (option1 != null)
|
||||
{
|
||||
options.Add(option1);
|
||||
}
|
||||
}
|
||||
|
||||
var td2 = tradeclone.Clone();
|
||||
if (trade.trade_risky_option.ParticipationRate2 != 0)
|
||||
{
|
||||
td2.Strike = trade.trade_risky_option.Strike2;
|
||||
td2.ParticipationRate = trade.trade_risky_option.ParticipationRate2;
|
||||
td2.TradeAmount = TradeCalcHelper.GetTradeAmount(td2, td2.TradeAmount, 1);
|
||||
td2.Notional = TradeCalcHelper.GetTradeAmount(td2, td2.Notional, underlying.CountRatio);
|
||||
|
||||
var option2 = ToQdpOption(td2, underlying);
|
||||
if (option2 != null)
|
||||
{
|
||||
options.Add(option2);
|
||||
}
|
||||
}
|
||||
|
||||
var td3 = tradeclone.Clone();
|
||||
//decimal 为了解决精度问题: 0.2-0.3=0.0999999999
|
||||
var participationRate3 = (decimal)trade.trade_risky_option.ParticipationRate3 - (decimal)trade.trade_risky_option.ParticipationRate2;
|
||||
if (participationRate3 != 0)
|
||||
{
|
||||
td3.Strike = trade.trade_risky_option.Strike3;
|
||||
td3.ParticipationRate = (double?)Math.Abs(participationRate3);
|
||||
td3.TradeAmount = TradeCalcHelper.GetTradeAmount(td3, td3.TradeAmount, 1);
|
||||
td3.Notional = TradeCalcHelper.GetTradeAmount(td3, td3.Notional, underlying.CountRatio);
|
||||
if (participationRate3 < 0)
|
||||
{
|
||||
td3.BuySell = trade.BuySell == "买入" ? "卖出" : "买入";
|
||||
}
|
||||
|
||||
var option3 = ToQdpOption(td3, underlying);
|
||||
if (option3 != null)
|
||||
{
|
||||
options.Add(option3);
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
public class CurvePoint
|
||||
{
|
||||
public double X { get; set; }
|
||||
public double Y { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,454 @@
|
||||
using Qdp.Pricing.Base.Implementations;
|
||||
using Qdp.Pricing.Library.Equity.Engines.Analytical;
|
||||
using YLErp.Abstract;
|
||||
using YLErp.Commons;
|
||||
using YLErp.Enums;
|
||||
using YLErp.Modules.CalculationModule.Abstract;
|
||||
using YLErp.Modules.VolatilityModule;
|
||||
using YLErp.QdpModule;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 期权估值计算上下文
|
||||
/// </summary>
|
||||
public class OptionValueCalcContext : IOtcTradeValueCalcContext
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected OptionValueCalcContext(DateTime valueDate, IOtcTradeValueCalcContext baseContext)
|
||||
{
|
||||
if (baseContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(baseContext));
|
||||
}
|
||||
|
||||
ValueDate = valueDate;
|
||||
VolType = baseContext.VolType;
|
||||
IsEodCalc = baseContext.IsEodCalc;
|
||||
DataProvider = baseContext.DataProvider;
|
||||
AddingVolRate = baseContext.AddingVolRate;
|
||||
ErrorHandler = baseContext.ErrorHandler;
|
||||
SysRiskFreeRate = baseContext.SysRiskFreeRate;
|
||||
CalcScenario = baseContext.CalcScenario;
|
||||
UserGroup = baseContext.UserGroup;
|
||||
CalcDeltaT1 = baseContext.CalcDeltaT1;
|
||||
|
||||
MarketProxy = new MarketProxy(valueDate, SysRiskFreeRate)
|
||||
{
|
||||
Trace = baseContext.Trace
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public OptionValueCalcContext(string volType, bool isEodCalc, DateTime valueDate, double sysRiskFreeRate, IOptionCalcDataProvider dataProvider)
|
||||
{
|
||||
DataProvider = CalcCheckHelper.CheckOptionCalcDataProvider(dataProvider);
|
||||
|
||||
VolType = volType;
|
||||
IsEodCalc = isEodCalc;
|
||||
ValueDate = valueDate;
|
||||
SysRiskFreeRate = sysRiskFreeRate;
|
||||
|
||||
MarketProxy = new MarketProxy(valueDate, sysRiskFreeRate);
|
||||
}
|
||||
|
||||
#region----属性定义----
|
||||
|
||||
/// <summary>
|
||||
/// 计算场景枚举
|
||||
/// </summary>
|
||||
public CalcScenarioEnum CalcScenario { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 波动率类型(默认:'对冲')
|
||||
/// </summary>
|
||||
public string VolType { get; } = "对冲";
|
||||
|
||||
public bool IsEodCalc { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 波动率用户组
|
||||
/// </summary>
|
||||
public string UserGroup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否精确时间模式
|
||||
/// </summary>
|
||||
public virtual bool IsPreciseTimeMode => CalcScenario != CalcScenarioEnum.EodSettlement;
|
||||
|
||||
/// <summary>
|
||||
/// 波动率加点值
|
||||
/// </summary>
|
||||
public double AddingVolRate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否计算T+1日Delta
|
||||
/// </summary>
|
||||
public bool CalcDeltaT1 { get; set; }
|
||||
|
||||
//--------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// 估值日
|
||||
/// </summary>
|
||||
public DateTime ValueDate { get; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public MarketProxy MarketProxy { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 期权计算数据提供接口
|
||||
/// </summary>
|
||||
public IOptionCalcDataProvider DataProvider { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误处理接口
|
||||
/// </summary>
|
||||
public IErrorHandler ErrorHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 系统全局无风险利率
|
||||
/// </summary>
|
||||
public double SysRiskFreeRate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 用于计算跟踪
|
||||
/// </summary>
|
||||
public TraceWrap Trace { get => MarketProxy.Trace; set => MarketProxy.Trace = value; }
|
||||
|
||||
public string SkipTradeTypes { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region----方法定义----
|
||||
|
||||
/// <summary>
|
||||
/// 获取期权计算模式
|
||||
/// </summary>
|
||||
public virtual PricingRequest GetPricingRequest(OtcTradeBase trade)
|
||||
{
|
||||
if (trade is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(trade));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(SkipTradeTypes) && SkipTradeTypes.Contains(trade.TradeType))
|
||||
{
|
||||
return PricingRequest.None;
|
||||
}
|
||||
|
||||
if (CalcScenario == CalcScenarioEnum.RealtimeRisk
|
||||
&& !PS.Config.ErpElement.CalcAutocallGreeksInRisk
|
||||
&& (trade.TradeType == "凤凰期权" || trade.TradeType == "雪球期权"))
|
||||
{
|
||||
return QdpPricingRequest.BASIC_PRICING;
|
||||
}
|
||||
|
||||
var pr = QdpPricingRequest.BASIC_GREEKS | PricingRequest.TimeValue;
|
||||
|
||||
if (trade.TradeType == "亚式期权")
|
||||
{
|
||||
pr |= PricingRequest.SA_Delta;
|
||||
}
|
||||
|
||||
//20220118:上期做了定制处理,可以返回DDeltaDt指标值
|
||||
return PS.Config.Company != Configuration.CompanyEnum.上期资本 ? pr : pr | PricingRequest.DDeltaDt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public double GetCorrelation(int underlyingId1, int underlyingId2)
|
||||
{
|
||||
var correlation = DataProvider.UnderlyingDataProvider.GetCorrelation(underlyingId1, underlyingId2);
|
||||
return correlation == null || correlation.Correlation == null ? 0.0 : correlation.Correlation.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据交易品种ID获取此交易品种是否存在夜盘
|
||||
/// </summary>
|
||||
public bool HasNightMarket(int varietyId)
|
||||
{
|
||||
var variety = DataProvider.UnderlyingDataProvider.GetVariety(varietyId);
|
||||
if (variety != null)
|
||||
{
|
||||
return variety.HasNightMarket;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#region----波动率----
|
||||
|
||||
//TODO:PrepareVolatility欠缺多标的交易的波动率处理
|
||||
|
||||
/// <summary>
|
||||
/// 准备波动率(仅适用于场外期权和场内期权交易)
|
||||
/// </summary>
|
||||
public bool PrepareVolatility(string qdpTradeId, OtcTradeBase tradeObj, double spotPrice, out string[] volsurfaceNames)
|
||||
{
|
||||
volsurfaceNames = new[] { qdpTradeId };
|
||||
|
||||
if (tradeObj.TradeType == "场内期权")
|
||||
{
|
||||
bool prepareExOptionSavedVol()
|
||||
{
|
||||
var vol = DataProvider.VolatilityDataProvider.GetExOptionSavedVol(tradeObj.ExchangeOptionCode, ValueDate);
|
||||
|
||||
if (vol != null)
|
||||
{
|
||||
var volitality = QdpVolHelper.GenerateFlatSurface(vol.Value);
|
||||
MarketProxy.SetVolSurface(qdpTradeId, volitality, AddingVolRate);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (PS.Config.Company == Configuration.CompanyEnum.光大光子)
|
||||
{
|
||||
return prepareExOptionSavedVol();
|
||||
}
|
||||
|
||||
if (VolType != "交易曲面") //交易曲面时获取交易Mid波动率
|
||||
{
|
||||
if (prepareExOptionSavedVol())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (PS.Config.ErpElement.ExchangeOptionVolType == Configuration.Enums.ExchangeOptionVolType.ImpliedVol)
|
||||
{
|
||||
double volValue = tradeObj.Vol ?? 0;
|
||||
if (!tradeObj.Vol.IsNormalize())
|
||||
{
|
||||
if (!tradeObj.TradeSinglePrice.HasValue)
|
||||
{
|
||||
throw new ServiceException($"计算场内期权隐含波动率失败,期权代码:{tradeObj.ExchangeOptionCode},错误信息:期权价格未获取到");
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
volValue = VolatilityHelper.GetImpliedVol(ValueDate, tradeObj, null, spotPrice, IsEodCalc);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ServiceException($"计算场内期权隐含波动率失败,期权代码:{tradeObj.ExchangeOptionCode},期权价格:{tradeObj.TradeSinglePrice:0.####},错误信息:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
MarketProxy.SetVolSurface(qdpTradeId, volValue);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else //场外期权
|
||||
{
|
||||
if (VolType == "对冲")
|
||||
{
|
||||
var vol = DataProvider.VolatilityDataProvider.GetOtcHedgingVol(tradeObj.id, ValueDate) ?? tradeObj.TradeSavedVol;
|
||||
if (vol != null)
|
||||
{
|
||||
var volitality = QdpVolHelper.GenerateFlatSurface(vol.Value);
|
||||
if (volitality == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
MarketProxy.SetVolSurface(qdpTradeId, volitality, AddingVolRate);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//只有在tradeVol模式下才应从开平仓波动率中插值,否则应去曲面上插值
|
||||
if (VolType == "持仓" && PS.Config.IsTradeVol)
|
||||
{
|
||||
//场外期权交易使用tradingVol
|
||||
var volitality = GetTradingVolatility(tradeObj, false);
|
||||
if (volitality == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
MarketProxy.SetVolSurface(qdpTradeId, volitality, AddingVolRate);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//设置标的波动率
|
||||
var volitalityU = GetUnderlyingVolatility(tradeObj, VolType, tradeObj.UnderlyingCode, spotPrice);
|
||||
if (volitalityU == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
MarketProxy.SetVolSurface(qdpTradeId, volitalityU, AddingVolRate);
|
||||
return true;
|
||||
}
|
||||
|
||||
//获取交易波动率(如果是结算,先查找结算波动率)
|
||||
private IVolatility GetTradingVolatility(OtcTradeBase tradeObj, bool isEod)
|
||||
{
|
||||
double? vol = null;
|
||||
|
||||
if (isEod)
|
||||
{
|
||||
vol = DataProvider.VolatilityDataProvider.GetOtcEodOverrideVol(tradeObj.id, ValueDate);
|
||||
}
|
||||
|
||||
if (vol == null)
|
||||
{
|
||||
var tradeVol = DataProvider.VolatilityDataProvider.GetOtcPositionVol(tradeObj.id, ValueDate);
|
||||
var daycountMode = PS.Config.ErpElement.SmoothingDaycountMode == Configuration.Enums.SmoothingDaycountMode.CalendarDay
|
||||
? Qdp.Pricing.Base.Enums.DayCountMode.CalendarDay
|
||||
: Qdp.Pricing.Base.Enums.DayCountMode.TradingDay;
|
||||
if (tradeVol == null)
|
||||
{
|
||||
vol = AnalyticalOptionTradeVolInterp.tradeVolLinearInterp(
|
||||
new Qdp.Foundation.Implementations.Date(ValueDate),
|
||||
tradeObj.TradeOpenVolatility ?? 0,
|
||||
tradeObj.TradeCloseVolatility ?? 0,
|
||||
new Qdp.Foundation.Implementations.Date(tradeObj.StartDate.Value),
|
||||
new Qdp.Foundation.Implementations.Date(tradeObj.ExerciseDate.Value),
|
||||
tradeObj.NumOfSmoothingDays ?? 0,
|
||||
daycountMode,
|
||||
CalendarImpl.Get("chn"));
|
||||
}
|
||||
else
|
||||
{
|
||||
//新增交易当天的持仓波动率需要划掉一天,修改后的持仓波动率不需要再划一天
|
||||
vol = AnalyticalOptionTradeVolInterp.tradeVolLinearInterp(
|
||||
new Qdp.Foundation.Implementations.Date(ValueDate),
|
||||
tradeVol.OpenVol,
|
||||
tradeVol.CloseVol,
|
||||
new Qdp.Foundation.Implementations.Date(tradeVol.ValueDate),
|
||||
new Qdp.Foundation.Implementations.Date(tradeObj.ExerciseDate.Value),
|
||||
tradeVol.SmoothingDays,
|
||||
daycountMode,
|
||||
CalendarImpl.Get("chn"), tradeVol.IsFirst);
|
||||
}
|
||||
}
|
||||
|
||||
return QdpVolHelper.GenerateFlatSurface(OtcFormatHelper.FormatValue(vol.Value, PS.Config.ErpElement.VolMoreAccurate ? 6 : 4));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取vol 没有则按照默认值新增
|
||||
/// </summary>
|
||||
private IVolatility GetUnderlyingVolatility(OtcTradeBase tradeObj, string voltype, string underlyingCode, double spotPrice, string volmode = "MoneynessVol")
|
||||
{
|
||||
voltype = VolatilityHelper.GetUnderlyingVolType(voltype);
|
||||
|
||||
var vol = DataProvider.VolatilityDataProvider.GetUnderlyingVol(PS.Config.Is润和 && !IsEodCalc ? DateTime.Today : ValueDate, voltype, underlyingCode, UserGroup);
|
||||
|
||||
if (vol != null)
|
||||
{
|
||||
var constVol = VolatilityHelper.GetInterpolatedVol(
|
||||
volConstructionType: PS.Config.ErpElement.SkewMapVolConstruction ? VolConstructionType.SkewMap : VolConstructionType.Normal,
|
||||
volSurface: vol,
|
||||
valueDate: PS.Config.Is润和 && !IsEodCalc ? DateTime.Today : ValueDate,
|
||||
underlyingCode: underlyingCode,
|
||||
exerciseDate: tradeObj.ExerciseDate.Value,
|
||||
strike: tradeObj.Strike ?? 0,
|
||||
isBuy: tradeObj.BuySell == "买入",
|
||||
isCall: ConsGlobal.CallPut.IsCall(tradeObj.CallPut),
|
||||
spotPrice: spotPrice,
|
||||
isMoneynessOption: tradeObj.IsMoneynessOption == "是",
|
||||
isEodCalc: IsEodCalc);
|
||||
|
||||
return QdpVolHelper.GenerateFlatSurface(constVol);
|
||||
}
|
||||
|
||||
var defVol = VolatilityHelper.GetDefaultVol(new SingleVolatilityRequest
|
||||
{
|
||||
QuotationDate = ValueDate,
|
||||
UnderlyingCode = underlyingCode,
|
||||
VolType = voltype,
|
||||
TradeVolWithBidAsk = false,
|
||||
UserGroup = UserGroup
|
||||
});
|
||||
defVol.VolSurfaceMode = volmode;
|
||||
return defVol;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取交易的无风险利率
|
||||
/// </summary>
|
||||
public virtual double GetRiskFreeRate(OtcTradeBase trade)
|
||||
{
|
||||
return trade.NoRiskRate ?? SysRiskFreeRate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取交易的分红率
|
||||
/// </summary>
|
||||
public virtual double GetDividendRate(OtcTradeBase trade)
|
||||
{
|
||||
if (trade.TradeType == "场内期权" && !string.IsNullOrWhiteSpace(trade.ExchangeOptionCode))
|
||||
{
|
||||
var exOption = DataProvider.UnderlyingDataProvider.GetExchange_List_Option(trade.ExchangeOptionCode);
|
||||
var underlying = DataProvider.UnderlyingDataProvider.GetUnderlying(exOption?.UnderlyingCode);
|
||||
return underlying?.DividendRate ?? SysRiskFreeRate;
|
||||
}
|
||||
|
||||
return trade.DividendRate ?? GetRiskFreeRate(trade);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
bool _disposed;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
MarketProxy.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public virtual string GetFixingString(OtcTradeBase trade, trade_asian_option asianOption, double spotPrice)
|
||||
{
|
||||
var fixing = AsianOptionFixingService.GetFixingString(ValueDate, trade, asianOption);
|
||||
|
||||
if (CalcScenario == CalcScenarioEnum.RealtimeRisk && IsPreciseTimeMode)
|
||||
{
|
||||
//修复实时计算中fixing最后一天的价格不是实时价格
|
||||
fixing = FixingService.AddOrReplaceLastDateSpotPrice(fixing, ValueDate, spotPrice);
|
||||
}
|
||||
|
||||
if (PS.Config.Is润和 && DateTime.Now.Hour < 15)
|
||||
{
|
||||
var index = fixing.IndexOf(ValueDate.ToString("yyyy-MM-dd"));
|
||||
if (index >= 0)
|
||||
{
|
||||
fixing = fixing.Remove(index).TrimEnd(';');
|
||||
}
|
||||
}
|
||||
|
||||
return fixing;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{ValueDate:yyyy-MM-dd}--{VolType}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
using Qdp.Foundation.Implementations;
|
||||
using Qdp.Pricing.Base.Implementations;
|
||||
using YLErp.Enums;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class OptionValueRequestBase
|
||||
{
|
||||
public OptionValueRequestBase(double sysRiskFreeRate)
|
||||
{
|
||||
this.sysRiskFreeRate = sysRiskFreeRate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 系统无风险利率
|
||||
/// </summary>
|
||||
public double sysRiskFreeRate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否商品期货使用精确时间模式
|
||||
/// </summary>
|
||||
public bool preciseTimeMode { get; set; }
|
||||
|
||||
public bool isEodCalc { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 到期日偏移量
|
||||
/// </summary>
|
||||
public int maturityShift { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 覆写OptionTradeParamBase的字段值
|
||||
/// </summary>
|
||||
public Action<OptionTradeParamBase> ParamOverride { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 期权交易构建参数
|
||||
/// </summary>
|
||||
public class OptionTradeParamRequest : OptionValueRequestBase
|
||||
{
|
||||
public OptionTradeParamRequest(double sysRiskFreeRate) : base(sysRiskFreeRate)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [可选]交易ID
|
||||
/// </summary>
|
||||
public string tradeId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否有夜盘交易
|
||||
/// </summary>
|
||||
public bool hasNightMarket { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// [可选]TTMDays
|
||||
/// </summary>
|
||||
public double timeToMaturityDays { get; set; } = double.NaN;
|
||||
|
||||
/// <summary>
|
||||
/// 波动率曲面名称(在market中添加的波动率曲面名称)
|
||||
/// </summary>
|
||||
public string[] volSurfaceNames { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用于亚式期权|区间累积
|
||||
/// </summary>
|
||||
public string fixings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// dividends
|
||||
/// </summary>
|
||||
public Dictionary<Date, double> dividends { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 已发生的观察日结算数据
|
||||
/// 累计期权--计算payoff时使用
|
||||
/// </summary>
|
||||
public List<autocall_observation> happenedObservations { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 期权计算请求参数
|
||||
/// </summary>
|
||||
public class OptionValueCalcRequest : OptionValueRequestBase
|
||||
{
|
||||
public OptionValueCalcRequest(double sysRiskFreeRate) : base(sysRiskFreeRate)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [必需]波动率
|
||||
/// </summary>
|
||||
public double[] vols { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// [必需]标的现价
|
||||
/// </summary>
|
||||
public double[] spotPrices { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联性
|
||||
/// </summary>
|
||||
public double[] correlations { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 计算枚举
|
||||
/// </summary>
|
||||
public PricingRequest pricingRequest { get; set; } = QdpModule.QdpPricingRequest.BASIC_GREEKS;
|
||||
|
||||
/// <summary>
|
||||
/// 引擎名称
|
||||
/// </summary>
|
||||
public string engineName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 快速模式,默认false
|
||||
/// </summary>
|
||||
public bool quadratureFastMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// [非必需]期权计算场景
|
||||
/// </summary>
|
||||
public CalcScenarioEnum calcScenario { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否计算T+1日的Delta
|
||||
/// </summary>
|
||||
public bool calcDeltaT1 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用于亚式期权|区间累积
|
||||
/// 如果为null时需要计算时现取
|
||||
/// </summary>
|
||||
public string fixings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// TTMDays
|
||||
/// </summary>
|
||||
public double? timeToMaturityDays { get; set; }
|
||||
|
||||
public OptionValueCalcRequest Clone()
|
||||
{
|
||||
return (OptionValueCalcRequest)MemberwiseClone();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,670 @@
|
||||
using Qdp.Foundation.Implementations;
|
||||
using YLErp.Abstract.DataProviders;
|
||||
using YLErp.BLL;
|
||||
using YLErp.Commons;
|
||||
using YLErp.DBModels.Enums;
|
||||
using YLErp.DBModels;
|
||||
using YLErp.Modules.DataProviderModule;
|
||||
using YLErp.Modules.EodModule;
|
||||
using YLErp.QdpModule;
|
||||
using NPOI.SS.Formula.Functions;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 收益互换计算服务
|
||||
/// </summary>
|
||||
public class PayoffSwapCalcService
|
||||
{
|
||||
/// <summary>
|
||||
/// 因为互换涉及到多标的所以需要确保所有标的都可以找到价格
|
||||
/// </summary>
|
||||
private static IAggregatePriceProvider GetAutoPriceProvider(DateTime valueDate, IPriceProvider priceProvider, bool isEodSettlement)
|
||||
{
|
||||
if (priceProvider is IAggregatePriceProvider)
|
||||
{
|
||||
return (IAggregatePriceProvider)priceProvider;
|
||||
}
|
||||
|
||||
if (isEodSettlement)
|
||||
{
|
||||
return new AggregatePriceProvider(priceProvider,
|
||||
new EodPriceProvider(valueDate).GetPriceProvider(priceProvider is IEodPriceProviderWrap wrap ? wrap.SettlementType : SettlementTypeEnum.ClosePrice));
|
||||
}
|
||||
|
||||
return new AggregatePriceProvider(priceProvider, DataCacheProvider.GetUnderlyingDataSource());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static TradeValueResult CalcValue(int tradeId, DateTime valueDate, IPriceProvider priceProvider, bool isEodSettlement)
|
||||
{
|
||||
using (var db = DbContextFactory.GetYLDbContext())
|
||||
{
|
||||
var trade = db.trade.Find(tradeId);
|
||||
if (trade == null)
|
||||
{
|
||||
return new TradeValueResult(false)
|
||||
{
|
||||
TradeId = tradeId,
|
||||
FailReason = TradeValueFailReason.missingTrade,
|
||||
ErrorMessage = "[收益互换]没有找到交易数据,tradeId:" + tradeId
|
||||
};
|
||||
}
|
||||
return CalcValue(trade, valueDate, priceProvider, isEodSettlement);
|
||||
}
|
||||
}
|
||||
|
||||
public static TradeValueResult CalcValue(OtcTradeBase trade, DateTime valueDate, IPriceProvider priceProvider, bool isEodSettlement)
|
||||
{
|
||||
return CalcValueSingle(trade, valueDate, priceProvider, isEodSettlement);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 互换收益PV计算 普通
|
||||
/// </summary>
|
||||
public static TradeValueResult CalcValueSingle(OtcTradeBase trade, DateTime valueDate, IPriceProvider priceProvider, bool isEodSettlement, double spotPrice = double.NaN)
|
||||
{
|
||||
if (trade is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(trade));
|
||||
}
|
||||
|
||||
if(double.IsNaN(spotPrice))
|
||||
{
|
||||
//因为互换涉及到多标的所以需要确保所有标的都可以找到价格
|
||||
if (trade.StructureType== "多空组合")
|
||||
{
|
||||
spotPrice = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
priceProvider = GetAutoPriceProvider(valueDate, priceProvider, isEodSettlement);
|
||||
if (!priceProvider.TryGetPrice(trade.UnderlyingCode, out spotPrice))
|
||||
{
|
||||
spotPrice = DataCacheProvider.GetUnderlyingDataSource().GetPrice(trade.UnderlyingCode);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
using (var db = DbContextFactory.GetYLDbContext())
|
||||
{
|
||||
var client = DataCacheProvider.GetClientDataSource().GetData(trade.ClientId);
|
||||
var rate = new EodCurrencyRateService(OptUserInfo.SystemUser).GetCurrencyRate(trade.QuoteCurrency, trade.SettlementCurrency, valueDate, seekPreday: !isEodSettlement);
|
||||
var rateTradeDate = new EodCurrencyRateService(OptUserInfo.SystemUser).GetCurrencyRate(trade.QuoteCurrency, trade.SettlementCurrency, trade.TradeDate.Value, seekPreday: !isEodSettlement);
|
||||
var lastEodSwap = GetEodSwapData(trade, db);
|
||||
var pv = lastEodSwap.PostionValue;
|
||||
var clientCashOut= db.ClientCashInCashOut.FirstOrDefault(x=>x.Action== "系统操作-期权费"&&x.TradeId== trade.id&&x.HappenDate<= valueDate) ;
|
||||
var credit = db.credit.FirstOrDefault(x => x.CreditStartDate <= valueDate && x.CreditDeadLine >= valueDate && x.ClientId == trade.ClientId && x.ProcessStatus == "已审批");
|
||||
var hasCredit = credit != null && credit.PFECredit > 0;
|
||||
|
||||
client_variety_marginrate clientVarietyMarginRate = new client_variety_marginrate
|
||||
{
|
||||
ClientId = trade.ClientId,
|
||||
ClientName = trade.ClientName,
|
||||
HighMarginRate = 1,
|
||||
LowMarginRate = 1,
|
||||
ValueDate = valueDate,
|
||||
};
|
||||
var OptionValue = new TradeValueResult
|
||||
{
|
||||
TradeId = trade.id,
|
||||
Pv =Convert.ToDouble(pv),
|
||||
ExtendInfo = new TradeValueResultExtend()
|
||||
{
|
||||
QuoteFloatingWinLoss =Convert.ToDouble(lastEodSwap.FloatingPnL),
|
||||
FloatingWinLoss = Convert.ToDouble(lastEodSwap.FloatingPnL) * rate,
|
||||
QuoteCommission = clientCashOut?.Money??0,
|
||||
Commission = (clientCashOut?.Money ?? 0) * rate,
|
||||
QuoteAnnualFee = 0,
|
||||
AnnualFee = 0,
|
||||
QuotePv = Convert.ToDouble(pv),
|
||||
QuoteIM = client.BoundSide == BoundSideEnum.南向 ? (trade.Notional * spotPrice * clientVarietyMarginRate.LowMarginRate) : (trade.StockEqvNotional * (hasCredit ? clientVarietyMarginRate.LowMarginRate : clientVarietyMarginRate.HighMarginRate)),
|
||||
IM = (client.BoundSide == BoundSideEnum.南向 ? (trade.Notional * spotPrice * clientVarietyMarginRate.LowMarginRate) : (trade.StockEqvNotional * (hasCredit ? clientVarietyMarginRate.LowMarginRate : clientVarietyMarginRate.HighMarginRate))) * rate,
|
||||
QuotePFE = client.BoundSide == BoundSideEnum.南向 ? trade.Notional * spotPrice * clientVarietyMarginRate.HighMarginRate : 0,
|
||||
PFE = client.BoundSide == BoundSideEnum.南向 ? trade.Notional * spotPrice * clientVarietyMarginRate.HighMarginRate * rate : 0
|
||||
},
|
||||
RoundedPv = Convert.ToDouble(pv),
|
||||
Delta = (lastEodSwap.MarketValueLong>0 ? 1 : -1) * trade.Notional,
|
||||
Gamma = 0,
|
||||
Vega = 0,
|
||||
TradingDayTheta = 0,
|
||||
CalendarDayTheta = 0,
|
||||
Rho = 0,
|
||||
DeltaCash = (lastEodSwap.MarketValueLong > 0 ? spotPrice : -spotPrice) * trade.Notional,
|
||||
GammaCash = 0,
|
||||
SpotPrice= spotPrice,
|
||||
DV01= Convert.ToDouble(lastEodSwap.DV01)
|
||||
};
|
||||
|
||||
return OptionValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建eodswap
|
||||
/// </summary>
|
||||
/// <param name="tradeId"></param>
|
||||
/// <param name="db"></param>
|
||||
/// <returns></returns>
|
||||
private static eod_swap GetEodSwapData(OtcTradeBase trade, YLContext db)
|
||||
{
|
||||
var eodSwap=new eod_swap();
|
||||
var positions = db.swap_position.Where(x=>x.PosiQuantity>0&&!x.IsInitial&&x.SwapTradeId== trade.id).ToList();
|
||||
eodSwap.SwapTradeId = trade.id;
|
||||
eodSwap.NotionalValueLong = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Long).Sum(s => s.PosiNotionalValue);
|
||||
eodSwap.NotionalValueShort = positions.Where(x => x.PositionType == (int)PositionTypeFlag.Short).Sum(s => s.PosiNotionalValue);
|
||||
eodSwap.NotionalValue = eodSwap.NotionalValueLong + eodSwap.NotionalValueShort;
|
||||
eodSwap.DV01 = 0;
|
||||
foreach (var item in positions)
|
||||
{
|
||||
decimal shortRatio = item.PositionType == (int)PositionTypeFlag.Long ? 1 : -1;//多空方向
|
||||
int directionRatio = item.PosiDirection == (int)SwapDirectionEnum.收取 ? 1 : -1;
|
||||
var pv= item.PosiQuantity * shortRatio * item.ContractSize;
|
||||
var pvNoPrice = item.PosiQuantity * item.ContractSize;
|
||||
decimal vobp = 0;
|
||||
var data = DataCacheProvider.GetUnderlyingDataSource().GetData(item.UnderlyingCode);
|
||||
if (data != null)
|
||||
{
|
||||
if (data.IsBond())
|
||||
{
|
||||
var dealDate = QdpCalendarHelper.GetNonHolidayDefore(valuedateBLL.ValueDate.AddDays(-1));
|
||||
var bondPrice = EodPriceQueryService.GetBondPrice(dealDate, data.UnderlyingCode);
|
||||
vobp = bondPrice.Vobp ?? 0;
|
||||
var price = Convert.ToDecimal(bondPrice.ClosePrice);
|
||||
eodSwap.FloatingPnL = (price - item.PosiNetPrice) * item.PosiQuantity * item.ContractSize * shortRatio * directionRatio;
|
||||
}
|
||||
}
|
||||
if (shortRatio>0)
|
||||
{
|
||||
eodSwap.MarketValueLong += pv;
|
||||
}
|
||||
else
|
||||
{
|
||||
eodSwap.MarketValueShort += pv;
|
||||
}
|
||||
eodSwap.PostionValue += pv;
|
||||
eodSwap.DV01+= pvNoPrice * vobp * shortRatio* directionRatio * 0.01m;
|
||||
}
|
||||
|
||||
return eodSwap;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取互换固定收益PV
|
||||
/// </summary>
|
||||
/// <param name="trades"></param>
|
||||
/// <param name="valueDate"></param>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<int, double> GetFixedInterestRatePV(List<int> tradeIds, DateTime valueDate)
|
||||
{
|
||||
var result = new Dictionary<int, double>();
|
||||
if (tradeIds == null || tradeIds.Count == 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
List<trade_swap> tradeSwapList = null;
|
||||
List<trade_cash> tradeCashList = null;
|
||||
List<trade_cash_swap> tradeCashSwapList = null;
|
||||
List<trade> tradeList = null;
|
||||
List<eod_trade> eodTradeList = null;
|
||||
using (var db = DbContextFactory.GetYLDbContext())
|
||||
{
|
||||
//tradeList = db.trade.AsNoTracking().Where(p => tradeIds.Contains(p.id)).ToList();
|
||||
eodTradeList = db.eod_trade.AsNoTracking().Where(p => p.ValueDate == valueDate && tradeIds.Contains(p.TradeId)).ToList();
|
||||
tradeSwapList = db.trade_swap.AsNoTracking().Where(p => tradeIds.Contains(p.TradeId)).ToList();
|
||||
tradeCashList = db.trade_cash.AsNoTracking().Where(y => tradeIds.Contains(y.TradeId) && y.Action == "系统操作-互换" && y.ValidState != "InValid" && !y.IsDeleted && y.ValueDate <= valueDate).ToList();
|
||||
tradeCashSwapList = db.trade_cash_swap.AsNoTracking().Where(p => tradeIds.Contains(p.TradeId)).ToList();
|
||||
}
|
||||
|
||||
if (tradeSwapList == null)
|
||||
{
|
||||
tradeSwapList = new List<trade_swap>();
|
||||
}
|
||||
if (tradeCashList == null)
|
||||
{
|
||||
tradeCashList = new List<trade_cash>();
|
||||
}
|
||||
if (tradeCashSwapList == null)
|
||||
{
|
||||
tradeCashSwapList = new List<trade_cash_swap>();
|
||||
}
|
||||
if (eodTradeList != null && eodTradeList.Count > 0)
|
||||
{
|
||||
tradeList = eodTradeList.Select(p => p.trade).ToList();
|
||||
}
|
||||
if (tradeList == null)
|
||||
{
|
||||
tradeList = new List<trade>();
|
||||
}
|
||||
if (tradeList.Count > 0)
|
||||
{
|
||||
foreach (var trade in tradeList)
|
||||
{
|
||||
var tradeSwap = tradeSwapList.FirstOrDefault(d => d.TradeId == trade.id);
|
||||
if (tradeSwap == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var tradeCashs = tradeCashList.Where(y => y.TradeId == trade.id && y.Action == "系统操作-互换" && y.ValidState != "InValid" && !y.IsDeleted && y.ValueDate <= valueDate);
|
||||
var tradeCashIds = tradeCashs.Select(x => x.id);
|
||||
var tradeCash = tradeCashs.OrderByDescending(y => y.id).FirstOrDefault();
|
||||
var cashSwaps = tradeCashSwapList.Where(x => x.TradeId == trade.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;
|
||||
DateTime endDate;
|
||||
double fixAmount = 0;
|
||||
if (!tradeSwap.IsGetFloatingProfit)
|
||||
{
|
||||
var preSwapDate = GetSwapRateStartDate(trade, tradeSwap, valueDate, tradeCash, lastManualCash, tradeSwap.IsGetFloatingProfit, out endDate);
|
||||
var extraAmountGet = GetExtraAmountBySwapRate(trade.ClientId, trade.TradeDate, tradeSwap.GetSwapTimeAndRate, preSwapDate, endDate, tradeSwap.AnnualDays ?? 0, trade.StockEqvNotional);
|
||||
fixAmount += extraAmountGet;
|
||||
}
|
||||
if (!tradeSwap.IsPayFloatingProfit)
|
||||
{
|
||||
var preSwapDate = GetSwapRateStartDate(trade, tradeSwap, valueDate, tradeCash, lastManualCash, tradeSwap.IsPayFloatingProfit, out endDate);
|
||||
var extraAmountPay = GetExtraAmountBySwapRate(trade.ClientId, trade.TradeDate, tradeSwap.PaySwapTimeAndRate, preSwapDate, endDate, tradeSwap.AnnualDays ?? 0, trade.StockEqvNotional);
|
||||
fixAmount -= extraAmountPay;
|
||||
}
|
||||
result.Add(trade.id, fixAmount);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 多空组合PV计算 子交易计算模式
|
||||
/// </summary>
|
||||
public static TradeValueResult CalcValue(OtcTradeBase trade, trade_swap trade_swap, List<trade_swap_detail> trade_swap_details, DateTime valueDate, IPriceProvider priceProvider, bool isEodSettlement)
|
||||
{
|
||||
if (trade_swap.SwapType == "多空组合")
|
||||
{
|
||||
using (var db = DbContextFactory.GetYLDbContext())
|
||||
{
|
||||
var tradeIds = trade_swap_details.Select(t => t.ChildTradeId).ToList();
|
||||
var subTradeList = db.trade.Where(t => tradeIds.Contains(t.id)).ToList();
|
||||
var resultList = new List<TradeValueResult>();
|
||||
foreach (var subTrade in subTradeList)
|
||||
{
|
||||
var price = DataCacheProvider.GetUnderlyingDataSource().GetPrice(subTrade.UnderlyingCode);
|
||||
var subResult = CalcValue(subTrade, valueDate, priceProvider, isEodSettlement);
|
||||
resultList.Add(subResult);
|
||||
}
|
||||
|
||||
var OptionValue = new TradeValueResult
|
||||
{
|
||||
TradeId = trade.id,
|
||||
Pv = 0,
|
||||
ExtendInfo = new TradeValueResultExtend()
|
||||
{
|
||||
QuoteFloatingWinLoss = 0,
|
||||
FloatingWinLoss = 0,
|
||||
QuoteCommission = 0,
|
||||
Commission = 0,
|
||||
QuoteAnnualFee = 0,
|
||||
AnnualFee = 0,
|
||||
QuotePv = 0,
|
||||
QuoteIM = 0,
|
||||
IM = 0,
|
||||
QuotePFE = 0,
|
||||
PFE = 0
|
||||
},
|
||||
RoundedPv = 0,
|
||||
Delta = 0,
|
||||
Gamma = 0,
|
||||
Vega = 0,
|
||||
TradingDayTheta = 0,
|
||||
CalendarDayTheta = 0,
|
||||
Rho = 0,
|
||||
DeltaCash = 0,
|
||||
GammaCash = 0
|
||||
};
|
||||
|
||||
foreach (var valueResult in resultList)
|
||||
{
|
||||
OptionValue.Pv += valueResult.Pv;
|
||||
OptionValue.ExtendInfo.QuoteFloatingWinLoss += valueResult.ExtendInfo.QuoteFloatingWinLoss;
|
||||
OptionValue.ExtendInfo.FloatingWinLoss += valueResult.ExtendInfo.FloatingWinLoss;
|
||||
OptionValue.ExtendInfo.QuoteCommission += valueResult.ExtendInfo.QuoteCommission;
|
||||
OptionValue.ExtendInfo.Commission += valueResult.ExtendInfo.Commission;
|
||||
OptionValue.ExtendInfo.QuoteAnnualFee += valueResult.ExtendInfo.QuoteAnnualFee;
|
||||
OptionValue.ExtendInfo.AnnualFee += valueResult.ExtendInfo.AnnualFee;
|
||||
OptionValue.ExtendInfo.QuotePv += valueResult.ExtendInfo.QuotePv;
|
||||
OptionValue.ExtendInfo.QuoteIM += valueResult.ExtendInfo.QuoteIM;
|
||||
OptionValue.ExtendInfo.IM += valueResult.ExtendInfo.IM;
|
||||
OptionValue.ExtendInfo.QuotePFE += valueResult.ExtendInfo.QuotePFE;
|
||||
OptionValue.ExtendInfo.PFE += valueResult.ExtendInfo.PFE;
|
||||
OptionValue.RoundedPv += valueResult.RoundedPv;
|
||||
OptionValue.DeltaCash += valueResult.DeltaCash;
|
||||
}
|
||||
return OptionValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return CalcValue(trade,valueDate, priceProvider, isEodSettlement);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算互换利息
|
||||
/// </summary>
|
||||
public static double GetExtraAmountBySwapRate(int clientId, DateTime? tradeDate, string timeRate, DateTime startDate, DateTime valueDate, int annualDays, double stockEqvNotional)
|
||||
{
|
||||
if (valueDate == tradeDate && PS.Config.Company == Configuration.CompanyEnum.中金)
|
||||
{
|
||||
var client = DataCacheProvider.GetClientDataSource().GetData(clientId);
|
||||
if (client.BoundSide == BoundSideEnum.北向)
|
||||
{
|
||||
startDate = startDate.AddDays(-1);
|
||||
}
|
||||
}
|
||||
|
||||
double extraAmountGet = 0;
|
||||
var swapDates = GetSwapDatesBetween(timeRate, startDate, valueDate);
|
||||
if (swapDates != null && swapDates.Any())
|
||||
{
|
||||
swapDates.ForEach(x =>
|
||||
{
|
||||
var itemDays = (x.DateTime - startDate).Days;
|
||||
var itemRate = GetSwapRateByDate(timeRate, x.DateTime);
|
||||
extraAmountGet += stockEqvNotional * itemRate * ((double)itemDays / annualDays);
|
||||
startDate = x.DateTime;
|
||||
});
|
||||
}
|
||||
|
||||
if (startDate < valueDate)
|
||||
{
|
||||
var latestDays = (valueDate - startDate).Days;
|
||||
var LatestRate = GetSwapRateByDate(timeRate, valueDate);
|
||||
extraAmountGet += stockEqvNotional * LatestRate * ((double)latestDays / annualDays);
|
||||
}
|
||||
|
||||
return extraAmountGet.Normalize().FormatValue(2);
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取计息开始日期,结束日期
|
||||
/// </summary>
|
||||
/// <param name="td"></param>
|
||||
/// <param name="valueDate"></param>
|
||||
/// <param name="tradeCash"></param>
|
||||
/// <param name="lastManualCashSwap"></param>
|
||||
/// <param name="floating"></param>
|
||||
/// <param name="endDate"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime GetSwapRateStartDate(OtcTradeBase td, trade_swap tradeSwap, DateTime valueDate, trade_cash tradeCash, trade_cash lastManualCash, bool floating, out DateTime endDate)
|
||||
{
|
||||
var tradeStartDate = PS.Config.Company == Configuration.CompanyEnum.中金 ? td.TradeDate.Value : td.StartDate.Value;
|
||||
var calcFirst = tradeSwap.RateCalcMode.StartsWith("1");//算头
|
||||
var calcLast = tradeSwap.RateCalcMode.EndsWith("1");//算尾
|
||||
DateTime startDate = calcFirst ? tradeStartDate.AddDays(-1) : tradeStartDate;
|
||||
//如果valueDate超过了到期日,利息以到期日来计算
|
||||
endDate = valueDate > td.ExerciseDate ? td.ExerciseDate.Value : valueDate;
|
||||
//是否算尾
|
||||
if (td.ExerciseDate == endDate && !calcLast)
|
||||
{
|
||||
endDate = endDate.AddDays(-1);
|
||||
}
|
||||
if (floating)
|
||||
{
|
||||
if(lastManualCash != null)
|
||||
{
|
||||
startDate = lastManualCash.ValueDate;
|
||||
}
|
||||
}
|
||||
else if (tradeCash != null)
|
||||
{
|
||||
startDate = tradeCash.ValueDate;
|
||||
}
|
||||
|
||||
return startDate;
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取计息开始日期,结束日期
|
||||
/// </summary>
|
||||
/// <param name="td"></param>
|
||||
/// <param name="valueDate"></param>
|
||||
/// <param name="tradeCash"></param>
|
||||
/// <param name="lastManualCashSwap"></param>
|
||||
/// <param name="floating"></param>
|
||||
/// <param name="endDate"></param>
|
||||
/// <returns></returns>
|
||||
public static DateTime GetSwapRateStartDatePre(OtcTradeBase td, trade_swap tradeSwap, DateTime valueDate, trade_cash tradeCash, trade_cash_pre lastManualCash, bool floating, out DateTime endDate)
|
||||
{
|
||||
var tradeStartDate = PS.Config.Company == Configuration.CompanyEnum.中金 ? td.TradeDate.Value : td.StartDate.Value;
|
||||
var calcFirst = tradeSwap.RateCalcMode.StartsWith("1");//算头
|
||||
var calcLast = tradeSwap.RateCalcMode.EndsWith("1");//算尾
|
||||
DateTime startDate = calcFirst ? tradeStartDate.AddDays(-1) : tradeStartDate;
|
||||
endDate = valueDate;
|
||||
if (td.ExerciseDate == endDate && !calcLast)
|
||||
{
|
||||
endDate = endDate.AddDays(-1);
|
||||
}
|
||||
if (floating)
|
||||
{
|
||||
if (lastManualCash != null)
|
||||
{
|
||||
startDate = lastManualCash.ValueDate;
|
||||
}
|
||||
}
|
||||
else if (tradeCash != null)
|
||||
{
|
||||
startDate = tradeCash.ValueDate;
|
||||
}
|
||||
|
||||
return startDate;
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算互换手续费
|
||||
/// </summary>
|
||||
/// <param name="tradePosition"></param>
|
||||
/// <param name="tradeImport"></param>
|
||||
/// <param name="tradeCash"></param>
|
||||
/// <param name="isForGet"></param>
|
||||
/// <returns></returns>
|
||||
public static double GetCostFee(trade tradePosition, trade tradeImport, trade_cash tradeCash, bool isForGet, bool isOpenFee)
|
||||
{
|
||||
if (tradeImport.trade_swap == null)
|
||||
{
|
||||
throw new Exception($"该交易[{tradeImport.TradeNumber}]对应的trade_swap未赋值");
|
||||
}
|
||||
|
||||
double costFee = 0;
|
||||
if (isForGet)
|
||||
{
|
||||
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(tradeImport.UnderlyingCode);
|
||||
costFee += (tradeImport.trade_swap.GetSingleFee ?? 0) * (tradeCash.UnwindNotional ?? 0) / underlying.ContractSize;
|
||||
costFee += (tradeImport.trade_swap.GetUnAnnualRate ?? 0) * (tradeCash.UnwindNotional ?? 0) * (isOpenFee ? (tradePosition.SpotPrice ?? 0) : (tradeCash.FinalPrice ?? 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(tradeImport.UnderlyingCode);
|
||||
costFee += (tradeImport.trade_swap.PaySingleFee ?? 0) * (tradeCash.UnwindNotional ?? 0) / underlying.ContractSize;
|
||||
costFee += (tradeImport.trade_swap.PayUnAnnualRate ?? 0) * (tradeCash.UnwindNotional ?? 0) * (isOpenFee ? (tradePosition.SpotPrice ?? 0) : (tradeCash.FinalPrice ?? 0));
|
||||
}
|
||||
|
||||
return costFee.FormatValue(2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static double GetInitialAmountSwapGet(OtcTradeBase trade, trade_swap trade_swap, double lastFinalPrice
|
||||
, double price, double stockEqvNotional, DateTime valueDate, DateTime? preSwapDate)
|
||||
{
|
||||
double? initialAmount;
|
||||
if (trade_swap.IsGetFloatingProfit)
|
||||
{
|
||||
var annualRate = GetAnnualVarIncomeRate(trade_swap, valueDate, preSwapDate, trade.StartDate.Value, trade.ExerciseDate.Value);
|
||||
initialAmount = GetInitialAmountSwap(lastFinalPrice, price
|
||||
, (trade_swap.GetNotional ?? 0) * stockEqvNotional / trade.OriginalStockEqvNotional.Value
|
||||
, trade_swap.GetLongShort, annualRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
initialAmount = trade_swap.GetFixedProfit * stockEqvNotional / trade.OriginalStockEqvNotional;
|
||||
}
|
||||
|
||||
return (initialAmount ?? 0).FormatValue(2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static double GetInitialAmountSwapPay(OtcTradeBase trade, trade_swap trade_swap, double lastFinalPrice
|
||||
, double price, double stockEqvNotional, DateTime valueDate, DateTime? preSwapDate)
|
||||
{
|
||||
double? initialAmount;
|
||||
|
||||
if (trade_swap.IsPayFloatingProfit)
|
||||
{
|
||||
var annualRate = GetAnnualVarIncomeRate(trade_swap, valueDate, preSwapDate, trade.StartDate.Value, trade.ExerciseDate.Value);
|
||||
initialAmount = GetInitialAmountSwap(lastFinalPrice, price
|
||||
, (trade_swap.PayNotional ?? 0) * stockEqvNotional / trade.OriginalStockEqvNotional.Value
|
||||
, trade_swap.PayLongShort, annualRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
initialAmount = trade_swap.PayFixedProfit * stockEqvNotional / trade.OriginalStockEqvNotional;
|
||||
}
|
||||
|
||||
return (initialAmount ?? 0).FormatValue(2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="lastFinalPrice"></param>
|
||||
/// <param name="price"></param>
|
||||
/// <param name="notional"></param>
|
||||
/// <param name="longShort"></param>
|
||||
/// <param name="annualVarIncomeRate">年化浮动收益率(为null表示非年化)</param>
|
||||
/// <returns></returns>
|
||||
public static double GetInitialAmountSwap(double lastFinalPrice, double price, double notional, string longShort, double? annualVarIncomeRate)
|
||||
{
|
||||
var amount = (price - lastFinalPrice) * notional.Normalize() * (longShort == "多头" ? 1 : -1);
|
||||
|
||||
return annualVarIncomeRate.HasValue ? amount * annualVarIncomeRate.Value : amount;
|
||||
}
|
||||
|
||||
private static List<Date> GetSwapDatesBetween(string swapTimeAndRate, DateTime startDate, DateTime endDate)
|
||||
{
|
||||
var customizedResults = QdpHelper.ParseAutocallCustomizedInfo(swapTimeAndRate);
|
||||
var dates = customizedResults.Item1;
|
||||
if (dates == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return dates.Where(x => x.DateTime > startDate && x.DateTime <= endDate).ToList();
|
||||
}
|
||||
|
||||
public static double GetSwapRateByDate(string swapTimeAndRate, DateTime valueDate)
|
||||
{
|
||||
double swapRate = 0;
|
||||
var customizedResults = QdpHelper.ParseAutocallCustomizedInfo(swapTimeAndRate);
|
||||
var dates = customizedResults.Item1;
|
||||
if (dates == null)
|
||||
{
|
||||
return swapRate;
|
||||
}
|
||||
|
||||
var getSwapRates = customizedResults.Item2;
|
||||
var latestDate = dates.Where(x => x.DateTime >= valueDate).OrderBy(x => x.DateTime).FirstOrDefault();
|
||||
|
||||
//展期情况互换利率获取最后一个日期的互换利率
|
||||
if (latestDate == null)
|
||||
{
|
||||
latestDate = dates.Max();
|
||||
}
|
||||
|
||||
if (getSwapRates != null && getSwapRates.Any())
|
||||
{
|
||||
swapRate = getSwapRates[GetDateIndex(dates, latestDate)];
|
||||
}
|
||||
|
||||
return swapRate;
|
||||
}
|
||||
|
||||
private static int GetDateIndex(Date[] source, Date value)
|
||||
{
|
||||
if (source is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
}
|
||||
|
||||
var index = 0;
|
||||
foreach (var item in source)
|
||||
{
|
||||
if (item.DateTime == value.DateTime)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取互换交易变动收益年化率,返回null表示非年化
|
||||
/// </summary>
|
||||
/// <param name="trade_swap">互换交易</param>
|
||||
/// <param name="valueDate">结算日期</param>
|
||||
/// <param name="preSwapDate">上次互换日期</param>
|
||||
/// <param name="tradeStartDate">交易开始日期</param>
|
||||
/// <returns>返回null表示非年化</returns>
|
||||
public static double? GetAnnualVarIncomeRate(trade_swap trade_swap, DateTime valueDate, DateTime? preSwapDate, DateTime tradeStartDate, DateTime exerciseDate)
|
||||
{
|
||||
if (trade_swap is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(trade_swap));
|
||||
}
|
||||
|
||||
//浮动收益是否年化
|
||||
if (!trade_swap.AnnualVarIncome)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!trade_swap.AnnualDays.HasValue || trade_swap.AnnualDays < 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
DateTime startDate;
|
||||
|
||||
if (preSwapDate != null)
|
||||
{
|
||||
startDate = preSwapDate.Value.AddDays(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
//2021-05-11:浮动收益年化时,首日计息规则会影响到收益金额的计算
|
||||
bool calcFirst = trade_swap.RateCalcMode.StartsWith("1");//算头
|
||||
startDate = calcFirst ? tradeStartDate : tradeStartDate.AddDays(1);
|
||||
}
|
||||
|
||||
var days = (valueDate - startDate.Date).Days + 1;
|
||||
|
||||
if (exerciseDate == valueDate && trade_swap.RateCalcMode.EndsWith("0"))
|
||||
{
|
||||
days -= 1;
|
||||
}
|
||||
|
||||
if (days < 0)
|
||||
{
|
||||
days = 0;
|
||||
}
|
||||
|
||||
return (double)days / trade_swap.AnnualDays.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,603 @@
|
||||
using Org.BouncyCastle.Ocsp;
|
||||
using Qdp.Pricing.Base.Implementations;
|
||||
using YLErp.Abstract;
|
||||
using YLErp.Abstract.DataProviders;
|
||||
using YLErp.BLL;
|
||||
using YLErp.Configuration;
|
||||
using YLErp.DBModels.Consts;
|
||||
using YLErp.DBModels.Enums;
|
||||
using YLErp.Enums;
|
||||
using YLErp.Model;
|
||||
using YLErp.Modules.ApiModule;
|
||||
using YLErp.Modules.DataProviderModule;
|
||||
using YLErp.Modules.VolatilityModule;
|
||||
using YLErp.QdpModule;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
//这个类的主要目标是代替CalculatorHelper.CalculateRisksForTrades
|
||||
//并且不再支持日终结算的计算,不再支持场内期权
|
||||
|
||||
/// <summary>
|
||||
/// 简单场外衍生品交易风险计算
|
||||
/// </summary>
|
||||
class SimpleOtcTradeRiskCalc
|
||||
{
|
||||
/// <summary>
|
||||
/// 波动率类型,默认:交易
|
||||
/// </summary>
|
||||
public string VolType { get; set; } = "交易";
|
||||
|
||||
public SettlementTypeEnum SettlementType { get; set; } = SettlementTypeEnum.ClosePrice;
|
||||
|
||||
/// <summary>
|
||||
/// 覆盖交易波动率
|
||||
/// </summary>
|
||||
public Dictionary<int, double> OverrideVolsForTrade { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否使用交易波动率,默认false
|
||||
/// </summary>
|
||||
public bool IsUseTradeVol { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 精确时间模式,默认true
|
||||
/// </summary>
|
||||
public bool PreciseTimeMode { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否增加波动率为百分比格式
|
||||
/// </summary>
|
||||
public bool IsAddVolPercent { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 增加波动率
|
||||
/// </summary>
|
||||
public Dictionary<int, double> AddVolRateDic { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 计算指标枚举,默认只计算PV
|
||||
/// </summary>
|
||||
public PricingRequest PricingRequest { get; set; } = PricingRequest.Pv;
|
||||
|
||||
/// <summary>
|
||||
/// 是否在进行预付金计算
|
||||
/// </summary>
|
||||
public bool IsMarginCalc { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 计算场景
|
||||
/// </summary>
|
||||
public CalcScenarioEnum CalcScenario { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否使用手动维护的风险值
|
||||
/// </summary>
|
||||
public bool CanUseManual { get; set; } = false;
|
||||
|
||||
DateTime _valueDate;
|
||||
IPriceProvider _priceProvider;
|
||||
List<string> _errorList;
|
||||
readonly UnderlyingDataProvider _umProvider;
|
||||
|
||||
public SimpleOtcTradeRiskCalc()
|
||||
{
|
||||
_umProvider = new UnderlyingDataProvider();
|
||||
}
|
||||
|
||||
bool IsEodSettle => CalcScenario == CalcScenarioEnum.EodSettlement;
|
||||
bool IsInitialMargin => CalcScenario == CalcScenarioEnum.InitialMargin;
|
||||
|
||||
private void AppendError(trade td, string errorMsg, Exception ex = null)
|
||||
{
|
||||
if (ex != null)
|
||||
{
|
||||
errorMsg = string.IsNullOrEmpty(errorMsg) ? ex.Messages() : errorMsg + "," + ex.Messages();
|
||||
}
|
||||
|
||||
errorMsg = $"[{td.TradeType},交易编号:{td.TradeNumber}]计算出错:{errorMsg}";
|
||||
|
||||
if (IsEodSettle)
|
||||
{
|
||||
throw new Exception(errorMsg, ex);
|
||||
}
|
||||
|
||||
if (_errorList == null)
|
||||
{
|
||||
_errorList = new List<string>();
|
||||
}
|
||||
|
||||
_errorList.Add(errorMsg);
|
||||
}
|
||||
|
||||
public TradeRiskResult CalculateRisksForTrades(DateTime valueDate, IEnumerable<trade> tradeList, IPriceProvider priceProvider, CalcScenarioEnum calcScenario)
|
||||
{
|
||||
var results = new List<TradeRiskResultRecord>();
|
||||
var trResult = new TradeRiskResult { Results = results };
|
||||
|
||||
if (tradeList == null || !tradeList.Any())
|
||||
{
|
||||
return trResult;
|
||||
}
|
||||
|
||||
if (IsEodSettle)
|
||||
{
|
||||
PreciseTimeMode = false;
|
||||
}
|
||||
|
||||
_valueDate = valueDate;
|
||||
_priceProvider = priceProvider ?? throw new ArgumentNullException(nameof(priceProvider));
|
||||
|
||||
var sysRiskFreeRate = (VolType == "光证" ? valuedateBLL.RiskFreeRateExtend : valuedateBLL.RiskFreeRate) / 100;
|
||||
|
||||
var recordlist = DbContextFactory.GetYLDbContext().dividendrate_record.Where(x => valueDate >= x.ValueDate).ToList();
|
||||
|
||||
using (var marketProxy = new MarketProxy(_valueDate, sysRiskFreeRate))
|
||||
{
|
||||
using var db = DbContextFactory.GetYLDbContext();
|
||||
var tradeIds = tradeList.Select(x => x.id).ToArray();
|
||||
var manualDicByTypes = db.eod_trade_risk_manual.Where(x => x.ValueDate == _valueDate && tradeIds.Contains(x.TradeId) && x.VolType == VolType && x.SettlementType == SettlementType).ToDictionary(x => x.TradeId);
|
||||
var manualDic = db.eod_trade_risk_manual.Where(x => x.ValueDate == _valueDate && tradeIds.Contains(x.TradeId) && string.IsNullOrEmpty(x.VolType)).ToDictionary(x => x.TradeId);
|
||||
foreach (var td in tradeList)
|
||||
{
|
||||
if (VolType == "光证")
|
||||
{
|
||||
td.NoRiskRate = sysRiskFreeRate;
|
||||
}
|
||||
|
||||
//国元固收默认从全局配置里取分红率,如果TradeHisData里有分红率,则根据生效时间取最优
|
||||
if (recordlist != null && recordlist.Any())
|
||||
{
|
||||
var _record = recordlist.Where(x => x.UnderlyingCode.Split(',').Any(code => code == td.UnderlyingCode) && x.TradeType.Contains(td.TradeType) && (x.OptionType == td.OptionType || x.OptionType == "全部")).OrderByDescending(x => x.OptDate).OrderByDescending(x => x.ValueDate);
|
||||
if (_record.Any())
|
||||
{
|
||||
td.DividendRate = _record.FirstOrDefault()?.DividendRate;
|
||||
}
|
||||
}
|
||||
|
||||
td.DividendRate = td.DividendRate ?? td.NoRiskRate ?? sysRiskFreeRate;
|
||||
|
||||
TradeRiskResultRecord record;
|
||||
|
||||
if (IsMarginCalc && td.TradeType == "自定义交易")
|
||||
{
|
||||
record = new TradeRiskResultRecord
|
||||
{
|
||||
Trade = td,
|
||||
Underlyings = new[] { DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode) },
|
||||
ValueResult = new TradeValueResult() //自定义交易的预付金不需要在这里处理
|
||||
};
|
||||
if (PS.Config.ErpElement.ExternalAPIForCustomCalcEnable)
|
||||
{
|
||||
td.StartDate = _valueDate;
|
||||
var tradeVol = IsUseTradeVol;
|
||||
IsUseTradeVol = false;
|
||||
var price = _priceProvider.GetPrice(td.UnderlyingCode);
|
||||
var um = DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
|
||||
var consVol = GetConstVol(td, price, IsEodSettle, um?.UnderlyingTypeId ?? 0);
|
||||
//从风险对冲那边抄来的逻辑,波动率精度会导致计算结果和风险对冲有差异;
|
||||
var volValue = Commons.OtcFormatHelper.FormatValue(consVol, PS.Config.ErpElement.VolMoreAccurate ? 6 : 4);
|
||||
|
||||
var apiValue = TradeCalcApiHelper.CalculateCustomizedTrade(_valueDate, td, price, volValue, CalcScenario == CalcScenarioEnum.EodSettlement, calcScenario, TradeCalcApiHelper.PV, TradeCalcApiHelper.DELTA, TradeCalcApiHelper.GAMMA, TradeCalcApiHelper.VEGA, TradeCalcApiHelper.THETA, TradeCalcApiHelper.RHO);
|
||||
|
||||
if (apiValue.Success)
|
||||
{
|
||||
record.ValueResult = apiValue.Content;
|
||||
}
|
||||
//异常
|
||||
else
|
||||
{
|
||||
throw new Exception(apiValue.Msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (td.TradeType != "场内期权" && ConsTrade.TradeTypesForHedge.Contains(td.TradeType))
|
||||
{
|
||||
record = new TradeRiskResultRecord
|
||||
{
|
||||
Trade = td,
|
||||
Underlyings = new[] { DataCacheProvider.GetUnderlyingDataSource().GetData(td.UnderlyingCode) },
|
||||
ValueResult = new TradeValueResult()
|
||||
{
|
||||
Pv = td.Notional * _priceProvider.GetPrice(td.UnderlyingCode),
|
||||
Delta = td.Notional
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
//分类维护的风险数据优先级高于普通的风险数据
|
||||
manualDicByTypes.TryGetValue(td.id, out var manual);
|
||||
if (manual == null)
|
||||
{
|
||||
manualDic.TryGetValue(td.id, out manual);
|
||||
}
|
||||
else
|
||||
{
|
||||
//如果分类维护的数据不全,通过单一数据补充
|
||||
manualDic.TryGetValue(td.id, out var singleManual);
|
||||
if (singleManual != null)
|
||||
{
|
||||
manual.Pv = manual.Pv ?? singleManual.Pv;
|
||||
manual.Delta = manual.Delta ?? singleManual.Delta;
|
||||
manual.DeltaCash = manual.DeltaCash ?? singleManual.DeltaCash;
|
||||
manual.Gamma = manual.Gamma ?? singleManual.Gamma;
|
||||
manual.GammaCash = manual.GammaCash ?? singleManual.GammaCash;
|
||||
manual.Theta = manual.Theta ?? singleManual.Theta;
|
||||
manual.Vega = manual.Vega ?? singleManual.Vega;
|
||||
manual.VegaCash = manual.VegaCash ?? singleManual.VegaCash;
|
||||
manual.Rho = manual.Rho ?? singleManual.Rho;
|
||||
}
|
||||
}
|
||||
record = CalcOtcTrade(marketProxy, td, CanUseManual ? manual : null);
|
||||
}
|
||||
|
||||
if (record != null)
|
||||
{
|
||||
if (PS.Config.ErpElement.IsPVIncludePrincipal)
|
||||
{
|
||||
record.ValueResult.Pv = record.ValueResult.Pv + td.PrincipalSum() * (td.BuySell == "卖出" ? -1 : 1);
|
||||
record.ValueResult.RoundedPv = record.ValueResult.RoundedPv + td.PrincipalSum() * (td.BuySell == "卖出" ? -1 : 1);
|
||||
}
|
||||
|
||||
results.Add(record);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return trResult;
|
||||
}
|
||||
|
||||
//计算衍生品交易价值
|
||||
private TradeRiskResultRecord CalcOtcTrade(MarketProxy mp, trade td, eod_trade_risk_manual manual)
|
||||
{
|
||||
var record = new TradeRiskResultRecord { Trade = td, ValueResult = null, Underlyings = null };
|
||||
|
||||
try
|
||||
{
|
||||
var um = _umProvider.GetUnderlying(td.UnderlyingCode);
|
||||
|
||||
record.Underlyings = new[] { um };
|
||||
|
||||
if (um == null && td.HasUnderlying())
|
||||
{
|
||||
AppendError(td, "未找到标的信息:" + td.UnderlyingCode);
|
||||
record.ValueResult = GetTradeValueResult(td, "未找到标的信息");
|
||||
return record;
|
||||
}
|
||||
|
||||
if (ConsTrade.TradeCompleteStatus.Contains(td.TradeStatus))
|
||||
{
|
||||
record.ValueResult = GetTradeValueResult(td);
|
||||
return record;
|
||||
}
|
||||
|
||||
TradeValueResult result = null;
|
||||
|
||||
if (manual != null)
|
||||
{
|
||||
var spotPrice = GetSpotPrice(td);
|
||||
manual.DeltaCash = manual.Delta * spotPrice;
|
||||
manual.GammaCash = manual.Gamma * Math.Pow(spotPrice, 2) / 100;
|
||||
manual.VegaCash = manual.Vega * spotPrice;
|
||||
result = new TradeValueResult
|
||||
{
|
||||
Pv = manual.Pv ?? 0,
|
||||
RoundedPv = manual.Pv ?? 0,
|
||||
Delta = manual.Delta ?? 0,
|
||||
Gamma = manual.Gamma ?? 0,
|
||||
Vega = manual.Vega ?? 0,
|
||||
TradingDayTheta = manual.Theta ?? 0,
|
||||
CalendarDayTheta = manual.Theta ?? 0,
|
||||
Rho = manual.Rho ?? 0,
|
||||
DeltaCash = manual.DeltaCash ?? 0,
|
||||
GammaCash = manual.GammaCash ?? 0
|
||||
};
|
||||
}
|
||||
|
||||
//没有自定义风险维护值 或者 自定风险维护没有涵盖PV和所有希腊值(部分维护场景)
|
||||
//部分维护场景下,需要系统计算出未赋值的属性,进行合并返还
|
||||
if (manual == null || manual != null && !manual.IsPVAndAllGreek)
|
||||
{
|
||||
switch (td.TradeType)
|
||||
{
|
||||
case "彩虹期权":
|
||||
case "价差期权":
|
||||
case "结构化交易":
|
||||
AppendError(td, "不支持此交易类型的计算");
|
||||
result = GetTradeValueResult(td, "不支持此交易类型的计算");
|
||||
break;
|
||||
case ConsGlobal.TradeType.Custom:
|
||||
result = CalcCustom(td, IsEodSettle, um?.UnderlyingTypeId ?? 0);
|
||||
break;
|
||||
case ConsGlobal.TradeType.PayoffSwap:
|
||||
result = PayoffSwapCalcService.CalcValue(td, _valueDate, IsMarginCalc ? null : _priceProvider, IsEodSettle);
|
||||
break;
|
||||
case ConsGlobal.TradeType.Forward:
|
||||
{
|
||||
var spotPrice = GetSpotPrice(td);
|
||||
result = ForwardradeCalcService.CalcValue(td, spotPrice);
|
||||
}
|
||||
break;
|
||||
case "商品期货":
|
||||
_priceProvider.TryGetPrice(td.UnderlyingCode, out var spotPriceGet);
|
||||
result = new TradeValueResult()
|
||||
{
|
||||
Pv = td.Notional * spotPriceGet,
|
||||
};
|
||||
break;
|
||||
case "场内期权":
|
||||
default:
|
||||
result = CalcOption(mp, td, IsEodSettle, IsInitialMargin, um?.UnderlyingTypeId ?? 0); //计算场外期权
|
||||
break;
|
||||
}
|
||||
|
||||
//系统计算结果和手动维护值合并
|
||||
TradeRiskCalcUtil.GetOptionValueWithManual(manual, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (td.TradeType != ConsGlobal.TradeType.CashFlow && result.Vol == 0)
|
||||
{
|
||||
result.Vol = GetConstVol(td, GetSpotPrice(td), IsEodSettle, um?.UnderlyingTypeId ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
record.ValueResult = result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendError(td, null, ex);
|
||||
record.ValueResult = GetTradeValueResult(td, ex.Message);
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
private TradeValueResult GetTradeValueResult(trade td, string errmsg = null)
|
||||
{
|
||||
return new TradeValueResult
|
||||
{
|
||||
TradeId = td.id,
|
||||
UnderlyingCode = td.UnderlyingCode,
|
||||
Strike = td.Strike,
|
||||
UnderlyingId = td.UnderlyingId,
|
||||
ErrorMessage = errmsg,
|
||||
Succeeded = string.IsNullOrEmpty(errmsg)
|
||||
};
|
||||
}
|
||||
|
||||
//获取标的现价
|
||||
private double GetSpotPrice(trade td)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(td.UnderlyingCode))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
//20210706:支持参考价处理 -- 远期没有参考价
|
||||
if (td.SettlementType == SettlementTypeEnum.ReferencePrice
|
||||
&& _priceProvider.TryGetPrice(ConsGlobal.RefPricePrefix + td.UnderlyingCode, out var price))
|
||||
{
|
||||
return price;
|
||||
}
|
||||
|
||||
double BasiseodPrice = 0;
|
||||
if (PS.Config.ErpElement.ForwardTradePriceModel == Configuration.Enums.ForwardTradePriceModel.STANDARD && !string.IsNullOrWhiteSpace(td.BasisUnderlyingCode) && td.TradeType == ConsGlobal.TradeType.Forward)
|
||||
{
|
||||
_priceProvider.TryGetPrice(td.BasisUnderlyingCode, out BasiseodPrice);
|
||||
}
|
||||
|
||||
if (_priceProvider.TryGetPrice(td.UnderlyingCode, out price))
|
||||
{
|
||||
return price -= BasiseodPrice;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("未找到标的价格:" + td.UnderlyingCode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//计算自定义交易
|
||||
private TradeValueResult CalcCustom(trade td, bool isEodCalc, int underlyingTypeId)
|
||||
{
|
||||
var spotPrice = GetSpotPrice(td);
|
||||
var volValue = GetConstVol(td, spotPrice, isEodCalc, underlyingTypeId);
|
||||
//从风险对冲那边抄来的逻辑,波动率精度会导致计算结果和风险对冲有差异;
|
||||
volValue = Commons.OtcFormatHelper.FormatValue(volValue, PS.Config.ErpElement.VolMoreAccurate ? 6 : 4);
|
||||
|
||||
var (manual, optionValue) = TradeRiskCalcUtil.GetManualOptionValue(_valueDate, td, spotPrice, volValue, td.TradeType == "自定义交易", CalcScenario, VolType, SettlementType, isSettle: CalcScenario == CalcScenarioEnum.EodSettlement);
|
||||
|
||||
//收盘时如果自定义交易还活着且没有维护当日风险,并且收的时系统日期当日的盘,抛出exception
|
||||
if (IsEodSettle && !ConsTrade.TradeCompleteStatus.Contains(td.TradeStatus) && _valueDate == valuedateBLL.ValueDate && !PS.Config.IsMustRiskManual)
|
||||
{
|
||||
if ((manual == null || manual.ValueDate != _valueDate) && !PS.Config.Is润和)
|
||||
{
|
||||
var error = $"[{td.TradeType}] 交易'{td.TradeNumber}'在{_valueDate:yyyy-MM-dd}需先进行交易风险维护";
|
||||
throw new Exception(error);
|
||||
}
|
||||
}
|
||||
|
||||
return optionValue;
|
||||
}
|
||||
|
||||
//计算场外期权
|
||||
private TradeValueResult CalcOption(MarketProxy mp, trade td, bool isEodCalc, bool isInitialMargin, int underlyingTypeId)
|
||||
{
|
||||
var spotPrice = GetSpotPrice(td);
|
||||
|
||||
var req = new OptionValueCalcRequest(mp.RiskFreeRate)
|
||||
{
|
||||
correlations = null, //因为没有处理多标的,所以这里为null
|
||||
engineName = null,
|
||||
maturityShift = 0,
|
||||
preciseTimeMode = PreciseTimeMode,
|
||||
ParamOverride = null,
|
||||
pricingRequest = PricingRequest,
|
||||
spotPrices = new[] { spotPrice },
|
||||
vols = new[] { 0d },
|
||||
calcScenario = CalcScenario
|
||||
};
|
||||
req.timeToMaturityDays = double.NaN;
|
||||
//象屿最后一个交易日实时计算时TTM需要和平仓时算法一致
|
||||
if (PS.Config.Is厦门象屿
|
||||
&& (CalcScenario == CalcScenarioEnum.RealtimePosition || CalcScenario == CalcScenarioEnum.RealtimeRisk)
|
||||
&& td.SettlementType == SettlementTypeEnum.ReferencePrice)
|
||||
{
|
||||
req.timeToMaturityDays = TradeCalcHelper.CalculateTTMDaysForXiangYu(valuedateBLL.ValueDate, td.ExerciseDate.Value, underlyingTypeId, false);
|
||||
//20210706:支持厦门象屿参考价相关交易(这类交易不需要支持精确模式)
|
||||
req.preciseTimeMode = false;
|
||||
}
|
||||
|
||||
if (PS.Config.Is润和 && IsInitialMargin)
|
||||
{
|
||||
req.timeToMaturityDays = td.TTMDays;
|
||||
}
|
||||
|
||||
if (td.TradeType != ConsGlobal.TradeType.CashFlow)
|
||||
{
|
||||
var consVol = GetConstVol(td, spotPrice, isEodCalc, underlyingTypeId);
|
||||
//从风险对冲那边抄来的逻辑,波动率精度会导致计算结果和风险对冲有差异;
|
||||
consVol = Commons.OtcFormatHelper.FormatValue(consVol, PS.Config.ErpElement.VolMoreAccurate ? 6 : 4);
|
||||
req.vols = new[] { consVol };
|
||||
if (IsMarginCalc && CalcScenario == CalcScenarioEnum.EodSettlement)
|
||||
{
|
||||
LogFactory.GetLogger("日终预付金计算").Info($"{td.TradeType}'{td.TradeNumber}',波动率:{consVol},spotPrice:{spotPrice}");
|
||||
}
|
||||
}
|
||||
|
||||
req.isEodCalc = IsEodSettle;
|
||||
return OptionCalculatorV2.GetOptionValueResult(mp, td, req, out _);
|
||||
}
|
||||
|
||||
#region----获取波动率----
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="td"></param>
|
||||
/// <param name="spotPrice"></param>
|
||||
/// <param name="isEodCalc"></param>
|
||||
/// <param name="underlyingTypeId">只适用于光证波动率</param>
|
||||
/// <returns></returns>
|
||||
private double GetConstVol(trade td, double spotPrice, bool isEodCalc, int underlyingTypeId)
|
||||
{
|
||||
var vol = GetConstVolRaw(td, spotPrice, isEodCalc, underlyingTypeId);
|
||||
|
||||
if (AddVolRateDic != null && AddVolRateDic.TryGetValue(td.id, out var addVolRate))
|
||||
{
|
||||
vol += IsAddVolPercent ? addVolRate * vol : addVolRate;
|
||||
}
|
||||
|
||||
return vol;
|
||||
}
|
||||
|
||||
private double GetConstVolRaw(trade td, double spotPrice, bool isEodCalc, int underlyingTypeId)
|
||||
{
|
||||
if (td.TradeType == "场内期权")
|
||||
{
|
||||
var vol = new ExOptionSavedVolProvider(_valueDate).GetSavedVol(td.ExchangeOptionCode, _valueDate);
|
||||
|
||||
if (vol != null)
|
||||
{
|
||||
return vol.Value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (OverrideVolsForTrade != null && OverrideVolsForTrade.TryGetValue(td.id, out var overrideVol))
|
||||
{
|
||||
return overrideVol;
|
||||
}
|
||||
|
||||
if (IsUseTradeVol)
|
||||
{
|
||||
if (VolType == "交易" || VolType == "持仓")
|
||||
{
|
||||
return VolatilityHelper.GetTradeVol(td, _valueDate, IsEodSettle);
|
||||
}
|
||||
|
||||
if (VolType == "对冲")
|
||||
{
|
||||
return TradeHedgeVolService.GetTradeHedgeVol(td, _valueDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (VolType == "光证")
|
||||
{
|
||||
var va = DataCacheProvider.GetVarietyDataSource().GetData(underlyingTypeId)
|
||||
?? throw new Exception("未找到光证波动率:" + td.UnderlyingCode);
|
||||
|
||||
return VarietyVolService.GetVarietyVol(_valueDate, va.id)
|
||||
?? throw new Exception("未找到光证波动率:" + td.UnderlyingCode);
|
||||
}
|
||||
|
||||
if (VolType == "开仓")
|
||||
{
|
||||
return td.TradeOpenVolatility ?? 0;
|
||||
}
|
||||
|
||||
var userGroup = string.Empty;
|
||||
|
||||
if (ConsUserGroup.HasGroup)
|
||||
{
|
||||
userGroup = DataCacheProvider.GetAssetUnitDataSource().GetData(td.AssetId)?.UserGroup;
|
||||
if (string.IsNullOrEmpty(userGroup))
|
||||
{
|
||||
throw new Exception($"取波动率时未能获取UserGroup");
|
||||
}
|
||||
}
|
||||
|
||||
if (VolType == "BidAskVol")
|
||||
{
|
||||
//结算时应以平仓的交易方向来选择曲面
|
||||
//如果交易是买入,平仓时是卖出,则应该用Ask曲面
|
||||
|
||||
var tempVolType = td.BuySell == "买入" ? "报价Ask" : "报价Bid";
|
||||
|
||||
var bidAskVol = VolatilityHelper.GetVol(_valueDate, tempVolType, td.UnderlyingCode, userGroup)
|
||||
?? throw new Exception($"未找到'{tempVolType}'波动率:{td.UnderlyingCode}");
|
||||
|
||||
return GetInterpolatedVol(td, bidAskVol, spotPrice, isEodCalc);
|
||||
}
|
||||
|
||||
//当需要去波动率曲面中查询时,所有公司的VolType都应该是交易;
|
||||
//上面这句描述应该是错误,当曲面波动率模式时,应该用配置的结算波动率,todo
|
||||
|
||||
string tempVolType2 = VolType;
|
||||
if (VolType == "对冲")
|
||||
{
|
||||
tempVolType2 = "交易";
|
||||
}
|
||||
else if (VolType == "持仓" || string.IsNullOrEmpty(tempVolType2))
|
||||
{
|
||||
tempVolType2 = IsUseTradeVol ? "交易" : valuedateBLL.SystemDate.EodSettleVolMode.TrimToNull() ?? "交易";
|
||||
}
|
||||
|
||||
var underlyingVols = VolatilityHelper.GetVol(_valueDate, tempVolType2, td.UnderlyingCode, userGroup)
|
||||
?? throw new Exception($"未找到'{tempVolType2}'波动率:{td.UnderlyingCode}");
|
||||
|
||||
return GetInterpolatedVol(td, underlyingVols, spotPrice, isEodCalc);
|
||||
}
|
||||
|
||||
private double GetInterpolatedVol(trade td, IVolatility vols, double spotPrice, bool isEodCalc)
|
||||
{
|
||||
return VolatilityHelper.GetInterpolatedVol(
|
||||
volConstructionType: PS.Config.ErpElement.SkewMapVolConstruction ? VolConstructionType.SkewMap : VolConstructionType.Normal,
|
||||
volSurface: vols,
|
||||
valueDate: _valueDate,
|
||||
underlyingCode: td.UnderlyingCode,
|
||||
exerciseDate: td.ExerciseDate.Value,
|
||||
strike: td.Strike ?? 0,
|
||||
isBuy: td.BuySell == "买入",
|
||||
isCall: td.CallPut == "Call",
|
||||
spotPrice: spotPrice,
|
||||
isMoneynessOption: td.IsMoneynessOption == "是",
|
||||
isEodCalc: isEodCalc);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
using Qdp.Pricing.Library.Common.MathMethods.VolTermStructure;
|
||||
using YLErp.Abstract;
|
||||
using YLErp.Models;
|
||||
using YLErp.Modules.SkewMapVolModule;
|
||||
using YLErp.QdpModule;
|
||||
using YLErp.QdpModule.Constants;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
public static class SkewMapVolHelper
|
||||
{
|
||||
public static double GetInterpolatedVol(
|
||||
IVolatility volSurface,
|
||||
DateTime valueDate,
|
||||
string underlyingCode,
|
||||
DateTime exerciseDate,
|
||||
double strikePrice,
|
||||
bool isBuy,
|
||||
bool isCall,
|
||||
double spotPrice,
|
||||
double timeToMaturityDays = double.NaN,
|
||||
int? skewMapVolVar = null)
|
||||
{
|
||||
var baseVolSurface = GetSkewMapBaseVolSurface(underlyingCode, volSurface);
|
||||
var skewMapVolSurface = new SkewMapVolSurface(baseVolSurface.BaseVol);
|
||||
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(underlyingCode);
|
||||
|
||||
var t = double.IsNaN(timeToMaturityDays)
|
||||
? TradeCalcHelper.CalculateTTMDays(
|
||||
valueDate,
|
||||
exerciseDate,
|
||||
varietyid: underlying?.UnderlyingTypeId ?? 0,
|
||||
precisionOfMinute: false)
|
||||
: timeToMaturityDays;
|
||||
|
||||
var volVar = skewMapVolVar ?? (int)(isBuy ? baseVolSurface.BidVar : baseVolSurface.AskVar);
|
||||
|
||||
return skewMapVolSurface.GetVol(
|
||||
t: Math.Ceiling(t), //不考虑日内精确时间
|
||||
k: strikePrice,
|
||||
spot: spotPrice,
|
||||
isCall: isCall,
|
||||
isBuy: isBuy,
|
||||
var: volVar);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// skewmapvol转换为正常波动率构造
|
||||
/// </summary>
|
||||
public static IVolatility GetNormalVolatility(
|
||||
IVolatility volSurface,
|
||||
DateTime valueDate,
|
||||
string underlyingCode,
|
||||
DateTime exerciseDate,
|
||||
double strikePrice,
|
||||
bool isBuy,
|
||||
bool isCall,
|
||||
double spotPrice,
|
||||
double timeToMaturityDays = double.NaN,
|
||||
int? skewMapVolVar = null)
|
||||
{
|
||||
var vol = GetInterpolatedVol(volSurface, valueDate, underlyingCode, exerciseDate, strikePrice, isBuy, isCall, spotPrice, timeToMaturityDays, skewMapVolVar);
|
||||
var singleVols = QdpVolHelper.GenerateFlatSingleVols(vol);
|
||||
return new VolatilityImpl
|
||||
{
|
||||
InterpolationMethod = ConsVolInfos.defInterpolationMethod,
|
||||
VolSurfaceMode = ConsVolInfos.defVolMode,
|
||||
VolTable = singleVols
|
||||
};
|
||||
}
|
||||
|
||||
public static SkewMapBaseVolSurface GetSkewMapBaseVolSurface(string underlyingCode, IVolatility volSurface)
|
||||
{
|
||||
Dictionary<string, double> baseVols = null;
|
||||
|
||||
try
|
||||
{
|
||||
baseVols = volSurface.VolTable.ToDictionary(x => x.Expire, x => x.Vol);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"{underlyingCode}的BaseVol格式不正确", ex);
|
||||
}
|
||||
|
||||
if (baseVols == null)
|
||||
{
|
||||
throw new Exception($"{underlyingCode}的BaseVol为空");
|
||||
}
|
||||
|
||||
if (!baseVols.ContainsKey("BidVar") || !baseVols.ContainsKey("AskVar"))
|
||||
{
|
||||
throw new Exception($"{underlyingCode}的BidVar或AskVar缺失");
|
||||
}
|
||||
|
||||
var baseVolSurface = new SkewMapBaseVolSurface
|
||||
{
|
||||
BaseVol = baseVols,
|
||||
BidVar = baseVols["BidVar"],
|
||||
AskVar = baseVols["AskVar"]
|
||||
};
|
||||
|
||||
return baseVolSurface;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取某交易的BaseVol
|
||||
/// </summary>
|
||||
public static double GetSkewMapBaseVolForTrade(
|
||||
DateTime valueDate,
|
||||
string UnderlyingCode,
|
||||
IVolatility volSurface,
|
||||
int UnderlyingTypeId,
|
||||
DateTime ExerciseDate,
|
||||
double timeToMaturityDays = double.NaN)
|
||||
{
|
||||
var baseVolSurface = GetSkewMapBaseVolSurface(UnderlyingCode, volSurface);
|
||||
var skewMapVolSurface = new SkewMapVolSurface(baseVolSurface.BaseVol);
|
||||
|
||||
var t = double.IsNaN(timeToMaturityDays)
|
||||
? TradeCalcHelper.CalculateTTMDays(
|
||||
valueDate,
|
||||
ExerciseDate,
|
||||
UnderlyingTypeId,
|
||||
precisionOfMinute: false)
|
||||
: timeToMaturityDays;
|
||||
|
||||
//用于波动率插值的t,不考虑日内精确时间,所以向上取整
|
||||
return skewMapVolSurface.GetBaseVol(Math.Ceiling(t));
|
||||
}
|
||||
|
||||
public static List<SingleVol> ContructSkewMapVolTable(double baseVol, int bidVar, int askVar)
|
||||
{
|
||||
return new List<SingleVol>()
|
||||
{
|
||||
new SingleVol()
|
||||
{
|
||||
Strike = 1.0,
|
||||
Expire = "1M",
|
||||
Vol = baseVol
|
||||
},
|
||||
new SingleVol()
|
||||
{
|
||||
Strike = 1.0,
|
||||
Expire = "3M",
|
||||
Vol = baseVol
|
||||
},
|
||||
new SingleVol()
|
||||
{
|
||||
Strike = 1.0,
|
||||
Expire = "6M",
|
||||
Vol = baseVol
|
||||
},
|
||||
new SingleVol()
|
||||
{
|
||||
Strike = 1.0,
|
||||
Expire = "BidVar",
|
||||
Vol = bidVar
|
||||
},
|
||||
new SingleVol()
|
||||
{
|
||||
Strike = 1.0,
|
||||
Expire = "AskVar",
|
||||
Vol = askVar
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
using CxxCalcLib;
|
||||
using Qdp.Foundation.Implementations;
|
||||
using Qdp.Pricing.Base.Enums;
|
||||
using Qdp.Pricing.Base.Utilities;
|
||||
using YLErp.BLL.Calculation.V2;
|
||||
using YLErp.Commons;
|
||||
using YLErp.Enums;
|
||||
using YLErp.Modules.CalculationLogModule;
|
||||
using YLErp.QdpModule;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
internal class SnowballSpecialistOptionCalculator
|
||||
{
|
||||
/// <summary>
|
||||
/// 计算Greeks
|
||||
/// </summary>
|
||||
/// <param name="valueDate"></param>
|
||||
/// <param name="vol"></param>
|
||||
/// <param name="td"></param>
|
||||
/// <returns></returns>
|
||||
public TradeValueResult CalcOptionValue(DateTime valueDate, double spotPrice, double vol, CalcScenarioEnum calcScenario, trade td)
|
||||
{
|
||||
return new TradeValueResult { Succeeded = false, ErrorMessage = "不支持专业版雪球" };
|
||||
|
||||
//using var wrapper = new CxxCalcApi();
|
||||
|
||||
//if (valueDate > td.ExerciseDate)
|
||||
//{
|
||||
// //OTC-5920:对于定价日期>到期日的交易,无需给定价引擎计算,PV和其他希腊字母等直接为0即可。
|
||||
// return new TradeValueResult(true)
|
||||
// {
|
||||
// UnderlyingCode = td.UnderlyingCode,
|
||||
// Pv = 0,
|
||||
// Delta = 0,
|
||||
// Gamma = 0,
|
||||
// Vega = 0,
|
||||
// Rho = 0,
|
||||
// CalendarDayTheta = 0,
|
||||
// TradingDayTheta = 0,
|
||||
// DeltaCash = 0,
|
||||
// GammaCash = 0,
|
||||
// VegaCash = 0,
|
||||
// Vol = vol,
|
||||
// SpotPrice = spotPrice
|
||||
// };
|
||||
//}
|
||||
//var calcIn = BuildCalcInParams(valueDate, spotPrice, vol, td);
|
||||
//AddCalculationLog(calcIn, calcScenario);
|
||||
//Dictionary<double, SnowballScenarioResult> result1;
|
||||
//try
|
||||
//{
|
||||
// result1 = wrapper.CalcPlatform_SnowballScenario(calcIn);
|
||||
//}
|
||||
//catch
|
||||
//{
|
||||
// throw new ServiceException("存在不符合定价条件的交易,无法完成定价,请检查交易要素是否完备");
|
||||
//}
|
||||
|
||||
//var result = result1.Values.FirstOrDefault();
|
||||
|
||||
//var direction = td.BuySell == "买入" ? 1 : -1;
|
||||
//var valueResult = new TradeValueResult(true)
|
||||
//{
|
||||
// UnderlyingCode = td.UnderlyingCode,
|
||||
// Pv = result.PV * direction,
|
||||
// Delta = result.DELTA * direction,
|
||||
// Gamma = result.GAMMA * direction,
|
||||
// Vega = result.VEGA * direction,
|
||||
// Rho = result.RHO * direction,
|
||||
// CalendarDayTheta = result.THETA * direction,
|
||||
// TradingDayTheta = result.THETA * direction,
|
||||
// DeltaCash = result.DELTA * spotPrice * direction,
|
||||
// GammaCash = result.GAMMA * spotPrice * spotPrice * 0.01 * direction,
|
||||
// VegaCash = result.VEGA * spotPrice * direction,
|
||||
// Vol = vol,
|
||||
// SpotPrice = spotPrice
|
||||
//};
|
||||
|
||||
//// 计算 RoundedPv
|
||||
//valueResult.RoundedPv = OtcFormatHelper.FormatValue(valueResult.Pv / td.Notional, 2) * td.Notional;
|
||||
|
||||
////计算DeltaInLots
|
||||
//var underlying = DataCacheModule.DataCacheManager.GetUnderlyingDataSource().GetData(td.UnderlyingCode);
|
||||
//valueResult.DeltaInLots = TradeLotsCalc.CalcDeltaInLots(result.DELTA, null, underlying);
|
||||
|
||||
|
||||
////if (!(td.trade_snowball?.PrepaymentAddedToPv ?? false))
|
||||
////{
|
||||
//// // 计算引擎返回的PV是包含预付金,如果pv不想包含预付金,需要减掉预付金
|
||||
//// var advanceAmount = td.StockEqvNotional * (td.trade_snowball.PrepaymentRatio ?? 0);
|
||||
//// valueResult.Pv -= advanceAmount;
|
||||
////}
|
||||
//ValueCalculator.ConvertTradeValueResultOfCompany(valueResult, td.TradeType);
|
||||
//return valueResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据目标PV反算票息、波动率等
|
||||
/// </summary>
|
||||
/// <param name="valueDate">定价日</param>
|
||||
/// <param name="vol">波动率</param>
|
||||
/// <param name="calcTarget">计算的目标</param>
|
||||
/// <param name="td">交易信息</param>
|
||||
/// <returns></returns>
|
||||
public double CalcTargets(DateTime valueDate, double spotPrice, double vol, int calcTarget, trade td)
|
||||
{
|
||||
return 0;
|
||||
|
||||
//using var wrapper = new CxxCalcApi();
|
||||
//var calcIn = BuildCalcInParams(valueDate, spotPrice, vol, td);
|
||||
//var targetPv = td.TradePrice ?? 0.0;
|
||||
//var clacTarget = (CalcTarget)calcTarget;
|
||||
//var isNan = false;
|
||||
//if (clacTarget == CalcTarget.COUPON)
|
||||
//{
|
||||
// //calcIn.koCoupons = calcIn.koCoupons.Select(g => double.NaN).ToArray();
|
||||
// calcIn.maturityCoupon = double.NaN;
|
||||
// isNan = false;
|
||||
//}
|
||||
//else if (clacTarget == CalcTarget.KO_COUPON)
|
||||
//{
|
||||
// //calcIn.koCoupons = calcIn.koCoupons.Select(g => double.NaN).ToArray();
|
||||
// isNan = true;
|
||||
//}
|
||||
//else if (clacTarget == CalcTarget.MATURITY_COUPON)
|
||||
//{
|
||||
// calcIn.maturityCoupon = double.NaN;
|
||||
// isNan = true;
|
||||
//}
|
||||
//AddCalculationLog(calcIn, CalcScenarioEnum.Pricing, targetPv, isNan);
|
||||
|
||||
//double targetValue = 0;
|
||||
//try
|
||||
//{
|
||||
// targetValue = wrapper.CalcPlatform_Infer(calcIn, targetPv, isNan, CalcTarget.COUPON);
|
||||
//}
|
||||
//catch
|
||||
//{
|
||||
// throw new ServiceException("存在不符合定价条件的交易,无法完成反算,请检查交易要素是否完备");
|
||||
//}
|
||||
//return targetValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据原始专业版雪球拆分为两个雪球
|
||||
/// 此处获取拆分的专业版雪球
|
||||
/// </summary>
|
||||
/// <param name="td"></param>
|
||||
/// <returns></returns>
|
||||
public trade GetSpecialTrade(trade td)
|
||||
{
|
||||
//预付金比例设置为0
|
||||
var tdClone = td.Clone();
|
||||
tdClone.trade_snowball = td.trade_snowball.Clone();
|
||||
tdClone.trade_snowball.PrepaymentRatio = 0;
|
||||
|
||||
return tdClone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据原始专业版雪球拆分为两个雪球
|
||||
/// 此处获取拆分的保本雪球
|
||||
/// </summary>
|
||||
/// <param name="td"></param>
|
||||
/// <returns></returns>
|
||||
public trade GetBreakevenTrade(trade td)
|
||||
{
|
||||
//无敲入条款,敲出票息与红利票息(不年化)=原始雪球的预付金比例
|
||||
//无风险利率=原始雪球的预付金折现率
|
||||
var tdClone = td.Clone();
|
||||
tdClone.trade_snowball = td.trade_snowball.Clone();
|
||||
tdClone.trade_snowball.KnockInOutDate = null;
|
||||
tdClone.trade_snowball.KnockInOutStatus = null;
|
||||
tdClone.trade_snowball.KIBarrier = 0;
|
||||
tdClone.trade_snowball.SpreadStrikeAtMaturity1 = 0;
|
||||
tdClone.trade_snowball.KIPayoffType = KIPayoffTypeEnum.None;
|
||||
tdClone.trade_snowball.KORebate = td.trade_snowball.PrepaymentRatio ?? 0;
|
||||
tdClone.trade_snowball.Coupon = td.trade_snowball.PrepaymentRatio ?? 0;
|
||||
tdClone.trade_snowball.IsFixedCoupon = true;
|
||||
tdClone.trade_snowball.PrepaymentUsed = false;
|
||||
|
||||
var customizedResults = QdpHelper.ParseAutocallCustomizedInfo(td.trade_snowball.KOObservationDates);
|
||||
var koObservationDates = customizedResults.Item1;
|
||||
var customizedKOBarriers = customizedResults.Item2;
|
||||
|
||||
tdClone.trade_snowball.KOObservationDates = $"{string.Join(",", koObservationDates.Select(O => O.DateTime.OtcFormatDate()))};" +
|
||||
$"{string.Join(",", customizedKOBarriers)};" +
|
||||
$"{string.Join(",", koObservationDates.Select(x => (td.trade_snowball.PrepaymentRatio ?? 0).OtcFormatFlex(6)))}";
|
||||
|
||||
tdClone.NoRiskRate = td.trade_snowball.PrepaymentConvertCashRate ?? 0;
|
||||
|
||||
return tdClone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加和result结果
|
||||
/// </summary>
|
||||
/// <param name="result1"></param>
|
||||
/// <param name="result2"></param>
|
||||
/// <returns></returns>
|
||||
public TradeValueResult MergeTradeValueResult(TradeValueResult specialSnowballResult, TradeValueResult breakevenSnowballResult)
|
||||
{
|
||||
specialSnowballResult.Pv += breakevenSnowballResult.Pv;
|
||||
specialSnowballResult.RoundedPv += breakevenSnowballResult.RoundedPv;
|
||||
specialSnowballResult.Delta += breakevenSnowballResult.Delta;
|
||||
specialSnowballResult.DeltaCash += breakevenSnowballResult.DeltaCash;
|
||||
specialSnowballResult.Gamma += breakevenSnowballResult.Gamma;
|
||||
specialSnowballResult.GammaCash += breakevenSnowballResult.GammaCash;
|
||||
specialSnowballResult.CalendarDayTheta += breakevenSnowballResult.CalendarDayTheta;
|
||||
specialSnowballResult.TradingDayTheta += breakevenSnowballResult.TradingDayTheta;
|
||||
specialSnowballResult.Rho += breakevenSnowballResult.Rho;
|
||||
specialSnowballResult.Vega += breakevenSnowballResult.Vega;
|
||||
specialSnowballResult.VegaCash += breakevenSnowballResult.VegaCash;
|
||||
specialSnowballResult.TimeValue += breakevenSnowballResult.TimeValue;
|
||||
return specialSnowballResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建计算引擎需要的参数
|
||||
/// </summary>
|
||||
/// <param name="valueDate">定价日</param>
|
||||
/// <param name="vol">波动率</param>
|
||||
/// <param name="td">交易信息</param>
|
||||
/// <returns></returns>
|
||||
private CalcIn BuildCalcInParams(DateTime valueDate, double spotPrice, double vol, trade td)
|
||||
{
|
||||
var trade_snowball = td.trade_snowball;
|
||||
|
||||
// 敲入观察日
|
||||
if (string.IsNullOrEmpty(trade_snowball?.KOObservationDates?.Trim(new[] { ' ', ';' })))
|
||||
{
|
||||
throw new ServiceException("敲出观察日列表不能为空,请生成敲出观察日列表");
|
||||
}
|
||||
if (vol <= 0)
|
||||
{
|
||||
throw new ServiceException("波动率必须大于0,请调整波动率");
|
||||
}
|
||||
var koParams = trade_snowball.KOObservationDates.Split(";");
|
||||
var koObsDateStr = koParams[0];
|
||||
var koObsDates = koObsDateStr.Split(",").Select(g => new Date(DateTime.Parse(g))).ToArray();
|
||||
var koBarriers = koParams[1].Split(",").Select(g => double.Parse(g)).ToArray();
|
||||
var koCoupons = koParams[2].Split(",").Select(g => string.IsNullOrEmpty(g) ? double.NaN : double.Parse(g)).ToArray();
|
||||
|
||||
var kiBarrier = trade_snowball.KIBarrier;
|
||||
var strike = trade_snowball.SpreadStrikeAtMaturity1 ?? 0;
|
||||
if (!td.IsMoneynessOptionData)
|
||||
{
|
||||
//转为相对价格比例
|
||||
koBarriers = koBarriers.Select(g => g / td.InitialSpotPrice ?? 0).ToArray();
|
||||
kiBarrier = kiBarrier / td.InitialSpotPrice ?? 0;
|
||||
strike = strike / td.InitialSpotPrice ?? 0;
|
||||
}
|
||||
|
||||
// 计算票息年化系数(外部有不同的计算方式)
|
||||
var iFixedCoupon = string.IsNullOrEmpty(trade_snowball.CouponDayCount); //是否年化,约定为空表示非年化
|
||||
var dayCount = iFixedCoupon ? null : trade_snowball.CouponDayCount.ToDayCountImpl();
|
||||
var koFractions = koObsDateStr.Split(",").Select(g =>
|
||||
{
|
||||
var koObsDate = DateTime.Parse(g);
|
||||
var koFraction = iFixedCoupon ? 1 : YLErp.QdpModule.QdpHelper.AnnualizeFactor(new Date(td.StartDate), koObsDate, dayCount);
|
||||
return koFraction;
|
||||
}).ToArray();
|
||||
|
||||
var market = GetMarket(td.UnderlyingCode);
|
||||
|
||||
// 计算入参
|
||||
var calcIn = new CalcIn()
|
||||
{
|
||||
// 市场(日历)名称,如China
|
||||
market = market,
|
||||
// 开始日期
|
||||
startDate = new Date(td.StartDate).ToString(),
|
||||
// 估值日期
|
||||
valueDate = valueDate.ToString("yyyy-MM-dd"),
|
||||
// 5是向上敲出的雪球,6是向下敲出的雪球
|
||||
barrierType = QdpConverter.ConvertOptionType(td.CallPut) == OptionType.Call ? CxxCalcLib.BarrierType.UP_OUT_DOWN_IN : CxxCalcLib.BarrierType.UP_IN_DOWN_OUT,
|
||||
// 期初价格
|
||||
initialSpot = td.InitialSpotPrice ?? 0,
|
||||
// 敲出观察日列表,以逗号分隔
|
||||
koObsDateStr = koObsDateStr,
|
||||
// 敲出障碍价格列表,与敲出观察日对应
|
||||
koBarriers = koBarriers,
|
||||
// 敲出票息列表??
|
||||
koCoupons = koCoupons,
|
||||
// 敲出票息的年化系数列表
|
||||
koFractions = koFractions,
|
||||
// 敲出观察日个数
|
||||
activeKoObsCount = koObsDates.Count(),//koObsDates.Where(g => g > new Date(td.StartDate)).Count(),
|
||||
// 敲出增强收益的参与率,无增强收益填0
|
||||
koPayoffParticipation = trade_snowball.EnhancedParticipationRate ?? 0,
|
||||
// 敲入障碍价格
|
||||
kiBarrier = kiBarrier,
|
||||
// 敲入后的行权价
|
||||
kiStrike = strike,
|
||||
// 是否仅在到期日进行敲入观察,否则为每日观察
|
||||
kiObsOnlyAtMaturity = trade_snowball.KIObservationType == KIObservationType.OnlyEndDate,
|
||||
// 敲入后的期权参与率??
|
||||
kiParticipationRate = trade_snowball.KIParticipationRate ?? 0,
|
||||
// 红利票息
|
||||
maturityCoupon = trade_snowball.Coupon,
|
||||
// 保本比例,如0.8表示敲入后亏损封顶20%
|
||||
protectionRatio = trade_snowball.PrincipalProtectionRate ?? 0,
|
||||
// 初始预付金比例
|
||||
initialMarginRatio = trade_snowball.PrepaymentRatio ?? 0,
|
||||
// 预付金利率
|
||||
marginInterestRate = trade_snowball.PrepaymentInterestRate ?? 0,
|
||||
// 数量??
|
||||
amount = td.Notional,
|
||||
// 是否已敲入
|
||||
isKnockedIn = trade_snowball.IsInitialKnockedIn,
|
||||
// 一组标的资产价格,每个价格都是一个情景
|
||||
spots = new double[] { spotPrice },
|
||||
// 情景个数??
|
||||
scenarioCount = 1,
|
||||
// 无风险利率
|
||||
r = td.NoRiskRate ?? 0,
|
||||
// 分红率
|
||||
q = td.DividendRate ?? 0,
|
||||
// 波动率
|
||||
v = vol,
|
||||
// 计算指标,逗号隔开
|
||||
greekType = "PV,DELTA,GAMMA,VEGA,THETA,RHO"
|
||||
};
|
||||
return calcIn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加日志
|
||||
/// </summary>
|
||||
/// <param name="trade"></param>
|
||||
/// <param name="scenarioEnum"></param>
|
||||
/// <param name="tradeNumber"></param>
|
||||
private static void AddCalculationLog(CalcIn CalcParams, CalcScenarioEnum calcScenario, double? targetPv = null, bool? isNan = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
// 添加计算日志
|
||||
var log = new CalculationLog()
|
||||
{
|
||||
CreateTime = DateTime.Now,
|
||||
Scenario = calcScenario,
|
||||
LogObject = CalcParams,
|
||||
//TradeNumber = tradeNumber,
|
||||
//Notional = CalcParams.amount,
|
||||
//Exercise = CalcParams..ToString(),
|
||||
//OptionType = CalcParams?.OptionType.ToString(),
|
||||
//TradeDate = trade.TradeDate?.ToString(),
|
||||
//MaturityDate = trade.MaturityDate?.ToString(),
|
||||
//InitialSpotPrice = option?.InitialSpotPrice.ToString(),
|
||||
//Strike = option?.Strike.ToString(),
|
||||
targetPv = targetPv,
|
||||
isNan = isNan
|
||||
};
|
||||
|
||||
ICalculationLogService calcLogService = new CalculationLogService();
|
||||
calcLogService.AddLog(log, calcScenario);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取交易日历名称
|
||||
/// </summary>
|
||||
/// <param name="underlyingCode"></param>
|
||||
/// <returns></returns>
|
||||
private static string GetMarket(string underlyingCode)
|
||||
{
|
||||
var underlyingManager = DataCacheModule.DataCacheManager.GetUnderlyingDataSource().GetData(underlyingCode);
|
||||
var market = DataCacheModule.DataCacheManager.GetMarketDataSource().AsQueryable(g => g.MarketName == underlyingManager.MarketName)?.FirstOrDefault();
|
||||
return market?.CalendarName ?? "chn";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 股票交易计算帮助类
|
||||
/// </summary>
|
||||
public class StockTradeCalcHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 股票印花税
|
||||
/// </summary>
|
||||
private static double GetStockTradeStampDuty(ExchangeTrade trade, StockCommissionConfig config)
|
||||
{
|
||||
if (trade.TradeType == "股票" && trade.Notional != 0.0)
|
||||
{
|
||||
if (config != null && config.StampDutyType != null && config.StampDutyType.Contains(CovertStockTradeSide(trade.TradeSide)))
|
||||
{
|
||||
return Math.Max((config.StampDuty ?? 0.0) / 100 * (trade.TradeSinglePrice) * trade.Notional, config.MinStampDuty ?? 0.0);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 股票佣金
|
||||
/// </summary>
|
||||
private static double GetStockTradeCommission(ExchangeTrade trade, StockCommissionConfig config)
|
||||
{
|
||||
if (trade.TradeType == "股票" && trade.Notional != 0.0)
|
||||
{
|
||||
if (config != null && config.CommissionType != null && config.CommissionType.Contains(CovertStockTradeSide(trade.TradeSide)))
|
||||
{
|
||||
return Math.Max((config.Commission ?? 0.0) / 100 * (trade.TradeSinglePrice) * trade.Notional, config.MinCommission ?? 0.0);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
private static string CovertStockTradeSide(string tradeSide)
|
||||
{
|
||||
if ("买入".Equals(tradeSide) || "卖出".Equals(tradeSide))
|
||||
{
|
||||
return tradeSide;
|
||||
}
|
||||
if ("多头开仓".Equals(tradeSide) || "空头平仓".Equals(tradeSide))
|
||||
{
|
||||
return "买入";
|
||||
}
|
||||
return "卖出";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 股票交易费
|
||||
/// </summary>
|
||||
private static double GetStockTradeTransferFee(ExchangeTrade trade, StockCommissionConfig config, bool actualTrade = true)
|
||||
{
|
||||
//交易费只上交所收取
|
||||
var um = DataCacheProvider.GetUnderlyingDataSource().GetData(trade.UnderlyingId);
|
||||
if (trade.TradeType == "股票" && (um == null || !"SZ".Equals(um.MarketCode)) && actualTrade && trade.Notional != 0.0)
|
||||
{
|
||||
if (config != null && config.TransferFeeType != null && config.TransferFeeType.Contains(CovertStockTradeSide(trade.TradeSide)))
|
||||
{
|
||||
var Fee = Math.Max((config.TransferFee ?? 0.0) / 100 * (trade.TradeSinglePrice) * trade.Notional, config.MinTransferFee ?? 0.0);
|
||||
if (trade.Notional <= 1000)
|
||||
{
|
||||
return Math.Max(Fee, config.MinTransferFee ?? 0.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Fee;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 股票对冲其他费用
|
||||
/// </summary>
|
||||
private static double GetStockTradeOtherExpenses(ExchangeTrade trade, StockCommissionConfig config)
|
||||
{
|
||||
if (trade.TradeType == "股票" && trade.Notional != 0.0)
|
||||
{
|
||||
if (config != null && config.OtherExpensesType != null && config.OtherExpensesType.Contains(CovertStockTradeSide(trade.TradeSide)))
|
||||
{
|
||||
return Math.Min(Math.Max((config.OtherExpenses ?? 0.0) / 100 * (trade.TradeSinglePrice) * trade.Notional, config.MinOtherExpenses ?? 0.0), config.MaxOtherExpenses ?? 0.0);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取交易手续费
|
||||
/// </summary>
|
||||
public static double GetStockAllTradeExpenses(ExchangeTrade trade, StockCommissionConfig config, bool actualTrade = true)
|
||||
{
|
||||
return GetStockTradeStampDuty(trade, config) + GetStockTradeCommission(trade, config) + GetStockTradeTransferFee(trade, config, actualTrade) + GetStockTradeOtherExpenses(trade, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,464 @@
|
||||
using Qdp.ComputeServiceV2.Data.CommonModels.TradeInfos;
|
||||
using Qdp.Foundation.Implementations;
|
||||
using Qdp.Pricing.Base.Utilities;
|
||||
using System.Runtime.CompilerServices;
|
||||
using YLErp.BLL;
|
||||
using YLErp.BLL.Calculation;
|
||||
using YLErp.QdpModule;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 交易计算帮助类
|
||||
/// </summary>
|
||||
public static class TradeCalcHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// [交易员角度]根据交易方向判断了结金额是否需要加符号
|
||||
/// <para>开仓费不适用 -- 开仓费是客户方向</para>
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetBuySellSign(string buySell)
|
||||
{
|
||||
switch (buySell)
|
||||
{
|
||||
case "卖出":
|
||||
case "融券卖出":
|
||||
case "多头平仓":
|
||||
case "空头开仓":
|
||||
return -1;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [交易员角度]根据交易方向判断了结金额是否需要加符号
|
||||
/// <para>开仓费不适用 -- 开仓费是客户方向</para>
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetSign(string buySell)
|
||||
{
|
||||
return GetBuySellSign(buySell);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 远期计算(从交易员角度计算)
|
||||
/// </summary>
|
||||
public static void CalcForwardValue(OtcTrade trade, double spotPrice, out double pv, out double pnl)
|
||||
{
|
||||
//1.结算报告,期权价格 / 单价: 现价 - 交割价格
|
||||
//2.持仓市值: 期权价格* 持仓数量
|
||||
//3.持仓盈亏: 持仓市值 - 持仓数量比率 * 开仓总费用
|
||||
|
||||
pv = pnl = 0;
|
||||
|
||||
if (trade.Notional <= 1e-7)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var priceChange = 0d;
|
||||
|
||||
if (trade.OptionType == "看涨")
|
||||
{
|
||||
priceChange = spotPrice - (trade?.Strike ?? 0);
|
||||
}
|
||||
else if (trade.OptionType == "看跌")
|
||||
{
|
||||
priceChange = (trade?.Strike ?? 0) - spotPrice;
|
||||
}
|
||||
|
||||
//以交易员角度
|
||||
if (trade.BuySell == "卖出")
|
||||
{
|
||||
priceChange = -priceChange;
|
||||
}
|
||||
|
||||
pv = priceChange * trade.Notional;
|
||||
pnl = pv + (trade.OriginalNotional > 0 && trade.TradePrice >= 0 ? trade.TradePrice.Value * trade.Notional / trade.OriginalNotional.Value : 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对冲交易 根据结构类型 买卖方向 看涨看跌 获取持仓long short
|
||||
/// </summary>
|
||||
public static string GetHedgeLongShort(string TradeType, string BuySell)
|
||||
{
|
||||
switch (TradeType)
|
||||
{
|
||||
case "商品期货":
|
||||
case "场内期权":
|
||||
return BuySell.Contains("多头") ? "long" : "short";
|
||||
case "股票":
|
||||
default: return "long";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据期权交易获取QDP Trade
|
||||
/// </summary>
|
||||
public static TradeBase GetQdpTrade(trade tr, DateTime? valueDate = null, string fixings = null)
|
||||
{
|
||||
if (!valueDate.HasValue)
|
||||
{
|
||||
valueDate = valuedateBLL.ValueDate;
|
||||
}
|
||||
|
||||
TradeBase tempTrade = null;
|
||||
|
||||
var sysRiskRate = valuedateBLL.RiskFreeRate / 100;
|
||||
|
||||
OptionTradeParamRequest getRequest()
|
||||
{
|
||||
return new OptionTradeParamRequest(sysRiskRate)
|
||||
{
|
||||
tradeId = tr.TradeNumber,
|
||||
fixings = null,
|
||||
hasNightMarket = false,
|
||||
maturityShift = 0,
|
||||
ParamOverride = null,
|
||||
preciseTimeMode = false,
|
||||
timeToMaturityDays = double.NaN,
|
||||
volSurfaceNames = null
|
||||
};
|
||||
}
|
||||
|
||||
switch (tr.TradeType)
|
||||
{
|
||||
case "自定义交易":
|
||||
{
|
||||
tempTrade = new ManualTrade(tr.id.ToString(), new Date(tr.TradeDate.Value.Date),
|
||||
new Date(tr.StartDate ?? valueDate.Value), new Date(tr.MaturityDate ?? valueDate.Value),
|
||||
QdpConverter.ConvertTradeType(tr.BuySell), tr.Notional, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
case "收益互换":
|
||||
{
|
||||
tempTrade = new SwapTrade(tr.id.ToString(), new Date(tr.TradeDate.Value.Date),
|
||||
new Date(tr.StartDate ?? valueDate.Value), new Date(tr.MaturityDate ?? valueDate.Value),
|
||||
QdpConverter.ConvertTradeType(tr.BuySell), tr.Notional, tr.SpotPrice ?? 0);
|
||||
break;
|
||||
}
|
||||
|
||||
case "亚式期权":
|
||||
{
|
||||
var request = getRequest();
|
||||
request.fixings = fixings.TrimToNull() ?? AsianOptionFixingService.GetFixingString(valueDate.Value, tr);
|
||||
tempTrade = QdpTradeBuilder.GetAsianOptionTrade(tr, tr.trade_asian_option, request);
|
||||
break;
|
||||
}
|
||||
|
||||
case "彩虹期权":
|
||||
tempTrade = QdpTradeBuilder.GetRainbowOptionTrade(tr, tr.trade_rainbow_option, getRequest());
|
||||
break;
|
||||
|
||||
case "合成价差期权":
|
||||
tempTrade = QdpTradeBuilder.GetSSpreadOptionTrade(tr, getRequest());
|
||||
break;
|
||||
|
||||
case "香草期权":
|
||||
tempTrade = QdpTradeBuilder.GetVanillaOptionTrade(tr, getRequest(), false);
|
||||
break;
|
||||
|
||||
case "Risky期权":
|
||||
tempTrade = QdpTradeBuilder.GetVanillaOptionTrade(tr, getRequest(), false);
|
||||
break;
|
||||
|
||||
case "障碍期权":
|
||||
tempTrade = QdpTradeBuilder.GetBarrierOptionTrade(tr, tr.trade_barrier_option, getRequest());
|
||||
break;
|
||||
|
||||
case "二元期权":
|
||||
|
||||
tempTrade = QdpTradeBuilder.GetBinaryOptionTrade(tr, tr.trade_binary_option, getRequest());
|
||||
break;
|
||||
|
||||
case "双鲨期权":
|
||||
tempTrade = QdpTradeBuilder.GetDoubleSharkFinOptionTrade(tr, tr.trade_double_sharkfin_option, getRequest());
|
||||
break;
|
||||
|
||||
case "凤凰期权":
|
||||
tempTrade = QdpTradeBuilder.GetAutocallOptionTrade(tr, tr.trade_autocall, getRequest());
|
||||
break;
|
||||
|
||||
case "雪球期权":
|
||||
tempTrade = QdpTradeBuilder.GetSnowballOptionTrade(tr, tr.trade_snowball, getRequest());
|
||||
break;
|
||||
|
||||
case "区间累积期权":
|
||||
tempTrade = QdpTradeBuilder.GetRangeAccrualTrade(tr, tr.trade_rangeaccrual, getRequest());
|
||||
break;
|
||||
|
||||
case "气囊结构":
|
||||
tempTrade = QdpTradeBuilder.GetAirbagOptionTrade(tr, tr.trade_airbag, getRequest());
|
||||
break;
|
||||
|
||||
case "累计期权":
|
||||
{
|
||||
var req = getRequest();
|
||||
req.happenedObservations = ObservationDataService.QueryDatas(tr.id, valueDate.Value);
|
||||
tempTrade = QdpTradeBuilder.GetAccumulatorOptionTrade(tr, tr.trade_accumulator_option, req);
|
||||
}
|
||||
break;
|
||||
|
||||
case "收益增强结构":
|
||||
tempTrade = QdpTradeBuilder.GetUnderlyingEnhanceTrade(tr, tr.trade_underlying_enhance, getRequest());
|
||||
break;
|
||||
case "现金流交易":
|
||||
tempTrade = QdpTradeBuilder.GetCashFlowTrade(tr, tr.trade_cashflow, getRequest());
|
||||
break;
|
||||
}
|
||||
|
||||
return tempTrade;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据valuedate配置判断是否使用收盘价进行结算
|
||||
/// </summary>
|
||||
public static bool UseClosePrice()
|
||||
{
|
||||
return ConsGlobal.SettlePriceMode.UseClosePrice(valuedateBLL.SystemDate.EodSettlePriceMode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据valuedate配置获取收盘结算价类型
|
||||
/// </summary>
|
||||
public static SettlementTypeEnum GetSettlementType()
|
||||
{
|
||||
return ConsGlobal.SettlePriceMode.GetSettlementType(valuedateBLL.SystemDate.EodSettlePriceMode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static string GetTradeStatus(trade trade, trade_cash tradeCash)
|
||||
{
|
||||
if (tradeCash.Action == "系统操作-平仓费" && (tradeCash.UnwindType == "部分平仓" || tradeCash.UnwindType == "全部平仓"))
|
||||
{
|
||||
return "已平仓";
|
||||
}
|
||||
if (tradeCash.Action == "系统操作-行权费" && tradeCash.ExerciseWay == "到期行权" && tradeCash.UnwindType == "到期")
|
||||
{
|
||||
return "已到期";
|
||||
}
|
||||
if ((tradeCash.Action == "系统操作-平仓费" && tradeCash.UnwindType == "部分行权") || tradeCash.Action == "系统操作-行权费")
|
||||
{
|
||||
return "已行权";
|
||||
}
|
||||
return trade.TradeStatus;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 以交易员角度计算盈亏
|
||||
/// </summary>
|
||||
/// <param name="buySell">交易方向</param>
|
||||
/// <param name="tradePrice">交易总额</param>
|
||||
/// <param name="tcUnwindPercent">平仓比例</param>
|
||||
/// <param name="tcAmount">平仓收支</param>
|
||||
public static double CalcWinLoss(string tradeType, string buySell, double tradePrice, double tcUnwindPercent, double tcAmount)
|
||||
{
|
||||
int costSign = 1; //收入计为1,支出计为-1
|
||||
|
||||
if (tradeType != "远期")
|
||||
{
|
||||
switch (buySell)
|
||||
{
|
||||
case "卖出":
|
||||
case "融券卖出":
|
||||
case "多头平仓":
|
||||
case "空头开仓": break;
|
||||
default:
|
||||
costSign = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return tcAmount + tradePrice * tcUnwindPercent * costSign;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算单笔交易或多笔交易实现盈亏
|
||||
/// </summary>
|
||||
/// <param name="td"></param>
|
||||
/// <param name="tc"></param>
|
||||
/// <param name="IsUsePremiumRate"></param>
|
||||
/// <param name="CountRatio"></param>
|
||||
/// <returns></returns>
|
||||
public static double CalcChildTradeSumWinLoss(trade td, trade_cash tc, bool IsUsePremiumRate = false, int CountRatio = 0)
|
||||
{
|
||||
using (var db = DbContextFactory.GetYLDbContext())
|
||||
{
|
||||
double TcTradePrice = 0;
|
||||
|
||||
if (td.IsGroup == 1)
|
||||
{
|
||||
var childTradeCashs = db.trade_cash.Where(x => x.ParentTradeCashId == tc.id).ToList();
|
||||
var childTradeIds = childTradeCashs.Select(x => x.TradeId).Distinct().ToList();
|
||||
var childTrades = db.trade.Where(x => childTradeIds.Contains(x.id)).ToList();
|
||||
|
||||
childTradeCashs.ForEach(x =>
|
||||
{
|
||||
var trade = childTrades.FirstOrDefault(y => y.id == x.TradeId);
|
||||
TcTradePrice += (x.UnwindPercentRate * trade?.TradePrice * (trade?.BuySell == "买入" ? -1 : 1)) ?? 0;
|
||||
});
|
||||
return tc.Amount + TcTradePrice;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsUsePremiumRate)
|
||||
{
|
||||
return (tc.Amount + (td.BuySell == "卖出" ? 1 : -1) * ((tc?.UnwindTradeAmount ?? 0) * CountRatio / td.OriginalNotional) * td.TradePrice) ?? 0;
|
||||
}
|
||||
|
||||
return (tc.Amount + (td.BuySell == "卖出" ? 1 : -1) * (tc?.UnwindPercentRate ?? 0) * td?.TradePrice) ?? 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算TTM
|
||||
/// </summary>
|
||||
public static double CalculateTTMDays(DateTime from, DateTime to, int varietyid, bool precisionOfMinute, DateTime? serverDateTime = null)
|
||||
{
|
||||
var dayCount = valuedateBLL.TradeDayCount.ToDayCountImpl();
|
||||
var variety = DataCacheProvider.GetVarietyDataSource().GetData(varietyid);
|
||||
if (serverDateTime == null)
|
||||
{
|
||||
serverDateTime = DateTime.Now;
|
||||
}
|
||||
return QdpHelper.CalculateTTMDays(from, to, valuedateBLL.ValueDate, serverDateTime.Value, dayCount, variety != null && variety.HasNightMarket, precisionOfMinute);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算TTM
|
||||
/// </summary>
|
||||
public static double CalculateTTMDaysForXiangYu(DateTime from, DateTime to, int varietyid, bool precisionOfMinute, DateTime? serverDateTime = null)
|
||||
{
|
||||
var dayCount = valuedateBLL.TradeDayCount.ToDayCountImpl();
|
||||
var variety = DataCacheProvider.GetVarietyDataSource().GetData(varietyid);
|
||||
if (serverDateTime == null)
|
||||
{
|
||||
serverDateTime = DateTime.Now;
|
||||
}
|
||||
return QdpHelper.CalculateTTMDaysForXiangYu(from, to, valuedateBLL.ValueDate, serverDateTime.Value, dayCount, variety != null && variety.HasNightMarket, precisionOfMinute);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 传入OTC买卖方向判断是否买入方向
|
||||
/// </summary>
|
||||
public static bool IsBuy(string buySell)
|
||||
{
|
||||
return buySell == "Buy" || buySell == "买入" || string.IsNullOrWhiteSpace(buySell);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于计算成交数量(虚拟),如果countRatio传入的是1,就可以用来计算成交份额
|
||||
/// </summary>
|
||||
public static double GetTradeAmountV(OtcTradeBase trade, int? countRatio = null)
|
||||
{
|
||||
return InnerGetTradeAmountV(trade, null, countRatio);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于计算成交数量(虚拟),如果countRatio传入的是1,就可以用来计算成交份额
|
||||
/// </summary>
|
||||
public static double GetTradeAmountV(OtcTradeBase trade, double notional, int? countRatio = null)
|
||||
{
|
||||
return InnerGetTradeAmountV(trade, notional, countRatio);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于计算成交数量(实际),如果countRatio传入的是1,就可以用来计算成交份额
|
||||
/// </summary>
|
||||
public static double GetTradeAmount(OtcTradeBase trade, double notional, int? countRatio = null)
|
||||
{
|
||||
return InnerGetTradeAmount(trade, notional, countRatio);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
private static double InnerGetTradeAmountV(OtcTradeBase trade, double? notional, int? countRatio)
|
||||
{
|
||||
if (trade is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(trade));
|
||||
}
|
||||
|
||||
var CountRatio = countRatio ?? trade.CountRatio ?? 0;
|
||||
|
||||
if (CountRatio < 1)
|
||||
{
|
||||
CountRatio = DataCacheProvider.GetUnderlyingDataSource().GetData(trade.UnderlyingCode)?.CountRatio ?? 1;
|
||||
}
|
||||
|
||||
if (trade.TradeType == "累计期权")
|
||||
{
|
||||
return (notional ?? trade.OriginalNotional ?? 0) / CountRatio;
|
||||
}
|
||||
|
||||
if (!notional.HasValue)
|
||||
{
|
||||
var SpotPrice = trade.SpotPrice ?? 0;
|
||||
|
||||
if (Math.Abs(SpotPrice) > 0)
|
||||
{
|
||||
return (trade.OriginalStockEqvNotional ?? 0) / Math.Abs(SpotPrice) / CountRatio;
|
||||
}
|
||||
}
|
||||
|
||||
var annRate = (trade.ParticipationRate ?? 1) * (trade.AnnualizeFactor ?? 1);
|
||||
|
||||
if (annRate < 1e-8)
|
||||
{
|
||||
annRate = 1;
|
||||
}
|
||||
|
||||
return (notional ?? trade.OriginalNotional ?? 0) / annRate / CountRatio;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
private static double InnerGetTradeAmount(OtcTradeBase trade, double? notional, int? countRatio)
|
||||
{
|
||||
if (trade is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(trade));
|
||||
}
|
||||
|
||||
var CountRatio = countRatio ?? trade.CountRatio ?? 0;
|
||||
|
||||
if (CountRatio < 1)
|
||||
{
|
||||
CountRatio = DataCacheProvider.GetUnderlyingDataSource().GetData(trade.UnderlyingCode)?.CountRatio ?? 1;
|
||||
}
|
||||
|
||||
if (trade.TradeType == "累计期权")
|
||||
{
|
||||
return (notional ?? trade.OriginalNotional ?? 0) / CountRatio;
|
||||
}
|
||||
|
||||
if (!notional.HasValue)
|
||||
{
|
||||
var SpotPrice = trade.SpotPrice ?? 0;
|
||||
|
||||
if (Math.Abs(SpotPrice) > 0)
|
||||
{
|
||||
return (trade.OriginalStockEqvNotional ?? 0) / Math.Abs(SpotPrice) / CountRatio;
|
||||
}
|
||||
}
|
||||
|
||||
var annRate = (trade.ParticipationRate ?? 1) * (trade.AnnualizeFactor ?? 1);
|
||||
|
||||
if (annRate < 1e-8)
|
||||
{
|
||||
annRate = 1;
|
||||
}
|
||||
|
||||
return (notional ?? trade.OriginalNotional ?? 0) * annRate / CountRatio;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using CsvHelper;
|
||||
using NPOI.OpenXmlFormats.Dml;
|
||||
using Qdp.Foundation.Implementations;
|
||||
using Qdp.Pricing.Base.Enums;
|
||||
using Qdp.Pricing.Base.Implementations;
|
||||
using Qdp.Pricing.Base.Utilities;
|
||||
using Qdp.Pricing.Ecosystem.Trade.Options;
|
||||
using Qdp.Pricing.Library.Options.Products.Autocall.Phoenix;
|
||||
using Qdp.Pricing.Library.Options.Products.Autocall.Snowball;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using YLErp.Abstract;
|
||||
using YLErp.BLL;
|
||||
using YLErp.BLL.Eod;
|
||||
using YLErp.DBModels;
|
||||
using YLErp.DBModels.Helpers;
|
||||
using YLErp.Model;
|
||||
using YLErp.Models;
|
||||
using YLErp.Modules.CalculationModule.Abstract;
|
||||
using YLErp.Modules.TradeModule;
|
||||
using YLErp.Modules.TradeModule.AccumulatorOptionModule;
|
||||
using YLErp.Modules.TradeModule.KnockOutModule;
|
||||
using YLErp.Modules.TradeModule.KnockOutModule.Dto;
|
||||
using YLErp.QdpModule;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 交易敲出收益计算服务
|
||||
/// </summary>
|
||||
public class TradeKnockOutPayoffCalcService : ITradeKnockOutPayoffCalcService
|
||||
{
|
||||
|
||||
private ITradeExtendDataProvider _tradeExtendDataProvider;
|
||||
private DateTime _valueDate;
|
||||
|
||||
public TradeKnockOutPayoffCalcService(ITradeExtendDataProvider tradeExtendDataProvider, DateTime valueDate)
|
||||
{
|
||||
_tradeExtendDataProvider = tradeExtendDataProvider;
|
||||
_valueDate = valueDate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于定价页面 敲出收益计算 此时交易未入库
|
||||
/// </summary>
|
||||
/// <param name="valueDate"></param>
|
||||
public TradeKnockOutPayoffCalcService(DateTime valueDate)
|
||||
{
|
||||
this._tradeExtendDataProvider = null;
|
||||
_valueDate = valueDate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取敲出payoff
|
||||
/// </summary>
|
||||
/// <param name="td"></param>
|
||||
/// <param name="underlyingPrice"></param>
|
||||
/// <returns></returns>
|
||||
public GetKnockOutPayoffResult GetKnockOutPayoff(trade td,double underlyingPrice)
|
||||
{
|
||||
GetKnockOutPayoffResult result = new GetKnockOutPayoffResult { IsKnockOut = false };
|
||||
if (td.ExerciseDate < _valueDate)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
ITradeKnockOutService knockOutService = null;
|
||||
switch (td.TradeType)
|
||||
{
|
||||
case "障碍期权":
|
||||
knockOutService = new BarrierOptionTradeKnockOutService();
|
||||
break;
|
||||
case "二元期权":
|
||||
if ("American".Equals(td.ExerciseMode))
|
||||
{
|
||||
knockOutService = new AmericanBinaryOptionTradeKnockOutService();
|
||||
}
|
||||
break;
|
||||
case "双鲨期权":
|
||||
knockOutService = new DoubleSharkTradeKnockOutService();
|
||||
break;
|
||||
case "凤凰期权":
|
||||
knockOutService = new AutocallTradeKnockOutService();
|
||||
break;
|
||||
case "雪球期权":
|
||||
knockOutService = new SnowBallTradeKnockOutService();
|
||||
break;
|
||||
case "累计期权":
|
||||
knockOutService=new AccumulatorTradeKnockOutService();
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if(knockOutService != null)
|
||||
{
|
||||
if (this._tradeExtendDataProvider != null)
|
||||
{
|
||||
return knockOutService.GetKnockOutPayoff(td, underlyingPrice, _valueDate, _tradeExtendDataProvider);
|
||||
}
|
||||
else
|
||||
{
|
||||
return knockOutService.GetKnockOutPayoff(td, underlyingPrice, _valueDate);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using YLErp.DBModels.Helpers;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 交易手数计算
|
||||
/// </summary>
|
||||
public static class TradeLotsCalc
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据标的计算手数,返回null表示未获取到合约乘数
|
||||
/// </summary>
|
||||
public static double GetLots(string underlyingCode, double notional)
|
||||
{
|
||||
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(underlyingCode);
|
||||
|
||||
if (underlying == null)
|
||||
{
|
||||
return notional;
|
||||
}
|
||||
|
||||
double contractSize;
|
||||
|
||||
if (underlying.ContractSize > 0)
|
||||
{
|
||||
contractSize = underlying.ContractSize;
|
||||
}
|
||||
else if (underlying.IsStock())
|
||||
{
|
||||
contractSize = 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
var variety = DataCacheProvider.GetVarietyDataSource().GetData(underlying?.UnderlyingTypeId ?? 0);
|
||||
if (variety == null)
|
||||
{
|
||||
return notional;
|
||||
}
|
||||
contractSize = variety.TradeUnitValue ?? 0;
|
||||
}
|
||||
|
||||
if (contractSize < 1)
|
||||
{
|
||||
return notional;
|
||||
}
|
||||
|
||||
return notional / contractSize;
|
||||
}
|
||||
|
||||
public static double CalcDeltaInLots(double Delta, Variety variety, underlying_manager udm)
|
||||
{
|
||||
return Delta / GetTradeUnitValue(variety, udm);
|
||||
}
|
||||
|
||||
public static double CalcGammaInLots(double gamma, Variety variety, underlying_manager udm)
|
||||
{
|
||||
return gamma / GetTradeUnitValue(variety, udm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取标的的交易手数单位,一手多少
|
||||
/// </summary>
|
||||
public static double GetTradeUnitValue(Variety variety, underlying_manager udm)
|
||||
{
|
||||
if (udm is null)
|
||||
{
|
||||
if (variety != null)
|
||||
{
|
||||
var TradeUnit = VarietyHelper.GetTradeUnitValue(variety.VarietyCode, variety.TradeUnit);
|
||||
return TradeUnit > 0 ? TradeUnit.Value : 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (udm.ContractSize > 1)
|
||||
{
|
||||
return udm.ContractSize;
|
||||
}
|
||||
|
||||
if (udm.IsStock())
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
|
||||
if (udm.IsSynthetic())
|
||||
{
|
||||
return udm.ContractSize < 1 ? 1 : udm.ContractSize;
|
||||
}
|
||||
|
||||
if (udm.ContractSize > 0)
|
||||
{
|
||||
return udm.ContractSize;
|
||||
}
|
||||
|
||||
if (variety == null)
|
||||
{
|
||||
variety = DataCacheProvider.GetVarietyDataSource().GetData(udm.UnderlyingTypeId);
|
||||
|
||||
if (variety == null)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
var TradeUnit2 = VarietyHelper.GetTradeUnitValue(variety.VarietyCode, variety.TradeUnit);
|
||||
return TradeUnit2 > 0 ? TradeUnit2.Value : 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,757 @@
|
||||
using NPOI.Util;
|
||||
using Qdp.Pricing.Base.Implementations;
|
||||
using YLErp.Abstract;
|
||||
using YLErp.Abstract.DataProviders;
|
||||
using YLErp.BLL;
|
||||
using YLErp.BLL.Calculation;
|
||||
using YLErp.DBModels.Enums;
|
||||
using YLErp.Enums;
|
||||
using YLErp.Modules.CalculationModule.Abstract;
|
||||
using YLErp.Modules.VolatilityModule;
|
||||
using YLErp.QdpModule;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 期权价值计算
|
||||
/// </summary>
|
||||
public class TradeRiskValueCalc
|
||||
{
|
||||
readonly OtcTradeBase _trade;
|
||||
readonly IOtcTradeValueCalcContext _context;
|
||||
readonly IPriceProvider _underlyingPriceProvider;
|
||||
|
||||
readonly int _maturityShift;
|
||||
|
||||
underlying_manager _underlying;
|
||||
underlying_manager[] _spreadUnderlyings;
|
||||
|
||||
readonly IOptionCalcDataProvider _dataProvider;
|
||||
readonly IUnderlyingDataProvider _underlyingDataProvider;
|
||||
readonly ITradeExtendDataProvider _tradeExtendDataProvider;
|
||||
readonly ITradeKnockOutPayoffCalcService _tradeKnockOutPayoffCalcService;
|
||||
|
||||
public TradeRiskValueCalc(IOtcTradeValueCalcContext context, OtcTradeBase trade)
|
||||
{
|
||||
_trade = trade ?? throw new ArgumentNullException(nameof(trade));
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
|
||||
_dataProvider = CalcCheckHelper.CheckOptionCalcDataProvider(context.DataProvider);
|
||||
_underlyingDataProvider = _dataProvider.UnderlyingDataProvider;
|
||||
_tradeExtendDataProvider = _dataProvider.TradeExtendDataProvider;
|
||||
_underlyingPriceProvider = _dataProvider.UnderlyingPriceProvider;
|
||||
|
||||
_maturityShift = 0;
|
||||
_tradeKnockOutPayoffCalcService = new TradeKnockOutPayoffCalcService(_tradeExtendDataProvider, _context.ValueDate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算衍生品价值
|
||||
/// </summary>
|
||||
public TradeValueResult GetTradeValue(out underlying_manager[] underlyingArr)
|
||||
{
|
||||
TradeValueResult result;
|
||||
|
||||
if (_trade.TradeType == "现金流交易")
|
||||
{
|
||||
result = InnerGetCashFlowTradeValue();
|
||||
|
||||
if (_context.CalcDeltaT1)
|
||||
{
|
||||
result.DeltaT1 = result.Delta;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = InnerGetTradeValue(null);
|
||||
}
|
||||
|
||||
underlyingArr = _spreadUnderlyings ?? Array.Empty<underlying_manager>();
|
||||
Array.Resize(ref underlyingArr, underlyingArr.Length + 1);
|
||||
underlyingArr[0] = _underlying;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算衍生品价值
|
||||
/// </summary>
|
||||
private TradeValueResult InnerGetTradeValue(underlying_manager unly)
|
||||
{
|
||||
//underlying
|
||||
_underlying = unly;
|
||||
|
||||
if (_underlying == null)
|
||||
{
|
||||
_underlying = _underlyingDataProvider.GetUnderlying(_trade.UnderlyingCode);
|
||||
if (_underlying == null)
|
||||
{
|
||||
throw new TradeCalcExpception(_trade.id, $"未获取到标的数据:{_trade.UnderlyingCode}");
|
||||
}
|
||||
_underlying.QuotationDate = _context.ValueDate;
|
||||
}
|
||||
|
||||
//结构化交易
|
||||
if (_trade.TradeType == "结构化交易")
|
||||
{
|
||||
throw new ServiceException($"不支持'结构化交易'主交易的计算,tradeId:{_trade.id},tradeNumber:{_trade.TradeNumber}");
|
||||
}
|
||||
|
||||
//准备计算
|
||||
_spreadUnderlyings = null;
|
||||
|
||||
//stock MaturityDate
|
||||
if (_underlying.UnderlyingInstrumentType == "Stock")
|
||||
{
|
||||
//股票默认到期日为行权日
|
||||
if (_trade.ExerciseDate != null)
|
||||
{
|
||||
_trade.MaturityDate = _trade.ExerciseDate;
|
||||
}
|
||||
}
|
||||
|
||||
//标的即期价格
|
||||
var blGetPrice = _underlyingPriceProvider.TryGetPrice(_trade.UnderlyingCode, out var spotPrice);
|
||||
|
||||
_underlying.Price = spotPrice;
|
||||
|
||||
//如果当天有维护过风险值,则直接获取风险值即可,未维护的风险值则通过系统计算
|
||||
var result = TradeRiskCalcUtil.GetManualOptionValue(_context.ValueDate, (trade)_trade, spotPrice, 0, false, _context.CalcScenario, volType: _context.VolType, isSettle: _context.CalcScenario == Enums.CalcScenarioEnum.EodSettlement);
|
||||
var optionValue = result.manual != null ? result.optionValue : null;
|
||||
//没有自定义风险维护值 或者 自定风险维护没有涵盖PV和所有希腊值(部分维护场景)
|
||||
//部分维护场景下,需要系统计算出未赋值的属性,进行合并返还
|
||||
if (result.manual == null || result.manual != null && !result.manual.IsPVAndAllGreek)
|
||||
{
|
||||
//开始计算
|
||||
switch (_trade.TradeType)
|
||||
{
|
||||
case "远期":
|
||||
if (PS.Config.ErpElement.ForwardTradePriceModel == Configuration.Enums.ForwardTradePriceModel.STANDARD && !string.IsNullOrWhiteSpace(_trade.BasisUnderlyingCode))
|
||||
{
|
||||
_underlyingPriceProvider.TryGetPrice(_trade.BasisUnderlyingCode, out var BasiseodPrice);
|
||||
spotPrice -= BasiseodPrice;
|
||||
}
|
||||
optionValue = ForwardradeCalcService.CalcValue(_trade, spotPrice);
|
||||
break;
|
||||
case "商品期货":
|
||||
case "股票":
|
||||
{
|
||||
var pv = spotPrice * _trade.Notional;
|
||||
optionValue = new TradeValueResult { Pv = pv, Delta = _trade.Notional, DeltaCash = pv };
|
||||
}
|
||||
break;
|
||||
case "自定义交易":
|
||||
{
|
||||
var positionVol = VolatilityHelper.GetTradeVol((trade)_trade, _context.ValueDate, _context.CalcScenario == CalcScenarioEnum.EodSettlement);
|
||||
optionValue = TradeRiskCalcUtil.GetManualOptionValue(_context.ValueDate, (trade)_trade, spotPrice, positionVol, true, _context.CalcScenario, isSettle: _context.CalcScenario == CalcScenarioEnum.EodSettlement).optionValue;
|
||||
}
|
||||
break;
|
||||
case "收益互换":
|
||||
{
|
||||
var trade_swap = _tradeExtendDataProvider.GetTrade_Swap(_trade.id);
|
||||
optionValue = PayoffSwapCalcService.CalcValue(_trade, _context.ValueDate, _underlyingPriceProvider, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//计算期权估值时包含了DeltaT1,所以不需要再次赋值
|
||||
if (optionValue == null)
|
||||
{
|
||||
if (!blGetPrice)
|
||||
{
|
||||
if (_trade.TradeType == "场内期权")
|
||||
{
|
||||
throw new ServiceException($"场内期权计算失败,期权代码:{_trade.ExchangeOptionCode},错误信息:期权标的'{_trade.UnderlyingCode}'缺少价格");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ServiceException($"{_trade.TradeType}计算失败,交易编号:{_trade.TradeNumber},标的:{_trade.UnderlyingCode},错误信息:缺少标的价格");
|
||||
}
|
||||
}
|
||||
optionValue = InnerGetOptionValue(spotPrice);
|
||||
}
|
||||
else if (_context.CalcDeltaT1)
|
||||
{
|
||||
optionValue.DeltaT1 = optionValue.Delta;
|
||||
}
|
||||
}
|
||||
|
||||
//系统计算结果和手动维护值合并
|
||||
TradeRiskCalcUtil.GetOptionValueWithManual(result.manual, optionValue);
|
||||
|
||||
if (optionValue != null)
|
||||
{
|
||||
optionValue.Strike = _trade.Strike ?? 0;
|
||||
optionValue.SpotPrice = spotPrice;
|
||||
optionValue.VegaCash = optionValue.VegaCash.IsNormalize() ? optionValue.VegaCash : (optionValue.Vega * optionValue.SpotPrice).Normalize();
|
||||
|
||||
optionValue.NPv = optionValue.Pv;
|
||||
optionValue.NRoundedPv = optionValue.RoundedPv;
|
||||
if (PS.Config.ErpElement.IsPVIncludePrincipal)
|
||||
{
|
||||
optionValue.Pv += _trade.PrincipalSum() * (_trade.BuySell == "卖出" ? -1 : 1);
|
||||
optionValue.RoundedPv += _trade.PrincipalSum() * (_trade.BuySell == "卖出" ? -1 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
return optionValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取场外期权价值计算结果
|
||||
/// </summary>
|
||||
private TradeValueResult InnerGetOptionValue(double spotPrice)
|
||||
{
|
||||
var pricingRequest = _context.GetPricingRequest(_trade);
|
||||
if (pricingRequest == PricingRequest.None)
|
||||
{
|
||||
return new TradeValueResult
|
||||
{
|
||||
TradeId = _trade.id,
|
||||
UnderlyingCode = _trade.UnderlyingCode,
|
||||
BuySell = _trade.BuySell,
|
||||
UnderlyingId = _trade.UnderlyingId,
|
||||
ErrorMessage = "PricingRequest.None"
|
||||
};
|
||||
}
|
||||
|
||||
var qdpTradeId = _context.MarketProxy.NextRequestId() + "_" +
|
||||
(_trade.id > 0 ? _trade.id.ToString() : string.IsNullOrWhiteSpace(_trade.ExchangeOptionCode) ? _trade.UnderlyingCode : _trade.ExchangeOptionCode);
|
||||
|
||||
//准备波动率
|
||||
if (!_context.PrepareVolatility(qdpTradeId, _trade, spotPrice, out var volsurfaceNames))
|
||||
{
|
||||
return new TradeValueResult(false)
|
||||
{
|
||||
UnderlyingCode = _underlying.UnderlyingCode,
|
||||
ErrorMessage = "没有波动率",
|
||||
FailReason = TradeValueFailReason.missingVol
|
||||
};
|
||||
}
|
||||
|
||||
TradeValueResult result = null;
|
||||
var tpRequest = new OptionTradeParamRequest(_context.SysRiskFreeRate)
|
||||
{
|
||||
tradeId = qdpTradeId,
|
||||
preciseTimeMode = _context.IsPreciseTimeMode,
|
||||
fixings = null,
|
||||
maturityShift = _maturityShift,
|
||||
volSurfaceNames = volsurfaceNames,
|
||||
hasNightMarket = false,
|
||||
isEodCalc = _context.IsEodCalc,
|
||||
timeToMaturityDays = double.NaN,
|
||||
ParamOverride = p =>
|
||||
{
|
||||
p.riskFreeRate = _context.GetRiskFreeRate(_trade);
|
||||
p.dividendRate = _context.GetDividendRate(_trade);
|
||||
}
|
||||
};
|
||||
var nextDay = QdpCalendarHelper.BizDayShift(valuedateBLL.ValueDate, 1);
|
||||
//象屿最后一个交易日实时计算时TTM需要和平仓时算法一致
|
||||
if (PS.Config.Is厦门象屿
|
||||
&& (_context.CalcScenario == CalcScenarioEnum.RealtimePosition
|
||||
|| _context.CalcScenario == CalcScenarioEnum.RealtimeRisk)
|
||||
&& _trade.SettlementType == SettlementTypeEnum.ReferencePrice)
|
||||
{
|
||||
tpRequest.timeToMaturityDays = TradeCalcHelper.CalculateTTMDays(valuedateBLL.ValueDate, _trade.ExerciseDate.Value, _underlying.UnderlyingTypeId, false);
|
||||
}
|
||||
|
||||
var variety = new Variety();
|
||||
if (_underlying.IsFutures())
|
||||
{
|
||||
variety = DataCacheProvider.GetVarietyDataSource().GetData(_underlying.UnderlyingTypeId);
|
||||
tpRequest.hasNightMarket = variety != null && variety.HasNightMarket;
|
||||
}
|
||||
|
||||
OptionCalcParam<T> getOptionCalcParam<T>(T tradeParam, double[] spotPrices = null) where T : OptionTradeParamBase
|
||||
{
|
||||
return new OptionCalcParam<T>(tradeParam)
|
||||
{
|
||||
engineName = null,
|
||||
pricingRequest = pricingRequest,
|
||||
spotPrices = spotPrices ?? new[] { spotPrice },
|
||||
calcScenario = _context.CalcScenario,
|
||||
CalcDeltaT1 = _context.CalcDeltaT1
|
||||
};
|
||||
}
|
||||
|
||||
var marketProxy = _context.MarketProxy;
|
||||
|
||||
//东证润和是精确时间模式参与计算,收盘的话,刚好是整数天,所以不需要特殊处理
|
||||
if (PS.Config.Is润和 && !tpRequest.isEodCalc && double.IsNaN(tpRequest.timeToMaturityDays))
|
||||
{
|
||||
tpRequest.timeToMaturityDays = TradeCalcHelper.CalculateTTMDays(marketProxy.ValueDate, _trade.ExerciseDate.Value, 0, false);
|
||||
}
|
||||
|
||||
//开始计算
|
||||
switch (_trade.TradeType)
|
||||
{
|
||||
case "香草期权":
|
||||
{
|
||||
var tradeParam = QdpTradeBuilder.GetVanillaOptionTradeParam(_trade, tpRequest, false);
|
||||
result = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
||||
}
|
||||
break;
|
||||
case "Risky期权":
|
||||
{
|
||||
var riskyOption = _tradeExtendDataProvider.GetTrade_Risky_Option(_trade.id);
|
||||
result = GetOptionRisky(_trade, riskyOption, tpRequest, _underlying, marketProxy, pricingRequest, spotPrice, _context.CalcScenario, _context.CalcDeltaT1);
|
||||
}
|
||||
break;
|
||||
case "彩虹期权":
|
||||
{
|
||||
var tradeParam = GetRainbowOptionTradeParam(tpRequest, out var spotPrices);
|
||||
if (tradeParam == null)
|
||||
{
|
||||
return new TradeValueResult { UnderlyingCode = _underlying.UnderlyingCode };
|
||||
}
|
||||
result = TradeRiskCalcUtil.GetRainbowOptionValue(marketProxy, getOptionCalcParam(tradeParam, spotPrices));
|
||||
}
|
||||
break;
|
||||
case "亚式期权":
|
||||
{
|
||||
if (!PS.Config.Is润和 && !tpRequest.isEodCalc && double.IsNaN(tpRequest.timeToMaturityDays))
|
||||
{
|
||||
tpRequest.timeToMaturityDays = TradeCalcHelper.CalculateTTMDays(marketProxy.ValueDate, _trade.ExerciseDate.Value, variety.id, false);
|
||||
}
|
||||
var tradeParam = GetAsianOptionCalcParam(tpRequest, spotPrice);
|
||||
result = TradeRiskCalcUtil.GetAsianOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
||||
}
|
||||
break;
|
||||
case "障碍期权":
|
||||
{
|
||||
var barrierOption = _tradeExtendDataProvider.GetTrade_Barrier_Option(_trade.id)
|
||||
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的障碍期权数据");
|
||||
var tradeParam = QdpTradeBuilder.GetBarrierOptionTradeParam(_trade, barrierOption, tpRequest);
|
||||
result = TradeRiskCalcUtil.GetBarrierOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
||||
}
|
||||
break;
|
||||
case "二元期权":
|
||||
{
|
||||
var binaryOption = _tradeExtendDataProvider.GetTrade_Binary_Option(_trade.id)
|
||||
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的二元期权数据");
|
||||
var tradeParam = QdpTradeBuilder.GetBinaryOptionTradeParam(_trade, binaryOption, tpRequest);
|
||||
result = TradeRiskCalcUtil.GetBinaryOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
||||
}
|
||||
break;
|
||||
case "价差期权":
|
||||
{
|
||||
var tradeParam = GetSpreadOptionCalcParam(tpRequest, out var spotPrices);
|
||||
if (tradeParam == null)
|
||||
{
|
||||
return new TradeValueResult { UnderlyingCode = _underlying.UnderlyingCode };
|
||||
}
|
||||
result = TradeRiskCalcUtil.GetSpreadOptionValue(marketProxy, getOptionCalcParam(tradeParam, spotPrices));
|
||||
}
|
||||
break;
|
||||
case "场内期权":
|
||||
{
|
||||
var tradeParam = QdpTradeBuilder.GetVanillaOptionTradeParam(_trade, tpRequest, true);
|
||||
result = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
||||
}
|
||||
break;
|
||||
case "合成价差期权":
|
||||
{
|
||||
var tradeParam = QdpTradeBuilder.GetSSpreadOptionTradeParam(_trade, tpRequest);
|
||||
result = TradeRiskCalcUtil.GetSSpreadOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
||||
}
|
||||
break;
|
||||
case "亚式合成价差期权":
|
||||
{
|
||||
var tradeParam = GetAsianOptionCalcParam(tpRequest, spotPrice);
|
||||
result = TradeRiskCalcUtil.GetAsianSSpreadOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
||||
}
|
||||
break;
|
||||
case "双鲨期权":
|
||||
{
|
||||
var dbsharkfinOption = _tradeExtendDataProvider.GetTrade_Double_SharkFin_Option(_trade.id)
|
||||
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的双鲨期权数据");
|
||||
var tradeParam = QdpTradeBuilder.GetDoubleSharkFinOptionTradeParam(_trade, dbsharkfinOption, tpRequest);
|
||||
result = TradeRiskCalcUtil.GetDoubleSharkFinOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
||||
}
|
||||
break;
|
||||
case "凤凰期权":
|
||||
{
|
||||
var autocallOption = _tradeExtendDataProvider.GetTrade_Autocall_Option(_trade.id)
|
||||
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的凤凰期权数据");
|
||||
autocallOption.HappenedObservations = _tradeExtendDataProvider.GetTrade_HappenedObservations(_trade.id);
|
||||
var tradeParam = QdpTradeBuilder.GetAutocallOptionTradeParam(_trade, autocallOption, tpRequest);
|
||||
try
|
||||
{
|
||||
result = TradeRiskCalcUtil.GetAutocallOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_trade.TradeNumber))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
throw new Exception($"凤凰期权'{_trade.TradeNumber}'计算出错:{ex.Message}");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "雪球期权":
|
||||
{
|
||||
var snowball = _tradeExtendDataProvider.GetTrade_Snowball_Option(_trade.id)
|
||||
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的雪球期权数据");
|
||||
|
||||
if (snowball?.PrepaymentUsed ?? false)
|
||||
{
|
||||
var snowballSpecialistOptionCalculator = new SnowballSpecialistOptionCalculator();
|
||||
var vol = marketProxy.QdpMarket.VolSurfaces[qdpTradeId].ValueOnGrids[0, 0];
|
||||
var trade = (trade)_trade;
|
||||
trade.trade_snowball = snowball;
|
||||
|
||||
if (snowball.PrepaymentRatio > 0)
|
||||
{
|
||||
var specialSnowballTrade = snowballSpecialistOptionCalculator.GetSpecialTrade(trade);
|
||||
var specialSnowballResult = snowballSpecialistOptionCalculator.CalcOptionValue(marketProxy.ValueDate, spotPrice, vol, _context.CalcScenario, specialSnowballTrade);
|
||||
var breakevenSnowballTrade = snowballSpecialistOptionCalculator.GetBreakevenTrade(trade);
|
||||
var tradeParam = QdpTradeBuilder.GetSnowballTradeParam(breakevenSnowballTrade, breakevenSnowballTrade.trade_snowball, tpRequest);
|
||||
//因为构建tradeParam的时候,会用全局变量_trade的NoRiskRate,所以这边需要覆盖一下
|
||||
tradeParam.riskFreeRate = breakevenSnowballTrade.NoRiskRate ?? 0;
|
||||
var breakevenSnowballResult = TradeRiskCalcUtil.GetSnowballOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
||||
result = snowballSpecialistOptionCalculator.MergeTradeValueResult(specialSnowballResult, breakevenSnowballResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = snowballSpecialistOptionCalculator.CalcOptionValue(marketProxy.ValueDate, spotPrice, vol, _context.CalcScenario, trade);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var tradeParam = QdpTradeBuilder.GetSnowballTradeParam(_trade, snowball, tpRequest);
|
||||
result = TradeRiskCalcUtil.GetSnowballOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "气囊结构":
|
||||
{
|
||||
var airbag = _tradeExtendDataProvider.GetTrade_Airbag(_trade.id)
|
||||
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的气囊结构期权数据");
|
||||
var tradeParam = QdpTradeBuilder.GetAirbagOptionTradeParam(_trade, airbag, tpRequest);
|
||||
result = TradeRiskCalcUtil.GetAirbagOptionValue(marketProxy, getOptionCalcParam(tradeParam));
|
||||
}
|
||||
break;
|
||||
case "收益增强结构":
|
||||
{
|
||||
var underlyingEnhance = _tradeExtendDataProvider.GetTrade_UnderlyingEnhance(_trade.id)
|
||||
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的收益增强结构期权数据");
|
||||
var tradeParam = QdpTradeBuilder.GetUnderlyingEnhanceTradeParam(_trade, underlyingEnhance, tpRequest);
|
||||
result = TradeRiskCalcUtil.GetUnderlyingEnhanceValue(marketProxy, getOptionCalcParam(tradeParam));
|
||||
}
|
||||
break;
|
||||
case "区间累积期权":
|
||||
{
|
||||
var rangeAccrual = _tradeExtendDataProvider.GetTrade_RangeAccrual(_trade.id)
|
||||
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的区间累积期权数据");
|
||||
//有些时候会传入大于估值日期的票息(比如实时风险算昨日pv的时候)
|
||||
rangeAccrual.HappenedObservations = _tradeExtendDataProvider.GetTrade_HappenedObservations(_trade.id)
|
||||
?.Where(n => n.EndDate < _context.ValueDate)?.ToList();
|
||||
var tradeParam = QdpTradeBuilder.GetRangeAccrualTradeParam(_trade, rangeAccrual, tpRequest);
|
||||
result = TradeRiskCalcUtil.GetRangeAccrualValue(marketProxy, getOptionCalcParam(tradeParam));
|
||||
}
|
||||
break;
|
||||
case "累计期权":
|
||||
{
|
||||
var accumulator = _tradeExtendDataProvider.GetTrade_Accumulator_Option(_trade.id)
|
||||
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的累计期权数据");
|
||||
var tradeParam = QdpTradeBuilder.GetAccumulatorOptionTradeParam(_trade, accumulator, tpRequest);
|
||||
result = TradeRiskCalcUtil.GetAccumulatorOptionValue(marketProxy, getOptionCalcParam(tradeParam), _trade.Notional);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
result = new TradeValueResult(false) { ErrorMessage = "未知的期权结构" };
|
||||
break;
|
||||
}
|
||||
if (result != null)
|
||||
{
|
||||
// 通过当前价格计算出来的交易是否敲出以及敲出payoff
|
||||
var knockOutPayoffResult = _tradeKnockOutPayoffCalcService.GetKnockOutPayoff((trade)_trade, spotPrice);
|
||||
if (knockOutPayoffResult != null)
|
||||
{
|
||||
result.IsKnockOut = knockOutPayoffResult.IsKnockOut;
|
||||
result.KnockOutPayoff = knockOutPayoffResult.Payoff;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取现金流价值计算结果
|
||||
/// </summary>
|
||||
private TradeValueResult InnerGetCashFlowTradeValue()
|
||||
{
|
||||
var qdpTradeId = _context.MarketProxy.NextRequestId() + "_" + (_trade.id > 0 ? _trade.id.ToString() : string.IsNullOrWhiteSpace(_trade.ExchangeOptionCode) ? _trade.UnderlyingCode : _trade.ExchangeOptionCode);
|
||||
|
||||
_context.MarketProxy.SetVolSurface(qdpTradeId, QdpModule.QdpVolHelper.GetDefaultVolatility(0.3));
|
||||
|
||||
var tpRequest = new OptionTradeParamRequest(_context.SysRiskFreeRate)
|
||||
{
|
||||
tradeId = qdpTradeId,
|
||||
preciseTimeMode = _context.IsPreciseTimeMode,
|
||||
fixings = null,
|
||||
maturityShift = _maturityShift,
|
||||
volSurfaceNames = new[] { qdpTradeId },
|
||||
hasNightMarket = false,
|
||||
timeToMaturityDays = double.NaN,
|
||||
ParamOverride = p =>
|
||||
{
|
||||
p.riskFreeRate = _context.GetRiskFreeRate(_trade);
|
||||
p.dividendRate = _context.GetDividendRate(_trade);
|
||||
}
|
||||
};
|
||||
|
||||
var rangeAccrual = _tradeExtendDataProvider.GetTrade_CashFlow(_trade.id) ?? throw new TradeCalcExpception(_trade.id, "未获取到对应的现金流交易数据");
|
||||
var tradeParam = QdpTradeBuilder.GetCashFlowTradeParam(_trade, rangeAccrual, tpRequest);
|
||||
|
||||
var calcParam = new OptionCalcParam<CashFlowTradeParam>(tradeParam)
|
||||
{
|
||||
engineName = null,
|
||||
pricingRequest = _context.GetPricingRequest(_trade),
|
||||
calcScenario = _context.CalcScenario
|
||||
};
|
||||
|
||||
return TradeRiskCalcUtil.GetCashFlowValue(_context.MarketProxy, calcParam, _trade.StockEqvNotional);
|
||||
}
|
||||
|
||||
|
||||
private static TradeValueResult GetOptionRisky(
|
||||
OtcTradeBase td,
|
||||
trade_risky_option risky_Option,
|
||||
OptionTradeParamRequest request,
|
||||
underlying_manager underlyings,
|
||||
MarketProxy marketProxy,
|
||||
PricingRequest pricingRequest,
|
||||
double spotPrice,
|
||||
CalcScenarioEnum CalcScenario,
|
||||
bool calcDeltaT)
|
||||
{
|
||||
OptionCalcParam<T> getOptionCalcParam<T>(T tradeParam, double[] spotPrices = null) where T : OptionTradeParamBase
|
||||
{
|
||||
return new OptionCalcParam<T>(tradeParam)
|
||||
{
|
||||
engineName = null,
|
||||
pricingRequest = pricingRequest,
|
||||
spotPrices = spotPrices ?? new[] { spotPrice },
|
||||
calcScenario = CalcScenario,
|
||||
CalcDeltaT1 = calcDeltaT
|
||||
};
|
||||
}
|
||||
|
||||
var result = new TradeValueResult();
|
||||
|
||||
var tradeclone = td.Copy();
|
||||
tradeclone.TradeAmount = tradeclone.TradeAmount = TradeCalcHelper.GetTradeAmountV(td, td.TradeAmount, 1);
|
||||
tradeclone.Notional = tradeclone.Notional = TradeCalcHelper.GetTradeAmountV(td, td.Notional, underlyings.CountRatio);
|
||||
|
||||
var td1 = tradeclone.Copy();
|
||||
var td2 = tradeclone.Copy();
|
||||
if (risky_Option.ParticipationRate2 != 0)
|
||||
{
|
||||
td2.Strike = risky_Option.Strike2;
|
||||
td2.ParticipationRate = risky_Option.ParticipationRate2;
|
||||
td2.TradeAmount = TradeCalcHelper.GetTradeAmount(td2, td2.TradeAmount, 1);
|
||||
td2.Notional = TradeCalcHelper.GetTradeAmount(td2, td2.Notional, underlyings.CountRatio);
|
||||
var tradeParam2 = QdpTradeBuilder.GetVanillaOptionTradeParam(td2, request, false);
|
||||
result = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, getOptionCalcParam(tradeParam2));
|
||||
}
|
||||
|
||||
if (risky_Option.ParticipationRate1 != 0)
|
||||
{
|
||||
td1.Strike = risky_Option.Strike1;
|
||||
td1.ParticipationRate = risky_Option.ParticipationRate1;
|
||||
td1.TradeAmount = TradeCalcHelper.GetTradeAmount(td1, td1.TradeAmount, 1);
|
||||
td1.Notional = TradeCalcHelper.GetTradeAmount(td1, td1.Notional, underlyings.CountRatio);
|
||||
td1.OptionType = "看跌";
|
||||
var tradeParam1 = QdpTradeBuilder.GetVanillaOptionTradeParam(td1, request, false);
|
||||
var singleresult1 = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, getOptionCalcParam(tradeParam1));
|
||||
result.Pv -= singleresult1.Pv;
|
||||
result.Delta -= singleresult1.Delta;
|
||||
result.Gamma -= singleresult1.Gamma;
|
||||
result.Vega -= singleresult1.Vega;
|
||||
result.CalendarDayTheta -= singleresult1.CalendarDayTheta;
|
||||
result.TradingDayTheta -= singleresult1.TradingDayTheta;
|
||||
result.Rho -= singleresult1.Rho;
|
||||
result.DeltaInLots -= singleresult1.DeltaInLots;
|
||||
result.DeltaCash -= singleresult1.DeltaCash;
|
||||
result.GammaCash -= singleresult1.GammaCash;
|
||||
result.VegaCash -= singleresult1.VegaCash;
|
||||
result.RoundedPv -= singleresult1.RoundedPv;
|
||||
result.DDeltaDVol -= singleresult1.DDeltaDVol;
|
||||
result.DDeltaDt -= singleresult1.DDeltaDt;
|
||||
result.DVegaDVol -= singleresult1.DVegaDVol;
|
||||
result.DVegaDt -= singleresult1.DVegaDt;
|
||||
result.DeltaT1 -= singleresult1.DeltaT1;
|
||||
result.ErrorMessage += singleresult1.ErrorMessage;
|
||||
|
||||
result.Vol = singleresult1.Vol;
|
||||
result.Succeeded = singleresult1.Succeeded && result.Succeeded;
|
||||
}
|
||||
|
||||
var td3 = tradeclone.Copy();
|
||||
td3.Strike = risky_Option.Strike3;
|
||||
//decimal 为了解决精度问题: 0.2-0.3=0.0999999999
|
||||
var participationRate3 = (decimal)risky_Option.ParticipationRate3 - (decimal)risky_Option.ParticipationRate2;
|
||||
if (participationRate3 != 0)
|
||||
{
|
||||
td3.ParticipationRate = (double?)Math.Abs(participationRate3);
|
||||
td3.TradeAmount = TradeCalcHelper.GetTradeAmount(td3, td3.TradeAmount, 1);
|
||||
td3.Notional = TradeCalcHelper.GetTradeAmount(td3, td3.Notional, underlyings.CountRatio);
|
||||
var tradeParam3 = QdpTradeBuilder.GetVanillaOptionTradeParam(td3, request, false);
|
||||
var singleresult3 = TradeRiskCalcUtil.GetVanillaOptionValue(marketProxy, getOptionCalcParam(tradeParam3));
|
||||
if (participationRate3 < 0)
|
||||
{
|
||||
result.Pv -= singleresult3.Pv;
|
||||
result.Delta -= singleresult3.Delta;
|
||||
result.Gamma -= singleresult3.Gamma;
|
||||
result.Vega -= singleresult3.Vega;
|
||||
result.CalendarDayTheta -= singleresult3.CalendarDayTheta;
|
||||
result.TradingDayTheta -= singleresult3.TradingDayTheta;
|
||||
result.Rho -= singleresult3.Rho;
|
||||
result.DeltaInLots -= singleresult3.DeltaInLots;
|
||||
result.DeltaCash -= singleresult3.DeltaCash;
|
||||
result.GammaCash -= singleresult3.GammaCash;
|
||||
result.VegaCash -= singleresult3.VegaCash;
|
||||
result.RoundedPv -= singleresult3.RoundedPv;
|
||||
result.DDeltaDVol -= singleresult3.DDeltaDVol;
|
||||
result.DDeltaDt -= singleresult3.DDeltaDt;
|
||||
result.DVegaDVol -= singleresult3.DVegaDVol;
|
||||
result.DVegaDt -= singleresult3.DVegaDt;
|
||||
result.DeltaT1 -= singleresult3.DeltaT1;
|
||||
result.ErrorMessage += singleresult3.ErrorMessage;
|
||||
|
||||
result.Vol = singleresult3.Vol;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Pv += singleresult3.Pv;
|
||||
result.Delta += singleresult3.Delta;
|
||||
result.Gamma += singleresult3.Gamma;
|
||||
result.Vega += singleresult3.Vega;
|
||||
result.CalendarDayTheta += singleresult3.CalendarDayTheta;
|
||||
result.TradingDayTheta += singleresult3.TradingDayTheta;
|
||||
result.Rho += singleresult3.Rho;
|
||||
result.DeltaInLots += singleresult3.DeltaInLots;
|
||||
result.DeltaCash += singleresult3.DeltaCash;
|
||||
result.GammaCash += singleresult3.GammaCash;
|
||||
result.VegaCash += singleresult3.VegaCash;
|
||||
result.RoundedPv += singleresult3.RoundedPv;
|
||||
result.DDeltaDVol += singleresult3.DDeltaDVol;
|
||||
result.DDeltaDt += singleresult3.DDeltaDt;
|
||||
result.DVegaDVol += singleresult3.DVegaDVol;
|
||||
result.DVegaDt += singleresult3.DVegaDt;
|
||||
result.DeltaT1 += singleresult3.DeltaT1;
|
||||
result.ErrorMessage += singleresult3.ErrorMessage;
|
||||
|
||||
result.Vol = singleresult3.Vol;
|
||||
}
|
||||
result.Succeeded = singleresult3.Succeeded && result.Succeeded;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#region----期权计算参数----
|
||||
|
||||
/// <summary>
|
||||
/// 彩虹期权(如果准备波动率失败,返回null)
|
||||
/// </summary>
|
||||
private RainbowOptionTradeParam GetRainbowOptionTradeParam(OptionTradeParamRequest request, out double[] spotPrices)
|
||||
{
|
||||
var rainbowOption = _tradeExtendDataProvider.GetTrade_Rainbow_Option(_trade.id)
|
||||
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的彩虹期权数据");
|
||||
|
||||
_underlyingPriceProvider.TryGetPrice(rainbowOption.UnderlyingAssetCode, out var price1);
|
||||
_underlyingPriceProvider.TryGetPrice(rainbowOption.UnderlyingAssetCode2, out var price2);
|
||||
|
||||
spotPrices = new double[2] { price1, price2 };
|
||||
|
||||
//彩虹期权,需要两个标的的初始化
|
||||
|
||||
return QdpTradeBuilder.GetRainbowOptionTradeParam(_trade, rainbowOption, request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 亚式期权
|
||||
/// </summary>
|
||||
private AsianOptionTradeParam GetAsianOptionCalcParam(OptionTradeParamRequest request, double spotPrice)
|
||||
{
|
||||
var asianOption = _tradeExtendDataProvider.GetTrade_Asian_Option(_trade.id)
|
||||
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的亚式期权数据");
|
||||
//主要是某些计算场景(比如实时)的fixing用了缓存提升性能所以才从context中获取
|
||||
request.fixings = _context.GetFixingString(_trade, asianOption, spotPrice);
|
||||
request.fixings = AsianOptionFixingService.CheckAsiaFixings(_trade, asianOption, request.fixings, spotPrice);
|
||||
return QdpTradeBuilder.GetAsianOptionTradeParam(_trade, asianOption, request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 价差期权
|
||||
/// </summary>
|
||||
private SpreadOptionTradeParam GetSpreadOptionCalcParam(OptionTradeParamRequest request, out double[] spotPrices)
|
||||
{
|
||||
var spreadOption = _tradeExtendDataProvider.GetTrade_Spread_Option(_trade.id)
|
||||
?? throw new TradeCalcExpception(_trade.id, "未获取到对应的价差期权数据");
|
||||
|
||||
var spreadOptionInput = GetMarketInputForSpreadOption(spreadOption);
|
||||
|
||||
spotPrices = spreadOptionInput.SpotPrices.ToArray();
|
||||
|
||||
var correlations = spreadOptionInput.Correlations.ToArray();
|
||||
|
||||
_spreadUnderlyings = spreadOptionInput.Underlyings;
|
||||
|
||||
return QdpTradeBuilder.GetSpreadOptionTradeParam(_trade, spreadOption, request, correlations);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 为价差期权的多个标的获取现价和相关性数据
|
||||
/// </summary>
|
||||
private SpreadOptionPricingInput GetMarketInputForSpreadOption(trade_spread_option spreadOption)
|
||||
{
|
||||
if (spreadOption == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(spreadOption));
|
||||
}
|
||||
var unlyArr = new underlying_manager[4];
|
||||
unlyArr[0] = _underlyingDataProvider.GetUnderlying(_trade.UnderlyingCode);
|
||||
unlyArr[1] = _underlyingDataProvider.GetUnderlying(spreadOption.UnderlyingAssetCode2);
|
||||
if (!string.IsNullOrEmpty(spreadOption.UnderlyingAssetCode3))
|
||||
{
|
||||
unlyArr[2] = _underlyingDataProvider.GetUnderlying(spreadOption.UnderlyingAssetCode3);
|
||||
if (!string.IsNullOrEmpty(spreadOption.UnderlyingAssetCode4))
|
||||
{
|
||||
unlyArr[3] = _underlyingDataProvider.GetUnderlying(spreadOption.UnderlyingAssetCode4);
|
||||
}
|
||||
}
|
||||
unlyArr = unlyArr.Where(n => n != null).ToArray();
|
||||
|
||||
var spotPrices = new List<double>();
|
||||
var correlations = new List<double>();
|
||||
|
||||
for (var i = 0; i < unlyArr.Length; i++)
|
||||
{
|
||||
var unly = unlyArr[i];
|
||||
if (unly == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
for (var j = 0; j < i; j++)
|
||||
{
|
||||
correlations.Add(_context.GetCorrelation(unlyArr[j].id, unly.id));
|
||||
}
|
||||
_underlyingPriceProvider.TryGetPrice(unly.UnderlyingCode, out var price);
|
||||
spotPrices.Add(price);
|
||||
unly.Price = price;
|
||||
}
|
||||
|
||||
return new SpreadOptionPricingInput()
|
||||
{
|
||||
Underlyings = unlyArr,
|
||||
SpotPrices = spotPrices.ToArray(),
|
||||
Correlations = correlations.ToArray()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user