European Central Bank

Systemic Stress

Introduction

The Systemic Stress dataset by the European Central Bank tracks the Composite Indicator of Systemic Stress from January 1980 to the present. The data covers 14 economies and is measured every weekday, including TARGET holidays and the occasional weekend, released in a weekly batch on Wednesdays. The ECB introduced the index in 2012 and computed the earlier years with it, so readings from before then became available on the day the series debuted rather than on the day they describe.

The indicator aggregates fifteen market-based stress measures across five segments of the financial system, and weights them by how strongly those segments are moving together. That weighting is the point of it: the reading rises further when trouble is broad than when one market is upset on its own, which is what separates a systemic episode from a local one. The scale runs from zero to one.

For more information about the Systemic Stress dataset, including CLI commands and pricing, see the dataset listing.

About the Provider

The European Central Bank is the central bank of the euro area, based in Frankfurt and operating since 1998. It publishes the indicator as part of its financial stability mandate. The ECB Data Portal is free to access and requires no API key and no registration.

Getting Started

The following snippet demonstrates how to request data from the Systemic Stress dataset:

self.euro_area = self.add_data(ECBSystemicStress, ECB.StressAreas.EURO_AREA, Resolution.DAILY).symbol
_euroArea = AddData<ECBSystemicStress>(ECB.StressAreas.EuroArea, Resolution.Daily).Symbol;

Data Summary

The following table describes the dataset properties:

PropertyValue
Start DateJanuary 1980
Asset Coverage14 Economies
Data DensitySparse
ResolutionDaily
TimezoneBerlin

Requesting Data

To add Systemic Stress data to your algorithm, call the AddDataadd_data method with the economy you want. Save a reference to the dataset Symbol so you can access the data later in your algorithm.

class ECBSystemicStressExampleAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        self.set_start_date(2020, 1, 1)
        self.set_end_date(2021, 1, 1)
        self._euro_area = self.add_data(ECBSystemicStress, ECB.StressAreas.EURO_AREA).symbol
public class ECBSystemicStressExampleAlgorithm : QCAlgorithm
{
    private Symbol _euroArea;

    public override void Initialize()
    {
        SetStartDate(2020, 1, 1);
        SetEndDate(2021, 1, 1);
        _euroArea = AddData<ECBSystemicStress>(ECB.StressAreas.EuroArea).Symbol;
    }
}

Accessing Data

To get the current Systemic Stress data, index the current Slice with the dataset Symbol. Slice objects deliver unique events to your algorithm as they happen, but the Slice may not contain data for your dataset at every time step, so check before you index it.

Only the euro area carries the segment contributions. For an individual economy those properties are null, so check them before acting on them rather than assuming a reading of zero.

def on_data(self, slice: Slice) -> None:
    readings = slice.get(ECBSystemicStress)
    if self._euro_area in readings:
        point = readings[self._euro_area]
        self.log(f"{point.end_time} stress {point.composite}, of which "
                 f"intermediaries {point.financial_intermediaries_contribution}")
public override void OnData(Slice slice)
{
    var readings = slice.Get<ECBSystemicStress>();
    if (readings.ContainsKey(_euroArea))
    {
        var point = readings[_euroArea];
        Log($"{point.EndTime} stress {point.Composite}, of which "
            + $"intermediaries {point.FinancialIntermediariesContribution}");
    }
}

The Value property is the composite indicator itself.

The ECB releases a whole week of readings at once, and data reaches an algorithm at its release, so OnDataon_data runs once a week with that week's latest reading rather than once a day. Over January 2020 the euro area's 23 readings arrived as 5 points, one per Wednesday release.

Historical Data

To get historical Systemic Stress data, call the Historyhistory method with the dataset Symbol. If there is no data in the period you request, the history result is empty. Ask for a period rather than a bar count: a count is read in daily bars and includes the weekends and holidays the indicator does not report on.

history = self.history[ECBSystemicStress](self._euro_area, timedelta(days=365), Resolution.DAILY)
var history = History<ECBSystemicStress>(_euroArea, TimeSpan.FromDays(365), Resolution.Daily);

History follows the release schedule, so it returns one row per weekly batch, the same readings OnDataon_data receives. Over the same January 2020 window it returns 5. The daily readings are all in the file, and a dataframe history request returns the 23 of them.

For more information about historical data, see History Requests.

Remove Subscriptions

To remove your subscription to Systemic Stress data, call the RemoveSecurityremove_security method.

self.remove_security(self._euro_area)
RemoveSecurity(_euroArea);

Supported Economies

Only the euro area aggregate carries the full decomposition into market segments. The euro area countries publish the headline index and their own sovereign stress, so their segment columns are empty. Three economies sit outside the euro and publish the headline index alone, tracked to place euro area stress against the rest of the world.

EconomyConstantDecomposition
Euro areaECB.StressAreas.EuroAreaFull
AustriaECB.StressAreas.AustriaHeadline and sovereign
BelgiumECB.StressAreas.BelgiumHeadline and sovereign
FinlandECB.StressAreas.FinlandHeadline and sovereign
FranceECB.StressAreas.FranceHeadline and sovereign
GermanyECB.StressAreas.GermanyHeadline and sovereign
IrelandECB.StressAreas.IrelandHeadline and sovereign
ItalyECB.StressAreas.ItalyHeadline and sovereign
NetherlandsECB.StressAreas.NetherlandsHeadline and sovereign
PortugalECB.StressAreas.PortugalHeadline and sovereign
SpainECB.StressAreas.SpainHeadline and sovereign
ChinaECB.StressAreas.ChinaHeadline
United KingdomECB.StressAreas.UnitedKingdomHeadline
United StatesECB.StressAreas.UnitedStatesHeadline

Example Applications

The Systemic Stress dataset lets you read financial stress as the ECB measures it rather than building a proxy from volatility. Examples include the following strategies:

  • Scaling risk exposure down as the composite indicator rises.
  • Reading the euro area against the United States to tell a regional upset from a global one.
  • Watching the financial intermediaries contribution, the segment whose stress reaches the real economy fastest.
  • Using sovereign stress to time peripheral versus core European exposure.

Classic Algorithm Example

Steps out of risk when systemic stress is elevated on both sides of the Atlantic. Reading the euro area and the United States together is what separates a local upset from a systemic one, which is the distinction the indicator is built to make.

from AlgorithmImports import *


class ECBSystemicStressAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2019, 1, 1)
        self.set_end_date(2021, 3, 31)
        self.set_cash(100000)

        self.equity = self.add_equity("SPY", Resolution.DAILY).symbol

        self.euro_area = self.add_data(ECBSystemicStress, ECB.StressAreas.EURO_AREA, Resolution.DAILY).symbol
        self.united_states = self.add_data(ECBSystemicStress, ECB.StressAreas.UNITED_STATES, Resolution.DAILY).symbol

        self.euro_area_stress = None
        self.united_states_stress = None

    def on_data(self, slice: Slice) -> None:
        readings = slice.get(ECBSystemicStress)

        # The two economies need not arrive in the same slice, so the latest reading of each is
        # kept. A reading with an empty composite must not clear it: the Reader writes None for an
        # empty cell, and assigning it would silently stop the algorithm until a filled one arrives.
        if self.euro_area in readings and readings[self.euro_area].composite is not None:
            self.euro_area_stress = readings[self.euro_area].composite

            self.log(f"{readings[self.euro_area].end_time} euro area stress {self.euro_area_stress}, "
                     f"of which intermediaries {readings[self.euro_area].financial_intermediaries_contribution}")

        if self.united_states in readings and readings[self.united_states].composite is not None:
            self.united_states_stress = readings[self.united_states].composite

        if self.euro_area_stress is None or self.united_states_stress is None:
            return

        # The readings land in the European morning, hours before the US session opens, so wait for
        # the bar rather than ordering against a security that has no price yet.
        if self.equity not in slice.bars:
            return

        # Stress on both sides at once is the systemic case worth stepping aside for.
        stressed = self.euro_area_stress > 0.1 and self.united_states_stress > 0.1

        if stressed:
            if self.portfolio[self.equity].invested:
                self.liquidate(self.equity)
        elif not self.portfolio[self.equity].invested:
            self.set_holdings(self.equity, 1)
public class ECBSystemicStressAlgorithm : QCAlgorithm
{
    private Symbol _equity;
    private Symbol _euroArea;
    private Symbol _unitedStates;

    private decimal? _euroAreaStress;
    private decimal? _unitedStatesStress;

    public override void Initialize()
    {
        SetStartDate(2019, 1, 1);
        SetEndDate(2021, 3, 31);
        SetCash(100000);

        _equity = AddEquity("SPY", Resolution.Daily).Symbol;

        _euroArea = AddData<ECBSystemicStress>(ECB.StressAreas.EuroArea, Resolution.Daily).Symbol;
        _unitedStates = AddData<ECBSystemicStress>(ECB.StressAreas.UnitedStates, Resolution.Daily).Symbol;
    }

    public override void OnData(Slice slice)
    {
        var readings = slice.Get<ECBSystemicStress>();

        // The two economies need not arrive in the same slice, so the latest reading of each
        // is kept.
        // A reading with an empty composite must not clear the one being carried: the
        // Reader writes null for an empty cell, and assigning it would silently stop the
        // algorithm until a filled reading arrives.
        if (readings.ContainsKey(_euroArea) && readings[_euroArea].Composite.HasValue)
        {
            _euroAreaStress = readings[_euroArea].Composite;

            Log($"{readings[_euroArea].EndTime} euro area stress {_euroAreaStress}, "
                + $"of which intermediaries {readings[_euroArea].FinancialIntermediariesContribution}");
        }

        if (readings.ContainsKey(_unitedStates) && readings[_unitedStates].Composite.HasValue)
        {
            _unitedStatesStress = readings[_unitedStates].Composite;
        }

        if (!_euroAreaStress.HasValue || !_unitedStatesStress.HasValue)
        {
            return;
        }

        // The readings land in the European morning, hours before the US session opens, so
        // wait for the bar rather than ordering against a security that has no price yet.
        if (!slice.Bars.ContainsKey(_equity))
        {
            return;
        }

        // Stress on both sides at once is the systemic case worth stepping aside for.
        var stressed = _euroAreaStress > 0.1m && _unitedStatesStress > 0.1m;

        if (stressed)
        {
            if (Portfolio[_equity].Invested)
            {
                Liquidate(_equity);
            }
        }
        else if (!Portfolio[_equity].Invested)
        {
            SetHoldings(_equity, 1);
        }
    }
}

Framework Algorithm Example

The following example algorithm trades a manually selected universe from systemic stress on both sides of the Atlantic. An alpha model subscribes to the euro area and the United States, and emits a flat insight while both readings are elevated and an up insight once either falls back.

from AlgorithmImports import *

class ECBSystemicStressFrameworkAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2019, 1, 1)
        self.set_end_date(2021, 3, 31)
        self.set_cash(100000)

        self.universe_settings.resolution = Resolution.DAILY

        symbols = [Symbol.create("SPY", SecurityType.EQUITY, Market.USA)]
        self.set_universe_selection(ManualUniverseSelectionModel(symbols))

        self.add_alpha(ECBSystemicStressAlphaModel(self))

        self.set_portfolio_construction(EqualWeightingPortfolioConstructionModel())
        self.set_execution(ImmediateExecutionModel())


class ECBSystemicStressAlphaModel(AlphaModel):
    """Emits insights from systemic stress on both sides of the Atlantic."""

    def __init__(self, algorithm):
        self._euro_area = algorithm.add_data(ECBSystemicStress, ECB.StressAreas.EURO_AREA, Resolution.DAILY).symbol
        self._united_states = algorithm.add_data(ECBSystemicStress, ECB.StressAreas.UNITED_STATES, Resolution.DAILY).symbol

        history = algorithm.history[ECBSystemicStress](self._euro_area, timedelta(days=365), Resolution.DAILY)
        algorithm.debug(f"Got {len(list(history))} historical stress readings")

        self._euro_area_stress = None
        self._united_states_stress = None
        self._symbols = []

    def update(self, algorithm, data):
        readings = data.get(ECBSystemicStress)

        # A reading with an empty composite must not clear the one being carried: the Reader
        # writes None for an empty cell, and assigning it would silently stop the model.
        if self._euro_area in readings and readings[self._euro_area].composite is not None:
            self._euro_area_stress = readings[self._euro_area].composite

        if self._united_states in readings and readings[self._united_states].composite is not None:
            self._united_states_stress = readings[self._united_states].composite

        if self._euro_area_stress is None or self._united_states_stress is None:
            return []

        # Stress on both sides at once is the systemic case worth stepping aside for.
        stressed = self._euro_area_stress > 0.1 and self._united_states_stress > 0.1
        direction = InsightDirection.FLAT if stressed else InsightDirection.UP

        # The ECB releases a whole week of readings at once, so a reading reaches the algorithm
        # once a week. The insight outlives two of those releases, which keeps it alive across a
        # batch that a TARGET holiday pushes back rather than lapsing between readings.
        # The readings land in the European morning, hours before the US session, so an insight
        # waits for the bar rather than being emitted against a security that has no price yet.
        return [Insight.price(symbol, timedelta(days=14), direction)
                for symbol in self._symbols if symbol in data.bars]

    def on_securities_changed(self, algorithm, changes):
        for security in changes.added_securities:
            self._symbols.append(security.symbol)

        for security in changes.removed_securities:
            if security.symbol in self._symbols:
                self._symbols.remove(security.symbol)
public class ECBSystemicStressFrameworkAlgorithm : QCAlgorithm
{
    public override void Initialize()
    {
        SetStartDate(2019, 1, 1);
        SetEndDate(2021, 3, 31);
        SetCash(100000);

        UniverseSettings.Resolution = Resolution.Daily;

        var symbols = new[] { QuantConnect.Symbol.Create("SPY", SecurityType.Equity, Market.USA) };
        SetUniverseSelection(new ManualUniverseSelectionModel(symbols));

        AddAlpha(new ECBSystemicStressAlphaModel(this));

        SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel());
        SetExecution(new ImmediateExecutionModel());
    }
}

public class ECBSystemicStressAlphaModel : AlphaModel
{
    private readonly Symbol _euroArea;
    private readonly Symbol _unitedStates;
    private readonly List<Symbol> _symbols = new();

    private decimal? _euroAreaStress;
    private decimal? _unitedStatesStress;

    public ECBSystemicStressAlphaModel(QCAlgorithm algorithm)
    {
        _euroArea = algorithm.AddData<ECBSystemicStress>(ECB.StressAreas.EuroArea, Resolution.Daily).Symbol;
        _unitedStates = algorithm.AddData<ECBSystemicStress>(ECB.StressAreas.UnitedStates, Resolution.Daily).Symbol;

        var history = algorithm.History<ECBSystemicStress>(_euroArea, TimeSpan.FromDays(365), Resolution.Daily);
        algorithm.Debug($"Got {history.Count()} historical stress readings");
    }

    public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
    {
        var readings = data.Get<ECBSystemicStress>();

        // A reading with an empty composite must not clear the one being carried: the Reader
        // writes null for an empty cell, and assigning it would silently stop the model.
        if (readings.ContainsKey(_euroArea) && readings[_euroArea].Composite.HasValue)
        {
            _euroAreaStress = readings[_euroArea].Composite;
        }

        if (readings.ContainsKey(_unitedStates) && readings[_unitedStates].Composite.HasValue)
        {
            _unitedStatesStress = readings[_unitedStates].Composite;
        }

        if (!_euroAreaStress.HasValue || !_unitedStatesStress.HasValue)
        {
            return Enumerable.Empty<Insight>();
        }

        // Stress on both sides at once is the systemic case worth stepping aside for.
        var stressed = _euroAreaStress > 0.1m && _unitedStatesStress > 0.1m;
        var direction = stressed ? InsightDirection.Flat : InsightDirection.Up;

        // The ECB releases a whole week of readings at once, so a reading reaches the algorithm
        // once a week. The insight outlives two of those releases, which keeps it alive across a
        // batch that a TARGET holiday pushes back rather than lapsing between readings.
        // The readings land in the European morning, hours before the US session, so an insight
        // waits for the bar rather than being emitted against a security that has no price yet.
        return _symbols.Where(symbol => data.Bars.ContainsKey(symbol))
            .Select(symbol => Insight.Price(symbol, TimeSpan.FromDays(14), direction));
    }

    public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
    {
        foreach (var security in changes.AddedSecurities)
        {
            _symbols.Add(security.Symbol);
        }

        foreach (var security in changes.RemovedSecurities)
        {
            _symbols.Remove(security.Symbol);
        }
    }
}

Data Point Attributes

The Systemic Stress dataset provides ECBSystemicStress objects, which have the following attributes:

You can also see our Videos. You can also get in touch with us via Discord.

Did you find this page helpful?

Contribute to the documentation: