mirror of
https://github.com/wassname/options_backtester.git
synced 2026-07-19 11:26:05 +08:00
Added calculation of total costs for signals. Moved check of 0 priced options to StrategyLeg
This commit is contained in:
+28
-33
@@ -1,3 +1,6 @@
|
||||
from functools import reduce
|
||||
from operator import add
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from .strategy import Strategy
|
||||
@@ -50,7 +53,26 @@ class Backtest:
|
||||
|
||||
return self.trade_log
|
||||
|
||||
def _execute_entry(self, date, orders, entry_signals):
|
||||
def _execute_exit(self, date, exit_signals):
|
||||
"""Executes exits and updates `self.inventory` and `self.trade_log`"""
|
||||
remove_set = set()
|
||||
|
||||
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]
|
||||
order = row["order"].values[0]
|
||||
profit = price * qty * self.shares_per_contract
|
||||
profit *= 1 if order == Order.STC.name else -1
|
||||
self.capital += profit
|
||||
self._update_trade_log(date, contract, order, qty, profit)
|
||||
remove_set.add((contract, leg, qty, expiration))
|
||||
elif expiration <= date:
|
||||
remove_set.add((contract, leg, qty, expiration))
|
||||
|
||||
self._inventory.difference_update(remove_set)
|
||||
|
||||
def _execute_entry(self, date, entry_signals):
|
||||
"""Executes entry orders and updates `self.inventory` and `self.trade_log`"""
|
||||
|
||||
orders = self._process_entry_signals(entry_signals)
|
||||
@@ -73,39 +95,12 @@ class Backtest:
|
||||
"""Returns a dictionary containing the orders to execute."""
|
||||
# Pass `qty` of contracts to buy/sell to `Backtest.__init__`
|
||||
|
||||
orders = {}
|
||||
|
||||
if not entry_signals.empty:
|
||||
for leg in entry_signals.legs:
|
||||
leg_signals = entry_signals[leg]
|
||||
# Filter out zero priced options
|
||||
leg_signals = leg_signals.query("price > 0.0")
|
||||
if leg_signals.empty:
|
||||
return {}
|
||||
if (leg_signals["order"] == Order.BTO.name).any():
|
||||
orders[leg] = (leg_signals["price"].idxmin(), 1)
|
||||
else:
|
||||
orders[leg] = (leg_signals["price"].idxmax(), 1)
|
||||
return orders
|
||||
|
||||
def _execute_exit(self, date, exit_signals):
|
||||
"""Executes exits and updates `self.inventory` and `self.trade_log`"""
|
||||
remove_set = set()
|
||||
|
||||
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]
|
||||
order = row["order"].values[0]
|
||||
profit = price * qty * self.shares_per_contract
|
||||
profit *= 1 if order == Order.STC.name else -1
|
||||
self.capital += profit
|
||||
self._update_trade_log(date, contract, order, qty, profit)
|
||||
remove_set.add((contract, leg, qty, expiration))
|
||||
elif expiration <= date:
|
||||
remove_set.add((contract, leg, qty, expiration))
|
||||
|
||||
self._inventory.difference_update(remove_set)
|
||||
legs = entry_signals.columns.levels[0]
|
||||
costs = reduce(add, (entry_signals[leg]["cost"] for leg in legs))
|
||||
return entry_signals.loc[costs.idxmin()]
|
||||
else:
|
||||
return entry_signals
|
||||
|
||||
def _update_trade_log(self, date, contract, order, qty, profit):
|
||||
"""Adds entry for the given order to `self.trade_log`."""
|
||||
|
||||
@@ -3,6 +3,7 @@ from collections import namedtuple
|
||||
import pandas as pd
|
||||
|
||||
from backtester.datahandler import Schema
|
||||
from backtester.option import Direction
|
||||
from .strategy_leg import StrategyLeg
|
||||
from .signal import Signal, get_order
|
||||
|
||||
@@ -78,7 +79,7 @@ class Strategy:
|
||||
|
||||
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]
|
||||
# entry_df.legs = exit_df.legs = exit_df.columns.levels[0]
|
||||
|
||||
yield (date, entry_df, exit_df)
|
||||
|
||||
@@ -88,13 +89,13 @@ class Strategy:
|
||||
"""
|
||||
schema = self.schema
|
||||
dfs = []
|
||||
for number, leg in enumerate(self.legs, start=1):
|
||||
for leg in self.legs:
|
||||
if signal == Signal.ENTRY:
|
||||
flt = leg.entry_filter
|
||||
price = leg.direction.value
|
||||
cost = leg.direction.value
|
||||
else:
|
||||
flt = leg.exit_filter
|
||||
price = (~leg.direction).value
|
||||
cost = (~leg.direction).value
|
||||
|
||||
df = flt(data)
|
||||
fields = {
|
||||
@@ -103,13 +104,18 @@ class Strategy:
|
||||
schema["expiration"]: "expiration",
|
||||
schema["type"]: "type",
|
||||
schema["strike"]: "strike",
|
||||
schema[price]: "price"
|
||||
schema[cost]: "cost"
|
||||
}
|
||||
subset_df = df.loc[:, fields.keys()]
|
||||
subset_df.rename(columns=fields, inplace=True)
|
||||
|
||||
order = get_order(leg.direction, signal)
|
||||
subset_df["order"] = order.name
|
||||
|
||||
# Change sign of cost for SELL orders
|
||||
if leg.direction == Direction.SELL:
|
||||
subset_df["cost"] = -subset_df["cost"]
|
||||
|
||||
dfs.append(subset_df.reset_index(drop=True))
|
||||
|
||||
return self._apply_conditions(dfs)
|
||||
|
||||
@@ -13,8 +13,9 @@ class StrategyLeg:
|
||||
self.schema = schema
|
||||
self.type = option_type
|
||||
self.direction = direction
|
||||
self._entry_filter = self.schema.type == self.type.value
|
||||
self._exit_filter = self.schema.type == self.type.value
|
||||
|
||||
self._entry_filter = self._base_entry_filter()
|
||||
self._exit_filter = self._base_exit_filter()
|
||||
|
||||
@property
|
||||
def entry_filter(self):
|
||||
@@ -24,7 +25,7 @@ class StrategyLeg:
|
||||
@entry_filter.setter
|
||||
def entry_filter(self, flt):
|
||||
"""Sets the entry filter"""
|
||||
self._entry_filter = (self.schema.type == self.type.value) & flt
|
||||
self._entry_filter = self._base_entry_filter() & flt
|
||||
|
||||
@property
|
||||
def exit_filter(self):
|
||||
@@ -34,7 +35,18 @@ class StrategyLeg:
|
||||
@exit_filter.setter
|
||||
def exit_filter(self, flt):
|
||||
"""Sets the exit filter"""
|
||||
self._exit_filter = (self.schema.type == self.type.value) & flt
|
||||
self._exit_filter = self._base_exit_filter() & flt
|
||||
|
||||
def _base_entry_filter(self):
|
||||
if self.direction == Direction.BUY:
|
||||
return (self.schema.type == self.type.value) & (self.schema.ask >
|
||||
0)
|
||||
else:
|
||||
return (self.schema.type == self.type.value) & (self.schema.bid >
|
||||
0)
|
||||
|
||||
def _base_exit_filter(self):
|
||||
return self.schema.type == self.type.value
|
||||
|
||||
def __repr__(self):
|
||||
return "StrategyLeg(type={}, direction={}, entry_filter={}, exit_filter={})".format(
|
||||
|
||||
Reference in New Issue
Block a user