def testStrangleUpdateValuesNoMatchingOption(self): """Tests that the profit loss calculation is unchanged if no option is available to update.""" putOpt = put.Put(underlyingTicker='SPX', underlyingPrice=decimal.Decimal(2786.24), strikePrice=decimal.Decimal(2690), dateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/20/2021', "%m/%d/%Y"), bidPrice=decimal.Decimal(7.45), askPrice=decimal.Decimal(7.50), tradePrice=decimal.Decimal(7.475)) callOpt = call.Call(underlyingTicker='SPX', underlyingPrice=decimal.Decimal(2786.24), strikePrice=decimal.Decimal(2855), dateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/20/2021', "%m/%d/%Y"), bidPrice=decimal.Decimal(5.20), askPrice=decimal.Decimal(5.40), tradePrice=decimal.Decimal(5.30)) strangleObj = strangle.Strangle( orderQuantity=1, callOpt=callOpt, putOpt=putOpt, buyOrSell=optionPrimitive.TransactionType.BUY) initialProfitLoss = strangleObj.calcProfitLoss() tickData = [] # Changed the PUT strike price from 2690 to 2790 to prevent a match. putOpt = put.Put(underlyingTicker='SPX', underlyingPrice=decimal.Decimal(2790.0), strikePrice=decimal.Decimal(2790), dateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/20/2021', "%m/%d/%Y"), bidPrice=decimal.Decimal(7.25), askPrice=decimal.Decimal(7.350)) tickData.append(putOpt) callOpt = call.Call(underlyingTicker='SPX', underlyingPrice=decimal.Decimal(2790.0), strikePrice=decimal.Decimal(2855), dateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/20/2021', "%m/%d/%Y"), bidPrice=decimal.Decimal(5.60), askPrice=decimal.Decimal(5.80)) tickData.append(callOpt) strangleObj.updateValues(tickData) # The profit / loss should be the same since the option wasn't updated. self.assertAlmostEqual(strangleObj.calcProfitLoss(), initialProfitLoss)
def testStrangleCalcProfitLossWithDataUpdateBuyingStrangle(self): """Tests that the profit / loss is calculated correctly when buying a strangle.""" putOpt = put.Put(underlyingTicker='SPX', underlyingPrice=decimal.Decimal(2786.24), strikePrice=decimal.Decimal(2690), dateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/20/2021', "%m/%d/%Y"), bidPrice=decimal.Decimal(7.45), askPrice=decimal.Decimal(7.50), tradePrice=decimal.Decimal(7.475)) callOpt = call.Call(underlyingTicker='SPX', underlyingPrice=decimal.Decimal(2786.24), strikePrice=decimal.Decimal(2855), dateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/20/2021', "%m/%d/%Y"), bidPrice=decimal.Decimal(5.20), askPrice=decimal.Decimal(5.40), tradePrice=decimal.Decimal(5.30)) strangleObj = strangle.Strangle( orderQuantity=1, callOpt=callOpt, putOpt=putOpt, buyOrSell=optionPrimitive.TransactionType.BUY) # The parameters below are used to update the prices of the initial strangle above. tickData = [] putOpt = put.Put(underlyingTicker='SPX', underlyingPrice=decimal.Decimal(2790.0), strikePrice=decimal.Decimal(2690), dateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/20/2021', "%m/%d/%Y"), bidPrice=decimal.Decimal(7.25), askPrice=decimal.Decimal(7.350)) tickData.append(putOpt) callOpt = call.Call(underlyingTicker='SPX', underlyingPrice=decimal.Decimal(2790.0), strikePrice=decimal.Decimal(2855), dateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/20/2021', "%m/%d/%Y"), bidPrice=decimal.Decimal(5.60), askPrice=decimal.Decimal(5.80)) tickData.append(callOpt) strangleObj.updateValues(tickData) self.assertAlmostEqual(strangleObj.calcProfitLoss(), decimal.Decimal(22.5))
def testGetDeltaNoneValue(self): """Tests that a value of None is returned if one of the delta is None.""" putOpt = put.Put(underlyingTicker='SPX', underlyingPrice=2786.24, strikePrice=2690, delta=None, dateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/20/2021', "%m/%d/%Y"), bidPrice=7.45, askPrice=7.50, tradePrice=7.475) callOpt = call.Call(underlyingTicker='SPX', underlyingPrice=2786.24, strikePrice=2855, delta=-0.16, dateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/20/2021', "%m/%d/%Y"), bidPrice=5.20, askPrice=5.40, tradePrice=5.30) strangleObj = strangle.Strangle( orderQuantity=1, callOpt=callOpt, putOpt=putOpt, buyOrSell=optionPrimitive.TransactionType.SELL) self.assertIsNone(strangleObj.getDelta())
def testStrangleBuyingPower15PercentRule(self): # Tests the buying power calculation for the 15% rule. # TODO(msantoro): Get live values from Tastyworks to use here. putOpt = put.Put(underlyingTicker='SPX', underlyingPrice=decimal.Decimal(2786.24), strikePrice=decimal.Decimal(2500), delta=0.01, dateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/20/2021', "%m/%d/%Y"), bidPrice=decimal.Decimal(1.45), askPrice=decimal.Decimal(1.50), tradePrice=decimal.Decimal(1.475)) callOpt = call.Call(underlyingTicker='SPX', underlyingPrice=decimal.Decimal(2786.24), strikePrice=decimal.Decimal(3200), delta=-0.01, dateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/20/2021', "%m/%d/%Y"), bidPrice=decimal.Decimal(1.20), askPrice=decimal.Decimal(1.40), tradePrice=decimal.Decimal(1.30)) strangleObj = strangle.Strangle( orderQuantity=1, callOpt=callOpt, putOpt=putOpt, buyOrSell=optionPrimitive.TransactionType.SELL) buyingPower = strangleObj.getBuyingPower() self.assertAlmostEqual(buyingPower, decimal.Decimal(48130.0))
def testStrangleUpdateValues(self): """Test that the option values are updated correctly by starting with a strangle and then receiving new option prices for the puts and calls. """ # Create a list with two options matching the strangleObj except with different option prices. tickData = [] putOpt = put.Put('SPX', 2690, 0.15, 34, underlyingPrice=2786.24, bidPrice=7.25, askPrice=7.25, optionSymbol="01") tickData.append(putOpt) callOpt = call.Call('SPX', 2855, -0.15, 34, underlyingPrice=2786.24, bidPrice=5.00, askPrice=5.00, optionSymbol="02") tickData.append(callOpt) self.strangleObj.updateValues(tickData) # Check that the bidPrice of the put and call options inside strangleObj have been updated. __putOpt = self.strangleObj.getPutOption() __callOpt = self.strangleObj.getCallOption() self.assertAlmostEqual(__putOpt.getBidPrice(), 7.25) self.assertAlmostEqual(__callOpt.getBidPrice(), 5.00)
def testStrangleManagePositionProfitTargetPercent(self): """Test that strangle is set to be deleted from the portfolio if the option prices are such that the desired profit target threshold has been reached. """ # Create put and call options. tickData = [] putOpt = put.Put('SPX', 2690, 0.15, 34, underlyingPrice=2786.24, bidPrice=3.00, askPrice=3.00, optionSymbol="01") tickData.append(putOpt) callOpt = call.Call('SPX', 2855, -0.15, 34, underlyingPrice=2786.24, bidPrice=2.00, askPrice=2.00, optionSymbol="02") tickData.append(callOpt) # Update strangle with new values. self.strangleObj.updateValues(tickData) # Check if managePosition() returns true since the combined put and call prices reflect more than a 50% profit. self.assertTrue(self.strangleObj.managePosition())
def testUpdatePortfolioRiskManagementHoldToExpiration(self): """Tests that the position is removed from the portfolio when expiration occurs.""" # Create a new position in addition to the default self.strangleObj position. startingCapital = decimal.Decimal(1000000) maxCapitalToUse = 0.5 maxCapitalToUsePerTrade = 0.25 portfolioObj = portfolio.Portfolio(startingCapital, maxCapitalToUse, maxCapitalToUsePerTrade) # Add first position to the portfolio event = signalEvent.SignalEvent() event.createEvent([self.strangleObj, self.riskManagement]) portfolioObj.onSignal(event) putOpt = put.Put(underlyingTicker='SPX', underlyingPrice=decimal.Decimal(2800.00), strikePrice=decimal.Decimal(2700), delta=-0.16, gamma=0.01, theta=0.02, vega=0.03, dateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), bidPrice=decimal.Decimal(8.00), askPrice=decimal.Decimal(8.50), tradePrice=decimal.Decimal(8.25)) callOpt = call.Call(underlyingTicker='SPX', underlyingPrice=decimal.Decimal(2800.00), strikePrice=decimal.Decimal(3000), delta=0.16, gamma=0.01, theta=0.02, vega=0.03, dateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), bidPrice=decimal.Decimal(6.00), askPrice=decimal.Decimal(6.50), tradePrice=decimal.Decimal(6.25)) strangleObj = strangle.Strangle( orderQuantity=1, callOpt=callOpt, putOpt=putOpt, buyOrSell=optionPrimitive.TransactionType.SELL) # Add second position to the portfolio. event = signalEvent.SignalEvent() event.createEvent([strangleObj, self.riskManagement]) portfolioObj.onSignal(event) # Update the portfolio, which should remove the second event. We do not change the prices of the putOpt or callOpt. testOptionChain = [callOpt, putOpt] event = tickEvent.TickEvent() event.createEvent(testOptionChain) portfolioObj.updatePortfolio(event) # Only one position should be left in the portfolio after removing the expired position. self.assertEqual(len(portfolioObj.activePositions), 1)
def testUpdatePortfolio(self): """Tests the ability to update option values for a position in the portfolio.""" # Create strangle event. event = signalEvent.SignalEvent() event.createEvent([self.strangleObj, self.riskManagement]) # Create portfolio onSignal event, which adds the position to the portfolio. startingCapital = decimal.Decimal(1000000) maxCapitalToUse = 0.5 maxCapitalToUsePerTrade = 0.5 portfolioObj = portfolio.Portfolio(startingCapital, maxCapitalToUse, maxCapitalToUsePerTrade) portfolioObj.onSignal(event) # Next, create a strangle with the next days prices and update the portfolio values. putOpt = put.Put(underlyingTicker='SPX', underlyingPrice=decimal.Decimal(2786.24), strikePrice=decimal.Decimal(2690), delta=-0.16, gamma=0.01, theta=0.02, vega=0.03, dateTime=datetime.datetime.strptime( '01/02/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/20/2021', "%m/%d/%Y"), bidPrice=decimal.Decimal(6.45), askPrice=decimal.Decimal(6.50), tradePrice=decimal.Decimal(6.475)) callOpt = call.Call(underlyingTicker='SPX', underlyingPrice=decimal.Decimal(2786.24), strikePrice=decimal.Decimal(2855), delta=0.16, gamma=0.01, theta=0.02, vega=0.03, dateTime=datetime.datetime.strptime( '01/02/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/20/2021', "%m/%d/%Y"), bidPrice=decimal.Decimal(4.20), askPrice=decimal.Decimal(4.40), tradePrice=decimal.Decimal(4.30)) # Create tick event and update portfolio values. testOptionChain = [callOpt, putOpt] event = tickEvent.TickEvent() event.createEvent(testOptionChain) portfolioObj.updatePortfolio(event) # Check that the new portfolio values are correct (e.g., buying power, total delta, total gamma, etc). self.assertAlmostEqual(portfolioObj.totalBuyingPower, decimal.Decimal(63310.0)) self.assertAlmostEqual(portfolioObj.totalVega, 0.06) self.assertAlmostEqual(portfolioObj.totalDelta, 0.0) self.assertAlmostEqual(portfolioObj.totalGamma, 0.02) self.assertAlmostEqual(portfolioObj.totalTheta, 0.04) self.assertAlmostEqual(portfolioObj.netLiquidity, decimal.Decimal(1000200.0))
def testStrangleManageDaysBeforeClosing(self): """Test that the strangle is set to be deleted from the portfolio if the number of days until expiration is less than the daysBeforeClosing threshold. """ # Create put and call options. tickData = [] # Set up date / time formats. local = pytz.timezone('US/Eastern') # Convert time zone of data 'US/Eastern' to UTC time. expDate = datetime.datetime.strptime("02/05/18", "%m/%d/%y") expDate = local.localize(expDate, is_dst=None) expDate = expDate.astimezone(pytz.utc) # Convert time zone of data 'US/Eastern' to UTC time. curDate = datetime.datetime.strptime("02/02/18", "%m/%d/%y") curDate = local.localize(curDate, is_dst=None) curDate = curDate.astimezone(pytz.utc) putOpt = put.Put('SPX', 2690, 0.15, expDate, underlyingPrice=2786.24, bidPrice=3.00, askPrice=3.00, tradePrice=3.00, optionSymbol="01", dateTime=curDate) tickData.append(putOpt) callOpt = call.Call('SPX', 2855, -0.15, expDate, underlyingPrice=2786.24, bidPrice=2.00, askPrice=2.00, tradePrice=2.00, optionSymbol="02", dateTime=curDate) tickData.append(callOpt) daysBeforeClosing = 5 orderQuantity = 1 strangleObj = strangle.Strangle(orderQuantity, callOpt, putOpt, "SELL", daysBeforeClosing) # Check if managePosition() returns true since the current dateTime and DTE are less than daysBeforeClosing = 5. self.assertTrue(strangleObj.managePosition())
def testCallOptionCreation(self): """Tests that a CALL option is created successfully.""" callOption = call.Call(underlyingTicker='SPY', strikePrice=250, delta=0.3, dateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/01/2050', "%m/%d/%Y"), bidPrice=1.50, askPrice=1.00, tradePrice=3.00) self.assertEqual(callOption.strikePrice, 250) self.assertEqual(callOption.optionType, option.OptionTypes.CALL)
def setUp(self): """Create portfolio object to be shared among tests.""" startingCapital = decimal.Decimal(1000000) maxCapitalToUse = 0.5 maxCapitalToUsePerTrade = 0.5 self.portfolioObj = portfolio.Portfolio(startingCapital, maxCapitalToUse, maxCapitalToUsePerTrade) # Strangle object to be shared among tests. putOpt = put.Put(underlyingTicker='SPX', underlyingPrice=decimal.Decimal(2786.24), strikePrice=decimal.Decimal(2690), delta=-0.16, gamma=0.01, theta=0.02, vega=0.03, dateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/20/2021', "%m/%d/%Y"), bidPrice=decimal.Decimal(7.45), askPrice=decimal.Decimal(7.50), tradePrice=decimal.Decimal(7.475)) callOpt = call.Call(underlyingTicker='SPX', underlyingPrice=decimal.Decimal(2786.24), strikePrice=decimal.Decimal(2855), delta=0.16, gamma=0.01, theta=0.02, vega=0.03, dateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/20/2021', "%m/%d/%Y"), bidPrice=decimal.Decimal(5.20), askPrice=decimal.Decimal(5.40), tradePrice=decimal.Decimal(5.30)) self.strangleObj = strangle.Strangle( orderQuantity=1, callOpt=callOpt, putOpt=putOpt, buyOrSell=optionPrimitive.TransactionType.SELL) self.riskManagement = strangleRiskManagement.StrangleRiskManagement( strangleRiskManagement.StrangleManagementStrategyTypes. HOLD_TO_EXPIRATION)
def testOnSignal(self): # Create a strangle. putOpt = put.Put('SPX', 2690, 0.16, 34, underlyingPrice=2786.24, bidPrice=7.45, askPrice=7.45) # Create CALL option. callOpt = call.Call('SPX', 2855, -0.15, 34, underlyingPrice=2786.24, bidPrice=5.20, askPrice=5.20) # Create Strangle. orderQuantity = 1 strangleObj = strangle.Strangle(orderQuantity, callOpt, putOpt, "SELL") # Create signal event. event = signalEvent.SignalEvent() event.createEvent(strangleObj) # Test portfolio onSignal event. self.portfolioObj.onSignal(event) # Check that positions array in portfolio is not empty. positions = self.portfolioObj.getPositions() self.assertNotEqual(len(positions), 0) # Check that the buying power used by the strangle is correct. self.assertAlmostEqual(self.portfolioObj.getTotalBuyingPower(), 64045.0) # Get the total delta value of the portfolio and check that it is 0.01. self.assertAlmostEqual(self.portfolioObj.getDelta(), 0.01)
def setUp(self): """Create strangle with necessary params and test buying power calculation. Params for creating object: daysBeforeClosing: Strangle must be closed if there are <= daysBeforeClosing to expiration. profitTargetPercent: Minimum percent profit we want in order to close strangle. orderQuantity: Number of strangles. callOpt: Call option putOpt: Put option """ daysBeforeClosing = 5 orderQuantity = 1 profitTargetPercent = 0.5 # Create put and call options. putOpt = put.Put('SPX', 2690, 0.15, 34, underlyingPrice=2786.24, bidPrice=7.45, askPrice=7.45, optionSymbol="01", tradePrice=7.45) callOpt = call.Call('SPX', 2855, -0.15, 34, underlyingPrice=2786.24, bidPrice=5.20, askPrice=5.20, optionSymbol="02", tradePrice=5.20) # Create Strangle. self.strangleObj = strangle.Strangle(orderQuantity, callOpt, putOpt, "SELL", daysBeforeClosing, profitTargetPercent)
def testStrangeGetNumberOfDaysLeft(self): """Tests that we calculate the number of days between two date / times correctly.""" putOpt = put.Put(underlyingTicker='SPX', underlyingPrice=2786.24, strikePrice=2690, delta=0.15, vega=None, theta=None, gamma=None, dateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/20/2021', "%m/%d/%Y"), bidPrice=7.45, askPrice=7.50, tradePrice=7.475) callOpt = call.Call(underlyingTicker='SPX', underlyingPrice=2786.24, strikePrice=2855, delta=-0.16, vega=0.05, theta=-0.06, gamma=0.12, dateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/20/2021', "%m/%d/%Y"), bidPrice=5.20, askPrice=5.40, tradePrice=5.30) strangleObj = strangle.Strangle( orderQuantity=1, callOpt=callOpt, putOpt=putOpt, buyOrSell=optionPrimitive.TransactionType.SELL) self.assertEqual(strangleObj.getNumberOfDaysLeft(), 19)
def createBaseType(self, inputData): """ Used to take a tick (e.g., row from CSV) and create a base type such as a stock or option (call or put). """ if self.__dataProvider == "iVolatility": # CSV header # symbol, exchange, company_name, date, stock_price_close, option_symbol, expiration_date, strike, # call_put, style, ask, bid, mean_price, settlement, iv, volume, open_interest, stock_price_for_iv, # isinterpolated, delta, vega, gamma, theta, rho # Do type checking on fields. underlyingTicker = inputData['symbol'] exchange = inputData['exchange'] optionSymbol = inputData['option_symbol'] # Check if style is American or European. style = inputData['style'] if style != 'A' and style != 'E': return None # Check that underlyingTicker is a character string. if not underlyingTicker.isalpha(): return None # Check that fields below are floating point numbers. try: strikePrice = float(inputData['strike']) underlyingPrice = float(inputData['stock_price_close']) underlyingTradePrice = underlyingPrice askPrice = float(inputData['ask']) bidPrice = float(inputData['bid']) tradePrice = (askPrice + bidPrice) / 2 impliedVol = float(inputData['iv']) volume = float(inputData['volume']) openInterest = int(inputData['open_interest']) delta = float(inputData['delta']) theta = float(inputData['theta']) vega = float(inputData['vega']) gamma = float(inputData['gamma']) rho = float(inputData['rho']) except: return None # Convert current date and expiration date to a datetime object. try: local = pytz.timezone('US/Eastern') # Convert time zone of data 'US/Eastern' to UTC time. # Try and except here to handle two or four digit year format. try: DTE = datetime.datetime.strptime( inputData['option_expiration'], "%m/%d/%y") except: DTE = datetime.datetime.strptime( inputData['option_expiration'], "%m/%d/%Y") DTE = local.localize(DTE, is_dst=None) DTE = DTE.astimezone(pytz.utc) try: curDateTime = datetime.datetime.strptime( inputData['date'], "%m/%d/%y") except: curDateTime = datetime.datetime.strptime( inputData['date'], "%m/%d/%Y") curDateTime = local.localize(curDateTime, is_dst=None) curDateTime = curDateTime.astimezone(pytz.utc) except: logging.warning( 'Something went wrong when trying to read the option data from the CSV.' ) return None call_put = inputData['call/put'] if call_put == 'C': return call.Call(underlyingTicker, strikePrice, delta, DTE, None, underlyingPrice, underlyingTradePrice, optionSymbol, None, bidPrice, askPrice, tradePrice, openInterest, volume, curDateTime, theta, gamma, rho, vega, impliedVol, exchange) elif call_put == 'P': return put.Put(underlyingTicker, strikePrice, delta, DTE, None, underlyingPrice, underlyingTradePrice, optionSymbol, None, bidPrice, askPrice, tradePrice, openInterest, volume, curDateTime, theta, gamma, rho, vega, impliedVol, exchange) else: return None else: print("Unrecognized CSV data source provider") sys.exit(1)
def testPortfolioPositionRemoveManagement(self): """Test that we can remove a managed position from the portfolio without affecting any of the other positions in the portfolio. """ # Create portfolio object. portfolioObj = portfolio.Portfolio(self.startingCapital, self.maxCapitalToUse, self.maxCapitalToUsePerTrade) # Add first position to the portfolio. putOpt = put.Put('SPX', 2690, 0.15, 34, underlyingPrice=2786.24, bidPrice=7.45, askPrice=7.45, optionSymbol="01", tradePrice=7.45) callOpt = call.Call('SPX', 2855, -0.15, 34, underlyingPrice=2786.24, bidPrice=5.20, askPrice=5.20, optionSymbol="02", tradePrice=5.20) # Create Strangle. orderQuantity = 1 profitTargetPercent = 0.5 strangleObj = strangle.Strangle( orderQuantity, callOpt, putOpt, "SELL", profitTargetPercent=profitTargetPercent) # Create signal event. event = signalEvent.SignalEvent() event.createEvent(strangleObj) # Create portfolio onSignal event, which adds the position to the portfolio. portfolioObj.onSignal(event) # Add second position to the portfolio. putOpt = put.Put('AAPL', 140, 0.15, 34, underlyingPrice=150, bidPrice=5.15, askPrice=5.15, optionSymbol="03", tradePrice=5.15) callOpt = call.Call('APPL', 160, -0.15, 34, underlyingPrice=150, bidPrice=3.20, askPrice=3.20, optionSymbol="04", tradePrice=3.20) strangleObj = strangle.Strangle( orderQuantity, callOpt, putOpt, "SELL", profitTargetPercent=profitTargetPercent) # Create signal event. event = signalEvent.SignalEvent() event.createEvent(strangleObj) # Create portfolio onSignal event, which adds the position to the portfolio. portfolioObj.onSignal(event) # Add a third position to the portfolio. putOpt = put.Put('SPY', 240, 0.15, 34, underlyingPrice=280, bidPrice=4.15, askPrice=4.15, optionSymbol="05", tradePrice=4.15) callOpt = call.Call('SPY', 300, -0.15, 34, underlyingPrice=280, bidPrice=2.20, askPrice=2.20, optionSymbol="06", tradePrice=2.20) strangleObj = strangle.Strangle( orderQuantity, callOpt, putOpt, "SELL", profitTargetPercent=profitTargetPercent) # Create signal event. event = signalEvent.SignalEvent() event.createEvent(strangleObj) # Create portfolio onSignal event, which adds the position to the portfolio. portfolioObj.onSignal(event) # For the second position in the portfolio, make the option prices less than 50% of the trade price, which # should cause the position to be closed / deleted from the portfolio. newOptionObjs = [] putOpt = put.Put('SPX', 2690, 0.15, 34, underlyingPrice=2786.24, bidPrice=7.45, askPrice=7.45, optionSymbol="01", tradePrice=7.45) newOptionObjs.append(putOpt) callOpt = call.Call('SPX', 2855, -0.15, 34, underlyingPrice=2786.24, bidPrice=5.20, askPrice=5.20, optionSymbol="02", tradePrice=5.20) newOptionObjs.append(callOpt) putOpt = put.Put('AAPL', 140, 0.15, 34, underlyingPrice=150, bidPrice=2.15, askPrice=2.15, optionSymbol="03") newOptionObjs.append(putOpt) callOpt = call.Call('APPL', 160, -0.15, 34, underlyingPrice=150, bidPrice=1.20, askPrice=1.20, optionSymbol="04") newOptionObjs.append(callOpt) putOpt = put.Put('SPY', 240, 0.15, 34, underlyingPrice=280, bidPrice=4.15, askPrice=4.15, optionSymbol="05", tradePrice=4.15) newOptionObjs.append(putOpt) callOpt = call.Call('SPY', 300, -0.15, 34, underlyingPrice=280, bidPrice=2.20, askPrice=2.20, optionSymbol="06", tradePrice=2.20) newOptionObjs.append(callOpt) newEvent = tickEvent.TickEvent() newEvent.createEvent(newOptionObjs) portfolioObj.updatePortfolio(newEvent) # Check that the first position is the SPX position, and the second position is the SPY position, i.e., the # AAPL position should have been managed / removed. positions = portfolioObj.getPositions() callPos0 = positions[0].getCallOption() callPos1 = positions[1].getCallOption() self.assertEqual(callPos0.getUnderlyingTicker(), 'SPX') self.assertEqual(callPos1.getUnderlyingTicker(), 'SPY') self.assertEqual(len(positions), 2)
def testPortfolioWithExpirationManagement(self): """Put on a strangle; update portfolio values, and then manage the strangle when the daysBeforeClosing threshold has been met. """ # Create portfolio object. portfolioObj = portfolio.Portfolio(self.startingCapital, self.maxCapitalToUse, self.maxCapitalToUsePerTrade) # Set up date / time formats. local = pytz.timezone('US/Eastern') # Convert time zone of data 'US/Eastern' to UTC time. expDate = datetime.datetime.strptime("02/20/18", "%m/%d/%y") expDate = local.localize(expDate, is_dst=None) expDate = expDate.astimezone(pytz.utc) # Convert time zone of data 'US/Eastern' to UTC time. curDate = datetime.datetime.strptime("02/02/18", "%m/%d/%y") curDate = local.localize(curDate, is_dst=None) curDate = curDate.astimezone(pytz.utc) # Add first position to the portfolio. putOpt = put.Put('SPX', 2690, 0.15, expDate, underlyingPrice=2786.24, bidPrice=7.45, askPrice=7.45, optionSymbol="01", tradePrice=7.45, dateTime=curDate) callOpt = call.Call('SPX', 2855, -0.15, expDate, underlyingPrice=2786.24, bidPrice=5.20, askPrice=5.20, optionSymbol="02", tradePrice=5.20, dateTime=curDate) # Create Strangle. orderQuantity = 1 daysBeforeClosing = 5 strangleObj = strangle.Strangle(orderQuantity, callOpt, putOpt, "SELL", daysBeforeClosing) # Create signal event. event = signalEvent.SignalEvent() event.createEvent(strangleObj) # Create portfolio onSignal event, which adds the position to the portfolio. portfolioObj.onSignal(event) # Check that the position was added to the portfolio. self.assertEqual(len(portfolioObj.getPositions()), 1) # Change the time to be within five days from the DTE, which should cause the position to be closed / deleted # from the portfolio. curDate = datetime.datetime.strptime("02/19/18", "%m/%d/%y") curDate = local.localize(curDate, is_dst=None) curDate = curDate.astimezone(pytz.utc) newOptionObjs = [] putOpt = put.Put('SPX', 2690, 0.15, expDate, underlyingPrice=2786.24, bidPrice=7.45, askPrice=7.45, optionSymbol="01", tradePrice=7.45, dateTime=curDate) newOptionObjs.append(putOpt) callOpt = call.Call('SPX', 2855, -0.15, expDate, underlyingPrice=2786.24, bidPrice=5.20, askPrice=5.20, optionSymbol="02", tradePrice=5.20, dateTime=curDate) newOptionObjs.append(callOpt) newEvent = tickEvent.TickEvent() newEvent.createEvent(newOptionObjs) portfolioObj.updatePortfolio(newEvent) # Check that the position was managed / deleted from the portfolio. self.assertEqual(len(portfolioObj.getPositions()), 0)
def testStrangleCalcProfitLossPercentage(self): # Set up date / time formats. local = pytz.timezone('US/Eastern') # Convert time zone of data 'US/Eastern' to UTC time. expDate = datetime.datetime.strptime("02/05/18", "%m/%d/%y") expDate = local.localize(expDate, is_dst=None) expDate = expDate.astimezone(pytz.utc) # Convert time zone of data 'US/Eastern' to UTC time. curDate = datetime.datetime.strptime("02/02/18", "%m/%d/%y") curDate = local.localize(curDate, is_dst=None) curDate = curDate.astimezone(pytz.utc) # Test the case where we sell a strangle and have a loss. putOpt = put.Put('SPX', 2690, 0.15, expDate, underlyingPrice=2786.24, bidPrice=6.00, askPrice=6.00, tradePrice=3.00, optionSymbol="01", dateTime=curDate) callOpt = call.Call('SPX', 2855, -0.15, expDate, underlyingPrice=2786.24, bidPrice=4.00, askPrice=4.00, tradePrice=2.00, optionSymbol="02", dateTime=curDate) orderQuantity = 1 strangleObj = strangle.Strangle(orderQuantity, callOpt, putOpt, "SELL") # Total credit = 5.00 or 500, and total loss is -300 + -200 = -500; (-500/500) * 100 = -100% profitLossPercentage = strangleObj.calcProfitLossPercentage() self.assertAlmostEqual(profitLossPercentage, -100) # Test the case where we sell a strangle and have a profit. putOpt = put.Put('SPX', 2690, 0.15, expDate, underlyingPrice=2786.24, bidPrice=1.50, askPrice=1.50, tradePrice=3.00, optionSymbol="01", dateTime=curDate) callOpt = call.Call('SPX', 2855, -0.15, expDate, underlyingPrice=2786.24, bidPrice=1.00, askPrice=1.00, tradePrice=2.00, optionSymbol="02", dateTime=curDate) strangleObj = strangle.Strangle(orderQuantity, callOpt, putOpt, "SELL") # Total credit = 5.00 or 500, and total profit is 150 + 100 = 250; 250/500 = 0.5 * 100 = 50% profitLossPercentage = strangleObj.calcProfitLossPercentage() self.assertAlmostEqual(profitLossPercentage, 50) # Test the case where we buy a strangle and have a loss. putOpt = put.Put('SPX', 2690, 0.15, expDate, underlyingPrice=2786.24, bidPrice=1.50, askPrice=1.50, tradePrice=3.00, optionSymbol="01", dateTime=curDate) callOpt = call.Call('SPX', 2855, -0.15, expDate, underlyingPrice=2786.24, bidPrice=1.00, askPrice=1.00, tradePrice=2.00, optionSymbol="02", dateTime=curDate) strangleObj = strangle.Strangle(orderQuantity, callOpt, putOpt, "BUY") # Total debit = 5.00 or 500, and total loss is -150 + -100 = -250 / 500 = -0.5* 100 = -50% profitLossPercentage = strangleObj.calcProfitLossPercentage() self.assertAlmostEqual(profitLossPercentage, -50) # Test the case where we buy a strangle and have a profit. putOpt = put.Put('SPX', 2690, 0.15, expDate, underlyingPrice=2786.24, bidPrice=6.00, askPrice=6.00, tradePrice=3.00, optionSymbol="01", dateTime=curDate) callOpt = call.Call('SPX', 2855, -0.15, expDate, underlyingPrice=2786.24, bidPrice=4.00, askPrice=4.00, tradePrice=2.00, optionSymbol="02", dateTime=curDate) strangleObj = strangle.Strangle(orderQuantity, callOpt, putOpt, "BUY") # Total debit = 5.00 or 500, and total profit is 300 + 200 = 500 / 500 = 1 * 100 = 100% profitLossPercentage = strangleObj.calcProfitLossPercentage() self.assertAlmostEqual(profitLossPercentage, 100)
def setUp(self): orderQuantity = 1 putOpt = put.Put(underlyingTicker='SPX', underlyingPrice=decimal.Decimal(2786.24), strikePrice=decimal.Decimal(2690), delta=0.15, vega=0.04, theta=-0.07, gamma=0.11, dateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/20/2021', "%m/%d/%Y"), bidPrice=decimal.Decimal(7.45), askPrice=decimal.Decimal(7.50), tradePrice=decimal.Decimal(7.475)) callOpt = call.Call(underlyingTicker='SPX', underlyingPrice=decimal.Decimal(2786.24), strikePrice=decimal.Decimal(2855), delta=-0.16, vega=0.05, theta=-0.06, gamma=0.12, dateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/20/2021', "%m/%d/%Y"), bidPrice=decimal.Decimal(5.20), askPrice=decimal.Decimal(5.40), tradePrice=decimal.Decimal(5.30)) self.__strangleObj = strangle.Strangle( orderQuantity=orderQuantity, callOpt=callOpt, putOpt=putOpt, buyOrSell=optionPrimitive.TransactionType.SELL) # The parameters below are used to update the prices of the initial strangle above. self.__tickData = [] putOpt = put.Put(underlyingTicker='SPX', underlyingPrice=decimal.Decimal(2790.0), strikePrice=decimal.Decimal(2690), delta=0.13, vega=0.03, theta=-0.06, gamma=0.12, dateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/20/2021', "%m/%d/%Y"), bidPrice=decimal.Decimal(7.25), askPrice=decimal.Decimal(7.350)) self.__tickData.append(putOpt) callOpt = call.Call(underlyingTicker='SPX', underlyingPrice=decimal.Decimal(2790.0), strikePrice=decimal.Decimal(2855), delta=-0.20, vega=0.06, theta=-0.07, gamma=0.14, dateTime=datetime.datetime.strptime( '01/01/2021', "%m/%d/%Y"), expirationDateTime=datetime.datetime.strptime( '01/20/2021', "%m/%d/%Y"), bidPrice=decimal.Decimal(5.60), askPrice=decimal.Decimal(5.80)) self.__tickData.append(callOpt)
def __createBaseType(self, optionChain: pd.DataFrame) -> Iterable[option.Option]: """ Convert an option chain held in a dataframe to base option types (calls or puts). Attributes: optionChain: Pandas dataframe with optionChain data as rows. :raises ValueError: Symbol for put/call in JSON not found in dataframe column. :return: List of Option base type objects (puts or calls). """ optionObjects = [] # Create a dictionary for the fields that we will read from each row of the dataframe. The fields should also be # specified in the dataProviders.json file. # Instead of manually specifying the fields below, we could read them from the Option class. optionFieldDict = { 'underlyingTicker': None, 'strikePrice': None, 'delta': None, 'expirationDateTime': None, 'underlyingPrice': None, 'optionSymbol': None, 'bidPrice': None, 'askPrice': None, 'tradePrice': None, 'openInterest': None, 'volume': None, 'dateTime': None, 'theta': None, 'gamma': None, 'rho': None, 'vega': None, 'impliedVol': None, 'exchangeCode': None, 'exercisePrice': None, 'assignPrice': None, 'openCost': None, 'closeCost': None, } dataProviderConfig = self.__dataConfig[self.__dataProvider] for _, row in optionChain.iterrows(): # Defaults to PUT (True). putOrCall = True for option_column_name, dataframe_column_name in dataProviderConfig[ 'column_names'].items(): # Check that we need to look up the field. if not dataframe_column_name: continue if option_column_name == 'optionType': optionType = row[dataframe_column_name] # Convert any lowercase symbols to uppercase. optionType = str(optionType).upper() if optionType == dataProviderConfig[ 'call_symbol_abbreviation']: putOrCall = False elif optionType == dataProviderConfig[ 'put_symbol_abbreviation']: putOrCall = True else: raise ValueError( 'Symbol for put / call in dataProviders.json not found in optionType dataframe column.' ) else: optionFieldDict[option_column_name] = row[ dataframe_column_name] if optionFieldDict['bidPrice'] is not None and optionFieldDict[ 'askPrice'] is not None: optionFieldDict['tradePrice'] = (decimal.Decimal( optionFieldDict['bidPrice']) + decimal.Decimal( optionFieldDict['askPrice'])) / decimal.Decimal(2.0) argsDict = { 'underlyingTicker': optionFieldDict['underlyingTicker'], 'strikePrice': decimal.Decimal(optionFieldDict['strikePrice']), 'delta': float(optionFieldDict['delta']), 'expirationDateTime': datetime.datetime.strptime( optionFieldDict['expirationDateTime'], dataProviderConfig['date_time_format']), 'underlyingPrice': decimal.Decimal(optionFieldDict['underlyingPrice']), 'optionSymbol': optionFieldDict['optionSymbol'], 'bidPrice': decimal.Decimal(optionFieldDict['bidPrice']), 'askPrice': decimal.Decimal(optionFieldDict['askPrice']), 'tradePrice': decimal.Decimal(optionFieldDict['tradePrice']), 'openInterest': int(optionFieldDict['openInterest']), 'volume': int(optionFieldDict['volume']), 'dateTime': datetime.datetime.strptime( optionFieldDict['dateTime'], dataProviderConfig['date_time_format']), 'theta': float(optionFieldDict['theta']), 'gamma': float(optionFieldDict['gamma']), 'rho': float(optionFieldDict['rho']), 'vega': float(optionFieldDict['vega']), 'impliedVol': float(optionFieldDict['impliedVol']), 'exchangeCode': optionFieldDict['exchangeCode'], 'exercisePrice': decimal.Decimal(optionFieldDict['exercisePrice']) if optionFieldDict['exercisePrice'] else None, 'assignPrice': decimal.Decimal(optionFieldDict['assignPrice']) if optionFieldDict['assignPrice'] else None, 'openCost': decimal.Decimal(optionFieldDict['openCost']) if optionFieldDict['openCost'] else None, 'closeCost': decimal.Decimal(optionFieldDict['closeCost']) if optionFieldDict['closeCost'] else None, } if not putOrCall: optionObjects.append(call.Call(**argsDict)) else: optionObjects.append(put.Put(**argsDict)) # Reset all the dictionary values back to None. This is probably overkill since we can just rewrite them. optionFieldDict = optionFieldDict.fromkeys(optionFieldDict, None) return optionObjects