Merge pull request #146 from quantopian/orders_m1d

Orders m1d
This commit is contained in:
fawce
2013-04-26 16:55:28 -07:00
9 changed files with 257 additions and 247 deletions
+1 -1
View File
@@ -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):
+3 -7
View File
@@ -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
+1 -1
View File
@@ -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
+7 -5
View File
@@ -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 <default: 1.0e5>
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):
return 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)):
+235
View File
@@ -0,0 +1,235 @@
#
# 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 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,
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',
'CANCELLED'
)
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 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 [], []
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]
# 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 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
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):
if self.status == ORDER_STATUS.CANCELLED:
return False
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 (stop_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
+5 -226
View File
@@ -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.
+4
View File
@@ -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):
-6
View File
@@ -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")
+1 -1
View File
@@ -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'):