title: LineIterator API description: Complete LineIterator class API reference - the foundation for Indicators, Strategies, and Observers
LineIterator API¶
The LineIterator class is the foundational base class for all objects that iterate over time-series data in Backtrader. It provides the core infrastructure for Indicator, Strategy, and Observer classes, managing execution phases, data flow, minimum period calculations, and child object registration.
Class Definition¶
class backtrader.LineIterator(LineSeries):
"""Base class for all time-series iterating objects."""
```bash
## Line Type Constants
The `_ltype` attribute identifies the type of LineIterator object:
| Constant | Value | Type |
|----------|-------|------|
| `LineIterator.IndType` | 0 | Indicator |
| `LineIterator.StratType` | 1 | Strategy |
| `LineIterator.ObsType` | 2 | Observer |
```python
# Check type at runtime
if obj._ltype == LineIterator.IndType:
print("This is an indicator")
```bash
## Core Attributes
### `_ltype`
Line type identifier (IndType=0, StratType=1, ObsType=2).
```python
# Defined in base classes
class IndicatorBase(LineIterator):
_ltype = LineIterator.IndType # = 0
class StrategyBase(LineIterator):
_ltype = LineIterator.StratType # = 1
class ObserverBase(LineIterator):
_ltype = LineIterator.ObsType # = 2
```bash
### `_lineiterators`
Dictionary mapping line types to lists of registered child objects.
```python
# Structure: {_ltype: [list of objects]}
self._lineiterators = {
LineIterator.IndType: [], # Registered indicators
LineIterator.ObsType: [], # Registered observers
LineIterator.StratType: [], # Registered strategies
}
```bash
### `_minperiod`
Minimum number of bars required before the object produces valid output.
```python
# Set minimum period
self._minperiod = 20
# Or use helper method
self.addminperiod(20)
# Get minimum period
period = self._minperiod
```bash
### `_nextforce`
Force cerebro to run in `next()` mode instead of `runonce()` mode.
```python
class MyIndicator(bt.Indicator):
_nextforce = True # Force next() mode for this indicator
```bash
### `_mindatas`
Minimum number of data feeds required (default: 1).
```python
class SpreadIndicator(bt.Indicator):
_mindatas = 2 # Requires 2 data feeds
```bash
### `plotinfo` / `plotlines`
Plotting configuration objects.
```python
class MyIndicator(bt.Indicator):
plotinfo = dict(
subplot=True,
plotname='My Indicator',
)
plotlines = dict(
value=dict(color='blue', linewidth=2),
)
```bash
## Execution Phases
LineIterator implements a three-phase execution model:
```mermaid
graph LR
A[Data Feed] --> B[prenext Phase]
B -->|minperiod not reached| B
B -->|minperiod reached| C[nextstart Phase]
C -->|called once| D[next Phase]
D -->|for each bar| D
D -->|no more data| E[stop]
```bash
### Phase Flow Diagram
```mermaid
sequenceDiagram
participant C as Cerebro
participant L as LineIterator
participant I as Indicators
C->>L: Call _next()
L->>L: _clk_update() - Update clock
L->>I: Call _next() for each indicator
alt clock_len <= _minperiod
L->>L: Call prenext()
else clock_len == _minperiod
L->>L: Call nextstart()
else clock_len > _minperiod
L->>L: Call next()
end
L->>L: Call _notify()
```bash
### `prenext(self)`
Called for each bar before the minimum period is reached. Use for warmup calculations and data collection.
```python
def prenext(self):
"""Called during warmup period."""
# Accumulate data for later calculation
if not hasattr(self, '_warmup_data'):
self._warmup_data = []
self._warmup_data.append(self.data[0])
```bash
### `nextstart(self)`
Called once when the minimum period is first reached. Use for initialization after warmup.
```python
def nextstart(self):
"""Called once when minperiod is satisfied."""
# Initialize with first valid value
self.lines.value[0] = sum(self._warmup_data) / len(self._warmup_data)
# Clean up warmup data
delattr(self, '_warmup_data')
```bash
Default implementation calls `next()`.
### `next(self)`
Called for each bar after the minimum period is reached. Contains main calculation logic.
```python
def next(self):
"""Main calculation logic."""
self.lines.value[0] = self.calculate()
```bash
### `stop(self)`
Called when backtesting stops.
```python
def stop(self):
"""Cleanup and final reporting."""
print(f'Final value: {self.lines.value[0]}')
```bash
## Indicator Registration
### `getindicators(self)`
Get all indicators registered with this lineiterator.
```python
indicators = self.getindicators()
for ind in indicators:
print(f'{ind.__class__.__name__}: {ind[0]}')
```bash
### `getobservers(self)`
Get all observers registered with this lineiterator.
```python
observers = self.getobservers()
```bash
### `_register_indicator(self, indicator)`
Register an indicator with this lineiterator (automatic in most cases).
```python
# Usually called automatically
self._lineiterators[LineIterator.IndType].append(indicator)
```bash
## Owner Management and donew() Pattern
The `donew()` pattern replaces the original metaclass-based initialization:
```python
@classmethod
def donew(cls, *args, **kwargs):
"""Process data arguments before instance creation.
1. Extract data feeds from args
2. Set up data aliases (data0, data1, etc.)
3. Configure clock reference
4. Calculate minimum period from data sources
5. Return (instance, remaining_args, kwargs)
"""
_obj = super().donew(*args, **kwargs)[0]
# Extract data feeds
_obj.datas = [arg for arg in args if hasattr(arg, 'lines')]
# Set up data aliases
for i, data in enumerate(_obj.datas):
setattr(_obj, f'data{i}', data)
# Set clock
_obj._clock = _obj.datas[0] if _obj.datas else None
return _obj, args, kwargs
```bash
### `dopreinit(cls, _obj, *args, **kwargs)`
Handle pre-initialization setup before `__init__()`.
```python
@classmethod
def dopreinit(cls, _obj, *args, **kwargs):
"""Setup before __init__: datas, clock, minperiod."""
# Ensure datas is set up
if not _obj.datas:
_obj.datas = [_obj._owner] if _obj._owner else []
# Set clock from first data
if _obj.datas:
_obj._clock = _obj.datas[0]
# Calculate minperiod from datas
if _obj.datas:
data_minperiods = [getattr(d, '_minperiod', 1) for d in _obj.datas]
_obj._minperiod = max(data_minperiods + [_obj._minperiod])
return _obj, args, kwargs
```bash
### `dopostinit(cls, _obj, *args, **kwargs)`
Handle post-initialization setup after `__init__()`.
```python
@classmethod
def dopostinit(cls, _obj, *args, **kwargs):
"""Final setup after __init__: minperiod, registration."""
# Recalculate minperiod from lines
line_minperiods = [getattr(x, '_minperiod', 1) for x in _obj.lines]
if line_minperiods:
_obj._minperiod = max(_obj._minperiod, max(line_minperiods))
# Register with owner
if _obj._owner:
_obj._owner.addindicator(_obj)
return _obj, args, kwargs
```bash
## Data Flow in LineIterator
### Clock Management
The clock synchronizes data processing across multiple data feeds:
```python
# Internal clock update
def _clk_update(self):
"""Update clock and return current data length."""
if self._clock:
return len(self._clock)
return 0
```bash
### Data Access Patterns
```python
# Access data by index
current = self.data[0] # Current bar
previous = self.data[-1] # Previous bar
ago_5 = self.data[-5] # 5 bars ago
# Access by name (for multi-line data)
close = self.data.close[0]
high = self.data.high[0]
volume = self.data.volume[0]
# Access multiple data feeds
data0_close = self.data0.close[0]
data1_close = self.data1.close[0]
```bash
### Forward Method
Advance the internal position:
```python
def forward(self, value=1):
"""Advance the internal position by value steps."""
self.lines.advance(value)
```bash
## Minimum Period and Warmup Handling
### Minimum Period Calculation
The minimum period is calculated as the maximum of:
1. All data source minimum periods
2. All sub-indicator minimum periods
3. Value set by `addminperiod()` calls
```python
# The effective minimum period
effective_minperiod = max(
data._minperiod for data in self.datas
)
effective_minperiod = max(effective_minperiod, self._minperiod)
```bash
### `addminperiod(self, period)`
Add to the minimum period required.
```python
def __init__(self):
super().__init__()
self.addminperiod(20) # Requires 20 bars warmup
```bash
### `updateminperiod(self, period)`
Update minimum period if the new value is greater.
```python
# Updates to max(current, new)
self.updateminperiod(30) # Sets to max(current, 30)
```bash
### `setminperiod(self, period)`
Directly set the minimum period (use with caution).
```python
# Override calculated minperiod
self.setminperiod(10)
```bash
### `_periodrecalc(self)`
Recalculate minimum period based on child indicators.
```python
def _periodrecalc(self):
"""Recalculate minperiod from child indicators."""
indicators = self._lineiterators[LineIterator.IndType]
indperiods = [ind._minperiod for ind in indicators]
self.updateminperiod(max(indperiods or [self._minperiod]))
```bash
## once() Mode Optimization
### next() vs once() Mode Comparison
| Aspect | next() Mode | once() Mode |
|--------|-------------|-------------|
| **Execution**| Bar-by-bar | Batch processing |
|**Complexity**| Simpler | More complex |
|**Performance**| Slower | Faster (2-10x) |
|**Debugging**| Easier | Harder |
|**Default** | Yes (with `_nextforce`) | No |
### `preonce(self, start, end)`
Called during minimum period phase in runonce mode.
```python
def preonce(self, start, end):
"""Batch processing during warmup in runonce mode."""
for i in range(start, end):
# Accumulate warmup data
pass
```bash
### `oncestart(self, start, end)`
Called once when minimum period is reached in runonce mode.
```python
def oncestart(self, start, end):
"""Transition from preonce to once."""
self.once(start, end)
```bash
### `once(self, start, end)`
Batch calculation mode for all bars at once.
```python
def once(self, start, end):
"""Vectorized calculation for all bars."""
src = self.data.array
dst = self.lines[0].array
for i in range(start, end):
dst[i] = self._calculate_at(i)
```bash
### Performance Considerations
For best performance in `once()` mode:
```python
def once(self, start, end):
"""Optimized batch calculation."""
# Access arrays directly
src = self.data.array
dst = self.lines[0].array
# Use local variables for speed
period = self.p.period
# Batch calculation
for i in range(start, end):
# Calculate using array access
dst[i] = sum(src[i-period:i]) / period
```bash
## Internal Methods
### `_next(self)`
Internal next method called for each bar.
```python
def _next(self):
"""Update indicators and call phase methods."""
# Update clock
clock_len = self._clk_update()
# Update child indicators
for indicator in self._lineiterators[LineIterator.IndType]:
indicator._next()
# Call notification
self._notify()
# Call phase method
if clock_len > self._minperiod:
self.next()
elif clock_len == self._minperiod:
self.nextstart()
elif clock_len:
self.prenext()
```bash
### `_notify(self)`
Process pending notifications.
```python
def _notify(self):
"""Process notifications (empty by default)."""
pass
```bash
### `_stage1(self)`
Stage 1 initialization for line operators.
```python
def _stage1(self):
"""Reset line operators."""
self._opstage = 1
for data in self.datas:
data._stage1()
```bash
### `_stage2(self)`
Stage 2 initialization for line operators.
```python
def _stage2(self):
"""Set up line operators."""
self._opstage = 2
for data in self.datas:
data._stage2()
```bash
## Memory Management
### `qbuffer(self, savemem=0)`
Enable memory saving mode for lines.
```python
# Save memory for all lines
self.qbuffer(savemem=1)
# savemem values:
# 0 - No memory saving
# 1 - Save memory for all lines and indicators
# -1 - Don't save for indicators at strategy level
# -2 - Also don't save for indicators with plot=False
```bash
## Plotting Support
### `_plotinit(self)`
Initialize plotting configuration.
```python
def _plotinit(self):
"""Initialize plotinfo defaults."""
# Set up plotinfo if not present
if not hasattr(self, 'plotinfo'):
self.plotinfo = PlotInfoObj()
return True
```bash
## Base Classes
### `IndicatorBase`
Base class for all indicators.
```python
class IndicatorBase(DataAccessor):
"""Base class for indicators."""
_ltype = LineIterator.IndType # = 0
```bash
### `ObserverBase`
Base class for all observers.
```python
class ObserverBase(DataAccessor):
"""Base class for observers."""
_ltype = LineIterator.ObsType # = 2
_mindatas = 0 # Observers don't consume data arguments
```bash
### `StrategyBase`
Base class for all strategies.
```python
class StrategyBase(DataAccessor):
"""Base class for strategies."""
_ltype = LineIterator.StratType # = 1
def once(self, start, end):
"""Strategies override once to do nothing."""
pass
```bash
## Complete Example: Custom LineIterator
```python
import backtrader as bt
class CustomOscillator(bt.Indicator):
"""Custom oscillator demonstrating LineIterator usage."""
lines = ('oscillator', 'signal')
params = (
('fast_period', 12),
('slow_period', 26),
('signal_period', 9),
)
def __init__(self):
super().__init__()
# Set minimum period
self.addminperiod(self.p.slow_period + self.p.signal_period)
# Create sub-indicators
self.fast_ma = bt.indicators.SMA(self.data, period=self.p.fast_period)
self.slow_ma = bt.indicators.SMA(self.data, period=self.p.slow_period)
# Initialize signal line calculation
self.signal_line = bt.indicators.SMA(self.lines.oscillator,
period=self.p.signal_period)
def next(self):
"""Calculate oscillator value for current bar."""
# Oscillator = fast_ma - slow_ma
self.lines.oscillator[0] = self.fast_ma[0] - self.slow_ma[0]
def prenext(self):
"""Track values during warmup."""
if not hasattr(self, '_warmup_count'):
self._warmup_count = 0
self._warmup_count += 1
def nextstart(self):
"""Initialize after warmup."""
print(f'Warmup complete after {self._warmup_count} bars')
# Call regular next()
self.next()
```bash
## Implementation Examples
### Example 1: Simple Moving Average with once() Optimization
```python
class FastSMA(bt.Indicator):
lines = ('sma',)
params = (('period', 20),)
def __init__(self):
super().__init__()
self.addminperiod(self.p.period)
def next(self):
"""Bar-by-bar calculation."""
self.lines.sma[0] = sum(self.data[-i] for i in range(self.p.period)) / self.p.period
def once(self, start, end):
"""Vectorized batch calculation (2-5x faster)."""
src = self.data.array
dst = self.lines.sma.array
period = self.p.period
for i in range(start, end):
dst[i] = sum(src[i-period:i]) / period
```bash
### Example 2: Multi-Data Indicator
```python
class SpreadIndicator(bt.Indicator):
lines = ('spread', 'zscore')
_mindatas = 2 # Requires 2 data feeds
params = (
('period', 20),
('stddev_period', 20),
)
def __init__(self):
super().__init__()
self.addminperiod(self.p.stddev_period)
# Calculate spread statistics
self.spread_mean = bt.indicators.SMA(
self.data0 - self.data1,
period=self.p.period
)
self.spread_std = bt.indicators.StandardDeviation(
self.data0 - self.data1,
period=self.p.stddev_period
)
def next(self):
spread = self.data0[0] - self.data1[0]
mean = self.spread_mean[0]
std = self.spread_std[0]
self.lines.spread[0] = spread
if std != 0:
self.lines.zscore[0] = (spread - mean) / std
```bash
### Example 3: Stateful Indicator (EMA)
```python
class CustomEMA(bt.Indicator):
lines = ('ema',)
params = (('period', 20),)
def __init__(self):
super().__init__()
self.addminperiod(self.p.period)
# Pre-calculate alpha
self.alpha = 2.0 / (self.p.period + 1)
self.alpha1 = 1.0 - self.alpha
def nextstart(self):
"""Seed EMA with SMA."""
sma = sum(self.data[-i] for i in range(self.p.period)) / self.p.period
self.lines.ema[0] = sma
def next(self):
"""EMA formula: EMA = prior_ema *alpha1 + price*alpha"""
self.lines.ema[0] = (
self.lines.ema[-1]*self.alpha1 +
self.data[0]*self.alpha
)
def once(self, start, end):
"""Optimized batch EMA calculation."""
src = self.data.array
dst = self.lines.ema.array
# Seed with SMA
if start < self.p.period:
sma_start = max(0, start)
for i in range(sma_start, min(self.p.period, end)):
dst[i] = sum(src[max(0, i-self.p.period+1):i+1]) / (i+1)
start = max(start, self.p.period)
# Calculate EMA for remaining bars
for i in range(start, end):
dst[i] = dst[i-1]*self.alpha1 + src[i]* self.alpha
```bash
## Common Pitfalls
1. **Forget to call `super().__init__()`**: Always call parent `__init__` first.
2. **Incorrect minperiod**: Use `addminperiod()` to specify warmup requirements.
3. **Mixing next() and once()**: If implementing `once()`, ensure logic matches `next()`.
4. **Array access in next()**: Use relative indexing (`data[0]`, `data[-1]`), not absolute indices.
5. **State initialization**: Use `nextstart()` for one-time initialization after warmup.
6. **Forgetting _ltype**: When subclassing LineIterator directly, set `_ltype` appropriately.
7. **Indicator registration**: Indicators created outside `__init__` may not register properly.
## Next Steps
- [Indicator API](indicator.md) - Creating custom indicators
- [Strategy API](strategy.md) - Building trading strategies
- [Observer API](observer.md) - Chart observers and visualization
- [Data Feeds API](data-feeds.md) - Data source configuration