From 9062b9636a0388a1970642a0634e6cc627b32d0d Mon Sep 17 00:00:00 2001 From: fawce Date: Thu, 25 Apr 2013 22:41:44 -0400 Subject: [PATCH 1/3] MAINT: refactoring for orders api - moved Order and Blotter to zipline.finance.blotter - moved order method from AlgoSimulator to Blotter - eliminated the set_order method in algorithm - moved blotter to the algorithm --- tests/finance/test_slippage.py | 2 +- tests/test_finance.py | 10 +- tests/test_perf_tracking.py | 2 +- zipline/algorithm.py | 12 +- zipline/finance/blotter.py | 233 ++++++++++++++++++++++++++++++++ zipline/gens/tradesimulation.py | 231 +------------------------------ zipline/test_algorithms.py | 6 - zipline/utils/test_utils.py | 2 +- 8 files changed, 251 insertions(+), 247 deletions(-) create mode 100644 zipline/finance/blotter.py diff --git a/tests/finance/test_slippage.py b/tests/finance/test_slippage.py index 27850ad2..193deebd 100644 --- a/tests/finance/test_slippage.py +++ b/tests/finance/test_slippage.py @@ -25,7 +25,7 @@ from unittest import TestCase from zipline.finance.slippage import VolumeShareSlippage from zipline.protocol import Event, DATASOURCE_TYPE -from zipline.gens.tradesimulation import Order +from zipline.finance.blotter import Order class SlippageTestCase(TestCase): diff --git a/tests/test_finance.py b/tests/test_finance.py index 2f8cd7f0..f6c7dae7 100644 --- a/tests/test_finance.py +++ b/tests/test_finance.py @@ -34,7 +34,7 @@ from zipline.protocol import Event, DATASOURCE_TYPE import zipline.utils.factory as factory import zipline.utils.simfactory as simfactory -from zipline.gens.tradesimulation import Order, Blotter +from zipline.finance.blotter import Blotter from zipline.gens.composites import date_sorted_sources import zipline.finance.trading as trading @@ -315,13 +315,9 @@ class FinanceTestCase(TestCase): order_date = start_date for i in xrange(order_count): - order = Order(**{ - 'sid': sid, - 'amount': order_amount * alternator ** i, - 'dt': order_date - }) - blotter.place_order(order) + blotter.set_date(order_date) + blotter.order(sid, order_amount * alternator ** i, None, None) order_date = order_date + order_interval # move after market orders to just after market next diff --git a/tests/test_perf_tracking.py b/tests/test_perf_tracking.py index 9e2ccd1f..28775ee7 100644 --- a/tests/test_perf_tracking.py +++ b/tests/test_perf_tracking.py @@ -29,7 +29,7 @@ from zipline.finance.slippage import Transaction, create_transaction from zipline.gens.composites import date_sorted_sources from zipline.finance.trading import SimulationParameters -from zipline.gens.tradesimulation import Order +from zipline.finance.blotter import Order import zipline.finance.trading as trading from zipline.protocol import DATASOURCE_TYPE from zipline.utils.factory import create_random_simulation_parameters diff --git a/zipline/algorithm.py b/zipline/algorithm.py index b446b87d..33cbca38 100644 --- a/zipline/algorithm.py +++ b/zipline/algorithm.py @@ -38,6 +38,7 @@ from zipline.finance.slippage import ( transact_partial ) from zipline.finance.commission import PerShare, PerTrade +from zipline.finance.blotter import Blotter from zipline.finance.constants import ANNUALIZER import zipline.finance.trading as trading import zipline.protocol @@ -85,7 +86,6 @@ class TradingAlgorithm(object): capital_base : float How much capital to start with. """ - self.order = None self._portfolio = None self.datetime = None @@ -115,6 +115,8 @@ class TradingAlgorithm(object): self.sim_params = kwargs.pop('sim_params', None) + self.blotter = kwargs.pop('blotter', Blotter()) + # an algorithm subclass needs to set initialized to True when # it is fully initialized. self.initialized = False @@ -318,6 +320,9 @@ class TradingAlgorithm(object): for name, value in kwargs.items(): self._recorded_vars[name] = value + def order(self, sid, amount, limit_price=None, stop_price=None): + self.blotter.order(sid, amount, limit_price, stop_price) + @property def recorded_vars(self): return copy(self._recorded_vars) @@ -329,9 +334,6 @@ class TradingAlgorithm(object): def set_portfolio(self, portfolio): self._portfolio = portfolio - def set_order(self, order_callable): - self.order = order_callable - def set_logger(self, logger): self.logger = logger @@ -356,7 +358,7 @@ class TradingAlgorithm(object): Set the method that will be called to create a transaction from open orders and trade events. """ - self.trading_client.blotter.transact = transact + self.blotter.transact = transact def set_slippage(self, slippage): if not isinstance(slippage, (VolumeShareSlippage, FixedSlippage)): diff --git a/zipline/finance/blotter.py b/zipline/finance/blotter.py new file mode 100644 index 00000000..5285e8fb --- /dev/null +++ b/zipline/finance/blotter.py @@ -0,0 +1,233 @@ +# +# Copyright 2013 Quantopian, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import math +import uuid + +from copy import copy +from itertools import chain +from logbook import Logger +from collections import defaultdict + +from zipline.protocol import DATASOURCE_TYPE + +from zipline.finance.slippage import ( + VolumeShareSlippage, + transact_partial, + check_order_triggers +) +from zipline.finance.commission import PerShare +import zipline.utils.math_utils as zp_math + +log = Logger('Blotter') + +from zipline.utils.protocol_utils import Enum + +ORDER_STATUS = Enum( + 'OPEN', + 'FILLED' +) + + +class Blotter(object): + + def __init__(self): + self.transact = transact_partial(VolumeShareSlippage(), PerShare()) + # these orders are aggregated by sid + self.open_orders = defaultdict(list) + # keep a dict of orders by their own id + self.orders = {} + # holding orders that have come in since the last + # event. + self.new_orders = [] + self.current_dt = None + + def set_date(self, dt): + self.current_dt = dt + + def order(self, sid, amount, limit_price, stop_price): + + # something could be done with amount to further divide + # between buy by share count OR buy shares up to a dollar amount + # numeric == share count AND "$dollar.cents" == cost amount + + """ + amount > 0 :: Buy/Cover + amount < 0 :: Sell/Short + Market order: order(sid, amount) + Limit order: order(sid, amount, limit_price) + Stop order: order(sid, amount, None, stop_price) + StopLimit order: order(sid, amount, limit_price, stop_price) + """ + + # just validates amount and passes rest on to TransactionSimulator + # Tell the user if they try to buy 0 shares of something. + if amount == 0: + zero_message = "Requested to trade zero shares of {psid}".format( + psid=sid + ) + log.debug(zero_message) + # Don't bother placing orders for 0 shares. + return + + order = Order(**{ + 'dt': self.current_dt, + 'sid': sid, + 'amount': int(amount), + 'filled': 0, + 'stop': stop_price, + 'limit': limit_price + }) + + # initialized filled field. + order.filled = 0 + self.open_orders[order.sid].append(order) + self.orders[order.id] = order + self.new_orders.append(order) + + return order.id + + def transform(self, stream_in): + """ + Main generator work loop. + """ + for date, snapshot in stream_in: + results = [] + + for event in snapshot: + results.append(event) + # We only fill transactions on trade events. + if event.type == DATASOURCE_TYPE.TRADE: + txns, modified_orders = self.process_trade(event) + results.extend(chain(txns, modified_orders)) + + yield date, results + + def process_trade(self, trade_event): + if trade_event.type != DATASOURCE_TYPE.TRADE: + return [], [] + + if zp_math.tolerant_equals(trade_event.volume, 0): + # there are zero volume trade_events bc some stocks trade + # less frequently than once per minute. + return [], [] + + if trade_event.sid in self.open_orders: + orders = self.open_orders[trade_event.sid] + orders = sorted(orders, key=lambda o: o.dt) + # Only use orders for the current day or before + current_orders = filter( + lambda o: o.dt <= trade_event.dt, + orders) + else: + return [], [] + + txns = self.transact(trade_event, current_orders) + for txn in txns: + self.orders[txn.order_id].filled += txn.amount + # mark the date of the order to match the transaction + # that is filling it. + self.orders[txn.order_id].dt = txn.dt + + modified_orders = [order for order + in self.open_orders[trade_event.sid] + if order.dt == trade_event.dt] + for order in modified_orders: + if not order.open: + del self.orders[order.id] + + # update the open orders for the trade_event's sid + self.open_orders[trade_event.sid] = \ + [order for order + in self.open_orders[trade_event.sid] + if order.open] + + return txns, modified_orders + + +class Order(object): + def __init__(self, dt, sid, amount, stop=None, limit=None, filled=0): + """ + @dt - datetime.datetime that the order was placed + @sid - stock sid of the order + @amount - the number of shares to buy/sell + a positive sign indicates a buy + a negative sign indicates a sell + @filled - how many shares of the order have been filled so far + """ + # get a string representation of the uuid. + self.id = self.make_id() + self.dt = dt + self.created = dt + self.sid = sid + self.amount = amount + self.filled = filled + self.status = ORDER_STATUS.OPEN + self.stop = stop + self.limit = limit + self.stop_reached = False + self.limit_reached = False + self.direction = math.copysign(1, self.amount) + self.type = DATASOURCE_TYPE.ORDER + + def make_id(self): + return uuid.uuid4().get_hex() + + def to_dict(self): + py = copy(self.__dict__) + for field in ['type', 'direction']: + del py[field] + return py + + def check_triggers(self, event): + """ + Update internal state based on price triggers and the + trade event's price. + """ + stop_reached, limit_reached = \ + check_order_triggers(self, event) + if (stop_reached, limit_reached) \ + != (self.stop_reached, self.limit_reached): + self.dt = event.dt + self.stop_reached = stop_reached + self.limit_reached = limit_reached + + @property + def open(self): + remainder = self.amount - self.filled + if remainder != 0: + self.status = ORDER_STATUS.OPEN + else: + self.status = ORDER_STATUS.FILLED + + return self.status == ORDER_STATUS.OPEN + + @property + def triggered(self): + """ + For a market order, True. + For a stop order, True IFF stop_reached. + For a limit order, True IFF limit_reached. + For a stop-limit order, True IFF (stp_reached AND limit_reached) + """ + if self.stop and not self.stop_reached: + return False + + if self.limit and not self.limit_reached: + return False + + return True + + def __getitem__(self, name): + return self.__dict__[name] diff --git a/zipline/gens/tradesimulation.py b/zipline/gens/tradesimulation.py index 2919596a..d8d54a35 100644 --- a/zipline/gens/tradesimulation.py +++ b/zipline/gens/tradesimulation.py @@ -13,10 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. import itertools -import math -import uuid - -from copy import copy from itertools import chain from logbook import Logger, Processor from collections import defaultdict @@ -25,177 +21,8 @@ from zipline.protocol import BarData, DATASOURCE_TYPE from zipline.finance.performance import PerformanceTracker from zipline.gens.utils import hash_args -from zipline.finance.slippage import ( - VolumeShareSlippage, - transact_partial, - check_order_triggers -) -from zipline.finance.commission import PerShare -import zipline.utils.math_utils as zp_math - log = Logger('Trade Simulation') -from zipline.utils.protocol_utils import Enum - -ORDER_STATUS = Enum( - 'OPEN', - 'FILLED' -) - - -class Blotter(object): - - def __init__(self): - self.transact = transact_partial(VolumeShareSlippage(), PerShare()) - # these orders are aggregated by sid - self.open_orders = defaultdict(list) - # keep a dict of orders by their own id - self.orders = {} - # holding orders that have come in since the last - # event. - self.new_orders = [] - - def place_order(self, order): - # initialized filled field. - order.filled = 0 - self.open_orders[order.sid].append(order) - self.orders[order.id] = order - self.new_orders.append(order) - - def transform(self, stream_in): - """ - Main generator work loop. - """ - for date, snapshot in stream_in: - results = [] - - for event in snapshot: - results.append(event) - # We only fill transactions on trade events. - if event.type == DATASOURCE_TYPE.TRADE: - txns, modified_orders = self.process_trade(event) - results.extend(chain(txns, modified_orders)) - - yield date, results - - def process_trade(self, trade_event): - if trade_event.type != DATASOURCE_TYPE.TRADE: - return [], [] - - if zp_math.tolerant_equals(trade_event.volume, 0): - # there are zero volume trade_events bc some stocks trade - # less frequently than once per minute. - return [], [] - - if trade_event.sid in self.open_orders: - orders = self.open_orders[trade_event.sid] - orders = sorted(orders, key=lambda o: o.dt) - # Only use orders for the current day or before - current_orders = filter( - lambda o: o.dt <= trade_event.dt, - orders) - else: - return [], [] - - txns = self.transact(trade_event, current_orders) - for txn in txns: - self.orders[txn.order_id].filled += txn.amount - # mark the date of the order to match the transaction - # that is filling it. - self.orders[txn.order_id].dt = txn.dt - - modified_orders = [order for order - in self.open_orders[trade_event.sid] - if order.dt == trade_event.dt] - for order in modified_orders: - if not order.open: - del self.orders[order.id] - - # update the open orders for the trade_event's sid - self.open_orders[trade_event.sid] = \ - [order for order - in self.open_orders[trade_event.sid] - if order.open] - - return txns, modified_orders - - -class Order(object): - def __init__(self, dt, sid, amount, stop=None, limit=None, filled=0): - """ - @dt - datetime.datetime that the order was placed - @sid - stock sid of the order - @amount - the number of shares to buy/sell - a positive sign indicates a buy - a negative sign indicates a sell - @filled - how many shares of the order have been filled so far - """ - # get a string representation of the uuid. - self.id = self.make_id() - self.dt = dt - self.created = dt - self.sid = sid - self.amount = amount - self.filled = filled - self.status = ORDER_STATUS.OPEN - self.stop = stop - self.limit = limit - self.stop_reached = False - self.limit_reached = False - self.direction = math.copysign(1, self.amount) - self.type = DATASOURCE_TYPE.ORDER - - def make_id(self): - return uuid.uuid4().get_hex() - - def to_dict(self): - py = copy(self.__dict__) - for field in ['type', 'direction']: - del py[field] - return py - - def check_triggers(self, event): - """ - Update internal state based on price triggers and the - trade event's price. - """ - stop_reached, limit_reached = \ - check_order_triggers(self, event) - if (stop_reached, limit_reached) \ - != (self.stop_reached, self.limit_reached): - self.dt = event.dt - self.stop_reached = stop_reached - self.limit_reached = limit_reached - - @property - def open(self): - remainder = self.amount - self.filled - if remainder != 0: - self.status = ORDER_STATUS.OPEN - else: - self.status = ORDER_STATUS.FILLED - - return self.status == ORDER_STATUS.OPEN - - @property - def triggered(self): - """ - For a market order, True. - For a stop order, True IFF stop_reached. - For a limit order, True IFF limit_reached. - For a stop-limit order, True IFF (stp_reached AND limit_reached) - """ - if self.stop and not self.stop_reached: - return False - - if self.limit and not self.limit_reached: - return False - - return True - - def __getitem__(self, name): - return self.__dict__[name] - class AlgorithmSimulator(object): @@ -211,15 +38,13 @@ class AlgorithmSimulator(object): """ return self.__class__.__name__ + hash_args() - def __init__(self, algo, sim_params, blotter=None): + def __init__(self, algo, sim_params): # ============== # Simulation # Param Setup # ============== self.sim_params = sim_params - if not blotter: - self.blotter = Blotter() # ============== # Perf Tracker @@ -239,10 +64,6 @@ class AlgorithmSimulator(object): second=0, microsecond=0) - # Monkey patch the user algorithm to place orders in the - # TransactionSimulator's order book and use our logger. - self.algo.set_order(self.order) - # ============== # Snapshot Setup # ============== @@ -268,49 +89,6 @@ class AlgorithmSimulator(object): record.extra['algo_dt'] = self.snapshot_dt self.processor = Processor(inject_algo_dt) - def order(self, sid, amount, limit_price=None, stop_price=None): - - # something could be done with amount to further divide - # between buy by share count OR buy shares up to a dollar amount - # numeric == share count AND "$dollar.cents" == cost amount - - """ - amount > 0 :: Buy/Cover - amount < 0 :: Sell/Short - Market order: order(sid, amount) - Limit order: order(sid, amount, limit_price) - Stop order: order(sid, amount, None, stop_price) - StopLimit order: order(sid, amount, limit_price, stop_price) - """ - - # just validates amount and passes rest on to TransactionSimulator - # Tell the user if they try to buy 0 shares of something. - if amount == 0: - zero_message = "Requested to trade zero shares of {psid}".format( - psid=sid - ) - log.debug(zero_message) - # Don't bother placing orders for 0 shares. - return - - order = Order(**{ - 'dt': self.simulation_dt, - 'sid': sid, - 'amount': int(amount), - 'filled': 0, - 'stop': stop_price, - 'limit': limit_price - }) - - # Add non-zero orders to the order book. - # !!!IMPORTANT SIDE-EFFECT!!! - # This modifies the internal state of the blotter - # so that it can fill the placed order when it - # receives its next message. - self.blotter.place_order(order) - - return order.id - def transform(self, stream_in): """ Main generator work loop. @@ -332,6 +110,7 @@ class AlgorithmSimulator(object): bm_updated = False for date, snapshot in stream: self.perf_tracker.set_date(date) + self.algo.blotter.set_date(date) # If we're still in the warmup period. Use the event to # update our universe, but don't yield any perf messages, # and don't send a snapshot to handle_data. @@ -351,7 +130,7 @@ class AlgorithmSimulator(object): updated = True if event.type == DATASOURCE_TYPE.BENCHMARK: bm_updated = True - txns, orders = self.blotter.process_trade(event) + txns, orders = self.algo.blotter.process_trade(event) for data in chain([event], txns, orders): self.perf_tracker.process_event(data) @@ -368,9 +147,9 @@ class AlgorithmSimulator(object): # above through perf tracker before emitting # the perf packet, so that the perf includes # placed orders - for order in self.blotter.new_orders: + for order in self.algo.blotter.new_orders: self.perf_tracker.process_event(order) - self.blotter.new_orders = [] + self.algo.blotter.new_orders = [] # The benchmark is our internal clock. When it # updates, we need to emit a performance message. diff --git a/zipline/test_algorithms.py b/zipline/test_algorithms.py index 09fc45d0..c5ba42be 100644 --- a/zipline/test_algorithms.py +++ b/zipline/test_algorithms.py @@ -148,12 +148,6 @@ class ExceptionAlgorithm(TradingAlgorithm): else: pass - def set_order(self, order_callable): - if self.throw_from == "set_order": - raise Exception("Algo exception in set_order") - else: - pass - def set_portfolio(self, portfolio): if self.throw_from == "set_portfolio": raise Exception("Algo exception in set_portfolio") diff --git a/zipline/utils/test_utils.py b/zipline/utils/test_utils.py index 32de1d00..5c674389 100644 --- a/zipline/utils/test_utils.py +++ b/zipline/utils/test_utils.py @@ -3,7 +3,7 @@ import blist from zipline.utils.date_utils import EPOCH from itertools import izip from logbook import FileHandler -from zipline.gens.tradesimulation import ORDER_STATUS +from zipline.finance.blotter import ORDER_STATUS def setup_logger(test, path='test.log'): From afef4ea34ca1d74d31e3eb412a61722547fa5c96 Mon Sep 17 00:00:00 2001 From: fawce Date: Thu, 25 Apr 2013 23:11:51 -0400 Subject: [PATCH 2/3] ENH: added cancel function - removed vestigial methods - removed code that drops filled orders from memory --- zipline/finance/blotter.py | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/zipline/finance/blotter.py b/zipline/finance/blotter.py index 5285e8fb..d327219b 100644 --- a/zipline/finance/blotter.py +++ b/zipline/finance/blotter.py @@ -16,7 +16,6 @@ import math import uuid from copy import copy -from itertools import chain from logbook import Logger from collections import defaultdict @@ -53,6 +52,12 @@ class Blotter(object): self.new_orders = [] self.current_dt = None + def cancel_order(self, order_id): + order = self.orders[order_id] + if order.open: + order_list = self.open_orders[order.sid] + order_list.remove(order) + def set_date(self, dt): self.current_dt = dt @@ -98,22 +103,6 @@ class Blotter(object): return order.id - def transform(self, stream_in): - """ - Main generator work loop. - """ - for date, snapshot in stream_in: - results = [] - - for event in snapshot: - results.append(event) - # We only fill transactions on trade events. - if event.type == DATASOURCE_TYPE.TRADE: - txns, modified_orders = self.process_trade(event) - results.extend(chain(txns, modified_orders)) - - yield date, results - def process_trade(self, trade_event): if trade_event.type != DATASOURCE_TYPE.TRADE: return [], [] @@ -143,9 +132,13 @@ class Blotter(object): modified_orders = [order for order in self.open_orders[trade_event.sid] if order.dt == trade_event.dt] - for order in modified_orders: - if not order.open: - del self.orders[order.id] + + # TODO: without this limit, orders can grow in memory + # unchecked. Taking it out so that it is easy to find + # orders by id during a running test. + # for order in modified_orders: + # if not order.open: + # del self.orders[order.id] # update the open orders for the trade_event's sid self.open_orders[trade_event.sid] = \ From f3cfc9623d67bc4160039c2088dd748a79cd6a67 Mon Sep 17 00:00:00 2001 From: fawce Date: Fri, 26 Apr 2013 14:21:55 -0400 Subject: [PATCH 3/3] ENH: new order management methods: - get_open_orders - get_order - cancel --- zipline/algorithm.py | 2 +- zipline/finance/blotter.py | 45 +++++++++++++++++++++++--------------- zipline/protocol.py | 4 ++++ 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/zipline/algorithm.py b/zipline/algorithm.py index 33cbca38..9f8b325f 100644 --- a/zipline/algorithm.py +++ b/zipline/algorithm.py @@ -321,7 +321,7 @@ class TradingAlgorithm(object): self._recorded_vars[name] = value def order(self, sid, amount, limit_price=None, stop_price=None): - self.blotter.order(sid, amount, limit_price, stop_price) + return self.blotter.order(sid, amount, limit_price, stop_price) @property def recorded_vars(self): diff --git a/zipline/finance/blotter.py b/zipline/finance/blotter.py index d327219b..af4d5ad2 100644 --- a/zipline/finance/blotter.py +++ b/zipline/finance/blotter.py @@ -20,6 +20,7 @@ from logbook import Logger from collections import defaultdict from zipline.protocol import DATASOURCE_TYPE +from zipline.protocol import Order as zpOrder from zipline.finance.slippage import ( VolumeShareSlippage, @@ -35,7 +36,8 @@ from zipline.utils.protocol_utils import Enum ORDER_STATUS = Enum( 'OPEN', - 'FILLED' + 'FILLED', + 'CANCELLED' ) @@ -52,12 +54,6 @@ class Blotter(object): self.new_orders = [] self.current_dt = None - def cancel_order(self, order_id): - order = self.orders[order_id] - if order.open: - order_list = self.open_orders[order.sid] - order_list.remove(order) - def set_date(self, dt): self.current_dt = dt @@ -103,6 +99,21 @@ class Blotter(object): return order.id + def cancel(self, order_id): + cur_order = self.orders[order_id] + if cur_order.open: + 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.status = ORDER_STATUS.CANCELLED + cur_order.dt = self.current_dt + # we want this order's new status to be relayed out + # along with newly placed orders. + self.new_orders.append(cur_order) + def process_trade(self, trade_event): if trade_event.type != DATASOURCE_TYPE.TRADE: return [], [] @@ -133,13 +144,6 @@ class Blotter(object): in self.open_orders[trade_event.sid] if order.dt == trade_event.dt] - # TODO: without this limit, orders can grow in memory - # unchecked. Taking it out so that it is easy to find - # orders by id during a running test. - # for order in modified_orders: - # if not order.open: - # del self.orders[order.id] - # update the open orders for the trade_event's sid self.open_orders[trade_event.sid] = \ [order for order @@ -183,6 +187,11 @@ class Order(object): del py[field] return py + def to_api_obj(self): + pydict = self.to_dict() + obj = zpOrder(initial_values=pydict) + return obj + def check_triggers(self, event): """ Update internal state based on price triggers and the @@ -198,6 +207,9 @@ class Order(object): @property def open(self): + if self.status == ORDER_STATUS.CANCELLED: + return False + remainder = self.amount - self.filled if remainder != 0: self.status = ORDER_STATUS.OPEN @@ -212,7 +224,7 @@ class Order(object): For a market order, True. For a stop order, True IFF stop_reached. For a limit order, True IFF limit_reached. - For a stop-limit order, True IFF (stp_reached AND limit_reached) + For a stop-limit order, True IFF (stop_reached AND limit_reached) """ if self.stop and not self.stop_reached: return False @@ -221,6 +233,3 @@ class Order(object): return False return True - - def __getitem__(self, name): - return self.__dict__[name] diff --git a/zipline/protocol.py b/zipline/protocol.py index f0e79244..ec052721 100644 --- a/zipline/protocol.py +++ b/zipline/protocol.py @@ -62,6 +62,10 @@ class Event(object): return "Event({0})".format(self.__dict__) +class Order(Event): + pass + + class Portfolio(object): def __init__(self, initial_values=None):