Completed
Pull Request — master (#858)
by Eddie
10:07 queued 01:13
created

tests.BlotterTestCase   A

Complexity

Total Complexity 10

Size/Duplication

Total Lines 191
Duplicated Lines 0 %
Metric Value
dl 0
loc 191
rs 10
wmc 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A test_order_rejection() 0 64 2
B setUpClass() 0 43 2
A tearDownClass() 0 5 1
B test_order_hold() 0 59 4
A test_blotter_order_types() 0 13 1
1
#
2
# Copyright 2014 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
import os
17
from nose_parameterized import parameterized
18
from unittest import TestCase
19
from testfixtures import TempDirectory
20
import pandas as pd
21
22
import zipline.utils.factory as factory
23
24
from zipline.finance import trading
25
from zipline.finance.blotter import Blotter
26
from zipline.finance.order import ORDER_STATUS
27
from zipline.finance.execution import (
28
    LimitOrder,
29
    MarketOrder,
30
    StopLimitOrder,
31
    StopOrder,
32
)
33
34
from zipline.utils.test_utils import(
35
    setup_logger,
36
    teardown_logger,
37
)
38
from zipline.finance.slippage import DEFAULT_VOLUME_SLIPPAGE_BAR_LIMIT, \
39
    FixedSlippage
40
from .utils.daily_bar_writer import DailyBarWriterFromDataFrames
41
from zipline.data.us_equity_pricing import BcolzDailyBarReader
42
from zipline.data.data_portal import DataPortal
43
44
45
class BlotterTestCase(TestCase):
46
47
    @classmethod
48
    def setUpClass(cls):
49
        setup_logger(cls)
50
        cls.env = trading.TradingEnvironment()
51
52
        cls.sim_params = factory.create_simulation_parameters(
53
            start=pd.Timestamp("2006-01-05", tz='UTC'),
54
            end=pd.Timestamp("2006-01-06", tz='UTC')
55
        )
56
57
        cls.env.write_data(equities_data={
58
            24: {
59
                'start_date': cls.sim_params.trading_days[0],
60
                'end_date': cls.sim_params.trading_days[-1]
61
            }
62
        })
63
64
        cls.tempdir = TempDirectory()
65
66
        assets = {
67
            24: pd.DataFrame({
68
                "open": [50, 50],
69
                "high": [50, 50],
70
                "low": [50, 50],
71
                "close": [50, 50],
72
                "volume": [100, 400],
73
                "day": [day.value for day in cls.sim_params.trading_days]
74
            })
75
        }
76
77
        path = os.path.join(cls.tempdir.path, "tempdata.bcolz")
78
79
        DailyBarWriterFromDataFrames(assets).write(
80
            path,
81
            cls.sim_params.trading_days,
82
            assets
83
        )
84
85
        equity_daily_reader = BcolzDailyBarReader(path)
86
87
        cls.data_portal = DataPortal(
88
            cls.env,
89
            equity_daily_reader=equity_daily_reader,
90
        )
91
92
    @classmethod
93
    def tearDownClass(cls):
94
        del cls.env
95
        cls.tempdir.cleanup()
96
        teardown_logger(cls)
97
98
    @parameterized.expand([(MarketOrder(), None, None),
99
                           (LimitOrder(10), 10, None),
100
                           (StopOrder(10), None, 10),
101
                           (StopLimitOrder(10, 20), 10, 20)])
102
    def test_blotter_order_types(self, style_obj, expected_lmt, expected_stp):
103
104
        blotter = Blotter('daily')
105
106
        blotter.order(24, 100, style_obj)
107
        result = blotter.open_orders[24][0]
108
109
        self.assertEqual(result.limit, expected_lmt)
110
        self.assertEqual(result.stop, expected_stp)
111
112
    def test_order_rejection(self):
113
        blotter = Blotter(self.sim_params.data_frequency)
114
115
        # Reject a nonexistent order -> no order appears in new_order,
116
        # no exceptions raised out
117
        blotter.reject(56)
118
        self.assertEqual(blotter.new_orders, [])
119
120
        # Basic tests of open order behavior
121
        open_order_id = blotter.order(24, 100, MarketOrder())
122
        second_order_id = blotter.order(24, 50, MarketOrder())
123
        self.assertEqual(len(blotter.open_orders[24]), 2)
124
        open_order = blotter.open_orders[24][0]
125
        self.assertEqual(open_order.status, ORDER_STATUS.OPEN)
126
        self.assertEqual(open_order.id, open_order_id)
127
        self.assertIn(open_order, blotter.new_orders)
128
129
        # Reject that order immediately (same bar, i.e. still in new_orders)
130
        blotter.reject(open_order_id)
131
        self.assertEqual(len(blotter.new_orders), 2)
132
        self.assertEqual(len(blotter.open_orders[24]), 1)
133
        still_open_order = blotter.new_orders[0]
134
        self.assertEqual(still_open_order.id, second_order_id)
135
        self.assertEqual(still_open_order.status, ORDER_STATUS.OPEN)
136
        rejected_order = blotter.new_orders[1]
137
        self.assertEqual(rejected_order.status, ORDER_STATUS.REJECTED)
138
        self.assertEqual(rejected_order.reason, '')
139
140
        # Do it again, but reject it at a later time (after tradesimulation
141
        # pulls it from new_orders)
142
        blotter = Blotter(self.sim_params.data_frequency)
143
144
        new_open_id = blotter.order(24, 10, MarketOrder())
145
        new_open_order = blotter.open_orders[24][0]
146
        self.assertEqual(new_open_id, new_open_order.id)
147
        # Pretend that the trade simulation did this.
148
        blotter.new_orders = []
149
150
        rejection_reason = "Not enough cash on hand."
151
        blotter.reject(new_open_id, reason=rejection_reason)
152
        rejected_order = blotter.new_orders[0]
153
        self.assertEqual(rejected_order.id, new_open_id)
154
        self.assertEqual(rejected_order.status, ORDER_STATUS.REJECTED)
155
        self.assertEqual(rejected_order.reason, rejection_reason)
156
157
        # You can't reject a filled order.
158
        # Reset for paranoia
159
        blotter = Blotter(self.sim_params.data_frequency)
160
        blotter.slippage_func = FixedSlippage()
161
        filled_id = blotter.order(24, 100, MarketOrder())
162
        filled_order = None
163
        blotter.current_dt = self.sim_params.trading_days[-1]
164
        txns, _ = blotter.get_transactions(self.data_portal)
165
        for txn in txns:
166
            filled_order = blotter.orders[txn.order_id]
167
168
        self.assertEqual(filled_order.id, filled_id)
169
        self.assertIn(filled_order, blotter.new_orders)
170
        self.assertEqual(filled_order.status, ORDER_STATUS.FILLED)
171
        self.assertNotIn(filled_order, blotter.open_orders[24])
172
173
        blotter.reject(filled_id)
174
        updated_order = blotter.orders[filled_id]
175
        self.assertEqual(updated_order.status, ORDER_STATUS.FILLED)
176
177
    def test_order_hold(self):
178
        """
179
        Held orders act almost identically to open orders, except for the
180
        status indication. When a fill happens, the order should switch
181
        status to OPEN/FILLED as necessary
182
        """
183
        blotter = Blotter(self.sim_params.data_frequency)
184
        # Nothing happens on held of a non-existent order
185
        blotter.hold(56)
186
        self.assertEqual(blotter.new_orders, [])
187
188
        open_id = blotter.order(24, 100, MarketOrder())
189
        open_order = blotter.open_orders[24][0]
190
        self.assertEqual(open_order.id, open_id)
191
192
        blotter.hold(open_id)
193
        self.assertEqual(len(blotter.new_orders), 1)
194
        self.assertEqual(len(blotter.open_orders[24]), 1)
195
        held_order = blotter.new_orders[0]
196
        self.assertEqual(held_order.status, ORDER_STATUS.HELD)
197
        self.assertEqual(held_order.reason, '')
198
199
        blotter.cancel(held_order.id)
200
        self.assertEqual(len(blotter.new_orders), 1)
201
        self.assertEqual(len(blotter.open_orders[24]), 0)
202
        cancelled_order = blotter.new_orders[0]
203
        self.assertEqual(cancelled_order.id, held_order.id)
204
        self.assertEqual(cancelled_order.status, ORDER_STATUS.CANCELLED)
205
206
        for data in ([100, self.sim_params.trading_days[0]],
207
                     [400, self.sim_params.trading_days[1]]):
208
            # Verify that incoming fills will change the order status.
209
            trade_amt = data[0]
210
            dt = data[1]
211
212
            order_size = 100
213
            expected_filled = int(trade_amt *
214
                                  DEFAULT_VOLUME_SLIPPAGE_BAR_LIMIT)
215
            expected_open = order_size - expected_filled
216
            expected_status = ORDER_STATUS.OPEN if expected_open else \
217
                ORDER_STATUS.FILLED
218
219
            blotter = Blotter(self.sim_params.data_frequency)
220
            open_id = blotter.order(24, order_size, MarketOrder())
221
            open_order = blotter.open_orders[24][0]
222
            self.assertEqual(open_id, open_order.id)
223
            blotter.hold(open_id)
224
            held_order = blotter.new_orders[0]
225
226
            filled_order = None
227
            blotter.current_dt = dt
228
            txns, _ = blotter.get_transactions(self.data_portal)
229
            for txn in txns:
230
                filled_order = blotter.orders[txn.order_id]
231
232
            self.assertEqual(filled_order.id, held_order.id)
233
            self.assertEqual(filled_order.status, expected_status)
234
            self.assertEqual(filled_order.filled, expected_filled)
235
            self.assertEqual(filled_order.open_amount, expected_open)
236