Quick Start Fundamental Data¶
This example showcases a trading strategy based on fundamental data on the Quantiacs platform. The strategy uses Nasdaq 100 index data and focuses on liquid stocks.
You can clone and edit this example there (tab Examples).
Strategy idea: We will buy shares of companies whose total revenue has increased over the last 65 days.
Full code
Below is the complete code snippet for this strategy:
import xarray as xr
import qnt.data as qndata
import qnt.output as qnout
import qnt.stats as qnstats
import qnt.graph as qngraph
import qnt.data.secgov_fundamental as fundamental
market_data = qndata.stocks.load_ndx_data(min_date="2005-01-01")
indicators_data = fundamental.load_indicators_for(market_data, indicator_names=['total_revenue'])
def calculate_weights(data, fundamental_data):
"""
Calculate weights for the strategy based on a simple revenue growth check.
If the total revenue for a given time period is greater than 65 days ago, assign a weight of 1 (buy), otherwise 0.
"""
total_revenue = fundamental_data.sel(field="total_revenue")
total_revenue_days_ago = total_revenue.shift(time=65)
buy = 1
is_up = xr.where(total_revenue > total_revenue_days_ago, buy, 0)
return is_up * data.sel(field='is_liquid') # use only liquidity assets
def add_buy_and_hold_enough_bid_for(data, weights_):
"""Add buy and hold condition based on the liquidity of the assets."""
time_traded = weights_.time[abs(weights_).fillna(0).sum('asset') > 0]
is_strategy_traded = len(time_traded)
if is_strategy_traded:
return xr.where(weights_.time < time_traded.min(), data.sel(field="is_liquid"), weights_)
return weights_
def plot_performance(stats):
"""Plot the performance of the strategy."""
performance = stats.to_pandas()["equity"]
qngraph.make_plot_filled(performance.index, performance, name="PnL (Equity)", type="log")
weights = calculate_weights(market_data, indicators_data)
# Fundamental data is available from 2010 onwards
# Add a simple "buy and hold" strategy.
weights = add_buy_and_hold_enough_bid_for(market_data, weights)
weights = qnout.clean(weights, market_data, "stocks_nasdaq100")
stats = qnstats.calc_stat(market_data, weights.sel(time=slice("2006-01-01", None)))
display(stats.to_pandas().tail())
plot_performance(stats)
weights = weights.sel(time=slice("2006-01-01", None))
qnout.check(weights, market_data, "stocks_nasdaq100")
qnout.write(weights) # to participate in the competition
1) Load libraries
Start by importing all the essential libraries.
import xarray as xr
import qnt.data as qndata
import qnt.output as qnout
import qnt.stats as qnstats
import qnt.graph as qngraph
import qnt.data.secgov_fundamental as fundamental
2) 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.
market_data - contains a list of assets by which indicators will be loaded
market_data.asset.to_pandas().to_list()
indicators_data: This dataset houses the principal fundamental indicators which are, by default, represented as LTM (Last Twelve Months). It essentially provides indicator values calculated for the last 4 quarters for each given date.
List of available indicators
display(fundamental.get_standard_indicator_names())
display(fundamental.get_complex_indicator_names())
display(fundamental.get_annual_indicator_names())
Loading data
# indicators_data = fundamental.load_indicators_for(market_data)
# indicators_data = fundamental.load_indicators_for(market_data, fundamental.get_standard_indicator_names())
# indicators_data = fundamental.load_indicators_for(market_data, fundamental.get_complex_indicator_names(),time_period = 'ltm')
# indicators_data = fundamental.load_indicators_for(market_data, fundamental.get_annual_indicator_names())
To construct fundamental indicators (equity, EV, EBITDA, etc.) fundamental facts are used (e.g., 'us-gaap:Revenues', 'us-gaap:StockholdersEquity', etc.).
You can check the source code of the library. It presents how fundamental indicators are constructed and how data is recovered in case of errors. You can create your own algorithm, here is an example
Data provider - https://www.sec.gov/. For example, Walmart, Inc. - List Reports and Annual report 2021-03-19
Load daily stock data for the Nasdaq-100 contest
market_data = qndata.stocks.load_ndx_data(min_date="2005-01-01")
indicators_data = fundamental.load_indicators_for(market_data, indicator_names=['total_revenue'])
3. Strategy. Weights allocation
Every day, the algorithm determines how much of each asset should be in the portfolio for the next trading day. These are called the portfolio weights.
A positive weight means you'll be buying that asset, while a negative weight means you'll be selling it.
These decisions are made at the end of each day and put into effect at the beginning of the next trading day.
Fundamental data is available from 2010 onwards. However, to participate in the competition, the strategy needs to generate weights from 2006. In this context, we have decided to apply a simple "buy and hold" strategy. You are free to choose any other strategy for use.
def calculate_weights(data, fundamental_data):
"""
Calculate weights for the strategy based on a simple revenue growth check.
If the total revenue for a given time period is greater than 65 days ago, assign a weight of 1 (buy), otherwise 0.
"""
total_revenue = fundamental_data.sel(field="total_revenue")
total_revenue_days_ago = total_revenue.shift(time=65)
buy = 1
is_up = xr.where(total_revenue > total_revenue_days_ago, buy, 0)
return is_up * data.sel(field='is_liquid')
def add_buy_and_hold_enough_bid_for(data, weights_):
"""Add buy and hold condition based on the liquidity of the assets."""
time_traded = weights_.time[abs(weights_).fillna(0).sum('asset') > 0]
is_strategy_traded = len(time_traded)
if is_strategy_traded:
return xr.where(weights_.time < time_traded.min(), data.sel(field="is_liquid"), weights_)
return weights_
weights = calculate_weights(market_data, indicators_data)
weights = add_buy_and_hold_enough_bid_for(market_data, weights)
weights = qnout.clean(weights, market_data, "stocks_nasdaq100")
4. Performance estimation
Once we have our trading algorithm, we can assess its performance by calculating various statistics.
stats = qnstats.calc_stat(market_data, weights.sel(time=slice("2006-01-01", None)))
display(stats.to_pandas().tail())
These stats show how well the algorithm is doing if you started with 1M USD. They include:
- 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.
We can also plot a chart to show how profits and losses have accumulated over time.
performance = stats.to_pandas()["equity"]
qngraph.make_plot_filled(performance.index, performance, name="PnL (Equity)", type="log")
4) Submit Your strategy to the competition
To send the strategy, use the Submit button.
weights = weights.sel(time=slice("2006-01-01", None))
qnout.check(weights, market_data, "stocks_nasdaq100")
qnout.write(weights) # to participate in the competition
Strategy Guidelines
Your trading algorithm can open both short and long positions.
At any given time, your algorithm can trade all or a subset of stocks that are or were part of the NASDAQ-100 stock index. Keep in mind that this index's composition changes over time. Quantiacs provides a suitable filter function for selecting these stocks.
The Sharpe ratio of your system since January 1, 2006, must be greater than 1.
Your system must not replicate the current examples. We use a correlation filter to identify and remove duplicates in the submissions.
For more detailed rules, please visit our competition rules page.
Working with Data
Quantiacs offers historical data for major financial markets, including stocks, futures (like Bitcoin futures), and cryptocurrencies. This section provides an overview of the data:
- Stocks: Market data for NASDAQ-listed companies, past and present.
- Futures: Market data for liquid global futures contracts with various underlying assets.
- Cryptocurrencies: Market data for top cryptocurrencies by market capitalization.
Additional Datasets:
- Indexes: Daily data for various stock market indices.
- U.S. Bureau of Labor Statistics (BLS Data): Offers macroeconomic data on prices, employment, unemployment, compensation, and working conditions.
- International Monetary Fund (IMF Data): Publishes time series data on IMF lending, exchange rates, economic and financial indicators, and commodity data.
- Fundamental Data: An experimental API for additional financial data.
Potential Issues in Working with Fundamental Data:
Inconsistency in fact publication among companies:
- One company might not publish a specific fact but might provide other data from which this fact can be derived.
- Another company, on the contrary, might directly provide the fact, omitting intermediary data.
Lack of standardized formulas for indicators:
- Not all indicators have standard calculation formulas.
- For some of them, each company decides on its own which fundamental facts should be used to form the indicator.
- This can lead to the same company using different data at different times for one indicator.
- It's not accurate to compare companies based on such indicators.
Changing the strategy of indicator construction:
- When updating financial statements, a company may change the methodology or calculation formulas for indicators, introducing an element of uncertainty.
Errors and corrections in reports:
- Reports can contain errors, which are corrected later, but the initial data can distort the analysis.
Data omissions:
- Some facts might be missing in the reports.
- Companies might release their reports on different dates.
Issues with indicators based on stock prices:
- If a company conducts a stock split before publishing a report, indicators can show unexpected changes, distorting the analysis.
The current implementation of Quantiacs partially resolve these issues:
- When constructing an indicator, one formula is used for all companies, allowing them to be compared under "similar" conditions.
- If key data for calculation is missing, the algorithm tries to restore it using other facts or indicators.
- If data from the SEC gov report is missing, the algorithm tries to restore the missing information based on annual and quarterly reports, or if absent, uses average values.
- By default, the strategy for constructing indicators is over 12 months (LTM). Users can build indicators for the quarter (QF) or use annual values (AF).
You can discover the available attributes in the us-gaap taxonomy here. Introduction to Financial Statements here