title: Observer API description: Complete Observer class API reference
Observer API¶
The Observer class is the base class for monitoring strategy execution and collecting data during backtesting. Observers track metrics like cash, value, drawdown, trades, and other performance statistics.
Unlike indicators which generate signals, observers primarily record and visualize strategy state.
Class Definition¶
class backtrader.Observer:
"""Base class for monitoring strategy execution."""
```bash
## Core Attributes
### `csv`
Whether to save observer data to CSV files (default: `True`).
```python
class MyObserver(bt.Observer):
csv = True # Include in CSV output
```bash
### `plotinfo`
Plotting configuration dictionary.
```python
plotinfo = dict(
plot=True, # Whether to plot this observer
subplot=True, # Whether to use a separate subplot
plotname='', # Name in plot legend
)
```bash
### `plotlines`
Line-specific plotting settings.
```python
plotlines = dict(
line1=dict(color='blue', linewidth=2),
line2=dict(_plotskip=True), # Skip plotting this line
)
```bash
### `_stclock`
Controls clock synchronization. When `True`, the observer uses strategy-wide clock (default: `False`).
### `_ltype`
Line iterator type. Set to `LineIterator.ObsType` (value: 2) for observers.
## Core Methods
### `__init__(self)`
Called during observer initialization. Initialize tracking variables and add analyzers if needed.
```python
def __init__(self):
super().__init__()
self._analyzers = list()
# Initialize tracking variables
self.peak = float('-inf')
```bash
### `start(self)`
Called at the start of the backtesting run.
```python
def start(self):
# Perform initialization before data processing
self.initial_value = self._owner.broker.getvalue()
```bash
### `_start(self)`
Internal method that ensures owner is set before calling `start()`.
### `prenext(self)`
Called for each bar before minimum period is reached. By default, observers call `next()` during prenext to track all bars from the beginning.
```python
def prenext(self):
self.next() # Default behavior - process every bar
```bash
### `next(self)`
Called for each bar. Contains the main logic for updating observer values.
```python
def next(self):
self.lines.cash[0] = self._owner.broker.getcash()
```bash
### `stop(self)`
Called after backtesting ends.
### `_register_analyzer(self, analyzer)`
Register an analyzer with this observer.
## Line System
Observers use the same line system as indicators:
```python
class MyObserver(bt.Observer):
lines = ('metric1', 'metric2')
def next(self):
self.lines.metric1[0] = calculate_metric1()
self.lines.metric2[0] = calculate_metric2()
```bash
## Built-in Observers
### Broker Observers
#### Cash
Tracks current cash amount in the broker.
```python
cerebro.addobserver(bt.observers.Cash)
```bash
- *Lines**: `cash`
#### Value
Tracks portfolio value including cash.
```python
cerebro.addobserver(bt.observers.Value)
```bash
- *Parameters**:
- `fund` (default: `None`) - Use fund value instead of total value
- *Lines**: `value`
#### Broker
Combines Cash and Value observers.
```python
cerebro.addobserver(bt.observers.Broker)
```bash
- *Parameters**:
- `fund` (default: `None`) - Use fund mode
- *Lines**: `cash`, `value`
#### FundValue
Tracks fund-like value.
- *Lines**: `fundval`
#### FundShares
Tracks fund-like shares.
- *Lines**: `fundshares`
### Drawdown Observers
#### DrawDown
Tracks current and maximum drawdown levels.
```python
cerebro.addobserver(bt.observers.DrawDown)
```bash
- *Parameters**:
- `fund` (default: `None`) - Use fund mode for returns
- *Lines**:
- `drawdown` - Current drawdown percentage (plotted)
- `maxdrawdown` - Maximum drawdown (not plotted)
```python
class DrawDown(Observer):
_stclock = True
lines = ('drawdown', 'maxdrawdown')
plotlines = dict(maxdrawdown=dict(_plotskip=True))
```bash
#### DrawDownLength
Tracks current drawdown length and maximum length.
- *Lines**:
- `len` - Current drawdown length
- `maxlen` - Maximum drawdown length
### Trade Observers
#### Trades
Tracks completed trades and plots PnL when trades close.
```python
cerebro.addobserver(bt.observers.Trades)
```bash
- *Parameters**:
- `pnlcomm` (default: `True`) - Show net PnL after commission
- *Lines**:
- `pnlplus` - Positive PnL values (blue markers)
- `pnlminus` - Negative PnL values (red markers)
```python
# Trades observer tracks:
# - Total trades count
# - Long/short trade counts
# - Win/loss statistics
# - Trade length statistics
```bash
#### DataTrades
Tracks PnL for multiple data feeds separately.
- *Parameters**:
- `usenames` (default: `True`) - Use data names for labels
### BuySell Observer
Visualizes buy and sell orders on the chart.
```python
cerebro.addobserver(bt.observers.BuySell)
```bash
- *Parameters**:
- `barplot` (default: `False`) - Plot signals at bar extremes
- `bardist` (default: `0.015`) - Distance from high/low (1.5%)
- *Lines**:
- `buy` - Buy marker (green triangle up)
- `sell` - Sell marker (red triangle down)
```python
# Customize marker appearance
cerebro.addobserver(bt.observers.BuySell, barplot=True, bardist=0.02)
```bash
### Return Observers
#### TimeReturn
Tracks strategy returns over time periods.
```python
cerebro.addobserver(bt.observers.TimeReturn, timeframe=bt.TimeFrame.Days)
```bash
- *Parameters**:
- `timeframe` (default: `None`) - Time aggregation period
- `compression` (default: `None`) - Compression for sub-day timeframes
- `fund` (default: `None`) - Use fund mode
- *Lines**: `timereturn`
```python
# Track daily returns
cerebro.addobserver(bt.observers.TimeReturn, timeframe=bt.TimeFrame.Days)
# Track weekly returns
cerebro.addobserver(bt.observers.TimeReturn, timeframe=bt.TimeFrame.Weeks)
```bash
#### LogReturns
Tracks log returns of the strategy.
```python
cerebro.addobserver(bt.observers.LogReturns)
```bash
- *Parameters**:
- `timeframe` (default: `None`) - Time aggregation period
- `compression` (default: `None`) - Compression for sub-day timeframes
- `fund` (default: `None`) - Use fund mode
- *Lines**: `logret1`
#### LogReturns2
Extends LogReturns to show two instruments.
- *Lines**: `logret1`, `logret2`
### Benchmark Observer
Compares strategy returns to a reference asset.
```python
data = bt.feeds.GenericCSVData(dataname='benchmark.csv')
cerebro.adddata(data)
cerebro.addobserver(bt.observers.Benchmark, data=data)
```bash
- *Parameters**:
- `data` (default: `None`) - Reference data feed
- `_doprenext` (default: `False`) - Track from data start
- `firstopen` (default: `False`) - Use opening price for first comparison
- `fund` (default: `None`) - Use fund mode
- *Lines**: `benchmark`
### TradeLogger
Comprehensive logging observer for all trading activities.
```python
cerebro.addobserver(bt.observers.TradeLogger,
log_dir='./logs',
log_orders=True,
log_trades=True,
log_positions=True,
log_indicators=True,
log_signals=True)
```bash
- *Parameters**:
- `log_dir` (default: `'./logs'`) - Directory for log files
- `log_orders` (default: `True`) - Log order status changes
- `log_trades` (default: `True`) - Log trade openings/closings
- `log_positions` (default: `True`) - Log positions every bar
- `log_indicators` (default: `True`) - Log indicator values every bar
- `log_signals` (default: `True`) - Log buy/sell signals
- `log_position_snapshot` (default: `True`) - Save position snapshot to YAML
- `snapshot_file` (default: `'current_position.yaml'`) - Snapshot filename
- `log_format` (default: `'json'`) - Log format ('json' or 'text')
- `log_to_console` (default: `False`) - Also print to console
- `mysql_enabled` (default: `False`) - Enable MySQL logging
- `mysql_host` (default: `'localhost'`) - MySQL host
- `mysql_port` (default: `3306`) - MySQL port
- `mysql_user` (default: `'root'`) - MySQL user
- `mysql_password` (default: `''`) - MySQL password
- `mysql_database` (default: `'backtrader'`) - MySQL database
- *Generated Files**:
- `order.log` - Order status changes
- `trade.log` - Trade openings and closings
- `position.log` - Position values every bar
- `indicator.log` - Indicator values every bar
- `signal.log` - Buy/sell signals
- `current_position.yaml` - Position snapshot
## Custom Observer Development
### Basic Observer
```python
import backtrader as bt
class CustomMetric(bt.Observer):
"""
Observer that tracks a custom metric.
"""
_stclock = True # Use strategy clock
lines = ('custom_value',)
params = (
('period', 20),
)
plotinfo = dict(
plot=True,
subplot=True,
plotname='Custom Metric',
)
def __init__(self):
super().__init__()
self.high_watermark = float('-inf')
def next(self):
# Calculate custom metric
value = self._owner.broker.getvalue()
# Track high watermark
if value > self.high_watermark:
self.high_watermark = value
# Store in line
self.lines.custom_value[0] = value - self.high_watermark
```bash
### Observer with Analyzer
```python
class SharpeRatioObserver(bt.Observer):
"""
Observer that tracks Sharpe ratio using an analyzer.
"""
_stclock = True
lines = ('sharpe',)
params = (
('period', 252), # Annualization period
('riskfreerate', 0.0),
)
plotinfo = dict(plot=True, subplot=True)
def __init__(self):
super().__init__()
# Add analyzer as slave
self._sharpe = self._owner._addanalyzer_slave(
bt.analyzers.SharpeRatio,
period=self.p.period,
riskfreerate=self.p.riskfreerate
)
def next(self):
# Get current Sharpe ratio from analyzer
if hasattr(self._sharpe, 'rets') and self._sharpe.rets:
self.lines.sharpe[0] = self._sharpe.rets.get('sharperatio', float('NaN'))
```bash
### Multi-Line Observer
```python
class PortfolioStats(bt.Observer):
"""
Observer tracking multiple portfolio statistics.
"""
_stclock = True
lines = (
'exposure',
'leverage',
'cash_ratio',
)
plotinfo = dict(plot=True, subplot=True)
plotlines = dict(
exposure=dict(color='blue'),
leverage=dict(color='orange'),
cash_ratio=dict(color='green'),
)
def next(self):
portfolio_value = self._owner.broker.getvalue()
cash = self._owner.broker.getcash()
# Calculate metrics
self.lines.cash_ratio[0] = cash / portfolio_value if portfolio_value else 0
self.lines.exposure[0] = 1 - self.lines.cash_ratio[0]
# Calculate leverage (total position value / portfolio value)
total_position = 0
for data in self._owner.datas:
position = self._owner.getposition(data)
total_position += abs(position.size) * data.close[0]
self.lines.leverage[0] = total_position / portfolio_value if portfolio_value else 0
```bash
## Registration Process
Observers are automatically registered when added via `cerebro.addobserver()`:
```python
# Observer registration
cerebro.addobserver(bt.observers.DrawDown)
# With parameters
cerebro.addobserver(bt.observers.DrawDown, fund=True)
# Multiple instances
cerebro.addobserver(bt.observers.DrawDown)
cerebro.addobserver(bt.observers.Trades)
```bash
The registration process:
1. Observer instance is created
2. `_ltype` is set to `LineIterator.ObsType` (2)
3. Observer is added to strategy's `_lineiterators[ObsType]` list
4. `prenext()`, `next()`, `stop()` are called during execution
## Observer vs Indicator
| Feature | Observer | Indicator |
|---------|----------|-----------|
| Purpose | Monitor and record | Generate signals |
| `_ltype` | `ObsType` (2) | `IndType` (0) |
| `_stclock` | Often `True` | Usually `False` |
| Default `prenext` | Calls `next()` | Does nothing |
| Plotting | Subplot by default | Overlay on data |
| Line calculation | External (broker/trades) | Internal calculation |
## Plotting Configuration
### Disable Plotting
```python
# Individual observer
cerebro.addobserver(bt.observers.DrawDown, _plot=False)
# Or modify plotinfo
class MyObserver(bt.Observer):
plotinfo = dict(plot=False)
```bash
### Subplot Configuration
```python
class MyObserver(bt.Observer):
plotinfo = dict(
plot=True,
subplot=True, # Separate subplot
plotlinelabels=True,
plotymargin=0.10, # Y-axis margin
plothlines=[0.0], # Horizontal lines
)
```bash
### Line Styling
```python
class MyObserver(bt.Observer):
plotlines = dict(
metric1=dict(
color='blue',
linewidth=2,
linestyle='-',
marker='o',
markersize=4,
),
metric2=dict(
color='red',
_plotskip=True, # Don't plot
),
)
```bash
## Full Example
```python
import backtrader as bt
class MyStrategy(bt.Strategy):
params = (
('sma_period', 20),
)
def __init__(self):
self.sma = bt.indicators.SMA(self.data.close, period=self.p.sma_period)
def next(self):
if not self.position:
if self.data.close[0] > self.sma[0]:
self.buy()
else:
if self.data.close[0] < self.sma[0]:
self.sell()
# Create cerebro
cerebro = bt.Cerebro()
# Add strategy
cerebro.addstrategy(MyStrategy)
# Add data
data = bt.feeds.YahooFinanceData(dataname='AAPL', fromdate='2020-01-01', todate='2023-12-31')
cerebro.adddata(data)
# Add observers
cerebro.addobserver(bt.observers.Broker) # Cash and Value
cerebro.addobserver(bt.observers.DrawDown) # Drawdown tracking
cerebro.addobserver(bt.observers.Trades) # Trade PnL
cerebro.addobserver(bt.observers.BuySell) # Buy/sell markers
# Add custom observer
class PositionSize(bt.Observer):
_stclock = True
lines = ('possize',)
plotinfo = dict(plot=True, subplot=True, plotname='Position Size')
def next(self):
self.lines.possize[0] = self._owner.getposition().size
cerebro.addobserver(PositionSize)
# Run
cerebro.run()
# Plot
cerebro.plot()
```bash
## Next Steps
- [Strategy API](strategy.md) - Strategy development
- [Analyzer API](analyzer.md) - Performance analysis
- [Indicator API](indicator.md) - Custom indicators