Back to Community
The Wisdom of the Crowd: How the Crowd Helps us Selects the Best Stock

If you were to pick just one stock which one would you pick and how?

How much would you win, and how much risky is that strategy?

Introduction

I ran the strategy described here from 2005-01-01 to 2019-06-06 and it made 1926% while the SPY benchmark made 206%. The drawdown was -0.61%.

This is a strategy that picks the Amazons, Netflixes, Microsofts and Apples of the day, invests patiently in them while a better opportunity appears.

Strategy

The strategy is based on picking the stock highest in AverageDollarVolume and sticking with it until another stock kicks it out of the leading spot. Over the long term, such a strategy leads to much higher returns than the baseline SPY index.

While the strategy described is not something one may want to run in practice, it demonstrates an anomaly - why would such a simple strategy even exist in the first place.

This post is made with this question in mind and hopes to evoke some useful answers.

I called this the "Wisdom of the Crowd", because ranking the stocks by AverageDollarVolume is like a popularity contest in which the crowd picks a winner, and the strategy just follows the winner. The crowd leaves a trace via high price and high volume for us to follow.

The details are available in the source code of the backtest.

Evaluation over multiple time periods

Period Algorithm Benchmark(SPY)
01/01/2003 - 01/01/2006 69% 49%
01/01/2006 - 01/01/2009 -15% -24%
01/01/2009 - 01/01/2012 297% 48%
01/01/2012 - 01/01/2015 96% 74%
01/01/2015 - 06/01/2018 61% 38%
01/01/2018 - 06/01/2019 12% 6%

Caveats

  • Holding a single stock is risky.
  • The selection criterion is based on AverageDollarVolume only. This may lead to False Positives, if for example the volume was associated with price decline, or it could be an issue due to a bubble forming effect and quick burst
  • This is a long term strategy suitable for a bull market. In a long bear market, holding stocks is of course leads to losses, but if the stock have sound fundamentals, they will rebound more than the index, when the bear market is over.
  • Tax Efficiency. Selling appreciated stocks leads to paying taxes. The strategy does not consider taxes.

Related work

Discussion

  • The distribution of returns among many stocks in a SHORT term (e.g. up to 2-3 years) is close to Gaussian (and is symmetric)
  • The distribution of returns among many stocks in a LONG term (e.g. 15 years) is Zipfean (and highly asymmetric). This means that the rich get richer phenomenon is in place. If fact, is this confirmed by the distribution of the market capitalization.

The strategy hints that perhaps there is a way for a non-professional investor to end up long term in the high end of this Zipfean distribution.

At last, the strategy was inspired by the book, "Dual Momentum Investing: An Innovative Strategy for Higher Returns with Lower Risk", by Gary Antonacci.

Disclaimer

This presentation is for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation for any security; nor does it constitute an offer to provide investment advisory or other services by the author or anyone else. Nothing contained herein constitutes investment advice or offers any opinion with respect to the suitability of any security, and any views expressed herein should not be taken as advice to buy, sell, or hold any security or as an endorsement of any security or company. This disclaimer was adapted from Quantopian's own disclaimer.

Clone Algorithm
26
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
"""
This is a template algorithm on Quantopian for you to adapt and fill in.
"""
import quantopian.algorithm as algo
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.filters import QTradableStocksUS
from quantopian.pipeline.filters import Q500US
from quantopian.pipeline.factors import AverageDollarVolume

def initialize(context):
    """
    Called once at the start of the algorithm.
    """
    # Rebalance every day, 1 hour after market open.
    algo.schedule_function(
        rebalance,
        algo.date_rules.every_day(),
        algo.time_rules.market_open(hours=1),
    )

    # Record tracking variables at the end of each day.
    algo.schedule_function(
        record_vars,
        algo.date_rules.every_day(),
        algo.time_rules.market_close(),
    )

    # Create our dynamic stock selector.
    algo.attach_pipeline(make_pipeline(), 'pipeline')


def make_pipeline():
    q500us = Q500US()
    dollar_volume = AverageDollarVolume(mask=q500us, window_length=178)

    screen = (dollar_volume.top(1) & q500us)

    pipe = Pipeline(
        columns={
            'dollar_volume': dollar_volume,
        },
        screen=screen
    )
    return pipe


def before_trading_start(context, data):
    """
    Called every day before market open.
    """
    context.output = algo.pipeline_output('pipeline')['dollar_volume']
    # These are the securities that we are interested in trading each day.
    context.security_list = context.output.index

def rebalance(context, data):
    """
    Execute orders according to our schedule_function() timing.
    """
    old_stocks = list(context.portfolio.positions.iterkeys())
    old_stock = None
    if len(old_stocks) > 0:
        old_stock = old_stocks[0]
        
    new_stocks = list(context.output.index)
    new_stock = new_stocks[0]
    if old_stock is not None and old_stock != new_stock:
        order_target_percent(old_stock, 0.0)
    if old_stock != new_stock:
        order_target_percent(new_stock, 1.0)

            
def record_vars(context, data):
    """
    Plot variables at the end of each day.
    """
    record(leverage=context.account.leverage)


def handle_data(context, data):
    """
    Called every minute.
    """
    pass
There was a runtime error.