mirror of
https://github.com/wassname/options_backtester.git
synced 2026-08-17 11:23:12 +08:00
Changed inventory to a dataframe and added exit filter by price
This commit is contained in:
@@ -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`."""
|
||||
|
||||
@@ -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."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user