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

tests.BlotterTestCase   A

Complexity

Total Complexity 10

Size/Duplication

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

5 Methods

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