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

zipline.finance.check_order_triggers()   F

Complexity

Conditions 19

Size

Total Lines 58

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 19
dl 0
loc 58
rs 3.2647

6 Methods

Rating   Name   Duplication   Size   Complexity  
A zipline.finance.SlippageModel.process_order() 0 3 1
B zipline.finance.SlippageModel.simulate() 0 20 6
A zipline.finance.SlippageModel.__call__() 0 2 1
A zipline.finance.SlippageModel.volume_for_bar() 0 3 1
A zipline.finance.VolumeShareSlippage.__init__() 0 7 1
A zipline.finance.SlippageModel.__init__() 0 2 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like zipline.finance.check_order_triggers() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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
from __future__ import division
16
17
import abc
18
19
import math
20
21
from copy import copy
22
23
from six import with_metaclass
24
25
from zipline.finance.transaction import create_transaction
26
from zipline.utils.serialization_utils import (
27
    VERSION_LABEL
28
)
29
30
SELL = 1 << 0
31
BUY = 1 << 1
32
STOP = 1 << 2
33
LIMIT = 1 << 3
34
35
36
class LiquidityExceeded(Exception):
37
    pass
38
39
40
DEFAULT_VOLUME_SLIPPAGE_BAR_LIMIT = 0.025
41
42
43
class SlippageModel(with_metaclass(abc.ABCMeta)):
44
    def __init__(self):
45
        self._volume_for_bar = 0
46
47
    @property
48
    def volume_for_bar(self):
49
        return self._volume_for_bar
50
51
    @abc.abstractproperty
52
    def process_order(self, price, volume, order, dt):
53
        pass
54
55
    def simulate(self, current_orders, dt, price, volume):
56
        self._volume_for_bar = 0
57
58
        for order in current_orders:
59
60
            if order.open_amount == 0:
61
                continue
62
63
            order.check_triggers(price, dt)
64
            if not order.triggered:
65
                continue
66
67
            try:
68
                txn = self.process_order(order, price, volume, dt)
69
            except LiquidityExceeded:
70
                break
71
72
            if txn:
73
                self._volume_for_bar += abs(txn.amount)
74
                yield order, txn
75
76
    def __call__(self, current_orders, dt, price, volume, **kwargs):
77
        return self.simulate(current_orders, dt, price, volume, **kwargs)
78
79
80
class VolumeShareSlippage(SlippageModel):
81
82
    def __init__(self, volume_limit=DEFAULT_VOLUME_SLIPPAGE_BAR_LIMIT,
83
                 price_impact=0.1):
84
85
        self.volume_limit = volume_limit
86
        self.price_impact = price_impact
87
88
        super(VolumeShareSlippage, self).__init__()
89
90
    def __repr__(self):
91
        return """
92
{class_name}(
93
    volume_limit={volume_limit},
94
    price_impact={price_impact})
95
""".strip().format(class_name=self.__class__.__name__,
96
                   volume_limit=self.volume_limit,
97
                   price_impact=self.price_impact)
98
99
    def process_order(self, order, price, volume, dt):
100
        max_volume = self.volume_limit * volume
101
102
        # price impact accounts for the total volume of transactions
103
        # created against the current minute bar
104
        remaining_volume = max_volume - self.volume_for_bar
105
        if remaining_volume < 1:
106
            # we can't fill any more transactions
107
            raise LiquidityExceeded()
108
109
        # the current order amount will be the min of the
110
        # volume available in the bar or the open amount.
111
        cur_volume = int(min(remaining_volume, abs(order.open_amount)))
112
113
        if cur_volume < 1:
114
            return
115
116
        # tally the current amount into our total amount ordered.
117
        # total amount will be used to calculate price impact
118
        total_volume = self.volume_for_bar + cur_volume
119
120
        volume_share = min(total_volume / volume,
121
                           self.volume_limit)
122
123
        simulated_impact = volume_share ** 2 \
124
            * math.copysign(self.price_impact, order.direction) \
125
            * price
126
        impacted_price = price + simulated_impact
127
128
        if order.limit:
129
            # this is tricky! if an order with a limit price has reached
130
            # the limit price, we will try to fill the order. do not fill
131
            # these shares if the impacted price is worse than the limit
132
            # price. return early to avoid creating the transaction.
133
134
            # buy order is worse if the impacted price is greater than
135
            # the limit price. sell order is worse if the impacted price
136
            # is less than the limit price
137
            if (order.direction > 0 and impacted_price > order.limit) or \
138
                    (order.direction < 0 and impacted_price < order.limit):
139
                return
140
141
        return create_transaction(
142
            order.sid,
143
            dt,
144
            order,
145
            impacted_price,
146
            math.copysign(cur_volume, order.direction)
147
        )
148
149
    def __getstate__(self):
150
151
        state_dict = copy(self.__dict__)
152
153
        STATE_VERSION = 1
154
        state_dict[VERSION_LABEL] = STATE_VERSION
155
156
        return state_dict
157
158
    def __setstate__(self, state):
159
160
        OLDEST_SUPPORTED_STATE = 1
161
        version = state.pop(VERSION_LABEL)
162
163
        if version < OLDEST_SUPPORTED_STATE:
164
            raise BaseException("VolumeShareSlippage saved state is too old.")
165
166
        self.__dict__.update(state)
167
168
169
class FixedSlippage(SlippageModel):
170
171
    def __init__(self, spread=0.0):
172
        """
173
        Use the fixed slippage model, which will just add/subtract
174
        a specified spread spread/2 will be added on buys and subtracted
175
        on sells per share
176
        """
177
        self.spread = spread
178
179
    def process_order(self, order, price, volume, dt):
180
        return create_transaction(
181
            order.sid,
182
            dt,
183
            order,
184
            price + (self.spread / 2.0 * order.direction),
185
            order.amount,
186
        )
187
188
    def __getstate__(self):
189
190
        state_dict = copy(self.__dict__)
191
192
        STATE_VERSION = 1
193
        state_dict[VERSION_LABEL] = STATE_VERSION
194
195
        return state_dict
196
197
    def __setstate__(self, state):
198
199
        OLDEST_SUPPORTED_STATE = 1
200
        version = state.pop(VERSION_LABEL)
201
202
        if version < OLDEST_SUPPORTED_STATE:
203
            raise BaseException("FixedSlippage saved state is too old.")
204
205
        self.__dict__.update(state)
206