-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathrandom_forecast.py
More file actions
112 lines (88 loc) · 4.4 KB
/
Copy pathrandom_forecast.py
File metadata and controls
112 lines (88 loc) · 4.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# -*- coding: utf-8 -*-
"""
Created on Tue Oct 20 13:40:43 2015
@author: ABerner
"""
#random_forecast.py
import numpy as np
import pandas as pd
import pandas.io.data as web
from backtest import Strategy, Portfolio
class RandomForecastingStrategy(Strategy):
"""Derives from Strategy to produce a set of signals that
are randomly generated long/shorts. Clearly a nonsensical
strategy, but perfectly acceptable for demonstrating the
backtesting infrasturcture!"""
def __init__(self, symbol, bars):
"""Requires the symbol ticker and the pandas DataFrame of bars"""
self.symbol = symbol
self.bars = bars
def generate_signals(self):
"""Creates a pandas DataFrame of random signals."""
signals = pd.DataFrame(index=self.bars.index)
signals['signal'] = np.random.randint(3,size=len(signals))-1
# The first five elements are set to zero in order to minimize
# upstream NaN errors in the forecaster.
signals['signal'][0:5] = 0.0
return signals
class MarketOnOpenPortfolio(Portfolio):
"""Inherits Portfolio to create a system that purchases 100 units of
a particular symbol upon a long/short signal, assuming the market
open price of a bar.
In addition, there are zero transaction costs and cash can be immediately
borrowed for shorting (no margin posting or interest requirements).
Requires:
symbol A stock symbol which forms the basis of the portfolio.
bars - A DataFrame of bars for a symbol set.
signals - A pandas DataFrame of signals (1, 0, -1) for each symbol.
initial_capital - The amount in cash at the start of the portfolio."""
def __init__(self, symbol, bars, signals, initial_capital=100000.0):
self.symbol = symbol
self.bars = bars
self.signals = signals
self.initial_capital = float(initial_capital)
self.positions = self.generate_positions()
def generate_positions(self):
"""Creates a 'positions' DataFrame that simply longs or shorts
100 of the particular symbol based on the forecast signals of
(1, 0, -1) from the signals DataFrame."""
positions = pd.DataFrame(index=self.signals.index).fillna(0.0)
positions[self.symbol] = 100*self.signals['signal']
return positions
def backtest_portfolio(self):
"""Constructs a portfolio from the positions DataFrame by
assuming the ability to trade at the precise market open price
of each bar (an unrealistic assumption!).
Calculates the total of cash and the holdings (market price of
each position per bar), in order to generate an equity curve
('total') and a set of bar-based returns ('returns').
Returns the portfolio object to be used elsewhere."""
# Construct the portfolio DataFrame to use the same index
# as 'positions' and with a set of 'trading orders' in the
# 'pos_diff' object, assuming market open prices.
portfolio = self.positions.mul(self.bars['Open'],axis=0)
pos_diff = self.positions.diff()
# Create the 'holdings' and 'cash' series by running through
# the trades and adding/subtracting the relevant quantity from
# each column
portfolio['holdings'] = (self.positions.mul(self.bars['Open'],axis=0)).sum(axis=1)
portfolio['cash'] = self.initial_capital - (pos_diff.mul(self.bars['Open'],axis=0)).sum(axis=1).cumsum()
# Finalize the total and bar-based returns based on the 'cash'
# and 'holdings' figures for the portfolio
portfolio['total'] = portfolio['cash'] + portfolio['holdings']
portfolio['returns'] = portfolio['total'].pct_change()
return portfolio
if __name__ == "__main__":
# Obtain daily bars of SPY (ETF that generally
# follows the S&P500) from Yahoo
symbol = 'SPY'
startDate = pd.datetime(1993,1,1)
endDate = pd.datetime.today()
bars = web.DataReader(symbol, 'yahoo', startDate, endDate)
# Create a set of random forecasting signals for SPY
rfs = RandomForecastingStrategy(symbol, bars)
signals = rfs.generate_signals()
# Create a portfolio of SPY
portfolio = MarketOnOpenPortfolio(symbol, bars, signals, initial_capital=100000.0)
returns = portfolio.backtest_portfolio()
print(returns.tail(10))