ENH: Adds new order types.

Adds the following order types:
- market
- limit
- stop
- stop limit
This commit is contained in:
Tony Worm
2013-03-06 13:44:17 -05:00
committed by Eddie Hebert
parent dba86153d2
commit 086679c3d6
4 changed files with 520 additions and 24 deletions
+436
View File
@@ -77,3 +77,439 @@ class SlippageTestCase(TestCase):
# TODO: Make expected_txn an Transaction object and ensure there
# is a __eq__ for that class.
self.assertEquals(expected_txn, txn.__dict__)
def test_orders_limit(self):
events = self.gen_trades()
slippage_model = VolumeShareSlippage()
# long, does not trade
open_orders = {133: [
Order(**{
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': 100,
'filled': 0,
'sid': 133,
'limit': 3.5})
]}
txn = slippage_model.simulate(
events[2],
open_orders
)
expected_txn = {}
self.assertIsNone(txn)
# long, does trade
open_orders = {133: [
Order(**{
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': 100,
'filled': 0,
'sid': 133,
'limit': 3.5})
]}
txn = slippage_model.simulate(
events[3],
open_orders
)
expected_txn = {
'price': float(3.500875),
'dt': datetime.datetime(
2006, 1, 5, 14, 34, tzinfo=pytz.utc),
'amount': int(100),
'sid': int(133)
}
self.assertIsNotNone(txn)
for key, value in expected_txn.items():
self.assertEquals(value, txn[key])
# short, does not trade
open_orders = {133: [
Order(**{
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': -100,
'filled': 0,
'sid': 133,
'limit': 3.5})
]}
txn = slippage_model.simulate(
events[0],
open_orders
)
expected_txn = {}
self.assertIsNone(txn)
# short, does trade
open_orders = {133: [
Order(**{
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': -100,
'filled': 0,
'sid': 133,
'limit': 3.5})
]}
txn = slippage_model.simulate(
events[1],
open_orders
)
expected_txn = {
'price': float(3.499125),
'dt': datetime.datetime(
2006, 1, 5, 14, 32, tzinfo=pytz.utc),
'amount': int(-100),
'sid': int(133)
}
self.assertIsNotNone(txn)
for key, value in expected_txn.items():
self.assertEquals(value, txn[key])
def test_orders_stop(self):
events = self.gen_trades()
slippage_model = VolumeShareSlippage()
# long, does not trade
open_orders = {133: [
Order(**{
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': 100,
'filled': 0,
'sid': 133,
'stop': 3.5})
]}
txn = slippage_model.simulate(
events[2],
open_orders
)
expected_txn = {}
self.assertIsNone(txn)
# long, does trade
open_orders = {133: [
Order(**{
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': 100,
'filled': 0,
'sid': 133,
'stop': 3.6})
]}
txn = slippage_model.simulate(
events[3],
open_orders
)
expected_txn = {
'price': float(3.500875),
'dt': datetime.datetime(
2006, 1, 5, 14, 34, tzinfo=pytz.utc),
'amount': int(100),
'sid': int(133)
}
self.assertIsNotNone(txn)
for key, value in expected_txn.items():
self.assertEquals(value, txn[key])
# short, does not trade
open_orders = {133: [
Order(**{
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': -100,
'filled': 0,
'sid': 133,
'stop': 3.5})
]}
txn = slippage_model.simulate(
events[0],
open_orders
)
expected_txn = {}
self.assertIsNone(txn)
# short, does trade
open_orders = {133: [
Order(**{
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': -100,
'filled': 0,
'sid': 133,
'stop': 3.4})
]}
txn = slippage_model.simulate(
events[1],
open_orders
)
expected_txn = {
'price': float(3.499125),
'dt': datetime.datetime(
2006, 1, 5, 14, 32, tzinfo=pytz.utc),
'amount': int(-100),
'sid': int(133)
}
self.assertIsNotNone(txn)
for key, value in expected_txn.items():
self.assertEquals(value, txn[key])
def test_orders_stop_limit(self):
events = self.gen_trades()
slippage_model = VolumeShareSlippage()
# long, does not trade
open_orders = {133: [
Order(**{
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': 100,
'filled': 0,
'sid': 133,
'stop': 4.0,
'limit': 3.0})
]}
txn = slippage_model.simulate(
events[2],
open_orders
)
expected_txn = {}
self.assertIsNone(txn)
txn = slippage_model.simulate(
events[3],
open_orders
)
expected_txn = {}
self.assertIsNone(txn)
# long, does trade
open_orders = {133: [
Order(**{
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': 100,
'filled': 0,
'sid': 133,
'stop': 4.0,
'limit': 3.5})
]}
txn = slippage_model.simulate(
events[2],
open_orders
)
expected_txn = {}
self.assertIsNone(txn)
txn = slippage_model.simulate(
events[3],
open_orders
)
expected_txn = {
'price': float(3.500875),
'dt': datetime.datetime(
2006, 1, 5, 14, 34, tzinfo=pytz.utc),
'amount': int(100),
'sid': int(133)
}
self.assertIsNotNone(txn)
for key, value in expected_txn.items():
self.assertEquals(value, txn[key])
# short, does not trade
open_orders = {133: [
Order(**{
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': -100,
'filled': 0,
'sid': 133,
'stop': 3.0,
'limit': 4.0})
]}
txn = slippage_model.simulate(
events[0],
open_orders
)
expected_txn = {}
self.assertIsNone(txn)
txn = slippage_model.simulate(
events[1],
open_orders
)
expected_txn = {}
self.assertIsNone(txn)
# short, does trade
open_orders = {133: [
Order(**{
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': -100,
'filled': 0,
'sid': 133,
'stop': 3.0,
'limit': 3.5})
]}
txn = slippage_model.simulate(
events[0],
open_orders
)
expected_txn = {}
self.assertIsNone(txn)
txn = slippage_model.simulate(
events[1],
open_orders
)
expected_txn = {
'price': float(3.499125),
'dt': datetime.datetime(
2006, 1, 5, 14, 32, tzinfo=pytz.utc),
'amount': int(-100),
'sid': int(133)
}
self.assertIsNotNone(txn)
for key, value in expected_txn.items():
self.assertEquals(value, txn[key])
def gen_trades(self):
# create a sequence of trades
events = [
Event({
'volume': 2000,
'TRANSACTION': None,
'type': 4,
'price': 3.0,
'datetime': datetime.datetime(
2006, 1, 5, 14, 31, tzinfo=pytz.utc),
'high': 3.15,
'low': 2.85,
'sid': 133,
'source_id': 'test_source',
'close': 3.0,
'dt':
datetime.datetime(2006, 1, 5, 14, 31, tzinfo=pytz.utc),
'open': 3.0
}),
Event({
'volume': 2000,
'TRANSACTION': None,
'type': 4,
'price': 3.5,
'datetime': datetime.datetime(
2006, 1, 5, 14, 32, tzinfo=pytz.utc),
'high': 3.15,
'low': 2.85,
'sid': 133,
'source_id': 'test_source',
'close': 3.5,
'dt':
datetime.datetime(2006, 1, 5, 14, 32, tzinfo=pytz.utc),
'open': 3.0
}),
Event({
'volume': 2000,
'TRANSACTION': None,
'type': 4,
'price': 4.0,
'datetime': datetime.datetime(
2006, 1, 5, 14, 33, tzinfo=pytz.utc),
'high': 3.15,
'low': 2.85,
'sid': 133,
'source_id': 'test_source',
'close': 4.0,
'dt':
datetime.datetime(2006, 1, 5, 14, 33, tzinfo=pytz.utc),
'open': 3.5
}),
Event({
'volume': 2000,
'TRANSACTION': None,
'type': 4,
'price': 3.5,
'datetime': datetime.datetime(
2006, 1, 5, 14, 34, tzinfo=pytz.utc),
'high': 3.15,
'low': 2.85,
'sid': 133,
'source_id': 'test_source',
'close': 3.5,
'dt':
datetime.datetime(2006, 1, 5, 14, 34, tzinfo=pytz.utc),
'open': 4.0
}),
Event({
'volume': 2000,
'TRANSACTION': None,
'type': 4,
'price': 3.0,
'datetime': datetime.datetime(
2006, 1, 5, 14, 35, tzinfo=pytz.utc),
'high': 3.15,
'low': 2.85,
'sid': 133,
'source_id': 'test_source',
'close': 3.0,
'dt':
datetime.datetime(2006, 1, 5, 14, 35, tzinfo=pytz.utc),
'open': 3.5
})
]
return events
+3 -2
View File
@@ -28,11 +28,12 @@ from nose.tools import timed
import zipline.utils.factory as factory
import zipline.utils.simfactory as simfactory
from zipline.gens.tradesimulation import Order
import zipline.finance.trading as trading
from zipline.finance.trading import SimulationParameters
from zipline.finance.performance import PerformanceTracker
from zipline.utils.protocol_utils import ndict
from zipline.finance.trading import TransactionSimulator
from zipline.utils.test_utils import(
setup_logger,
@@ -302,7 +303,7 @@ class FinanceTestCase(TestCase):
order_date = start_date
for i in xrange(order_count):
order = ndict({
order = Order(**{
'sid': sid,
'amount': order_amount * alternator ** i,
'dt': order_date
+44 -6
View File
@@ -108,10 +108,27 @@ class VolumeShareSlippage(object):
open_amount = order.amount - order.filled
if not np.allclose(open_amount, 0):
direction = math.copysign(1, open_amount)
else:
direction = 1
if np.allclose(open_amount, 0):
continue
direction = math.copysign(1, open_amount)
# if the stop price is reached, simply set stop to None
# othrewise we skip this order with a continue
if order.stop is not None:
if (direction * (event.price - order.stop) < 0):
# convert stop -> limit or market
order.stop = None
else:
continue
# 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 (direction * (event.price - order.limit) > 0):
continue
desired_order = total_order + open_amount
@@ -146,6 +163,8 @@ class VolumeShareSlippage(object):
return create_transaction(
event.sid,
simulated_amount,
# In the future, we may want to change the next line
# for limit pricing
event.price + simulated_impact,
dt.replace(tzinfo=pytz.utc),
)
@@ -170,13 +189,32 @@ class FixedSlippage(object):
amount = 0
for order in orders:
# what if we have 2 orders, one for 100 shares long,
# and one for 100 shares short
# such as in a hedging scenario?
amount += order.amount
direction = math.copysign(1, amount)
# if the stop price is reached, simply set stop to None
# othrewise we skip this order with a continue
if order.stop is not None:
if (direction * (event.price - order.stop) < 0):
# convert stop -> limit or market
order.stop = None
else:
continue
# 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 (direction * (event.price - order.limit) > 0):
continue
if np.allclose(amount, 0):
return
direction = math.copysign(1, amount)
txn = create_transaction(
event.sid,
amount,
+37 -16
View File
@@ -28,7 +28,7 @@ log = Logger('Trade Simulation')
class Order(object):
def __init__(self, dt, sid, amount, filled=0):
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
@@ -41,6 +41,11 @@ class Order(object):
self.sid = sid
self.amount = amount
self.filled = filled
self.stop = stop
self.limit = limit
def __getitem__(self, name):
return self.__dict__[name]
class TradeSimulationClient(object):
@@ -181,33 +186,49 @@ class AlgorithmSimulator(object):
record.extra['algo_dt'] = self.snapshot_dt
self.processor = Processor(inject_algo_dt)
def order(self, sid, amount):
"""
Closure to pass into the user's algo to allow placing orders
into the transaction simulator's dict of open orders.
"""
order = Order(**{
'dt': self.simulation_dt,
'sid': sid,
'amount': int(amount),
'filled': 0
})
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 order.amount == 0:
zero_message = "Requested to trade zero shares of {sid}".format(
sid=order.sid
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 transaction
# simulator so that it can fill the placed order when it
# receives its next message.
self.order_book.place_order(order)
err_str = self.order_book.place_order(order)
if err_str is not None and len(err_str) > 0:
# error, trade was not placed, log it out
log.debug(err_str)
def transform(self, stream_in):
"""