Merge fix for stop orders.

Pull in patch that fixes the stop orders so they behave correctly
with regard to the price being greater or less than the stop for both
buys and sells.

Also, update unit test and add a refactoring on top of the fix to
make each sell/buy, stop and limit combination more clear.
This commit is contained in:
Eddie Hebert
2013-10-28 21:10:46 -04:00
3 changed files with 63 additions and 29 deletions
+15 -15
View File
@@ -238,7 +238,12 @@ class SlippageTestCase(TestCase):
'open': 3.5
},
'expected': {
'transaction': None
'transaction': {
'price': 4.001,
'dt': pd.Timestamp('2006-01-05 14:31', tz='UTC'),
'amount': 100,
'sid': 133,
}
}
},
'long | price lt stop': {
@@ -260,13 +265,8 @@ class SlippageTestCase(TestCase):
'open': 4.0
},
'expected': {
'transaction': {
'price': 3.500875,
'dt': pd.Timestamp('2006-01-05 14:31', tz='UTC'),
'amount': 100,
'sid': 133,
}
},
'transaction': None
}
},
'short | price gt stop': {
'order': {
@@ -287,12 +287,7 @@ class SlippageTestCase(TestCase):
'open': 3.0
},
'expected': {
'transaction': {
'price': 3.499125,
'dt': pd.Timestamp('2006-01-05 14:31', tz='UTC'),
'amount': -100,
'sid': 133,
}
'transaction': None
}
},
'short | price lt stop': {
@@ -314,7 +309,12 @@ class SlippageTestCase(TestCase):
'open': 3.0
},
'expected': {
'transaction': None
'transaction': {
'price': 2.99925,
'dt': pd.Timestamp('2006-01-05 14:31', tz='UTC'),
'amount': -100,
'sid': 133,
}
}
},
}
+4 -2
View File
@@ -282,13 +282,16 @@ class Order(object):
Update internal state based on price triggers and the
trade event's price.
"""
stop_reached, limit_reached = \
stop_reached, limit_reached, sl_stop_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
if sl_stop_reached:
# Change the STOP LIMIT order into a LIMIT order
self.stop = None
def handle_split(self, split_event):
ratio = split_event.ratio
@@ -329,7 +332,6 @@ 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 (stop_reached AND limit_reached)
"""
if self.stop and not self.stop_reached:
return False
+44 -12
View File
@@ -23,6 +23,11 @@ from functools import partial
from zipline.protocol import DATASOURCE_TYPE
import zipline.utils.math_utils as zp_math
SELL = 0
BUY = 1
STOP = 1 << 1
LIMIT = 1 << 2
def check_order_triggers(order, event):
"""
@@ -31,30 +36,57 @@ def check_order_triggers(order, event):
For market orders, will return (False, False).
For stop orders, limit_reached will always be False.
For limit orders, stop_reached will always be False.
For stop limit orders a Boolean is returned to flag
that the stop has been reached.
Orders that have been triggered already (price targets reached),
the order's current values are returned.
"""
if order.triggered:
return (order.stop_reached, order.limit_reached)
return (order.stop_reached, order.limit_reached, False)
stop_reached = False
limit_reached = False
# if the stop price is reached, simply set stop_reached
if order.stop is not None:
if (order.direction * (event.price - order.stop) <= 0):
# convert stop -> limit or market
stop_reached = True
sl_stop_reached = False
order_type = 0
if order.amount > 0:
order_type |= BUY
else:
order_type |= SELL
if order.stop is not None:
order_type |= STOP
# if the limit price is reached, we execute this order at
# (event.price + simulated_impact)
# we skip this order with a continue when the limit is not reached
if order.limit is not None:
# if limit conditions not met, then continue
if (order.direction * (event.price - order.limit) <= 0):
order_type |= LIMIT
if order_type == BUY | STOP | LIMIT:
if event.price >= order.stop:
sl_stop_reached = True
if event.price <= order.limit:
limit_reached = True
elif order_type == SELL | STOP | LIMIT:
if event.price <= order.stop:
sl_stop_reached = True
if event.price >= order.limit:
limit_reached = True
elif order_type == BUY | STOP:
if event.price >= order.stop:
stop_reached = True
elif order_type == SELL | STOP:
if event.price <= order.stop:
stop_reached = True
elif order_type == BUY | LIMIT:
if event.price <= order.limit:
limit_reached = True
elif order_type == SELL | LIMIT:
# This is a SELL LIMIT order
if event.price >= order.limit:
limit_reached = True
return (stop_reached, limit_reached)
return (stop_reached, limit_reached, sl_stop_reached)
def transact_stub(slippage, commission, event, open_orders):