Skip to content

Commit

Permalink
LV fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
gavbrennan committed Sep 18, 2024
1 parent 9b150c5 commit f554c69
Show file tree
Hide file tree
Showing 15 changed files with 206 additions and 15 deletions.
1 change: 1 addition & 0 deletions src/Qwack.Core/Basic/IVolSurface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public interface IVolSurface
string Name { get; }

Dictionary<string, IVolSurface> GetATMVegaScenarios(double bumpSize, DateTime? LastSensitivityDate);
Dictionary<string, IVolSurface> GetATMVegaWaveyScenarios(double bumpSize, DateTime? LastSensitivityDate);

Currency Currency { get; set; }
string AssetId { get; set; }
Expand Down
3 changes: 2 additions & 1 deletion src/Qwack.Core/Basic/RiskMetric.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public enum RiskMetric
HistSimVaR,
ContangoSwapDelta,
ParRate,
AssetParallelGamma
AssetParallelGamma,
AssetWaveyVega
}
}
110 changes: 109 additions & 1 deletion src/Qwack.Models/Risk/BasicMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public static Dictionary<string, ICube> ComputeBumpedScenarios(Dictionary<string
};
public static ICube AssetVega(this IPvModel pvModel, Currency reportingCcy, bool parallelize = true)
{
var bumpSize = 0.001;
var bumpSize = -0.01;
var cube = new ResultCube();
var dataTypes = new Dictionary<string, Type>
{
Expand Down Expand Up @@ -148,6 +148,114 @@ public static ICube AssetVega(this IPvModel pvModel, Currency reportingCcy, bool
return cube.Sort(new List<string> { AssetId, "PointDate", TradeType });
}

public static ICube AssetVegaWavey(this IPvModel pvModel, Currency reportingCcy)
{
var bumpSize = 0.01;
var cube = new ResultCube();
var dataTypes = new Dictionary<string, Type>
{
{ TradeId, typeof(string) },
{ TradeType, typeof(string) },
{ AssetId, typeof(string) },
{ "PointDate", typeof(DateTime) },
{ PointLabel, typeof(string) },
{ Metric, typeof(string) },
{ "Strike", typeof(double) },
{ "RefPrice", typeof(double) },
{ Consts.Cubes.Portfolio, typeof(string) }

};
var metaKeys = pvModel.Portfolio.Instruments.Where(x => x.TradeId != null).SelectMany(x => x.MetaData.Keys).Distinct().ToArray();
foreach (var key in metaKeys)
{
dataTypes[key] = typeof(string);
}
var insDict = pvModel.Portfolio.Instruments.Where(x => x.TradeId != null).ToDictionary(x => x.TradeId, x => x);
cube.Initialize(dataTypes);

var model = pvModel.VanillaModel;

foreach (var surfaceName in model.VolSurfaceNames)
{
var volObj = model.GetVolSurface(surfaceName);

var subPortfolio = new Portfolio()
{
Instruments = pvModel.Portfolio.Instruments.Where(x => (x is IHasVega || (x is CashWrapper cw && cw.UnderlyingInstrument is IHasVega)) && (x is IAssetInstrument ia) && ia.AssetIds.Contains(volObj.AssetId)).ToList()
};

if (subPortfolio.Instruments.Count == 0)
continue;

var strikesByTradeId = subPortfolio.Instruments.ToDictionary(t => t.TradeId, t => t.GetStrike());

var lastDateInBook = subPortfolio.LastSensitivityDate;

var basePvModel = pvModel.Rebuild(model, subPortfolio);
var pvCube = basePvModel.PV(reportingCcy);

var tidIx = pvCube.GetColumnIndex(TradeId);
var tTypeIx = pvCube.GetColumnIndex(TradeType);
var pfIx = pvCube.GetColumnIndex(Consts.Cubes.Portfolio);

var bumpedSurfaces = volObj.GetATMVegaWaveyScenarios(bumpSize, lastDateInBook);
var maturityDict = bumpedSurfaces.Keys.ToDictionary(x => x, volObj.PillarDatesForLabel);

foreach(var bCurve in bumpedSurfaces.OrderByDescending(x=> maturityDict[x.Key]))
{
var pvRows = pvCube.GetAllRows();
var newVanillaModel = model.Clone();
newVanillaModel.AddVolSurface(surfaceName, bCurve.Value);
var bumpedPvModel = basePvModel.Rebuild(newVanillaModel, subPortfolio);
var bumpedPVCube = bumpedPvModel.PV(reportingCcy);
var bumpedRows = bumpedPVCube.GetAllRows();
if (bumpedRows.Length != pvRows.Length)
throw new Exception("Dimensions do not match");

for (var i = 0; i < bumpedRows.Length; i++)
{
//vega quoted for a 1% shift, irrespective of bump size
var vega = (bumpedRows[i].Value - pvRows[i].Value) / bumpSize * 0.01;
if (vega != 0.0)
{
var trdId = bumpedRows[i].MetaData[tidIx] as string;
var pillarDate = bCurve.Value.PillarDatesForLabel(bCurve.Key);
var fwdCurve = model.VanillaModel.GetPriceCurve(bCurve.Value.AssetId);
var fwdPrice = fwdCurve?.GetPriceForDate(pillarDate) ?? 100;
var cleanVol = model.VanillaModel.GetVolForDeltaStrikeAndDate(bCurve.Value.AssetId, pillarDate, 0.5);
var row = new Dictionary<string, object>
{
{ TradeId, trdId },
{ TradeType, bumpedRows[i].MetaData[tTypeIx] },
{ AssetId, surfaceName },
{ "PointDate", pillarDate },
{ PointLabel, bCurve.Key },
{ Metric, "Vega" },
{ "Strike", strikesByTradeId[trdId] },
{ "Portfolio", bumpedRows[i].MetaData[pfIx] },
{ "RefPrice", cleanVol },
};

if (insDict.TryGetValue((string)bumpedRows[i].MetaData[tidIx], out var trade))
{
foreach (var key in metaKeys)
{
if (trade.MetaData.TryGetValue(key, out var metaData))
row[key] = metaData;
}
}
cube.AddRow(row, vega);
}
}

pvCube = bumpedPVCube;
}
}

return cube.Sort(new List<string> { AssetId, "PointDate", TradeType });
}


public static ICube AssetSegaRega(this IPvModel pvModel, Currency reportingCcy)
{
var bumpSize = 0.001;
Expand Down
1 change: 1 addition & 0 deletions src/Qwack.Options/VolSurfaces/CompositeVolSurface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,5 +104,6 @@ public double GetVolForDeltaStrike(double deltaStrike, double maturity, double f

public double InverseCDF(DateTime expiry, double fwd, double p) => VolSurfaceEx.InverseCDFex(this, OriginDate.CalculateYearFraction(expiry, DayCountBasis.Act365F), fwd, p);
public double CDF(DateTime expiry, double fwd, double strike) => this.GenerateCDF2(100, expiry, fwd).Interpolate(strike);
public Dictionary<string, IVolSurface> GetATMVegaWaveyScenarios(double bumpSize, DateTime? LastSensitivityDate) => throw new NotImplementedException();
}
}
5 changes: 5 additions & 0 deletions src/Qwack.Options/VolSurfaces/ConstantVolSurface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ public void Build(DateTime originDate, double volatility)
{ "Flat", new ConstantVolSurface(OriginDate, Volatility + bumpSize) {Currency = Currency, AssetId=AssetId, Name=Name } }
};

public Dictionary<string, IVolSurface> GetATMVegaWaveyScenarios(double bumpSize, DateTime? LastSensitivityDate) => new()
{
{ "Flat", new ConstantVolSurface(OriginDate, Volatility + bumpSize) {Currency = Currency, AssetId=AssetId, Name=Name } }
};

public DateTime PillarDatesForLabel(string label) => OriginDate;

public double InverseCDF(DateTime expiry, double fwd, double p) => VolSurfaceEx.InverseCDFex(this, OriginDate.CalculateYearFraction(expiry, DayCountBasis.Act365F), fwd, p);
Expand Down
34 changes: 33 additions & 1 deletion src/Qwack.Options/VolSurfaces/GridVolSurface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ private double GetDeltaStrikeForAbs(double fwd, double strike, double maturity)

public double GetVolForDeltaStrike(double strike, DateTime expiry, double forward) => GetVolForDeltaStrike(strike, TimeBasis.CalculateYearFraction(OriginDate, expiry), forward);

public Dictionary<string, IVolSurface> GetATMVegaScenarios(double bumpSize, DateTime? LastSensitivityDate)
public virtual Dictionary<string, IVolSurface> GetATMVegaScenarios(double bumpSize, DateTime? LastSensitivityDate)
{
var o = new Dictionary<string, IVolSurface>();

Expand Down Expand Up @@ -256,6 +256,37 @@ public Dictionary<string, IVolSurface> GetATMVegaScenarios(double bumpSize, Date
return o;
}

public virtual Dictionary<string, IVolSurface> GetATMVegaWaveyScenarios(double bumpSize, DateTime? LastSensitivityDate)
{
var o = new Dictionary<string, IVolSurface>();

var lastBumpIx = Expiries.Length;

if (LastSensitivityDate.HasValue)
{
var ix = Array.BinarySearch(Expiries, LastSensitivityDate.Value);
ix = (ix < 0) ? ~ix : ix;
ix += 2;
lastBumpIx = Min(ix, lastBumpIx); //cap at last pillar
}

var volsBumped = (double[][])Volatilities.Clone();

for (var i = lastBumpIx-1; i >= 0; i--)
{
volsBumped[i] = volsBumped[i].Select(x => x + bumpSize).ToArray();
o.Add(PillarLabels[i],
new GridVolSurface(OriginDate, Strikes, Expiries, volsBumped, StrikeType, StrikeInterpolatorType, TimeInterpolatorType, TimeBasis, PillarLabels)
{
Currency = Currency,
AssetId = AssetId
});
}

return o;
}


public DateTime PillarDatesForLabel(string label)
{
var labelIx = Array.IndexOf(PillarLabels, label);
Expand Down Expand Up @@ -416,5 +447,6 @@ public double InverseCDF(DateTime expiry, double fwd, double p)
Strikes = Strikes,
Volatilities = new MultiDimArray<double>(Volatilities)
};

}
}
2 changes: 2 additions & 0 deletions src/Qwack.Options/VolSurfaces/InverseFxSurface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public InverseFxSurface(string Name, IATMVolSurface fxSurface, ICurrencyProvider
public double CDF(DateTime expiry, double fwd, double strike) => 1.0 - FxSurface.CDF(expiry, 1 / fwd, 1 / strike);

public Dictionary<string, IVolSurface> GetATMVegaScenarios(double bumpSize, DateTime? LastSensitivityDate) => FxSurface.GetATMVegaScenarios(bumpSize, LastSensitivityDate);
public Dictionary<string, IVolSurface> GetATMVegaWaveyScenarios(double bumpSize, DateTime? LastSensitivityDate) => FxSurface.GetATMVegaWaveyScenarios(bumpSize, LastSensitivityDate);


public double GetForwardATMVol(DateTime startDate, DateTime endDate) => FxSurface.GetForwardATMVol(startDate, endDate);
public double GetForwardATMVol(double start, double end) => FxSurface.GetForwardATMVol(start, end);
Expand Down
20 changes: 12 additions & 8 deletions src/Qwack.Options/VolSurfaces/LocalVol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ public static double[][] ComputeLocalVarianceOnGridFromCalls2(this IVolSurface V
var T = timeSteps[it];
var T1 = timeSteps[it - 1];
var fwd = fwds[it];
var bump = fwd / 10000;
var bump = fwd / 100000;
var fwdtm1 = fwds[it - 1];
var rmq = Log(fwd / fwdtm1) / (T - T1);
var numberOfStrikes = strikes[it - 1].Length;
Expand All @@ -159,21 +159,25 @@ public static double[][] ComputeLocalVarianceOnGridFromCalls2(this IVolSurface V
{

var K = strikes[it][ik];

var V = VanillaSurface.GetVolForAbsoluteStrike(K, T, fwd);
var Vp = VanillaSurface.GetVolForAbsoluteStrike(K+bump, T, fwd);
var Vm = VanillaSurface.GetVolForAbsoluteStrike(K-bump, T, fwd);

var C = BlackFunctions.BlackPV(fwd, K, 0.0, T, V, OptionType.C);
var Vtm1 = VanillaSurface.GetVolForAbsoluteStrike(K, T1, fwdtm1);

var Cp1 = BlackFunctions.BlackPV(fwd, K + bump, 0.0, T, V, OptionType.C);
var Cp2 = BlackFunctions.BlackPV(fwd, K + 2*bump, 0.0, T, V, OptionType.C);
var Cm1 = BlackFunctions.BlackPV(fwd, K - bump, 0.0, T, V, OptionType.C);
var Cm2 = BlackFunctions.BlackPV(fwd, K - 2 * bump, 0.0, T, V, OptionType.C);
var Cp1 = BlackFunctions.BlackPV(fwd, K + bump, 0.0, T, Vp, OptionType.C);
//var Cp2 = BlackFunctions.BlackPV(fwd, K + 2 * bump, 0.0, T, V, OptionType.C);
var Cm1 = BlackFunctions.BlackPV(fwd, K - bump, 0.0, T, Vm, OptionType.C);
//var Cm2 = BlackFunctions.BlackPV(fwd, K - 2 * bump, 0.0, T, V, OptionType.C);


//var dcdt = -BlackFunctions.BlackTheta(fwd, K, 0.0, T, V, OptionType.C);
var dcdt = -(BlackFunctions.BlackPV(fwdtm1, K, 0.0, T1, Vtm1, OptionType.C) - C) / (T - T1);
var dcdt = (C - BlackFunctions.BlackPV(fwdtm1, K, 0.0, T1, Vtm1, OptionType.C)) / (T - T1);
var dcdk = (Cp1 - Cm1) / (2 * bump);
var dcdkp = (Cp2 - Cp1) / bump;
var dcdkm = (Cm1 - Cm2) / bump;
var dcdkp = (Cp1 - C) / bump;
var dcdkm = (C - Cm1) / bump;
var d2cdk2 = (dcdkp - dcdkm) / bump;

var localVariance = d2cdk2 == 0 ? V * V : (dcdt - rmq * (C - K * dcdk)) / (0.5 * K * K * d2cdk2);
Expand Down
32 changes: 30 additions & 2 deletions src/Qwack.Options/VolSurfaces/RiskyFlySurface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ private int LastIx(DateTime? LastSensitivityDate)
return lastExpiry;
}

public new Dictionary<string, IVolSurface> GetATMVegaScenarios(double bumpSize, DateTime? LastSensitivityDate)
public override Dictionary<string, IVolSurface> GetATMVegaScenarios(double bumpSize, DateTime? LastSensitivityDate)
{
var o = new Dictionary<string, IVolSurface>();

Expand All @@ -169,7 +169,35 @@ private int LastIx(DateTime? LastSensitivityDate)
{
var volsBumped = (double[])ATMs.Clone();
volsBumped[i] += bumpSize;
o.Add(PillarLabels[i], new RiskyFlySurface(OriginDate, volsBumped, Expiries, WingDeltas, Riskies, Flies, Forwards, WingQuoteType, AtmVolType, StrikeInterpolatorType, TimeInterpolatorType, PillarLabels));
var bumped = new RiskyFlySurface(OriginDate, volsBumped, Expiries, WingDeltas, Riskies, Flies, Forwards, WingQuoteType, AtmVolType, StrikeInterpolatorType, TimeInterpolatorType, PillarLabels)
{
AssetId = AssetId,
Currency = Currency,
Name = Name,
};
o.Add(PillarLabels[i], bumped);
}

return o;
}

public override Dictionary<string, IVolSurface> GetATMVegaWaveyScenarios(double bumpSize, DateTime? LastSensitivityDate)
{
var o = new Dictionary<string, IVolSurface>();

var lastExpiry = LastIx(LastSensitivityDate);
var volsBumped = (double[])ATMs.Clone();

for (var i = lastExpiry-1; i >=0; i--)
{
volsBumped[i] += bumpSize;
var bumped = new RiskyFlySurface(OriginDate, volsBumped, Expiries, WingDeltas, Riskies, Flies, Forwards, WingQuoteType, AtmVolType, StrikeInterpolatorType, TimeInterpolatorType, PillarLabels)
{
AssetId = AssetId,
Currency = Currency,
Name = Name,
};
o.Add(PillarLabels[i], bumped);
}

return o;
Expand Down
1 change: 1 addition & 0 deletions src/Qwack.Options/VolSurfaces/SABRVolSurface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,5 +237,6 @@ public static double GetVolForAbsoluteStrike(double strike, double maturity, dou

public double InverseCDF(DateTime expiry, double fwd, double p) => VolSurfaceEx.InverseCDFex(this, OriginDate.CalculateYearFraction(expiry, DayCountBasis.Act365F), fwd, p);
public double CDF(DateTime expiry, double fwd, double strike) => this.GenerateCDF2(100, expiry, fwd).Interpolate(strike);
public Dictionary<string, IVolSurface> GetATMVegaWaveyScenarios(double bumpSize, DateTime? LastSensitivityDate) => throw new NotImplementedException();
}
}
1 change: 1 addition & 0 deletions src/Qwack.Options/VolSurfaces/SVIVolSurface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,5 +161,6 @@ public static double GetVolForAbsoluteStrike(double strike, double maturity, dou
public double InverseCDF(DateTime expiry, double fwd, double p) => VolSurfaceEx.InverseCDFex(this, OriginDate.CalculateYearFraction(expiry, DayCountBasis.Act365F), fwd, p);

public double CDF(DateTime expiry, double fwd, double strike) => this.GenerateCDF2(100, expiry, fwd).Interpolate(strike);
public Dictionary<string, IVolSurface> GetATMVegaWaveyScenarios(double bumpSize, DateTime? LastSensitivityDate) => throw new NotImplementedException();
}
}
1 change: 1 addition & 0 deletions src/Qwack.Options/VolSurfaces/SparsePointSurface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,5 +102,6 @@ public Dictionary<string, IVolSurface> GetATMVegaScenarios(double bumpSize, Date
Vols = _vols.ToDictionary(kv => $"{kv.Key.expiry:yyyy-MM-dd}~{kv.Key.strike}", kv => kv.Value),
PointLabels = _pillarLabels.ToDictionary(kv => $"{kv.Key:yyyy-MM-dd}", kv => kv.Value),
};
public Dictionary<string, IVolSurface> GetATMVegaWaveyScenarios(double bumpSize, DateTime? LastSensitivityDate) => throw new NotImplementedException();
}
}
6 changes: 6 additions & 0 deletions src/Qwack.Paths/Processes/BlackSingleAsset.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using Qwack.Core.Models;
Expand Down Expand Up @@ -101,6 +102,11 @@ public void Finish(IFeatureCollection collection)
var spot = _forwardCurve(time) * driftAdj;
var varStart = Pow(_surface.GetForwardATMVol(0, prevTime), 2) * prevTime;
var varEnd = Pow(atmVol, 2) * time;
var x0 = varEnd - varStart;
if (x0 < 0)
{
Debug.Assert(true);
}
var fwdVariance = Max(0, varEnd - varStart);
_vols[t] = Sqrt(fwdVariance / _timesteps.TimeSteps[t]);
_drifts[t] = Log(spot / prevSpot) / _timesteps.TimeSteps[t];
Expand Down
2 changes: 1 addition & 1 deletion version.props
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<Project>
<PropertyGroup>
<VersionPrefix>0.8.89</VersionPrefix>
<VersionPrefix>0.8.90</VersionPrefix>
</PropertyGroup>
</Project>
2 changes: 1 addition & 1 deletion version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.8.89
0.8.90

0 comments on commit f554c69

Please sign in to comment.