diff --git a/backtester/backtester.py b/backtester/backtester.py index 24e7725..deab0c4 100644 --- a/backtester/backtester.py +++ b/backtester/backtester.py @@ -1,4 +1,5 @@ import pandas as pd +import numpy as np import pyprind from .strategy import Strategy @@ -78,8 +79,6 @@ class Backtest: def _execute_exit(self, exit_signals): """Executes exits and updates `self.inventory` and `self.trade_log`""" - if exit_signals is None: - return exits, exits_mask, total_costs = exit_signals self.trade_log = self.trade_log.append(exits, ignore_index=True) @@ -98,24 +97,51 @@ class Backtest: def summary(self): df = self.trade_log + df.loc[:, ('totals', 'capital')] = (-df['totals']['cost']).cumsum() + self.initial_capital + df.loc[:, ('totals', 'return')] = (df['totals']['capital'].pct_change() * 100) entries_mask = df.apply(lambda row: row['leg_1']['order'][2] == 'O', axis=1) entries = df.loc[entries_mask] exits = df.loc[~entries_mask] - trades = entries.merge(exits, - on=[(l.name, 'contract') for l in self._strategy.legs], - suffixes=['_entry', '_exit']) - costs = trades.apply(lambda row: row['totals_entry']['cost'] + row['totals_exit']['cost'], axis=1) - wins_mask = costs < 0 - total_trades = len(trades) - win_number = sum(wins_mask) + costs = np.array([]) + returns = np.array([]) + for contract in entries['leg_1']['contract']: + entry = entries.loc[entries['leg_1']['contract'] == contract] + exit_ = exits.loc[exits['leg_1']['contract'] == contract] + try: + # Here we assume we are entering only once per contract (i.e both entry and exit_ have only one row) + costs = np.append(costs, entry['totals']['cost'].values[0] + exit_['totals']['cost'].values[0]) + returns = np.append(returns, exit_['totals']['return']) + except IndexError: + continue + + # trades = entries.merge(exits, + # on=[(l.name, 'contract') for l in self._strategy.legs], + # suffixes=['_entry', '_exit']) + + # costs = trades.apply(lambda row: row['totals_entry']['cost'] + row['totals_exit']['cost'], axis=1) + + wins = costs < 0 + losses = costs >= 0 + profit_factor = np.sum(wins) / np.sum(losses) + total_trades = len(exits) + win_number = np.sum(wins) loss_number = total_trades - win_number win_pct = win_number / total_trades - largest_loss = costs.max() + largest_loss = np.max(costs) + avg_profit = np.sum(-costs) / len(costs) + profit_loss = returns + avg_pl = np.mean(profit_loss) + total_pl = np.sum(profit_loss) - data = [total_trades, win_number, loss_number, win_pct, largest_loss] - stats = ['Total trades', 'Number of wins', 'Number of losses', 'Win %', 'Largest loss'] + data = [ + total_trades, win_number, loss_number, win_pct, largest_loss, profit_factor, avg_profit, avg_pl, total_pl + ] + stats = [ + 'Total trades', 'Number of wins', 'Number of losses', 'Win %', 'Largest loss', 'Profit factor', + 'Average profit', 'Average P&L %', 'Total P&L %' + ] strat = ['Strategy'] summary = pd.DataFrame(data, stats, strat) return summary diff --git a/backtester/demos/backtester_demo.ipynb b/backtester/demos/backtester_demo.ipynb index f162946..9c596ce 100644 --- a/backtester/demos/backtester_demo.ipynb +++ b/backtester/demos/backtester_demo.ipynb @@ -2206,7 +2206,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.7.5" } }, "nbformat": 4, diff --git a/backtester/strategy/strategy.py b/backtester/strategy/strategy.py index 9dba892..dfbb80c 100644 --- a/backtester/strategy/strategy.py +++ b/backtester/strategy/strategy.py @@ -18,7 +18,6 @@ class Strategy: Takes in a number of `StrategyLeg`'s (option contracts), and filters that determine entry and exit conditions. """ - def __init__(self, schema, qty=1, shares_per_contract=100): assert isinstance(schema, Schema) self.schema = schema diff --git a/data_cleanup.ipynb b/data_cleanup.ipynb index d9da285..a11f7c5 100644 --- a/data_cleanup.ipynb +++ b/data_cleanup.ipynb @@ -2630,7 +2630,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.3" + "version": "3.7.5" } }, "nbformat": 4,