From ec20b3be8aac8f9e367b274f3ac67f8cc9da1d06 Mon Sep 17 00:00:00 2001 From: John Ricklefs Date: Fri, 6 Jun 2014 11:32:17 -0400 Subject: [PATCH] ENH: Add new order statuses for broker integration --- zipline/finance/blotter.py | 65 ++++++++++++++++++++++++++++---------- zipline/protocol.py | 3 +- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/zipline/finance/blotter.py b/zipline/finance/blotter.py index 3415c466..c2283cab 100644 --- a/zipline/finance/blotter.py +++ b/zipline/finance/blotter.py @@ -38,10 +38,11 @@ from zipline.utils.protocol_utils import Enum ORDER_STATUS = Enum( 'OPEN', 'FILLED', - 'CANCELLED' + 'CANCELLED', + 'REJECTED', + 'HELD', ) - class Blotter(object): def __init__(self): @@ -84,9 +85,9 @@ class Blotter(object): amount > 0 :: Buy/Cover amount < 0 :: Sell/Short Market order: order(sid, amount) - Limit order: order(sid, amount, LimitOrder(price)) - Stop order: order(sid, amount, StopOrder(price)) - StopLimit order: order(sid, amount, StopLimitOrder(price)) + Limit order: order(sid, amount, style=LimitOrder(limit_price)) + Stop order: order(sid, amount, style=StopOrder(stop_price)) + StopLimit order: order(sid, amount, style=StopLimitOrder(limit_price, stop_price)) """ if amount == 0: # Don't bother placing orders for 0 shares. @@ -113,20 +114,42 @@ class Blotter(object): return order.id - def cancel(self, order_id): + def cancel(self, order_id, rejected=False, reason=False): if order_id not in self.orders: return cur_order = self.orders[order_id] - if cur_order.open: + # if the order has already been rejected, do not update its status to + # cancelled + if cur_order.broker_status == ORDER_STATUS.REJECTED: + return + + if cur_order.open or rejected: order_list = self.open_orders[cur_order.sid] if cur_order in order_list: order_list.remove(cur_order) if cur_order in self.new_orders: self.new_orders.remove(cur_order) - cur_order.cancel() + cur_order.cancel(rejected) cur_order.dt = self.current_dt + if reason: + cur_order.reason = reason + # we want this order's new status to be relayed out + # along with newly placed orders. + self.new_orders.append(cur_order) + + def hold(self, order_id, reason): + if order_id not in self.orders: + return + + cur_order = self.orders[order_id] + if cur_order.open: + if cur_order in self.new_orders: + self.new_orders.remove(cur_order) + cur_order.hold() + cur_order.dt = self.current_dt + cur_order.reason = reason # we want this order's new status to be relayed out # along with newly placed orders. self.new_orders.append(cur_order) @@ -208,12 +231,13 @@ class Order(object): # get a string representation of the uuid. self.id = id or self.make_id() self.dt = dt + self.reason = '' self.created = dt self.sid = sid self.amount = amount self.filled = filled self.commission = commission - self._cancelled = False + self.broker_status = ORDER_STATUS.OPEN self.stop = stop self.limit = limit self.stop_reached = False @@ -226,7 +250,7 @@ class Order(object): def to_dict(self): py = copy(self.__dict__) - for field in ['type', 'direction', '_cancelled']: + for field in ['type', 'direction']: del py[field] py['status'] = self.status return py @@ -274,18 +298,27 @@ class Order(object): @property def status(self): - if self._cancelled: + if self.broker_status in [ORDER_STATUS.REJECTED, + ORDER_STATUS.CANCELLED]: return ORDER_STATUS.CANCELLED + elif not self.open_amount: + self.broker_status = ORDER_STATUS.FILLED + return ORDER_STATUS.FILLED + else: + return ORDER_STATUS.OPEN - return ORDER_STATUS.FILLED \ - if not self.open_amount else ORDER_STATUS.OPEN + def cancel(self, rejected=False): + if rejected: + self.broker_status = ORDER_STATUS.REJECTED + else: + self.broker_status = ORDER_STATUS.CANCELLED - def cancel(self): - self._cancelled = True + def hold(self): + self.broker_status = ORDER_STATUS.HELD @property def open(self): - return self.status == ORDER_STATUS.OPEN + return self.status in [ORDER_STATUS.OPEN, ORDER_STATUS.HELD] @property def triggered(self): diff --git a/zipline/protocol.py b/zipline/protocol.py index 9aa7fa5c..726a388b 100644 --- a/zipline/protocol.py +++ b/zipline/protocol.py @@ -32,7 +32,8 @@ DATASOURCE_TYPE = Enum( 'DONE', 'CUSTOM', 'BENCHMARK', - 'COMMISSION' + 'COMMISSION', + 'MESSAGE' ) # Expected fields/index values for a dividend Series.