diff --git a/backtester/__init__.py b/backtester/__init__.py index 052efec..afb16ef 100644 --- a/backtester/__init__.py +++ b/backtester/__init__.py @@ -1,3 +1,3 @@ from . import datahandler from .backtester import Backtest -from .enums import Stock +from .enums import Stock, Type, Direction diff --git a/backtester/backtester.py b/backtester/backtester.py index 7342157..bb4aaf9 100644 --- a/backtester/backtester.py +++ b/backtester/backtester.py @@ -24,7 +24,7 @@ class Backtest: self.shares_per_contract = shares_per_contract self._stocks = [] self._options_strategy = None - self._stocks_data = None + self._stock_data = None self._options_data = None @property @@ -34,7 +34,8 @@ class Backtest: @stocks.setter def stocks(self, stocks): assert all(isinstance(stock, Stock) for stock in stocks), 'Invalid stocks' - assert sum(stock.percentage for stock in stocks) == 1.0, 'Stock percentages must sum to 1.0' + assert np.isclose(sum(stock.percentage for stock in stocks), 1.0, + atol=0.000001), 'Stock percentages must sum to 1.0' self._stocks = list(stocks) return self @@ -49,14 +50,16 @@ class Backtest: self.current_cash = strat.initial_capital @property - def stocks_data(self): - return self._stocks_data + def stock_data(self): + return self._stock_data - @stocks_data.setter - def stocks_data(self, data): + @stock_data.setter + def stock_data(self, data): assert isinstance(data, TiingoData) self._stocks_schema = data.schema - self._stocks_data = data + self._stock_data = data + self._stock_data.first_date = data['date'].min() + self._stock_data.end_date = data['date'].max() @property def options_data(self): @@ -68,7 +71,7 @@ class Backtest: self._options_schema = data.schema self._options_data = data - def run(self, rebalance_freq=0, monthly=False): + def run(self, rebalance_freq=0, monthly=False, sma_days=None): """Runs the backtest and returns a `pd.DataFrame` of the orders executed (`self.trade_log`) Args: @@ -79,7 +82,7 @@ class Backtest: pd.DataFrame: Log of the trades executed. """ - assert self._stocks_data, 'Stock data not set' + assert self._stock_data, 'Stock data not set' assert self._options_data, 'Options data not set' assert self._options_strategy, 'Options Strategy not set' assert self._options_data.schema == self._options_strategy.schema @@ -91,11 +94,14 @@ class Backtest: self._initialize_inventories() self.trade_log = pd.DataFrame() self.balance = pd.DataFrame({ - 'total_capital': self.current_cash, - 'total_cash': self.current_cash + 'total capital': self.current_cash, + 'cash': self.current_cash }, index=[self.stock_data.start_date - pd.Timedelta(1, unit='day')]) + if sma_days: + self._stock_data.sma(sma_days) + rebalancing_days = pd.date_range( self.stock_data.first_date, self.stock_data.end_date, freq=str(rebalance_freq) + 'BMS') if rebalance_freq else [] @@ -105,12 +111,12 @@ class Backtest: for date, stocks, options in data_iterator: if date in rebalancing_days or date == self.stock_data.start_date: - self._rebalance_portfolio(date, stocks, options) + self._rebalance_portfolio(date, stocks, options, sma_days) self._update_balance(date, stocks, options) bar.update() - self.balance['% change'] = self.balance['total_capital'].pct_change() + self.balance['% change'] = self.balance['total capital'].pct_change() self.balance['accumulated return'] = (1.0 + self.balance['% change']).cumprod() return self.trade_log @@ -140,35 +146,30 @@ class Backtest: return ((date, stocks, options) for (date, stocks), (_, options) in it) - def _rebalance_portfolio(self, date, stocks, options): + def _rebalance_portfolio(self, date, stocks, options, sma_days): """Rebalances the portfolio according to `self.allocation`.""" + # Sell all the options currently in the inventory + self._sell_options(options, date) stock_capital = self._current_stock_capital(stocks) - options_capital = self._current_options_capital(options) - total_capital = self.current_cash + stock_capital + options_capital + total_capital = self.current_cash + stock_capital options_allocation = self.allocation['options'] * total_capital stocks_allocation = self.allocation['stocks'] * total_capital # Clear inventories self._initialize_inventories() - for stock in self._stocks: - query = '{} == "{}"'.format(self.schema['symbol'], stock.symbol) - stock_row = stocks.query(query) - stock_price = stock_row[self._stocks_schema['adjClose']].values[0] - qty = (stocks_allocation * stock.percentage) // stock_price - stock_entry = pd.Series([stock.symbol, stock_price, qty], index=self._stocks_inventory.columns) - self._stocks_inventory = self._stocks_inventory.append(stock_entry, ignore_index=True) - - self._sell_options() - entry_signals = self._strategy.filter_entries(options, self.inventory, date) + self._buy_stocks(stocks, stocks_allocation, sma_days) + entry_signals = self._options_strategy.filter_entries(options, self._options_inventory, date, + options_allocation) self._execute_entry(entry_signals) - options_value = sum(self.options_inventory['totals']['cost'] * self.options_inventory['totals']['qty']) + stocks_value = sum(self._stocks_inventory['price'] * self._stocks_inventory['qty']) + options_value = sum(self._options_inventory['totals']['cost'] * self._options_inventory['totals']['qty']) # Update current cash - invested_capital = sum(self.inventory['cost'] * self.inventory['qty']) - self.current_cash = money_total - invested_capital + invested_capital = options_value + stocks_value + self.current_cash = total_capital - invested_capital def _current_stock_capital(self, stocks): """Return the current value of the stocks inventory. @@ -182,37 +183,99 @@ class Backtest: current_stocks = self._stocks_inventory.merge(stocks, how='left', left_on='symbol', - right_on=self._stock_schema['symbol']) + right_on=self._stocks_schema['symbol']) return (current_stocks[self._stocks_schema['adjClose']] * current_stocks['qty']).sum() def _current_options_capital(self, options): + # Currently unused method total_cost = 0.0 for leg in self._options_strategy.legs: current_options = self._options_inventory[leg.name].merge(options, how='left', left_on='contract', right_on=self._options_schema['contract']) - price_col = ~(leg.direction).value - total_cost += current_options[price_col].fillna(0.0).iloc[0] * current_options['qty'] + price_col = (~leg.direction).value + try: + # 100 = shares_per_contract + cost = current_options[price_col].fillna( + 0.0).iloc[0] * self._options_inventory['totals']['qty'].values[0] * 100 + if price_col == 'bid': + total_cost += cost + else: + total_cost -= cost + except IndexError: + total_cost += 0.0 return total_cost + def _sell_options(self, options, date): + # This method essentially recycles most of the code in the filter_exits method in Strategy. + # The whole thing needs a refactor. + + leg_candidates = [ + self._options_strategy._exit_candidates(l.direction, self._options_inventory[l.name], options, + self._options_inventory.index) for l in self._options_strategy.legs + ] + + for i, leg in enumerate(self._options_strategy.legs): + fields = self._options_strategy._signal_fields((~leg.direction).value) + leg_candidates[i] = leg_candidates[i].loc[:, fields.values()] + leg_candidates[i].columns = pd.MultiIndex.from_product([["leg_{}".format(i + 1)], + leg_candidates[i].columns]) + + candidates = pd.concat(leg_candidates, axis=1) + + # If a contract is missing we replace the NaN values with those of the inventory + # except for cost, which we imput as zero. + imputed_inventory = self._options_strategy._imput_missing_data(self._options_inventory) + candidates = candidates.fillna(imputed_inventory) + total_costs = sum([candidates[l.name]['cost'] for l in self._options_strategy.legs]) + + # Append the 'totals' column to candidates + qtys = self._options_inventory['totals']['qty'] + dates = [date] * len(self._options_inventory) + totals = pd.DataFrame.from_dict({"cost": total_costs, "qty": qtys, "date": dates}) + totals.columns = pd.MultiIndex.from_product([["totals"], totals.columns]) + candidates = pd.concat([candidates, totals], axis=1) + + exits_mask = pd.Series([True] * len(self._options_inventory)) + exits_mask.index = self._options_inventory.index + + total_costs *= candidates['totals']['qty'] + + self._execute_exit((candidates, exits_mask, total_costs)) + + def _buy_stocks(self, stocks, allocation, sma_days): + for stock in self._stocks: + query = '{} == "{}"'.format(self._stocks_schema['symbol'], stock.symbol) + stock_row = stocks.query(query) + stock_price = stock_row[self._stocks_schema['adjClose']].values[0] + + if sma_days is not None: + if stock_row['sma'].values[0] < stock_price: + qty = (allocation * stock.percentage) // stock_price + else: + qty = 0 + else: + qty = (allocation * stock.percentage) // stock_price + + stock_entry = pd.Series([stock.symbol, stock_price, qty], index=self._stocks_inventory.columns) + self._stocks_inventory = self._stocks_inventory.append(stock_entry, ignore_index=True) + def _execute_entry(self, entry_signals): """Executes entry orders and updates `self.inventory` and `self.trade_log`""" entry, total_price = self._process_entry_signals(entry_signals) - if (not self.stop_if_broke) or (self.current_options_cash >= total_price): - self.options_inventory = self.options_inventory.append(entry, ignore_index=True) - self.trade_log = self.trade_log.append(entry, ignore_index=True) - self.current_options_cash -= total_price + self._options_inventory = self._options_inventory.append(entry, ignore_index=True) + self.trade_log = self.trade_log.append(entry, ignore_index=True) def _execute_exit(self, exit_signals): """Executes exits and updates `self.inventory` and `self.trade_log`""" exits, exits_mask, total_costs = exit_signals self.trade_log = self.trade_log.append(exits, ignore_index=True) - self.options_inventory.drop(self.options_inventory[exits_mask].index, inplace=True) - self.current_options_cash -= sum(total_costs) + self._options_inventory.drop(self._options_inventory[exits_mask].index, inplace=True) + self.current_cash -= sum(total_costs) def _process_entry_signals(self, entry_signals): """Returns the entry signals to execute and their cost.""" @@ -223,80 +286,6 @@ class Backtest: else: return entry_signals, 0 - def _rebalance_portfolio(self, date, stocks, options): - """Rebalance portfolio, done after an _update_balance""" - - #first we need to exit the options - exit_signals = self._options_strategy.filter_exits(options, self.options_inventory, date) - self._execute_exit(exit_signals) - - leg_candidates = [ - self._options_strategy._exit_candidates(l.direction, self.options_inventory[l.name], options, - self.options_inventory.index) for l in self._options_strategy.legs - ] - - # If a contract is missing we replace the NaN values with those of the inventory - # except for cost, which we imput as zero. - - for leg in leg_candidates: - leg['cost'].fillna(0, inplace=True) - - calls_value = -np.sum( - sum(leg['cost'] * self.options_inventory['totals']['qty'] - for leg in leg_candidates if (leg['type'] == 'call').any())) - puts_value = -np.sum( - sum(leg['cost'] * self.options_inventory['totals']['qty'] - for leg in leg_candidates if (leg['type'] == 'put').any())) - - options_capital = calls_value + puts_value - old_options_capital = self.current_options_cash + options_capital - - costs = [] - for stock in self.stocks: - query = '{} == "{}"'.format(self.stock_data.schema['symbol'], stock.symbol) - current_stock = stocks.query(query) - current_stock_price = current_stock[self.stock_data.schema['adjClose']].values[0] - stock_inventory = self.stocks_inventory.query(query) - if stock_inventory.empty: - qty = 0 - else: - qty = stock_inventory['qty'].values[0] - - costs.append(current_stock_price * qty) - - old_stock_capital = self.current_stocks_cash + sum(costs) - if old_options_capital + old_stock_capital != 0: - - self.total_capital = old_options_capital + old_stock_capital - - new_stocks_capital = self.total_capital * self.stocks_percentaje - - new_options_capital = self.total_capital * self.options_percentaje - - #update stock with new_stock_capital - stocks_costs = [] - for stock in self.stocks: - query = '{} == "{}"'.format(self.stock_data.schema['symbol'], stock.symbol) - current_stock = stocks.query(query) - current_stock_price = current_stock[self.stock_data.schema['adjClose']].values[0] - qty = (new_stocks_capital * stock.percentage) // current_stock_price - stocks_costs.append(qty * current_stock_price) - stocks_inventory_entry = self.stocks_inventory.query(query) - self.stocks_inventory.drop(stocks_inventory_entry.index, inplace=True) - updated_asset = pd.Series([stock.symbol, current_stock_price, qty]) - updated_asset.index = self.stocks_inventory.columns - self.stocks_inventory = self.stocks_inventory.append(updated_asset, ignore_index=True) - - self.stock_capital = new_stocks_capital - self.current_stocks_cash = self.stock_capital - sum(stocks_costs) - #update options - - self.current_options_cash += new_options_capital - self.current_options_cash - self._options_strategy.initial_capital = new_options_capital - entry_signals = self._options_strategy.filter_entries(options, self.options_inventory, date) - self._execute_entry(entry_signals) - self.options_capital = new_options_capital - def _update_balance(self, date, stocks, options): """Updates positions and calculates statistics for the current date. @@ -305,13 +294,13 @@ class Backtest: stocks (pd.DataFrame): DataFrame of stocks options (pd.DataFrame): DataFrame of (daily/monthly) options. """ - exit_signals = self._options_strategy.filter_exits(options, self.options_inventory, date) + exit_signals = self._options_strategy.filter_exits(options, self._options_inventory, date) self._execute_exit(exit_signals) # update options leg_candidates = [ - self._options_strategy._exit_candidates(l.direction, self.options_inventory[l.name], options, - self.options_inventory.index) for l in self._options_strategy.legs + self._options_strategy._exit_candidates(l.direction, self._options_inventory[l.name], options, + self._options_inventory.index) for l in self._options_strategy.legs ] # If a contract is missing we replace the NaN values with those of the inventory @@ -321,40 +310,42 @@ class Backtest: leg['cost'].fillna(0, inplace=True) calls_value = -np.sum( - sum(leg['cost'] * self.options_inventory['totals']['qty'] + sum(leg['cost'] * self._options_inventory['totals']['qty'] for leg in leg_candidates if (leg['type'] == 'call').any())) puts_value = -np.sum( - sum(leg['cost'] * self.options_inventory['totals']['qty'] + sum(leg['cost'] * self._options_inventory['totals']['qty'] for leg in leg_candidates if (leg['type'] == 'put').any())) options_capital = calls_value + puts_value - self.options_capital = self.current_options_cash + options_capital - # if self.balance == - #update stocks portfolio information due to change in price over time + self.options_capital = options_capital + # update stocks portfolio information due to change in price over time costs = [] for stock in self.stocks: query = '{} == "{}"'.format(self.stock_data.schema['symbol'], stock.symbol) - asset_current = stocks.query(query) - cost = asset_current[self.stock_data.schema['adjClose']].values[0] - stock_inventory = self.stocks_inventory.query(query) - qty = qty = stock_inventory['qty'].values[0] + stock_current = stocks.query(query) + cost = stock_current[self.stock_data.schema['adjClose']].values[0] + stock_inventory = self._stocks_inventory.query(query) + try: + qty = stock_inventory['qty'].values[0] + except IndexError: + qty = 0 costs.append(cost * qty) total_value = sum(costs) - self.stock_capital = total_value + self.current_stocks_cash - self.total_capital = self.stock_capital + self.options_capital + self.stock_capital = total_value + self.total_capital = self.stock_capital + self.options_capital + self.current_cash row = pd.Series( { 'total capital': self.stock_capital + self.options_capital, - 'cash': self.current_stocks_cash + self.current_options_cash, + 'cash': self.current_cash, 'stocks capital': self.stock_capital, 'stocks qty': self._stocks_inventory['qty'].sum(), 'options capital': options_capital, 'options qty': self._options_inventory['totals']['qty'].sum(), - 'calls capital': calls_capital, - 'puts capital': puts_capital + 'calls capital': calls_value, + 'puts capital': puts_value }, name=date) self.balance = self.balance.append(row) diff --git a/backtester/datahandler/__init__.py b/backtester/datahandler/__init__.py index d71f3d4..59759c2 100644 --- a/backtester/datahandler/__init__.py +++ b/backtester/datahandler/__init__.py @@ -1,5 +1,4 @@ from .schema import Schema from .historical_options_data import HistoricalOptionsData -from .historical_asset_data import HistoricalAssetData from .tiingo_data import TiingoData diff --git a/backtester/examples/merged_bt_example.ipynb b/backtester/examples/merged_bt_example.ipynb new file mode 100644 index 0000000..c680184 --- /dev/null +++ b/backtester/examples/merged_bt_example.ipynb @@ -0,0 +1,1055 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from backtester.datahandler import HistoricalOptionsData, TiingoData\n", + "from backtester.strategy import Strategy, StrategyLeg \n", + "from backtester import Type, Direction, Stock\n", + "from backtester import Backtest\n", + "from backtester.strategy import Strangle\n", + "from backtester.statistics import monthly_returns_heatmap, returns_histogram, returns_chart" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "data = HistoricalOptionsData(\"allspx/SPX_2017.csv\")\n", + "schema = data.schema" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "stock_data = TiingoData('./data/portfolio_data_2017.csv')" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "short_straddle = Strategy(schema)\n", + "\n", + "leg1 = StrategyLeg(\"leg_1\", schema, option_type=Type.CALL, direction=Direction.SELL)\n", + "leg1.entry_filter = (schema.underlying == \"SPX\") & (schema.dte >= 31) & (schema.dte <= 60) & (schema.strike >= schema.underlying_last * 0.95) & (schema.strike <= schema.underlying_last * 1.05)\n", + "\n", + "leg1.exit_filter = (schema.dte <= 2)\n", + "\n", + "leg2 = StrategyLeg(\"leg_2\", schema, option_type=Type.PUT, direction=Direction.SELL)\n", + "leg2.entry_filter = (schema.underlying == \"SPX\") & (schema.dte >= 31) & (schema.dte <= 60) & (schema.strike >= schema.underlying_last * 0.95) & (schema.strike <= schema.underlying_last * 1.05)\n", + "\n", + "leg2.exit_filter = (schema.dte <= 2)\n", + "\n", + "short_straddle.add_legs([leg1, leg2]);" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "VOO = Stock('VOO', 0.1)\n", + "TUR = Stock('TUR', 0.05)\n", + "RSX = Stock('RSX', 0.05)\n", + "EWY = Stock('EWY', 0.05)\n", + "EWS = Stock('EWS', 0.05)\n", + "VTIP = Stock('VTIP', 0.10)\n", + "TLT = Stock('TLT', 0.20)\n", + "BWX = Stock('BWX', 0.10)\n", + "PDBC = Stock('PDBC', 0.05)\n", + "IAU = Stock('IAU', 0.15)\n", + "VNQI = Stock('VNQI', 0.10)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1.0000000000000002" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "0.1 + 0.05 + 0.05 + 0.05 + 0.05 + 0.10 + 0.20 + 0.10 + 0.05 + 0.15 + 0.10" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "stocks = [\n", + " VOO,\n", + " TUR,\n", + " RSX,\n", + " EWY,\n", + " EWS,\n", + " VTIP,\n", + " TLT,\n", + " BWX,\n", + " PDBC,\n", + " IAU,\n", + " VNQI\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "data._data['quotedate'] = data._data['quotedate'].apply(lambda x: x.tz_localize('UTC'))" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "bt = Backtest({'stocks': 0.99, 'options': 0.01, 'cash': 0})\n", + "bt.stocks = stocks\n", + "bt._options_strategy = short_straddle\n", + "bt.options_data = data\n", + "bt.stock_data = stock_data" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "0% [██████████████████████████████] 100% | ETA: 00:00:00\n", + "Total time elapsed: 00:00:38\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
leg_1leg_2totals
contractunderlyingexpirationtypestrikecostordercontractunderlyingexpirationtypestrikecostordercostqtydate
0SPX170217C02150000SPX2017-02-17call2150-11480.0Order.STOSPX170217P02150000SPX2017-02-17put2150-1120.0Order.STO-12600.01.02017-01-03 00:00:00+00:00
1SPX170217C02150000SPX2017-02-17call215013130.0Order.BTCSPX170217P02150000SPX2017-02-17put2150220.0Order.BTC13350.01.02017-02-01 00:00:00+00:00
2SPX170317C02170000SPX2017-03-17call2170-11350.0Order.STOSPX170317P02170000SPX2017-03-17put2170-930.0Order.STO-12280.01.02017-02-01 00:00:00+00:00
3SPX170317C02170000SPX2017-03-17call217022640.0Order.BTCSPX170317P02170000SPX2017-03-17put2170100.0Order.BTC22740.01.02017-03-01 00:00:00+00:00
4SPX170421C02280000SPX2017-04-21call2280-12370.0Order.STOSPX170421P02280000SPX2017-04-21put2280-1030.0Order.STO-13400.01.02017-03-01 00:00:00+00:00
5SPX170421C02280000SPX2017-04-21call22808490.0Order.BTCSPX170421P02280000SPX2017-04-21put2280360.0Order.BTC8850.01.02017-04-03 00:00:00+00:00
6SPX170519C02245000SPX2017-05-19call2245-12060.0Order.STOSPX170519P02245000SPX2017-05-19put2245-940.0Order.STO-13000.01.02017-04-03 00:00:00+00:00
7SPX170519C02245000SPX2017-05-19call224514560.0Order.BTCSPX170519P02245000SPX2017-05-19put2245150.0Order.BTC14710.01.02017-05-01 00:00:00+00:00
8SPX170616C02270000SPX2017-06-16call2270-12120.0Order.STOSPX170616P02270000SPX2017-06-16put2270-750.0Order.STO-12870.01.02017-05-01 00:00:00+00:00
9SPX170616C02270000SPX2017-06-16call227016290.0Order.BTCSPX170616P02270000SPX2017-06-16put227090.0Order.BTC16380.01.02017-06-01 00:00:00+00:00
10SPX170721C02310000SPX2017-07-21call2310-12510.0Order.STOSPX170721P02310000SPX2017-07-21put2310-810.0Order.STO-13320.01.02017-06-01 00:00:00+00:00
11SPX170721C02310000SPX2017-07-21call231012170.0Order.BTCSPX170721P02310000SPX2017-07-21put2310265.0Order.BTC12435.01.02017-07-03 00:00:00+00:00
12SPX170818C02310000SPX2017-08-18call2310-12370.0Order.STOSPX170818P02310000SPX2017-08-18put2310-960.0Order.STO-13330.01.02017-07-03 00:00:00+00:00
13SPX170818C02310000SPX2017-08-18call231016720.0Order.BTCSPX170818P02310000SPX2017-08-18put2310145.0Order.BTC16865.01.02017-08-01 00:00:00+00:00
14SPX170915C02355000SPX2017-09-15call2355-12270.0Order.STOSPX170915P02355000SPX2017-09-15put2355-740.0Order.STO-13010.01.02017-08-01 00:00:00+00:00
15SPX170915C02355000SPX2017-09-15call235512350.0Order.BTCSPX170915P02355000SPX2017-09-15put2355125.0Order.BTC12475.01.02017-09-01 00:00:00+00:00
16SPX171020C02355000SPX2017-10-20call2355-12840.0Order.STOSPX171020P02355000SPX2017-10-20put2355-970.0Order.STO-13810.01.02017-09-01 00:00:00+00:00
17SPX171020C02355000SPX2017-10-20call235517790.0Order.BTCSPX171020P02355000SPX2017-10-20put2355115.0Order.BTC17905.01.02017-10-02 00:00:00+00:00
18SPX171117C02405000SPX2017-11-17call2405-12740.0Order.STOSPX171117P02405000SPX2017-11-17put2405-730.0Order.STO-13470.01.02017-10-02 00:00:00+00:00
19SPX171117C02405000SPX2017-11-17call240517510.0Order.BTCSPX171117P02405000SPX2017-11-17put2405125.0Order.BTC17635.01.02017-11-01 00:00:00+00:00
20SPX171215C02455000SPX2017-12-15call2455-12650.0Order.STOSPX171215P02455000SPX2017-12-15put2455-820.0Order.STO-13470.01.02017-11-01 00:00:00+00:00
21SPX171215C02455000SPX2017-12-15call245519250.0Order.BTCSPX171215P02455000SPX2017-12-15put2455145.0Order.BTC19395.01.02017-12-01 00:00:00+00:00
22SPX180119C02515000SPX2018-01-19call2515-13990.0Order.STOSPX180119P02515000SPX2018-01-19put2515-1000.0Order.STO-14990.01.02017-12-01 00:00:00+00:00
\n", + "
" + ], + "text/plain": [ + " leg_1 \\\n", + " contract underlying expiration type strike cost order \n", + "0 SPX170217C02150000 SPX 2017-02-17 call 2150 -11480.0 Order.STO \n", + "1 SPX170217C02150000 SPX 2017-02-17 call 2150 13130.0 Order.BTC \n", + "2 SPX170317C02170000 SPX 2017-03-17 call 2170 -11350.0 Order.STO \n", + "3 SPX170317C02170000 SPX 2017-03-17 call 2170 22640.0 Order.BTC \n", + "4 SPX170421C02280000 SPX 2017-04-21 call 2280 -12370.0 Order.STO \n", + "5 SPX170421C02280000 SPX 2017-04-21 call 2280 8490.0 Order.BTC \n", + "6 SPX170519C02245000 SPX 2017-05-19 call 2245 -12060.0 Order.STO \n", + "7 SPX170519C02245000 SPX 2017-05-19 call 2245 14560.0 Order.BTC \n", + "8 SPX170616C02270000 SPX 2017-06-16 call 2270 -12120.0 Order.STO \n", + "9 SPX170616C02270000 SPX 2017-06-16 call 2270 16290.0 Order.BTC \n", + "10 SPX170721C02310000 SPX 2017-07-21 call 2310 -12510.0 Order.STO \n", + "11 SPX170721C02310000 SPX 2017-07-21 call 2310 12170.0 Order.BTC \n", + "12 SPX170818C02310000 SPX 2017-08-18 call 2310 -12370.0 Order.STO \n", + "13 SPX170818C02310000 SPX 2017-08-18 call 2310 16720.0 Order.BTC \n", + "14 SPX170915C02355000 SPX 2017-09-15 call 2355 -12270.0 Order.STO \n", + "15 SPX170915C02355000 SPX 2017-09-15 call 2355 12350.0 Order.BTC \n", + "16 SPX171020C02355000 SPX 2017-10-20 call 2355 -12840.0 Order.STO \n", + "17 SPX171020C02355000 SPX 2017-10-20 call 2355 17790.0 Order.BTC \n", + "18 SPX171117C02405000 SPX 2017-11-17 call 2405 -12740.0 Order.STO \n", + "19 SPX171117C02405000 SPX 2017-11-17 call 2405 17510.0 Order.BTC \n", + "20 SPX171215C02455000 SPX 2017-12-15 call 2455 -12650.0 Order.STO \n", + "21 SPX171215C02455000 SPX 2017-12-15 call 2455 19250.0 Order.BTC \n", + "22 SPX180119C02515000 SPX 2018-01-19 call 2515 -13990.0 Order.STO \n", + "\n", + " leg_2 \\\n", + " contract underlying expiration type strike cost order \n", + "0 SPX170217P02150000 SPX 2017-02-17 put 2150 -1120.0 Order.STO \n", + "1 SPX170217P02150000 SPX 2017-02-17 put 2150 220.0 Order.BTC \n", + "2 SPX170317P02170000 SPX 2017-03-17 put 2170 -930.0 Order.STO \n", + "3 SPX170317P02170000 SPX 2017-03-17 put 2170 100.0 Order.BTC \n", + "4 SPX170421P02280000 SPX 2017-04-21 put 2280 -1030.0 Order.STO \n", + "5 SPX170421P02280000 SPX 2017-04-21 put 2280 360.0 Order.BTC \n", + "6 SPX170519P02245000 SPX 2017-05-19 put 2245 -940.0 Order.STO \n", + "7 SPX170519P02245000 SPX 2017-05-19 put 2245 150.0 Order.BTC \n", + "8 SPX170616P02270000 SPX 2017-06-16 put 2270 -750.0 Order.STO \n", + "9 SPX170616P02270000 SPX 2017-06-16 put 2270 90.0 Order.BTC \n", + "10 SPX170721P02310000 SPX 2017-07-21 put 2310 -810.0 Order.STO \n", + "11 SPX170721P02310000 SPX 2017-07-21 put 2310 265.0 Order.BTC \n", + "12 SPX170818P02310000 SPX 2017-08-18 put 2310 -960.0 Order.STO \n", + "13 SPX170818P02310000 SPX 2017-08-18 put 2310 145.0 Order.BTC \n", + "14 SPX170915P02355000 SPX 2017-09-15 put 2355 -740.0 Order.STO \n", + "15 SPX170915P02355000 SPX 2017-09-15 put 2355 125.0 Order.BTC \n", + "16 SPX171020P02355000 SPX 2017-10-20 put 2355 -970.0 Order.STO \n", + "17 SPX171020P02355000 SPX 2017-10-20 put 2355 115.0 Order.BTC \n", + "18 SPX171117P02405000 SPX 2017-11-17 put 2405 -730.0 Order.STO \n", + "19 SPX171117P02405000 SPX 2017-11-17 put 2405 125.0 Order.BTC \n", + "20 SPX171215P02455000 SPX 2017-12-15 put 2455 -820.0 Order.STO \n", + "21 SPX171215P02455000 SPX 2017-12-15 put 2455 145.0 Order.BTC \n", + "22 SPX180119P02515000 SPX 2018-01-19 put 2515 -1000.0 Order.STO \n", + "\n", + " totals \n", + " cost qty date \n", + "0 -12600.0 1.0 2017-01-03 00:00:00+00:00 \n", + "1 13350.0 1.0 2017-02-01 00:00:00+00:00 \n", + "2 -12280.0 1.0 2017-02-01 00:00:00+00:00 \n", + "3 22740.0 1.0 2017-03-01 00:00:00+00:00 \n", + "4 -13400.0 1.0 2017-03-01 00:00:00+00:00 \n", + "5 8850.0 1.0 2017-04-03 00:00:00+00:00 \n", + "6 -13000.0 1.0 2017-04-03 00:00:00+00:00 \n", + "7 14710.0 1.0 2017-05-01 00:00:00+00:00 \n", + "8 -12870.0 1.0 2017-05-01 00:00:00+00:00 \n", + "9 16380.0 1.0 2017-06-01 00:00:00+00:00 \n", + "10 -13320.0 1.0 2017-06-01 00:00:00+00:00 \n", + "11 12435.0 1.0 2017-07-03 00:00:00+00:00 \n", + "12 -13330.0 1.0 2017-07-03 00:00:00+00:00 \n", + "13 16865.0 1.0 2017-08-01 00:00:00+00:00 \n", + "14 -13010.0 1.0 2017-08-01 00:00:00+00:00 \n", + "15 12475.0 1.0 2017-09-01 00:00:00+00:00 \n", + "16 -13810.0 1.0 2017-09-01 00:00:00+00:00 \n", + "17 17905.0 1.0 2017-10-02 00:00:00+00:00 \n", + "18 -13470.0 1.0 2017-10-02 00:00:00+00:00 \n", + "19 17635.0 1.0 2017-11-01 00:00:00+00:00 \n", + "20 -13470.0 1.0 2017-11-01 00:00:00+00:00 \n", + "21 19395.0 1.0 2017-12-01 00:00:00+00:00 \n", + "22 -14990.0 1.0 2017-12-01 00:00:00+00:00 " + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bt.run(rebalance_freq=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
total capitalcashcalls capitaloptions capitaloptions qtyputs capitalstocks capitalstocks qty% changeaccumulated return
2017-01-02 00:00:00+00:001.000000e+061000000.000000NaNNaNNaNNaNNaNNaNNaNNaN
2017-01-03 00:00:00+00:009.767215e+0522858.490787-11800.0-13020.01.0-1220.09.897415e+0535248.0-0.0232780.976722
2017-01-04 00:00:00+00:009.814920e+0522858.490787-12640.0-13530.01.0-890.09.950220e+0535248.00.0048840.981492
2017-01-05 00:00:00+00:009.905463e+0522858.490787-12530.0-13390.01.0-860.01.003936e+0635248.00.0092250.990546
2017-01-06 00:00:00+00:009.847099e+0522858.490787-13350.0-14040.01.0-690.09.987499e+0535248.0-0.0058920.984710
.................................
2017-12-22 00:00:00+00:001.079712e+0626263.716990-17740.0-18050.01.0-310.01.097762e+0634825.00.0037911.079712
2017-12-26 00:00:00+00:001.082893e+0626263.716990-17700.0-17975.01.0-275.01.100868e+0634825.00.0029471.082893
2017-12-27 00:00:00+00:001.088539e+0626263.716990-17700.0-18010.01.0-310.01.106549e+0634825.00.0052141.088539
2017-12-28 00:00:00+00:001.093571e+0626263.716990-17700.0-17955.01.0-255.01.111526e+0634825.00.0046231.093571
2017-12-29 00:00:00+00:001.096880e+0626263.716990-16200.0-16510.01.0-310.01.113390e+0634825.00.0030251.096880
\n", + "

252 rows × 10 columns

\n", + "
" + ], + "text/plain": [ + " total capital cash calls capital \\\n", + "2017-01-02 00:00:00+00:00 1.000000e+06 1000000.000000 NaN \n", + "2017-01-03 00:00:00+00:00 9.767215e+05 22858.490787 -11800.0 \n", + "2017-01-04 00:00:00+00:00 9.814920e+05 22858.490787 -12640.0 \n", + "2017-01-05 00:00:00+00:00 9.905463e+05 22858.490787 -12530.0 \n", + "2017-01-06 00:00:00+00:00 9.847099e+05 22858.490787 -13350.0 \n", + "... ... ... ... \n", + "2017-12-22 00:00:00+00:00 1.079712e+06 26263.716990 -17740.0 \n", + "2017-12-26 00:00:00+00:00 1.082893e+06 26263.716990 -17700.0 \n", + "2017-12-27 00:00:00+00:00 1.088539e+06 26263.716990 -17700.0 \n", + "2017-12-28 00:00:00+00:00 1.093571e+06 26263.716990 -17700.0 \n", + "2017-12-29 00:00:00+00:00 1.096880e+06 26263.716990 -16200.0 \n", + "\n", + " options capital options qty puts capital \\\n", + "2017-01-02 00:00:00+00:00 NaN NaN NaN \n", + "2017-01-03 00:00:00+00:00 -13020.0 1.0 -1220.0 \n", + "2017-01-04 00:00:00+00:00 -13530.0 1.0 -890.0 \n", + "2017-01-05 00:00:00+00:00 -13390.0 1.0 -860.0 \n", + "2017-01-06 00:00:00+00:00 -14040.0 1.0 -690.0 \n", + "... ... ... ... \n", + "2017-12-22 00:00:00+00:00 -18050.0 1.0 -310.0 \n", + "2017-12-26 00:00:00+00:00 -17975.0 1.0 -275.0 \n", + "2017-12-27 00:00:00+00:00 -18010.0 1.0 -310.0 \n", + "2017-12-28 00:00:00+00:00 -17955.0 1.0 -255.0 \n", + "2017-12-29 00:00:00+00:00 -16510.0 1.0 -310.0 \n", + "\n", + " stocks capital stocks qty % change \\\n", + "2017-01-02 00:00:00+00:00 NaN NaN NaN \n", + "2017-01-03 00:00:00+00:00 9.897415e+05 35248.0 -0.023278 \n", + "2017-01-04 00:00:00+00:00 9.950220e+05 35248.0 0.004884 \n", + "2017-01-05 00:00:00+00:00 1.003936e+06 35248.0 0.009225 \n", + "2017-01-06 00:00:00+00:00 9.987499e+05 35248.0 -0.005892 \n", + "... ... ... ... \n", + "2017-12-22 00:00:00+00:00 1.097762e+06 34825.0 0.003791 \n", + "2017-12-26 00:00:00+00:00 1.100868e+06 34825.0 0.002947 \n", + "2017-12-27 00:00:00+00:00 1.106549e+06 34825.0 0.005214 \n", + "2017-12-28 00:00:00+00:00 1.111526e+06 34825.0 0.004623 \n", + "2017-12-29 00:00:00+00:00 1.113390e+06 34825.0 0.003025 \n", + "\n", + " accumulated return \n", + "2017-01-02 00:00:00+00:00 NaN \n", + "2017-01-03 00:00:00+00:00 0.976722 \n", + "2017-01-04 00:00:00+00:00 0.981492 \n", + "2017-01-05 00:00:00+00:00 0.990546 \n", + "2017-01-06 00:00:00+00:00 0.984710 \n", + "... ... \n", + "2017-12-22 00:00:00+00:00 1.079712 \n", + "2017-12-26 00:00:00+00:00 1.082893 \n", + "2017-12-27 00:00:00+00:00 1.088539 \n", + "2017-12-28 00:00:00+00:00 1.093571 \n", + "2017-12-29 00:00:00+00:00 1.096880 \n", + "\n", + "[252 rows x 10 columns]" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bt.balance" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "bt.balance['total capital'].plot();" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/backtester/strategy/strategy.py b/backtester/strategy/strategy.py index 77950cc..3c0db3f 100644 --- a/backtester/strategy/strategy.py +++ b/backtester/strategy/strategy.py @@ -17,11 +17,10 @@ class Strategy: Takes in a number of `StrategyLeg`'s (option contracts), and filters that determine entry and exit conditions. """ - def __init__(self, schema, shares_per_contract=100, initial_capital=1_000_000): + def __init__(self, schema, shares_per_contract=100): assert isinstance(schema, Schema) self.schema = schema self._shares_per_contract = shares_per_contract - self.initial_capital = initial_capital self.legs = [] self.conditions = [] self.exit_thresholds = (math.inf, math.inf) @@ -72,7 +71,7 @@ class Strategy: assert loss_pct >= 0 self.exit_thresholds = (profit_pct, loss_pct) - def filter_entries(self, options, inventory, date): + def filter_entries(self, options, inventory, date, capital): """Returns the entry signals chosen by the strategy for the given (daily) options. @@ -87,7 +86,7 @@ class Strategy: inventory_contracts = pd.concat([inventory[leg.name]['contract'] for leg in self.legs]) subset_options = options[~options[self.schema['contract']].isin(inventory_contracts)] - return self._filter_legs(subset_options, Signal.ENTRY, date) + return self._filter_legs(subset_options, Signal.ENTRY, date, capital) def filter_exits(self, options, inventory, date): """Returns the exit signals chosen by the strategy for the given @@ -142,10 +141,10 @@ class Strategy: return (exits, exits_mask, total_costs) - def _filter_legs(self, options, signal, date): + def _filter_legs(self, options, signal, date, capital): """Returns a hierarchically indexed `pd.DataFrame` containing signals for each leg in the strategy. - +s Args: options (pd.DataFrame): DataFrame of (daily) options signal (Signal): Either `Signal.ENTRY` or `Signal.EXIT` @@ -179,7 +178,7 @@ class Strategy: dfs.append(subset_df.reset_index(drop=True)) - return self._apply_conditions(dfs, date) + return self._apply_conditions(dfs, date, capital) def _signal_fields(self, cost_field): fields = { @@ -194,7 +193,7 @@ class Strategy: return fields - def _apply_conditions(self, dfs, date): + def _apply_conditions(self, dfs, date, capital): """Applies conditions on the specified legs.""" for condition in self.conditions: @@ -215,7 +214,7 @@ class Strategy: cost = sum(leg["cost"] for leg in dfs) # Put qty of contracts to buy/sell in ['totals']['qty'] - qty = self.initial_capital // cost + qty = capital // cost qty = np.abs(qty) totals = pd.DataFrame.from_dict({"cost": cost, "qty": qty, "date": date}) totals.columns = pd.MultiIndex.from_product([["totals"], totals.columns])