Skip to content

Commit

Permalink
Orders now get an id only after getting submitted.
Browse files Browse the repository at this point in the history
  • Loading branch information
gbeced committed Jun 11, 2014
1 parent 44bfa22 commit f101cc8
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 57 deletions.
59 changes: 32 additions & 27 deletions pyalgotrade/broker/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ def roundQuantity(self, quantity):


######################################################################
## Orders
## http://stocks.about.com/od/tradingbasics/a/markords.htm
## http://www.interactivebrokers.com/en/software/tws/usersguidebook/ordertypes/basic_order_types.htm
# Orders
# http://stocks.about.com/od/tradingbasics/a/markords.htm
# http://www.interactivebrokers.com/en/software/tws/usersguidebook/ordertypes/basic_order_types.htm
#
# State chart:
# INITIAL -> SUBMITTED
Expand All @@ -58,8 +58,6 @@ def roundQuantity(self, quantity):
class Order(object):
"""Base class for orders.
:param orderId: The order id.
:type orderId: string.
:param type_: The order type
:type type_: :class:`Order.Type`
:param action: The order action.
Expand Down Expand Up @@ -132,10 +130,10 @@ class Type(object):
State.PARTIALLY_FILLED: [State.PARTIALLY_FILLED, State.FILLED, State.CANCELED],
}

def __init__(self, orderId, type_, action, instrument, quantity, instrumentTraits):
def __init__(self, type_, action, instrument, quantity, instrumentTraits):
if quantity <= 0:
raise Exception("Invalid quantity")
self.__id = orderId
self.__id = None
self.__type = type_
self.__action = action
self.__instrument = instrument
Expand All @@ -151,22 +149,28 @@ def __init__(self, orderId, type_, action, instrument, quantity, instrumentTrait
self.__submitDateTime = None

# This is to check that orders are not compared directly. order ids should be compared.
#def __eq__(self, other):
# if other is None:
# return False
# assert(False)
# def __eq__(self, other):
# if other is None:
# return False
# assert(False)

# This is to check that orders are not compared directly. order ids should be compared.
#def __ne__(self, other):
# if other is None:
# return True
# assert(False)
# def __ne__(self, other):
# if other is None:
# return True
# assert(False)

def getInstrumentTraits(self):
return self.__instrumentTraits

def getId(self):
"""Returns the order id."""
"""
Returns the order id.
.. note::
This will be None if the order was not submitted.
"""
return self.__id

def getType(self):
Expand All @@ -183,7 +187,9 @@ def getSubmitDateTime(self):
"""Returns the datetime when the order was submitted."""
return self.__submitDateTime

def setSubmitDateTime(self, dateTime):
def setSubmitted(self, orderId, dateTime):
assert(self.__id is None or orderId == self.__id)
self.__id = orderId
self.__submitDateTime = dateTime

def getAction(self):
Expand Down Expand Up @@ -347,8 +353,8 @@ class MarketOrder(Order):
This is a base class and should not be used directly.
"""

def __init__(self, orderId, action, instrument, quantity, onClose, instrumentTraits):
Order.__init__(self, orderId, Order.Type.MARKET, action, instrument, quantity, instrumentTraits)
def __init__(self, action, instrument, quantity, onClose, instrumentTraits):
Order.__init__(self, Order.Type.MARKET, action, instrument, quantity, instrumentTraits)
self.__onClose = onClose

def getFillOnClose(self):
Expand All @@ -364,8 +370,8 @@ class LimitOrder(Order):
This is a base class and should not be used directly.
"""

def __init__(self, orderId, action, instrument, limitPrice, quantity, instrumentTraits):
Order.__init__(self, orderId, Order.Type.LIMIT, action, instrument, quantity, instrumentTraits)
def __init__(self, action, instrument, limitPrice, quantity, instrumentTraits):
Order.__init__(self, Order.Type.LIMIT, action, instrument, quantity, instrumentTraits)
self.__limitPrice = limitPrice

def getLimitPrice(self):
Expand All @@ -381,8 +387,8 @@ class StopOrder(Order):
This is a base class and should not be used directly.
"""

def __init__(self, orderId, action, instrument, stopPrice, quantity, instrumentTraits):
Order.__init__(self, orderId, Order.Type.STOP, action, instrument, quantity, instrumentTraits)
def __init__(self, action, instrument, stopPrice, quantity, instrumentTraits):
Order.__init__(self, Order.Type.STOP, action, instrument, quantity, instrumentTraits)
self.__stopPrice = stopPrice

def getStopPrice(self):
Expand All @@ -398,8 +404,8 @@ class StopLimitOrder(Order):
This is a base class and should not be used directly.
"""

def __init__(self, orderId, action, instrument, stopPrice, limitPrice, quantity, instrumentTraits):
Order.__init__(self, orderId, Order.Type.STOP_LIMIT, action, instrument, quantity, instrumentTraits)
def __init__(self, action, instrument, stopPrice, limitPrice, quantity, instrumentTraits):
Order.__init__(self, Order.Type.STOP_LIMIT, action, instrument, quantity, instrumentTraits)
self.__stopPrice = stopPrice
self.__limitPrice = limitPrice

Expand Down Expand Up @@ -465,7 +471,7 @@ def getEventInfo(self):


######################################################################
## Base broker class
# Base broker class
class Broker(observer.Subject):
"""Base class for brokers.
Expand Down Expand Up @@ -534,7 +540,6 @@ def submitOrder(self, order):
"""
raise NotImplementedError()


def placeOrder(self, order):
# Deprecated since v0.16
warninghelpers.deprecation_warning("placeOrder will be deprecated in the next version. Please use submitOrder instead.", stacklevel=2)
Expand Down
63 changes: 36 additions & 27 deletions pyalgotrade/broker/backtesting.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def roundQuantity(self, quantity):


######################################################################
## Commission models
# Commission models

class Commission(object):
"""Base class for implementing different commission schemes.
Expand Down Expand Up @@ -97,7 +97,7 @@ def calculate(self, order, price, quantity):


######################################################################
## Order filling strategies
# Order filling strategies

# Returns the trigger price for a Stop or StopLimit order, or None if the stop price was not yet penetrated.
def get_stop_price_trigger(action, stopPrice, useAdjustedValues, bar):
Expand Down Expand Up @@ -411,7 +411,7 @@ def fillStopLimitOrder(self, order, broker_, bar):


######################################################################
## Orders
# Orders

class BacktestingOrder(object):
def __init__(self):
Expand All @@ -430,26 +430,26 @@ def process(self, broker_, bar_):


class MarketOrder(broker.MarketOrder, BacktestingOrder):
def __init__(self, orderId, action, instrument, quantity, onClose, instrumentTraits):
broker.MarketOrder.__init__(self, orderId, action, instrument, quantity, onClose, instrumentTraits)
def __init__(self, action, instrument, quantity, onClose, instrumentTraits):
broker.MarketOrder.__init__(self, action, instrument, quantity, onClose, instrumentTraits)
BacktestingOrder.__init__(self)

def process(self, broker_, bar_):
return broker_.getFillStrategy().fillMarketOrder(self, broker_, bar_)


class LimitOrder(broker.LimitOrder, BacktestingOrder):
def __init__(self, orderId, action, instrument, limitPrice, quantity, instrumentTraits):
broker.LimitOrder.__init__(self, orderId, action, instrument, limitPrice, quantity, instrumentTraits)
def __init__(self, action, instrument, limitPrice, quantity, instrumentTraits):
broker.LimitOrder.__init__(self, action, instrument, limitPrice, quantity, instrumentTraits)
BacktestingOrder.__init__(self)

def process(self, broker_, bar_):
return broker_.getFillStrategy().fillLimitOrder(self, broker_, bar_)


class StopOrder(broker.StopOrder, BacktestingOrder):
def __init__(self, orderId, action, instrument, stopPrice, quantity, instrumentTraits):
broker.StopOrder.__init__(self, orderId, action, instrument, stopPrice, quantity, instrumentTraits)
def __init__(self, action, instrument, stopPrice, quantity, instrumentTraits):
broker.StopOrder.__init__(self, action, instrument, stopPrice, quantity, instrumentTraits)
BacktestingOrder.__init__(self)
self.__stopHit = False

Expand All @@ -466,8 +466,8 @@ def getStopHit(self):
# http://www.sec.gov/answers/stoplim.htm
# http://www.interactivebrokers.com/en/trading/orders/stopLimit.php
class StopLimitOrder(broker.StopLimitOrder, BacktestingOrder):
def __init__(self, orderId, action, instrument, stopPrice, limitPrice, quantity, instrumentTraits):
broker.StopLimitOrder.__init__(self, orderId, action, instrument, stopPrice, limitPrice, quantity, instrumentTraits)
def __init__(self, action, instrument, stopPrice, limitPrice, quantity, instrumentTraits):
broker.StopLimitOrder.__init__(self, action, instrument, stopPrice, limitPrice, quantity, instrumentTraits)
BacktestingOrder.__init__(self)
self.__stopHit = False # Set to true when the limit order is activated (stop price is hit)

Expand All @@ -486,7 +486,7 @@ def process(self, broker_, bar_):


######################################################################
## Broker
# Broker

class Broker(broker.Broker):
"""Backtesting broker.
Expand Down Expand Up @@ -522,17 +522,27 @@ def __init__(self, cash, barFeed, commission=None):
self.__allowNegativeCash = False
self.__nextOrderId = 1

def __getNextOrderId(self):
def _getNextOrderId(self):
ret = self.__nextOrderId
self.__nextOrderId += 1
return ret

def __getBar(self, bars, instrument):
def _getBar(self, bars, instrument):
ret = bars.getBar(instrument)
if ret is None:
ret = self.__barFeed.getLastBar(instrument)
return ret

def _registerOrder(self, order):
assert(order.getId() not in self.__activeOrders)
assert(order.getId() is not None)
self.__activeOrders[order.getId()] = order

def _unregisterOrder(self, order):
assert(order.getId() in self.__activeOrders)
assert(order.getId() is not None)
del self.__activeOrders[order.getId()]

def getLogger(self):
return self.__logger

Expand All @@ -545,7 +555,7 @@ def getCash(self, includeShort=True):
bars = self.__barFeed.getCurrentBars()
for instrument, shares in self.__shares.iteritems():
if shares < 0:
instrumentPrice = self.__getBar(bars, instrument).getClose(self.getUseAdjustedValues())
instrumentPrice = self._getBar(bars, instrument).getClose(self.getUseAdjustedValues())
ret += instrumentPrice * shares
return ret

Expand Down Expand Up @@ -617,7 +627,7 @@ def __getEquityWithBars(self, bars):
ret = self.getCash()
if bars is not None:
for instrument, shares in self.__shares.iteritems():
instrumentPrice = self.__getBar(bars, instrument).getClose(self.getUseAdjustedValues())
instrumentPrice = self._getBar(bars, instrument).getClose(self.getUseAdjustedValues())
ret += instrumentPrice * shares
return ret

Expand Down Expand Up @@ -672,7 +682,7 @@ def commitOrderExecution(self, order, dateTime, fillInfo):

# Notify the order update
if order.isFilled():
del self.__activeOrders[order.getId()]
self._unregisterOrder(order)
self.notifyOrderEvent(broker.OrderEvent(order, broker.OrderEvent.Type.FILLED, orderExecutionInfo))
elif order.isPartiallyFilled():
self.notifyOrderEvent(broker.OrderEvent(order, broker.OrderEvent.Type.PARTIALLY_FILLED, orderExecutionInfo))
Expand All @@ -683,13 +693,12 @@ def commitOrderExecution(self, order, dateTime, fillInfo):

def submitOrder(self, order):
if order.isInitial():
# assert(order.getId() not in self.__activeOrders)
self.__activeOrders[order.getId()] = order
order.setSubmitted(self._getNextOrderId(), self._getCurrentDateTime())
self._registerOrder(order)
# Switch from INITIAL -> SUBMITTED
# IMPORTANT: Do not emit an event for this switch because when using the position interface
# the order is not yet mapped to the position and Position.onOrderUpdated will get called.
order.switchState(broker.Order.State.SUBMITTED)
order.setSubmitDateTime(self._getCurrentDateTime())
else:
raise Exception("The order was already processed")

Expand All @@ -704,7 +713,7 @@ def __preProcessOrder(self, order, bar_):
# Cancel the order if it is expired.
if expired:
ret = False
del self.__activeOrders[order.getId()]
self._unregisterOrder(order)
order.switchState(broker.Order.State.CANCELED)
self.notifyOrderEvent(broker.OrderEvent(order, broker.OrderEvent.Type.CANCELED, "Expired"))

Expand All @@ -720,7 +729,7 @@ def __postProcessOrder(self, order, bar_):

# Cancel the order if it will expire in the next bar.
if expired:
del self.__activeOrders[order.getId()]
self._unregisterOrder(order)
order.switchState(broker.Order.State.CANCELED)
self.notifyOrderEvent(broker.OrderEvent(order, broker.OrderEvent.Type.CANCELED, "Expired"))

Expand Down Expand Up @@ -789,16 +798,16 @@ def peekDateTime(self):
return None

def createMarketOrder(self, action, instrument, quantity, onClose=False):
return MarketOrder(self.__getNextOrderId(), action, instrument, quantity, onClose, self.getInstrumentTraits(instrument))
return MarketOrder(action, instrument, quantity, onClose, self.getInstrumentTraits(instrument))

def createLimitOrder(self, action, instrument, limitPrice, quantity):
return LimitOrder(self.__getNextOrderId(), action, instrument, limitPrice, quantity, self.getInstrumentTraits(instrument))
return LimitOrder(action, instrument, limitPrice, quantity, self.getInstrumentTraits(instrument))

def createStopOrder(self, action, instrument, stopPrice, quantity):
return StopOrder(self.__getNextOrderId(), action, instrument, stopPrice, quantity, self.getInstrumentTraits(instrument))
return StopOrder(action, instrument, stopPrice, quantity, self.getInstrumentTraits(instrument))

def createStopLimitOrder(self, action, instrument, stopPrice, limitPrice, quantity):
return StopLimitOrder(self.__getNextOrderId(), action, instrument, stopPrice, limitPrice, quantity, self.getInstrumentTraits(instrument))
return StopLimitOrder(action, instrument, stopPrice, limitPrice, quantity, self.getInstrumentTraits(instrument))

def cancelOrder(self, order):
activeOrder = self.__activeOrders.get(order.getId())
Expand All @@ -807,6 +816,6 @@ def cancelOrder(self, order):
if activeOrder.isFilled():
raise Exception("Can't cancel order that has already been filled")

del self.__activeOrders[activeOrder.getId()]
self._unregisterOrder(activeOrder)
activeOrder.switchState(broker.Order.State.CANCELED)
self.notifyOrderEvent(broker.OrderEvent(activeOrder, broker.OrderEvent.Type.CANCELED, "User requested cancellation"))
2 changes: 1 addition & 1 deletion testcases/broker_backtesting_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ def testNoCommission(self):

def testFixedPerTrade(self):
comm = backtesting.FixedPerTrade(1.2)
order = backtesting.MarketOrder(1, broker.Order.Action.BUY, "orcl", 1, False, backtesting.DefaultTraits())
order = backtesting.MarketOrder(broker.Order.Action.BUY, "orcl", 1, False, backtesting.DefaultTraits())
self.assertEqual(comm.calculate(order, 1, 1), 1.2)

def testTradePercentage(self):
Expand Down
4 changes: 2 additions & 2 deletions testcases/broker_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ def roundQuantity(self, quantity):

class OrderTestCase(unittest.TestCase):
def __buildAcceptedLimitOrder(self, action, limitPrice, quantity):
ret = broker.LimitOrder(1, action, "orcl", limitPrice, quantity, DefaultTraits())
ret = broker.LimitOrder(action, "orcl", limitPrice, quantity, DefaultTraits())
self.assertEquals(ret.getSubmitDateTime(), None)
ret.switchState(broker.Order.State.SUBMITTED)
ret.setSubmitDateTime(datetime.datetime.now())
ret.setSubmitted(1, datetime.datetime.now())
self.assertNotEquals(ret.getSubmitDateTime(), None)
ret.switchState(broker.Order.State.ACCEPTED)
return ret
Expand Down

0 comments on commit f101cc8

Please sign in to comment.