Completed
Pull Request — master (#858)
by Eddie
05:34 queued 02:25
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

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