Custom Volatility Curve

Building a Custom VolatilityCurve with the Freeway VolatilityCurve.Custom API

The Custom Volatility Curve API was added to Freeway/Metro in version 6.1.7 and allows the user the create their own implementation of a Volatility Curve that can be uploaded via onramp and managed either by a freeway job or via the Trade Sheets Model Control.

The VolatilityCurve.Custom class is an object that is uploaded via Freeway from which the Metro CustomVolatilityCurve object pulls its values. The VolatilityCurve.Custom object is implemented by the Freeway user and lives within the CustomVolatiltyCurve (and not in Freeway) in the Theoretical Center after upload.

The VolatilityCurve.Custom API

There are several methods described in the VolatilityCurve.Custom java doc that you need to implement for the custom skew to properly interact with the system. They are listed below:

String[] getParameters()

This method returns an array of Strings to the system and is the means by which clients (tradesheets and your freeway jobs) knows which parameters can be listed in the TradeSheets Model Control and changed. Each String in the returned array should correspond to a Parameter that can be set and fetched.

String getParameter(String)

This method returns the value of the desired parameter from your curve object. The system uses this method to know what to display in various cells in the TradeSheet Model Control. This also helps us understand why the value returned is a String. This means you can have a skew with a non-numeric parameter. By default, you can get the parameters "ATM Vol" and "ATM Strike", although you cannot set them--they are set by the system.

String setParameter(String, String)

This method allows clients to set the desired parameter (first argument) to the desired value (second argument). The system uses this to feed parameter values to your curve from the TradeSheets Model Control. Your freeway jobs can also use this method to set custom skew parameters accordingly.

double volatilityAt(double)

This method is used by the system to determine what volatility will be associated with which strike (no other point types are currently supported with the VolatilityCurve.Custom object), which is the argument passed to the method. How you represent your skew internally is at your discretion.

double slopeAt(double)

This method is used by the system to determine what the slope is of your skew at the desired strike. While the returned value has no implication on the graph of your skew, it is used by the Theoretical Center to come up with adjusted greeks. For this reason you probably will want to ensure your custom curve returns an approximated function whose first derivatives (slopes) are continuous.

double curvatureAt(double)

This method is used by the system to determine what the curvature (second derivative, with respect to strike) is of your skew at the desired strike. As with the slopeAt(double) method, this has no implication on the charted skew, but is used by the Theoretical center to come up with adjusted greeks for Skews using a VolatilitySlide/VolatilityPath.

void prepare(double, double, double)

This method is the main point of interaction between your skew and the slide your skew is using (if any). If you do nothing in this method, then your skew will not leverage your Volatility Path/Slide, even if you have one set! Here you are given the underlying price (first argument), the forward price (second argument), which will use the interest rate you have set for the corresponding instrument month, and the volatility at the forward point on your slide (third argument).

getCopy()

This returns a copy of your VolatilityCurve.Custom implementation. It is important that this is a deepy copy.

In addition to this, you should implement a default no-argument constructor. Any fields and methods beyond this you can implement and expose for your Freeway jobs to use.

Some important caveats of this before we move onto a code sample:

  • The VolatilityCurve.Custom object only supports the strike point type
  • Custom Volatility Curves can use no slides, built-in Metro volatility slides, or custom volatility slides
  • Parameters that start with the prefix "Slide" will be used as Custom Slide parameters
  • You cannot fit Custom Volatility Curves via the Trade Sheets Model Control 'Imply' functionality
  • There is no debugging environment/logging environment for custom volatility curves. When you use your skew via the Trade Sheets Model Control, anything logged to Standard out will be picked up in the oc_cache/{yourip}_{yourlaunchpageport}_optionscity{yourinstancenumber}/metro/log/output.log. You should avoid print statements in your skew code in production as it does not use low-priority threads, as is the case for traditional Freeway logging.

Code Sample

The code below is an example of one such non-trivial implementation of VolatilityCurve.Custom. This is the ElevenPointStraightLineCurve, which is a skew that allows for 11 points--one denoting the center of the custom skew, 5 denoting the left (put) side of the skew, and 5 denoting the right (call) side of the skew. Vols in between each points are linearly-interpolated.

package curve;

import com.optionscity.freeway.api.VolatilityCurve;

import java.io.Serializable;
import java.util.*;

public class ElevenPointStraightLineCurve extends VolatilityCurve.Custom {

/*******************************************************************************************************************
* Parameters
******************************************************************************************************************/

// Strike Point Params
private static final String PUT_WING_STRIKE_PARAM_NAME = "putWing"; // Not manageable by user
private static final String PUT_FIFTH_STRIKE_PARAM_NAME = "putFifthStrike";
private static final String PUT_FOURTH_STRIKE_PARAM_NAME = "putFourthStrike";
private static final String PUT_THIRD_STRIKE_PARAM_NAME = "putThirdStrike";
private static final String PUT_SECOND_STRIKE_PARAM_NAME = "putSecondStrike";
private static final String PUT_FIRST_STRIKE_PARAM_NAME = "putFirstStrike";
private static final String ATM_STRIKE_PARAM_NAME = "atm";
private static final String CALL_FIRST_STRIKE_PARAM_NAME = "callFirstStrike";
private static final String CALL_SECOND_STRIKE_PARAM_NAME = "callSecondStrike";
private static final String CALL_THIRD_STRIKE_PARAM_NAME = "callThirdStrike";
private static final String CALL_FOURTH_STRIKE_PARAM_NAME = "callFourthStrike";
private static final String CALL_FIFTH_STRIKE_PARAM_NAME = "callFifthStrike";

private static final String[] strikeParams = {PUT_FIFTH_STRIKE_PARAM_NAME, PUT_FOURTH_STRIKE_PARAM_NAME, PUT_THIRD_STRIKE_PARAM_NAME, PUT_SECOND_STRIKE_PARAM_NAME, PUT_FIRST_STRIKE_PARAM_NAME,
ATM_STRIKE_PARAM_NAME,
CALL_FIRST_STRIKE_PARAM_NAME, CALL_SECOND_STRIKE_PARAM_NAME, CALL_THIRD_STRIKE_PARAM_NAME, CALL_FOURTH_STRIKE_PARAM_NAME, CALL_FIFTH_STRIKE_PARAM_NAME};
private static final Set<String> strikeParamSet = new HashSet<>(Arrays.asList(strikeParams));

private static Map<String, Double> defaultStrikes = new HashMap<>();
static {
defaultStrikes.put(PUT_WING_STRIKE_PARAM_NAME, Double.NEGATIVE_INFINITY);
defaultStrikes.put(PUT_FIFTH_STRIKE_PARAM_NAME, 10d);
defaultStrikes.put(PUT_FOURTH_STRIKE_PARAM_NAME, 15d);
defaultStrikes.put(PUT_THIRD_STRIKE_PARAM_NAME, 20d);
defaultStrikes.put(PUT_SECOND_STRIKE_PARAM_NAME, 25d);
defaultStrikes.put(PUT_FIRST_STRIKE_PARAM_NAME, 30d);
defaultStrikes.put(ATM_STRIKE_PARAM_NAME, 35d);
defaultStrikes.put(CALL_FIRST_STRIKE_PARAM_NAME, 35d);
defaultStrikes.put(CALL_SECOND_STRIKE_PARAM_NAME, 40d);
defaultStrikes.put(CALL_THIRD_STRIKE_PARAM_NAME, 45d);
defaultStrikes.put(CALL_FOURTH_STRIKE_PARAM_NAME, 50d);
defaultStrikes.put(CALL_FIFTH_STRIKE_PARAM_NAME, 55d);
}

// Vol Params
private static final String PUT_WING_VOL_PARAM_NAME = "putWingVol"; // Not manageable by user
private static final String PUT_FIFTH_VOL_PARAM_NAME = "putFifthVol";
private static final String PUT_FOURTH_VOL_PARAM_NAME = "putFourthVol";
private static final String PUT_THIRD_VOL_PARAM_NAME = "putThirdVol";
private static final String PUT_SECOND_VOL_PARAM_NAME = "putSecondVol";
private static final String PUT_FIRST_VOL_PARAM_NAME = "putFirstVol";
private static final String ATM_VOL_PARAM_NAME = "atmVol";
private static final String CALL_FRIST_VOL_PARAM_NAME = "callFirstVol";
private static final String CALL_SECOND_VOL_PARAM_NAME = "callSecondVol";
private static final String CALL_THIRD_VOL_PARAM_NAME = "callThirdVol";
private static final String CALL_FOURTH_VOL_PARAM_NAME = "callFourthVol";
private static final String CALL_FIFTH_VOL_PARAM_NAME = "callFifthVol";

private static final String[] volParams = {PUT_FIFTH_VOL_PARAM_NAME, PUT_FOURTH_VOL_PARAM_NAME, PUT_THIRD_VOL_PARAM_NAME, PUT_SECOND_VOL_PARAM_NAME, PUT_FIRST_VOL_PARAM_NAME,
ATM_VOL_PARAM_NAME,
CALL_FRIST_VOL_PARAM_NAME, CALL_SECOND_VOL_PARAM_NAME, CALL_THIRD_VOL_PARAM_NAME, CALL_FOURTH_VOL_PARAM_NAME, CALL_FIFTH_VOL_PARAM_NAME};
private static final Set<String> volParamSet = new HashSet<>(Arrays.asList(volParams));

private static Map<String, Double> defaultVols = new HashMap<>();
static {
defaultVols.put(PUT_WING_VOL_PARAM_NAME, 0d);
defaultVols.put(PUT_FIFTH_VOL_PARAM_NAME, 0d);
defaultVols.put(PUT_FOURTH_VOL_PARAM_NAME, 0d);
defaultVols.put(PUT_THIRD_VOL_PARAM_NAME, 0d);
defaultVols.put(PUT_SECOND_VOL_PARAM_NAME, 0d);
defaultVols.put(PUT_FIRST_VOL_PARAM_NAME, 0d);
defaultVols.put(ATM_VOL_PARAM_NAME, 0d);
defaultVols.put(CALL_FRIST_VOL_PARAM_NAME, 0d);
defaultVols.put(CALL_SECOND_VOL_PARAM_NAME, 0d);
defaultVols.put(CALL_THIRD_VOL_PARAM_NAME, 0d);
defaultVols.put(CALL_FOURTH_VOL_PARAM_NAME, 0d);
defaultVols.put(CALL_FIFTH_VOL_PARAM_NAME, 0d);
}

private static Map<String, String> strikeToVolParamMap = new HashMap<>();
static {
strikeToVolParamMap.put(PUT_FIFTH_STRIKE_PARAM_NAME, PUT_FIFTH_VOL_PARAM_NAME);
strikeToVolParamMap.put(PUT_FOURTH_STRIKE_PARAM_NAME, PUT_FOURTH_VOL_PARAM_NAME);
strikeToVolParamMap.put(PUT_THIRD_STRIKE_PARAM_NAME, PUT_THIRD_VOL_PARAM_NAME);
strikeToVolParamMap.put(PUT_SECOND_STRIKE_PARAM_NAME, PUT_SECOND_VOL_PARAM_NAME);
strikeToVolParamMap.put(PUT_FIRST_STRIKE_PARAM_NAME, PUT_FIRST_VOL_PARAM_NAME);
strikeToVolParamMap.put(ATM_STRIKE_PARAM_NAME, ATM_VOL_PARAM_NAME);
strikeToVolParamMap.put(CALL_FIRST_STRIKE_PARAM_NAME, CALL_FRIST_VOL_PARAM_NAME);
strikeToVolParamMap.put(CALL_SECOND_STRIKE_PARAM_NAME, CALL_SECOND_VOL_PARAM_NAME);
strikeToVolParamMap.put(CALL_THIRD_STRIKE_PARAM_NAME, CALL_THIRD_VOL_PARAM_NAME);
strikeToVolParamMap.put(CALL_FOURTH_STRIKE_PARAM_NAME, CALL_FOURTH_VOL_PARAM_NAME);
strikeToVolParamMap.put(CALL_FIFTH_STRIKE_PARAM_NAME, CALL_FIFTH_VOL_PARAM_NAME);
}

private Map<String, Double> pointStrikes;
private Map<String, Double> pointVols;
private SplineSection[] sections = new SplineSection[strikeParams.length+1];
private boolean pointValuesSetup = false;
private boolean splineSectionsSetup = false;

/*******************************************************************************************************************
* Constructors and Initialization
******************************************************************************************************************/

public ElevenPointStraightLineCurve() {
//System.out.println("Setting up ElevenPointStraightLineCurve"); //TODO remove
setupPointValues();
setupSplineSections();
}

public ElevenPointStraightLineCurve(Map<String, Double> pointStrikes, Map<String, Double> pointVols, SplineSection[] sections,
boolean pointValuesSetup, boolean splineSectionsSetup) {
setupPointValues();
this.pointStrikes = new HashMap<>(pointStrikes);
this.pointVols = new HashMap<>(pointVols);
this.sections = new SplineSection[sections.length];
for (int i = 0; i < this.sections.length; i++) {
this.sections[i] = new SplineSection(sections[i]);
}
this.pointValuesSetup = pointValuesSetup;
this.splineSectionsSetup = splineSectionsSetup;
}

private void setupPointValues() {
//System.out.println("Setting up point values"); //TODO remove
if (pointStrikes == null)
pointStrikes = new HashMap<>();

for (String point : defaultStrikes.keySet()) {
if (pointStrikes.get(point) == null)
pointStrikes.put(point, defaultStrikes.get(point));

if (Double.isNaN(pointStrikes.get(point)))
pointStrikes.put(point, defaultStrikes.get(point));
}

if (pointVols == null)
pointVols = new HashMap<>();

for (String point : defaultVols.keySet()) {
if (pointVols.get(point) == null)
pointVols.put(point, defaultVols.get(point));

if (Double.isNaN(pointVols.get(point)))
pointVols.put(point, defaultVols.get(point));
}

pointValuesSetup = true;
}

private void setupSplineSections() {
sections[0] = new SplineSection(PUT_WING_STRIKE_PARAM_NAME, PUT_WING_VOL_PARAM_NAME);
sections[0].setStartStrike(Double.NEGATIVE_INFINITY);

for (int i = 0; i < strikeParams.length; i++) {
sections[i+1] = new SplineSection(strikeParams[i], volParams[i]);

Double strike = pointStrikes.get(strikeParams[i]);
System.out.println("strike is " + strike + " for strikeParam " + strikeParams[i]);

sections[i].setEndStrike(strike);
sections[i+1].setStartStrike(strike);

Double vol = pointVols.get(volParams[i]);
sections[i].setEndVol(vol);
sections[i+1].setStartVol(vol);
}

sections[strikeParams.length].setEndStrike(Double.POSITIVE_INFINITY);
splineSectionsSetup = true;
}

/*******************************************************************************************************************
* API Methods
******************************************************************************************************************/

@Override
public double volatilityAt(double strike) {

SplineSection splineSection = sections[0];
for (int i = 0; i < sections.length; i++) {
if (sections[i].strikeInSection(strike)) {
splineSection = sections[i];
}
}

if (splineSection == sections[0]) {
Double sectionEndStrike = splineSection.getEndStrike();
Double sectionEndVol = splineSection.getEndVol();
Double s = strike - sectionEndStrike;

System.out.println("section end strike for strike " + strike + " is " + sectionEndStrike);
System.out.println("section end vol for strike " + strike + " is " + sectionEndVol);

return slopeAt(strike)*s + sectionEndVol;

} else {

Double sectionStartStrike = splineSection.getStartStrike();
Double sectionStartVol = splineSection.startVol;
Double s = strike - splineSection.getStartStrike();

System.out.println("section start strike for strike " + strike + " is " + sectionStartStrike);
System.out.println("section start vol for strike " + strike + " is " + sectionStartVol);

return slopeAt(strike) * s + sectionStartVol;
}
}

@Override
public double slopeAt(double strike) {
for (int i = 0; i < sections.length; i++) {
if (sections[i].strikeInSection(strike)) {

System.out.println("End vol at " + strike + " is " + sections[i].getEndVol());
System.out.println("Start vol at " + strike + " is " + sections[i].getStartVol());

double slope = (sections[i].getEndVol() - sections[i].getStartVol())/(sections[i].getEndStrike() - sections[i].getStartStrike());
System.out.println("Slope at strike " + strike + " is " + slope);
return slope;
}
}

return 0;
}

@Override
public double curvatureAt(double strike) {
return 0;
}

@Override
public void prepare(double v, double v1, double v2) {
/*if (!pointValuesSetup)
setupPointValues();

if (!splineSectionsSetup)
setupSplineSections();*/
}

@Override
public String[] getParameters() {
String[] parameters = new String[strikeParams.length + volParams.length];
for (int i = 0; i < strikeParams.length; i++) {
parameters[2*i] = strikeParams[i];
parameters[2*i+1] = volParams[i];
}
return parameters;
}

@Override
public String getParameter(String parameter) {
for (int i = 0; i < sections.length; i++) {
if (sections[i].startStrikeParam.equals(parameter)) {

System.out.println("section start strike " + sections[i].getStartStrike()); //TODO remove
return Double.toString(sections[i].getStartStrike());
} else if (sections[i].startVolParam.equals(parameter)) {

System.out.println("section end strike " + sections[i].getStartVol()); //TODO remove
return Double.toString(sections[i].getStartVol());
}
}

return Double.toString(0d);
}

@Override
public void setParameter(String parameter, String value) {
System.out.println("Attempting to set " + parameter + " to " + value);

SplineSection lastSplineSection = sections[0];
for (int i = 0; i < sections.length; i++) {
if (sections[i].startStrikeParam.equals(parameter)) {

Double newStrike = Double.parseDouble(value);
boolean newStrikeLessThanPreviousSectionStartStrike = newStrike <= lastSplineSection.getStartStrike();
//boolean newStrikeGreaterThanNextSectionStartStrike = newStrike >= sections[i].getEndStrike();
boolean newStrikeValid = newStrike != sections[i].getStartStrike();

System.out.println("newStrikeLessThanPrev? " + newStrikeLessThanPreviousSectionStartStrike);
//System.out.println("newStrikeGreaterThanNext? " + newStrikeGreaterThanNextSectionStartStrike);
System.out.println("newStrikeValid? " + newStrikeValid);

if (!newStrikeLessThanPreviousSectionStartStrike && newStrikeValid) {
lastSplineSection.setEndStrike(newStrike);
sections[i].setStartStrike(newStrike);
}

return;
} else if (sections[i].startVolParam.equals(parameter)) {

Double newVol = Double.parseDouble(value);
if (newVol != sections[i].getStartVol()) {
lastSplineSection.setEndVol(newVol);
sections[i].setStartVol(newVol);
}
return;
}
lastSplineSection = sections[i];
}
}

@Override
public Custom getCopy() {
return new ElevenPointStraightLineCurve(this.pointStrikes, this.pointVols, this.sections, this.pointValuesSetup, this.splineSectionsSetup);
}

/**
*
*/
class SplineSection implements Serializable {
public double[] coefficients;
public final String startStrikeParam;
public final String startVolParam;

private Double startStrike = 0d;
private Double endStrike = 0d;

private Double startVol = 0d;
private Double endVol = 0d;

public SplineSection(String startStrikeParam, String startVolParam) {
this.startStrikeParam = startStrikeParam;
this.startVolParam = startVolParam;
}

public SplineSection(SplineSection other) {
this.startStrikeParam = other.startStrikeParam;
this.startVolParam = other.startVolParam;
this.startStrike = new Double(other.getStartStrike());
this.endStrike = new Double(other.getEndStrike());
this.startVol = new Double(other.startVol);
this.endVol = new Double(other.endVol);
}

public double getStartStrike() {
return startStrike;
}

public double getEndStrike() {
return endStrike;
}

public void setStartStrike(Double newStartStrike) {
if (newStartStrike == null) {
System.out.println("newStartStrike is null, not setting"); //TODO remove
return;
}

if (endStrike == null) {
System.out.println("endStrike is null, setting startStrike"); //TODO remove
startStrike = newStartStrike;
return;
}

if (Double.isNaN(endStrike)) {
System.out.println("endStrike is NaN, setting startStrike"); //TODO remove
startStrike = newStartStrike;
return;
}

if (newStartStrike < endStrike) {
System.out.println("setting startStrike"); //TODO remove
startStrike = newStartStrike;
}
}

public void setEndStrike(Double newEndStrike) {
if (newEndStrike == null) {
System.out.println("newEndStrike is null, not setting"); //TODO remove
return;
}

if (startStrike == null) {
System.out.println("startStrike is null, setting endStrike"); //TODO remove
endStrike = newEndStrike;
return;
}

if (Double.isNaN(startStrike)) {
System.out.println("startStrike is NaN, setting endStrike"); //TODO remove
endStrike = newEndStrike;
return;
}

if (newEndStrike > startStrike) {
System.out.println("setting endStrike"); //TODO remove
endStrike = newEndStrike;
}
}

public Double getStartVol() {
return startVol;
}

public Double getEndVol() {
return endVol;
}

public void setStartVol(Double newStartVol) {
if (newStartVol == null) return;
if (Double.isNaN(newStartVol)) return;

if (newStartVol >= 0)
startVol = newStartVol;
}

public void setEndVol(Double newEndVol) {
if (newEndVol == null) return;
if (Double.isNaN(newEndVol)) return;

if (newEndVol >= 0)
endVol = newEndVol;
}

public boolean strikeInSection(Double strike) {
return strike >= startStrike && strike < endStrike;
}
}
}

In our implementation here, our code is represented by an array of ElevenPointStraightLineCurve.SplineSection objects, called sections. There is a default no-argument constructor that is to be used by the Trade Sheets and Theoretical Center and another constructor with arguments to be used by your freeway jobs, if desired.

In each constructor, maps of parameters to values are populated via setupPointValues(). The map pointStrikes represents the map of all point strike parameters ("callFirstStrike", "callSecondStrike", ..., "callFifthStrike", "callWing", etc) to the corresponding strikes. The map pointVols represents the map of all point vol params ("callFirstVol", "callSecondVol", ..., "callFifthVol", "atmVol", etc) to corresponding vols. These maps represent the internal state of the parameters. The array of SplineSections are used based on these to get the actual vol at a particular strike.

The array of SplineSection objects are setup for the no-arg constructor via the setupSplineSections() method. Otherwise they are setup in the alternative constructor directly.

As we can see the volatilityAt() method takes the strike passed to the method, iterates over each SplineSection and checks if the strike is in that section via SplineSection.strikeInSection(double) method. If the spline section is the first section (left-most), which goes from negative infinity to the put wing, then interpolation needs to with the end strike (the put wing) being the starting point in the range. Otherwise, interpolation is carried out normally.

The slopeAt() method similarly iterates over each section to find the correct SplineSection and the slope is calculated based on the end points. The curvatureAt() method returns 0 always, as straight lines do not produce second derivatives.

Our prepare() method does nothing, so it does not support a Volatility Slide.

The getParameters() method returns an array containing all the elements of the strikeParams and volParams arrays, which simply contain the constant parameter variables listed near the start of the class definition. The ordering of elements in the returned array is important, as it matches the order of the elements listed in the model control. The resulting model control can be seen below:

Title

The getParameter() method iterates over each SplineSection and gets the start strike or start vol that was stored to that section upon creation, depending on whether the strike or vol parameter was requested. The setParameter() method contains additional validation checks, so that you cannot set the strike of a single point (say, putFirstPoint), to a strike value before the strike of the previous point (the putSecondPoint).

The getCopy() method returns a new ElevenPointStraightLineCurve that is created via the argument-constructor.

If you set each point to the following values (making sure to set each value in reverse from top to bottom on the Model control so as to not violate the aforementioned input validation restrictions), you will see the following set curve:

Title

Note that in this implementation, all strikes are set to 0 initially (initialized by TradeSheets via default constructor) and our setParameter() will find the corresponding SplineSection and attempt to set the start strike via SplineSection.setStartStrike() and then the end strike of the previous section via SplineSection.setEndStrike(). SplineSection.setStartStrike() will only set the strike if it is less than the endStrike of the section, which is initially 0. This is why we need to set the strikes in reverse order--so that the end strike of each section is set before we set the start strike of that section.

At this point, our custom slide is behaving just like any other in the model control. You can set the parameter values and revert them if necessary; to publish them to all clients and the server, click the publish button. Note that you cannot fit via the imply or imply all functionality.

Uploading Your Custom Curve

Since your implementation of VolatilityCurve.Custom object is not an extension of AbstractJob, it is cannot be in the default package, which is why we have put it in a package called curves in our common module. Moreover, we include a META-INF/services/ directory in our common module. In this directory are the following two files

  • com.optionscity.freeway.api.VolatilityCurve$Custom: contains fully-qualified class names of our VolatilityCurve.Custom implementations
  • com.optionscity.freeway.api.VolatilityCurve$VolatilitySlide$Custom: contains fully-qualified class names of our VolatilityCurve.VolatilitySlide.Custom implementations

We must package our compiled custom curve classes and the META-INF directory and all the aforementioned contents in a jar named common.curves.jar and upload this via OnRamp. The explicit name common.curves.jar indicates the the classloader that this jar contains implementations of VolatilityCurve.Custom and VolatilityCurve.VolatilitySlide.Custom. The provider files tell the classloader which exact classes to look for. The classloader then creates the Theoretical Center wrapper CustomVolatilityCurve and slide objects and attaches the newly-loaded classes accordingly.

After compilation, our jar components look like:

ekutan@THUNDERDOME:~/code/POC/newcustomcurvepoc/artifacts>jar -tf common/common.curves.jar 
META-INF/
META-INF/MANIFEST.MF
Config.class
curve/
curve/ElevenPointSplineCurve$SplineSection.class
curve/ElevenPointSplineCurve.class
curve/ElevenPointStraightLineCurve$SplineSection.class
curve/ElevenPointStraightLineCurve.class
curve/MyFlatCurve.class
slide/
slide/FlatWingSlide.class
slide/MyConstantSlide.class
META-INF/services/
META-INF/services/com.optionscity.freeway.api.VolatilityCurve$Custom
META-INF/services/com.optionscity.freeway.api.VolatilityCurve$VolatilitySlide$Custom

Slide

A Volatility Slide (the term is interchangeably used with Volatility Path) is a curve that specifies how the VolatilityCurve object moves as the underlying moves. Specifically, the VolatilityCurve object by default adjusts its height so that the atm-strike vol is equal to the volatility of the volatility slide at the current underlying. This effectively makes the Volatility Curve a function of strike and underlying price (as opposed to just strike, when not using a slide). Since this changes the volatility function, it will consequently change the model greeks, thus creating what are known as adjusted greeks in the OptionsCity Metro system.

A VolatilitySlide is set via the Theoretical Model Wizard Pivot Type selection. The Float by Strike and Float by Log-Strike will use the standard out-of-the-box Volatility Slide. Custom slides can be enabled via the Custom Slide API and will be discussed in a subsection.

The standard volatility slide has 6 parameters:

  • left point strike
  • left point vol
  • center point strike
  • center point vol
  • right point strike
  • right point vol

These parameters define a parabola that fits all three points.

The VolatilityCurve.VolatilitySlide Object

The VolatilityCurve.VolatilitySlide object contained the aforementioned points/vols as parameters prior to 6.1.6. For Metro/Freeway versions 6.1.6 the there are two subclasses of VolatilityCurve.VolatilitySlide

  • VolatilityCurve.VolatilitySlide.Custom
  • VolatilityCurve.VolatilitySlide.PathPoints

The Custom skew represents a custom skew created by the user. We will touch on this in a later subsection. The traditional volatility path is representing by the VolatilityCurve.VolatilitySlide.PathPoints type. It has the following members:

  • leftStrike
  • leftVolatility
  • middleStrike
  • middleVolatility
  • rightStrike
  • rightVolatility

This object is mutable, of course, which is how you would change or set the VolatilitySlide for an instrument month.

Getting VolatilitySlide

Getting a VolatilitySlide object is straight-forward. You must use the theo service method:

theos().getVolatilitySlide(String)

The String passed in, must be an instrument month String whose product supports the floating skew pivot type. The following job demonstrates usage of this method:

import com.optionscity.freeway.api.AbstractJob;
import com.optionscity.freeway.api.IContainer;
import com.optionscity.freeway.api.IJobSetup;
import com.optionscity.freeway.api.VolatilityCurve;
import com.optionscity.freeway.api.services.ITheoService;

/**
* Created by ekutan on 12/6/17.
*/
public class GetVolatilitySlideDemoJob extends AbstractJob {

private final String INSTRUMENT_MONTH_VAR_NAME = "Instrument Month";


@Override
public void install(IJobSetup setup) {
setup.setDefaultDescription("Demo job that gets slide for a desired instrument month");
setup.addVariable(INSTRUMENT_MONTH_VAR_NAME, "Instrument Month to fetch Slide for", "String", "");
}

public void begin(IContainer container) {
super.begin(container);

String instrumentMonth = getStringVar(INSTRUMENT_MONTH_VAR_NAME);
String[] instrumentMonthParts = instrumentMonth.split("\\.");
String product = instrumentMonthParts[0];

ITheoService.SkewPivot skewPivot = theos().getSkewPivot(product);
if (ITheoService.SkewPivot.FLOATING.equals(skewPivot)) {
logSlide(instrumentMonth);
} else {
log("Cannot get slide for instrument month " + instrumentMonth + ". Skew pivot type is not floating!");
}

container.stopJob("Finished!");
}

private void logSlide(String instrumentMonth) {
VolatilityCurve.VolatilitySlide slide = theos().getVolatilitySlide(instrumentMonth);

if (slide instanceof VolatilityCurve.VolatilitySlide.Custom) {
log("Using a custom slide for instrument month " + instrumentMonth);
} else {
VolatilityCurve.VolatilitySlide.PathPoints traditionalSlide = (VolatilityCurve.VolatilitySlide.PathPoints) slide;
StringBuilder sb = new StringBuilder("Found traditional slide with parameters").append("\n ");
sb.append("leftStrike: ").append(traditionalSlide.leftStrike).append("\n ")
.append("leftVolatility: ").append(traditionalSlide.leftVolatility).append("\n ")
.append("middleStrike: ").append(traditionalSlide.middleStrike).append("\n ")
.append("middleVolatility: ").append(traditionalSlide.middleVolatility).append("\n ")
.append("rightStrike: ").append(traditionalSlide.rightStrike).append("\n ")
.append("rightVolatility: ").append(traditionalSlide.rightVolatility).append("\n ")
.append("for instrument month ").append(instrumentMonth);

log(sb.toString());
}
}
}

This logs out the following:

localhost    2017-12-06 18:05:29,348.094    GetVolatilitySlideDemoJob.1    job started
localhost 2017-12-06 18:05:29,348.100 GetVolatilitySlideDemoJob.1 is running is test order mode !
localhost 2017-12-06 18:05:29,349.678 GetVolatilitySlideDemoJob.1 Found traditional slide with parameters
leftStrike: 1880.25
leftVolatility: 0.18
middleStrike: 2500.0
middleVolatility: 0.15
rightStrike: 3000.0
rightVolatility: 0.2
for instrument month ES.O.20171215
localhost 2017-12-06 18:05:29,349.871 GetVolatilitySlideDemoJob.1 job finished : Finished!

For the volpath depicted below:

Title

Note how the pivot type is checked before fetched the vol path. While not a requirement, this will prevent an exception from being thrown if there is no volpath for the corresponding instrument month. An important detail here is that the pivot type is a per-product (symbol) setting and thus is requested by passing in the symbol String to the theo service.

Setting VolatilitySlide

You can set the volatility slide via the freeway theo service method:

theos().setVolatilitySlide(String, VolatilityCurve.VolatilitySlide)

This will set the volatility slide of the specified instrument month (the String passed in) to the specified VolatilitySlide. As with the case with the volatility skew, you publish immediately after to flush the theoretical transaction queue and invalidate/rebuild the theo cache. The following demo job demonstrates the use of this method to update a parameter in the volatility slide:

import com.optionscity.freeway.api.AbstractJob;
import com.optionscity.freeway.api.IContainer;
import com.optionscity.freeway.api.IJobSetup;
import com.optionscity.freeway.api.VolatilityCurve;
import com.optionscity.freeway.api.services.ITheoService;

public class GetSetVolatilitySlideDemo extends AbstractJob {

private final String INSTRUMENT_MONTH_VAR_NAME = "Instrument Month";
private final String LEFT_STRIKE_VAR_NAME = "Left Strike";
private final String LEFT_VOL_VAR_NAME = "Left Vol";
private final String MIDDLE_STRIKE_VAR_NAME = "Middle Strike";
private final String MIDDLE_VOL_VAR_NAME = "Middle Vol";
private final String RIGHT_STRIKE_VAR_NAME = "Right Strike";
private final String RIGHT_VOL_VAR_NAME = "Right Vol";

@Override
public void install(IJobSetup setup) {
setup.setDefaultDescription("Demo job that gets slide for a desired instrument month");
setup.addVariable(INSTRUMENT_MONTH_VAR_NAME, "Instrument Month to fetch Slide for", "String", "");
setup.addVariable(LEFT_STRIKE_VAR_NAME, LEFT_STRIKE_VAR_NAME, "double", "");
setup.addVariable(LEFT_VOL_VAR_NAME, LEFT_VOL_VAR_NAME, "double", "");
setup.addVariable(MIDDLE_STRIKE_VAR_NAME, MIDDLE_STRIKE_VAR_NAME, "double", "");
setup.addVariable(MIDDLE_VOL_VAR_NAME, MIDDLE_VOL_VAR_NAME, "double", "");
setup.addVariable(RIGHT_STRIKE_VAR_NAME, RIGHT_STRIKE_VAR_NAME, "double", "");
setup.addVariable(RIGHT_VOL_VAR_NAME, RIGHT_VOL_VAR_NAME, "double", "");
}

public void begin(IContainer container) {
super.begin(container);

String instrumentMonth = getStringVar(INSTRUMENT_MONTH_VAR_NAME);
String[] instrumentMonthParts = instrumentMonth.split("\\.");
String product = instrumentMonthParts[0];

Double newLeftStrike = getDoubleVar(LEFT_STRIKE_VAR_NAME);
Double newLeftVol = getDoubleVar(LEFT_VOL_VAR_NAME);
Double newMiddleStrike = getDoubleVar(MIDDLE_STRIKE_VAR_NAME);
Double newMiddlevol = getDoubleVar(MIDDLE_STRIKE_VAR_NAME);
Double newRightStrike = getDoubleVar(RIGHT_STRIKE_VAR_NAME);
Double newRightVol = getDoubleVar(RIGHT_VOL_VAR_NAME);

ITheoService.SkewPivot skewPivot = theos().getSkewPivot(product);
if (ITheoService.SkewPivot.FLOATING.equals(skewPivot)) {
logSlide(instrumentMonth);
setSlide(instrumentMonth, newLeftStrike, newLeftVol, newMiddleStrike, newMiddlevol, newRightStrike, newRightVol);
logSlide(instrumentMonth);
} else {
log("Cannot get slide for instrument month " + instrumentMonth + ". Skew pivot type is not floating!");
}

container.stopJob("Finished!");
}

private void logSlide(String instrumentMonth) {
VolatilityCurve.VolatilitySlide slide = theos().getVolatilitySlide(instrumentMonth);

if (slide instanceof VolatilityCurve.VolatilitySlide.Custom) {
log("Using a custom slide for instrument month " + instrumentMonth);
} else {
VolatilityCurve.VolatilitySlide.PathPoints traditionalSlide = (VolatilityCurve.VolatilitySlide.PathPoints) slide;
StringBuilder sb = new StringBuilder("Found traditional slide with parameters").append("\n ");
sb.append("leftStrike: ").append(traditionalSlide.leftStrike).append("\n ")
.append("leftVolatility: ").append(traditionalSlide.leftVolatility).append("\n ")
.append("middleStrike: ").append(traditionalSlide.middleStrike).append("\n ")
.append("middleVolatility: ").append(traditionalSlide.middleVolatility).append("\n ")
.append("rightStrike: ").append(traditionalSlide.rightStrike).append("\n ")
.append("rightVolatility: ").append(traditionalSlide.rightVolatility).append("\n ")
.append("for instrument month ").append(instrumentMonth);

log(sb.toString());
}
}

private void setSlide(String instrumentMonth, double leftStrike, double leftVol, double middleStrike, double middleVol, double rightStrike, double rightVol) {
VolatilityCurve.VolatilitySlide.PathPoints slide = (VolatilityCurve.VolatilitySlide.PathPoints) theos().getVolatilitySlide(instrumentMonth);

boolean paramChanged = !(leftStrike == slide.leftStrike && leftVol == slide.leftVolatility && middleStrike == slide.middleStrike
&& middleVol == slide.middleVolatility && rightStrike == slide.rightStrike && rightVol == slide.rightVolatility);
if (!paramChanged)
return;

slide.leftStrike = leftStrike;
slide.leftVolatility = leftVol;
slide.middleStrike = middleStrike;
slide.middleVolatility = middleVol;
slide.rightStrike = rightStrike;
slide.rightVolatility = rightVol;

theos().setVolatilitySlide(instrumentMonth, slide);
theos().publish();
}
}