Skip to content
All library documents

NASDAQ-100 Long-Short Strategy Using 20- and 200-Day Averages

Article Strategy library · Author: Quantiacs

Summary

This quick-start example demonstrates a daily long-short stock strategy for a changing NASDAQ-100 universe. For each eligible stock, it compares a 20-day simple moving average with a 200-day average: the stock receives a positive allocation when the shorter average is higher and a negative allocation otherwise. A liquidity flag limits trading to qualifying index constituents, and the resulting weights are scaled across assets. The backtest framework uses information available at the close to set allocations for execution at the next session’s open.

The document also explains exposure-based weights, historical constituent filtering, performance statistics, and common data tasks in the Quantiacs environment. It describes a backtest beginning in 2006 and says contest eligibility requires a Sharpe ratio above one, but supplies no achieved results for this example. Its tutorial code warns that single-pass prototypes can introduce lookahead. The sample is educational rather than evidence of a durable edge; average-based signals can lag, and the contest rules and framework determine how the example should be interpreted.

Key ideas

  • The example takes long or short exposure according to the relative position of 20-day and 200-day averages.
  • A liquidity filter restricts positions to eligible NASDAQ-100 constituents.
  • Signals calculated from closing data are applied at the following session’s open.
  • Portfolio weights represent capital exposure, with positive and negative values indicating long and short positions.
  • The tutorial outlines backtesting statistics and warns that single-pass prototypes can introduce lookahead.

Tags

Full text
# strategy-first-crypto-daily-long


# strategy-first-crypto-daily-long









## Source (MIT)

```python
# # Q18 Quick Start
# 
# This template shows you the basic steps for taking part to the **Q18 NASDAQ-100 Stock Long-Short contest**. 
# 
# Please note that:
# 
# * Your trading algorithm can open short and long positions.
# 
# * At each point in time your algorithm can trade all or a subset of the stocks which at that point of time are or were part of the NASDAQ-100 stock index. Note that the composition of this set changes in time, and Quantiacs provides you with an appropriate filter function for selecting them.
# 
# * The Sharpe ratio of your system since January 1st, 2006, has to be larger than 1.
# 
# * Your system cannot be a copy of the current examples. We run a correlation filter on the submissions and detect duplicates.
# 
# More details on the rules can be found [here](https://quantiacs.com/contest).
# 
# **Need help?** Check the [**Documentation**](https://quantiacs.com/documentation/en/) and find solutions/report problems in the [**Forum**](https://quantiacs.com/community/categories) section.
# 
# **More help with Jupyter?** Check the official [**Jupyter**](https://jupyter.org/) page.
# 
# Once you are done, click on **Submit to the contest** and take part to our competitions.
# 
# API reference:
# 
# * **data**: check how to work with [data](https://quantiacs.com/documentation/en/reference/data_load_functions.html);
# 
# * **backtesting**: read how to run the [simulation](https://quantiacs.com/documentation/en/reference/evaluation.html) and check the results.
# 
# Need to use the optimizer function to automate tedious tasks?
# 
# * **optimization**: read more on our [article](https://quantiacs.com/community/topic/29/optimizing-and-monitoring-a-trading-system-with-quantiacs).

%%javascript
window.IPython && (IPython.OutputArea.prototype._should_scroll = function(lines) { return false; })
// disable widget scrolling

import xarray as xr

import qnt.ta as qnta
import qnt.backtester as qnbt
import qnt.data as qndata



def load_data(period):
    return qndata.stocks.load_ndx_data(tail=period)



def strategy(data):
    close     = data.sel(field="close")
    is_liquid = data.sel(field="is_liquid") # this field tags NASDAQ-100 stocks
    sma_slow  = qnta.sma(close, 200).isel(time=-1)
    sma_fast  = qnta.sma(close, 20).isel(time=-1)
    weights   = xr.where(sma_slow < sma_fast, 1, -1) # 1 - long position (**buy**), -1 - short position (**sell**)
    weights   = weights * is_liquid  # trade only NASDAQ-100 stocks
    weights   = weights / 100.0
    return weights



weights = qnbt.backtest(
    competition_type = "stocks_nasdaq100",
    load_data        = load_data,
    lookback_period  = 365*4,
    start_date       = "2006-01-01",
    strategy         = strategy,
    analyze          = True,
    build_plots      = True
)

# # Description
# 
# ## 1. Data
# 
# The variable **qndata.stocks.load_ndx_data(tail=period)** is an xarray.DataArray structure which contains historical market data for the last (tail=period) days and whose coordinates are:
# 
# * **time**: a date in format yyyy-mm-dd;
# * **field**: an attribute, for example the opening daily price;
# * **asset**: the identifying symbol for the asset, for example NAS:APPL for Apple.
# 
# More details on xarray can be found at https://xarray.pydata.org/en/stable/.
# 
# ## 2. Strategy. Weights allocation
# 
# For each date, the algorithm calculates the portfolio weights which should be used at the opening of the next day's trading.
# 
# Quantiacs uses an exposure-based backtester. The trading algorithm should define the fractions of capital which will be distributed to the assets (allocation weights). A **positive** weight means a long position (**buy**), a **negative** value means a short position (**sell**).
# 
# > Note that algorithm decisions can use all data available at the close of the session, and will be applied at the opening of the next day's session. The chosen allocation weights are translated to positions (number of contracts to be bought/sold) immediately after the close of the session and transactions are executed at the open of the next day.
# 
# ## 3. Performance estimation
# 
# After we have built the algorithm, we can evaluate its performance calculating statistics.
# 
# We can display the values of statistical indicators on a cumulative basis, assuming that we have 1M USD at the starting point.
# 
# The call will produce:
# 
# * **equity**: the cumulative value of profits and losses since inception (1M USD);
# * **relative_return**: the relative daily variation of **equity**;
# * **volatility**: the volatility of the investment since inception (i.e. the annualized standard deviation of the daily returns);
# * **underwater**: the time evolution of drawdowns;
# * **max_drawdown**: the absolute minimum of the underwater chart;
# * **sharpe_ratio**: the annualized Sharpe ratio since inception; the value must be larger than 1 for taking part to contests;
# * **mean_return**: the annualized mean return of the investment since inception;
# * **bias**: the daily asymmetry between long and short exposure: 1 for a long-only system, -1 for a short-only one;
# * **instruments**: the number of instruments which get allocations on a given day;
# * **avg_turnover**: the average turnover;
# * **avg_holding_time**: the average holding time in days.
# 
# A detailed explanation can be found inspecting the source code for the library in your directory at **/qnt/stats.py**
# 
# Moreover we can produce a chart which shows the cumulative profits and losses.

# # How can I write a quick prototype?
# 
# A quick prototpye can be written using a single-pass implementation where all the time series are processed at once. Beware that unintentional forward looking can take place!
# 
# ```python
# import xarray as xr
# 
# import qnt.ta as qnta
# import qnt.data as qndata
# import qnt.output as qnout
# import qnt.stats as qns
# 
# data = qndata.stocks.load_ndx_data(min_date="2006-01-01")
# 
# close     = data.sel(field="close")
# is_liquid = data.sel(field="is_liquid")
# sma_slow  = qnta.sma(close, 200)
# sma_fast  = qnta.sma(close, 20)
# weights   = xr.where(sma_slow < sma_fast, 1, -1)
# weights   = weights * is_liquid 
# weights   = weights / 100.0
# 
# weights = qnout.clean(weights, data, "stocks_nasdaq100")
# 
# qnout.check(weights, data, "stocks_nasdaq100")
# 
# qnout.write(weights)
# 
# # calc stats
# stats = qns.calc_stat(data, weights.sel(time=slice("2006-01-01",None)))
# stats.to_pandas().tail()
# ```

# # What libraries are available?
# 
# Our library makes extensive use of xarray: 
# 
# * [xarray](http://xarray.pydata.org/en/stable/)
# 
# pandas:
# 
# * [pandas](https://pandas.pydata.org/)
# 
# and numpy:
# 
# * [numpy](https://numpy.org/)
# 
# Function definitions can be found in the qnt folder in your private root directory.
# 
# ```python
# # Import basic libraries.
# import xarray as xr
# import pandas as pd
# import numpy as np
# 
# # Import quantnet libraries.
# import qnt.data    as qndata  # load and manipulate data
# import qnt.output as output   # manage output
# import qnt.backtester as qnbt # backtester
# import qnt.stats   as qnstats # statistical functions for analysis
# import qnt.graph   as qngraph # graphical tools
# import qnt.ta      as qnta    # indicators library
# ```

# # May I import libraries?
# 
# Yes, please refer to the file **init.ipynb** in your home directory. You can for example use:
# 
# ! conda install -y scikit-learn

# # How to load data?
# 
# Daily stock data for the **Q18 Nasdaq-100** contest can be loaded using:
# ```python
# data = qndata.stocks.load_ndx_data(tail = 17*365, dims = ("time", "field", "asset"))
# ```
# 
# Cryptocurrency daily data used for the Q16/Q17 contests can be loaded using:
# ```python
# data = qndata.cryptodaily.load_data(tail = 17*365, dims = ("time", "field", "asset"))
# ```
# 
# Futures data for the Q15 contest can be loaded using:
# ```python
# data= qndata.futures.load_data(tail = 17*365, dims = ("time", "field", "asset"))
# ```
# 
# BTC Futures data for the Q15 contest can be loaded using:
# ```python
# data= qndata.cryptofutures.load_data(tail = 17*365, dims = ("time", "field", "asset"))
# ```

# # How to view a list of all tickers?
# 
# ```python
# data.asset.to_pandas().to_list()
# ```

# # How to see which fields are available?
# 
# ```python
# data.field.to_pandas().to_list()
# ```

# # How to load specific tickers?
# 
# ```python
# data = qndata.stocks.load_ndx_data(tail=17 * 365, assets=["NAS:AAPL", "NAS:AMZN"])
# ```

# # How to select specific tickers after loading all data?
# 
# ```python
# def get_data_filter(data, assets):
#     filler= data.sel(asset=assets)
#     return filler
# 
# get_data_filter(data, ["NAS:AAPL", "NAS:AMZN"])
# ```

# # How to get the prices for the previous day?
# 
# ```python
# qnta.shift(data.sel(field="open"), periods=1)
# ```
# 
# or:
# 
# ```python
# data.sel(field="open").shift(time=1)
# ```

# # How to get the Sharpe ratio?
# 
# ```python
# import qnt.stats as qnstats
# 
# def get_sharpe(market_data, weights):
#     rr = qnstats.calc_relative_return(market_data, weights)
#     sharpe = qnstats.calc_sharpe_ratio_annualized(rr).values[-1]
#     return sharpe
# 
# sharpe = get_sharpe(data, weights) # weights.sel(time=slice("2006-01-01",None))
# ```

# # How do I get a list of the top 3 assets ranked by Sharpe ratio?
# 
# ```python
# import qnt.stats as qnstats
# 
# data = qndata.stocks.load_ndx_data(tail = 17*365, dims = ("time", "field", "asset"))
# 
# def get_best_instruments(data, weights, top_size):
#     # compute statistics:
#     stats_per_asset = qnstats.calc_stat(data, weights, per_asset=True)
#     # calculate ranks of assets by "sharpe_ratio":
#     ranks = (-stats_per_asset.sel(field="sharpe_ratio")).rank("asset")
#     # select top assets by rank "top_period" days ago:
#     top_period = 1
#     rank = ranks.isel(time=-top_period)
#     top = rank.where(rank <= top_size).dropna("asset").asset
# 
#     # select top stats:
#     top_stats = stats_per_asset.sel(asset=top.values)
# 
#     # print results:
#     print("SR tail of the top assets:")
#     display(top_stats.sel(field="sharpe_ratio").to_pandas().tail())
# 
#     print("avg SR = ", top_stats[-top_period:].sel(field="sharpe_ratio").mean("asset")[-1].item())
#     display(top_stats)
#     return top_stats.coords["asset"].values
# 
# get_best_instruments(data, weights, 3)
# ```

# # How can I check the results for only the top 3 assets ranked by Sharpe ratio?
# 
# Select the top assets and then load their data:
# 
# ```python
# best_assets= get_best_instruments(data, weights, 3)
# 
# data= qndata.stocks.load_ndx_data(tail = 17*365, assets=best_assets)
# ```

# # How can prices be processed?
# 
# Simply import standard libraries, for example **numpy**:
# 
# ```python
# import numpy as np
# 
# high= np.log(data.sel(field="high"))
# ```

# # How can you reduce slippage impace when trading?
# 
# Just apply some technique to reduce turnover:
# 
# ```python
# def get_lower_slippage(weights, rolling_time=6):
#     return weights.rolling({"time": rolling_time}).max()
# 
# improved_weights = get_lower_slippage(weights, rolling_time=6)
# ```

# # How to use technical analysis indicators?
# 
# For available indicators see the source code of the library: /qnt/ta
# 
# ## ATR
# 
# ```python
# def get_atr(data, days=14):
#     high = data.sel(field="high") * 1.0 
#     low  = data.sel(field="low") * 1.0 
#     close= data.sel(field="close") * 1.0
# 
#     return qnta.atr(high, low, close, days)
# 
# atr= get_atr(data, days=14)
# ```
# 
# ## EMA
# 
# ```python
# prices= data.sel(field="high")
# prices_ema= qnta.ema(prices, 15)
# ```
# 
# ## TRIX
# 
# ```python
# prices= data.sel(field="high")
# prices_trix= qnta.trix(prices, 15)
# ```
# 
# ## ADL and EMA
# 
# ```python
# adl= qnta.ad_line(data.sel(field="close")) * 1.0 
# adl_ema= qnta.ema(adl, 18)
# ```

# # How can you check the quality of your strategy?
# 
# ```python
# import qnt.output as qnout
# qnout.check(weights, data, "stocks_nasdaq100")
# ```
# 
# or
# 
# ```python
# stat= qnstats.calc_stat(data, weights)
# display(stat.to_pandas().tail())
# ```
# 
# or
# 
# ```python
# import qnt.graph   as qngraph
# statistics= qnstats.calc_stat(data, weights)
# display(statistics.to_pandas().tail())
# 
# performance= statistics.to_pandas()["equity"]
# qngraph.make_plot_filled(performance.index, performance, name="PnL (Equity)", type="log")
# 
# display(statistics[-1:].sel(field = ["sharpe_ratio"]).transpose().to_pandas())
# qnstats.print_correlation(weights, data)
# 
# ```

# # An example using pandas
# 
# One can work with pandas DataFrames at intermediate steps and at the end convert them to xarray data structures:
# 
# ```python
# def get_price_pct_change(prices):
#     prices_pandas = prices.to_pandas()
#     assets = data.coords["asset"].values
#     for asset in assets:
#         prices_pandas[asset] = prices_pandas[asset].pct_change()
#     return prices_pandas
# 
# prices = data.sel(field="close") * 1.0
# prices_pct_change = get_price_pct_change(prices).unstack().to_xarray()
# ```

# # How can I combine datasets?
# 
# Let us suppose that we want to use some Futures data as external indicators for taking positions on Nasdaq-100 stocks. This can easily be achieved as follows:
# 
# ```python
# import xarray as xr
# import numpy as np
# 
# import qnt.backtester as qnbt
# import qnt.data as qndata
# import qnt.ta as qnta
# 
# 
# 
# def load_data(period):
#     futures = qndata.futures.load_data(tail=period, assets=["F_DX"]).isel(asset=0)
#     stocks  = qndata.stocks.load_ndx_data(tail=period)
#     return {"futures": futures, "stocks": stocks}, futures.time.values
# 
# 
# 
# def window(data, max_date: np.datetime64, lookback_period: int):
#     min_date = max_date - np.timedelta64(lookback_period, "D")
#     return {
#         "futures": data["futures"].sel(time=slice(min_date, max_date)),
#         "stocks":  data["stocks"].sel(time=slice(min_date, max_date)),
#     }
# 
# 
# 
# def strategy(data):
#     close_futures = data["futures"].sel(field="close")
#     close_stocks  = data["stocks"].sel(field="close")
#     sma20 = qnta.sma(close_futures, 20).isel(time=-1)
#     sma20_stocks = qnta.sma(close_stocks, 20).isel(time=-1)
#     is_liquid = data["stocks"].sel(field="is_liquid").isel(time=-1)
#     weights = xr.where(sma20 < sma20_stocks, 1, -1)
#     weights = weights * is_liquid 
#     weights = weights / 100.0
#     return weights
# 
# 
# 
# qnbt.backtest(
#     competition_type= "stocks_nasdaq100",
#     load_data= load_data,
#     lookback_period= 365,
#     start_date= "2006-01-01",
#     strategy= strategy,
#     window= window
# )
# 
# ```

# # How to submit a strategy to the competition?
# 
# Check that weights are fine:
# 
# ```python
# import qnt.output as qnout
# qnout.check(weights, data, "stocks_nasdaq100")
# ```
# 
# If everything is ok, write the weights to file:
# 
# ```python
# qnout.write(weights)
# ```
# 
# In your **personal account**:
# 
# * **choose** a strategy;
# * click on the **Submit** button;
# * select the type of competition.
# 
# At the beginning you will find the strategy under the **Checking** area:
# 
# * **Sent strategies** > **Checking**. 
# 
# If technical checks are successful, the strategy will go under the **Candidates** area:
# 
# * **Sent strategies** > **Candidates**. 
# 
# Otherwise it will be **Filtered**:
# 
# * **Sent strategies** > **Filtered**
# 
# and you should inspect error and warning messages. 
# 
# Note that a strategy under the **Candidates** area should have a Sharpe ratio larger than 1 for being eligible for a prize. Please check warning messages in your **Candidates** area!

```

Shown in full with attribution under the source's licence. Licence: MIT

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