mirror of
https://github.com/wassname/options_backtester.git
synced 2026-06-27 19:31:36 +08:00
Added strangle and option for reading csv
This commit is contained in:
@@ -1,17 +1,26 @@
|
||||
import pandas as pd
|
||||
import os
|
||||
from .schema import Schema
|
||||
|
||||
|
||||
class HistoricalOptionsData:
|
||||
"""Historical Options Data container class."""
|
||||
|
||||
def __init__(self, file, schema=None, **params):
|
||||
if schema:
|
||||
assert isinstance(schema, Schema)
|
||||
else:
|
||||
self.schema = HistoricalOptionsData.default_schema()
|
||||
|
||||
self._data = pd.read_hdf(file, **params)
|
||||
file_extension = os.path.splitext(file)[1]
|
||||
|
||||
if file_extension == '.h5':
|
||||
self._data = pd.read_hdf(file, **params)
|
||||
elif file_extension == '.csv':
|
||||
params["parse_dates"] = [
|
||||
self.schema.expiration.mapping, self.schema.date.mapping
|
||||
]
|
||||
self._data = pd.read_csv(file, **params)
|
||||
|
||||
columns = self._data.columns
|
||||
assert all((col in columns for _key, col in self.schema))
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ class Schema:
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._mappings[key] = value
|
||||
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Returns mapping of given `key`"""
|
||||
return self._mappings[key]
|
||||
@@ -45,6 +45,9 @@ class Schema:
|
||||
return "Schema({})".format(
|
||||
[Field(k, m) for k, m in self._mappings.items()])
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._mappings == other._mappings
|
||||
|
||||
|
||||
class Field:
|
||||
"""Encapsulates data fields to build filters used by strategies"""
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
from .strategy import Strategy
|
||||
from .strategy_leg import StrategyLeg
|
||||
from .straddle import Straddle
|
||||
from .strangle import Strangle
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import pandas as pd
|
||||
|
||||
|
||||
class Strangle:
|
||||
def __init__(self,
|
||||
underlying,
|
||||
strike,
|
||||
dte,
|
||||
strike_diff,
|
||||
shares_per_contract=100,
|
||||
capital=1000000.0):
|
||||
self.underlying = underlying
|
||||
self.strike = strike
|
||||
self.dte = dte
|
||||
self.strike_diff = strike_diff
|
||||
self.inventory = set()
|
||||
self.shares_per_contract = shares_per_contract
|
||||
self.capital = capital
|
||||
|
||||
def execute_entry(self, date, group):
|
||||
calls = group.loc[(group.type == 'call')
|
||||
& (group.strike >= self.strike[0]) &
|
||||
(group.strike <= self.strike[1]) &
|
||||
(group.dte >= self.dte[0])
|
||||
& (group.dte <= self.dte[1])]
|
||||
puts = group.loc[group.type == 'put']
|
||||
merge = calls.merge(puts, on=['dte'], suffixes=('_call', '_put'))
|
||||
merge['ask_sum'] = merge['ask_call'] + merge['ask_put']
|
||||
merge['strike_diff'] = abs(merge['strike_call'] - merge['strike_put'])
|
||||
merge_strangle = merge.loc[merge['strike_diff'] <= self.strike_diff]
|
||||
if merge_strangle.empty:
|
||||
return
|
||||
entry_index = merge_strangle['ask_sum'].idxmin()
|
||||
entry = merge_strangle.loc[entry_index]
|
||||
cost = sum([entry['ask_sum'] * self.shares_per_contract])
|
||||
if cost <= self.capital:
|
||||
self.capital -= cost
|
||||
self.inventory.add((entry.optionroot_call, entry.dte))
|
||||
self.inventory.add((entry.optionroot_put, entry.dte))
|
||||
self._update_trade_log(date, entry.optionroot_call,
|
||||
entry.type_call,
|
||||
-entry.ask_call * self.shares_per_contract)
|
||||
self._update_trade_log(date, entry.optionroot_put, entry.type_put,
|
||||
-entry.ask_put * self.shares_per_contract)
|
||||
|
||||
def execute_exits(self, inventory, date, group):
|
||||
exits = []
|
||||
remove_set = set()
|
||||
for entry in inventory:
|
||||
exit = group.loc[(group.optionroot == entry[0]) & (group.dte == 1)]
|
||||
if not exit.empty:
|
||||
exits.append(exit)
|
||||
remove_set.add(entry)
|
||||
for exit in exits:
|
||||
profit = exit.bid.values[0] * self.shares_per_contract
|
||||
contract = exit.optionroot.values[0]
|
||||
type_ = exit.type.values[0]
|
||||
self.capital += profit
|
||||
self._update_trade_log(date, contract, type_, profit)
|
||||
self.inventory.difference_update(remove_set)
|
||||
|
||||
def run(self, data):
|
||||
self.trade_log = pd.DataFrame(
|
||||
columns=["date", "contract", "type", "profit", "capital"])
|
||||
|
||||
for date, group in self._iter_dates(data):
|
||||
self.execute_entry(date, group)
|
||||
self.execute_exits(self.inventory, date, group)
|
||||
|
||||
return self.trade_log
|
||||
|
||||
def _update_trade_log(self, date, contract, type_, profit):
|
||||
"""Adds entry for the given order to `self.trade_log`."""
|
||||
self.trade_log.loc[len(
|
||||
self.trade_log)] = [date, contract, type_, profit, self.capital]
|
||||
|
||||
def _iter_dates(self, data):
|
||||
"""Returns `pd.DataFrameGroupBy` with the given underlying and with contracts grouped by date"""
|
||||
df = data._data.loc[data._data.underlying == self.underlying]
|
||||
return df.groupby(data.schema["date"])
|
||||
@@ -10,7 +10,6 @@ class Strategy:
|
||||
Takes in a number of `legs` (option contracts), and filters that determine
|
||||
entry and exit conditions.
|
||||
"""
|
||||
|
||||
def __init__(self, schema):
|
||||
assert isinstance(schema, Schema)
|
||||
self.schema = schema
|
||||
|
||||
Reference in New Issue
Block a user