mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-10 18:26:49 +08:00
ENH: Adds new order types.
Adds the following order types: - market - limit - stop - stop limit
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user