Skip to content
All library documents

Generating Directional Insights from Historical Returns

Article Strategy library · Author: QuantConnect

Summary

This alpha model turns a security’s historical rate of change into a directional forecast. For each symbol, it calculates returns over a configurable lookback at a chosen data resolution. Positive returns generate an upward insight, negative returns generate a downward insight, and a zero return cancels any existing insight. The forecast duration is set to the lookback period multiplied by the selected resolution.

When securities enter the data feed, the model requests history to warm up its rate-of-change indicator and registers it for ongoing updates. It avoids emitting another insight until the indicator has received new samples, and removes consolidators and cancels insights when a security leaves. The code explains signal construction and data handling, but includes no portfolio rules, transaction-cost model, backtest, or evidence that past returns predict future performance. Its output is therefore a basic momentum-style input for a larger trading system, not a complete evaluated strategy.

Key ideas

  • The model maps positive and negative historical returns to upward and downward insights.
  • Lookback and data resolution determine the return window and forecast duration.
  • Indicator warm-up uses historical data when a security is added.
  • Existing insights are canceled for zero returns and when securities are removed.
  • The source provides no backtest or evidence of predictive performance.

Tags

Full text
# HistoricalReturnsAlphaModel


# HistoricalReturnsAlphaModel









Uses Historical returns to create insights.

## Source (Apache-2.0)

```python
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from AlgorithmImports import *

class HistoricalReturnsAlphaModel(AlphaModel):
    '''Uses Historical returns to create insights.'''

    def __init__(self, *args, **kwargs):
        '''Initializes a new default instance of the HistoricalReturnsAlphaModel class.
        Args:
            lookback(int): Historical return lookback period
            resolution: The resolution of historical data'''
        self.lookback = kwargs['lookback'] if 'lookback' in kwargs else 1
        self.resolution = kwargs['resolution'] if 'resolution' in kwargs else Resolution.DAILY
        self.prediction_interval = Time.multiply(Extensions.to_time_span(self.resolution), self.lookback)
        self._symbol_data_by_symbol = {}
        self.insight_collection = InsightCollection()

    def update(self, algorithm, data):
        '''Updates this alpha model with the latest data from the algorithm.
        This is called each time the algorithm receives data for subscribed securities
        Args:
            algorithm: The algorithm instance
            data: The new data available
        Returns:
            The new insights generated'''
        insights = []

        for symbol, symbol_data in self._symbol_data_by_symbol.items():
            if symbol_data.can_emit:

                direction = InsightDirection.FLAT
                magnitude = symbol_data.return_
                if magnitude > 0: direction = InsightDirection.UP
                if magnitude < 0: direction = InsightDirection.DOWN

                if direction == InsightDirection.FLAT:
                    self.cancel_insights(algorithm, symbol)
                    continue

                insights.append(Insight.price(symbol, self.prediction_interval, direction, magnitude, None))

        self.insight_collection.add_range(insights)
        return insights

    def on_securities_changed(self, algorithm, changes):
        '''Event fired each time the we add/remove securities from the data feed
        Args:
            algorithm: The algorithm instance that experienced the change in securities
            changes: The security additions and removals from the algorithm'''

        # clean up data for removed securities
        for removed in changes.removed_securities:
            symbol_data = self._symbol_data_by_symbol.pop(removed.symbol, None)
            if symbol_data is not None:
                symbol_data.remove_consolidators(algorithm)
            self.cancel_insights(algorithm, removed.symbol)

        # initialize data for added securities
        symbols = [ x.symbol for x in changes.added_securities ]
        history = algorithm.history(symbols, self.lookback, self.resolution)
        if history.empty: return

        tickers = history.index.levels[0]
        for ticker in tickers:
            symbol = SymbolCache.get_symbol(ticker)

            if symbol not in self._symbol_data_by_symbol:
                symbol_data = SymbolData(symbol, self.lookback)
                self._symbol_data_by_symbol[symbol] = symbol_data
                symbol_data.register_indicators(algorithm, self.resolution)
                symbol_data.warm_up_indicators(history.loc[ticker])

    def cancel_insights(self, algorithm, symbol):
        if not self.insight_collection.contains_key(symbol):
            return
        insights = self.insight_collection[symbol]
        algorithm.insights.cancel(insights)
        self.insight_collection.clear([ symbol ]);


class SymbolData:
    '''Contains data specific to a symbol required by this model'''
    def __init__(self, symbol, lookback):
        self.symbol = symbol
        self.roc = RateOfChange('{}.roc({})'.format(symbol, lookback), lookback)
        self.consolidator = None
        self.previous = 0

    def register_indicators(self, algorithm, resolution):
        self.consolidator = algorithm.resolve_consolidator(self.symbol, resolution)
        algorithm.register_indicator(self.symbol, self.roc, self.consolidator)

    def remove_consolidators(self, algorithm):
        if self.consolidator is not None:
            algorithm.subscription_manager.remove_consolidator(self.symbol, self.consolidator)

    def warm_up_indicators(self, history):
        for tuple in history.itertuples():
            self.roc.update(tuple.Index, tuple.close)

    @property
    def return_(self):
        return float(self.roc.current.value)

    @property
    def can_emit(self):
        if self.previous == self.roc.samples:
            return False

        self.previous = self.roc.samples
        return self.roc.is_ready

    def __str__(self, **kwargs):
        return '{}: {:.2%}'.format(self.roc.name, (1 + self.return_)**252 - 1)

```

Shown in full with attribution under the source's licence. Licence: Apache-2.0

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