从山证v2.3.0拷贝
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using YLErp.BLL.Calculation;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class ForwardradeCalcServiceTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestCalcValueOld()
|
||||
{
|
||||
var testItems = new[] {
|
||||
new{ BuySell = "买入", CallPut = "Call", Strike = 3500,Notional = 100,SpotPrice= 3550 },
|
||||
new{ BuySell = "买入", CallPut = "Call", Strike = 3500, Notional = 100, SpotPrice= 3450 },
|
||||
new{ BuySell = "买入", CallPut = "Put", Strike = 3500, Notional = 100, SpotPrice= 3550 },
|
||||
new{ BuySell = "买入", CallPut = "Put", Strike = 3500, Notional = 100, SpotPrice= 3450 },
|
||||
|
||||
new{ BuySell = "卖出", CallPut = "Call", Strike = 3500,Notional = 100,SpotPrice= 3550 },
|
||||
new{ BuySell = "卖出", CallPut = "Call", Strike = 3500, Notional = 100, SpotPrice= 3450 },
|
||||
new{ BuySell = "卖出", CallPut = "Put", Strike = 3500, Notional = 100, SpotPrice= 3550 },
|
||||
new{ BuySell = "卖出", CallPut = "Put", Strike = 3500, Notional = 100, SpotPrice= 3450 },
|
||||
};
|
||||
|
||||
foreach (var item in testItems)
|
||||
{
|
||||
var r1 = CalcValueV1(item.Strike, item.SpotPrice, item.Notional, item.CallPut, item.BuySell);
|
||||
var r2 = CalcValueV2(item.Strike, item.SpotPrice, item.Notional, item.CallPut, item.BuySell);
|
||||
Console.WriteLine(item.ToJson());
|
||||
Console.WriteLine($"pv1: {r1.Pv}, pv2: {r2.Pv}, delta1: {r1.Delta}, delta2: {r2.Delta}");
|
||||
Assert.IsTrue(r1.Pv == r2.Pv && r1.Delta == r2.Delta);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算远期交易(买方角度)ValueCalculator.CalculateForward
|
||||
/// </summary>
|
||||
static TradeValueResult CalculateForwardV1(double strike, double spotPrice, double notional, string callPut)
|
||||
{
|
||||
var pv = 0.0;
|
||||
if (callPut == "Call")
|
||||
{
|
||||
pv = (spotPrice - strike) * notional;
|
||||
}
|
||||
else if (callPut == "Put")
|
||||
{
|
||||
pv = (strike - spotPrice) * notional;
|
||||
}
|
||||
|
||||
return new TradeValueResult()
|
||||
{
|
||||
Pv = pv,
|
||||
Delta = callPut == "Call" ? notional : -notional,
|
||||
DeltaCash = callPut == "Call" ? spotPrice * notional : -spotPrice * notional
|
||||
};
|
||||
}
|
||||
|
||||
public static TradeValueResult CalcValueV1(double strike, double spotPrice, double notional, string callput, string buysell)
|
||||
{
|
||||
var result = CalculateForwardV1(strike, spotPrice, notional, callput);
|
||||
result.Pv *= TradeCalcHelper.GetSign(buysell);
|
||||
result.Delta *= TradeCalcHelper.GetSign(buysell);
|
||||
//买入看跌和卖出看涨取反
|
||||
//var flag = (IsBuy(buysell) ? 1 : 2) | (callput == "Call" ? 1 : 2);
|
||||
//if (flag == 3)
|
||||
//{
|
||||
// result.Delta = -result.Delta;
|
||||
//}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算PV/Risk(交易员角度)
|
||||
/// </summary>
|
||||
public static TradeValueResult CalcValueV2(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 = -spotPrice * notional
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
result = new TradeValueResult
|
||||
{
|
||||
Pv = pv,
|
||||
Delta = notional,
|
||||
DeltaCash = spotPrice * notional
|
||||
};
|
||||
}
|
||||
|
||||
result.RoundedPv = result.Pv;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool IsBuy(string tradeType)
|
||||
{
|
||||
return tradeType == "Buy" || tradeType == "买入" || string.IsNullOrWhiteSpace(tradeType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using YLErp.Abstract.DataProviders;
|
||||
using YLErp.BLL;
|
||||
using YLErp.DBModels;
|
||||
using YLErp.Helpers;
|
||||
using YLErp.Modules.CalculationModule.Abstract;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
[TestClass]
|
||||
public class HedgePnlCalcTest : YLUnitTestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestCalculate()
|
||||
{
|
||||
var valueDate = DateTime.Today;
|
||||
|
||||
var calc = new InnerHedgePnlCalcContext(true, valueDate, "对冲"
|
||||
, new InnerUnderlyingPriceProvider()
|
||||
, new InnerExchangeOptionPriceProvider(), OptUser).GetHedgePnlCalc();
|
||||
|
||||
var exchangeTrades = GetExchangeTrades();
|
||||
var eodTradePositions = GetEodTradePositions();
|
||||
|
||||
var results = calc.Calculate(exchangeTrades, eodTradePositions);
|
||||
|
||||
Assert.AreEqual(results.Count(), 3);
|
||||
}
|
||||
|
||||
private IEnumerable<ExchangeTrade> GetExchangeTrades()
|
||||
{
|
||||
var valueDate = valuedateBLL.ValueDate;
|
||||
|
||||
var un = GetUnderlyingManager(true);
|
||||
|
||||
var baseTrade = new ExchangeTrade
|
||||
{
|
||||
AssetBookId = 1,
|
||||
Comments = "单元测试",
|
||||
Commission = 0,
|
||||
CommissionType = DBModels.Enums.CommissionType.不收取,
|
||||
CreateTime = DateTime.Now,
|
||||
ExchangeAccountCode = "TEST",
|
||||
ExchangeAccountId = 1,
|
||||
ExerciseMode = "European",
|
||||
InstrumentType = ConsGlobal.InstrumentType.CommodityFutures,
|
||||
IsValid = true,
|
||||
MaturityDate = valueDate.AddMonths(1),
|
||||
Notional = 100,
|
||||
OptionCode = "RB00-C-3400",
|
||||
OptionStrike = 3400,
|
||||
TradeAmount = 100,
|
||||
TradeDate = valueDate.AddDays(-10),
|
||||
TradeLots = 10,
|
||||
TradeNumber = Guid.NewGuid().ToString("N"),
|
||||
TraderId = 1,
|
||||
TraderName = "",
|
||||
TradeSide = "多头开仓",
|
||||
TradeSinglePrice = 16,
|
||||
TradeSource = "",
|
||||
OptId = 0,
|
||||
OptDate = valueDate,
|
||||
OptionType = "看涨",
|
||||
OptName = "",
|
||||
TradeType = "场内期权",
|
||||
UnderlyingCode = "RB00",
|
||||
UnderlyingId = un.id,
|
||||
id = 1
|
||||
};
|
||||
|
||||
var td1 = baseTrade.Clone();
|
||||
td1.TradeType = "商品期权";
|
||||
td1.TradeSinglePrice = 3233;
|
||||
|
||||
var td2 = baseTrade.Clone();
|
||||
td2.id = 2;
|
||||
|
||||
var td3 = baseTrade.Clone();
|
||||
td3.id = 3;
|
||||
td3.OptionCode = "RB00-P-3400";
|
||||
td3.OptionType = "看跌";
|
||||
|
||||
return new[] { td1, td2, td3 };
|
||||
}
|
||||
|
||||
private IEnumerable<EodTradePosition> GetEodTradePositions()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class InnerHedgePnlCalcContext : HedgePnlCalcContext
|
||||
{
|
||||
public InnerHedgePnlCalcContext(bool isEodSettle, DateTime valueDate, string volType
|
||||
, IPriceProvider underlyingPriceProvider, IPriceProvider exchangeOptionPriceProvider, OptUserInfo optUser)
|
||||
: base(isEodSettle, valueDate, volType, underlyingPriceProvider, exchangeOptionPriceProvider, optUser)
|
||||
{
|
||||
CommissionCalc = new InnerExchangeTradeCommissionCalc();
|
||||
ExchangeOptionPriceUseFlag = ExchangeOptionPriceUseFlag.CalcPv;
|
||||
UnderlyingDataProvider = new InnerUnderlyingDataProvider();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using YLErp.Abstract;
|
||||
using YLErp.Abstract.DataProviders;
|
||||
using YLErp.BLL;
|
||||
using YLErp.DBModels;
|
||||
using YLErp.Models;
|
||||
using YLErp.Modules.CalculationModule.Abstract;
|
||||
using YLErp.Modules.DataProviderModule;
|
||||
using YLErp.Modules.VolatilityModule;
|
||||
using YLErp.QdpModule;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
[TestClass]
|
||||
public class OptionCaclTest : YLUnitTestBase
|
||||
{
|
||||
[TestMethod("测试雪球期权PV")]
|
||||
public void TestSnowball()
|
||||
{
|
||||
var td = DbContext.trade.FirstOrDefault(n => n.TradeNumber == "CW20200053E0008");
|
||||
Assert.IsNotNull(td);
|
||||
var calcDataProvider = new CalcDataProvider(this)
|
||||
{
|
||||
UnderlyingPriceProvider = new InnerUnderlyingPriceProvider(),
|
||||
VolatilityDataProvider = new InnerVolatilityDataProvider()
|
||||
};
|
||||
var context = new OptionValueCalcContext("对冲", valuedateBLL.ValueDate, valuedateBLL.RiskFreeRate / 100, calcDataProvider)
|
||||
{
|
||||
AddingVolRate = 0,
|
||||
IsEodSettle = false,
|
||||
IsUseTradeVol = true
|
||||
};
|
||||
context.IsPreciseTimeMode = context.IsUseTradeVol || !context.IsEodSettle;
|
||||
var result = TradeRiskCalcUtil.CalcTradeRisk(td, context, out var underlyings);
|
||||
Console.WriteLine($"PV:{result.Pv}; Delta:{result.Delta}");
|
||||
}
|
||||
|
||||
[TestMethod("测试二元期权计算")]
|
||||
public void TestBinaryOption()
|
||||
{
|
||||
var path = Path.Combine(AppContext.BaseDirectory, "Resources\\期权计算\\二元期权.json");
|
||||
var json = File.ReadAllText(path);
|
||||
var td = JsonHelper.Deserialize<trade>(json);
|
||||
var result = OptionCalculatorV2.GetOptionValueResult(DateTime.Today, td, new OptionValueCalcRequest( 0.025)
|
||||
{
|
||||
spotPrices = new[] { 6105.8249 },
|
||||
vols = new[] { 0.23 }
|
||||
}, out _);
|
||||
Console.WriteLine(result.Delta);
|
||||
}
|
||||
|
||||
[TestMethod("测试二元期权计算")]
|
||||
public void TestBinaryOption2()
|
||||
{
|
||||
var path = Path.Combine(AppContext.BaseDirectory, "Resources\\期权计算\\二元期权计算参数.json");
|
||||
var json = File.ReadAllText(path);
|
||||
var calcParam = JsonHelper.Deserialize<OptionCalcParam<BinaryOptionTradeParam>>(json);
|
||||
using (var mp = new MarketProxy(DateTime.Today, 0.025))
|
||||
{
|
||||
var vols = QdpVolHelper.GetDefaultVolatility(0.23);
|
||||
mp.SaveVolSurface(calcParam.volSurfaceNames[0], vols);
|
||||
var result = TradeRiskCalcUtil.GetBinaryOptionValue(mp, calcParam);
|
||||
Console.WriteLine(result.Gamma);
|
||||
}
|
||||
}
|
||||
|
||||
class CalcDataProvider : IOptionCalcDataProvider
|
||||
{
|
||||
public CalcDataProvider(YLBaseService baseService)
|
||||
{
|
||||
UnderlyingDataProvider = new UnderlyingDataProvider();
|
||||
TradeExtendDataProvider = new TradeExtendDataProvider(baseService);
|
||||
}
|
||||
|
||||
public IPriceProvider UnderlyingPriceProvider { get; set; }
|
||||
|
||||
public IUnderlyingDataProvider UnderlyingDataProvider { get; }
|
||||
|
||||
public ITradeExtendDataProvider TradeExtendDataProvider { get; }
|
||||
|
||||
public IVolatilityDataProvider VolatilityDataProvider { get; set; }
|
||||
}
|
||||
|
||||
class InnerUnderlyingPriceProvider : IPriceProvider
|
||||
{
|
||||
public double GetPrice(string instrumentCode)
|
||||
{
|
||||
return 15.45;
|
||||
}
|
||||
|
||||
public bool TryGetPrice(string instrumentCode, out double price)
|
||||
{
|
||||
price = 15.45;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class InnerVolatilityDataProvider : IVolatilityDataProvider
|
||||
{
|
||||
public double? GetExchangeOptionTradeHedgeVol(string optionCode, DateTime valueDate)
|
||||
{
|
||||
return 0.25;
|
||||
}
|
||||
|
||||
public double? GetOtcOptionTradeEodVol(int tradeId, DateTime valueDate)
|
||||
{
|
||||
return 0.25;
|
||||
}
|
||||
|
||||
public double? GetOtcOptionTradeHedgeVol(int tradeId, DateTime valueDate)
|
||||
{
|
||||
return 0.25;
|
||||
}
|
||||
|
||||
public IOtcTradeVolatility GetOtcOptionTradeVol(int tradeId, DateTime valueDate)
|
||||
{
|
||||
return new OtcTradeVolatility
|
||||
{
|
||||
OpenVol = 0.25,
|
||||
CloseVol = 0.25,
|
||||
SmoothingDays = 1,
|
||||
IsFirst = false,
|
||||
ValueDate = valueDate
|
||||
};
|
||||
}
|
||||
|
||||
public IVolatility GetUnderlyingVolatility(string voltype, string contractCode, string userGroup)
|
||||
{
|
||||
return VolatilityHelper.GetDefaultVol(new SingleVolatilityRequest
|
||||
{
|
||||
QuotationDate = DateTime.Today,
|
||||
TradeVolWithBidAsk = false,
|
||||
UnderlyingCode = contractCode,
|
||||
UnderlyingId = 0,
|
||||
UserGroup = userGroup,
|
||||
VolType = voltype
|
||||
}, 0.25);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Qdp.Pricing.Base.Enums;
|
||||
using System;
|
||||
using YLErp.BLL;
|
||||
using YLErp.BLL.Calculation;
|
||||
using YLErp.DBModels;
|
||||
using YLErp.QdpModule;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 期权计算比较(新版本和老版本)
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class OptionCalculatorCompare
|
||||
{
|
||||
const double ConstVol = 0.3;
|
||||
const double Notional = 1;
|
||||
const double SpotPrice = 3000;
|
||||
const double RiskFreeRate = 0.03;
|
||||
const string ExerciseType = "European";
|
||||
const string UnderlyingCode = "RB00";
|
||||
const int TTMDays = 36;
|
||||
const string InstrumentType = "CommodityFutures";
|
||||
static readonly string QdpMarketID = Guid.NewGuid().ToString();
|
||||
static readonly DateTime TradeDate = new DateTime(2021, 1, 4);
|
||||
static readonly DateTime ExerciseDate = new DateTime(2021, 3, 1);
|
||||
|
||||
[TestMethod]
|
||||
public void TestVanillaOption()
|
||||
{
|
||||
var vols = QdpVolHelper.GetDefaultVolatility(ConstVol);
|
||||
var valueDateStr = TradeDate.ToString("yyyy-MM-dd");
|
||||
var marketProxy = QdpMarketManager.Instance.GetPrebuiltMarketProxy(QdpMarketID);
|
||||
|
||||
var underlying = new
|
||||
{
|
||||
UnderlyingCode = UnderlyingCode,
|
||||
UnderlyingInstrumentType = InstrumentType,
|
||||
Price = SpotPrice
|
||||
};
|
||||
|
||||
//使用全局的DiscountCurve以提高计算效率
|
||||
var discountCurveName = Guid.NewGuid().ToString();
|
||||
var discountCurve = CalculatorHelper.CreateConstantRiskFreeCurve(discountCurveName, valuedateBLL.RiskFreeRate / 100.0);
|
||||
marketProxy.AddYieldCurve(discountCurveName, valueDateStr, discountCurve);
|
||||
|
||||
var initParam = new VolSurfaceInitParamsBuilder(QdpMarketID)
|
||||
.SetValueDate(TradeDate)
|
||||
.SetUnderlying(0, UnderlyingCode, UnderlyingCode)
|
||||
.SetVolatility(vols).Build(QdpMarketID);
|
||||
VolSurfaceInitializerSingleton.GetInitializer(false).InitializeMarketProxy(initParam);
|
||||
|
||||
var bidMaturityDate = ExerciseDate.ToString(ConsGlobal.DateFormat);
|
||||
|
||||
var tv1 = OptionCalculatorV1.ValueVanillaOptionTrade(
|
||||
marketProxy: marketProxy,
|
||||
valueDate: valueDateStr,
|
||||
underlyingTicker: underlying.UnderlyingCode,
|
||||
underlyingInstrumentType: underlying.UnderlyingInstrumentType,
|
||||
strike: SpotPrice,
|
||||
startDate: valueDateStr,
|
||||
endDate: bidMaturityDate,
|
||||
optionType: "Call",
|
||||
exerciseType: ExerciseType,
|
||||
spotPrice: underlying.Price,
|
||||
notional: Notional,
|
||||
volSurfaceName: initParam.volSurfaceNameKey,
|
||||
riskFreeRate: RiskFreeRate,
|
||||
modelName: null,
|
||||
tradeType: "Buy",
|
||||
exerciseDate: bidMaturityDate,
|
||||
hasNightMarket: false,
|
||||
commodityFuturesPreciseTimeMode: true,
|
||||
discountCurveName: discountCurveName,
|
||||
participationRate: 1.0,
|
||||
principalRate: 0.0,
|
||||
isAnnualized: false,
|
||||
annualizeFactor: 1.0,
|
||||
timeToMaturityDays: TTMDays);
|
||||
|
||||
System.Diagnostics.Debug.WriteLine("V1 PV:" + tv1.Pv);
|
||||
|
||||
var vtParam = new VanillaOptionTradeParam
|
||||
{
|
||||
annualizedFactor = 0,
|
||||
isAnnualized = false,
|
||||
buysell = "买入",
|
||||
commodityFuturesPreciseTimeMode = true,
|
||||
dividendRate = 0,
|
||||
dividends = null,
|
||||
endDate = ExerciseDate,
|
||||
exerciseDate = ExerciseDate,
|
||||
exerciseType = ExerciseType,
|
||||
hasNightMarket = false,
|
||||
initialSpotPrice = SpotPrice,
|
||||
isForwardTrade = false,
|
||||
isMoneynessOption = false,
|
||||
notional = Notional,
|
||||
optionType = OptionType.Call,
|
||||
participationRate = 1,
|
||||
principalRate = 0,
|
||||
riskFreeRate = RiskFreeRate,
|
||||
settlementDate = ExerciseDate,
|
||||
startDate = TradeDate,
|
||||
strike = SpotPrice,
|
||||
timeToMaturityDays = TTMDays,
|
||||
tradeDate = TradeDate,
|
||||
tradeId = QdpMarketID,
|
||||
underlyingInstrumentType = InstrumentType,
|
||||
underlyingTickers = new[] { UnderlyingCode },
|
||||
volSurfaceNames = new[] { QdpMarketID }
|
||||
};
|
||||
|
||||
TradeValueResult tv2;
|
||||
|
||||
using (var mp = new MarketProxy(TradeDate, 0.03))
|
||||
{
|
||||
mp.SaveVolSurface(QdpMarketID, vols);
|
||||
|
||||
tv2 = TradeRiskCalcUtil.GetVanillaOptionValue(mp, new OptionCalcParam<VanillaOptionTradeParam>(vtParam)
|
||||
{
|
||||
pricingRequest = QdpPricingRequest.BASIC_GREEKS,
|
||||
spotPrices = new[] { 3000d },
|
||||
});
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine("V2 PV:" + tv2.Pv);
|
||||
|
||||
Assert.AreEqual(tv1.Pv, tv2.Pv, 1e-6);
|
||||
Assert.AreEqual(tv1.Delta, tv2.Delta, 1e-6);
|
||||
Assert.AreEqual(tv1.Gamma, tv2.Gamma, 1e-6);
|
||||
Assert.AreEqual(tv1.Theta, tv2.Theta, 1e-6);
|
||||
Assert.AreEqual(tv1.Rho, tv2.Rho, 1e-6);
|
||||
Assert.AreEqual(tv1.Vega, tv2.Vega, 1e-6);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestAsiaOption()
|
||||
{
|
||||
var vols = QdpVolHelper.GetDefaultVolatility(ConstVol);
|
||||
var valueDateStr = TradeDate.ToString("yyyy-MM-dd");
|
||||
var marketProxy = QdpMarketManager.Instance.GetPrebuiltMarketProxy(QdpMarketID);
|
||||
|
||||
var underlying = DataCacheProvider.GetUnderlyingDataSource().GetData(UnderlyingCode);
|
||||
|
||||
//使用全局的DiscountCurve以提高计算效率
|
||||
var discountCurveName = Guid.NewGuid().ToString();
|
||||
var discountCurve = CalculatorHelper.CreateConstantRiskFreeCurve(discountCurveName, valuedateBLL.RiskFreeRate / 100.0);
|
||||
marketProxy.AddYieldCurve(discountCurveName, valueDateStr, discountCurve);
|
||||
|
||||
var initParam = new VolSurfaceInitParamsBuilder(QdpMarketID)
|
||||
.SetValueDate(TradeDate)
|
||||
.SetUnderlying(0, UnderlyingCode, UnderlyingCode)
|
||||
.SetVolatility(vols).Build(QdpMarketID);
|
||||
VolSurfaceInitializerSingleton.GetInitializer(false).InitializeMarketProxy(initParam);
|
||||
|
||||
underlying.Price = SpotPrice;
|
||||
underlying.QuotationDate = TradeDate;
|
||||
|
||||
var td = new trade()
|
||||
{
|
||||
TradeType = "亚式期权",
|
||||
UnderlyingCode = underlying.UnderlyingCode,
|
||||
UnderlyingInstrumentType = InstrumentType,
|
||||
TradeDate = TradeDate,
|
||||
StartDate = TradeDate,
|
||||
MaturityDate = ExerciseDate,
|
||||
ExerciseDate = ExerciseDate,
|
||||
OptionType = "看涨",
|
||||
ExerciseMode = ExerciseType,
|
||||
Strike = SpotPrice,
|
||||
SpotPrice = SpotPrice,
|
||||
Notional = Notional,
|
||||
NoRiskRate = RiskFreeRate,
|
||||
BuySell = "Buy",
|
||||
QuotationType = "波动率调整",
|
||||
TradeOpenVolatility = ConstVol,
|
||||
TTMDays = TTMDays,
|
||||
trade_asian_option = new trade_asian_option()
|
||||
{
|
||||
PayoffType = "ArithmeticAverage",
|
||||
StrikeType = "Fixed",
|
||||
AveragingPeriodStartDate = TradeDate
|
||||
}
|
||||
};
|
||||
|
||||
var tv1 = OptionCalculatorV1.GetOptionValueResult(QdpMarketID, underlying, td, new[] { SpotPrice },
|
||||
useTradeVolMode: true, volSurfaceNames: new[] { initParam.volSurfaceNameKey },
|
||||
fixing: $"{TradeDate:yyyy-MM-dd},{SpotPrice}", commodityFuturesPreciseTimeMode: false);
|
||||
|
||||
System.Diagnostics.Debug.WriteLine("V1 PV:" + tv1.Pv);
|
||||
|
||||
var vtParam = new AsianOptionTradeParam
|
||||
{
|
||||
annualizedFactor = 0,
|
||||
isAnnualized = false,
|
||||
buysell = "买入",
|
||||
commodityFuturesPreciseTimeMode = false,
|
||||
dividendRate = 0,
|
||||
dividends = null,
|
||||
endDate = ExerciseDate,
|
||||
exerciseDate = ExerciseDate,
|
||||
exerciseType = ExerciseType,
|
||||
hasNightMarket = false,
|
||||
initialSpotPrice = SpotPrice,
|
||||
isForwardTrade = false,
|
||||
isMoneynessOption = false,
|
||||
notional = 1,
|
||||
optionType = OptionType.Call,
|
||||
participationRate = 1,
|
||||
principalRate = 0,
|
||||
riskFreeRate = RiskFreeRate,
|
||||
settlementDate = ExerciseDate,
|
||||
startDate = TradeDate,
|
||||
strike = SpotPrice,
|
||||
timeToMaturityDays = TTMDays,
|
||||
tradeDate = TradeDate,
|
||||
tradeId = QdpMarketID,
|
||||
underlyingInstrumentType = InstrumentType,
|
||||
underlyingTickers = new[] { UnderlyingCode },
|
||||
volSurfaceNames = new[] { QdpMarketID },
|
||||
payoffType = "ArithmeticAverage",
|
||||
strikeStyle = "Fixed",
|
||||
averagingPeriodStartDate = TradeDate
|
||||
};
|
||||
|
||||
TradeValueResult tv2;
|
||||
|
||||
using (var mp = new MarketProxy(TradeDate, RiskFreeRate))
|
||||
{
|
||||
mp.SaveVolSurface(QdpMarketID, vols);
|
||||
|
||||
tv2 = TradeRiskCalcUtil.GetAsianOptionValue(mp, new OptionCalcParam<AsianOptionTradeParam>(vtParam)
|
||||
{
|
||||
pricingRequest = QdpPricingRequest.BASIC_GREEKS,
|
||||
spotPrices = new[] { SpotPrice },
|
||||
});
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine("V2 PV:" + tv2.Pv);
|
||||
|
||||
Assert.AreEqual(tv1.Pv, tv2.Pv, 1e-6);
|
||||
Assert.AreEqual(tv1.Delta, tv2.Delta, 1e-6);
|
||||
Assert.AreEqual(tv1.Gamma, tv2.Gamma, 1e-6);
|
||||
Assert.AreEqual(tv1.Theta, tv2.Theta, 1e-6);
|
||||
Assert.AreEqual(tv1.Rho, tv2.Rho, 1e-6);
|
||||
Assert.AreEqual(tv1.Vega, tv2.Vega, 1e-6);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Qdp.ComputeService.Data.CommonModels.ValuationParams.Equity;
|
||||
using Qdp.ComputeServiceV2.Data.CommonModels.TradeInfos.Options;
|
||||
using Qdp.Foundation.Implementations;
|
||||
using Qdp.Pricing.Base.Enums;
|
||||
using Qdp.Pricing.Base.Implementations;
|
||||
using Qdp.Pricing.Base.Utilities;
|
||||
using Qdp.Pricing.Library.Options.Products.SyntheticSpread;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using YLErp.BLL.Calculation;
|
||||
using YLErp.DBModels;
|
||||
using YLErp.DBModels.Helpers;
|
||||
using YLErp.QdpModule;
|
||||
|
||||
namespace YLErp.Modules.CalculationModule
|
||||
{
|
||||
[TestClass]
|
||||
public class SSpreadOptionCalcTest
|
||||
{
|
||||
[TestMethod("测试雪球期权PV")]
|
||||
public void TestSSpreadOptionCalcCrossGammas()
|
||||
{
|
||||
var spotPrice = 3200d;
|
||||
var coefficients = new double[] { -1, 1 };
|
||||
var td = new trade
|
||||
{
|
||||
id = 1,
|
||||
AssetBookName = "test",
|
||||
AssetId = 1,
|
||||
BasisGap = 0,
|
||||
BasisUnderlyingCode = null,
|
||||
BasisUnderlyingId = 0,
|
||||
BuySell = "卖出",
|
||||
CalcFlag = 1,
|
||||
ClientId = 1,
|
||||
ClientName = "客户名称",
|
||||
Comments = null,
|
||||
CreateDate = DateTime.Now,
|
||||
UnderlyingCode = "RB00-TA00",
|
||||
UnderlyingInstrumentType = ConsGlobal.InstrumentType.CommodityFutures,
|
||||
Strike = 3000,
|
||||
StartDate = new DateTime(2020, 12, 1),
|
||||
ExerciseDate = new DateTime(2020, 12, 31),
|
||||
MaturityDate = new DateTime(2023, 12, 31),
|
||||
OptionType = "看涨",
|
||||
ExerciseMode = ConsGlobal.ExerciseMode.American,
|
||||
Notional = 100,
|
||||
NoRiskRate = 0.05,
|
||||
ParticipationRate = 1,
|
||||
PrincipalRate = 0,
|
||||
IsAnnualized = false,
|
||||
AnnualizeFactor = 1,
|
||||
DividendRate = 0,
|
||||
IsMoneynessOption = "否",
|
||||
SpotPrice = 3200
|
||||
};
|
||||
|
||||
var marketProxy = QdpMarketManager.Instance.GetPrebuiltMarketProxy("111");
|
||||
|
||||
var crossGammas = SSpreadOptionCalc.CalculateSyntheticNormalSpreadCrossGammas(
|
||||
marketProxy,
|
||||
"2020-12-25",
|
||||
td.UnderlyingCode,
|
||||
td.UnderlyingInstrumentType,
|
||||
td.Strike ?? 0,
|
||||
td.StartDate.Value.ToString("yyyy-MM-dd"),
|
||||
td.MaturityDate.Value.ToString("yyyy-MM-dd"),
|
||||
td.CallPut,
|
||||
td.ExerciseMode,
|
||||
spotPrice,
|
||||
coefficients.ToArray(),
|
||||
td.Notional,
|
||||
"1111111",
|
||||
td.NoRiskRate ?? 0.0,
|
||||
td.BuySell,
|
||||
td.ExerciseDate.Value.ToString("yyyy-MM-dd"),
|
||||
td.ParticipationRate ?? 1.0,
|
||||
td.PrincipalRate ?? 0.0,
|
||||
td.IsAnnualized,
|
||||
td.AnnualizeFactor ?? 1.0,
|
||||
td.DividendRate ?? 0,
|
||||
td.IsMoneynessOptionData,
|
||||
td.SpotPrice ?? 0,
|
||||
hasNightMarket: false,
|
||||
commodityFuturesPreciseTimeMode: false,
|
||||
riskFreeRateOverride: td.NoRiskRate ?? double.NaN,
|
||||
dividendRateOverride: td.DividendRate ?? double.NaN);
|
||||
Assert.IsNotNull(crossGammas);
|
||||
|
||||
var crossGammas2 = OptionCalculatorV2.CalcSSpreadCrossGammas(new DateTime(2020, 12, 25), td, new OptionValueCalcRequest(0.05)
|
||||
{
|
||||
spotPrices = new[] { spotPrice },
|
||||
vols = new[] { 1.3 },
|
||||
}, coefficients);
|
||||
|
||||
Assert.IsNotNull(crossGammas);
|
||||
}
|
||||
}
|
||||
|
||||
static class SSpreadOptionCalc
|
||||
{
|
||||
/// <summary>
|
||||
/// 股指期货类型转换为商品期货类型
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static string ConvertInstrumentType(string instrumentType)
|
||||
{
|
||||
return ConsGlobal.InstrumentType.IsStockIF(instrumentType) ? ConsGlobal.InstrumentType.CommodityFutures : instrumentType;
|
||||
}
|
||||
|
||||
public static OptionExercise ConvertExerciseType(string exerciseType)
|
||||
{
|
||||
if (exerciseType != null)
|
||||
{
|
||||
switch (exerciseType.ToUpper())
|
||||
{
|
||||
case "美式":
|
||||
case "AMERICAN":
|
||||
return OptionExercise.American;
|
||||
default:
|
||||
return OptionExercise.European;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return OptionExercise.European;
|
||||
}
|
||||
}
|
||||
|
||||
public static SyntheticNormalSpreadOptionTrade CreateSyntheticNormalSpreadOptionTrade(
|
||||
string tradeId,
|
||||
string volSurfaceName,
|
||||
string tradeDate,
|
||||
string underlyingTicker,
|
||||
string underlyingInstrumentType,
|
||||
double strike,
|
||||
string startDate,
|
||||
string endDate,
|
||||
string optionType,
|
||||
string exerciseType,
|
||||
double notional,
|
||||
string tradeType,
|
||||
string exerciseDate,
|
||||
double participationRate,
|
||||
double principalRate,
|
||||
bool isAnnualized,
|
||||
double annualizeFactor,
|
||||
double[] coefficients = null,
|
||||
bool isMoneynessOption = false,
|
||||
double initialSpotPrice = 0.0,
|
||||
Dictionary<Date, double> dividends = null,
|
||||
bool hasNightMarket = false,
|
||||
bool commodityFuturesPreciseTimeMode = false,
|
||||
double timeToMaturityDays = double.NaN,
|
||||
double riskFreeRateOverride = double.NaN,
|
||||
double dividendRateOverride = double.NaN)
|
||||
{
|
||||
underlyingInstrumentType = ConvertInstrumentType(underlyingInstrumentType);
|
||||
|
||||
var exercise = ConvertExerciseType(exerciseType);
|
||||
var optionStartDate = startDate.ToDate();
|
||||
var underlyingMaturityDate = string.IsNullOrWhiteSpace(endDate) ? null : endDate.ToDate();
|
||||
var temp_exerciseDate = exerciseDate.ToDate();
|
||||
|
||||
if (temp_exerciseDate < optionStartDate)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Date[] exerciseDates = null;
|
||||
Date[] observationDates = null;
|
||||
if (exercise == OptionExercise.American)
|
||||
{
|
||||
exerciseDates = CalendarImpl.Get("chn").BizDaysBetweenDatesInclEndDay(optionStartDate, temp_exerciseDate).ToArray();
|
||||
observationDates = CalendarImpl.Get("chn").BizDaysBetweenDatesExcluStartDay(optionStartDate, temp_exerciseDate).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
exerciseDates = new Date[] { temp_exerciseDate };
|
||||
observationDates = new Date[] { temp_exerciseDate };
|
||||
}
|
||||
|
||||
if (underlyingInstrumentType == null)
|
||||
{
|
||||
throw new Exception("标的资产类型不能为空");
|
||||
}
|
||||
|
||||
var optionDayCount = CalculatorHelper.GetTradeDayCount();
|
||||
var pricingTOverride = double.IsNaN(timeToMaturityDays) ? double.NaN : QdpCalendarHelper.CalculateTFromDays(timeToMaturityDays, optionDayCount, optionStartDate.DateTime);
|
||||
|
||||
var syntheticNormalSpreadOption =
|
||||
new SyntheticNormalSpreadOption(
|
||||
optionStartDate,
|
||||
exercise,
|
||||
(OptionType)Enum.Parse(typeof(OptionType), optionType),
|
||||
strike,
|
||||
(InstrumentType)Enum.Parse(typeof(InstrumentType), underlyingInstrumentType),
|
||||
CalendarImpl.Get("chn"),
|
||||
optionDayCount.ToDayCountImpl(),
|
||||
CurrencyCode.CNY,
|
||||
CurrencyCode.CNY,
|
||||
exerciseDates,
|
||||
observationDates,
|
||||
coefficients, // coefficients
|
||||
notional,
|
||||
null,
|
||||
null,
|
||||
0.0,
|
||||
isMoneynessOption,
|
||||
initialSpotPrice,
|
||||
dividends,
|
||||
hasNightMarket: hasNightMarket,
|
||||
commodityFuturesPreciseTimeMode: commodityFuturesPreciseTimeMode,
|
||||
pricingToverride: pricingTOverride,
|
||||
riskFreeRateOverride: riskFreeRateOverride,
|
||||
dividendRateOverride: dividendRateOverride,
|
||||
participationRate: participationRate,
|
||||
isAnnualized: isAnnualized,
|
||||
annualizedFactor: annualizeFactor)
|
||||
{
|
||||
UnderlyingTickers = new string[] { underlyingTicker }
|
||||
};
|
||||
|
||||
if (string.IsNullOrWhiteSpace(tradeId))
|
||||
{
|
||||
tradeId = Guid.NewGuid().ToString();
|
||||
}
|
||||
return new SyntheticNormalSpreadOptionTrade(
|
||||
tradeId,
|
||||
tradeDate.ToDate(),
|
||||
syntheticNormalSpreadOption.StartDate,
|
||||
syntheticNormalSpreadOption.ExerciseDates.Last(),
|
||||
QdpConverter.ConvertTradeType(tradeType),
|
||||
syntheticNormalSpreadOption.Notional,
|
||||
0.0,
|
||||
syntheticNormalSpreadOption)
|
||||
{
|
||||
ValuationParameters = new OptionValuationParameters("RiskFreeDiscountCurve", MarketProxy.ConstantZeroCurve, volSurfaceName, syntheticNormalSpreadOption.UnderlyingTickers[0]),
|
||||
ProtectionRate = principalRate,
|
||||
ParticipationRate = participationRate,
|
||||
AnnualizedFactor = annualizeFactor,
|
||||
OriginalNotional = TradeHelper.GetStockEqvNotional(notional * initialSpotPrice, participationRate, annualizeFactor)
|
||||
};
|
||||
}
|
||||
|
||||
public static double[] CalculateSyntheticNormalSpreadCrossGammas(
|
||||
IQdpMarketProxy marketProxy,
|
||||
string valueDate,
|
||||
string underlyingTicker,
|
||||
string underlyingInstrumentType,
|
||||
double strike,
|
||||
string startDate,
|
||||
string endDate,
|
||||
string optionType,
|
||||
string exerciseType,
|
||||
double spotPrice,
|
||||
double[] coefficients,
|
||||
double notional,
|
||||
string volSurfaceName,
|
||||
double riskFreeRate,
|
||||
string tradeType,
|
||||
string 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 commodityFuturesPreciseTimeMode = false,
|
||||
string engineName = null,
|
||||
string discountCurveName = null,
|
||||
bool ignoreSkewMap = false,
|
||||
bool isForwardTrade = false,
|
||||
double timeToMaturityDays = double.NaN,
|
||||
double riskFreeRateOverride = double.NaN,
|
||||
double dividendRateOverride = double.NaN)
|
||||
{
|
||||
if (coefficients == null || coefficients.Length == 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string tradeId = null;
|
||||
|
||||
var trade = CreateSyntheticNormalSpreadOptionTrade(
|
||||
tradeId,
|
||||
volSurfaceName,
|
||||
valueDate,
|
||||
underlyingTicker,
|
||||
underlyingInstrumentType,
|
||||
strike,
|
||||
startDate,
|
||||
endDate,
|
||||
optionType,
|
||||
exerciseType,
|
||||
notional,
|
||||
tradeType,
|
||||
exerciseDate,
|
||||
participationRate,
|
||||
principalRate,
|
||||
isAnnualized,
|
||||
annualizeFactor,
|
||||
coefficients,
|
||||
isMoneynessOption,
|
||||
initialSpotPrice,
|
||||
dividends,
|
||||
hasNightMarket,
|
||||
commodityFuturesPreciseTimeMode,
|
||||
timeToMaturityDays,
|
||||
riskFreeRateOverride,
|
||||
dividendRateOverride);
|
||||
|
||||
var market = marketProxy.GetQdpMarket(valueDate);
|
||||
if (market == null)
|
||||
{
|
||||
marketProxy.CreateMarket(valueDate);
|
||||
market = marketProxy.GetQdpMarket(valueDate);
|
||||
if (market == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var volPart = QdpVolHelper.GetDefaultVolatility(1.3);
|
||||
|
||||
var volSurfaceWrap = new VolSurfaceBuilder
|
||||
{
|
||||
volSurfaceName = volSurfaceName,
|
||||
volSurfaceType = "MoneynessVol",
|
||||
interpolation = "BiLinear"
|
||||
}.SetVectors(volPart.VolTable).Build(valueDate);
|
||||
marketProxy.SaveVolSurface(volSurfaceWrap);
|
||||
}
|
||||
|
||||
var useLocalDiscountCurve = string.IsNullOrWhiteSpace(discountCurveName);
|
||||
|
||||
//设置DiscountCurve
|
||||
if (useLocalDiscountCurve)
|
||||
{
|
||||
discountCurveName = Guid.NewGuid().ToString();
|
||||
var discountCurve = CalculatorHelper.CreateConstantRiskFreeCurve(discountCurveName, riskFreeRate);
|
||||
marketProxy.AddYieldCurve(discountCurveName, valueDate, discountCurve);
|
||||
}
|
||||
|
||||
//设置标的价格
|
||||
marketProxy.AddStockPrice(underlyingTicker, valueDate, spotPrice);
|
||||
|
||||
OptionValuationParameters parameters;
|
||||
if (underlyingInstrumentType == "Stock")
|
||||
{
|
||||
//设置DividendCurve
|
||||
var dividendCurveName = Guid.NewGuid().ToString();
|
||||
var dividendCurve = CalculatorHelper.CreateConstantRiskFreeCurve(dividendCurveName, dividendRate);
|
||||
marketProxy.AddYieldCurve(dividendCurveName, valueDate, dividendCurve);
|
||||
|
||||
parameters = new OptionValuationParameters(
|
||||
isForwardTrade ? MarketProxy.ConstantZeroCurve : discountCurveName,
|
||||
dividendCurveName,
|
||||
volSurfaceName,
|
||||
underlyingTicker);
|
||||
}
|
||||
else
|
||||
{
|
||||
parameters = new OptionValuationParameters(
|
||||
isForwardTrade ? MarketProxy.ConstantZeroCurve : discountCurveName,
|
||||
MarketProxy.ConstantZeroCurve,
|
||||
volSurfaceName,
|
||||
underlyingTicker);
|
||||
}
|
||||
|
||||
var result = trade.CalcCrossGammas(marketProxy.GetQdpMarket(valueDate), parameters);
|
||||
if (result == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var crossGammas = new List<double>();
|
||||
|
||||
// 先获取返回的结果矩阵中的对角线上的元素,对应的是每个标的的Gamma
|
||||
for (var i = 0; i < coefficients.Length; ++i)
|
||||
{
|
||||
crossGammas.Add(result[i, i]);
|
||||
}
|
||||
|
||||
// 再获取两两对应的Cross Gamma
|
||||
for (var i = 0; i < coefficients.Length - 1; ++i)
|
||||
{
|
||||
for (var j = i + 1; j < coefficients.Length; ++j)
|
||||
{
|
||||
crossGammas.Add(result[i, j]);
|
||||
}
|
||||
}
|
||||
|
||||
return crossGammas.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user