Changed inventory to a dataframe and added exit filter by price

This commit is contained in:
Javier Rodríguez Chatruc
2019-11-28 10:43:27 -03:00
parent c60ddb0e99
commit 182f24d874
2 changed files with 70 additions and 14 deletions
+41 -9
View File
@@ -16,7 +16,7 @@ class Backtest:
self.shares_per_contract = shares_per_contract
self._strategy = None
self._data = None
self._inventory = set()
self.inventory = pd.DataFrame()
@property
def strategy(self):
@@ -47,9 +47,11 @@ class Backtest:
columns=["date", "contract", "order", "qty", "profit", "capital"])
for date, entry_signals, exit_signals in self._strategy.signals(
self._data):
self._execute_exit(date, exit_signals)
self._execute_entry(date, entry_signals)
self._data, self):
# self._execute_exit(date, exit_signals)
# self._execute_entry(date, entry_signals)
self._execute_exit_new(date, exit_signals)
self._execute_entry_new(date, entry_signals)
return self.trade_log
@@ -57,7 +59,7 @@ class Backtest:
"""Executes exits and updates `self.inventory` and `self.trade_log`"""
remove_set = set()
for contract, leg, qty, expiration in self._inventory:
for contract, leg, qty, expiration in self.inventory:
if contract in exit_signals[leg]["contract"].values:
row = exit_signals[leg].query("contract == @contract")
price = row["price"].values[0]
@@ -70,7 +72,7 @@ class Backtest:
elif expiration <= date:
remove_set.add((contract, leg, qty, expiration))
self._inventory.difference_update(remove_set)
self.inventory.difference_update(remove_set)
def _execute_entry(self, date, entry_signals):
"""Executes entry orders and updates `self.inventory` and `self.trade_log`"""
@@ -87,10 +89,40 @@ class Backtest:
cost *= -1 if order == Order.STO.name else 1
if self.capital >= cost:
self.capital -= cost
self._inventory.add((contract, leg, qty, expiration))
self.inventory.add((contract, leg, qty, expiration))
self.strategy.register_entry(contract, price)
self._update_trade_log(date, contract, order, qty, -cost)
def _execute_entry_new(self, date, entry_signals):
"""Executes entry orders and updates `self.inventory` and `self.trade_log`"""
if entry_signals.empty:
return
entry, total_price = self._process_entry_signals(entry_signals)
cost = total_price * 1 * self.shares_per_contract
if self.capital >= cost:
self.capital -= total_price
self.inventory = self.inventory.append(entry, ignore_index=True)
legs = entry_signals.columns.levels[0]
for leg in legs:
row = entry[leg]
contract = row["contract"]
order = row["order"]
price = row["cost"]
self._update_trade_log(date, contract, order, 1, -price)
def _execute_exit_new(self, date, exit_signals):
"""Executes exits and updates `self.inventory` and `self.trade_log`"""
for contracts, price in exit_signals:
profit = price * 1 * self.shares_per_contract
for contract, order in contracts:
self._update_trade_log(date, contract, order, 1, profit)
self.capital += profit
legs = exit_signals.columns.levels[0]
for leg in legs:
self.inventory = self.inventory.drop(self.inventory[
self.inventory[leg]['optionroot'] == contract].index)
def _process_entry_signals(self, entry_signals):
"""Returns a dictionary containing the orders to execute."""
# Pass `qty` of contracts to buy/sell to `Backtest.__init__`
@@ -98,9 +130,9 @@ class Backtest:
if not entry_signals.empty:
legs = entry_signals.columns.levels[0]
costs = reduce(add, (entry_signals[leg]["cost"] for leg in legs))
return entry_signals.loc[costs.idxmin()]
return entry_signals.loc[costs.idxmin()], costs.min()
else:
return entry_signals
return entry_signals, 0
def _update_trade_log(self, date, contract, order, qty, profit):
"""Adds entry for the given order to `self.trade_log`."""
+29 -5
View File
@@ -63,7 +63,7 @@ class Strategy:
given profit/loss levels"""
self.entries.add(contract)
def signals(self, data):
def signals(self, data, bt):
"""Iterates over `data` and yields a tuple of
`(date, entry_signals, exit_signals)` for each time step.
"""
@@ -77,10 +77,10 @@ class Strategy:
else:
entry_df = pd.concat(entry_legs, axis=1)
exit_legs = self._filter_legs(group, signal=Signal.EXIT)
exit_df = pd.concat(exit_legs, axis=1)
# entry_df.legs = exit_df.legs = exit_df.columns.levels[0]
# exit_legs = self._filter_legs(group, signal=Signal.EXIT)
# exit_df = pd.concat(exit_legs, axis=1)
exit_df = self._filter_exits(data, bt.inventory,
['leg_1', 'leg_2'])
yield (date, entry_df, exit_df)
def _filter_legs(self, data, signal=Signal.ENTRY):
@@ -120,6 +120,30 @@ class Strategy:
return self._apply_conditions(dfs)
def _filter_exits(self, data, inventory, legs):
exits = []
for index, row in inventory.iterrows():
old_price = 0
current_price = 0
contracts = set()
for leg in legs:
contract = row[leg]['contract']
order = get_order(~leg.direction, Signal.EXIT)
contracts.add((contract, order))
old_price += row[leg]['price']
option = data[data['optionroot'] == contract]
if order[0] == 'B':
current_price -= option['ask']
else:
current_price += option['bid']
if (current_price <= 0.8 * old_price) & (current_price >=
1.2 * old_price):
exits.append((contracts, current_price))
else:
# Filter the data according to the exit filters and append to exits the contracts that need to exit
pass
return exits
def _apply_conditions(self, dfs):
"""Applies conditions on the specified legs."""