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