Added calculation of total costs for signals. Moved check of 0 priced options to StrategyLeg

This commit is contained in:
Juan Pablo Amoroso
2019-11-27 17:59:02 -03:00
parent 0b0fb7e87e
commit c60ddb0e99
3 changed files with 55 additions and 42 deletions
+28 -33
View File
@@ -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`."""
+11 -5
View File
@@ -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)
+16 -4
View File
@@ -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(