Quant Journey

Quant Journey

Share this post

Quant Journey
Quant Journey
How to select ETFs - Simple Technical & Fundamental Scanner with Quant Trading Framework
Framework

How to select ETFs - Simple Technical & Fundamental Scanner with Quant Trading Framework

Jakub's avatar
Jakub
Mar 27, 2024
∙ Paid
4

Share this post

Quant Journey
Quant Journey
How to select ETFs - Simple Technical & Fundamental Scanner with Quant Trading Framework
Share

Introduction

The Exchange-Traded Funds (ETFs) have become increasingly popular among investors seeking diversification, low costs, and exposure to specific sectors or investment strategies. However, with a vast array of ETFs available, it can be challenging to identify the most promising opportunities. This is where we applied our Quant Trading Framework (QuantJP) library to come into play, providing a powerful toolkit for quantitative analysis and trading strategies.

In this post, we'll explore an ETF Scanner, which aims to identify top long and short candidates among a set of ETFs based on technical and fundamental analysis. We'll delve into the code, explaining each step and the rationale behind the various parameters and functions used.

Setting Up the ETF Strategy Class

The ETFStrategy class serves as the backbone of our ETF Scanner. It encapsulates the necessary functionality to fetch data, calculate technical and fundamental metrics, and generate buy/sell signals based on predefined conditions.

class ETFStrategy:
    def __init__(self, dc, tickers, sectors, start_date, end_date, params=None):
        self.data = None
        self.dc = dc
        self.tickers = tickers
        self.sectors = sectors
        self.start_date = start_date
        self.end_date = end_date
        self.params = params if params is not None else {}

The __init__ method initializes the class with the following parameters:

  • dc: A DataConnector object from the QuantJP library, used to fetch financial data.

  • tickers: A list of ETF tickers to be analyzed.

  • sectors: A list of corresponding sectors for each ETF ticker.

  • start_date and end_date: The date range for which data should be fetched.

  • params: A dictionary containing conditions for generating long and short signals (more on this later).

The provided code allows to create table plots as below for any ETFs - we used the following ones from the following sectors:

tickers = ['XLE', 'XLY', 'XLB', 'RSPT', 'XLK', 'XLV', 'RSPS', 'XLRE', 'RSPH', 'RSPU', 'RSPN', 'XLP', 'RSPD', 'RSPM', 'XLI', 'RSPG', 'XLF', 'XLU', 'XLC', 'RSPF', 'RSPR', 'XME', 'BLOK', 'BUZZ', 'XBI', 'PBW', 'GBTC', 'DRIV', 'XOP', 'FCG']

sectors = ['Energy', 'Consumer Dicr', 'Materials', 'TechnologyEW', 'Technology', 'Healthcare' ,'Consumer Staples EW', 'Real Estate', 'Healthcare EW', 'Utilities EW', 'Industrial EW', 'Consumer Staples', 'Consumer Discr EW', 'Materiars EW', 'Industrial', 'Energy EW', 'Financial', 'Utilities', 'Communication', 'Financial EW', 'Real Estate EW', 'Metal & Minings', 'Crypto Related', 'Social Sentiment', 'Biotech', 'Clean Energy', 'Bitcoin Trust', 'Automation & EV', 'Oil & Gas', 'Natural Gas']

Advanced filtering is also possible, and available as any combination of any Technical and Fundamental data per each ETF, e.g.

The params dictionary we have used in the code for selecting top 5 ETFs to Long, and top 5 ETFs for Short, contains conditions for both long and short signals, specified as key-value pairs, e.g.

params = {
    'long': {
        'RSI': (operator.lt, 30),
        'MACD_Hist': (operator.lt, -0.5),
        'Drawdown': (operator.lt, 0.5),
        'Volatility': (operator.lt, 0.5),
        'P/E': (operator.lt, 15)
    },
    'short': {
        'RSI': (operator.gt, 70),
        'MACD_Hist': (operator.gt, 0.5),
        'Drawdown': (operator.lt, 0.4),
        'Volatility': (operator.lt, 0.5),
        'P/E': (operator.gt, 30)
    }
}

Fetching Data

The fetch_data method retrieves the historical Open-High-Low-Close-Volume (OHLCV) data for the specified tickers and date range using the DataConnector object. It is utilizing etfs.py and function get_etf_ohlcv, for set period of time (period_starts, period_ends).

It uses batch fetching, allowing us to specify multiple tickers and retrieve them in a single request.

async def fetch_data(self):
		"""
		Fetch data for the given tickers and date range.
	
		params:
			dc: DataConnector object
			tickers: list of tickers
			start_date: start date in the format 'YYYY-MM-DD'
			end_date: end date in the format 'YYYY-MM-DD'
		"""
	
		exchanges = ["US"]*len(self.tickers)
		granularity = '1d'

		period_starts = [self.start_date]*len(self.tickers)
		period_ends = [self.end_date]*len(self.tickers)
		
		try:
			data_list = await self.dc.etfs.get_etf_ohlcv(	tickers=self.tickers, 
															exchanges=exchanges, 
												   			granularity=granularity, 
													  		period_starts=period_starts, 
															period_ends=period_ends)
		except http.client.HTTPException as e:
			if e.code == 404:
				print("Data for some tickers not found")
			else:
				raise e
		else:
			data = [df.set_index("datetime") for df in data_list]
			data = [df.assign(Ticker=ticker) for df, ticker in zip(data, self.tickers)]

			combined_data = pd.concat(data)

		return combined_data

The complete code is available to paid subscribers on GitHub. Please support me and Subscribe for more.

The fetched data is combined into a single pandas DataFrame, with each row representing a specific date and ticker, and columns containing the OHLCV values.

This post is for paid subscribers

Already a paid subscriber? Sign in
© 2025 Quant Journey with Code
Privacy ∙ Terms ∙ Collection notice
Start writingGet the app
Substack is the home for great culture

Share