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

zipline.finance.Order.reject()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 1
dl 0
loc 3
rs 10
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 copy import copy
16
import math
17
import uuid
18
19
from six import text_type, iteritems
20
21
import zipline.protocol as zp
22
from zipline.utils.serialization_utils import VERSION_LABEL
23
from zipline.utils.enum import enum
24
25
ORDER_STATUS = enum(
26
    'OPEN',
27
    'FILLED',
28
    'CANCELLED',
29
    'REJECTED',
30
    'HELD',
31
)
32
33
SELL = 1 << 0
34
BUY = 1 << 1
35
STOP = 1 << 2
36
LIMIT = 1 << 3
37
38
39
class Order(object):
40
    def __init__(self, dt, sid, amount, stop=None, limit=None, filled=0,
41
                 commission=None, id=None):
42
        """
43
        @dt - datetime.datetime that the order was placed
44
        @sid - stock sid of the order
45
        @amount - the number of shares to buy/sell
46
                  a positive sign indicates a buy
47
                  a negative sign indicates a sell
48
        @filled - how many shares of the order have been filled so far
49
        """
50
        # get a string representation of the uuid.
51
        self.id = id or self.make_id()
52
        self.dt = dt
53
        self.reason = None
54
        self.created = dt
55
        self.sid = sid
56
        self.amount = amount
57
        self.filled = filled
58
        self.commission = commission
59
        self._status = ORDER_STATUS.OPEN
60
        self.stop = stop
61
        self.limit = limit
62
        self.stop_reached = False
63
        self.limit_reached = False
64
        self.direction = math.copysign(1, self.amount)
65
        self.type = zp.DATASOURCE_TYPE.ORDER
66
67
    def make_id(self):
68
        return uuid.uuid4().hex
69
70
    def to_dict(self):
71
        py = copy(self.__dict__)
72
        for field in ['type', 'direction', '_status']:
73
            del py[field]
74
        py['status'] = self.status
75
        return py
76
77
    def to_api_obj(self):
78
        pydict = self.to_dict()
79
        obj = zp.Order(initial_values=pydict)
80
        return obj
81
82
    def check_triggers(self, price, dt):
83
        """
84
        Update internal state based on price triggers and the
85
        trade event's price.
86
        """
87
        stop_reached, limit_reached, sl_stop_reached = \
88
            self.check_order_triggers(price)
89
        if (stop_reached, limit_reached) \
90
                != (self.stop_reached, self.limit_reached):
91
            self.dt = dt
92
        self.stop_reached = stop_reached
93
        self.limit_reached = limit_reached
94
        if sl_stop_reached:
95
            # Change the STOP LIMIT order into a LIMIT order
96
            self.stop = None
97
98
    def check_order_triggers(self, current_price):
99
        """
100
        Given an order and a trade event, return a tuple of
101
        (stop_reached, limit_reached).
102
        For market orders, will return (False, False).
103
        For stop orders, limit_reached will always be False.
104
        For limit orders, stop_reached will always be False.
105
        For stop limit orders a Boolean is returned to flag
106
        that the stop has been reached.
107
108
        Orders that have been triggered already (price targets reached),
109
        the order's current values are returned.
110
        """
111
        if self.triggered:
112
            return (self.stop_reached, self.limit_reached, False)
113
114
        stop_reached = False
115
        limit_reached = False
116
        sl_stop_reached = False
117
118
        order_type = 0
119
120
        if self.amount > 0:
121
            order_type |= BUY
122
        else:
123
            order_type |= SELL
124
125
        if self.stop is not None:
126
            order_type |= STOP
127
128
        if self.limit is not None:
129
            order_type |= LIMIT
130
131
        if order_type == BUY | STOP | LIMIT:
132
            if current_price >= self.stop:
133
                sl_stop_reached = True
134
                if current_price <= self.limit:
135
                    limit_reached = True
136
        elif order_type == SELL | STOP | LIMIT:
137
            if current_price <= self.stop:
138
                sl_stop_reached = True
139
                if current_price >= self.limit:
140
                    limit_reached = True
141
        elif order_type == BUY | STOP:
142
            if current_price >= self.stop:
143
                stop_reached = True
144
        elif order_type == SELL | STOP:
145
            if current_price <= self.stop:
146
                stop_reached = True
147
        elif order_type == BUY | LIMIT:
148
            if current_price <= self.limit:
149
                limit_reached = True
150
        elif order_type == SELL | LIMIT:
151
            # This is a SELL LIMIT order
152
            if current_price >= self.limit:
153
                limit_reached = True
154
155
        return (stop_reached, limit_reached, sl_stop_reached)
156
157
    def handle_split(self, ratio):
158
        # update the amount, limit_price, and stop_price
159
        # by the split's ratio
160
161
        # info here: http://finra.complinet.com/en/display/display_plain.html?
162
        # rbid=2403&element_id=8950&record_id=12208&print=1
163
164
        # new_share_amount = old_share_amount / ratio
165
        # new_price = old_price * ratio
166
167
        self.amount = int(self.amount / ratio)
168
169
        if self.limit is not None:
170
            self.limit = round(self.limit * ratio, 2)
171
172
        if self.stop is not None:
173
            self.stop = round(self.stop * ratio, 2)
174
175
    @property
176
    def status(self):
177
        if not self.open_amount:
178
            return ORDER_STATUS.FILLED
179
        elif self._status == ORDER_STATUS.HELD and self.filled:
180
            return ORDER_STATUS.OPEN
181
        else:
182
            return self._status
183
184
    @status.setter
185
    def status(self, status):
186
        self._status = status
187
188
    def cancel(self):
189
        self.status = ORDER_STATUS.CANCELLED
190
191
    def reject(self, reason=''):
192
        self.status = ORDER_STATUS.REJECTED
193
        self.reason = reason
194
195
    def hold(self, reason=''):
196
        self.status = ORDER_STATUS.HELD
197
        self.reason = reason
198
199
    @property
200
    def open(self):
201
        return self.status in [ORDER_STATUS.OPEN, ORDER_STATUS.HELD]
202
203
    @property
204
    def triggered(self):
205
        """
206
        For a market order, True.
207
        For a stop order, True IFF stop_reached.
208
        For a limit order, True IFF limit_reached.
209
        """
210
        if self.stop is not None and not self.stop_reached:
211
            return False
212
213
        if self.limit is not None and not self.limit_reached:
214
            return False
215
216
        return True
217
218
    @property
219
    def open_amount(self):
220
        return self.amount - self.filled
221
222
    def __repr__(self):
223
        """
224
        String representation for this object.
225
        """
226
        return "Order(%s)" % self.to_dict().__repr__()
227
228
    def __unicode__(self):
229
        """
230
        Unicode representation for this object.
231
        """
232
        return text_type(repr(self))
233
234
    def __getstate__(self):
235
236
        state_dict = \
237
            {k: v for k, v in iteritems(self.__dict__)
238
                if not k.startswith('_')}
239
240
        state_dict['_status'] = self._status
241
242
        STATE_VERSION = 1
243
        state_dict[VERSION_LABEL] = STATE_VERSION
244
245
        return state_dict
246
247
    def __setstate__(self, state):
248
249
        OLDEST_SUPPORTED_STATE = 1
250
        version = state.pop(VERSION_LABEL)
251
252
        if version < OLDEST_SUPPORTED_STATE:
253
            raise BaseException("Order saved state is too old.")
254
255
        self.__dict__.update(state)
256