From 1d9953255a21783ce96ae7006ce0b204e05b980b Mon Sep 17 00:00:00 2001 From: fawce Date: Fri, 20 Apr 2012 23:41:23 -0400 Subject: [PATCH 1/2] added stop out for max drawdown. --- zipline/finance/performance.py | 20 ++++++++++++++++---- zipline/finance/trading.py | 4 ++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/zipline/finance/performance.py b/zipline/finance/performance.py index 351466a8..e90d4e0d 100644 --- a/zipline/finance/performance.py +++ b/zipline/finance/performance.py @@ -154,6 +154,7 @@ class PerformanceTracker(): self.result_stream = None self.last_dict = None self.order_log = [] + self.exceeded_max_loss = False # this performance period will span the entire simulation. self.cumulative_performance = PerformancePeriod( @@ -267,7 +268,17 @@ class PerformanceTracker(): if self.result_stream: msg = zp.PERF_FRAME(self.to_dict()) self.result_stream.send(msg) - + + # check the day's returns versus the max drawdown + max_dd = -1 * self.trading_environment.max_drawdown + if self.todays_performance.returns < max_dd: + qutil.LOGGER.info("Exceeded max drawdown.") + # TODO: any other information we need to relay on the + # result socket? + self.exceeded_max_loss = True + self.handle_simulation_end(skip_close=True) + return + #move the market day markers forward self.market_open = self.market_open + self.calendar_day @@ -288,7 +299,7 @@ class PerformanceTracker(): keep_transactions = True ) - def handle_simulation_end(self): + def handle_simulation_end(self, skip_close=False): """ When the simulation is complete, run the full period risk report and send it out on the result_stream. @@ -300,8 +311,9 @@ class PerformanceTracker(): # the stream will end on the last trading day, but will not trigger # an end of day, so we trigger the final market close here. - self.handle_market_close() - + # In the case of errors, we needn't close again. + if not skip_close: + self.handle_market_close() self.risk_report = risk.RiskReport( self.returns, diff --git a/zipline/finance/trading.py b/zipline/finance/trading.py index 9c815156..b503e501 100644 --- a/zipline/finance/trading.py +++ b/zipline/finance/trading.py @@ -99,6 +99,10 @@ class TradeSimulationClient(qmsg.Component): def process_event(self, event): + if self.perf.exceeded_max_loss: + self.control_out.send(str(zp.CONTROL_PROTOCOL.SHUTDOWN)) + return + # generate transactions, if applicable txn = self.txn_sim.apply_trade_to_open_orders(event) if txn: From 54d3579ceb20b562527a7911d952e2c8e83e0976 Mon Sep 17 00:00:00 2001 From: fawce Date: Mon, 23 Apr 2012 15:10:44 -0400 Subject: [PATCH 2/2] added boolean to results for exceeding max losses in a single simulated day. --- zipline/finance/performance.py | 47 ++++++++++++++++++++++++---------- zipline/finance/risk.py | 16 ++++++++---- zipline/protocol.py | 1 + 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/zipline/finance/performance.py b/zipline/finance/performance.py index e90d4e0d..63129b31 100644 --- a/zipline/finance/performance.py +++ b/zipline/finance/performance.py @@ -40,9 +40,12 @@ Performance Tracking | | through all the events delivered to this tracker. | | | For details look at the comments for | | | :py:meth:`zipline.finance.risk.RiskMetrics.to_dict`| + +-----------------+----------------------------------------------------+ + | exceeded_max_ | True if the simulation was stopped because single | + | loss | day losses exceeded the max_drawdown stipulated in | + | | trading_environment. | +-----------------+----------------------------------------------------+ - Position Tracking ================= @@ -214,7 +217,7 @@ class PerformanceTracker(): 'capital_base' : self.capital_base, 'cumulative_perf' : self.cumulative_performance.to_dict(), 'daily_perf' : self.todays_performance.to_dict(), - 'cumulative_risk_metrics' : self.cumulative_risk_metrics.to_dict(), + 'cumulative_risk_metrics' : self.cumulative_risk_metrics.to_dict() } def log_order(self, order): @@ -264,21 +267,37 @@ class PerformanceTracker(): # calculate progress of test self.progress = self.day_count / self.total_days + if self.trading_environment.max_drawdown: + max_dd = -1 * self.trading_environment.max_drawdown + if self.todays_performance.returns < max_dd: + qutil.LOGGER.info("Exceeded max drawdown.") + # mark the perf period with max loss flag, + # so it shows up in the update, but don't end the test + # here. Let the update go out before stopping + self.exceeded_max_loss = True + # Output results if self.result_stream: msg = zp.PERF_FRAME(self.to_dict()) self.result_stream.send(msg) - # check the day's returns versus the max drawdown - max_dd = -1 * self.trading_environment.max_drawdown - if self.todays_performance.returns < max_dd: - qutil.LOGGER.info("Exceeded max drawdown.") - # TODO: any other information we need to relay on the - # result socket? - self.exceeded_max_loss = True + if self.exceeded_max_loss: + # now that we've sent the day's update, kill this test self.handle_simulation_end(skip_close=True) return + # check the day's returns versus the max drawdown + # max_drawdown is optional: + if self.trading_environment.max_drawdown: + max_dd = -1 * self.trading_environment.max_drawdown + if self.todays_performance.returns < max_dd: + qutil.LOGGER.info("Exceeded max drawdown.") + # TODO: any other information we need to relay on the + # result socket? + self.exceeded_max_loss = True + self.handle_simulation_end(skip_close=True) + return + #move the market day markers forward self.market_open = self.market_open + self.calendar_day @@ -317,13 +336,15 @@ class PerformanceTracker(): self.risk_report = risk.RiskReport( self.returns, - self.trading_environment + self.trading_environment, + exceeded_max_loss = self.exceeded_max_loss ) - + if self.result_stream: qutil.LOGGER.info("about to stream the risk report...") - report = self.risk_report.to_dict() - msg = zp.RISK_FRAME(report) + risk_dict = self.risk_report.to_dict() + + msg = zp.RISK_FRAME(risk_dict) self.result_stream.send(msg) # this signals that the simulation is complete. self.result_stream.send("DONE") diff --git a/zipline/finance/risk.py b/zipline/finance/risk.py index 98f7b7e7..248e75a7 100644 --- a/zipline/finance/risk.py +++ b/zipline/finance/risk.py @@ -315,7 +315,11 @@ class RiskMetrics(): class RiskReport(): - def __init__(self, algorithm_returns, trading_environment): + def __init__( + self, + algorithm_returns, + trading_environment, + exceeded_max_loss=False): """ algorithm_returns needs to be a list of daily_return objects sorted in date ascending order @@ -323,6 +327,7 @@ class RiskReport(): self.algorithm_returns = algorithm_returns self.trading_environment = trading_environment + self.exceeded_max_loss = exceeded_max_loss if len(self.algorithm_returns) == 0: start_date = self.trading_environment.period_start @@ -352,10 +357,11 @@ class RiskReport(): provided for each period. """ return { - 'one_month' : [x.to_dict() for x in self.month_periods], - 'three_month' : [x.to_dict() for x in self.three_month_periods], - 'six_month' : [x.to_dict() for x in self.six_month_periods], - 'twelve_month' : [x.to_dict() for x in self.year_periods] + 'one_month' : [x.to_dict() for x in self.month_periods], + 'three_month' : [x.to_dict() for x in self.three_month_periods], + 'six_month' : [x.to_dict() for x in self.six_month_periods], + 'twelve_month' : [x.to_dict() for x in self.year_periods], + 'exceeded_max_loss' : self.exceeded_max_loss } def periodsInRange(self, months_per, start, end): diff --git a/zipline/protocol.py b/zipline/protocol.py index 0736d79c..c3291556 100644 --- a/zipline/protocol.py +++ b/zipline/protocol.py @@ -663,6 +663,7 @@ def convert_transactions(transactions): for txn in transactions: txn['date'] = EPOCH(txn['dt']) del(txn['dt']) + del(txn['source_id']) results.append(txn) return results