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