diff --git a/docs/zipline.finance.rst b/docs/zipline.finance.rst index c339bc89..e99189f6 100644 --- a/docs/zipline.finance.rst +++ b/docs/zipline.finance.rst @@ -1,10 +1,10 @@ finance Package =============== -:mod:`data` Module ------------------- +:mod:`performance` Module +------------------------- -.. automodule:: zipline.finance.data +.. automodule:: zipline.finance.performance :members: :undoc-members: :show-inheritance: diff --git a/docs/zipline.rst b/docs/zipline.rst index 386b03de..889ee67c 100644 --- a/docs/zipline.rst +++ b/docs/zipline.rst @@ -9,14 +9,6 @@ zipline Package :undoc-members: :show-inheritance: -:mod:`cli` Module ------------------ - -.. automodule:: zipline.cli - :members: - :undoc-members: - :show-inheritance: - :mod:`component` Module ----------------------- @@ -57,6 +49,14 @@ zipline Package :undoc-members: :show-inheritance: +:mod:`simulator` Module +----------------------- + +.. automodule:: zipline.simulator + :members: + :undoc-members: + :show-inheritance: + :mod:`sources` Module --------------------- @@ -65,6 +65,14 @@ zipline Package :undoc-members: :show-inheritance: +:mod:`topology` Module +---------------------- + +.. automodule:: zipline.topology + :members: + :undoc-members: + :show-inheritance: + :mod:`topos` Module ------------------- @@ -86,6 +94,7 @@ Subpackages .. toctree:: + zipline.finance zipline.test zipline.transforms diff --git a/docs/zipline.test.rst b/docs/zipline.test.rst index 443c1e28..470d4b2a 100644 --- a/docs/zipline.test.rst +++ b/docs/zipline.test.rst @@ -9,10 +9,18 @@ test Package :undoc-members: :show-inheritance: -:mod:`test_devsimulator` Module -------------------------------- +:mod:`factory` Module +--------------------- -.. automodule:: zipline.test.test_devsimulator +.. automodule:: zipline.test.factory + :members: + :undoc-members: + :show-inheritance: + +:mod:`test_finance` Module +-------------------------- + +.. automodule:: zipline.test.test_finance :members: :undoc-members: :show-inheritance: @@ -33,6 +41,22 @@ test Package :undoc-members: :show-inheritance: +:mod:`test_perf_tracking` Module +-------------------------------- + +.. automodule:: zipline.test.test_perf_tracking + :members: + :undoc-members: + :show-inheritance: + +:mod:`test_risk` Module +----------------------- + +.. automodule:: zipline.test.test_risk + :members: + :undoc-members: + :show-inheritance: + :mod:`test_sanity` Module ------------------------- @@ -41,3 +65,11 @@ test Package :undoc-members: :show-inheritance: +:mod:`transform` Module +----------------------- + +.. automodule:: zipline.test.transform + :members: + :undoc-members: + :show-inheritance: + diff --git a/zipline/finance/performance.py b/zipline/finance/performance.py index a4b1223c..79e10309 100644 --- a/zipline/finance/performance.py +++ b/zipline/finance/performance.py @@ -40,37 +40,131 @@ class PerformanceTracker(): starting_cash = capital_base ) - + def to_dict(self): + """ + Creates a dictionary representing the state of this tracker. + Returns a dict object of the form: - def update(self, event): - self.event_count += 1 - if(event.dt >= self.market_close): - self.handle_market_close() + +-----------------+----------------------------------------------------+ + | 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`| + +-----------------+----------------------------------------------------+ + + + + """ + 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, + '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_risk_metrics' : self.cumulative_risk_metrics.to_dict() + } + + def update(self, event_frame): + for dt, event_series in event_frame.iteritems(): + self.process_event(event_series) + + 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 event.TRANSACTION != None: + self.txn_count += 1 + self.cumulative_performance.execute_transaction(event.TRANSACTION) + self.todays_performance.execute_transaction(event.TRANSACTION) - if event.TRANSACTION != None: - 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, and then rounding to the nearest 5k - self.cumulative_capital_used += event.TRANSACTION.price * event.TRANSACTION.amount - if(math.fabs(self.cumulative_capital_used) > self.max_capital_used): - self.max_capital_used = math.fabs(self.cumulative_capital_used) - self.max_capital_used = self.round_to_nearest(1.1 * self.max_capital_used, base=5000) - self.max_leverage = self.max_capital_used/self.capital_base + # 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 + if(math.fabs(self.cumulative_capital_used) > self.max_capital_used): + self.max_capital_used = math.fabs(self.cumulative_capital_used) - #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() - + cushioned_capital = 1.1 * self.max_capital_used + self.max_capital_used = self.round_to_nearest( + cushioned_capital, + base=5000 + ) + self.max_leverage = self.max_capital_used/self.capital_base + + #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 daily return objects. + #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.daily_return(todays_date, self.todays_performance.returns) + todays_return_obj = risk.DailyReturn( + todays_date, + self.todays_performance.returns + ) self.returns.append(todays_return_obj) #calculate risk metrics for cumulative performance @@ -92,14 +186,10 @@ class PerformanceTracker(): #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 ########### - #######TODO: report/relay position data out to qexec -- values come from self.cumulative_performance # - ###################################################################################################### + + #################################################################### + #######TODO: relay the results of self.to_dict() ########### + #################################################################### #roll over positions to current day. self.todays_performance.calculate_performance() @@ -115,24 +205,21 @@ class PerformanceTracker(): self.trading_environment ) - ###################################################################################################### - #######TODO: report/relay metrics out to qexec -- values come from self.risk_report ########### - ###################################################################################################### + #################################################################### + #######TODO: relay the results of self.risk_report.to_dict() ####### + #################################################################### def round_to_nearest(self, x, base=5): return int(base * round(float(x)/base)) class Position(): - sid = None - amount = None - cost_basis = None - last_sale = None - last_date = None 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): @@ -156,24 +243,53 @@ class Position(): def __repr__(self): template = "sid: {sid}, amount: {amount}, cost_basis: {cost_basis}, \ - last_sale: {last_sale}" + last_sale_price: {last_sale_price}" return template.format( sid=self.sid, amount=self.amount, cost_basis=self.cost_basis, - last_sale=self.last_sale + 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 + } + 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.positions = initial_positions #sid => position object - self.starting_value = starting_value + self.ending_value = 0.0 + self.period_capital_used = 0.0 + self.pnl = 0.0 + #sid => position object + 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 + self.starting_cash = starting_cash + self.ending_cash = starting_cash def calculate_performance(self): self.ending_value = self.calculate_positions_value() @@ -194,7 +310,6 @@ class PerformancePeriod(): 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(): @@ -202,9 +317,48 @@ class PerformancePeriod(): return mktValue def update_last_sale(self, event): - 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 + 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_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 = { + '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 + } - - \ No newline at end of file + 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 diff --git a/zipline/finance/risk.py b/zipline/finance/risk.py index c865aaa6..cfca36bf 100644 --- a/zipline/finance/risk.py +++ b/zipline/finance/risk.py @@ -7,20 +7,25 @@ import zipline.util as qutil import zipline.protocol as zp from pymongo import ASCENDING, DESCENDING -class daily_return(): +class DailyReturn(): def __init__(self, date, returns): self.date = date self.returns = returns + def to_dict(self): + d = { + 'dt': self.date, + 'returns': self.returns + } + + return d + def __repr__(self): return str(self.date) + " - " + str(self.returns) class RiskMetrics(): def __init__(self, start_date, end_date, returns, trading_environment): - """ - :param treasury_curves: {datetime in utc -> {duration label -> interest rate}} - """ self.treasury_curves = trading_environment.treasury_curves self.start_date = start_date @@ -31,32 +36,100 @@ class RiskMetrics(): self.benchmark_period_returns, self.benchmark_returns = self.calculate_period_returns(benchmark_returns) if(len(self.benchmark_returns) != len(self.algorithm_returns)): - raise Exception("Mismatch between benchmark_returns ({bm_count}) and algorithm_returns ({algo_count}) in range {start} : {end}".format( - bm_count=len(self.benchmark_returns), - algo_count=len(self.algorithm_returns), - start=start_date, - end=end_date)) + message = "Mismatch between benchmark_returns ({bm_count}) and \ + algorithm_returns ({algo_count}) in range {start} : {end}" + message.format( + bm_count=len(self.benchmark_returns), + algo_count=len(self.algorithm_returns), + start=start_date, + end=end_date + ) + + 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) self.treasury_period_return = self.choose_treasury() self.sharpe = self.calculate_sharpe() - self.beta, self.algorithm_covariance, self.benchmark_variance, self.condition_number, self.eigen_values = self.calculate_beta() + self.beta, self.algorithm_covariance, self.benchmark_variance, \ + self.condition_number, self.eigen_values = self.calculate_beta() self.alpha = self.calculate_alpha() self.excess_return = self.algorithm_period_returns - self.treasury_period_return self.max_drawdown = self.calculate_max_drawdown() + def to_dict(self): + """ + +-----------------+----------------------------------------------------+ + | key | value | + +=================+====================================================+ + | trading_days | The number of trading days between self.start_date | + | | and self.end_date | + +-----------------+----------------------------------------------------+ + | benchmark_volat\| The volatility of the benchmark between | + | ility | self.start_date and self.end_date. | + +-----------------+----------------------------------------------------+ + | algo_volatility | The volatility of the algo between self.start_date | + | | and self.end_date. | + +-----------------+----------------------------------------------------+ + | treasury_period\| The return of treasuries over the period. Treasury | + | _return | maturity is chosen to match the duration of the | + | | test period. | + +-----------------+----------------------------------------------------+ + | sharpe | The sharpe ratio based on the _algorithm_ (rather | + | | than the static portfolio) returns. | + +-----------------+----------------------------------------------------+ + | beta | The _algorithm_ beta to the benchmark. | + +-----------------+----------------------------------------------------+ + | alpha | The _algorithm_ alpha to the benchmark. | + +-----------------+----------------------------------------------------+ + | excess_return | The excess return of the algorithm over the | + | | benchmark. | + +-----------------+----------------------------------------------------+ + | max_drawdown | The largest relative peak to relative trough move | + | | for the portfolio returns between self.start_date | + | | and self.end_date. | + +-----------------+----------------------------------------------------+ + """ + d = { + 'trading_days' : self.trading_days, + 'benchmark_volatility' : self.benchmark_volatility, + 'algo_volatility' : self.algo_volatility, + 'treasury_period_return': self.treasury_period_return, + 'sharpe' : self.sharpe, + 'beta' : self.beta, + 'alpha' : self.alpha, + 'excess_return' : self.excess_return, + 'max_drawdown' : self.max_drawdown + } + def __repr__(self): statements = [] - for metric in ["algorithm_period_returns", "benchmark_period_returns", "excess_return", "trading_days", "benchmark_volatility", "algorithm_volatility", "sharpe", "algorithm_covariance", "benchmark_variance", "beta", "alpha", "max_drawdown", "algorithm_returns", "benchmark_returns", "condition_number", "eigen_values"]: + for metric in [ + "algorithm_period_returns", + "benchmark_period_returns", + "excess_return", + "trading_days", + "benchmark_volatility", + "algorithm_volatility", + "sharpe", + "algorithm_covariance", + "benchmark_variance", + "beta", + "alpha", + "max_drawdown", + "algorithm_returns", + "benchmark_returns", + "condition_number", + "eigen_values" + ]: value = getattr(self, metric) - statements.append("{metric}:{value}".format(metric=metric, value=value)) + statements.append("{m}:{v}".format(m=metric, v=value)) return '\n'.join(statements) def calculate_period_returns(self, daily_returns): + #TODO: replace this with pandas. returns = [x.returns for x in daily_returns if x.date >= self.start_date and x.date <= self.end_date and self.trading_environment.is_trading_day(x.date)] - #qutil.LOGGER.debug("using {count} daily returns out of {total}".format(count=len(returns),total=len(daily_returns))) period_returns = 1.0 for r in returns: period_returns = period_returns * (1.0 + r) @@ -71,8 +144,8 @@ class RiskMetrics(): return (self.algorithm_period_returns - self.treasury_period_return) / self.algorithm_volatility def calculate_beta(self): - #qutil.LOGGER.debug("algorithm has {acount} days, benchmark has {bmcount} days".format(acount=len(self.algorithm_returns), bmcount=len(self.benchmark_returns))) - #it doesn't make much sense to calculate beta for less than two days, so return none. + #it doesn't make much sense to calculate beta for less than two days, + #so return none. if len(self.algorithm_returns) < 2: return 0.0, 0.0, 0.0, 0.0, [] returns_matrix = np.vstack([self.algorithm_returns, self.benchmark_returns]) @@ -82,7 +155,6 @@ class RiskMetrics(): algorithm_covariance = C[0][1] benchmark_variance = C[1][1] beta = C[0][1] / C[1][1] - #qutil.LOGGER.debug("bm variance is {bmv}, returns matrix is {rm}, covariance is {c}, beta is {beta}".format(rm=returns_matrix, bmv=C[1][1], c=C, beta=beta)) return beta, algorithm_covariance, benchmark_variance, condition_number, eigen_values @@ -101,7 +173,6 @@ class RiskMetrics(): cur_return = 0.0 compounded_returns.append(cur_return) - #qutil.LOGGER.debug("compounded returns are {cr}".format(cr=compounded_returns)) cur_max = None max_drawdown = None for cur in compounded_returns: @@ -112,7 +183,6 @@ class RiskMetrics(): if max_drawdown == None or drawdown < max_drawdown: max_drawdown = drawdown - #qutil.LOGGER.debug("max drawdown is: {dd}".format(dd=max_drawdown)) if max_drawdown == None: return 0.0 @@ -146,10 +216,10 @@ class RiskMetrics(): one_day = datetime.timedelta(days=1) curve = None - #in case end date is not a trading day, search for the next market day for an interest rate + # in case end date is not a trading day, search for the next market + # day for an interest rate for i in range(7): if(self.treasury_curves.has_key(self.end_date + i * one_day)): - #qutil.LOGGER.info(self.treasury_curves[self.end_date + i * one_day]) curve = self.treasury_curves[self.end_date + i * one_day] break @@ -162,38 +232,58 @@ class RiskMetrics(): if rate != None: return rate * (td.days + 1) / 365 - raise Exception("no rate for end date = {dt} and term = {term}. Using zero.".format(dt=self.end_date, - term=self.treasury_duration)) + message = "no rate for end date = {dt} and term = {term}. Using zero." + message = message.format(dt=self.end_date,term=self.treasury_duration) + raise Exception(message) class RiskReport(): - def __init__(self, algorithm_returns, benchmark_returns, treasury_curves, trading_environment): - """algorithm_returns needs to be a list of daily_return objects sorted in date ascending order""" + def __init__(self, algorithm_returns, trading_environment): + """ algorithm_returns needs to be a list of daily_return objects + sorted in date ascending order + """ self.algorithm_returns = algorithm_returns - self.treasury_curves = treasury_curves self.trading_environment = trading_environment + + start_date = self.algorithm_returns[0].date + end_date = self.algorithm_returns[-1].date + self.month_periods = self.periodsInRange(1, start_date, end_date) + self.three_month_periods = self.periodsInRange(3, start_date, end_date) + self.six_month_periods = self.periodsInRange(6, start_date, end_date) + self.year_periods = self.periodsInRange(12, start_date, end_date) - #calculate month ends - self.month_periods = self.periodsInRange(1, self.algorithm_returns[0].date, self.algorithm_returns[-1].date) - #calculate 3 month ends - self.three_month_periods = self.periodsInRange(3, self.algorithm_returns[0].date, self.algorithm_returns[-1].date) - #calculate 6 month ends - self.six_month_periods = self.periodsInRange(6, self.algorithm_returns[0].date, self.algorithm_returns[-1].date) - #calculate 1 year ends - self.year_periods = self.periodsInRange(12, self.algorithm_returns[0].date, self.algorithm_returns[-1].date) - #calculate 3 year ends - self.three_year_periods = self.periodsInRange(36, self.algorithm_returns[0].date, self.algorithm_returns[-1].date) - #calculate 5 year ends - self.five_year_periods = self.periodsInRange(60, self.algorithm_returns[0].date, self.algorithm_returns[-1].date) + def to_dict(self): + """ + RiskMetrics are calculated for rolling windows in four lengths:: + - 1_month + - 3_month + - 6_month + - 12_month + The return value of this funciton is a dictionary keyed by the above + list of durations. The value of each entry is a list of RiskMetric + dicts of the same duration as denoted by the top_level key. + + See :py:meth:`RiskMetrics.to_dict` for the detailed list of fields + provided for each period. + """ + d = { + '1_month' : [x.to_dict() for x in self.month_periods], + '3_month' : [x.to_dict() for x in self.three_year_periods], + '6_month' : [x.to_dict() for x in self.six_month_periods], + '12_month' : [x.to_dict() for x in self.month_periods] + } + + return d def periodsInRange(self, months_per, start, end): one_day = datetime.timedelta(days = 1) ends = [] cur_start = start.replace(day=1) - #ensure that we have an end at the end of a calendar month, in case the return series ends mid-month... + #ensure that we have an end at the end of a calendar month, in case + #the return series ends mid-month... the_end = advance_by_months(end.replace(day=1),1) - one_day while True: cur_end = advance_by_months(cur_start, months_per) - one_day @@ -223,9 +313,10 @@ def advance_by_months(dt, jump_in_months): years = month / 12 month = month % 12 - #no remainder means that we are landing in december. - #modulo is, in a way, a zero indexed circular array. - #this is a way of converting to 1 indexed months. (in our modulo index, december is zeroth) + # no remainder means that we are landing in december. + # modulo is, in a way, a zero indexed circular array. + # this is a way of converting to 1 indexed months. + # (in our modulo index, december is zeroth) if(month == 0): month = 12 years = years - 1 @@ -245,7 +336,12 @@ class TradingEnvironment(object): self.trading_day_map[bm.date] = bm def normalize_date(self, test_date): - return datetime.datetime(year=test_date.year, month=test_date.month, day=test_date.day, tzinfo=pytz.utc) + return datetime.datetime( + year=test_date.year, + month=test_date.month, + day=test_date.day, + tzinfo=pytz.utc + ) def is_trading_day(self, test_date): dt = self.normalize_date(test_date) diff --git a/zipline/finance/trading.py b/zipline/finance/trading.py index 685be0c3..9f0abab5 100644 --- a/zipline/finance/trading.py +++ b/zipline/finance/trading.py @@ -15,7 +15,7 @@ class TradeSimulationClient(qmsg.Component): qmsg.Component.__init__(self) self.received_count = 0 self.prev_dt = None - self.event_queue = [] + self.event_queue = None self.event_callbacks = [] self.txn_count = 0 self.current_dt = simulation_dt @@ -60,12 +60,10 @@ class TradeSimulationClient(qmsg.Component): if event.source_id != zp.FINANCE_COMPONENT.ORDER_SOURCE: #mark the start time for client's processing of this event. event_start = datetime.datetime.utcnow() - + self.queue_event(event) for cb in self.event_callbacks: - if(event.dt < self.current_dt): - self.queue_event(event) - else: - cb(self.event_frame) + if(event.dt >= self.current_dt): + cb(self.get_frame()) #update time based on receipt of the order self.last_iteration_duration = datetime.datetime.utcnow() - event_start @@ -90,10 +88,16 @@ class TradeSimulationClient(qmsg.Component): def signal_order_done(self): self.order_socket.send(str(zp.ORDER_PROTOCOL.DONE)) - def frame_event(self, event): - if self.event_frame == None: - self.event_frame = pandas.DataFrame() - self.event_frame.append(event) + def queue_event(self, event): + if self.event_queue == None: + self.event_queue = {} + self.event_queue[event.dt] = event.as_series() + + def get_frame(self): + sorted_dates = sorted(self.event_queue.keys()) + frame = pandas.DataFrame(self.event_queue, index=sorted_dates) + self.event_queue = None + return frame class OrderDataSource(qmsg.DataSource): """DataSource that relays orders from the client""" diff --git a/zipline/protocol.py b/zipline/protocol.py index 3088b3bd..fba7018a 100644 --- a/zipline/protocol.py +++ b/zipline/protocol.py @@ -208,7 +208,8 @@ class namedict(object): return self.__dict__.has_key(name) def as_series(self): - s = pandas.Series(self.values(), self.keys()) + s = pandas.Series(self.__dict__.values(), self.__dict__.keys()) + return s # ================ # Control Protocol diff --git a/zipline/test/factory.py b/zipline/test/factory.py index 48b01bb2..223a665b 100644 --- a/zipline/test/factory.py +++ b/zipline/test/factory.py @@ -19,7 +19,7 @@ def load_market_data(): tzinfo=pytz.utc ) - daily_return = risk.daily_return(date=event_dt, returns=returns) + daily_return = risk.DailyReturn(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") @@ -93,7 +93,7 @@ def create_returns(daycount, start, trading_calendar): one_day = datetime.timedelta(days = 1) while i < daycount: i += 1 - r = risk.daily_return(current, random.random()) + r = risk.DailyReturn(current, random.random()) test_range.append(r) current = current + one_day return [ x for x in test_range if(trading_calendar.is_trading_day(x.date)) ] @@ -109,7 +109,7 @@ def create_returns_from_range(start, end, trading_calendar): current = current + one_day if(not trading_calendar.is_trading_day(current)): continue - r = risk.daily_return(current, random.random()) + r = risk.DailyReturn(current, random.random()) i += 1 test_range.append(r) @@ -122,7 +122,7 @@ def create_returns_from_list(returns, start, trading_calendar): i = 0 while len(test_range) < len(returns): if(trading_calendar.is_trading_day(current)): - r = risk.daily_return(current, returns[i]) + r = risk.DailyReturn(current, returns[i]) i += 1 test_range.append(r) current = current + one_day