Captura de pantalla 2022 09 28 a las 7.16.50

Co-fundador de Pynacle.io. Autor y creador del blog Gsnchez.com y del canal de youtube “GSNCHEZ”, es profesor en diversos centros y escuelas de negocios.
Gerard Sánchez / Pynacle.io

 

  • Tener la capacidad de seleccionar activos que cumplan con nuestros criterios es parte fundamental de una operativa efectiva y sistemática. ¿Cómo podemos crear un buen screener?
  • Artículo publicado en Hispatrading 51.

La búsqueda de oportunidades en los mercados es un camino que recorremos constantemente. Gracias a la democratización de gran parte de los datos financieros, hoy día no es difícil realizar herramientas que nos facilite esta tarea. 

En esta ocasión quiero mostraros un pequeño prototipo de screener altamente configurable que espero pueda resultar de utilidad. Para que sea replicable por cualquiera, voy a utilizar datos gratuitos de Yahoo Finance, con lo que vamos a estar limitados a realizar los análisis en temporalidad diaria.

Primero de todo, importaremos las librerías necesarias para poder crear nuestra herramienta:

import yfinance as yf

import pandas as pd

import yahoo_fin.stock_info as si

import pandas_ta as ta

import numpy as np

Descargaremos los datos de compañías del Nasdaq, algo más de 4000 empresas, estas serán las que nos sirvan para el ejemplo. 

Utilizaremos la función de tickers_nasdaq(), que hace una petición ftp a ftp.nasdaqtrader.com y se descarga los componentes actuales, para a continuación descargar los datos de velas de “yahoo finance” (gratuitos) diarios.

Eliminaremos compañías que no tengan suficiente histórico con dropna(axis=1) para el periodo que queramos.

data = yf.download(si.tickers_nasdaq(), start=’2022-12-01′).dropna(axis=1)

data.index = pd.to_datetime(data.index)

Ahora viene la parte entretenida. Aquí vamos a crear los indicadores que creamos necesarios a la hora de screenear. Tened en cuenta que no necesariamente tendríamos por qué usar OHLC. Podríamos usar datos fundamentales, outputs de modelos, etc… todo depende de lo que dispongamos. Para este ejemplo se hará algo que todo el mundo pueda replicar de forma gratuita y sin demasiada dificultad.

Os propongo una batería de indicadores clásicos de análisis técnico. Analizar los gaps, la diferencia entre el máximo y el mínimo diarios y la apertura del día/cierre (amplitud), el rate of change o retornos, el volumen relativo, la distancia con respecto a máximos anuales en porcentaje, días consecutivos de caída (en este caso 2), la volatilidad, medias móviles (periodos a escoger), el internal bar strength, el precio típico, y el rsi en varias longitudes.

gap = data[‘Open’]/data[‘Close’].shift(1)

high_low = data[‘High’]/data[‘Low’]

open_close = data[‘Open’]/data[‘Close’]

roc = data[‘Close’].pct_change()

volume_diff = data[‘Volume’]/data[‘Volume’].rolling(10).mean()

annual_max_dist = (data[‘Close’]/data[‘Close’].rolling(252).max() – 1) * 100

consecutive_2_falling_days = ((roc < roc.shift(1)) & (roc.shift(1) < roc.shift(2))).astype(int)

volatility =  roc.rolling(window=252).std() * np.sqrt(252)

means = [data[‘Close’][data[‘Close’].columns].apply(lambda x: ta.sma(x, length=l)) for l in range(10,101,10)]ibs = (data[‘Close’] – data[‘Low’]) / (data[‘High’] – data[‘Low’])

ibs = (data[‘Close’] – data[‘Low’]) / (data[‘High’] – data[‘Low’])

typical_price = (data[‘High’] + data[‘Low’] + data[‘Close’]) / 3

rsi = [data[‘Close’][data[‘Close’].columns].apply(lambda x: ta.rsi(x, length=l)) for l in [2,10,14]]

Todo esto lo calculamos en distintas variables tipo DataFrame que van a tener el mismo tamaño de filas y columnas, por lo que luego, simplemente comparando valores, tendremos una forma fácil y amigable de ver cuándo se cumplen nuestros criterios en función de si los valores son “True” (verdaderos) o “False” (falsos).

Ahora toca ser hábiles con los filtros. Podemos jugar con todo lo que hemos calculado.

mask = ((gap > 1.5) & (roc > 1.5) & (volume_diff > 2)).astype(int).loc[data.index.year >= 2024]

mask2 = ((consecutive_2_falling_days == 1) & (annual_max_dist > -10) & (volatility > 1.5) & (gap > 1)).astype(int).loc[data.index.year >= 2024]

Por ejemplo, en el primer filtro llamado “mask”, buscamos compañías con un gap superior al 50%, con un retorno sobre el día anterior del 150% y un volumen relativo 2 veces superior a la media de los últimos 10 días. Estamos buscando acciones que acaben de “despertar”, con grandes revalorizaciones y probablemente de pequeña capitalización.

En el segundo filtro buscamos compañías que lleven 2 días de caídas consecutivas, que tengan una distancia sobre máximos del 10% o superior y con una volatilidad anualizada de 1.5 o superior.

Imaginación al poder. Os sugiero que intentéis ampliar la cantidad de indicadores y filtros, es muy divertido y puede ser muy útil.

Para acabar, nos falta poder visualizar qué compañías cumplen los criterios escogidos. Mostramos qué compañías cumplen los criterios para el último año en periodos diarios:

selected_stocks_by_date = {}

for date in mask2.index:

    selected_tickers = mask2.loc[date][mask2.loc[date] == 1].index.tolist()

    selected_stocks_by_date[date.strftime(‘%Y-%m-%d’)] = selected_tickers

for date, tickers in selected_stocks_by_date.items():

    print(f»On {date}, selected tickers: {tickers}»)

Ahora podemos pasar a una fase de análisis posterior, conociendo qué empresas vamos a tener en nuestra “watchlist”.

pastedGraphic.png