From cdb383d99935aba8b5b20d2fa17de6d6556fca15 Mon Sep 17 00:00:00 2001 From: Stephen Diehl Date: Thu, 8 Mar 2012 17:18:29 -0500 Subject: [PATCH 1/9] Backtest protocol, ala Bredeche. --- zipline/component.py | 15 +++++++++++++-- zipline/protocol.py | 21 +++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/zipline/component.py b/zipline/component.py index 4595c192..db9b7bc8 100644 --- a/zipline/component.py +++ b/zipline/component.py @@ -14,7 +14,8 @@ import humanhash from datetime import datetime import zipline.util as qutil -from zipline.protocol import CONTROL_PROTOCOL, COMPONENT_STATE +from zipline.protocol import CONTROL_PROTOCOL, COMPONENT_STATE, \ + COMPONENT_FAILURE, BACKTEST_STATE class Component(object): """ @@ -66,6 +67,7 @@ class Component(object): self.controller = None self.heartbeat_timeout = 2000 self.state_flag = COMPONENT_STATE.OK + self.error_state = COMPONENT_FAILURE.NOFAILURE self.on_done = None self._exception = None @@ -254,8 +256,17 @@ class Component(object): # Internal Maintenance # ---------------------- - def signal_exception(self, exc=None): + def signal_exception(self, exc=None, scope=None): + + if scope == 'algo': + self.error_state = COMPONENT_FAILURE.ALGOEXCEPT + else: + self.error_state = COMPONENT_FAILURE.HOSTEXCEPT + self.state_flag = COMPONENT_STATE.EXCEPTION + # mark the time of failure so we can track the failure + # progogation through the system. + self.stop_tic = time.time() self._exception = exc diff --git a/zipline/protocol.py b/zipline/protocol.py index 89726fe6..22e2547b 100644 --- a/zipline/protocol.py +++ b/zipline/protocol.py @@ -274,6 +274,27 @@ COMPONENT_STATE = Enum( 'EXCEPTION' , # 2 ) +# NOFAILURE - Component is either not running or has not failed +# ALGOEXCEPT - Exception thrown in the given algorithm +# HOSTEXCEPT - Exception thrown on our end. +# INTERRUPT - Manually interuptted by user + +COMPONENT_FAILURE = Enum( + 'NOFAILURE' , + 'ALGOEXCEPT' , + 'HOSTEXCEPT' , + 'INTERRUPT' , +) + +BACKTEST_STATE = Enum( + 'IDLE' , + 'QUEUED' , + 'INPROGRESS' , + 'CANCELLED' , # cancelled ( before natural completion ) + 'EXCEPTION' , # failure ( due to unnatural causes ) + 'DONE' , # done ( naturally completed ) +) + # ================== # Datasource Protocol # ================== From 4df88d5a503d08ac89151500f0d31200fa303a6a Mon Sep 17 00:00:00 2001 From: Stephen Diehl Date: Thu, 8 Mar 2012 18:07:43 -0500 Subject: [PATCH 2/9] Move protocol data structures. --- zipline/protocol.py | 85 +------------------------------------- zipline/protocol_utils.py | 87 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 83 deletions(-) create mode 100644 zipline/protocol_utils.py diff --git a/zipline/protocol.py b/zipline/protocol.py index 22e2547b..7286ce1d 100644 --- a/zipline/protocol.py +++ b/zipline/protocol.py @@ -118,94 +118,13 @@ import msgpack import numbers import datetime import pytz -import copy from collections import namedtuple -import zipline.util as qutil +from protocol_utils import Enum, FrameExceptionFactory, namedict + #import ujson #import ultrajson_numpy -from ctypes import Structure, c_ubyte - -def Enum(*options): - """ - Fast enums are very important when we want really tight zmq - loops. These are probably going to evolve into pure C structs - anyways so might as well get going on that. - """ - class cstruct(Structure): - _fields_ = [(o, c_ubyte) for o in options] - return cstruct(*range(len(options))) - -def FrameExceptionFactory(name): - """ - Exception factory with a closure around the frame class name. - """ - class InvalidFrame(Exception): - def __init__(self, got): - self.got = got - - def __str__(self): - return "Invalid {framecls} Frame: {got}".format( - framecls = name, - got = self.got, - ) - - return InvalidFrame - -class namedict(object): - """ - So that you can use:: - - foo.BAR - -- or -- - foo['BAR'] - - For more complex structs use collections.namedtuple: - """ - - def __init__(self, dct=None): - if(dct): - self.__dict__.update(dct) - - def __setitem__(self, key, value): - """ - Required for use by pymongo as_class parameter to find. - """ - if(key == '_id'): - self.__dict__['id'] = value - else: - self.__dict__[key] = value - - def __getitem__(self, key): - return self.__dict__[key] - - def keys(self): - return self.__dict__.keys() - - def as_dict(self): - # shallow copy is O(n) - return copy.copy(self.__dict__) - - def delete(self, key): - del(self.__dict__[key]) - - def merge(self, other_nd): - assert isinstance(other_nd, namedict) - self.__dict__.update(other_nd.__dict__) - - def __repr__(self): - return "namedict: " + str(self.__dict__) - - def __eq__(self, other): - # !!!!!!!!!!!!!!!!!!!! - # !!!! DANGEROUS !!!!! - # !!!!!!!!!!!!!!!!!!!! - return other != None and self.__dict__ == other.__dict__ - - def has_attr(self, name): - return self.__dict__.has_key(name) - # ================ # Control Protocol # ================ diff --git a/zipline/protocol_utils.py b/zipline/protocol_utils.py new file mode 100644 index 00000000..0e6cb858 --- /dev/null +++ b/zipline/protocol_utils.py @@ -0,0 +1,87 @@ +import copy +from ctypes import Structure, c_ubyte + +def Enum(*options): + """ + Fast enums are very important when we want really tight zmq + loops. These are probably going to evolve into pure C structs + anyways so might as well get going on that. + """ + class cstruct(Structure): + _fields_ = [(o, c_ubyte) for o in options] + return cstruct(*range(len(options))) + +def FrameExceptionFactory(name): + """ + Exception factory with a closure around the frame class name. + """ + class InvalidFrame(Exception): + def __init__(self, got): + self.got = got + + def __str__(self): + return "Invalid {framecls} Frame: {got}".format( + framecls = name, + got = self.got, + ) + + return InvalidFrame + +class namedict(object): + """ + + Namedicts are dict like objects that have fields accessible by attribute lookup + as well as being indexable and iterable:: + + HEARTBEAT_PROTOCOL = namedict({ + 'REQ' : b'\x01', + 'REP' : b'\x02', + }) + + HEARTBEAT_PROTOCOL.REQ # syntactic sugar + HEARTBEAT_PROTOCOL.REP # oh suga suga + + For more complex structs use collections.namedtuple: + """ + + def __init__(self, dct=None): + if(dct): + self.__dict__.update(dct) + + def __setitem__(self, key, value): + """ + Required for use by pymongo as_class parameter to find. + """ + if(key == '_id'): + self.__dict__['id'] = value + else: + self.__dict__[key] = value + + def __getitem__(self, key): + return self.__dict__[key] + + def keys(self): + return self.__dict__.keys() + + def as_dict(self): + # shallow copy is O(n) + return copy.copy(self.__dict__) + + def delete(self, key): + del(self.__dict__[key]) + + def merge(self, other_nd): + assert isinstance(other_nd, namedict) + self.__dict__.update(other_nd.__dict__) + + def __repr__(self): + return "namedict: " + str(self.__dict__) + + def __eq__(self, other): + # !!!!!!!!!!!!!!!!!!!! + # !!!! DANGEROUS !!!!! + # !!!!!!!!!!!!!!!!!!!! + return other != None and self.__dict__ == other.__dict__ + + def has_attr(self, name): + return self.__dict__.has_key(name) From aba06d40e988c6648f9466f9a3b1a60431ed88b3 Mon Sep 17 00:00:00 2001 From: Stephen Diehl Date: Tue, 13 Mar 2012 11:03:37 -0400 Subject: [PATCH 3/9] Added zmq debug console for great good. --- zipline/zmq_utils.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 zipline/zmq_utils.py diff --git a/zipline/zmq_utils.py b/zipline/zmq_utils.py new file mode 100644 index 00000000..e7c09047 --- /dev/null +++ b/zipline/zmq_utils.py @@ -0,0 +1,16 @@ +import gevent +from gevent_zeromq import zmq + +def ZmqConsole(sock_typ, socket_addr, sock_conn=None, context=None): + + context = context or zmq.Context.instance() + socket = context.socket(zmq.PULL) + socket.connect('tcp://127.0.0.1:3141') + + def console(): + while True: + msg = socket.recv() + print msg + import pdb; pdb.set_trace() + + return gevent.spawn(console) From 23a4f0680b780ae81d203bc2ba1eb86eb0de19d7 Mon Sep 17 00:00:00 2001 From: Stephen Diehl Date: Thu, 8 Mar 2012 17:18:29 -0500 Subject: [PATCH 4/9] Backtest protocol, ala Bredeche. --- zipline/component.py | 15 +++++++++++++-- zipline/protocol.py | 21 +++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/zipline/component.py b/zipline/component.py index 4595c192..db9b7bc8 100644 --- a/zipline/component.py +++ b/zipline/component.py @@ -14,7 +14,8 @@ import humanhash from datetime import datetime import zipline.util as qutil -from zipline.protocol import CONTROL_PROTOCOL, COMPONENT_STATE +from zipline.protocol import CONTROL_PROTOCOL, COMPONENT_STATE, \ + COMPONENT_FAILURE, BACKTEST_STATE class Component(object): """ @@ -66,6 +67,7 @@ class Component(object): self.controller = None self.heartbeat_timeout = 2000 self.state_flag = COMPONENT_STATE.OK + self.error_state = COMPONENT_FAILURE.NOFAILURE self.on_done = None self._exception = None @@ -254,8 +256,17 @@ class Component(object): # Internal Maintenance # ---------------------- - def signal_exception(self, exc=None): + def signal_exception(self, exc=None, scope=None): + + if scope == 'algo': + self.error_state = COMPONENT_FAILURE.ALGOEXCEPT + else: + self.error_state = COMPONENT_FAILURE.HOSTEXCEPT + self.state_flag = COMPONENT_STATE.EXCEPTION + # mark the time of failure so we can track the failure + # progogation through the system. + self.stop_tic = time.time() self._exception = exc diff --git a/zipline/protocol.py b/zipline/protocol.py index c1ad89fa..3daa7f91 100644 --- a/zipline/protocol.py +++ b/zipline/protocol.py @@ -274,6 +274,27 @@ COMPONENT_STATE = Enum( 'EXCEPTION' , # 2 ) +# NOFAILURE - Component is either not running or has not failed +# ALGOEXCEPT - Exception thrown in the given algorithm +# HOSTEXCEPT - Exception thrown on our end. +# INTERRUPT - Manually interuptted by user + +COMPONENT_FAILURE = Enum( + 'NOFAILURE' , + 'ALGOEXCEPT' , + 'HOSTEXCEPT' , + 'INTERRUPT' , +) + +BACKTEST_STATE = Enum( + 'IDLE' , + 'QUEUED' , + 'INPROGRESS' , + 'CANCELLED' , # cancelled ( before natural completion ) + 'EXCEPTION' , # failure ( due to unnatural causes ) + 'DONE' , # done ( naturally completed ) +) + # ================== # Datasource Protocol # ================== From 8002303510b6fe509a2bdef69c22c815f45e4977 Mon Sep 17 00:00:00 2001 From: Stephen Diehl Date: Thu, 8 Mar 2012 18:07:43 -0500 Subject: [PATCH 5/9] Move protocol data structures. --- zipline/protocol.py | 85 +------------------------------------- zipline/protocol_utils.py | 87 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 83 deletions(-) create mode 100644 zipline/protocol_utils.py diff --git a/zipline/protocol.py b/zipline/protocol.py index 3daa7f91..caadfaa9 100644 --- a/zipline/protocol.py +++ b/zipline/protocol.py @@ -118,94 +118,13 @@ import msgpack import numbers import datetime import pytz -import copy from collections import namedtuple -import zipline.util as qutil +from protocol_utils import Enum, FrameExceptionFactory, namedict + #import ujson #import ultrajson_numpy -from ctypes import Structure, c_ubyte - -def Enum(*options): - """ - Fast enums are very important when we want really tight zmq - loops. These are probably going to evolve into pure C structs - anyways so might as well get going on that. - """ - class cstruct(Structure): - _fields_ = [(o, c_ubyte) for o in options] - return cstruct(*range(len(options))) - -def FrameExceptionFactory(name): - """ - Exception factory with a closure around the frame class name. - """ - class InvalidFrame(Exception): - def __init__(self, got): - self.got = got - - def __str__(self): - return "Invalid {framecls} Frame: {got}".format( - framecls = name, - got = self.got, - ) - - return InvalidFrame - -class namedict(object): - """ - So that you can use:: - - foo.BAR - -- or -- - foo['BAR'] - - For more complex structs use collections.namedtuple: - """ - - def __init__(self, dct=None): - if(dct): - self.__dict__.update(dct) - - def __setitem__(self, key, value): - """ - Required for use by pymongo as_class parameter to find. - """ - if(key == '_id'): - self.__dict__['id'] = value - else: - self.__dict__[key] = value - - def __getitem__(self, key): - return self.__dict__[key] - - def keys(self): - return self.__dict__.keys() - - def as_dict(self): - # shallow copy is O(n) - return copy.copy(self.__dict__) - - def delete(self, key): - del(self.__dict__[key]) - - def merge(self, other_nd): - assert isinstance(other_nd, namedict) - self.__dict__.update(other_nd.__dict__) - - def __repr__(self): - return "namedict: " + str(self.__dict__) - - def __eq__(self, other): - # !!!!!!!!!!!!!!!!!!!! - # !!!! DANGEROUS !!!!! - # !!!!!!!!!!!!!!!!!!!! - return other != None and self.__dict__ == other.__dict__ - - def has_attr(self, name): - return self.__dict__.has_key(name) - # ================ # Control Protocol # ================ diff --git a/zipline/protocol_utils.py b/zipline/protocol_utils.py new file mode 100644 index 00000000..0e6cb858 --- /dev/null +++ b/zipline/protocol_utils.py @@ -0,0 +1,87 @@ +import copy +from ctypes import Structure, c_ubyte + +def Enum(*options): + """ + Fast enums are very important when we want really tight zmq + loops. These are probably going to evolve into pure C structs + anyways so might as well get going on that. + """ + class cstruct(Structure): + _fields_ = [(o, c_ubyte) for o in options] + return cstruct(*range(len(options))) + +def FrameExceptionFactory(name): + """ + Exception factory with a closure around the frame class name. + """ + class InvalidFrame(Exception): + def __init__(self, got): + self.got = got + + def __str__(self): + return "Invalid {framecls} Frame: {got}".format( + framecls = name, + got = self.got, + ) + + return InvalidFrame + +class namedict(object): + """ + + Namedicts are dict like objects that have fields accessible by attribute lookup + as well as being indexable and iterable:: + + HEARTBEAT_PROTOCOL = namedict({ + 'REQ' : b'\x01', + 'REP' : b'\x02', + }) + + HEARTBEAT_PROTOCOL.REQ # syntactic sugar + HEARTBEAT_PROTOCOL.REP # oh suga suga + + For more complex structs use collections.namedtuple: + """ + + def __init__(self, dct=None): + if(dct): + self.__dict__.update(dct) + + def __setitem__(self, key, value): + """ + Required for use by pymongo as_class parameter to find. + """ + if(key == '_id'): + self.__dict__['id'] = value + else: + self.__dict__[key] = value + + def __getitem__(self, key): + return self.__dict__[key] + + def keys(self): + return self.__dict__.keys() + + def as_dict(self): + # shallow copy is O(n) + return copy.copy(self.__dict__) + + def delete(self, key): + del(self.__dict__[key]) + + def merge(self, other_nd): + assert isinstance(other_nd, namedict) + self.__dict__.update(other_nd.__dict__) + + def __repr__(self): + return "namedict: " + str(self.__dict__) + + def __eq__(self, other): + # !!!!!!!!!!!!!!!!!!!! + # !!!! DANGEROUS !!!!! + # !!!!!!!!!!!!!!!!!!!! + return other != None and self.__dict__ == other.__dict__ + + def has_attr(self, name): + return self.__dict__.has_key(name) From 340fdbc7b63292c3e33a58bfc434587db3e22569 Mon Sep 17 00:00:00 2001 From: Stephen Diehl Date: Tue, 13 Mar 2012 11:03:37 -0400 Subject: [PATCH 6/9] Added zmq debug console for great good. --- zipline/zmq_utils.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 zipline/zmq_utils.py diff --git a/zipline/zmq_utils.py b/zipline/zmq_utils.py new file mode 100644 index 00000000..e7c09047 --- /dev/null +++ b/zipline/zmq_utils.py @@ -0,0 +1,16 @@ +import gevent +from gevent_zeromq import zmq + +def ZmqConsole(sock_typ, socket_addr, sock_conn=None, context=None): + + context = context or zmq.Context.instance() + socket = context.socket(zmq.PULL) + socket.connect('tcp://127.0.0.1:3141') + + def console(): + while True: + msg = socket.recv() + print msg + import pdb; pdb.set_trace() + + return gevent.spawn(console) From 674f445cda5c686bd12caa0ea239500b3694aab8 Mon Sep 17 00:00:00 2001 From: Stephen Diehl Date: Wed, 14 Mar 2012 23:48:59 -0400 Subject: [PATCH 7/9] Fix spelling mishaps. --- zipline/finance/performance.py | 143 ++++++++++++++++++--------------- zipline/finance/risk.py | 7 +- 2 files changed, 83 insertions(+), 67 deletions(-) diff --git a/zipline/finance/performance.py b/zipline/finance/performance.py index f5f675ee..f7cdcdae 100644 --- a/zipline/finance/performance.py +++ b/zipline/finance/performance.py @@ -3,6 +3,7 @@ import pytz import math import pandas +import zmq from zmq.core.poll import select import zipline.messaging as qmsg @@ -11,36 +12,48 @@ import zipline.protocol as zp import zipline.finance.risk as risk class PerformanceTracker(): - + def __init__(self, period_start, period_end, capital_base, trading_environment): - self.trading_day = datetime.timedelta(hours=6, minutes=30) - self.calendar_day = datetime.timedelta(hours=24) - self.period_start = period_start - self.period_end = period_end - self.market_open = self.period_start - self.market_close = self.market_open + self.trading_day - self.progress = 0.0 - self.total_days = (self.period_end - self.period_start).days - self.day_count = 0 - self.cumulative_capital_used= 0.0 - self.max_capital_used = 0.0 - self.capital_base = capital_base - self.trading_environment = trading_environment - self.returns = [] - self.txn_count = 0 - self.event_count = 0 + + self.trading_day = datetime.timedelta(hours = 6, minutes = 30) + self.calendar_day = datetime.timedelta(hours = 24) + + self.period_start = period_start + self.period_end = period_end + self.market_open = self.period_start + self.market_close = self.market_open + self.trading_day + self.progress = 0.0 + self.total_days = (self.period_end - self.period_start).days + self.day_count = 0 + self.cumulative_capital_used = 0.0 + self.max_capital_used = 0.0 + self.capital_base = capital_base + self.trading_environment = trading_environment + self.returns = [] + self.txn_count = 0 + self.event_count = 0 + self.result_stream = None + self.cumulative_performance = PerformancePeriod( - {}, - capital_base, + {}, + capital_base, starting_cash = capital_base ) - - self.todays_performance = PerformancePeriod( - {}, - capital_base, + + self.todays_performance = PerformancePeriod( + {}, + capital_base, starting_cash = capital_base ) - + + + def publish_to(self, zmq_socket, context=None): + ctx = context or zmq.Context.instance() + sock = ctx.socket(zmq.PUSH) + sock.connect(zmq_socket) + + self.result_stream = sock + def to_dict(self): """ Creates a dictionary representing the state of this tracker. @@ -97,50 +110,49 @@ class PerformanceTracker(): | | overkill. | +-----------------+----------------------------------------------------+ | cumulative_risk | A dictionary representing the risk metrics | - | _metrics | calculated based on the positions aggregated | + | _metrics | calculated based on the positions aggregated | | | through all the events delivered to this tracker. | | | For details look at the comments for | | | :py:meth:`zipline.finance.risk.RiskMetrics.to_dict`| +-----------------+----------------------------------------------------+ - - - + """ returns_list = [x.to_dict() for x in self.returns] d = { 'period_start' : self.period_start, 'period_end' : self.period_end, 'progress' : self.progress, - 'cumulative_captial_used' : self.cumulative_captial_used, + 'cumulative_captial_used' : self.cumulative_capital_used, 'max_capital_used' : self.max_capital_used, 'last_close' : self.market_close, 'last_open' : self.market_open, 'capital_base' : self.capital_base, 'returns' : returns_list, - 'cumulative_perf' : self.cumulative_perf.to_dict(), - 'todays_perf' : self.todays_perf.to_dict(), + 'cumulative_perf' : self.cumulative_performance.to_dict(), + 'todays_perf' : self.todays_performance.to_dict(), 'cumulative_risk_metrics' : self.cumulative_risk_metrics.to_dict() } - + return d + def update(self, event_frame): for dt, event_series in event_frame.iteritems(): data = {} data.update(event_series) event = zp.namedict(data) self.process_event(event) - + def process_event(self, event): qutil.LOGGER.debug("series is " + str(event)) self.event_count += 1 if(event.dt >= self.market_close): self.handle_market_close() - - if not pandas.isnull(event.TRANSACTION): + + if not pandas.isnull(event.TRANSACTION): self.txn_count += 1 self.cumulative_performance.execute_transaction(event.TRANSACTION) self.todays_performance.execute_transaction(event.TRANSACTION) - - # we're adding a 10% cushion to the capital used, + + # we're adding a 10% cushion to the capital used, # and then rounding to the nearest 5k transaction_cost = event.TRANSACTION.price * event.TRANSACTION.amount self.cumulative_capital_used += transaction_cost @@ -190,28 +202,30 @@ class PerformanceTracker(): #calculate progress of test self.progress = self.day_count / self.total_days - - #################################################################### - #######TODO: relay the results of self.to_dict() ########### - #################################################################### - + + # Output Results + if self.result_stream: + # TODO: proper framing + self.result_stream.send(str(self.to_dict())) + #roll over positions to current day. self.todays_performance.calculate_performance() self.todays_performance = PerformancePeriod( - self.todays_performance.positions, - self.todays_performance.ending_value, + self.todays_performance.positions, + self.todays_performance.ending_value, self.todays_performance.ending_cash ) def handle_simulation_end(self): self.risk_report = risk.RiskReport( - self.returns, + self.returns, self.trading_environment ) - - #################################################################### - #######TODO: relay the results of self.risk_report.to_dict() ####### - #################################################################### + + # Output Results + if self.result_stream: + # TODO: proper framing + self.result_stream.send(str(self.risk_report.to_dict())) def round_to_nearest(self, x, base=5): return int(base * round(float(x)/base)) @@ -274,11 +288,11 @@ class Position(): +-----------------+----------------------------------------------------+ """ state = { - 'sid':self.sid, - 'amount':self.amount, - 'cost_basis':self.cost_basis, - 'last_sale_price':self.last_sale_price, - 'last_sale_date':self.last_sale_date + 'sid' : self.sid, + 'amount' : self.amount, + 'cost_basis' : self.cost_basis, + 'last_sale_price' : self.last_sale_price, + 'last_sale_date' : self.last_sale_date } return state @@ -353,16 +367,17 @@ class PerformancePeriod(): +---------------+-----------------------------------------------------------+ """ d = { - 'ending_value':self.ending_value, - 'capital_used':self.capital_used, - 'starting_value':self.starting_value, - 'starting_cash':self.starting_cash, - 'ending_cash':self.ending_cash + 'ending_value' : self.ending_value, + 'capital_used' : self.period_capital_used, + 'starting_value' : self.starting_value, + 'starting_cash' : self.starting_cash, + 'ending_cash' : self.ending_cash } - + position_list = [] + for pos in self.positions: - position_list.append(pos.to_dict()) - - d['positions'] = positions_list - return d \ No newline at end of file + position_list.append(pos) + + d['positions'] = position_list + return d diff --git a/zipline/finance/risk.py b/zipline/finance/risk.py index cfca36bf..a3f123f9 100644 --- a/zipline/finance/risk.py +++ b/zipline/finance/risk.py @@ -46,6 +46,7 @@ class RiskMetrics(): ) raise Exception(messge) + self.trading_days = len(self.benchmark_returns) self.benchmark_volatility = self.calculate_volatility(self.benchmark_returns) self.algorithm_volatility = self.calculate_volatility(self.algorithm_returns) @@ -90,10 +91,10 @@ class RiskMetrics(): | | and self.end_date. | +-----------------+----------------------------------------------------+ """ - d = { + return { 'trading_days' : self.trading_days, 'benchmark_volatility' : self.benchmark_volatility, - 'algo_volatility' : self.algo_volatility, + 'algo_volatility' : self.algorithm_volatility, 'treasury_period_return': self.treasury_period_return, 'sharpe' : self.sharpe, 'beta' : self.beta, @@ -101,7 +102,7 @@ class RiskMetrics(): 'excess_return' : self.excess_return, 'max_drawdown' : self.max_drawdown } - + def __repr__(self): statements = [] for metric in [ From f4bdf1fc1136b3b9128b72924ff1a8e741646e8b Mon Sep 17 00:00:00 2001 From: Stephen Diehl Date: Thu, 15 Mar 2012 11:47:28 -0400 Subject: [PATCH 8/9] Rearrange docs & and fix some name conflicts. --- zipline/finance/performance.py | 317 +++++++++++++++++---------------- 1 file changed, 167 insertions(+), 150 deletions(-) diff --git a/zipline/finance/performance.py b/zipline/finance/performance.py index f7cdcdae..2be62334 100644 --- a/zipline/finance/performance.py +++ b/zipline/finance/performance.py @@ -1,12 +1,121 @@ +""" + +Performance Tracking +==================== + + +-----------------+----------------------------------------------------+ + | key | value | + +=================+====================================================+ + | period_start | The beginning of the period to be tracked. datetime| + | | in pytz.utc timezone. Will always be 0:00 on the | + | | date in UTC. The fact that the time may be on the | + | | prior day in the exchange's local time is ignored | + +-----------------+----------------------------------------------------+ + | period_end | The end of the period to be tracked. datetime | + | | in pytz.utc timezone. Will always be 23:59 on the | + | | date in UTC. The fact that the time may be on the | + | | next day in the exchange's local time is ignored | + +-----------------+----------------------------------------------------+ + | progress | percentage of test completed | + +-----------------+----------------------------------------------------+ + | cumulative_capti| The net capital used (positive is spent) through | + | al_used | the course of all the events sent to this tracker | + +-----------------+----------------------------------------------------+ + | max_capital_used| The maximum amount of capital deployed through the | + | | course of all the events sent to this tracker | + +-----------------+----------------------------------------------------+ + | last_close | The most recent close of the market. datetime in | + | | pytz.utc timezone. Will always be 23:59 on the | + | | date in UTC. The fact that the time may be on the | + | | next day in the exchange's local time is ignored | + +-----------------+----------------------------------------------------+ + | last_open | The most recent open of the market. datetime in | + | | pytz.utc timezone. Will always be 00:00 on the | + | | date in UTC. The fact that the time may be on the | + | | next day in the exchange's local time is ignored | + +-----------------+----------------------------------------------------+ + | capital_base | The initial capital assumed for this tracker. | + +-----------------+----------------------------------------------------+ + | returns | List of dicts representing daily returns. See the | + | | comments for | + | | :py:meth:`zipline.finance.risk.DailyReturn.to_dict`| + +-----------------+----------------------------------------------------+ + | cumulative_perf | A dictionary representing the cumulative | + | | performance through all the events delivered to | + | | this tracker. For details see the comments on | + | | :py:meth:`PerformancePeriod.to_dict` | + +-----------------+----------------------------------------------------+ + | todays_perf | A dictionary representing the cumulative | + | | performance through all the events delivered to | + | | this tracker with datetime stamps between last_open| + | | and last_close. For details see the comments on | + | | :py:meth:`PerformancePeriod.to_dict` | + | | TODO: adding this because we calculate it. May be | + | | overkill. | + +-----------------+----------------------------------------------------+ + | cumulative_risk | A dictionary representing the risk metrics | + | _metrics | calculated based on the positions aggregated | + | | through all the events delivered to this tracker. | + | | For details look at the comments for | + | | :py:meth:`zipline.finance.risk.RiskMetrics.to_dict`| + +-----------------+----------------------------------------------------+ + | timestamp | System time evevent occurs in zipilne | + +-----------------+----------------------------------------------------+ + + +Position Tracking +================= + + +-----------------+----------------------------------------------------+ + | key | value | + +=================+====================================================+ + | sid | the identifier for the security held in this | + | | position. | + +-----------------+----------------------------------------------------+ + | amount | whole number of shares in the position | + +-----------------+----------------------------------------------------+ + | last_sale_price | price at last sale of the security on the exchange | + +-----------------+----------------------------------------------------+ + | last_sale_date | datetime of the last trade of the position's | + | | security on the exchange | + +-----------------+----------------------------------------------------+ + | timestamp | System time evevent occurs in zipilne | + +-----------------+----------------------------------------------------+ + +Performance Period +================== + + +---------------+------------------------------------------------------+ + | key | value | + +===============+======================================================+ + | ending_value | the total market value of the positions held at the | + | | end of the period | + +---------------+------------------------------------------------------+ + | capital_used | the net capital consumed (positive means spent) by | + | | buying and selling securities in the period | + +---------------+------------------------------------------------------+ + | starting_value| the total market value of the positions held at the | + | | start of the period | + +---------------+------------------------------------------------------+ + | starting_cash | cash on hand at the beginning of the period | + +---------------+------------------------------------------------------+ + | ending_cash | cash on hand at the end of the period | + +---------------+------------------------------------------------------+ + | positions | a list of dicts representing positions, see | + | | :py:meth:`Position.to_dict()` | + | | for details on the contents of the dict | + +---------------+------------------------------------------------------+ + | timestamp | System time evevent occurs in zipilne | + +---------------+------------------------------------------------------+ + +""" import datetime -import pytz -import math +import msgpack import pandas +import math import zmq -from zmq.core.poll import select -import zipline.messaging as qmsg import zipline.util as qutil import zipline.protocol as zp import zipline.finance.risk as risk @@ -56,69 +165,13 @@ class PerformanceTracker(): def to_dict(self): """ - Creates a dictionary representing the state of this tracker. - Returns a dict object of the form: - - +-----------------+----------------------------------------------------+ - | key | value | - +=================+====================================================+ - | period_start | The beginning of the period to be tracked. datetime| - | | in pytz.utc timezone. Will always be 0:00 on the | - | | date in UTC. The fact that the time may be on the | - | | prior day in the exchange's local time is ignored | - +-----------------+----------------------------------------------------+ - | period_end | The end of the period to be tracked. datetime | - | | in pytz.utc timezone. Will always be 23:59 on the | - | | date in UTC. The fact that the time may be on the | - | | next day in the exchange's local time is ignored | - +-----------------+----------------------------------------------------+ - | progress | percentage of test completed | - +-----------------+----------------------------------------------------+ - | cumulative_capti| The net capital used (positive is spent) through | - | al_used | the course of all the events sent to this tracker | - +-----------------+----------------------------------------------------+ - | max_capital_used| The maximum amount of capital deployed through the | - | | course of all the events sent to this tracker | - +-----------------+----------------------------------------------------+ - | last_close | The most recent close of the market. datetime in | - | | pytz.utc timezone. Will always be 23:59 on the | - | | date in UTC. The fact that the time may be on the | - | | next day in the exchange's local time is ignored | - +-----------------+----------------------------------------------------+ - | last_open | The most recent open of the market. datetime in | - | | pytz.utc timezone. Will always be 00:00 on the | - | | date in UTC. The fact that the time may be on the | - | | next day in the exchange's local time is ignored | - +-----------------+----------------------------------------------------+ - | capital_base | The initial capital assumed for this tracker. | - +-----------------+----------------------------------------------------+ - | returns | List of dicts representing daily returns. See the | - | | comments for | - | | :py:meth:`zipline.finance.risk.DailyReturn.to_dict`| - +-----------------+----------------------------------------------------+ - | cumulative_perf | A dictionary representing the cumulative | - | | performance through all the events delivered to | - | | this tracker. For details see the comments on | - | | :py:meth:`PerformancePeriod.to_dict` | - +-----------------+----------------------------------------------------+ - | todays_perf | A dictionary representing the cumulative | - | | performance through all the events delivered to | - | | this tracker with datetime stamps between last_open| - | | and last_close. For details see the comments on | - | | :py:meth:`PerformancePeriod.to_dict` | - | | TODO: adding this because we calculate it. May be | - | | overkill. | - +-----------------+----------------------------------------------------+ - | cumulative_risk | A dictionary representing the risk metrics | - | _metrics | calculated based on the positions aggregated | - | | through all the events delivered to this tracker. | - | | For details look at the comments for | - | | :py:meth:`zipline.finance.risk.RiskMetrics.to_dict`| - +-----------------+----------------------------------------------------+ + Creates a dictionary representing the state of this tracker. + Returns a dict object of the form: """ returns_list = [x.to_dict() for x in self.returns] - d = { + + return { 'period_start' : self.period_start, 'period_end' : self.period_end, 'progress' : self.progress, @@ -130,9 +183,9 @@ class PerformanceTracker(): 'returns' : returns_list, 'cumulative_perf' : self.cumulative_performance.to_dict(), 'todays_perf' : self.todays_performance.to_dict(), - 'cumulative_risk_metrics' : self.cumulative_risk_metrics.to_dict() + 'cumulative_risk_metrics' : self.cumulative_risk_metrics.to_dict(), + 'timestamp' : datetime.datetime.now(), } - return d def update(self, event_frame): for dt, event_series in event_frame.iteritems(): @@ -158,55 +211,55 @@ class PerformanceTracker(): self.cumulative_capital_used += transaction_cost if(math.fabs(self.cumulative_capital_used) > self.max_capital_used): self.max_capital_used = math.fabs(self.cumulative_capital_used) - + cushioned_capital = 1.1 * self.max_capital_used self.max_capital_used = self.round_to_nearest( - cushioned_capital, + cushioned_capital, base=5000 ) self.max_leverage = self.max_capital_used/self.capital_base - - #update last sale + + #update last sale self.cumulative_performance.update_last_sale(event) self.todays_performance.update_last_sale(event) - + #calculate performance as of last trade self.cumulative_performance.calculate_performance() self.todays_performance.calculate_performance() - + def handle_market_close(self): #add the return results from today to the list of DailyReturn objects. todays_date = self.market_close.replace(hour=0, minute=0, second=0) todays_return_obj = risk.DailyReturn( - todays_date, + todays_date, self.todays_performance.returns ) self.returns.append(todays_return_obj) - + #calculate risk metrics for cumulative performance self.cumulative_risk_metrics = risk.RiskMetrics( - start_date=self.period_start, - end_date=self.market_close.replace(hour=0, minute=0, second=0), + 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 ) - + #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.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 # Output Results if self.result_stream: # TODO: proper framing - self.result_stream.send(str(self.to_dict())) + self.result_stream.send_pyobj(self.to_dict()) #roll over positions to current day. self.todays_performance.calculate_performance() @@ -225,26 +278,27 @@ class PerformanceTracker(): # Output Results if self.result_stream: # TODO: proper framing - self.result_stream.send(str(self.risk_report.to_dict())) - + self.result_stream.send_pyobj(self.risk_report.to_dict()) + def round_to_nearest(self, x, base=5): return int(base * round(float(x)/base)) class Position(): - + def __init__(self, sid): self.sid = sid self.amount = 0 self.cost_basis = 0.0 ##per share self.last_sale_price = None self.last_sale_date = None - + def update(self, txn): if(self.sid != txn.sid): raise NameError('updating position with txn for a different sid') #throw exception - - if(self.amount + txn.amount == 0): #we're covering a short or closing a position + + #we're covering a short or closing a position + if(self.amount + txn.amount == 0): self.cost_basis = 0.0 self.amount = 0 else: @@ -254,130 +308,93 @@ class Position(): total_shares = self.amount + txn.amount self.cost_basis = total_cost/total_shares self.amount = self.amount + txn.amount - + def currentValue(self): return self.amount * self.last_sale_price - - + + def __repr__(self): template = "sid: {sid}, amount: {amount}, cost_basis: {cost_basis}, \ - last_sale_price: {last_sale_price}" + last_sale_price: {last_sale_price}" return template.format( - sid=self.sid, - amount=self.amount, - cost_basis=self.cost_basis, + sid=self.sid, + amount=self.amount, + cost_basis=self.cost_basis, last_sale_price=self.last_sale_price ) - + def to_dict(self): """ Creates a dictionary representing the state of this position. Returns a dict object of the form: - +-----------------+----------------------------------------------------+ - | key | value | - +=================+====================================================+ - | sid | the identifier for the security held in this | - | | position. | - +-----------------+----------------------------------------------------+ - | amount | whole number of shares in the position | - +-----------------+----------------------------------------------------+ - | last_sale_price | price at last sale of the security on the exchange | - +-----------------+----------------------------------------------------+ - | last_sale_date | datetime of the last trade of the position's | - | | security on the exchange | - +-----------------+----------------------------------------------------+ """ state = { 'sid' : self.sid, 'amount' : self.amount, 'cost_basis' : self.cost_basis, 'last_sale_price' : self.last_sale_price, - 'last_sale_date' : self.last_sale_date + 'last_sale_date' : self.last_sale_date, + 'timestamp' : datetime.datetime.now(), } return state - + class PerformancePeriod(): - + def __init__(self, initial_positions, starting_value, starting_cash): self.ending_value = 0.0 self.period_capital_used = 0.0 self.pnl = 0.0 #sid => position object - self.positions = initial_positions + self.positions = initial_positions self.starting_value = starting_value #cash balance at start of period self.starting_cash = starting_cash self.ending_cash = starting_cash - + def calculate_performance(self): self.ending_value = self.calculate_positions_value() - + total_at_start = self.starting_cash + self.starting_value self.ending_cash = self.starting_cash + self.period_capital_used total_at_end = self.ending_cash + self.ending_value - + self.pnl = total_at_end - total_at_start if(total_at_start != 0): self.returns = self.pnl / total_at_start else: self.returns = 0.0 - + def execute_transaction(self, txn): if(not self.positions.has_key(txn.sid)): self.positions[txn.sid] = Position(txn.sid) self.positions[txn.sid].update(txn) self.period_capital_used += -1 * txn.price * txn.amount - + def calculate_positions_value(self): mktValue = 0.0 for key,pos in self.positions.iteritems(): mktValue += pos.currentValue() return mktValue - + def update_last_sale(self, event): is_trade = event.type == zp.DATASOURCE_TYPE.TRADE if self.positions.has_key(event.sid) and is_trade: - self.positions[event.sid].last_sale_price = event.price + self.positions[event.sid].last_sale_price = event.price self.positions[event.sid].last_sale_date = event.dt - + def to_dict(self): """ Creates a dictionary representing the state of this performance period Returns a dict object of the form: - -+---------------+-----------------------------------------------------------+ -| key | value | -+===============+===========================================================+ -| ending_value | the total market value of the positions held at the | -| | end of the period | -+---------------+-----------------------------------------------------------+ -| capital_used | the net capital consumed (positive means spent) by | -| | buying and selling securities in the period | -+---------------+-----------------------------------------------------------+ -| starting_value| the total market value of the positions held at the | -| | start of the period | -+---------------+-----------------------------------------------------------+ -| starting_cash | cash on hand at the beginning of the period | -+---------------+-----------------------------------------------------------+ -| ending_cash | cash on hand at the end of the period | -+---------------+-----------------------------------------------------------+ -| positions | a list of dicts representing positions, see | -| | :py:meth:`Position.to_dict()` | -| | for details on the contents of the dict | -+---------------+-----------------------------------------------------------+ + """ - d = { + + return { 'ending_value' : self.ending_value, 'capital_used' : self.period_capital_used, 'starting_value' : self.starting_value, 'starting_cash' : self.starting_cash, - 'ending_cash' : self.ending_cash + 'ending_cash' : self.ending_cash, + 'positions' : self.positions, + 'timestamp' : datetime.datetime.now(), } - - position_list = [] - - for pos in self.positions: - position_list.append(pos) - - d['positions'] = position_list - return d From f980e7d22fe89d24d61c3ed9087bdf1ff9afe5d9 Mon Sep 17 00:00:00 2001 From: Stephen Diehl Date: Thu, 15 Mar 2012 16:08:01 -0400 Subject: [PATCH 9/9] Interstitial commit for fawce. --- zipline/finance/performance.py | 62 +++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/zipline/finance/performance.py b/zipline/finance/performance.py index 2be62334..21557dc6 100644 --- a/zipline/finance/performance.py +++ b/zipline/finance/performance.py @@ -121,6 +121,17 @@ import zipline.protocol as zp import zipline.finance.risk as risk class PerformanceTracker(): + """ + + Tracks the performance of the zipstream as it is running in + the simulotr, relays this out to the Deluge broker and then + to the client. + + +--------------------+ Result Stream +--------+ + | PerformanceTracker | ----------------> | Deluge | + +--------------------+ +--------+ + + """ def __init__(self, period_start, period_end, capital_base, trading_environment): @@ -157,6 +168,10 @@ class PerformanceTracker(): def publish_to(self, zmq_socket, context=None): + """ + Publish the performance results asynchronously to a + socket. + """ ctx = context or zmq.Context.instance() sock = ctx.socket(zmq.PUSH) sock.connect(zmq_socket) @@ -167,24 +182,24 @@ class PerformanceTracker(): """ Creates a dictionary representing the state of this tracker. Returns a dict object of the form: - """ + returns_list = [x.to_dict() for x in self.returns] return { - 'period_start' : self.period_start, - 'period_end' : self.period_end, - 'progress' : self.progress, - 'cumulative_captial_used' : self.cumulative_capital_used, - 'max_capital_used' : self.max_capital_used, - 'last_close' : self.market_close, - 'last_open' : self.market_open, - 'capital_base' : self.capital_base, - 'returns' : returns_list, - 'cumulative_perf' : self.cumulative_performance.to_dict(), - 'todays_perf' : self.todays_performance.to_dict(), - 'cumulative_risk_metrics' : self.cumulative_risk_metrics.to_dict(), - 'timestamp' : datetime.datetime.now(), + 'period_start' : self.period_start, + 'period_end' : self.period_end, + 'progress' : self.progress, + 'cumulative_captial_used' : self.cumulative_capital_used, + 'max_capital_used' : self.max_capital_used, + 'last_close' : self.market_close, + 'last_open' : self.market_open, + 'capital_base' : self.capital_base, + 'returns' : returns_list, + 'cumulative_perf' : self.cumulative_performance.to_dict(), + 'todays_perf' : self.todays_performance.to_dict(), + 'cumulative_risk_metrics' : self.cumulative_risk_metrics.to_dict(), + 'timestamp' : datetime.datetime.now(), } def update(self, event_frame): @@ -196,7 +211,9 @@ class PerformanceTracker(): def process_event(self, event): qutil.LOGGER.debug("series is " + str(event)) + self.event_count += 1 + if(event.dt >= self.market_close): self.handle_market_close() @@ -209,7 +226,8 @@ class PerformanceTracker(): # and then rounding to the nearest 5k transaction_cost = event.TRANSACTION.price * event.TRANSACTION.amount self.cumulative_capital_used += transaction_cost - if(math.fabs(self.cumulative_capital_used) > self.max_capital_used): + + if math.fabs(self.cumulative_capital_used) > self.max_capital_used: self.max_capital_used = math.fabs(self.cumulative_capital_used) cushioned_capital = 1.1 * self.max_capital_used @@ -217,7 +235,7 @@ class PerformanceTracker(): cushioned_capital, base=5000 ) - self.max_leverage = self.max_capital_used/self.capital_base + self.max_leverage = self.max_capital_used / self.capital_base #update last sale self.cumulative_performance.update_last_sale(event) @@ -246,10 +264,12 @@ class PerformanceTracker(): #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 @@ -270,6 +290,8 @@ class PerformanceTracker(): ) def handle_simulation_end(self): + assert False + self.risk_report = risk.RiskReport( self.returns, self.trading_environment @@ -280,9 +302,12 @@ class PerformanceTracker(): # TODO: proper framing self.result_stream.send_pyobj(self.risk_report.to_dict()) + self.result_stream.send_pyobj(None) + def round_to_nearest(self, x, base=5): return int(base * round(float(x)/base)) + class Position(): def __init__(self, sid): @@ -295,7 +320,6 @@ class Position(): def update(self, txn): if(self.sid != txn.sid): raise NameError('updating position with txn for a different sid') - #throw exception #we're covering a short or closing a position if(self.amount + txn.amount == 0): @@ -328,7 +352,7 @@ class Position(): Creates a dictionary representing the state of this position. Returns a dict object of the form: """ - state = { + return { 'sid' : self.sid, 'amount' : self.amount, 'cost_basis' : self.cost_basis, @@ -336,7 +360,7 @@ class Position(): 'last_sale_date' : self.last_sale_date, 'timestamp' : datetime.datetime.now(), } - return state + class PerformancePeriod():