mirror of
https://github.com/wassname/catalyst.git
synced 2026-08-19 12:00:15 +08:00
major fix is with the non-blocking behavior of order source. also fixed time-compression in the trading client.
This commit is contained in:
@@ -166,6 +166,7 @@ class PerformanceTracker():
|
|||||||
self.event_count = 0
|
self.event_count = 0
|
||||||
self.result_stream = None
|
self.result_stream = None
|
||||||
self.last_dict = None
|
self.last_dict = None
|
||||||
|
self.order_log = []
|
||||||
|
|
||||||
# this performance period will span the entire simulation.
|
# this performance period will span the entire simulation.
|
||||||
self.cumulative_performance = PerformancePeriod(
|
self.cumulative_performance = PerformancePeriod(
|
||||||
@@ -229,6 +230,9 @@ class PerformanceTracker():
|
|||||||
'timestamp' : datetime.datetime.now(),
|
'timestamp' : datetime.datetime.now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def log_order(self, order):
|
||||||
|
self.order_log.append(order)
|
||||||
|
|
||||||
def process_event(self, event):
|
def process_event(self, event):
|
||||||
assert isinstance(event, zp.namedict)
|
assert isinstance(event, zp.namedict)
|
||||||
self.event_count += 1
|
self.event_count += 1
|
||||||
@@ -300,13 +304,14 @@ class PerformanceTracker():
|
|||||||
and send it out on the result_stream.
|
and send it out on the result_stream.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
log_msg = "Simulated {n} trading days out of {m}."
|
||||||
|
qutil.LOGGER.info(log_msg.format(n=self.day_count, m=self.total_days))
|
||||||
|
qutil.LOGGER.info("first open: {d}".format(d=self.trading_environment.first_open))
|
||||||
|
|
||||||
# the stream will end on the last trading day, but will not trigger
|
# the stream will end on the last trading day, but will not trigger
|
||||||
# an end of day, so we trigger the final market close here.
|
# an end of day, so we trigger the final market close here.
|
||||||
self.handle_market_close()
|
self.handle_market_close()
|
||||||
|
|
||||||
log_msg = "Simulated {n} trading days out of {m}."
|
|
||||||
qutil.LOGGER.info(log_msg.format(n=self.day_count, m=self.total_days))
|
|
||||||
qutil.LOGGER.info("first open: {d}".format(d=self.trading_environment.first_open))
|
|
||||||
|
|
||||||
self.risk_report = risk.RiskReport(
|
self.risk_report = risk.RiskReport(
|
||||||
self.returns,
|
self.returns,
|
||||||
|
|||||||
+32
-29
@@ -119,12 +119,15 @@ class TradeSimulationClient(qmsg.Component):
|
|||||||
# otherwise, the algorithm has fallen behind the feed
|
# otherwise, the algorithm has fallen behind the feed
|
||||||
# and processing per event is longer than time between events.
|
# and processing per event is longer than time between events.
|
||||||
if event.dt >= self.current_dt:
|
if event.dt >= self.current_dt:
|
||||||
|
# compress time by moving the current_time up to the event
|
||||||
|
# time.
|
||||||
|
self.current_dt = event.dt
|
||||||
self.run_algorithm()
|
self.run_algorithm()
|
||||||
|
|
||||||
# tally the time spent on this iteration
|
# tally the time spent on this iteration
|
||||||
self.last_iteration_dur = datetime.datetime.utcnow() - event_start
|
self.last_iteration_dur = datetime.datetime.utcnow() - event_start
|
||||||
# move the algorithm's clock forward to include iteration time
|
# move the algorithm's clock forward to include iteration time
|
||||||
self.current_dt = self.current_dt + self.last_iteration_dur
|
# self.current_dt = self.current_dt + self.last_iteration_dur
|
||||||
|
|
||||||
|
|
||||||
def run_algorithm(self):
|
def run_algorithm(self):
|
||||||
@@ -152,6 +155,7 @@ class TradeSimulationClient(qmsg.Component):
|
|||||||
})
|
})
|
||||||
self.order_socket.send(zp.ORDER_FRAME(order))
|
self.order_socket.send(zp.ORDER_FRAME(order))
|
||||||
self.order_count += 1
|
self.order_count += 1
|
||||||
|
self.perf.log_order(order)
|
||||||
|
|
||||||
def signal_order_done(self):
|
def signal_order_done(self):
|
||||||
self.order_socket.send(str(zp.ORDER_PROTOCOL.DONE))
|
self.order_socket.send(str(zp.ORDER_PROTOCOL.DONE))
|
||||||
@@ -185,6 +189,7 @@ class OrderDataSource(qmsg.DataSource):
|
|||||||
"""
|
"""
|
||||||
qmsg.DataSource.__init__(self, zp.FINANCE_COMPONENT.ORDER_SOURCE)
|
qmsg.DataSource.__init__(self, zp.FINANCE_COMPONENT.ORDER_SOURCE)
|
||||||
self.sent_count = 0
|
self.sent_count = 0
|
||||||
|
self.works = 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def get_type(self):
|
def get_type(self):
|
||||||
@@ -194,7 +199,8 @@ class OrderDataSource(qmsg.DataSource):
|
|||||||
@property
|
@property
|
||||||
def is_blocking(self):
|
def is_blocking(self):
|
||||||
"""
|
"""
|
||||||
This datasource is in a loop with the TradingSimulationClient
|
This datasource is in a loop with the TradingSimulationClient,
|
||||||
|
so we don't want it to block processing.
|
||||||
"""
|
"""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -207,18 +213,13 @@ class OrderDataSource(qmsg.DataSource):
|
|||||||
|
|
||||||
def do_work(self):
|
def do_work(self):
|
||||||
|
|
||||||
|
self.works += 1
|
||||||
|
|
||||||
#TODO: if this is the first iteration, break deadlock by sending a dummy order
|
|
||||||
if(self.sent_count == 0):
|
|
||||||
self.send(zp.namedict({}))
|
|
||||||
|
|
||||||
#pull all orders from client.
|
#pull all orders from client.
|
||||||
orders = []
|
orders = []
|
||||||
count = 0
|
count = 0
|
||||||
|
|
||||||
# TODO : this can be written in a concurrency agnostic
|
|
||||||
# way... have a chat with Fawce about this ~Steve
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
# poll all the sockets
|
# poll all the sockets
|
||||||
# we reduce the timeout here by a factor of 2, because we need
|
# we reduce the timeout here by a factor of 2, because we need
|
||||||
@@ -247,17 +248,6 @@ class OrderDataSource(qmsg.DataSource):
|
|||||||
count += 1
|
count += 1
|
||||||
self.sent_count += 1
|
self.sent_count += 1
|
||||||
|
|
||||||
# TODO: why didn't any unit tests catch this bug????
|
|
||||||
|
|
||||||
#else:
|
|
||||||
# # no orders, break out
|
|
||||||
# break
|
|
||||||
|
|
||||||
#TODO: we have to send at least one dummy order per do_work iteration
|
|
||||||
# or the feed will block waiting for our messages.
|
|
||||||
if(count == 0):
|
|
||||||
self.send(zp.namedict({}))
|
|
||||||
|
|
||||||
class TransactionSimulator(qmsg.BaseTransform):
|
class TransactionSimulator(qmsg.BaseTransform):
|
||||||
|
|
||||||
def __init__(self, style=SIMULATION_STYLE.PARTIAL_VOLUME):
|
def __init__(self, style=SIMULATION_STYLE.PARTIAL_VOLUME):
|
||||||
@@ -278,6 +268,19 @@ class TransactionSimulator(qmsg.BaseTransform):
|
|||||||
elif style == SIMULATION_STYLE.NOOP:
|
elif style == SIMULATION_STYLE.NOOP:
|
||||||
self.apply_trade_to_open_orders = self.simulate_noop
|
self.apply_trade_to_open_orders = self.simulate_noop
|
||||||
|
|
||||||
|
#
|
||||||
|
@property
|
||||||
|
def is_blocking(self):
|
||||||
|
"""
|
||||||
|
Including this explicitly for clarity, even though we are using the
|
||||||
|
default value. TransactionSimulator has a defined action for every
|
||||||
|
event type. Downstream components depend on the presence of the
|
||||||
|
TRANSACTION transform in all cases. When no transaction happens,
|
||||||
|
None is the value. Thus, we do want merging to block on the
|
||||||
|
availability of transaction messages.
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
def transform(self, event):
|
def transform(self, event):
|
||||||
"""
|
"""
|
||||||
Pulls one message from the event feed, then
|
Pulls one message from the event feed, then
|
||||||
@@ -304,6 +307,8 @@ class TransactionSimulator(qmsg.BaseTransform):
|
|||||||
Amount is explicitly converted to an int.
|
Amount is explicitly converted to an int.
|
||||||
Orders of amount zero are ignored.
|
Orders of amount zero are ignored.
|
||||||
"""
|
"""
|
||||||
|
self.order_count += 1
|
||||||
|
|
||||||
event.amount = int(event.amount)
|
event.amount = int(event.amount)
|
||||||
if event.amount == 0:
|
if event.amount == 0:
|
||||||
log = "requested to trade zero shares of {sid}".format(
|
log = "requested to trade zero shares of {sid}".format(
|
||||||
@@ -312,7 +317,7 @@ class TransactionSimulator(qmsg.BaseTransform):
|
|||||||
qutil.LOGGER.debug(log)
|
qutil.LOGGER.debug(log)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.order_count += 1
|
|
||||||
|
|
||||||
if(not self.open_orders.has_key(event.sid)):
|
if(not self.open_orders.has_key(event.sid)):
|
||||||
self.open_orders[event.sid] = []
|
self.open_orders[event.sid] = []
|
||||||
@@ -321,9 +326,6 @@ class TransactionSimulator(qmsg.BaseTransform):
|
|||||||
event.filled = 0
|
event.filled = 0
|
||||||
self.open_orders[event.sid].append(event)
|
self.open_orders[event.sid].append(event)
|
||||||
|
|
||||||
#def apply_trade_to_open_orders(self, event):
|
|
||||||
# return self.simulate_with_fixed_cost(event)
|
|
||||||
|
|
||||||
def simulate_buy_all(self, event):
|
def simulate_buy_all(self, event):
|
||||||
txn = self.create_transaction(
|
txn = self.create_transaction(
|
||||||
event.sid,
|
event.sid,
|
||||||
@@ -348,10 +350,11 @@ class TransactionSimulator(qmsg.BaseTransform):
|
|||||||
for order in orders:
|
for order in orders:
|
||||||
amount += order.amount
|
amount += order.amount
|
||||||
|
|
||||||
if(amount != 0):
|
if(amount == 0):
|
||||||
direction = amount / math.fabs(amount)
|
return
|
||||||
else:
|
|
||||||
direction = 1
|
direction = amount / math.fabs(amount)
|
||||||
|
|
||||||
|
|
||||||
txn = self.create_transaction(
|
txn = self.create_transaction(
|
||||||
event.sid,
|
event.sid,
|
||||||
@@ -426,9 +429,9 @@ for order:
|
|||||||
)
|
)
|
||||||
qutil.LOGGER.warn(warning)
|
qutil.LOGGER.warn(warning)
|
||||||
|
|
||||||
orders = [ x for x in orders if abs(x.amount - x.filled) > 0 and x.dt.day >= event.dt.day]
|
#orders = [ x for x in orders if abs(x.amount - x.filled) > 0 and x.dt.day >= event.dt.day]
|
||||||
|
|
||||||
self.open_orders[event.sid] = orders
|
#self.open_orders[event.sid] = orders
|
||||||
|
|
||||||
|
|
||||||
if simulated_amount != 0:
|
if simulated_amount != 0:
|
||||||
|
|||||||
+13
-1
@@ -125,6 +125,9 @@ class SimulatedTrading(object):
|
|||||||
:py:class:`zipline.simulator.AddressAllocator`
|
:py:class:`zipline.simulator.AddressAllocator`
|
||||||
- simulator_class: a :py:class:`zipline.messaging.ComponentHost`
|
- simulator_class: a :py:class:`zipline.messaging.ComponentHost`
|
||||||
subclass (not an instance)
|
subclass (not an instance)
|
||||||
|
- simulation_style: optional parameter that configures the
|
||||||
|
:py:class:`zipline.finance.trading.TransactionSimulator`. Expects
|
||||||
|
a SIMULATION_STYLE as defined in :py:mod:`zipline.finance.trading`
|
||||||
"""
|
"""
|
||||||
assert isinstance(config, dict)
|
assert isinstance(config, dict)
|
||||||
self.algorithm = config['algorithm']
|
self.algorithm = config['algorithm']
|
||||||
@@ -203,6 +206,9 @@ class SimulatedTrading(object):
|
|||||||
- trade_source - optional parameter to specify trades, if present.
|
- trade_source - optional parameter to specify trades, if present.
|
||||||
If not present :py:class:`ziplien.sources.SpecificEquityTrades`
|
If not present :py:class:`ziplien.sources.SpecificEquityTrades`
|
||||||
is the source, with daily frequency in trades.
|
is the source, with daily frequency in trades.
|
||||||
|
- simulation_style: optional parameter that configures the
|
||||||
|
:py:class:`zipline.finance.trading.TransactionSimulator`. Expects
|
||||||
|
a SIMULATION_STYLE as defined in :py:mod:`zipline.finance.trading`
|
||||||
"""
|
"""
|
||||||
assert isinstance(config, dict)
|
assert isinstance(config, dict)
|
||||||
|
|
||||||
@@ -230,6 +236,8 @@ class SimulatedTrading(object):
|
|||||||
if config.has_key('trade_count'):
|
if config.has_key('trade_count'):
|
||||||
trade_count = config['trade_count']
|
trade_count = config['trade_count']
|
||||||
else:
|
else:
|
||||||
|
# to ensure all orders are filled, we provide one more
|
||||||
|
# trade than order
|
||||||
trade_count = 101
|
trade_count = 101
|
||||||
|
|
||||||
if config.has_key('simulator_class'):
|
if config.has_key('simulator_class'):
|
||||||
@@ -237,6 +245,10 @@ class SimulatedTrading(object):
|
|||||||
else:
|
else:
|
||||||
simulator_class = Simulator
|
simulator_class = Simulator
|
||||||
|
|
||||||
|
simulation_style = config.get('simulation_style')
|
||||||
|
if not simulation_style:
|
||||||
|
simulation_style = SIMULATION_STYLE.FIXED_SLIPPAGE
|
||||||
|
|
||||||
#-------------------
|
#-------------------
|
||||||
# Trade Source
|
# Trade Source
|
||||||
#-------------------
|
#-------------------
|
||||||
@@ -269,7 +281,7 @@ class SimulatedTrading(object):
|
|||||||
'trading_environment':trading_environment,
|
'trading_environment':trading_environment,
|
||||||
'allocator':allocator,
|
'allocator':allocator,
|
||||||
'simulator_class':simulator_class,
|
'simulator_class':simulator_class,
|
||||||
'simulation_style':SIMULATION_STYLE.FIXED_SLIPPAGE
|
'simulation_style':simulation_style
|
||||||
})
|
})
|
||||||
#-------------------
|
#-------------------
|
||||||
|
|
||||||
|
|||||||
+19
-4
@@ -4,6 +4,8 @@ Commonly used messaging components.
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
from collections import Counter
|
||||||
|
|
||||||
import zipline.util as qutil
|
import zipline.util as qutil
|
||||||
from zipline.component import Component
|
from zipline.component import Component
|
||||||
import zipline.protocol as zp
|
import zipline.protocol as zp
|
||||||
@@ -81,11 +83,11 @@ class ComponentHost(Component):
|
|||||||
self.sync_register[component.get_id] = datetime.datetime.utcnow()
|
self.sync_register[component.get_id] = datetime.datetime.utcnow()
|
||||||
|
|
||||||
if isinstance(component, DataSource):
|
if isinstance(component, DataSource):
|
||||||
self.feed.add_source(component.get_id)
|
self.feed.add_source(component.get_id, component.is_blocking)
|
||||||
if not component.is_blocking:
|
if not component.is_blocking:
|
||||||
self.feed.ds_finished_counter +=1
|
self.feed.ds_finished_counter +=1
|
||||||
if isinstance(component, BaseTransform):
|
if isinstance(component, BaseTransform):
|
||||||
self.merge.add_source(component.get_id)
|
self.merge.add_source(component.get_id, component.is_blocking)
|
||||||
if not component.is_blocking:
|
if not component.is_blocking:
|
||||||
self.feed.ds_finished_counter +=1
|
self.feed.ds_finished_counter +=1
|
||||||
|
|
||||||
@@ -193,6 +195,13 @@ class Feed(Component):
|
|||||||
# structure with better asymptotics.
|
# structure with better asymptotics.
|
||||||
self.data_buffer = {}
|
self.data_buffer = {}
|
||||||
|
|
||||||
|
# source_id -> integer count
|
||||||
|
self.sent_counters = Counter()
|
||||||
|
self.recv_counters = Counter()
|
||||||
|
|
||||||
|
# source_id -> boolean. True is for blocking
|
||||||
|
self.is_blocking_map = {}
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -294,6 +303,7 @@ class Feed(Component):
|
|||||||
event = self.next()
|
event = self.next()
|
||||||
if(event != None):
|
if(event != None):
|
||||||
self.feed_socket.send(self.frame(event), self.zmq.NOBLOCK)
|
self.feed_socket.send(self.frame(event), self.zmq.NOBLOCK)
|
||||||
|
self.sent_counters[event.source_id] += 1
|
||||||
self.sent_count += 1
|
self.sent_count += 1
|
||||||
|
|
||||||
def append(self, event):
|
def append(self, event):
|
||||||
@@ -302,6 +312,7 @@ class Feed(Component):
|
|||||||
source_id.
|
source_id.
|
||||||
"""
|
"""
|
||||||
self.data_buffer[event.source_id].append(event)
|
self.data_buffer[event.source_id].append(event)
|
||||||
|
self.recv_counters[event.source_id] += 1
|
||||||
self.received_count += 1
|
self.received_count += 1
|
||||||
|
|
||||||
def next(self):
|
def next(self):
|
||||||
@@ -338,7 +349,10 @@ class Feed(Component):
|
|||||||
Indicates whether the buffer has messages in buffer for
|
Indicates whether the buffer has messages in buffer for
|
||||||
all un-DONE sources.
|
all un-DONE sources.
|
||||||
"""
|
"""
|
||||||
for events in self.data_buffer.values():
|
for source_id, events in self.data_buffer.iteritems():
|
||||||
|
if not self.is_blocking_map[source_id]:
|
||||||
|
continue
|
||||||
|
|
||||||
if len(events) == 0:
|
if len(events) == 0:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
@@ -353,11 +367,12 @@ class Feed(Component):
|
|||||||
total += len(events)
|
total += len(events)
|
||||||
return total
|
return total
|
||||||
|
|
||||||
def add_source(self, source_id):
|
def add_source(self, source_id, is_blocking=True):
|
||||||
"""
|
"""
|
||||||
Add a data source to the buffer.
|
Add a data source to the buffer.
|
||||||
"""
|
"""
|
||||||
self.data_buffer[source_id] = []
|
self.data_buffer[source_id] = []
|
||||||
|
self.is_blocking_map[source_id] = is_blocking
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class TestAlgorithm():
|
|||||||
|
|
||||||
def handle_frame(self, frame):
|
def handle_frame(self, frame):
|
||||||
self.frame_count += 1
|
self.frame_count += 1
|
||||||
#place an order for 100 shares of sid:133
|
#place an order for 100 shares of sid
|
||||||
if self.incr < self.count:
|
if self.incr < self.count:
|
||||||
self.order(self.sid, self.amount)
|
self.order(self.sid, self.amount)
|
||||||
self.incr += 1
|
self.incr += 1
|
||||||
|
|||||||
@@ -38,12 +38,12 @@ def load_market_data():
|
|||||||
|
|
||||||
return bm_returns, tr_curves
|
return bm_returns, tr_curves
|
||||||
|
|
||||||
def create_trading_environment():
|
def create_trading_environment(year=2006):
|
||||||
"""Construct a complete environment with reasonable defaults"""
|
"""Construct a complete environment with reasonable defaults"""
|
||||||
benchmark_returns, treasury_curves = load_market_data()
|
benchmark_returns, treasury_curves = load_market_data()
|
||||||
|
|
||||||
start = datetime(2006, 1, 1, tzinfo=pytz.utc)
|
start = datetime(year, 1, 1, tzinfo=pytz.utc)
|
||||||
end = datetime(2006, 12, 31, tzinfo=pytz.utc)
|
end = datetime(year, 12, 31, tzinfo=pytz.utc)
|
||||||
trading_environment = TradingEnvironment(
|
trading_environment = TradingEnvironment(
|
||||||
benchmark_returns,
|
benchmark_returns,
|
||||||
treasury_curves,
|
treasury_curves,
|
||||||
|
|||||||
Reference in New Issue
Block a user