Skip to content
All library documents

Backtrader Drawdown Monitoring with a Moving-Average Crossover Strategy

Code backtrader

Summary

This Backtrader example demonstrates how to monitor portfolio drawdown while running a simple moving-average crossover strategy. It calculates a 15-period simple moving average and uses a crossover between closing price and the average to generate buy and sell orders. The sample also registers both current and legacy drawdown observers, then logs their drawdown and maximum drawdown readings during strategy updates.

The code notes that observer values are read from the prior bar because the current bar’s drawdown is calculated after the strategy’s update step. A daily CSV feed and plotting provide a basic way to inspect the resulting signals and risk metrics. The document is an implementation example rather than a performance study: it provides no return analysis, transaction-cost modeling, or evidence that the crossover is profitable. Its usefulness lies in illustrating drawdown observation and timing within the Backtrader strategy lifecycle.

Key ideas

  • The example enters long when closing price crosses above a simple moving average and sells when it crosses below.
  • It registers current and legacy drawdown observers to expose drawdown and maximum drawdown statistics.
  • The strategy reads drawdown values from the prior bar because the observer updates after the strategy’s next-step logic.
  • The sample demonstrates framework mechanics and supplies no evidence of trading profitability.

Tags

Full text
# observers-default-drawdown.py


```py
#!/usr/bin/env python
# -*- coding: utf-8; py-indent-offset:4 -*-
###############################################################################
#
# Copyright (C) 2015-2023 Daniel Rodriguez
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import argparse
import datetime
import os.path
import time
import sys


import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind


class MyStrategy(bt.Strategy):
    params = (('smaperiod', 15),)

    def log(self, txt, dt=None):
        ''' Logging function fot this strategy'''
        dt = dt or self.data.datetime[0]
        if isinstance(dt, float):
            dt = bt.num2date(dt)
        print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):
        # SimpleMovingAverage on main data
        # Equivalent to -> sma = btind.SMA(self.data, period=self.p.smaperiod)
        sma = btind.SMA(period=self.p.smaperiod)

        # CrossOver (1: up, -1: down) close / sma
        self.buysell = btind.CrossOver(self.data.close, sma, plot=True)

        # Sentinel to None: new ordersa allowed
        self.order = None

    def next(self):
        # Access -1, because drawdown[0] will be calculated after "next"
        self.log('DrawDown: %.2f' % self.stats.drawdown.drawdown[-1])
        self.log('MaxDrawDown: %.2f' % self.stats.drawdown.maxdrawdown[-1])

        # Check if we are in the market
        if self.position:
            if self.buysell < 0:
                self.log('SELL CREATE, %.2f' % self.data.close[0])
                self.sell()

        elif self.buysell > 0:
            self.log('BUY CREATE, %.2f' % self.data.close[0])
            self.buy()


def runstrat():
    cerebro = bt.Cerebro()

    data = bt.feeds.BacktraderCSVData(dataname='../../datas/2006-day-001.txt')
    cerebro.adddata(data)

    cerebro.addobserver(bt.observers.DrawDown)
    cerebro.addobserver(bt.observers.DrawDown_Old)

    cerebro.addstrategy(MyStrategy)
    cerebro.run()

    cerebro.plot()


if __name__ == '__main__':
    runstrat()

```

Shown in full with attribution under the source's licence. Licence: GPL-3.0

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.