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