Skip to content

Instantly share code, notes, and snippets.

@chinchalinchin
Last active January 23, 2023 16:49
Show Gist options
  • Save chinchalinchin/739680a90d458d8d339bdc6465ac4193 to your computer and use it in GitHub Desktop.
Save chinchalinchin/739680a90d458d8d339bdc6465ac4193 to your computer and use it in GitHub Desktop.
Calculate breakeven point of an arbitrary portfolio of options
// 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