changed date serialization to use a tuple of all properties rather than the epoch time to eliminate any timezone sensitivity.

add performance tracker unit tests, made various fixes to perf tracker. still have a hang on exit for zipline/test/test_finance.py:FinanceTestCase.test_orders and zipline/test/test_finance.py:FinanceTestCase.test_performance. pinging realdiehl for help...
This commit is contained in:
fawce
2012-03-11 16:21:10 -04:00
parent 8f65b71fd4
commit 366405927d
9 changed files with 87 additions and 71 deletions
+25 -24
View File
@@ -29,19 +29,15 @@ class PerformanceTracker():
self.txn_count = 0
self.event_count = 0
self.cumulative_performance = PerformancePeriod(
self.period_start,
self.period_end,
{},
capital_base,
capital_base = capital_base
starting_cash = capital_base
)
self.todays_performance = PerformancePeriod(
self.market_open,
self.market_close,
{},
capital_base,
capital_base = capital_base
starting_cash = capital_base
)
@@ -72,26 +68,33 @@ class PerformanceTracker():
self.todays_performance.calculate_performance()
def handle_market_close(self):
qutil.LOGGER.debug("###########market close###############")
self.market_open = self.market_open + self.calendar_day
while not self.trading_environment.is_trading_day(self.market_open):
if self.market_open > self.trading_environment.trading_days[-1]:
raise Exception("Attempting to backtest beyond available history.")
self.market_open = self.market_open + self.calendar_day
self.market_close = self.market_open + self.trading_day
self.day_count += 1.0
self.progress = self.day_count / self.total_days
#add the return results from today to the list of daily return objects.
todays_date = self.todays_performance.period_end.replace(hour=0, minute=0, second=0)
#add the return results from today to the list of daily return objects.
todays_date = self.market_close.replace(hour=0, minute=0, second=0)
todays_return_obj = risk.daily_return(todays_date, self.todays_performance.returns)
self.returns.append(todays_return_obj)
#calculate risk metrics for cumulative performance
self.cur_period_metrics = risk.RiskMetrics(
start_date=self.cumulative_performance.period_start,
end_date=self.cumulative_performance.period_end.replace(hour=0, minute=0, second=0),
self.cumulative_risk_metrics = risk.RiskMetrics(
start_date=self.period_start,
end_date=self.market_close.replace(hour=0, minute=0, second=0),
returns=self.returns,
trading_environment=self.trading_environment)
trading_environment=self.trading_environment
)
#move the market day markers forward
self.market_open = self.market_open + self.calendar_day
while not self.trading_environment.is_trading_day(self.market_open):
if self.market_open > self.trading_environment.trading_days[-1]:
raise Exception("Attempt to backtest beyond available history.")
self.market_open = self.market_open + self.calendar_day
self.market_close = self.market_open + self.trading_day
self.day_count += 1.0
#calculate progress of test
self.progress = self.day_count / self.total_days
######################################################################################################
#######TODO: report/relay metrics out to qexec -- values come from self.cur_period_metrics ###########
@@ -101,8 +104,6 @@ class PerformanceTracker():
#roll over positions to current day.
self.todays_performance.calculate_performance()
self.todays_performance = PerformancePeriod(
self.market_open,
self.market_close,
self.todays_performance.positions,
self.todays_performance.ending_value,
self.todays_performance.ending_cash
@@ -201,7 +202,7 @@ class PerformancePeriod():
return mktValue
def update_last_sale(self, event):
if self.positions.has_key(event.sid):
if self.positions.has_key(event.sid) and event.type == zp.DATASOURCE_TYPE.TRADE:
self.positions[event.sid].last_sale = event.price
self.positions[event.sid].last_date = event.dt
+5 -1
View File
@@ -16,6 +16,7 @@ class TradeSimulationClient(qmsg.Component):
self.prev_dt = None
self.event_queue = []
self.event_callbacks = []
self.txn_count = 0
@property
def get_id(self):
@@ -48,6 +49,9 @@ class TradeSimulationClient(qmsg.Component):
event = zp.MERGE_UNFRAME(msg)
if(event.TRANSACTION != None):
self.txn_count += 1
for cb in self.event_callbacks:
cb(event)
@@ -123,6 +127,7 @@ class OrderDataSource(qmsg.DataSource):
order_msg = rlist[0].recv()
if order_msg == str(zp.ORDER_PROTOCOL.DONE):
qutil.LOGGER.debug("Order source received done message.")
self.signal_done()
return
@@ -144,7 +149,6 @@ class OrderDataSource(qmsg.DataSource):
# or the feed will block waiting for our messages.
if(count == 0):
self.send(zp.namedict({}))
self.sent_count += 1
+2 -1
View File
@@ -287,7 +287,8 @@ class ParallelBuffer(Component):
cur_source = None
earliest_source = None
earliest_event = None
#iterate over the queues of events from all sources (1 queue per datasource)
#iterate over the queues of events from all sources
#(1 queue per datasource)
for events in self.data_buffer.values():
if len(events) == 0:
continue
+27 -35
View File
@@ -492,15 +492,14 @@ def TRADE_FRAME(event):
event.sid,
event.price,
event.volume,
event.epoch,
event.micros,
event.dt,
event.type,
]))
def TRADE_UNFRAME(msg):
try:
packed = msgpack.loads(msg)
sid, price, volume, epoch, micros, source_type = packed
sid, price, volume, dt, source_type = packed
assert isinstance(sid, int)
assert isinstance(price, numbers.Real)
@@ -509,8 +508,7 @@ def TRADE_UNFRAME(msg):
'sid' : sid,
'price' : price,
'volume' : volume,
'epoch' : epoch,
'micros' : micros,
'dt' : dt,
'type' : source_type
})
UNPACK_DATE(rval)
@@ -559,13 +557,12 @@ def TRANSACTION_FRAME(event):
event.price,
event.amount,
event.commission,
event.epoch,
event.micros
event.dt
]))
def TRANSACTION_UNFRAME(msg):
try:
sid, price, amount, commission, epoch, micros = msgpack.loads(msg)
sid, price, amount, commission, dt = msgpack.loads(msg)
assert isinstance(sid, int)
assert isinstance(price, numbers.Real)
@@ -576,8 +573,7 @@ def TRANSACTION_UNFRAME(msg):
'price' : price,
'amount' : amount,
'commission' : commission,
'epoch' : epoch,
'micros' : micros
'dt' : dt
})
UNPACK_DATE(rval)
@@ -602,8 +598,7 @@ def ORDER_SOURCE_FRAME(event):
return msgpack.dumps(tuple([
event.sid,
event.amount,
event.epoch,
event.micros,
event.dt,
event.source_id,
event.type
]))
@@ -611,12 +606,11 @@ def ORDER_SOURCE_FRAME(event):
def ORDER_SOURCE_UNFRAME(msg):
try:
sid, amount, epoch, micros, source_id, source_type = msgpack.loads(msg)
sid, amount, dt, source_id, source_type = msgpack.loads(msg)
event = namedict({
"sid" : sid,
"amount" : amount,
"epoch" : epoch,
"micros" : micros,
"dt" : dt,
"source_id" : source_id,
"type" : source_type
})
@@ -639,9 +633,8 @@ def PACK_DATE(event):
"""
Packs the datetime property of event into msgpack'able longs.
This function should be called purely for its side effects.
The event's 'dt' property is replaced by two longs: epoch and micros.
Epoch is the unix epoch time in UTC, and micros is the microsecond
property of the original event.dt datetime object.
The event's 'dt' property is replaced by a tuple of integers::
- year, month, day, hour, minute, second, microsecond
PACK_DATE and UNPACK_DATE are inverse operations.
@@ -650,33 +643,32 @@ def PACK_DATE(event):
"""
assert isinstance(event.dt, datetime.datetime)
assert event.dt.tzinfo == pytz.utc #utc only please
epoch = long(event.dt.strftime('%s'))
event['epoch'] = epoch
event['micros'] = event.dt.microsecond
event.delete('dt')
year, month, day, hour, minute, second = event.dt.timetuple()[0:6]
micros = event.dt.microsecond
event['dt'] = tuple([year, month, day, hour, minute, second, micros])
def UNPACK_DATE(event):
"""
Unpacks the datetime property of event from msgpack'able longs.
This function should be called purely for its side effects.
The event's 'dt' property is created by reading and then combining two longs: epoch and micros.
The epoch and micros properties are removed after dt is added.
The event's 'dt' property is converted to a datetime by reading and then
combining a tuple of integers.
UNPACK_DATE and PACK_DATE are inverse operations.
:param event: event must a namedict with::
- a property named 'epoch' that is an integral representing the unix \
epoch time in UTC
- a property named 'micros' that is an integral the microsecond \
property of the original event.dt datetime object
:param tuple event: event must a namedict with::
- a property named 'dt_tuple' that is a tuple of integers
representing the date and time in UTC. dt_tumple must have year,
month, day, hour, minute, second, and microsecond
:rtype: None
"""
assert isinstance(event.epoch, numbers.Integral)
assert isinstance(event.micros, numbers.Integral)
dt = datetime.datetime.fromtimestamp(event.epoch)
dt = dt.replace(microsecond = event.micros, tzinfo = pytz.utc)
event.delete('epoch')
event.delete('micros')
assert isinstance(event.dt, tuple)
assert len(event.dt) == 7
for item in event.dt:
assert isinstance(item, numbers.Integral)
year, month, day, hour, minute, second, micros = event.dt
dt = datetime.datetime(year, month, day, hour, minute, second)
dt = dt.replace(microsecond = micros, tzinfo = pytz.utc)
event.dt = dt
+3 -3
View File
@@ -75,14 +75,14 @@ class TestAlgorithm():
self.sid = sid
self.amount = amount
self.incr = 0
self.done = False
def handle_event(self, event):
qutil.LOGGER.debug(event)
#place an order for 100 shares of sid:133
if self.incr < self.count:
if event.source_id != zp.FINANCE_COMPONENT.ORDER_SOURCE:
self.trading_client.order(self.sid, self.amount)
self.incr += 1
else:
elif not self.done:
self.trading_client.signal_order_done()
self.trading_client.signal_done()
self.done = True
+13 -2
View File
@@ -11,13 +11,24 @@ def load_market_data():
bm_map = msgpack.loads(fp_bm.read())
bm_returns = []
for epoch, returns in bm_map.iteritems():
bm_returns.append(risk.daily_return(date=datetime.datetime.fromtimestamp(epoch).replace(hour=0, minute=0, second=0, tzinfo=pytz.utc), returns=returns))
event_dt = datetime.datetime.fromtimestamp(epoch)
event_dt = event_dt.replace(
hour=0,
minute=0,
second=0,
tzinfo=pytz.utc
)
daily_return = risk.daily_return(date=event_dt, returns=returns)
bm_returns.append(daily_return)
bm_returns = sorted(bm_returns, key=lambda(x): x.date)
fp_tr = open("./zipline/test/treasury_curves.msgpack", "rb")
tr_map = msgpack.loads(fp_tr.read())
tr_curves = {}
for epoch, curve in tr_map.iteritems():
tr_curves[datetime.datetime.fromtimestamp(epoch).replace(hour=0, minute=0, second=0, tzinfo=pytz.utc)] = curve
tr_dt = datetime.datetime.fromtimestamp(epoch)
tr_dt = tr_dt.replace(hour=0, minute=0, second=0, tzinfo=pytz.utc)
tr_curves[tr_dt] = curve
return bm_returns, tr_curves
+11 -3
View File
@@ -231,9 +231,10 @@ class FinanceTestCase(TestCase):
# ---------------------
# TODO: Perhaps something more self-documenting for variables names?
trade_count = 100
sid = 133
price = [10.1] * 16
volume = [100] * 16
price = [10.1] * trade_count
volume = [100] * trade_count
start_date = datetime.strptime("02/1/2012","%m/%d/%Y")
trade_time_increment = timedelta(days=1)
@@ -283,6 +284,13 @@ class FinanceTestCase(TestCase):
"The feed should be drained of all messages, found {n} remaining." \
.format(n=sim.feed.pending_messages())
)
self.assertEqual(
sim.merge.pending_messages(),
0,
"The merge should be drained of all messages, found {n} remaining." \
.format(n=sim.merge.pending_messages())
)
self.assertEqual(
test_algo.count,
@@ -299,7 +307,7 @@ class FinanceTestCase(TestCase):
transaction_sim.txn_count,
perf_tracker.txn_count,
"The perf tracker should handle the same number of transactions as\
as the simulator emits."
as the simulator emits."
)
self.assertEqual(
@@ -5,7 +5,6 @@ import datetime
import zipline.test.factory as factory
import zipline.util as qutil
import zipline.protocol as zp
import zipline.finance.performance as perf
import zipline.finance.risk as risk
class PerformanceTestCase(unittest.TestCase):
+1 -1
View File
@@ -41,7 +41,7 @@ class Risk(unittest.TestCase):
start_date = datetime.datetime(year=2006, month=1, day=1)
returns = factory.create_returns_from_list([1.0,-0.5,0.8,.17,1.0,-0.1,-0.45], start_date, self.trading_calendar)
#200, 100, 180, 210.6, 421.2, 379.8, 208.494
metrics = risk.RiskMetrics(returns[0].date, returns[-1].date, returns, self.benchmark_returns, self.treasury_curves, self.trading_calendar)
metrics = risk.RiskMetrics(returns[0].date, returns[-1].date, returns, self.trading_calendar)
self.assertEqual(metrics.max_drawdown, 0.505)
def test_benchmark_returns_06(self):