Skip to main content
On this page

Backtrader Multiple Timeframes: Multi-Timeframe Trading in Python

· 7 min read
Pineify Team
Pine Script and AI trading workflow research team

Backtrader can combine a lower-timeframe execution feed with a higher-timeframe filter. The engineering problem is knowing which bar is available when, allowing indicators to warm up, and managing pending orders. Adding a weekly filter does not by itself demonstrate fewer false signals or better returns.

Backtrader Multiple Timeframes Multi-Timeframe Trading Strategy Setup

Data Order and Alignment​

Backtrader's multiple-timeframe documentation specifies adding the smallest timeframe first and supplying properly date-time-aligned feeds. For daily execution and a weekly filter, daily data is data0 and weekly data is data1.

Declare the actual timeframe when loading separate files. Both feeds must use compatible time zones, sessions, adjustments, and timestamp conventions. Synchronization cannot repair a weekly close that your input file incorrectly makes available on Monday morning.

If both series represent the same instrument and source, resampling avoids maintaining a second independently constructed file. It does not eliminate the need to inspect weekly boundaries or incomplete final periods.

Resampling: What Compression Means​

With timeframe=bt.TimeFrame.Weeks, compression=1 produces one-week bars. A compression of 5 means five-week bars, not five trading days. With a minutes timeframe, compression 60 means sixty-minute bars. This follows the resampling documentation.

The number of input rows in a weekly bar varies with holidays, missing data, and the instrument's session. Do not substitute a fixed five-row rolling aggregation for calendar-week resampling without intending that different behavior.

A Complete Daily/Weekly Example​

The following Python example demonstrates feed construction, a weekly trend filter, one decision per new daily bar, and pending-order tracking. It is an educational long-only rule, not a performance claim or a complete risk-managed trading system. It has no protective stop.

Create daily-data.csv with a header and these columns in order: date,open,high,low,close,volume. Dates must use YYYY-MM-DD, rows must be chronological, and prices must be consistently adjusted or unadjusted. Supply enough history for a ten-week average and its previous value.

import backtrader as bt


class DailyWeeklyExample(bt.Strategy):
def __init__(self):
self.daily_sma = bt.ind.SMA(self.data0.close, period=20)
self.weekly_sma = bt.ind.SMA(self.data1.close, period=10)
self.weekly_previous = self.weekly_sma(-1)
self.daily_cross = bt.ind.CrossOver(self.data0.close, self.daily_sma)
self.pending = None
self.last_daily_length = 0

def notify_order(self, order):
if order.status in (order.Submitted, order.Accepted, order.Partial):
return
if self.pending is not None and order.ref == self.pending.ref:
self.pending = None

def next(self):
if len(self.data0) == self.last_daily_length:
return
self.last_daily_length = len(self.data0)
if self.pending is not None:
return

position = self.getposition(self.data0)
weekly_rising = self.weekly_sma[0] > self.weekly_previous[0]
if not position.size and self.daily_cross[0] > 0 and weekly_rising:
self.pending = self.buy(data=self.data0, size=1)
elif position.size and self.daily_cross[0] < 0:
self.pending = self.close(data=self.data0)


cerebro = bt.Cerebro()
daily = bt.feeds.GenericCSVData(
dataname="daily-data.csv",
dtformat="%Y-%m-%d",
datetime=0,
open=1,
high=2,
low=3,
close=4,
volume=5,
openinterest=-1,
timeframe=bt.TimeFrame.Days,
compression=1,
)
cerebro.adddata(daily, name="daily")
cerebro.resampledata(
daily, timeframe=bt.TimeFrame.Weeks, compression=1, name="weekly"
)
cerebro.addstrategy(DailyWeeklyExample)
cerebro.broker.setcash(10000.0)
cerebro.broker.setcommission(commission=0.001)
cerebro.run()

The one-share size and 0.1% commission are illustrative assumptions. This example does not model spread, slippage, taxes, financing, or liquidity. Under ordinary historical broker behavior, a market order submitted after evaluating a bar is eligible for the following bar's opening price; it is not a trade at the already observed close. See the order execution documentation.

Warm-Up and Repeated Calls​

A ten-week moving average requires ten weekly observations, and comparing it with its preceding value needs additional history. That may consume many daily rows before next() begins. “Exactly 50 daily bars” is not a general guarantee: holidays, starting dates, resampling boundaries, and other indicator dependencies affect the start.

Backtrader has prenext, nextstart, and next lifecycle methods. In mixed-timeframe processing, the same slower-timeframe value can remain visible across several faster bars. The strategy reference describes these lifecycle and notification methods. Log both feed lengths and timestamps rather than inferring a new weekly observation from each call.

The example gates decisions on changes in the daily feed length. A strategy intended to act only once per new weekly observation would need a different gate. Resampling and replay also have different semantics: replay can update a developing larger bar, so code designed for completed resampled bars should not be transferred without checking availability.

Why Specifying the Order Feed Is Not a Duplicate-Order Fix​

The order reference documents that an omitted data argument defaults to the first feed. It does not submit one order to every timeframe.

Specifying data=self.data0 makes the intended instrument explicit. Preventing duplicates additionally requires tracking pending orders and defining when a signal may be acted on again. A position can remain flat between submitting an order and its execution, so testing only not self.position can permit repeated submissions. The example retains the order reference until a terminal notification.

A production strategy should log rejected, canceled, margin-failed, and expired orders rather than merely clearing the reference. Decide whether to retry, wait for a new signal, or stop trading; those policies can materially change results.

Verify Timing Before Measuring Returns​

Use a small, known input sample and print, at each decision, both feed timestamps and lengths, the weekly close and average, the signal, and the pending order reference. Confirm that:

  • No weekly close is used before the source bars forming it were available.
  • A repeated slower-timeframe value does not produce unintended repeated orders.
  • Missing days and the final partial week behave as intended.
  • Actual fill notifications match the intended order policy.

Then compare the same daily rule with and without the weekly filter, preserving costs, dates, position sizing, and data adjustments. Report excluded trades as well as retained trades. A filter can avoid losses, miss winners, or change exposure; none of those outcomes follows automatically from using more timeframes.

Runtime and Live-Trading Limits​

Remove unused indicators and profile the data pipeline before changing the trading rule to make it faster. Shortening a lookback or changing compression changes the strategy's meaning. Record those changes as new experiments.

Live support depends on the specific data feed and broker integration. Verify current API compatibility, backfill behavior, time zones, connection recovery, and order-status handling in that integration. A successful historical run does not verify a live deployment.

Do I use compression 5 for daily-to-weekly resampling?

No. Use the Weeks timeframe with compression 1 for one-week bars. Compression counts units of the target timeframe.

Does data=self.data0 prevent all duplicate orders?

No. It selects the order's feed. Track pending orders and gate repeated decisions separately.

Does a higher-timeframe filter improve profitability?

That is a hypothesis to test with matched rules, realistic costs, and an evaluation period separate from tuning. This example reports no market performance result.

Pineify can help develop Pine Script chart logic, but Backtrader is a separate Python execution environment. Porting a rule requires checking indicator definitions, bar availability, and broker-emulator differences; matching the strategy's name or parameters is insufficient.

Pineify Website