Custom Option Spreads
If you want to create non-standard / custom option spreads, you can define your own custom option spreads by writing your strategy in Scala using the Advanced Scala Editor.
Here are the steps on how to construct your own custom option spreads in your trading strategies: (Ratio Straddle Custom Spread Example, Ratio Collar Custom Spread Example)
- Create a New Strategy using the Advanced Scala Editor.
- Create a CustomSpread object
// create a CustomSpread object
val customSpread = stock.strategies.CustomSpread();
- Define the individual legs of your CustomSpread using the addLeg method
- Specify the legID, which can be used to reference the leg later on
- Specify the optionType (CALL, PUT, STOCK)
- Specify the number of contracts
- Specify whether you want to buy or sell the leg.
// Create the individual legs of the custom spread
// long call (x15)
customSpread.addLeg(legID="MyCall",optionType="CALL",numContracts=15, isLong=true);
// long put (x10)
customSpread.addLeg(legID="MyPut",optionType="PUT",numContracts=10,isLong = true);
- Define the filter criteria for each leg.
- Select the leg using the selectLegBy method, along with the legID defined using addLeg.
// Define the filter criteria for each individual leg separately.
// To select a leg, use the same legID used during the addLeg() method
customSpread.selectLegBy(legID="MyCall").daysToExpiration(20, 30);
customSpread.selectLegBy(legID="MyPut").daysToExpiration(20, 30);
- Define the filter criteria across multiple legs, such as the relationship between one leg versus another using the addCustomFilter method
// Define the filter criteria across multiple legs using addCustomFilter() method
customSpread.addCustomFilter(filterFunction=sameExpirationDateFilter);
- This filter function will be invoked with potential candidate positions (OrderT). You can check the candidate positions to determine whether it meets your criteria.
- Define the filter function, which accepts an OrderT object as an argument and returns TRUE/FALSE.
- Function should return TRUE if the attributes of OrderT meet your desired criteria. Otherwise the function should return FALSE
/* Example of a filter that compares the days to expiration of the legs of the custom spread, and checks that the DTE are equal */
def sameExpirationDateFilter (order:OrderT):Boolean = {
val callExpir=order.getCustomSpreadLeg(legID="MyCall").getDaysToExpiration;
val putExpir = order.getCustomSpreadLeg(legID="MyPut").getDaysToExpiration;
// check if put expiration and call expiration are the same
return (callExpir == putExpir);
}
- Limitations: Methods that compare the relationship amongst different legs will NOT work when using CustomSpread. For instance, the distanceBetweenXXX methods will NOT work when using CustomSpreads. (i.e. distanceBetweenLongAndShortStrike, etc)
- Instead, you can add custom filter functions that compare the relationship between the different legs, such as the distance between the different leg strikes.
customSpread.addCustomFilter(filterFunction=strikeDistanceFilter);
/**
* Example of a filter that compares the distance between the calls / puts
*/
def strikeDistanceFilter (order:OrderT):Boolean = {
val callStrike =order.getCustomSpreadLeg(legID="MyCall").getStrikePrice
val putStrike = order.getCustomSpreadLeg(legID="MyPut").getStrikePrice
val diff = Math.abs (putStrike.d - callStrike.d);
// check if distance between call and put strike is between 50 - 100
return (diff <= 100 && diff >= 50);
}
- Here is an example of creating a custom Ratio Straddle spread (10×15)
/**
* Example of creating a Ratioed Straddle using a CustomSpread
*/
class RatioStraddleCustomSpread (config:BacktestConfig) extends StrategyRunnerT (config) {
var symbol = "SPY";
var stock= this.getSecurity (symbol);
def handleData () = {
// create a CustomSpread object
val customSpread = stock.strategies.CustomSpread();
// Create the individual legs of the custom spread
// long call (x15)
customSpread.addLeg(legID="MyCall",optionType="CALL",numContracts=15, isLong=true);
// long put (x10)
customSpread.addLeg(legID="MyPut",optionType="PUT",numContracts=10, isLong =true);
// Define the filter criteria for each individual leg separately.
// To select a leg, use the same legID used during the addLeg() method
customSpread.selectLegBy(legID="MyCall").daysToExpiration(20, 30);
customSpread.selectLegBy(legID="MyPut").daysToExpiration(20, 30);
customSpread.selectLegBy(legID="MyCall").atTheMoney();
customSpread.selectLegBy(legID="MyPut").atTheMoney();
//Define the filter criteria across multiple legs using addCustomFilter() method
customSpread.addCustomFilter(filterFunction=sameExpirationDateFilter);
customSpread.setMaxOpenPositions(n=2);
customSpread.buy("RatioStraddle", "My Ratio Straddle (15x10)").sizeBy.quantityFunction(() => {1})
}
/**
* Example of a filter that compares the days to expiration of the legs of the custom spread, and checks that the DTE are equal
*/
def sameExpirationDateFilter (order:OrderT):Boolean = {
val callExpir=order.getCustomSpreadLeg(legID="MyCall").getDaysToExpiration;
val putExpir = order.getCustomSpreadLeg(legID="MyPut").getDaysToExpiration;
// check if put expiration and call expiration are the same
return (callExpir == putExpir);
}
def initialize () = { }
}