zipline.finance.Order.open()   A
last analyzed

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, event):
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(event)
89
        if (stop_reached, limit_reached) \
90
                != (self.stop_reached, self.limit_reached):
91
            self.dt = event.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, event):
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 event.price >= self.stop:
133
                sl_stop_reached = True
134
                if event.price <= self.limit:
135
                    limit_reached = True
136
        elif order_type == SELL | STOP | LIMIT:
137
            if event.price <= self.stop:
138
                sl_stop_reached = True
139
                if event.price >= self.limit:
140
                    limit_reached = True
141
        elif order_type == BUY | STOP:
142
            if event.price >= self.stop:
143
                stop_reached = True
144
        elif order_type == SELL | STOP:
145
            if event.price <= self.stop:
146
                stop_reached = True
147
        elif order_type == BUY | LIMIT:
148
            if event.price <= self.limit:
149
                limit_reached = True
150
        elif order_type == SELL | LIMIT:
151
            # This is a SELL LIMIT order
152
            if event.price >= self.limit:
153
                limit_reached = True
154
155
        return (stop_reached, limit_reached, sl_stop_reached)
156
157
    def handle_split(self, split_event):
158
        ratio = split_event.ratio
159
160
        # update the amount, limit_price, and stop_price
161
        # by the split's ratio
162
163
        # info here: http://finra.complinet.com/en/display/display_plain.html?
164
        # rbid=2403&element_id=8950&record_id=12208&print=1
165
166
        # new_share_amount = old_share_amount / ratio
167
        # new_price = old_price * ratio
168
169
        self.amount = int(self.amount / ratio)
170
171
        if self.limit is not None:
172
            self.limit = round(self.limit * ratio, 2)
173
174
        if self.stop is not None:
175
            self.stop = round(self.stop * ratio, 2)
176
177
    @property
178
    def status(self):
179
        if not self.open_amount:
180
            return ORDER_STATUS.FILLED
181
        elif self._status == ORDER_STATUS.HELD and self.filled:
182
            return ORDER_STATUS.OPEN
183
        else:
184
            return self._status
185
186
    @status.setter
187
    def status(self, status):
188
        self._status = status
189
190
    def cancel(self):
191
        self.status = ORDER_STATUS.CANCELLED
192
193
    def reject(self, reason=''):
194
        self.status = ORDER_STATUS.REJECTED
195
        self.reason = reason
196
197
    def hold(self, reason=''):
198
        self.status = ORDER_STATUS.HELD
199
        self.reason = reason
200
201
    @property
202
    def open(self):
203
        return self.status in [ORDER_STATUS.OPEN, ORDER_STATUS.HELD]
204
205
    @property
206
    def triggered(self):
207
        """
208
        For a market order, True.
209
        For a stop order, True IFF stop_reached.
210
        For a limit order, True IFF limit_reached.
211
        """
212
        if self.stop is not None and not self.stop_reached:
213
            return False
214
215
        if self.limit is not None and not self.limit_reached:
216
            return False
217
218
        return True
219
220
    @property
221
    def open_amount(self):
222
        return self.amount - self.filled
223
224
    def __repr__(self):
225
        """
226
        String representation for this object.
227
        """
228
        return "Order(%s)" % self.to_dict().__repr__()
229
230
    def __unicode__(self):
231
        """
232
        Unicode representation for this object.
233
        """
234
        return text_type(repr(self))
235
236
    def __getstate__(self):
237
238
        state_dict = \
239
            {k: v for k, v in iteritems(self.__dict__)
240
                if not k.startswith('_')}
241
242
        state_dict['_status'] = self._status
243
244
        STATE_VERSION = 1
245
        state_dict[VERSION_LABEL] = STATE_VERSION
246
247
        return state_dict
248
249
    def __setstate__(self, state):
250
251
        OLDEST_SUPPORTED_STATE = 1
252
        version = state.pop(VERSION_LABEL)
253
254
        if version < OLDEST_SUPPORTED_STATE:
255
            raise BaseException("Order saved state is too old.")
256
257
        self.__dict__.update(state)
258