def test_sell_too_many_contracts(self): call = Option('call', TSLA, 500, datetime(2020, 12, 28)) port = Portfolio(0, {TSLA: 100}) te = TradingEngine([port]) te.sell_contract(port, call, 10) self.assertRaises(Exception, te.sell_contract, port, call, 10)
def test_sell_contract(self): call = Option('call', TSLA, 500, datetime(2020, 12, 28)) port = Portfolio(0, {TSLA: 100}) te = TradingEngine([port]) te.sell_contract(port, call, 10) self.assertEqual(1000, port.cash) self.assertEqual(-1, port.securities[call])
def test_option_expiration(self): call = Option('call', TSLA, 500, datetime(2020, 12, 28)) port = Portfolio(0, {TSLA: 100}) te = TradingEngine([port]) te.sell_contract(port, call, 10) te.eval({TSLA: 400}, datetime(2020, 12, 28)) self.assertFalse(port.contracts()) self.assertEqual(0, port.collateral[TSLA])
def test_expire_and_assign(self): call = Option('call', TSLA, 500, datetime(2020, 12, 28)) port = Portfolio(0, {TSLA: 100}) te = TradingEngine([port]) te.sell_contract(port, call, 10) te.eval({TSLA: 400}, datetime(2020, 12, 28)) call = Option('call', TSLA, 500, datetime(2021, 1, 15)) te.sell_contract(port, call, 5) te.eval({TSLA: 600}, datetime(2021, 1, 15)) self.assertEqual(1000 + 500 + 50000, port.cash) self.assertEqual(0, port.securities[TSLA]) self.assertEqual(0, port.contracts()[call])
def test_release_collateral(self): call = Option('call', TSLA, 500, datetime(2020, 12, 28)) port = Portfolio(50000, {TSLA: 100}) te = TradingEngine([port]) # sell a contract to start, putting up 100 shares as collateral te.sell_contract(port, call, 10) self.assertEqual(-1, port.securities[call]) self.assertEqual(100, port.collateral[TSLA]) # buy back the contract, eliminating the obligation te.buy_contract(port, call, 10) self.assertEqual(0, port.securities[call]) self.assertEqual(0, port.collateral[TSLA]) # go long instead, purchasing a call te.buy_contract(port, call, 10) self.assertEqual(1, port.securities[call]) self.assertEqual(0, port.collateral[TSLA]) # close that long call te.sell_contract(port, call, 10) self.assertEqual(0, port.securities[call]) self.assertEqual(0, port.collateral[TSLA]) # sell an option to go short again te.sell_contract(port, call, 10) self.assertEqual(-1, port.securities[call]) self.assertEqual(100, port.collateral[TSLA])
class Simulator: def __init__(self, security, portfolio, path, training, reporter=None): self.security = security self.portfolio = portfolio self.path = path self.training = training self.reporter = reporter self.scales = pd.read_csv(path / 'scales.csv', index_col=0) self.engine = TradingEngine([portfolio], reporter) self.split_handler = StockSplitHandler(path / 'splits.csv', security) self.importer = CsvImporter() def simulate(self, net, start=None, end=None): """ runs a simulation with provided network net: neural network start: datetime to start simulation end: datetime to end the simulation """ close = None for row in self._days_in_range(start, end): params = self._map_row(row) date = params[0] close = params[1] params = params[1::] # shave off date try: # process assignments, expirations self.engine.eval({self.security: self._denormalize(close)}, date) if not np.isnan(params).any(): # Check for stock split and adjust portfolio accordingly self.split_handler.check_and_invoke(self.portfolio, date) # Activate! 🤖 buy, sell, hold, delta, theta = net.activate(params) # Buy if buy > sell and buy > hold: self._buy(date) # Sell elif sell > buy and sell > hold: self._sell(date, close, delta, theta) except Exception as e: print(f"Failed on {self.security}:{date}") raise e return self._calculate_fitness(close, end) def _most_recent_chain(self, date): while True: path = self.path / 'chains' / f"{small_date(date)}.csv" if path.exists(): return self.importer.parse_chain(date, self.security, path) else: date -= timedelta(days=1) def _calculate_fitness(self, close, end): cash = 0 for contract, amt in self.portfolio.contracts().items(): if amt != 0: chain = self._most_recent_chain(end) # option prices are not normalized cash += chain.get_price(contract) * amt * 100 denorm_close = self._denormalize(close) for stock, amt in self.portfolio.stocks().items(): cash += amt * denorm_close cash += self.portfolio.cash # compare against a buy-and-hold strategy return cash - (100 * denorm_close) def _days_in_range(self, start, end): mask = (self.training['date'] > start) & (self.training['date'] <= end) for idx, row in self.training.loc[mask].iterrows(): yield row def _map_row(self, row): # TODO add held option delta/theta, maybe vega? date = row['date'] close = row['close'] macd = row['macd'] macd_signal = row['macd_signal'] macd_diff = row['macd_diff'] bb_bbm = row['bb_bbm'] bb_bbh = row['bb_bbh'] bb_bbl = row['bb_bbl'] rsi = row['rsi'] return (date, close, macd, macd_signal, macd_diff, bb_bbm, bb_bbh, bb_bbl, rsi) def _buy(self, date): # only covered calls are supported right now so this means close the position for contract, amt in self.portfolio.contracts().items(): if amt < 0: # right now there should only be one short contract at a time chain = self._most_recent_chain(date) new_price = chain.get_price(contract) try: self.engine.buy_contract(self.portfolio, contract, new_price) if self.reporter: self.reporter.record(date, 'buy', contract, new_price) except Exception as e: print(e) def _sell(self, date, close, delta, theta): if not self.portfolio.contracts(): chain = self._most_recent_chain(date) # sell a new contract contract = chain.search(self._denormalize(close), delta=delta, theta=theta) try: self.engine.sell_contract(self.portfolio, contract, contract.price) if self.reporter: self.reporter.record(date, 'sell', contract, contract.price) except Exception as e: print(e) def _denormalize(self, x): mn = self.scales.loc['close', 'min'] mx = self.scales.loc['close', 'max'] return un_min_max(x, mn, mx)