Completed
Pull Request — master (#867)
by Joe
02:09
created

tests.finance.SlippageTestCase   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 611
Duplicated Lines 0 %
Metric Value
dl 0
loc 611
rs 9.9675
wmc 13
1
#
2
# Copyright 2013 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
"""
17
Unit tests for finance.slippage
18
"""
19
import datetime
20
21
import pytz
22
23
from unittest import TestCase
24
25
from nose_parameterized import parameterized
26
27
import pandas as pd
28
29
from zipline.finance.slippage import VolumeShareSlippage
30
31
from zipline.protocol import Event, DATASOURCE_TYPE
32
from zipline.finance.blotter import Order
33
34
35
class SlippageTestCase(TestCase):
36
37
    def test_volume_share_slippage(self):
38
        event = Event(
39
            {'volume': 200,
40
             'type': 4,
41
             'price': 3.0,
42
             'datetime': datetime.datetime(
43
                 2006, 1, 5, 14, 31, tzinfo=pytz.utc),
44
             'high': 3.15,
45
             'low': 2.85,
46
             'sid': 133,
47
             'source_id': 'test_source',
48
             'close': 3.0,
49
             'dt':
50
             datetime.datetime(2006, 1, 5, 14, 31, tzinfo=pytz.utc),
51
             'open': 3.0}
52
        )
53
54
        slippage_model = VolumeShareSlippage()
55
56
        open_orders = [
57
            Order(dt=datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
58
                  amount=100,
59
                  filled=0,
60
                  sid=133)
61
        ]
62
63
        orders_txns = list(slippage_model.simulate(
64
            event,
65
            open_orders
66
        ))
67
68
        self.assertEquals(len(orders_txns), 1)
69
        _, txn = orders_txns[0]
70
71
        expected_txn = {
72
            'price': float(3.01875),
73
            'dt': datetime.datetime(
74
                2006, 1, 5, 14, 31, tzinfo=pytz.utc),
75
            'amount': int(50),
76
            'sid': int(133),
77
            'commission': None,
78
            'type': DATASOURCE_TYPE.TRANSACTION,
79
            'order_id': open_orders[0].id
80
        }
81
82
        self.assertIsNotNone(txn)
83
84
        # TODO: Make expected_txn an Transaction object and ensure there
85
        # is a __eq__ for that class.
86
        self.assertEquals(expected_txn, txn.__dict__)
87
88
    def test_orders_limit(self):
89
90
        events = self.gen_trades()
91
92
        slippage_model = VolumeShareSlippage()
93
94
        # long, does not trade
95
96
        open_orders = [
97
            Order(**{
98
                'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
99
                'amount': 100,
100
                'filled': 0,
101
                'sid': 133,
102
                'limit': 3.5})
103
        ]
104
105
        orders_txns = list(slippage_model.simulate(
106
            events[3],
107
            open_orders
108
        ))
109
        self.assertEquals(len(orders_txns), 0)
110
111
        # long, does not trade - impacted price worse than limit price
112
113
        open_orders = [
114
            Order(**{
115
                'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
116
                'amount': 100,
117
                'filled': 0,
118
                'sid': 133,
119
                'limit': 3.5})
120
        ]
121
122
        orders_txns = list(slippage_model.simulate(
123
            events[3],
124
            open_orders
125
        ))
126
127
        self.assertEquals(len(orders_txns), 0)
128
129
        # long, does trade
130
131
        open_orders = [
132
            Order(**{
133
                'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
134
                'amount': 100,
135
                'filled': 0,
136
                'sid': 133,
137
                'limit': 3.6})
138
        ]
139
140
        orders_txns = list(slippage_model.simulate(
141
            events[3],
142
            open_orders
143
        ))
144
145
        self.assertEquals(len(orders_txns), 1)
146
        txn = orders_txns[0][1]
147
148
        expected_txn = {
149
            'price': float(3.500875),
150
            'dt': datetime.datetime(
151
                2006, 1, 5, 14, 34, tzinfo=pytz.utc),
152
            'amount': int(100),
153
            'sid': int(133),
154
            'order_id': open_orders[0].id
155
        }
156
157
        self.assertIsNotNone(txn)
158
159
        for key, value in expected_txn.items():
160
            self.assertEquals(value, txn[key])
161
162
        # short, does not trade
163
164
        open_orders = [
165
            Order(**{
166
                'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
167
                'amount': -100,
168
                'filled': 0,
169
                'sid': 133,
170
                'limit': 3.5})
171
        ]
172
173
        orders_txns = list(slippage_model.simulate(
174
            events[0],
175
            open_orders
176
        ))
177
178
        expected_txn = {}
179
180
        self.assertEquals(len(orders_txns), 0)
181
182
        # short, does not trade - impacted price worse than limit price
183
184
        open_orders = [
185
            Order(**{
186
                'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
187
                'amount': -100,
188
                'filled': 0,
189
                'sid': 133,
190
                'limit': 3.5})
191
        ]
192
193
        orders_txns = list(slippage_model.simulate(
194
            events[1],
195
            open_orders
196
        ))
197
198
        self.assertEquals(len(orders_txns), 0)
199
200
        # short, does trade
201
202
        open_orders = [
203
            Order(**{
204
                'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
205
                'amount': -100,
206
                'filled': 0,
207
                'sid': 133,
208
                'limit': 3.4})
209
        ]
210
211
        orders_txns = list(slippage_model.simulate(
212
            events[1],
213
            open_orders
214
        ))
215
216
        self.assertEquals(len(orders_txns), 1)
217
        _, txn = orders_txns[0]
218
219
        expected_txn = {
220
            'price': float(3.499125),
221
            'dt': datetime.datetime(
222
                2006, 1, 5, 14, 32, tzinfo=pytz.utc),
223
            'amount': int(-100),
224
            'sid': int(133)
225
        }
226
227
        self.assertIsNotNone(txn)
228
229
        for key, value in expected_txn.items():
230
            self.assertEquals(value, txn[key])
231
232
    STOP_ORDER_CASES = {
233
        # Stop orders can be long/short and have their price greater or
234
        # less than the stop.
235
        #
236
        # A stop being reached is conditional on the order direction.
237
        # Long orders reach the stop when the price is greater than the stop.
238
        # Short orders reach the stop when the price is less than the stop.
239
        #
240
        # Which leads to the following 4 cases:
241
        #
242
        #                    |   long   |   short  |
243
        # | price > stop     |          |          |
244
        # | price < stop     |          |          |
245
        #
246
        # Currently the slippage module acts according to the following table,
247
        # where 'X' represents triggering a transaction
248
        #                    |   long   |   short  |
249
        # | price > stop     |          |     X    |
250
        # | price < stop     |    X     |          |
251
        #
252
        # However, the following behavior *should* be followed.
253
        #
254
        #                    |   long   |   short  |
255
        # | price > stop     |    X     |          |
256
        # | price < stop     |          |     X    |
257
258
        'long | price gt stop': {
259
            'order': {
260
                'dt': pd.Timestamp('2006-01-05 14:30', tz='UTC'),
261
                'amount': 100,
262
                'filled': 0,
263
                'sid': 133,
264
                'stop': 3.5
265
            },
266
            'event': {
267
                'dt': pd.Timestamp('2006-01-05 14:31', tz='UTC'),
268
                'volume': 2000,
269
                'price': 4.0,
270
                'high': 3.15,
271
                'low': 2.85,
272
                'sid': 133,
273
                'close': 4.0,
274
                'open': 3.5
275
            },
276
            'expected': {
277
                'transaction': {
278
                    'price': 4.001,
279
                    'dt': pd.Timestamp('2006-01-05 14:31', tz='UTC'),
280
                    'amount': 100,
281
                    'sid': 133,
282
                }
283
            }
284
        },
285
        'long | price lt stop': {
286
            'order': {
287
                'dt': pd.Timestamp('2006-01-05 14:30', tz='UTC'),
288
                'amount': 100,
289
                'filled': 0,
290
                'sid': 133,
291
                'stop': 3.6
292
            },
293
            'event': {
294
                'dt': pd.Timestamp('2006-01-05 14:31', tz='UTC'),
295
                'volume': 2000,
296
                'price': 3.5,
297
                'high': 3.15,
298
                'low': 2.85,
299
                'sid': 133,
300
                'close': 3.5,
301
                'open': 4.0
302
            },
303
            'expected': {
304
                'transaction': None
305
            }
306
        },
307
        'short | price gt stop': {
308
            'order': {
309
                'dt': pd.Timestamp('2006-01-05 14:30', tz='UTC'),
310
                'amount': -100,
311
                'filled': 0,
312
                'sid': 133,
313
                'stop': 3.4
314
            },
315
            'event': {
316
                'dt': pd.Timestamp('2006-01-05 14:31', tz='UTC'),
317
                'volume': 2000,
318
                'price': 3.5,
319
                'high': 3.15,
320
                'low': 2.85,
321
                'sid': 133,
322
                'close': 3.5,
323
                'open': 3.0
324
            },
325
            'expected': {
326
                'transaction': None
327
            }
328
        },
329
        'short | price lt stop': {
330
            'order': {
331
                'dt': pd.Timestamp('2006-01-05 14:30', tz='UTC'),
332
                'amount': -100,
333
                'filled': 0,
334
                'sid': 133,
335
                'stop': 3.5
336
            },
337
            'event': {
338
                'dt': pd.Timestamp('2006-01-05 14:31', tz='UTC'),
339
                'volume': 2000,
340
                'price': 3.0,
341
                'high': 3.15,
342
                'low': 2.85,
343
                'sid': 133,
344
                'close': 3.0,
345
                'open': 3.0
346
            },
347
            'expected': {
348
                'transaction': {
349
                    'price': 2.99925,
350
                    'dt': pd.Timestamp('2006-01-05 14:31', tz='UTC'),
351
                    'amount': -100,
352
                    'sid': 133,
353
                }
354
            }
355
        },
356
    }
357
358
    @parameterized.expand([
359
        (name, case['order'], case['event'], case['expected'])
360
        for name, case in STOP_ORDER_CASES.items()
361
    ])
362
    def test_orders_stop(self, name, order_data, event_data, expected):
363
        order = Order(**order_data)
364
        event = Event(initial_values=event_data)
365
366
        slippage_model = VolumeShareSlippage()
367
368
        try:
369
            _, txn = next(slippage_model.simulate(event, [order]))
370
        except StopIteration:
371
            txn = None
372
373
        if expected['transaction'] is None:
374
            self.assertIsNone(txn)
375
        else:
376
            self.assertIsNotNone(txn)
377
378
            for key, value in expected['transaction'].items():
379
                self.assertEquals(value, txn[key])
380
381
    def test_orders_stop_limit(self):
382
383
        events = self.gen_trades()
384
        slippage_model = VolumeShareSlippage()
385
386
        # long, does not trade
387
388
        open_orders = [
389
            Order(**{
390
                'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
391
                'amount': 100,
392
                'filled': 0,
393
                'sid': 133,
394
                'stop': 4.0,
395
                'limit': 3.0})
396
        ]
397
398
        orders_txns = list(slippage_model.simulate(
399
            events[2],
400
            open_orders
401
        ))
402
403
        self.assertEquals(len(orders_txns), 0)
404
405
        orders_txns = list(slippage_model.simulate(
406
            events[3],
407
            open_orders
408
        ))
409
410
        self.assertEquals(len(orders_txns), 0)
411
412
        # long, does not trade - impacted price worse than limit price
413
414
        open_orders = [
415
            Order(**{
416
                'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
417
                'amount': 100,
418
                'filled': 0,
419
                'sid': 133,
420
                'stop': 4.0,
421
                'limit': 3.5})
422
        ]
423
424
        orders_txns = list(slippage_model.simulate(
425
            events[2],
426
            open_orders
427
        ))
428
429
        self.assertEquals(len(orders_txns), 0)
430
431
        orders_txns = list(slippage_model.simulate(
432
            events[3],
433
            open_orders
434
        ))
435
436
        self.assertEquals(len(orders_txns), 0)
437
438
        # long, does trade
439
440
        open_orders = [
441
            Order(**{
442
                'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
443
                'amount': 100,
444
                'filled': 0,
445
                'sid': 133,
446
                'stop': 4.0,
447
                'limit': 3.6})
448
        ]
449
450
        orders_txns = list(slippage_model.simulate(
451
            events[2],
452
            open_orders
453
        ))
454
455
        self.assertEquals(len(orders_txns), 0)
456
457
        orders_txns = list(slippage_model.simulate(
458
            events[3],
459
            open_orders
460
        ))
461
462
        self.assertEquals(len(orders_txns), 1)
463
        _, txn = orders_txns[0]
464
465
        expected_txn = {
466
            'price': float(3.500875),
467
            'dt': datetime.datetime(
468
                2006, 1, 5, 14, 34, tzinfo=pytz.utc),
469
            'amount': int(100),
470
            'sid': int(133)
471
        }
472
473
        for key, value in expected_txn.items():
474
            self.assertEquals(value, txn[key])
475
476
        # short, does not trade
477
478
        open_orders = [
479
            Order(**{
480
                'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
481
                'amount': -100,
482
                'filled': 0,
483
                'sid': 133,
484
                'stop': 3.0,
485
                'limit': 4.0})
486
        ]
487
488
        orders_txns = list(slippage_model.simulate(
489
            events[0],
490
            open_orders
491
        ))
492
493
        self.assertEquals(len(orders_txns), 0)
494
495
        orders_txns = list(slippage_model.simulate(
496
            events[1],
497
            open_orders
498
        ))
499
500
        self.assertEquals(len(orders_txns), 0)
501
502
        # short, does not trade - impacted price worse than limit price
503
504
        open_orders = [
505
            Order(**{
506
                'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
507
                'amount': -100,
508
                'filled': 0,
509
                'sid': 133,
510
                'stop': 3.0,
511
                'limit': 3.5})
512
        ]
513
514
        orders_txns = list(slippage_model.simulate(
515
            events[0],
516
            open_orders
517
        ))
518
519
        self.assertEquals(len(orders_txns), 0)
520
521
        orders_txns = list(slippage_model.simulate(
522
            events[1],
523
            open_orders
524
        ))
525
526
        self.assertEquals(len(orders_txns), 0)
527
528
        # short, does trade
529
530
        open_orders = [
531
            Order(**{
532
                'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
533
                'amount': -100,
534
                'filled': 0,
535
                'sid': 133,
536
                'stop': 3.0,
537
                'limit': 3.4})
538
        ]
539
540
        orders_txns = list(slippage_model.simulate(
541
            events[0],
542
            open_orders
543
        ))
544
545
        self.assertEquals(len(orders_txns), 0)
546
547
        orders_txns = list(slippage_model.simulate(
548
            events[1],
549
            open_orders
550
        ))
551
552
        self.assertEquals(len(orders_txns), 1)
553
        _, txn = orders_txns[0]
554
555
        expected_txn = {
556
            'price': float(3.499125),
557
            'dt': datetime.datetime(
558
                2006, 1, 5, 14, 32, tzinfo=pytz.utc),
559
            'amount': int(-100),
560
            'sid': int(133)
561
        }
562
563
        for key, value in expected_txn.items():
564
            self.assertEquals(value, txn[key])
565
566
    def gen_trades(self):
567
        # create a sequence of trades
568
        events = [
569
            Event({
570
                'volume': 2000,
571
                'type': 4,
572
                'price': 3.0,
573
                'datetime': datetime.datetime(
574
                    2006, 1, 5, 14, 31, tzinfo=pytz.utc),
575
                'high': 3.15,
576
                'low': 2.85,
577
                'sid': 133,
578
                'source_id': 'test_source',
579
                'close': 3.0,
580
                'dt':
581
                datetime.datetime(2006, 1, 5, 14, 31, tzinfo=pytz.utc),
582
                'open': 3.0
583
            }),
584
            Event({
585
                'volume': 2000,
586
                'type': 4,
587
                'price': 3.5,
588
                'datetime': datetime.datetime(
589
                    2006, 1, 5, 14, 32, tzinfo=pytz.utc),
590
                'high': 3.15,
591
                'low': 2.85,
592
                'sid': 133,
593
                'source_id': 'test_source',
594
                'close': 3.5,
595
                'dt':
596
                datetime.datetime(2006, 1, 5, 14, 32, tzinfo=pytz.utc),
597
                'open': 3.0
598
            }),
599
            Event({
600
                'volume': 2000,
601
                'type': 4,
602
                'price': 4.0,
603
                'datetime': datetime.datetime(
604
                    2006, 1, 5, 14, 33, tzinfo=pytz.utc),
605
                'high': 3.15,
606
                'low': 2.85,
607
                'sid': 133,
608
                'source_id': 'test_source',
609
                'close': 4.0,
610
                'dt':
611
                datetime.datetime(2006, 1, 5, 14, 33, tzinfo=pytz.utc),
612
                'open': 3.5
613
            }),
614
            Event({
615
                'volume': 2000,
616
                'type': 4,
617
                'price': 3.5,
618
                'datetime': datetime.datetime(
619
                    2006, 1, 5, 14, 34, tzinfo=pytz.utc),
620
                'high': 3.15,
621
                'low': 2.85,
622
                'sid': 133,
623
                'source_id': 'test_source',
624
                'close': 3.5,
625
                'dt':
626
                datetime.datetime(2006, 1, 5, 14, 34, tzinfo=pytz.utc),
627
                'open': 4.0
628
            }),
629
            Event({
630
                'volume': 2000,
631
                'type': 4,
632
                'price': 3.0,
633
                'datetime': datetime.datetime(
634
                    2006, 1, 5, 14, 35, tzinfo=pytz.utc),
635
                'high': 3.15,
636
                'low': 2.85,
637
                'sid': 133,
638
                'source_id': 'test_source',
639
                'close': 3.0,
640
                'dt':
641
                datetime.datetime(2006, 1, 5, 14, 35, tzinfo=pytz.utc),
642
                'open': 3.5
643
            })
644
        ]
645
        return events
646