从山证v2.3.0拷贝

This commit is contained in:
吴方海
2024-05-09 14:06:26 +08:00
parent 566ff33259
commit f9d8a256a6
4471 changed files with 1203456 additions and 9 deletions
@@ -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()
};
}
}
}