Last active
January 23, 2023 16:49
-
-
Save chinchalinchin/739680a90d458d8d339bdc6465ac4193 to your computer and use it in GitHub Desktop.
Calculate breakeven point of an arbitrary portfolio of options
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// single option strategy test | |
const legsTest = [ | |
{ | |
StrikePrice: 97, | |
Type: "put", | |
TransType: "buy", | |
Quantity: 100, | |
Ask: 1.55, | |
Bid: 1.53 | |
} | |
] | |
// strangle option strategy test | |
const legsTest2 = [ | |
{ | |
StrikePrice:101, | |
Type:"call", | |
TransType:"buy", | |
Quantity:100, | |
Ask:0.23, | |
Bid:0.22 | |
}, | |
{ | |
StrikePrice:93, | |
Type:"put", | |
TransType:"buy", | |
Quantity:100, | |
Ask:0.5, | |
Bid:0.46 | |
} | |
] | |
function sortLegs(legs) { | |
return legs.sort((firstLeg, secondLeg) => { | |
return firstLeg.StrikePrice - secondLeg.StrikePrice | |
}) | |
} | |
/** | |
* Calculates the profit of a portfolio of options at a given stock price, _x_. | |
* @param {*} x : represents the stock price | |
* @param {*} legs : the options that compose the portfolio | |
* @note the second argument to reduce sets the initial value of profit to zero. | |
*/ | |
function profitFunction(x, legs) { | |
return legs.reduce((profit, leg) => { | |
const creditOrDebit = leg.TransType === "buy" | |
? 1 | |
: -1 | |
const price = leg.TransType === "buy" | |
? leg.Ask | |
: leg.Bid | |
const payoffDirection = leg.Type === "call" | |
? 1 | |
: -1 | |
profit += creditOrDebit * leg.Quantity * ( | |
Math.max(payoffDirection * (x - leg.StrikePrice), 0) - price | |
) | |
return profit | |
}, 0); | |
} | |
/** | |
* Calculates the delta (slope of the profit) of a portfolio of options at a given stock price, _x_. | |
* @param {*} x : represents the stock price | |
* @param {*} legs : the options that compose the portfolio | |
* @note the second argument to reduce sets the initial value of profit to zero. | |
* @note the sign of the delta doesn't depend on the stock price; it depends on the option type. | |
* the stock price determines the magnitude of the delta at expiration, i.e. either 0 or |1|. | |
* the option type determines the sign (direction) of delta at expiratoin, i.e. 1 or -1. | |
*/ | |
function deltaFunction(x, legs) { | |
return legs.reduce((delta, leg) =>{ | |
const creditOrDebit = leg.TransType === "buy" | |
? 1 | |
: -1 | |
const optionDelta = leg.Type === "put" | |
? -1 | |
: 1 | |
const strikeCondition = leg.Type === "put" | |
? (leg.StrikePrice - x > 0) | |
: (x - leg.StrikePrice > 0) | |
if (strikeCondition) delta += creditOrDebit * leg.Quantity * optionDelta | |
return delta | |
}, 0); | |
} | |
/** | |
* Find the breakeven points using a variant of Newton's method. | |
* See: https://en.wikipedia.org/wiki/Newton%27s_method#Code | |
* @param {*} legs : | |
* @param {*} tolerance : amount of accuracy in decimals sought in the solution. | |
*/ | |
function findBreakevenPoint( | |
legs, | |
initialGuess = 0.01, | |
tolerance = 0.001, | |
maxIterations = 1000, | |
) { | |
console.log(`finding solution with initial guess: ${initialGuess}`) | |
let guess = initialGuess | |
for(let i=0; i < maxIterations; i++){ | |
const profit = profitFunction(guess, legs) | |
const delta = deltaFunction(guess, legs) | |
console.log(`current guess: ${guess}`) | |
console.log(`profit at current guess: ${profit}`) | |
console.log(`delta at current guess: ${delta}`) | |
// if delta hits a zero, algorithm will iterate over same point, | |
// so return at that point | |
if (delta === 0) return guess | |
const newGuess = guess - profit / delta | |
if (Math.abs(newGuess - guess) < tolerance) return newGuess | |
guess = newGuess | |
} | |
return undefined | |
} | |
/** | |
* Find all breakeven points. Note: since the first breakeven point is found by starting the | |
* search at 0.01 and iterating upward in stock price until profit equals zero, we know if there | |
* is another breakeven point to be found, it must occur after the first breakeven point for this reason. | |
* | |
* Furthermore, another breakeven point only occurs if the portfolio slope (its delta) switches direction, | |
* i.e. goes from 1 to -1 or visa versa. | |
* | |
* In other words, we have two conditions for continuing our search for more breakeven points once the | |
* first is found. | |
* | |
* 1. It must occur on the interval (breakeven, infinity) | |
* 2. It can only occur if the options above the current breakeven differ in delta from the | |
* delta the breakeven point. | |
* | |
* The first condition shortens the range of the next pass through the breakeven calculation; | |
* the second condition allows us to break early, in the event none of the subsequent strikes alter the **sign** | |
* of the portfolio delta. | |
* @param {*} legs | |
* @returns | |
*/ | |
function findAllBreakevenPoints( | |
legs, | |
initialGuess = 0.01, | |
tolerance = 0.001, | |
) { | |
legs = sortLegs(legs) | |
let searching = true | |
let breakevens = [] | |
let guess = initialGuess | |
while(searching) { | |
const breakeven = findBreakevenPoint(legs, guess) | |
if(!breakeven) return [] | |
// if algorithm result is actually solution, i.e. if algorithm | |
// didn't hit a point where delta = 0 | |
if (profitFunction(breakeven, legs) < tolerance) breakevens.push(breakeven) | |
currentDelta = deltaFunction(breakeven || guess, legs) | |
legsAboveCUrrent = legs.filter(leg=> leg.StrikePrice > breakeven); | |
searching = false | |
for(let i = 0; i < legsAboveCurrent.length; i++) { | |
// offset strike by a cent so guess isn't evaluated at discontinuity next iteration | |
guess = legsAboveCurrent[i].StrikePrice+0.01; | |
nextLegDelta = deltaFunction(guess, legs); | |
// the product of deltas needs to be negative for the delta to switch | |
// signs on a given leg, i.e. (+) * (-) = (-), while (+) * (+) = (+) | |
// and (-) * (-) = (+) | |
searching = nextLegDelta * currentDelta < 0; | |
if (searching) break; | |
} | |
} | |
return breakevens; | |
} | |
console.log('testing strategy:') | |
console.log(legsTest) | |
console.log(`breakevens: ${findAllBreakevenPoints(legsTest)}`) | |
console.log('testing strategy:') | |
console.log(legsTest2) | |
console.log(`breakevens: ${findAllBreakevenPoints(legsTest2)}`) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment