import pandas as pd
from zipline.utils import tradingcalendar
def initialize(context):
set_commission(commission.PerShare(0.0))
context.x = symbol('PBMD')
context.y = symbol('ALDR')
context.order_manager = OrderManager()
schedule_function(buy_before_close,
time_rule=time_rules.market_close(minutes=3))
def handle_data(context, data):
context.order_manager.update(context, data)
now = get_datetime()
# Trys to buy 5000 shares every 30 min
# Any unexecuted portion is cancelled after one minute.
if now.minute % 30:
return
context.order_manager.place_order(order, context.x, 5000, tif='IOC')
def buy_before_close(context, data):
# Defaults to a day order which is cancelled in at the end of the day.
context.order_manager.place_order(order, context.y, 5000)
class OrderManager(object):
def __init__(self):
self.orders = []
def update(self, context, data):
orders = []
for order_ in self.orders:
order_.update(context, data)
if not order_.expired:
orders.append(order_)
self.orders = orders
def place_order(self, order_func, sid, amount,
limit_price=None, stop_price=None,style=None,
tif=None, first_valid_dt=None, last_valid_dt=None):
order_ = OrderWrapper(order_func, sid, amount,
limit_price=limit_price,
stop_price=stop_price,
style=style,
tif=tif,
first_valid_dt=first_valid_dt,
last_valid_dt=last_valid_dt)
self.orders.append(order_)
class OrderWrapper(object):
def __init__(self, order_func, sid, amount,
limit_price=None, stop_price=None,style=None,
tif=None, first_valid_dt=None, last_valid_dt=None):
self.func = order_func
self.sid = sid
self.amount = amount
self.limit_price = limit_price
self.stop_price = stop_price
self.style = style
if first_valid_dt is None:
first_valid_dt = get_datetime()
self.first_valid_dt = first_valid_dt
self.last_valid_dt = last_valid_dt
self.tif = self._get_tif_model(tif)
self.active = False
self.expired = False
self.oid = None
def _get_tif_model(self, tif):
if isinstance(tif, TimeInForceModel):
return tif
if tif == 'GTC':
return GoodTillCancelled()
if tif == 'IOC':
return ImmediateOrCancel()
if self.last_valid_dt is not None:
return GoodBetweenDates(self.first_valid_dt, self.last_valid_dt)
return DayOrder()
def update(self, context, data):
now = get_datetime()
isafter, isbefore = self.tif.valid_date_bools(now)
if not isafter:
return
if not isbefore:
self.cancel()
self.expired = True
return
if not self.active:
self.place_order()
self.active = True
def place_order(self):
self.oid = self.func(self.sid, self.amount,
limit_price=self.limit_price,
stop_price=self.stop_price,
style=self.style)
def cancel(self):
if self.oid is not None:
cancel_order(self.oid)
class TimeInForceModel(object):
"""
Base class representing when an order is valid
"""
_tif = None
def get_first_valid_dt(self):
"""
Get the first valid datetime for this order
"""
return self.first_valid_dt
def get_last_valid_dt(self):
"""
Get the last valid datetime for this order
"""
return self.last_valid_dt
def set_last_valid_dt(self, dt):
"""
Alters the last valid datetime for this order
"""
self.last_valid_dt = dt
def valid_date_bools(self, dt):
"""
Returns a tuple of (f(dt) ==> bool) outputs.
"""
return (dt >= self.get_first_valid_dt(),
dt <= self.get_last_valid_dt())
class DayOrder(TimeInForceModel):
"""
Represents an order that only remains open for
the trading day in which it was submitted.
"""
def __init__(self):
"""
Set the last valid dt the market_close on the day
the order was placed.
"""
self._tif = 'DAY'
self.first_valid_dt = get_datetime()
open_and_closes = tradingcalendar.open_and_closes
dt = tradingcalendar.canonicalize_datetime(self.first_valid_dt)
idx = open_and_closes.index.searchsorted(dt)
self.last_valid_dt = open_and_closes.iloc[idx]['market_close']
# Subtract one minute so the order is cancelled on the last bar of the day
self.last_valid_dt -= pd.offsets.Minute()
class GoodTillCancelled(TimeInForceModel):
"""
Represents an order that remains open
until the end of the following business quarter
unless explicitly cancelled.
"""
def __init__(self):
self._tif = 'GTC'
self.first_valid_dt = get_datetime()
self.last_valid_dt = self.first_valid_dt + pd.offsets.BQuarterEnd(2)
class ImmediateOrCancel(TimeInForceModel):
"""
Represents an order that must fill immediatley,
any amount still open after the first bar is cancelled.
"""
def __init__(self):
"""
Set the last dt one minute later because
zipline orders process on the bar following
the bar the order was placed.
"""
self._tif = 'IOC'
self.first_valid_dt = get_datetime()
self.last_valid_dt = get_datetime() + pd.offsets.Minute()
class GoodBetweenDates(TimeInForceModel):
"""
Represents an order that only remains open
between two specified dates (inclusive).
"""
def __init__(self, first_valid_dt, last_valid_dt):
self._tif = 'GBD'
self.first_valid_dt = first_valid_dt
self.last_valid_dt = last_valid_dt