Completed
Pull Request — master (#858)
by Eddie
02:02
created

zipline.sources.BenchmarkSource.get_value()   A

Complexity

Conditions 1

Size

Total Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 1
dl 0
loc 2
rs 10
1
#
2
# Copyright 2015 Quantopian, Inc.
3
#
4
# Licensed under the Apache License, Version 2.0 (the "License");
5
# you may not use this file except in compliance with the License.
6
# You may obtain a copy of the License at
7
#
8
#     http://www.apache.org/licenses/LICENSE-2.0
9
#
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS,
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
# See the License for the specific language governing permissions and
14
# limitations under the License.
15
16
from zipline.errors import (
17
    InvalidBenchmarkAsset,
18
    BenchmarkAssetNotAvailableTooEarly,
19
    BenchmarkAssetNotAvailableTooLate
20
)
21
22
23
class BenchmarkSource(object):
24
    def __init__(self, benchmark_sid, env, trading_days, data_portal,
25
                 emission_rate="daily"):
26
        self.benchmark_sid = benchmark_sid
27
        self.env = env
28
        self.trading_days = trading_days
29
        self.emission_rate = emission_rate
30
        self.data_portal = data_portal
31
32
        if self.benchmark_sid:
33
            self.benchmark_asset = self.env.asset_finder.retrieve_asset(
34
                self.benchmark_sid)
35
36
            self._validate_benchmark()
37
38
        self.precalculated_series = \
39
            self._initialize_precalculated_series(
40
                self.benchmark_sid,
41
                self.env,
42
                self.trading_days,
43
                self.data_portal
44
            )
45
46
    def get_value(self, dt):
47
        return self.precalculated_series.loc[dt]
48
49
    def _validate_benchmark(self):
50
        # check if this security has a stock dividend.  if so, raise an
51
        # error suggesting that the user pick a different asset to use
52
        # as benchmark.
53
        stock_dividends = \
54
            self.data_portal.get_stock_dividends(self.benchmark_sid,
55
                                                 self.trading_days)
56
57
        if len(stock_dividends) > 0:
58
            raise InvalidBenchmarkAsset(
59
                sid=str(self.benchmark_sid),
60
                dt=stock_dividends[0]["ex_date"]
61
            )
62
63
        if self.benchmark_asset.start_date > self.trading_days[0]:
64
            # the asset started trading after the first simulation day
65
            raise BenchmarkAssetNotAvailableTooEarly(
66
                sid=str(self.benchmark_sid),
67
                dt=self.trading_days[0],
68
                start_dt=self.benchmark_asset.start_date
69
            )
70
71
        if self.benchmark_asset.end_date < self.trading_days[-1]:
72
            # the asset stopped trading before the last simulation day
73
            raise BenchmarkAssetNotAvailableTooLate(
74
                sid=str(self.benchmark_sid),
75
                dt=self.trading_days[0],
76
                end_dt=self.benchmark_asset.end_date
77
            )
78
79
    def _initialize_precalculated_series(self, sid, env, trading_days,
80
                                         data_portal):
81
        """
82
        Internal method that precalculates the benchmark return series for
83
        use in the simulation.
84
85
        Parameters
86
        ----------
87
        sid: (int) Asset to use
88
89
        env: TradingEnvironment
90
91
        trading_days: pd.DateTimeIndex
92
93
        data_portal: DataPortal
94
95
        Notes
96
        -----
97
        If the benchmark asset started trading after the simulation start,
98
        or finished trading before the simulation end, exceptions are raised.
99
100
        If the benchmark asset started trading the same day as the simulation
101
        start, the first available minute price on that day is used instead
102
        of the previous close.
103
104
        We use history to get an adjusted price history for each day's close,
105
        as of the look-back date (the last day of the simulation).  Prices are
106
        fully adjusted for dividends, splits, and mergers.
107
108
        Returns
109
        -------
110
        A pd.Series, indexed by trading day, whose values represent the %
111
        change from close to close.
112
        """
113
        if sid is None:
114
            # get benchmark info from trading environment, which defaults to
115
            # downloading data from Yahoo.
116
            daily_series = \
117
                env.benchmark_returns[trading_days[0]:trading_days[-1]]
118
119
            if self.emission_rate == "minute":
120
                # we need to take the env's benchmark returns, which are daily,
121
                # and resample them to minute
122
                minutes = env.minutes_for_days_in_range(
123
                    start=trading_days[0],
124
                    end=trading_days[-1]
125
                )
126
127
                minute_series = daily_series.reindex(
128
                    index=minutes,
129
                    method="ffill"
130
                )
131
132
                return minute_series
133
            else:
134
                return daily_series
135
        elif self.emission_rate == "minute":
136
            minutes = env.minutes_for_days_in_range(self.trading_days[0],
137
                                                    self.trading_days[-1])
138
            benchmark_series = data_portal.get_history_window(
139
                [sid],
140
                minutes[-1],
141
                bar_count=len(minutes) + 1,
142
                frequency="1m",
143
                field="price",
144
                ffill=True
145
            )
146
147
            return benchmark_series.pct_change()[1:]
148
        else:
149
            start_date = env.asset_finder.retrieve_asset(sid).start_date
150
            if start_date < trading_days[0]:
151
                # get the window of close prices for benchmark_sid from the
152
                # last trading day of the simulation, going up to one day
153
                # before the simulation start day (so that we can get the %
154
                # change on day 1)
155
                benchmark_series = data_portal.get_history_window(
156
                    [sid],
157
                    trading_days[-1],
158
                    bar_count=len(trading_days) + 1,
159
                    frequency="1d",
160
                    field="price",
161
                    ffill=True
162
                )[sid]
163
                return benchmark_series.pct_change()[1:]
164
            elif start_date == trading_days[0]:
165
                # Attempt to handle case where stock data starts on first
166
                # day, in this case use the open to close return.
167
                benchmark_series = data_portal.get_history_window(
168
                    [sid],
169
                    trading_days[-1],
170
                    bar_count=len(trading_days),
171
                    frequency="1d",
172
                    field="price",
173
                    ffill=True
174
                )[sid]
175
176
                # get a minute history window of the first day
177
                first_open = data_portal.get_spot_value(
178
                    sid, 'open', trading_days[0], 'daily')
179
                first_close = data_portal.get_spot_value(
180
                    sid, 'close', trading_days[0], 'daily')
181
182
                first_day_return = (first_close - first_open) / first_open
183
184
                returns = benchmark_series.pct_change()[:]
185
                returns[0] = first_day_return
186
                return returns
187