DateTime Management
Up until release 1.5.0, backtrader used a direct approach to time management in that whatever datetime was calculated by data sources was simply used at face value.
And the same for any user input like in the case of the parameter
fromdate
(or sessionstart
) which can be given to any data source
The approach was fine given the direct control over frozen data sources for backtesting. It was easy to assume that the input datetimes had already been taken care of before they entered the system.
But with 1.5.0, live data sources are supported and this forces to take into account datetime management. Such management would not be needed if the following were always true:
-
A trader in New York trades the ES-Mini. The time zone for both in
US/Eastern
(or one of the aliases) -
A trader in Berlin trades the DAX future. In this case for both the
CET
(orEurope/Berling
) timezone applies
The direct input-output datetime approach from above would work, becase the trader, in Berlin for example, could always do something like this:
class Strategy(bt.Strategy): def next(self): # The DAX future opens at 08:00 CET if self.data.datetime.time() < datetime.time(8, 30): # don't operate until the market has been running 30 minutes return #
The problem with the direct approach surfaces when the same trader in Berlin
decides to trade the ES-Mini
. Because the change to from DST happens at
different point in time in the year and this causes the time difference to be
out of sync a couple of weeks during the year. The following wouldn’t always
work:
class Strategy(bt.Strategy): def next(self): # The SPX opens at 09:30 US/Eastern all year long # This is most of the year 15:30 CET # But it is sometimes 16:30 CET or 14:30 CET if a DST switch on-off # has happened in the USA and not in Europe # That's why the code below is unreliable if self.data.datetime.time() < datetime.time(16, 0): # don't operate until the market has been running 30 minutes return #
Operation with timezones
To solve the aforementioned situations and still remain compatible with the
direct input-output time approach, backtrader
offers the end user the
following
Datetime Input
-
As a default the platform will not touch the datetime provided by a data source
-
The end-user can override this input by:
-
Providing a
tzinput
parameter to the data source. This must be an object compatible with thedatetime.tzinfo
interface. Most likely the user will provide apytz.timezone
instance
With this decision the time used internally by
backtrader
is considered to be inUTC-like
format, ie:-
If the data source has already stored it in
UTC
format -
After a conversion through
tzinput
-
It’s not really
UTC
but it’s the reference for the user, henceUTC-like
-
Datetime output
-
If the data feed can automatically determine the timezone for the output, this will be the default
This makes sense in the case of live-feeds and especially in use cases like the one in which a trader in Berlin (
CET
timezone), trades products withUS/Eastern
timezone.Because the trader gets always the right time and in the example above the opening time remains constant at
09:30 US/Eastern
, rather than15:30 CET
most of the year, but sometimes16:30 CET
and sometimes14:30 CET
. -
If it cannot be determined, then the output will be whatever was determined during input (the
UTC-like
) time -
The end user can override and determine the actual timezone for the output
- Providing a
tz
parameter to the data source. This must be an object compatible with thedatetime.tzinfo
interface. Most likely the user will provide apytz.timezone
instance
- Providing a
Note
Input fromt the user like for example the parameters fromdate
or
sessionstart
are expected to be in sync with the actual tz
, be it
automatically calculated by the data source, supplied by the user or left
as default (None
, which means direct input-output of datetime)
With all that in mind let’s recall the Berlin trader, trading in
US/Eastern
:
import pytz import bt data = bt.feeds.MyFeed('ES-Mini', tz=pytz.timezone('US/Eastern')) class Strategy(bt.Strategy): def next(self): # This will work all year round. # The data source will return in the frame of the 'US/Eastern' time # zone and the user is quoting '10:00' as reference time # Because in the 'US/Eastern' timezone the SPX index always starts # trading at 09:30, this will always work if self.data.datetime.time() < datetime.time(10, 0): # don't operate until the market has been running 30 minutes return #
In the case of a data source which can automatically determine the output timezone:
import bt data = bt.feeds.MyFeedAutoTZ('ES-Mini') class Strategy(bt.Strategy): def next(self): # This will work all year round. # The data source will return in the frame of the 'US/Eastern' time # zone and the user is quoting '10:00' as reference time # Because in the 'US/Eastern' timezone the SPX index always starts # trading at 09:30, this will always work if self.data.datetime.time() < datetime.time(10, 0): # don't operate until the market has been running 30 minutes return #
Even less work than above.
Obviously MyFeed
and MyFeedAuto
in the example above are just dummy
names.
Note
At the time of writing the only data source included in the distribution which can automatically determine the timezone is the one connecting to Interactive Brokers