Back to Community
how to detect the last bar of a backtest? (for generating a summary report)

I would like to be able to programatically detect the last bar / day of a backtest so I can generate a summary report (via the log api) on that day

is there any way I can detect this?

6 responses

Hi Jason,

I gather that you would prefer not to enter the ending bar datetime stamp manually? This would work but is awkward.

I've attached code that sorta solves the problem. The assumption here is that end_date = context.spy.security_end_date will provide the most recent trading date for SPY, advanced by one trading day. Then, if your backtest ends on a typical Mon. through Thurs. (market open and no early close), you can detect the last call to handle_data. I think that the more general case can be coded, as well, but I have to dig into https://github.com/quantopian/zipline/blob/master/zipline/utils/tradingcalendar.py.

Quantopian folks, it would be handy if security_end_date actually returned the date for the last trade data available to the backtest algorithm. Why is it advanced by one trading day? Is this for compatibility with live trading?

Grant

from datetime import timedelta

def initialize(context):  
    context.spy = sid(8554)  
    end_date = context.spy.security_end_date  
    context.last_date = end_date - timedelta(days=1)  
    print context.last_date

def handle_data(context, data):  
    if get_datetime() == context.last_date:  
        print 'last call to handle_data'  
Clone Algorithm
8
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Backtest ID: 5412c05ff49c8707e6b01c65
There was a runtime error.

Thanks for the suggestion Grant! I will give this a try. Just looking at the code though, it seems a bit strange that it would work? I though security_end_date was for the current bar (if the security isn't delisted ) so really seems strange that should be detectable as the end date....

also, I am assuming this only works in daily mode? I do all my work in minute mode so I will give this a try to verify, then dig through tradigncalendar.py and try to figure out a solution as you suggest.

Grant,

Firstly, I am a big fan - thank you so much for your contributions to this forum.

However, I can't seem to get your code to do what I think is intended. Context.last_date always ends up being one day before the security_end_date without regard to the backtester dates. So it will only print 'last call to handle_data' if the backtester end date is set to the most recent (ie today's) date. I thought the timedelta function would somehow magically calculate the delta of the security_end_date and the backtester end-date so then context.last_date would be the last backtesting date. Am i misunderstanding the point of the thing?

Currently, I manually set a date to run final performance metrics. In practice this means I do every backtest twice - the first time just to realize that i forgot to set the final date in the code. Any help would be great appreciated!

Hello Robby,

The assumption here is that you'll be running the backtest up to the most recent date for which data are available. As you can see from the attached backtest, sometimes the value of 'days' in timedelta(days=3) will need to be adjusted upward, when the security_end_date is advanced by more than one calendar day (e.g. the backtest ends on a Friday).

This is not ideal, and buried within the backtester is the datetime stamp of the last bar (daily/minute). If this could be accessed, then testing for the last call to handle_data would be straightforward.

The code here should work, so long as you tweak the 'days' value. For minute bars, it'd have to be modified to include the last minute bar datetime stamp. If you need this, just let me know.

Grant

from datetime import timedelta

def initialize(context):  
    context.spy = sid(8554)  
    end_date = context.spy.security_end_date  
    context.last_date = end_date - timedelta(days=3) # adjust value of days  
    print 'context.last_date = ' + str(context.last_date)

def handle_data(context, data):  
    print get_datetime()  
    if get_datetime() == context.last_date:  
        print 'last call to handle_data'  
Clone Algorithm
8
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Backtest ID: 54397c0a38faa508ff38ac53
There was a runtime error.

Thanks to Grant's revelation of the strange (maybe just unexpected?) output from the "dir" command in another thread, I think I solved this problem for you. It uses a regex to grab the last date set in the simulation. It then looks up this date in the zipline utilities to find the closing time.

Clone Algorithm
7
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Backtest ID: 543b3b127dc51f09037f06d7
There was a runtime error.

Hey great! works like a charm. Thx.