mirror of
https://github.com/wassname/options_backtester.git
synced 2026-08-07 11:25:39 +08:00
First working version of backtester. See notebook demo in /backtester/demos
This commit is contained in:
@@ -1 +1 @@
|
||||
from .backtester import *
|
||||
from .backtester import Backtest
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import argparse
|
||||
import os
|
||||
import logging
|
||||
from .backtester import run
|
||||
from .utils import get_data_dir
|
||||
|
||||
parser = argparse.ArgumentParser(prog="backtester.py")
|
||||
parser.add_argument(
|
||||
"-t", "--symbols", nargs="+", help="Symbols to fetch", required=True)
|
||||
parser.add_argument("-s", "--scraper", choices=["cboe"])
|
||||
args = parser.parse_args()
|
||||
|
||||
data_dir = get_data_dir()
|
||||
spx_data = os.path.join(data_dir, "SPX_2008-2018.csv")
|
||||
run(spx_data)
|
||||
+108
-38
@@ -1,45 +1,115 @@
|
||||
"""Event based backtester"""
|
||||
import pandas as pd
|
||||
|
||||
from queue import Queue
|
||||
from .datahandler import BalancedDataHandler
|
||||
from .strategy import Balanced
|
||||
from .portfolio import BalancedPortfolio
|
||||
from .strategy import Strategy
|
||||
from .strategy.signal import Order
|
||||
from .datahandler import HistoricalOptionsData
|
||||
|
||||
|
||||
def run(data_path,
|
||||
data_handler=BalancedDataHandler,
|
||||
port_class=BalancedPortfolio,
|
||||
strat_class=Balanced,
|
||||
**strat_args):
|
||||
events = Queue()
|
||||
bars = data_handler(data_path, events)
|
||||
class Backtest:
|
||||
"""Processes signals from the Strategy object"""
|
||||
|
||||
weights = {
|
||||
"VOO": 0.3,
|
||||
"GLD": 0.1,
|
||||
"VNQ": 0.05,
|
||||
"VNQI": 0.05,
|
||||
"TLT": 0.2,
|
||||
"TIP": 0.1,
|
||||
"BNDX": 0.1,
|
||||
"RJI": 0.1
|
||||
}
|
||||
port = port_class(bars, events, weights=weights)
|
||||
strat = strat_class(bars, events, **strat_args)
|
||||
def __init__(self, capital=1_000_000, shares_per_contract=100):
|
||||
self.capital = capital
|
||||
self.shares_per_contract = shares_per_contract
|
||||
self._strategy = None
|
||||
self._data = None
|
||||
self._inventory = set()
|
||||
|
||||
while True:
|
||||
bars.update_bars()
|
||||
if not bars.continue_backtest:
|
||||
break
|
||||
@property
|
||||
def strategy(self):
|
||||
return self._strategy
|
||||
|
||||
while True:
|
||||
if events.empty():
|
||||
break
|
||||
event = events.get()
|
||||
if event.type == "MARKET":
|
||||
strat.generate_signals(event)
|
||||
port.update_timeindex(event)
|
||||
elif event.type == "SIGNAL":
|
||||
port.update_signal(event)
|
||||
@strategy.setter
|
||||
def strategy(self, strat):
|
||||
assert isinstance(strat, Strategy)
|
||||
self._strategy = strat
|
||||
return self
|
||||
|
||||
return port
|
||||
@property
|
||||
def data(self):
|
||||
return self._data
|
||||
|
||||
@data.setter
|
||||
def data(self, data):
|
||||
assert isinstance(data, HistoricalOptionsData)
|
||||
self._data = data
|
||||
return self
|
||||
|
||||
def run(self):
|
||||
"""Runs the backtest and returns a `pd.DataFrame` of the orders executed."""
|
||||
assert self._data is not None
|
||||
assert self._strategy is not None
|
||||
|
||||
self.trade_log = pd.DataFrame(
|
||||
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)
|
||||
entry_orders = self.process_entry_signals(entry_signals)
|
||||
self._execute_entry(date, entry_orders, entry_signals)
|
||||
|
||||
return self.trade_log
|
||||
|
||||
def process_entry_signals(self, entry_signals):
|
||||
"""Returns the a dictionary containing the orders to execute."""
|
||||
# TODO: Move this logic to Strategy.
|
||||
# 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_entry(self, date, orders, entry_signals):
|
||||
"""Executes entry orders and updates `self.inventory` and `self.trade_log`"""
|
||||
for leg, (idx, qty) in orders.items():
|
||||
row = entry_signals[leg].iloc[idx, :]
|
||||
contract = row["contract"]
|
||||
order = row["order"]
|
||||
price = row["price"]
|
||||
expiration = row["expiration"]
|
||||
cost = price * qty * self.shares_per_contract
|
||||
cost *= -1 if order == Order.STO.name else 1
|
||||
if self.capital >= cost:
|
||||
self.capital -= cost
|
||||
self._inventory.add((contract, leg, qty, expiration))
|
||||
self._update_trade_log(date, contract, order, qty, -cost)
|
||||
|
||||
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"]:
|
||||
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 _update_trade_log(self, date, contract, order, qty, profit):
|
||||
"""Adds entry for the given order to `self.trade_log`."""
|
||||
self.trade_log.loc[len(self.trade_log)] = [
|
||||
date, contract, order, qty, profit, self.capital
|
||||
]
|
||||
|
||||
def __repr__(self):
|
||||
return "Backtest(capital={}, strategy={})".format(
|
||||
self._strategy, self.capital)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
class Schema:
|
||||
"""Data schema class (used to run validations)"""
|
||||
"""Data schema class.
|
||||
Used to run validations and provide uniform access to fields in the data set.
|
||||
"""
|
||||
|
||||
columns = [
|
||||
"underlying", "underlying_last", "date", "contract", "type",
|
||||
|
||||
@@ -0,0 +1,739 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from backtester.datahandler import HistoricalOptionsData\n",
|
||||
"from backtester.strategy import Strategy, StrategyLeg\n",
|
||||
"from backtester.option import Type, Direction\n",
|
||||
"from backtester import Backtest"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"data = HistoricalOptionsData(\"data/options_data_v2.h5\",\n",
|
||||
" key=\"/SPX\",\n",
|
||||
" where=\"quotedate < 1991\")\n",
|
||||
"schema = data.schema"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"Schema([Field(name='underlying', mapping='underlying'), Field(name='underlying_last', mapping='underlying_last'), Field(name='date', mapping='quotedate'), Field(name='contract', mapping='optionroot'), Field(name='type', mapping='type'), Field(name='expiration', mapping='expiration'), Field(name='strike', mapping='strike'), Field(name='bid', mapping='bid'), Field(name='ask', mapping='ask'), Field(name='volume', mapping='volume'), Field(name='open_interest', mapping='openinterest'), Field(name='last', mapping='last'), Field(name='impliedvol', mapping='impliedvol'), Field(name='delta', mapping='delta'), Field(name='gamma', mapping='gamma'), Field(name='theta', mapping='theta'), Field(name='vega', mapping='vega'), Field(name='dte', mapping='dte')])"
|
||||
]
|
||||
},
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"schema"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"Strategy(legs=[])"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"strat = Strategy(schema)\n",
|
||||
"strat"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We'll implement a simple [short straddle](https://www.optionseducation.org/strategies/all-strategies/short-straddle), selling calls and puts 10% otm between 30 and 20 days prior to expiration, and covering the position 2 days before expiration."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"Strategy(legs=[StrategyLeg(type=Type.CALL, direction=Direction.SELL, entry_filter=Filter(query='(type == 'call') & ((((underlying == 'SPX') & (strike >= 1.1 * underlying_last)) & (dte >= 20)) & (dte <= 30))'), exit_filter=Filter(query='(type == 'call') & ((underlying == 'SPX') & (dte <= 2))')), StrategyLeg(type=Type.PUT, direction=Direction.SELL, entry_filter=Filter(query='(type == 'put') & ((((underlying == 'SPX') & (strike <= underlying_last * 0.9)) & (dte >= 20)) & (dte <= 30))'), exit_filter=Filter(query='(type == 'put') & ((underlying == 'SPX') & (dte <= 2))'))])"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"leg1 = StrategyLeg(schema, option_type=Type.CALL, direction=Direction.SELL)\n",
|
||||
"leg1.entry_filter = (schema.underlying == \"SPX\") & (\n",
|
||||
" schema.strike >= 1.1 * schema.underlying_last) & (schema.dte >=\n",
|
||||
" 20) & (schema.dte <= 30)\n",
|
||||
"leg1.exit_filter = (schema.underlying == \"SPX\") & (schema.dte <= 2)\n",
|
||||
"\n",
|
||||
"leg2 = StrategyLeg(schema, option_type=Type.PUT, direction=Direction.SELL)\n",
|
||||
"leg2.entry_filter = (schema.underlying == \"SPX\") & (\n",
|
||||
" schema.strike <= schema.underlying_last * 0.9) & (schema.dte >=\n",
|
||||
" 20) & (schema.dte <= 30)\n",
|
||||
"leg2.exit_filter = (schema.underlying == \"SPX\") & (schema.dte <= 2)\n",
|
||||
"strat.add_leg(leg1)\n",
|
||||
"strat.add_leg(leg2)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"Backtest(capital=Strategy(legs=[StrategyLeg(type=Type.CALL, direction=Direction.SELL, entry_filter=Filter(query='(type == 'call') & ((((underlying == 'SPX') & (strike >= 1.1 * underlying_last)) & (dte >= 20)) & (dte <= 30))'), exit_filter=Filter(query='(type == 'call') & ((underlying == 'SPX') & (dte <= 2))')), StrategyLeg(type=Type.PUT, direction=Direction.SELL, entry_filter=Filter(query='(type == 'put') & ((((underlying == 'SPX') & (strike <= underlying_last * 0.9)) & (dte >= 20)) & (dte <= 30))'), exit_filter=Filter(query='(type == 'put') & ((underlying == 'SPX') & (dte <= 2))'))]), strategy=1000000)"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"bt = Backtest()\n",
|
||||
"bt.strategy = strat\n",
|
||||
"bt.data = data\n",
|
||||
"bt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"/Users/jamoroso/work/backtester_options/backtester/strategy/strategy.py:59: UserWarning: Pandas doesn't allow columns to be created via a new attribute name - see https://pandas.pydata.org/pandas-docs/stable/indexing.html#attribute-access\n",
|
||||
" entry_df.legs = exit_df.legs = exit_df.columns.levels[0]\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/html": [
|
||||
"<div>\n",
|
||||
"<style scoped>\n",
|
||||
" .dataframe tbody tr th:only-of-type {\n",
|
||||
" vertical-align: middle;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe tbody tr th {\n",
|
||||
" vertical-align: top;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe thead th {\n",
|
||||
" text-align: right;\n",
|
||||
" }\n",
|
||||
"</style>\n",
|
||||
"<table border=\"1\" class=\"dataframe\">\n",
|
||||
" <thead>\n",
|
||||
" <tr style=\"text-align: right;\">\n",
|
||||
" <th></th>\n",
|
||||
" <th>date</th>\n",
|
||||
" <th>contract</th>\n",
|
||||
" <th>order</th>\n",
|
||||
" <th>qty</th>\n",
|
||||
" <th>profit</th>\n",
|
||||
" <th>capital</th>\n",
|
||||
" </tr>\n",
|
||||
" </thead>\n",
|
||||
" <tbody>\n",
|
||||
" <tr>\n",
|
||||
" <th>0</th>\n",
|
||||
" <td>1990-01-19</td>\n",
|
||||
" <td>SPX900217C00375000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1000010.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>1</th>\n",
|
||||
" <td>1990-01-19</td>\n",
|
||||
" <td>SPX900217P00225000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1000020.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>2</th>\n",
|
||||
" <td>1990-01-22</td>\n",
|
||||
" <td>SPX900217C00365000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1000030.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>3</th>\n",
|
||||
" <td>1990-01-22</td>\n",
|
||||
" <td>SPX900217P00225000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1000040.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>4</th>\n",
|
||||
" <td>1990-01-23</td>\n",
|
||||
" <td>SPX900217C00365000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1000050.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>5</th>\n",
|
||||
" <td>1990-01-23</td>\n",
|
||||
" <td>SPX900217P00225000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1000060.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>6</th>\n",
|
||||
" <td>1990-01-24</td>\n",
|
||||
" <td>SPX900217C00365000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1000070.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>7</th>\n",
|
||||
" <td>1990-01-24</td>\n",
|
||||
" <td>SPX900217P00225000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1000080.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>8</th>\n",
|
||||
" <td>1990-01-25</td>\n",
|
||||
" <td>SPX900217C00360000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1000090.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>9</th>\n",
|
||||
" <td>1990-01-25</td>\n",
|
||||
" <td>SPX900217P00225000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1000100.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>10</th>\n",
|
||||
" <td>1990-01-26</td>\n",
|
||||
" <td>SPX900217C00360000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1000110.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>11</th>\n",
|
||||
" <td>1990-01-26</td>\n",
|
||||
" <td>SPX900217P00225000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1000120.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>12</th>\n",
|
||||
" <td>1990-02-20</td>\n",
|
||||
" <td>SPX900317C00365000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1000130.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>13</th>\n",
|
||||
" <td>1990-02-20</td>\n",
|
||||
" <td>SPX900317P00275000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>50.0</td>\n",
|
||||
" <td>1000180.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>14</th>\n",
|
||||
" <td>1990-08-23</td>\n",
|
||||
" <td>SPX900922C00340000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>80.0</td>\n",
|
||||
" <td>1000260.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>15</th>\n",
|
||||
" <td>1990-08-23</td>\n",
|
||||
" <td>SPX900922P00275000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>320.0</td>\n",
|
||||
" <td>1000580.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>16</th>\n",
|
||||
" <td>1990-08-24</td>\n",
|
||||
" <td>SPX900922C00345000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>20.0</td>\n",
|
||||
" <td>1000600.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>17</th>\n",
|
||||
" <td>1990-08-24</td>\n",
|
||||
" <td>SPX900922P00275000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>220.0</td>\n",
|
||||
" <td>1000820.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>18</th>\n",
|
||||
" <td>1990-08-27</td>\n",
|
||||
" <td>SPX900922C00355000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>20.0</td>\n",
|
||||
" <td>1000840.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>19</th>\n",
|
||||
" <td>1990-08-27</td>\n",
|
||||
" <td>SPX900922P00275000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>110.0</td>\n",
|
||||
" <td>1000950.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>20</th>\n",
|
||||
" <td>1990-08-28</td>\n",
|
||||
" <td>SPX900922C00355000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1000960.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>21</th>\n",
|
||||
" <td>1990-08-28</td>\n",
|
||||
" <td>SPX900922P00275000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>90.0</td>\n",
|
||||
" <td>1001050.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>22</th>\n",
|
||||
" <td>1990-08-29</td>\n",
|
||||
" <td>SPX900922C00360000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1001060.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>23</th>\n",
|
||||
" <td>1990-08-29</td>\n",
|
||||
" <td>SPX900922P00290000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>140.0</td>\n",
|
||||
" <td>1001200.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>24</th>\n",
|
||||
" <td>1990-08-31</td>\n",
|
||||
" <td>SPX900922C00355000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1001210.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>25</th>\n",
|
||||
" <td>1990-08-31</td>\n",
|
||||
" <td>SPX900922P00290000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>160.0</td>\n",
|
||||
" <td>1001370.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>26</th>\n",
|
||||
" <td>1990-09-20</td>\n",
|
||||
" <td>SPX901020C00345000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>20.0</td>\n",
|
||||
" <td>1001390.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>27</th>\n",
|
||||
" <td>1990-09-20</td>\n",
|
||||
" <td>SPX901020P00250000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>60.0</td>\n",
|
||||
" <td>1001450.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>28</th>\n",
|
||||
" <td>1990-09-21</td>\n",
|
||||
" <td>SPX901020C00345000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>40.0</td>\n",
|
||||
" <td>1001490.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>29</th>\n",
|
||||
" <td>1990-09-21</td>\n",
|
||||
" <td>SPX901020P00250000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>80.0</td>\n",
|
||||
" <td>1001570.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>30</th>\n",
|
||||
" <td>1990-09-24</td>\n",
|
||||
" <td>SPX901020C00340000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>30.0</td>\n",
|
||||
" <td>1001600.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>31</th>\n",
|
||||
" <td>1990-09-24</td>\n",
|
||||
" <td>SPX901020P00250000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>80.0</td>\n",
|
||||
" <td>1001680.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>32</th>\n",
|
||||
" <td>1990-09-25</td>\n",
|
||||
" <td>SPX901020C00340000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1001690.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>33</th>\n",
|
||||
" <td>1990-09-25</td>\n",
|
||||
" <td>SPX901020P00275000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>120.0</td>\n",
|
||||
" <td>1001810.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>34</th>\n",
|
||||
" <td>1990-09-26</td>\n",
|
||||
" <td>SPX901020C00340000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1001820.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>35</th>\n",
|
||||
" <td>1990-09-26</td>\n",
|
||||
" <td>SPX901020P00250000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>60.0</td>\n",
|
||||
" <td>1001880.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>36</th>\n",
|
||||
" <td>1990-09-27</td>\n",
|
||||
" <td>SPX901020C00335000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>20.0</td>\n",
|
||||
" <td>1001900.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>37</th>\n",
|
||||
" <td>1990-09-27</td>\n",
|
||||
" <td>SPX901020P00250000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>60.0</td>\n",
|
||||
" <td>1001960.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>38</th>\n",
|
||||
" <td>1990-09-28</td>\n",
|
||||
" <td>SPX901020C00340000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1001970.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>39</th>\n",
|
||||
" <td>1990-09-28</td>\n",
|
||||
" <td>SPX901020P00275000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>120.0</td>\n",
|
||||
" <td>1002090.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>40</th>\n",
|
||||
" <td>1990-10-18</td>\n",
|
||||
" <td>SPX901117C00340000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1002100.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>41</th>\n",
|
||||
" <td>1990-10-18</td>\n",
|
||||
" <td>SPX901117P00275000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>210.0</td>\n",
|
||||
" <td>1002310.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>42</th>\n",
|
||||
" <td>1990-10-19</td>\n",
|
||||
" <td>SPX901117C00345000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1002320.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>43</th>\n",
|
||||
" <td>1990-10-19</td>\n",
|
||||
" <td>SPX901117P00280000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>190.0</td>\n",
|
||||
" <td>1002510.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>44</th>\n",
|
||||
" <td>1990-10-23</td>\n",
|
||||
" <td>SPX901117C00345000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>20.0</td>\n",
|
||||
" <td>1002530.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>45</th>\n",
|
||||
" <td>1990-10-23</td>\n",
|
||||
" <td>SPX901117P00280000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>110.0</td>\n",
|
||||
" <td>1002640.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>46</th>\n",
|
||||
" <td>1990-10-24</td>\n",
|
||||
" <td>SPX901117C00345000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1002650.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>47</th>\n",
|
||||
" <td>1990-10-24</td>\n",
|
||||
" <td>SPX901117P00280000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>110.0</td>\n",
|
||||
" <td>1002760.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>48</th>\n",
|
||||
" <td>1990-10-25</td>\n",
|
||||
" <td>SPX901117C00345000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1002770.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>49</th>\n",
|
||||
" <td>1990-10-25</td>\n",
|
||||
" <td>SPX901117P00275000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>110.0</td>\n",
|
||||
" <td>1002880.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>50</th>\n",
|
||||
" <td>1990-10-26</td>\n",
|
||||
" <td>SPX901117C00340000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>10.0</td>\n",
|
||||
" <td>1002890.0</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>51</th>\n",
|
||||
" <td>1990-10-26</td>\n",
|
||||
" <td>SPX901117P00250000</td>\n",
|
||||
" <td>STO</td>\n",
|
||||
" <td>1</td>\n",
|
||||
" <td>40.0</td>\n",
|
||||
" <td>1002930.0</td>\n",
|
||||
" </tr>\n",
|
||||
" </tbody>\n",
|
||||
"</table>\n",
|
||||
"</div>"
|
||||
],
|
||||
"text/plain": [
|
||||
" date contract order qty profit capital\n",
|
||||
"0 1990-01-19 SPX900217C00375000 STO 1 10.0 1000010.0\n",
|
||||
"1 1990-01-19 SPX900217P00225000 STO 1 10.0 1000020.0\n",
|
||||
"2 1990-01-22 SPX900217C00365000 STO 1 10.0 1000030.0\n",
|
||||
"3 1990-01-22 SPX900217P00225000 STO 1 10.0 1000040.0\n",
|
||||
"4 1990-01-23 SPX900217C00365000 STO 1 10.0 1000050.0\n",
|
||||
"5 1990-01-23 SPX900217P00225000 STO 1 10.0 1000060.0\n",
|
||||
"6 1990-01-24 SPX900217C00365000 STO 1 10.0 1000070.0\n",
|
||||
"7 1990-01-24 SPX900217P00225000 STO 1 10.0 1000080.0\n",
|
||||
"8 1990-01-25 SPX900217C00360000 STO 1 10.0 1000090.0\n",
|
||||
"9 1990-01-25 SPX900217P00225000 STO 1 10.0 1000100.0\n",
|
||||
"10 1990-01-26 SPX900217C00360000 STO 1 10.0 1000110.0\n",
|
||||
"11 1990-01-26 SPX900217P00225000 STO 1 10.0 1000120.0\n",
|
||||
"12 1990-02-20 SPX900317C00365000 STO 1 10.0 1000130.0\n",
|
||||
"13 1990-02-20 SPX900317P00275000 STO 1 50.0 1000180.0\n",
|
||||
"14 1990-08-23 SPX900922C00340000 STO 1 80.0 1000260.0\n",
|
||||
"15 1990-08-23 SPX900922P00275000 STO 1 320.0 1000580.0\n",
|
||||
"16 1990-08-24 SPX900922C00345000 STO 1 20.0 1000600.0\n",
|
||||
"17 1990-08-24 SPX900922P00275000 STO 1 220.0 1000820.0\n",
|
||||
"18 1990-08-27 SPX900922C00355000 STO 1 20.0 1000840.0\n",
|
||||
"19 1990-08-27 SPX900922P00275000 STO 1 110.0 1000950.0\n",
|
||||
"20 1990-08-28 SPX900922C00355000 STO 1 10.0 1000960.0\n",
|
||||
"21 1990-08-28 SPX900922P00275000 STO 1 90.0 1001050.0\n",
|
||||
"22 1990-08-29 SPX900922C00360000 STO 1 10.0 1001060.0\n",
|
||||
"23 1990-08-29 SPX900922P00290000 STO 1 140.0 1001200.0\n",
|
||||
"24 1990-08-31 SPX900922C00355000 STO 1 10.0 1001210.0\n",
|
||||
"25 1990-08-31 SPX900922P00290000 STO 1 160.0 1001370.0\n",
|
||||
"26 1990-09-20 SPX901020C00345000 STO 1 20.0 1001390.0\n",
|
||||
"27 1990-09-20 SPX901020P00250000 STO 1 60.0 1001450.0\n",
|
||||
"28 1990-09-21 SPX901020C00345000 STO 1 40.0 1001490.0\n",
|
||||
"29 1990-09-21 SPX901020P00250000 STO 1 80.0 1001570.0\n",
|
||||
"30 1990-09-24 SPX901020C00340000 STO 1 30.0 1001600.0\n",
|
||||
"31 1990-09-24 SPX901020P00250000 STO 1 80.0 1001680.0\n",
|
||||
"32 1990-09-25 SPX901020C00340000 STO 1 10.0 1001690.0\n",
|
||||
"33 1990-09-25 SPX901020P00275000 STO 1 120.0 1001810.0\n",
|
||||
"34 1990-09-26 SPX901020C00340000 STO 1 10.0 1001820.0\n",
|
||||
"35 1990-09-26 SPX901020P00250000 STO 1 60.0 1001880.0\n",
|
||||
"36 1990-09-27 SPX901020C00335000 STO 1 20.0 1001900.0\n",
|
||||
"37 1990-09-27 SPX901020P00250000 STO 1 60.0 1001960.0\n",
|
||||
"38 1990-09-28 SPX901020C00340000 STO 1 10.0 1001970.0\n",
|
||||
"39 1990-09-28 SPX901020P00275000 STO 1 120.0 1002090.0\n",
|
||||
"40 1990-10-18 SPX901117C00340000 STO 1 10.0 1002100.0\n",
|
||||
"41 1990-10-18 SPX901117P00275000 STO 1 210.0 1002310.0\n",
|
||||
"42 1990-10-19 SPX901117C00345000 STO 1 10.0 1002320.0\n",
|
||||
"43 1990-10-19 SPX901117P00280000 STO 1 190.0 1002510.0\n",
|
||||
"44 1990-10-23 SPX901117C00345000 STO 1 20.0 1002530.0\n",
|
||||
"45 1990-10-23 SPX901117P00280000 STO 1 110.0 1002640.0\n",
|
||||
"46 1990-10-24 SPX901117C00345000 STO 1 10.0 1002650.0\n",
|
||||
"47 1990-10-24 SPX901117P00280000 STO 1 110.0 1002760.0\n",
|
||||
"48 1990-10-25 SPX901117C00345000 STO 1 10.0 1002770.0\n",
|
||||
"49 1990-10-25 SPX901117P00275000 STO 1 110.0 1002880.0\n",
|
||||
"50 1990-10-26 SPX901117C00340000 STO 1 10.0 1002890.0\n",
|
||||
"51 1990-10-26 SPX901117P00250000 STO 1 40.0 1002930.0"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"bt.run()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.6.7"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
from .portfolio import Portfolio
|
||||
from .kellyportfolio import KellyPortfolio
|
||||
from .simpleportfolio import SimplePortfolio
|
||||
from .balancedportfolio import BalancedPortfolio
|
||||
@@ -1,17 +0,0 @@
|
||||
from .portfolio import Portfolio
|
||||
|
||||
|
||||
class BalancedPortfolio(Portfolio):
|
||||
"""Buys and holds a basket of securities, and allocates them
|
||||
according to given weights.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, weights={}):
|
||||
self.weights = weights
|
||||
super().__init__(*args)
|
||||
|
||||
def _get_allocation(self, signal, price):
|
||||
"""Allocates capital in porportion to given weight"""
|
||||
weight = self.weights.get(signal.symbol, 0)
|
||||
cash_proportion = self.initial_capital * weight
|
||||
return cash_proportion / price
|
||||
@@ -1,16 +0,0 @@
|
||||
import math
|
||||
from .portfolio import Portfolio
|
||||
|
||||
|
||||
class KellyPortfolio(Portfolio):
|
||||
"""Allocates signals using Kelly's criterion"""
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
|
||||
def _get_allocation(self, strength, price):
|
||||
"""Calculates allocation using Kelly's criterion"""
|
||||
(win_percent, win_loss_ratio) = strength
|
||||
kelly = max(0, win_percent - (1 - win_percent) / win_loss_ratio)
|
||||
total_allocation = self.current_position["Cash"] * kelly
|
||||
return math.floor(total_allocation / price)
|
||||
@@ -1,88 +0,0 @@
|
||||
from abc import ABCMeta, abstractmethod
|
||||
import pandas as pd
|
||||
|
||||
|
||||
class Portfolio(metaclass=ABCMeta):
|
||||
"""Processes signals from the Strategy object"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, data_handler, events, capital=1000000):
|
||||
self.data_handler = data_handler
|
||||
self.events = events
|
||||
self.initial_capital = capital
|
||||
self.current_position = {"Cash": self.initial_capital}
|
||||
self.all_positions = {}
|
||||
self.current_balance = {"Cash": self.initial_capital}
|
||||
self.all_balances = {}
|
||||
|
||||
@abstractmethod
|
||||
def _get_allocation(self, strength, price):
|
||||
"""Calculates symbol allocation"""
|
||||
raise NotImplementedError("Portfolio must implement _get_allocation()")
|
||||
|
||||
def update_signal(self, signal):
|
||||
"""Processes signal event and updates the current position"""
|
||||
date = self.data_handler.current_date
|
||||
if date not in self.all_positions:
|
||||
self.all_positions[date] = self.current_position.copy()
|
||||
self.current_position = self.all_positions[date]
|
||||
|
||||
(price, direction) = self._get_price(signal)
|
||||
qty = self._get_allocation(signal, price)
|
||||
(current_amount, current_open_price) = self.current_position.get(
|
||||
signal.symbol, (0, 0))
|
||||
new_open_price = (current_open_price * current_amount +
|
||||
direction * price * qty) / (current_amount + qty)
|
||||
self.current_position[signal.symbol] = (
|
||||
current_amount + direction * qty, new_open_price)
|
||||
self.current_position["Cash"] -= direction * price * qty
|
||||
|
||||
def update_timeindex(self, event):
|
||||
"""Calculates new balance for the current timeindex.
|
||||
Appends current position to all_positions list."""
|
||||
date = self.data_handler.current_date
|
||||
self.all_balances[date] = self.current_balance.copy()
|
||||
self.current_balance = self.all_balances[date]
|
||||
self.current_balance["Total Exposure"] = 0
|
||||
|
||||
for symbol, values in self.current_position.items():
|
||||
if symbol == "Cash":
|
||||
self.current_balance["Cash"] = values
|
||||
continue
|
||||
|
||||
(amount, open_price) = values
|
||||
current_bar = self.data_handler.get_latest_bars(symbol)
|
||||
if amount < 0:
|
||||
price = current_bar["ask"]
|
||||
else:
|
||||
price = current_bar["bid"]
|
||||
market_value = amount * price
|
||||
self.current_balance[symbol + " Amount"] = amount
|
||||
self.current_balance[symbol + " Open"] = open_price
|
||||
self.current_balance[symbol + " Exposure"] = market_value
|
||||
self.current_balance["Total Exposure"] += market_value
|
||||
|
||||
self.all_positions[date] = self.current_position
|
||||
self.all_balances[date] = self.current_balance
|
||||
|
||||
def _get_price(self, signal):
|
||||
"""Returns price and direction for given symbol.
|
||||
Ask price if signal.type == BUY, bid price if signal.type == SELL.
|
||||
Also returns 1 or -1 for types BUY, SELL respectively"""
|
||||
current_bar = self.data_handler.get_latest_bars(signal.symbol)
|
||||
if signal.direction == "BUY":
|
||||
direction = 1
|
||||
price = current_bar["ask"]
|
||||
else:
|
||||
direction = -1
|
||||
price = current_bar["bid"]
|
||||
return (price, direction)
|
||||
|
||||
def create_report(self):
|
||||
"""Creates a pandas DataFrame from all_balances."""
|
||||
curve = pd.DataFrame(self.all_balances)
|
||||
curve = curve.transpose()
|
||||
curve["Total Portfolio"] = curve["Total Exposure"] + curve["Cash"]
|
||||
curve["Interval Change"] = curve["Total Portfolio"].pct_change()
|
||||
curve["% Price"] = (1.0 + curve["Interval Change"]).cumprod() - 1
|
||||
return curve
|
||||
@@ -1,13 +0,0 @@
|
||||
import math
|
||||
from .portfolio import Portfolio
|
||||
|
||||
|
||||
class SimplePortfolio(Portfolio):
|
||||
"""Allocates all capital to the first signal processed"""
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
|
||||
def _get_allocation(self, strength, price):
|
||||
"""Allocates all capital to the given signal"""
|
||||
return math.floor(self.current_position["Cash"] / price)
|
||||
@@ -41,9 +41,9 @@ class Strategy:
|
||||
self.legs = []
|
||||
return self
|
||||
|
||||
def run(self, data):
|
||||
"""Returns a dataframe of trades executed as a result of
|
||||
runnning the strategy on the data.
|
||||
def signals(self, data):
|
||||
"""Iterates over `data` and yields a tuple of
|
||||
(date, entry_signals, exit_signals) for each time step.
|
||||
"""
|
||||
assert self.schema == data.schema
|
||||
|
||||
@@ -56,6 +56,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]
|
||||
|
||||
yield (date, entry_df, exit_df)
|
||||
|
||||
@@ -63,17 +64,22 @@ class Strategy:
|
||||
"""Returns a list of `pd.DataFrame`.
|
||||
Each dataframe contains signals for each leg in the strategy.
|
||||
"""
|
||||
|
||||
schema = self.schema
|
||||
dfs = []
|
||||
for number, leg in enumerate(self.legs, start=1):
|
||||
flt = leg.entry_filter if signal == Signal.ENTRY else leg.exit_filter
|
||||
df = flt(data)
|
||||
price = leg.direction.value
|
||||
fields = (self.schema["contract"], self.schema["type"],
|
||||
self.schema["strike"], self.schema[price])
|
||||
subset_df = df.loc[:, fields]
|
||||
subset_df.rename(columns={self.schema[price]: "price"},
|
||||
inplace=True)
|
||||
fields = {
|
||||
schema["contract"]: "contract",
|
||||
schema["underlying"]: "underlying",
|
||||
schema["expiration"]: "expiration",
|
||||
schema["type"]: "type",
|
||||
schema["strike"]: "strike",
|
||||
schema[price]: "price"
|
||||
}
|
||||
subset_df = df.loc[:, fields.keys()]
|
||||
subset_df.rename(columns=fields, inplace=True)
|
||||
|
||||
order = get_order(leg.direction, signal)
|
||||
subset_df["order"] = order.name
|
||||
|
||||
Reference in New Issue
Block a user