├── requirements.txt ├── README.md ├── screener.py └── screener_csv.py /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavierCastilloGuillen/Market_Screener/HEAD/requirements.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Important 2 | [ES] & [EN] 3 | 4 | This repository contains a script we used in a educational class (screener_csv.py) and the screener.py itself. 5 | 6 | The screener_csv.py has some functions which are not essential/efficient (storing data, os functions, etc.) but comes from a learning environment. 7 | 8 | For screening and further usage go to screener.py which is computationally more efficient. 9 | 10 | Although it uses stocks, it works for crypto, currencies, etc just by changing data source or adding specific functions. As said, is the skeleton. 11 | 12 | # Screener Técnico 13 | 14 | [ES] 15 | Screener técnico. 16 | 17 | Video de ayuda en youtube: https://www.youtube.com/watch?v=Dr51rhwa2TY 18 | 19 | El proyecto tiene la intención de ser una iniciación al screener esencial técnico. Viene derivado de una clase (propósito educacional). 20 | 21 | El ejemplo usado es adaptado al mercado de Argentina. Aunque escalable a distintos mercados (USA - EUROPA - Crypto - Forex, etc). Tanto por 22 | la propia librería investpy, como manteniendo las funciones y utilizando otras librerías. 23 | 24 | Las librerías principales utilizadas serán: 25 | 26 | TA - Para análisis técnico : https://technical-analysis-library-in-python.readthedocs.io/en/latest/index.html 27 | 28 | PANDAS - Para el procesado esencial de datos: https://pandas.pydata.org/ 29 | 30 | INVESTPY - Para el acceso a datos de mercado : https://pypi.org/project/investpy/ 31 | 32 | Espero que te sirva, te ahorre tiempo en tu análisis y complemente tu visión de mercado. 33 | A medida que tenga tiempo iré complementando con algún recurso más. 34 | 35 | ¡Un saludo! 36 | 37 | # Technical Screener 38 | [EN] 39 | 40 | Technical Screener. 41 | 42 | Help video on youtube: https://www.youtube.com/watch?v=ewr_2nkHsxo 43 | 44 | The project is intended to be an introduction to the essential technical screener. The project is coming from a lesson, therefore has educational purpose. 45 | 46 | In this case adapted to the Argentina market. Although scalable to different markets (US - EUROPE - Crypto - FX, etc) Using the same library or others and 47 | keeping the main functions. 48 | 49 | The main libraries used will be: 50 | 51 | TA - For technical analysis: https://technical-analysis-library-in-python.readthedocs.io/en/latest/index.html 52 | 53 | PANDAS - For essential data processing: https://pandas.pydata.org/ 54 | 55 | INVESTPY - For access to market data: https://pypi.org/project/investpy/ 56 | 57 | I hope it helps you, saves you time in your analysis and complements your market vision. 58 | As soon I have time I will supplement with some more resources. 59 | 60 | All the best! -------------------------------------------------------------------------------- /screener.py: -------------------------------------------------------------------------------- 1 | """ 2 | This screener the skeleton for a basic market screener. It will help you with: 3 | 4 | 1. Market Technical Screening 5 | 2. Market Alerts Signals 6 | 3. Further Analysis you might require 7 | 8 | Main difference is on this script you will NOT BE STORING data on your local machine. With it 9 | pros and cons. Because it doesn't store data is computationally more efficient and fast. 10 | 11 | For the example, the output of the screener is a prompt print with 12 | the list of tickers stored on the list variables. You can use this lists for further analyisis or 13 | add more. 14 | 15 | Example on the script: 16 | 17 | country ='Argentina' <- Change this country for the required one 18 | days_back = 120 <- Data gathering from this day. Will impact on the indicators (200SMA won't work on 120 days of data ;-) ) 19 | stocks = investpy.get_stocks_overview(country, n_results=1000) <- n_results=1000 For wider markets go for bigger results. 20 | 21 | 22 | """ 23 | 24 | import investpy 25 | import time 26 | from datetime import datetime, timedelta 27 | import warnings 28 | warnings.filterwarnings('ignore') 29 | 30 | # List to store variables after screener is launched 31 | 32 | b_out = [] 33 | cons = [] 34 | mcd_up = [] 35 | mcd_up0 = [] 36 | mcd_d = [] 37 | mcd_d0 = [] 38 | bb_up = [] 39 | already_bb_up = [] 40 | bb_d = [] 41 | already_bb_d = [] 42 | rsi_d = [] 43 | on_rsi_d = [] 44 | on_rsi_up = [] 45 | rsi_up = [] 46 | rsi_bf_d = [] 47 | rsi_bf_up = [] 48 | 49 | # Functions area. Create here as many functions as required for the screener. 50 | # Some basic technical examples shown (MACD, RSI, BB) and some price actions Consolidation and Breakout. 51 | 52 | 53 | def MACD_signal_up(df): 54 | """ 55 | This Function will analyze the SIGNAL UP on the MACD of the asset 56 | Asset MACD on Crossover SIGNAL and Asset MACD Crossover below 0 57 | 58 | """ 59 | from ta.trend import MACD 60 | indicator_macd = MACD(df['Adj Close']) 61 | df['MACD'] = indicator_macd.macd() 62 | df['Signal']= indicator_macd.macd_signal() 63 | df['MACD Histogram']= indicator_macd.macd_diff() 64 | df['Below_0_Crossover_MACD_Signal'] = False 65 | df['Simple_Crossover_MACD_Signal'] = False 66 | 67 | # MACD Crossover logics 68 | if (df[-2:]['MACD'].values[0] <= df[-1:]['MACD'].values[0]) and (df[-2:]['MACD'].values[0] <= df[-2:]['Signal'].values[0]) and (df[-1:]['MACD'].values[0]>=df[-1:]['Signal'].values[0]): 69 | 70 | 71 | # MACD crossover AND Below 0 72 | if (df[-2:]['MACD'].values[0] <= df[-1:]['MACD'].values[0]) and (df[-2:]['MACD'].values[0] <= df[-2:]['Signal'].values[0]) and (df[-1:]['MACD'].values[0]>=df[-1:]['Signal'].values[0]) and df[-1:]['MACD'].values[0]<= 0: 73 | mcd_up0.append(symbol) 74 | df['Below_0_Crossover_MACD_Signal'][-1] = True 75 | else: 76 | mcd_up.append(symbol) 77 | df['Simple_Crossover_MACD_Signal'][-1] = True 78 | df['Below_0_Crossover_MACD_Signal'][-1] = False 79 | return True 80 | return False 81 | 82 | def MACD_signal_down(df): 83 | """ 84 | This Function will analyze the SIGNAL DOWN on the MACD asset 85 | Asset MACD on Crossunder SIGNAL and Asset MACD Crossunder above 0 86 | 87 | """ 88 | from ta.trend import MACD 89 | indicator_macd = MACD(df['Adj Close']) 90 | df['MACD'] = indicator_macd.macd() 91 | df['Signal']= indicator_macd.macd_signal() 92 | df['MACD Histogram']= indicator_macd.macd_diff() 93 | df['Simple_Crossdown_MACD_Signal'] = False 94 | df['Above_0_Crossunder_MACD_Signal'] = False 95 | 96 | # MACD croosunder 97 | if (df[-2:]['MACD'].values[0] >= df[-1:]['MACD'].values[0]) and (df[-2:]['MACD'].values[0] >= df[-2:]['Signal'].values[0]) and (df[-1:]['MACD'].values[0]<=df[-1:]['Signal'].values[0]): 98 | # MACD crossunder AND above 0 99 | if (df[-2:]['MACD'].values[0] >= df[-1:]['MACD'].values[0]) and (df[-2:]['MACD'].values[0] >= df[-2:]['Signal'].values[0]) and (df[-1:]['MACD'].values[0]<=df[-1:]['Signal'].values[0]) and df[-1:]['MACD'].values[0]>= 0: 100 | mcd_d0.append(symbol) 101 | df['Above_0_Crossunder_MACD_Signal'][-1] = True 102 | 103 | else: 104 | mcd_d.append(symbol) 105 | df['Simple_Crossdown_MACD_Signal'][-1] = True 106 | df['Above_0_Crossunder_MACD_Signal'][-1] = False 107 | return True 108 | return False 109 | 110 | def Bollinger_signal_up(df, window=20, window_dev=2): 111 | """ 112 | This Function will analyze the Bollinger UP on the asset 113 | Bollinger Up signal, Asset already above upper Bollinger Band 114 | 115 | """ 116 | 117 | from ta.volatility import BollingerBands 118 | indicator_bb = BollingerBands(df["Adj Close"], 20, 2) 119 | df['bb_bbm'] = indicator_bb.bollinger_mavg() 120 | df['bb_bbh'] = indicator_bb.bollinger_hband() 121 | df['bb_bbl'] = indicator_bb.bollinger_lband() 122 | df['bb_bbhi'] = indicator_bb.bollinger_hband_indicator() 123 | df['bb_bbli'] = indicator_bb.bollinger_lband_indicator() 124 | df['Boll_UP'] = False 125 | df['Boll_UP2']= False 126 | 127 | # Asset on Upper Bollinger Band Signal 128 | if (df[-2:]['bb_bbhi'].values[0] == 0) and (df[-1:]['bb_bbhi'].values[0] == 1): 129 | df['Boll_UP'][-1] = True 130 | bb_up.append(symbol) 131 | return True 132 | 133 | # Asset already avobe Upper Bollinger Band 134 | elif (df[-2:]['bb_bbhi'].values[0] == 1) and (df[-1:]['bb_bbhi'].values[0] == 1): 135 | df['Boll_UP2'][-1] = True 136 | already_bb_up.append(symbol) 137 | return True 138 | return False 139 | 140 | def Bollinger_signal_down(df, window=20, window_dev=2): 141 | """ 142 | This Function will analyze the Bollinger DOWN on the asset 143 | Bollinger down signal, Asset already below lower Bollinger Band 144 | 145 | """ 146 | 147 | from ta.volatility import BollingerBands 148 | indicator_bb = BollingerBands(df["Adj Close"], 20, 2) 149 | df['bb_bbm'] = indicator_bb.bollinger_mavg() 150 | df['bb_bbh'] = indicator_bb.bollinger_hband() 151 | df['bb_bbl'] = indicator_bb.bollinger_lband() 152 | df['bb_bbhi'] = indicator_bb.bollinger_hband_indicator() 153 | df['bb_bbli'] = indicator_bb.bollinger_lband_indicator() 154 | df['Boll_Down'] = False 155 | df['Boll_Down2']= False 156 | 157 | # Asset on Signal Lower Bollinger Band 158 | if (df[-2:]['bb_bbli'].values[0] == 0) and (df[-1:]['bb_bbli'].values[0] == 1): 159 | bb_d.append(symbol) 160 | df['Boll_Down'][-1]= True 161 | return True 162 | 163 | # Asset already below lower Bollinger 164 | elif (df[-2:]['bb_bbli'].values[0] == 1) and (df[-1:]['bb_bbli'].values[0] == 1): 165 | already_bb_d.append(symbol) 166 | df['Boll_Down2'][-1]= True 167 | return True 168 | return False 169 | 170 | def RSI_signal_up(df, window = 14): 171 | """ 172 | This Function will analyze the SIGNAL UP on the RSI asset 173 | Overbought signal, Asset already Overbought and asset back to range from Overbought 174 | 175 | """ 176 | 177 | from ta.momentum import RSIIndicator 178 | indicator_rsi= RSIIndicator(df['Adj Close'], window= 14) 179 | df['RSI'] = indicator_rsi.rsi() 180 | df['RSI_Overbought'] = False 181 | 182 | # Asset back to range 70-30 from Overbought 183 | if (df[-2:]['RSI'].values[0] >= 70) and (df[-1:]['RSI'].values[0] <= 70): 184 | rsi_bf_up.append(symbol) 185 | 186 | # Asset on RSI > 70 187 | if (df[-1:]['RSI'].values[0] >= 70): 188 | on_rsi_up.append(symbol) 189 | df['RSI_Overbought'][-1] = True 190 | 191 | # RSI Overbought SIGNAL 192 | if (df[-2:]['RSI'].values[0] <= 70) and (df[-1:]['RSI'].values[0] >= 70): 193 | rsi_up.append(symbol) 194 | return True 195 | return False 196 | 197 | def RSI_signal_down(df, window= 14): 198 | """ 199 | This Function will analyze the SIGNAL DOWN on the RSI asset 200 | Oversold signal, Asset already oversold and asset back to range from oversold 201 | 202 | """ 203 | 204 | from ta.momentum import RSIIndicator 205 | indicator_rsi= RSIIndicator(df['Adj Close'], window= 14) 206 | df['RSI'] = indicator_rsi.rsi() 207 | df['RSI_Oversold'] = False 208 | 209 | # Asset back to range 30-70 from Oversold 210 | if (df[-2:]['RSI'].values[0] <= 30) and (df[-1:]['RSI'].values[0] >= 30): 211 | rsi_bf_d.append(symbol) 212 | 213 | # Asset on RSI < 30 214 | if (df[-1:]['RSI'].values[0] <= 30): 215 | on_rsi_d.append(symbol) 216 | df['RSI_Oversold'][-1] = True 217 | 218 | # RSI just crossed down SIGNAL 219 | if (df[-2:]['RSI'].values[0] >= 30) and (df[-1:]['RSI'].values[0] <= 30): 220 | rsi_d.append(symbol) 221 | return True 222 | return False 223 | 224 | def consolidating_signal(df, perc = 3.5): 225 | """ 226 | This Function will analyze the asset is consolidating within the perc range. 227 | Ex: perc =3.5 means the closing price within the last 15 sessions, hasn't changed 228 | further than 3.5% 229 | 230 | """ 231 | range_of_candlesticks= df[-15:] 232 | max_close_price = range_of_candlesticks['Adj Close'].max() 233 | min_close_price = range_of_candlesticks['Adj Close'].min() 234 | threshold_detection = 1 - (perc / 100) 235 | if min_close_price > (max_close_price * threshold_detection): 236 | cons.append(symbol) 237 | return True 238 | return False 239 | 240 | def breaking_out_signal(df, perc=1,): 241 | """ 242 | This Function will analyze the an asset which is coming out from a consolidation 243 | period. 244 | 245 | [perc] = will be the threshold in % for the closing price to determinate if the asset is 246 | under consolidation. 247 | 248 | On the example perc = 1, the asset will be closing within 1% range on the last 15 sessions and then 249 | on current candle is breaking out. 250 | 251 | """ 252 | last_close = df[-1:]['Adj Close'].values[0] 253 | if consolidating_signal(df[:-1], perc = perc): 254 | recent_close = df[-16:-1] 255 | if last_close > recent_close['Adj Close'].max(): 256 | b_out.append(symbol) 257 | return True 258 | return False 259 | 260 | 261 | # Screener parameters (country, days_back and n_results) depending on indicators and market must be changed. 262 | 263 | country ='Argentina' 264 | days_back = 120 265 | today = datetime.now() 266 | start = today -timedelta(days_back) 267 | today = datetime.strftime(today, '%d/%m/%Y') 268 | start = datetime.strftime(start, '%d/%m/%Y') 269 | stocks = investpy.get_stocks_overview(country, n_results=1000) 270 | stocks = stocks.drop_duplicates(subset='symbol') 271 | 272 | # Dates 273 | today = datetime.now() 274 | start = today -timedelta(days_back) 275 | today = datetime.strftime(today, '%d/%m/%Y') 276 | start = datetime.strftime(start, '%d/%m/%Y') 277 | 278 | # Screener launch count variable added to while loop limit if necessary to control the API-HTTP call/requests. 279 | # Uncomment while and indent for if necessary. 280 | 281 | count = 0 282 | # while count < (n) : 283 | for symbol in stocks['symbol']: 284 | try: 285 | # count += 1 286 | df = investpy.get_stock_historical_data(stock=symbol,country=country,from_date=f'{start}', to_date=f'{today}') 287 | time.sleep(0.25) 288 | df= df.rename(columns={"Close": "Adj Close"}) 289 | if breaking_out_signal(df, 3): 290 | pass 291 | if consolidating_signal(df, perc=2): 292 | pass 293 | if RSI_signal_up(df): 294 | pass 295 | if RSI_signal_down(df): 296 | pass 297 | if MACD_signal_up(df): 298 | pass 299 | if MACD_signal_down(df): 300 | pass 301 | if Bollinger_signal_up(df): 302 | pass 303 | if Bollinger_signal_down(df): 304 | pass 305 | 306 | except Exception as e: 307 | print(f'No data on {symbol}') 308 | print(e) 309 | 310 | 311 | # OUTPUT => For the example just a print, but you've got the tickers stored on the variables to do further analysis 312 | 313 | 314 | print(f'--------- GENERAL MARKET SCREENER in {country} for {len(stocks)} assets: data analyzed from {start} until {today} --------\n') 315 | print('--- BOLLINGER ANALYSIS --- \n') 316 | print(f'The stocks on SIGNAL BOLLINGER UP are:\n==> {bb_up}\n') 317 | print(f'The stocks are already in BOLLINGER UP:\n==> {already_bb_up}\n') 318 | print(f'The stocks on SIGNAL BOLLINGER DOWN are:\n==> {bb_d}\n') 319 | print(f'The stocks are already in BOLLINGER_DOWN:\n==> {already_bb_d}\n') 320 | print('--- MACD ANALYSIS --- \n') 321 | print(f'The stocks on MACD SIGNAL UP are:\n==> {mcd_up}\n') 322 | print(f'The stocks on MACD SIGNAL UP BELOW 0 are:\n==> {mcd_up0}\n') 323 | print(f'The stocks on MACD SIGNAL DOWN are:\n==> {mcd_d} \n') 324 | print(f'The stocks on MACD SIGNAL DOWN above 0 are:\n==> {mcd_d0}\n') 325 | print('--- RSI ANALYSIS --- \n') 326 | print(f'The stocks on OVERBOUGHT SIGNAL [RSI] are:\n==> {rsi_up}\n') 327 | print(f'The stocks on OVERSOLD SIGNAL [RSI] are:\n==> {rsi_d}\n') 328 | print(f'The stocks went to RANGE from OVERSOLD are:\n==> {rsi_bf_d}\n') 329 | print(f'The stocks went to RANGE from OVERBOUGHT are:\n==> {rsi_bf_up}\n') 330 | print(f'The stocks on OVERBOUGHT [RSI] are:\n==> {on_rsi_up}\n') 331 | print(f'The stocks on OVERSOLD [RSI] are:\n==> {on_rsi_d}\n') 332 | print('--- PRICE ACTION ANALYSIS --- \n') 333 | print(f'The stocks on CONSOLIDATION are:\n==> {cons}\n') 334 | print(f'The stocks on BREAKOUT are:\n==> {b_out}\n') 335 | 336 | 337 | 338 | -------------------------------------------------------------------------------- /screener_csv.py: -------------------------------------------------------------------------------- 1 | """ 2 | Note: This is a script used as example for the class: 3 | - For computationally efficient script USE screener.py on 4 | this repository. 5 | 6 | Which topics we will work here: 7 | 1. System process (make folder - delete folder with data) 8 | 2. Data process (gather - cleasing - store in .csv - read .csv) 9 | 3. Dates introduction (TimeSeries) 10 | 4. List creation and append values 11 | 5. Function creations (Technical screener) 12 | 6. Conditional statments (screening process) 13 | 7. Usage of some librarires (investpy / yfinance - pandas (essentials) - TA) 14 | 8. Try/Except and introduction to errors. 15 | 16 | This screener the skeleton for a basic market screener. It will help you with: 17 | 18 | 1. Market Technical Screening 19 | 2. Market Alerts Signals 20 | 3. Further Analysis you might require 21 | 22 | Main difference is on this script you will NOT BE STORING data on your local machine. With it 23 | pros and cons. Because it doesn't store data is computationally more efficient and fast. 24 | 25 | For the example, the output of the screener is a prompt print with 26 | the list of tickers stored on the list variables. You can use this lists for further analyisis or 27 | add more. 28 | 29 | Example on the script: 30 | 31 | country ='Argentina' <- Change this country for the required one 32 | days_back = 120 <- Data gathering from this day. Will impact on the indicators (200SMA won't work on 120 days of data ;-) ) 33 | stocks = investpy.get_stocks_overview(country, n_results=1000) <- n_results=1000 For wider markets go for bigger results. 34 | 35 | """ 36 | 37 | 38 | 39 | import pandas as pd 40 | import os 41 | import shutil 42 | import investpy 43 | import time 44 | from datetime import datetime, timedelta 45 | import warnings 46 | warnings.filterwarnings('ignore') 47 | 48 | 49 | # 1. Creamos la carpeta donde se almacenan temporalmente los .csv 50 | # 1. We will create the path were our temporary data will be stored in .csv 51 | if not os.path.exists('data'): 52 | os.mkdir('data') 53 | 54 | # 2. Para este ejemplo, leeremos los activos de la propia base de datos. 55 | # 2. On this particular script, we will read our data from the database. 56 | 57 | country ='Argentina' 58 | days_back = 120 59 | today = datetime.now() 60 | start = today -timedelta(days_back) 61 | today = datetime.strftime(today, '%d/%m/%Y') 62 | start = datetime.strftime(start, '%d/%m/%Y') 63 | stocks = investpy.get_stocks_overview(country, n_results=1000) 64 | stocks = stocks.drop_duplicates(subset='symbol') 65 | 66 | # 3. Seleccionamos la ruta en la que estará nuestro proyecto con el final en \data puesto que ahí se almacenará la información. 67 | # 3. Select the path in where our project will be stored. Remember end on \data puesto que ahí se almacenará la información. 68 | path = (r"data") 69 | 70 | # 4. Manejo de fechas. Recordad modificar days_back si necesitamos indicadores con más longitud de datos (mm200, etc.) 71 | # 4. Date handling. Remember to modify days_back in case we need indicators with longer data (sma200, etc.) 72 | 73 | today = datetime.now() 74 | start = today -timedelta(days_back) 75 | today = datetime.strftime(today, '%d/%m/%Y') 76 | start = datetime.strftime(start, '%d/%m/%Y') 77 | 78 | # 5. Proceso de solicitud de datos a través de la librería investpy para el documento especies.csv columna (Ticker) 79 | # 5. Data gathering process through investpy library for especies.csv on (Ticker) column 80 | 81 | count = 0 82 | for ticker in stocks['symbol']: 83 | try: 84 | count += 1 85 | df = investpy.get_stock_historical_data(stock=ticker,country=country,from_date=f'{start}', to_date=f'{today}') 86 | df= df.rename(columns={"Close": "Adj Close"}) 87 | # print(f'Analyzing {count}.....{ticker}') 88 | # print(df.info()) <== To see what you're getting 89 | df.to_csv(fr'data/{ticker}.csv') 90 | time.sleep(0.25) 91 | except Exception as e: 92 | print(e) 93 | print(f'No data on {ticker}') 94 | 95 | 96 | # 6. A continuación definiremos algún ejemplo de funciones técnicas utilizando 97 | # la librería TA para análisis técnico. Modificar al gusto de cada usuario. 98 | # Además, crearemos las listas para añadir nuestros ticker que cumplan con el screener. 99 | # 6. Following we will write some functions in order to retreive the technical 100 | # indicator from TA, add it to our dataframe and define logics for the signals. 101 | # On top, we will create the list to fulfill with the tickers filtered. 102 | 103 | b_out = [] 104 | cons = [] 105 | mcd_up = [] 106 | mcd_up0 = [] 107 | mcd_d = [] 108 | mcd_d0 = [] 109 | bb_up = [] 110 | already_bb_up = [] 111 | bb_d = [] 112 | already_bb_d = [] 113 | rsi_d = [] 114 | on_rsi_d = [] 115 | on_rsi_up = [] 116 | rsi_up = [] 117 | rsi_bf_d = [] 118 | rsi_bf_up = [] 119 | 120 | def MACD_signal_up(df): 121 | """ 122 | This Function will analyze the SIGNAL UP on the MACD of the asset 123 | Asset MACD on Crossover SIGNAL and Asset MACD Crossover below 0 124 | 125 | """ 126 | from ta.trend import MACD 127 | indicator_macd = MACD(df['Adj Close']) 128 | df['MACD'] = indicator_macd.macd() 129 | df['Signal']= indicator_macd.macd_signal() 130 | df['MACD Histogram']= indicator_macd.macd_diff() 131 | df['Below_0_Crossover_MACD_Signal'] = False 132 | df['Simple_Crossover_MACD_Signal'] = False 133 | 134 | # MACD Crossover logics 135 | if (df[-2:]['MACD'].values[0] <= df[-1:]['MACD'].values[0]) and (df[-2:]['MACD'].values[0] <= df[-2:]['Signal'].values[0]) and (df[-1:]['MACD'].values[0]>=df[-1:]['Signal'].values[0]): 136 | 137 | 138 | # MACD crossover AND Below 0 139 | if (df[-2:]['MACD'].values[0] <= df[-1:]['MACD'].values[0]) and (df[-2:]['MACD'].values[0] <= df[-2:]['Signal'].values[0]) and (df[-1:]['MACD'].values[0]>=df[-1:]['Signal'].values[0]) and df[-1:]['MACD'].values[0]<= 0: 140 | mcd_up0.append(symbol) 141 | df['Below_0_Crossover_MACD_Signal'][-1] = True 142 | else: 143 | mcd_up.append(symbol) 144 | df['Simple_Crossover_MACD_Signal'][-1] = True 145 | df['Below_0_Crossover_MACD_Signal'][-1] = False 146 | return True 147 | return False 148 | 149 | def MACD_signal_down(df): 150 | """ 151 | This Function will analyze the SIGNAL DOWN on the MACD asset 152 | Asset MACD on Crossunder SIGNAL and Asset MACD Crossunder above 0 153 | 154 | """ 155 | from ta.trend import MACD 156 | indicator_macd = MACD(df['Adj Close']) 157 | df['MACD'] = indicator_macd.macd() 158 | df['Signal']= indicator_macd.macd_signal() 159 | df['MACD Histogram']= indicator_macd.macd_diff() 160 | df['Simple_Crossdown_MACD_Signal'] = False 161 | df['Above_0_Crossunder_MACD_Signal'] = False 162 | 163 | # MACD croosunder 164 | if (df[-2:]['MACD'].values[0] >= df[-1:]['MACD'].values[0]) and (df[-2:]['MACD'].values[0] >= df[-2:]['Signal'].values[0]) and (df[-1:]['MACD'].values[0]<=df[-1:]['Signal'].values[0]): 165 | # MACD crossunder AND above 0 166 | if (df[-2:]['MACD'].values[0] >= df[-1:]['MACD'].values[0]) and (df[-2:]['MACD'].values[0] >= df[-2:]['Signal'].values[0]) and (df[-1:]['MACD'].values[0]<=df[-1:]['Signal'].values[0]) and df[-1:]['MACD'].values[0]>= 0: 167 | mcd_d0.append(symbol) 168 | df['Above_0_Crossunder_MACD_Signal'][-1] = True 169 | 170 | else: 171 | mcd_d.append(symbol) 172 | df['Simple_Crossdown_MACD_Signal'][-1] = True 173 | df['Above_0_Crossunder_MACD_Signal'][-1] = False 174 | return True 175 | return False 176 | 177 | def Bollinger_signal_up(df, window=20, window_dev=2): 178 | """ 179 | This Function will analyze the Bollinger UP on the asset 180 | Bollinger Up signal, Asset already above upper Bollinger Band 181 | 182 | """ 183 | 184 | from ta.volatility import BollingerBands 185 | indicator_bb = BollingerBands(df["Adj Close"], 20, 2) 186 | df['bb_bbm'] = indicator_bb.bollinger_mavg() 187 | df['bb_bbh'] = indicator_bb.bollinger_hband() 188 | df['bb_bbl'] = indicator_bb.bollinger_lband() 189 | df['bb_bbhi'] = indicator_bb.bollinger_hband_indicator() 190 | df['bb_bbli'] = indicator_bb.bollinger_lband_indicator() 191 | df['Boll_UP'] = False 192 | df['Boll_UP2']= False 193 | 194 | # Asset on Upper Bollinger Band Signal 195 | if (df[-2:]['bb_bbhi'].values[0] == 0) and (df[-1:]['bb_bbhi'].values[0] == 1): 196 | df['Boll_UP'][-1] = True 197 | bb_up.append(symbol) 198 | return True 199 | 200 | # Asset already avobe Upper Bollinger Band 201 | elif (df[-2:]['bb_bbhi'].values[0] == 1) and (df[-1:]['bb_bbhi'].values[0] == 1): 202 | df['Boll_UP2'][-1] = True 203 | already_bb_up.append(symbol) 204 | return True 205 | return False 206 | 207 | def Bollinger_signal_down(df, window=20, window_dev=2): 208 | """ 209 | This Function will analyze the Bollinger DOWN on the asset 210 | Bollinger down signal, Asset already below lower Bollinger Band 211 | 212 | """ 213 | 214 | from ta.volatility import BollingerBands 215 | indicator_bb = BollingerBands(df["Adj Close"], 20, 2) 216 | df['bb_bbm'] = indicator_bb.bollinger_mavg() 217 | df['bb_bbh'] = indicator_bb.bollinger_hband() 218 | df['bb_bbl'] = indicator_bb.bollinger_lband() 219 | df['bb_bbhi'] = indicator_bb.bollinger_hband_indicator() 220 | df['bb_bbli'] = indicator_bb.bollinger_lband_indicator() 221 | df['Boll_Down'] = False 222 | df['Boll_Down2']= False 223 | 224 | # Asset on Signal Lower Bollinger Band 225 | if (df[-2:]['bb_bbli'].values[0] == 0) and (df[-1:]['bb_bbli'].values[0] == 1): 226 | bb_d.append(symbol) 227 | df['Boll_Down'][-1]= True 228 | return True 229 | 230 | # Asset already below lower Bollinger 231 | elif (df[-2:]['bb_bbli'].values[0] == 1) and (df[-1:]['bb_bbli'].values[0] == 1): 232 | already_bb_d.append(symbol) 233 | df['Boll_Down2'][-1]= True 234 | return True 235 | return False 236 | 237 | def RSI_signal_up(df, window = 14): 238 | """ 239 | This Function will analyze the SIGNAL UP on the RSI asset 240 | Overbought signal, Asset already Overbought and asset back to range from Overbought 241 | 242 | """ 243 | 244 | from ta.momentum import RSIIndicator 245 | indicator_rsi= RSIIndicator(df['Adj Close'], window= 14) 246 | df['RSI'] = indicator_rsi.rsi() 247 | df['RSI_Overbought'] = False 248 | 249 | # Asset back to range 70-30 from Overbought 250 | if (df[-2:]['RSI'].values[0] >= 70) and (df[-1:]['RSI'].values[0] <= 70): 251 | rsi_bf_up.append(symbol) 252 | 253 | # Asset on RSI > 70 254 | if (df[-1:]['RSI'].values[0] >= 70): 255 | on_rsi_up.append(symbol) 256 | df['RSI_Overbought'][-1] = True 257 | 258 | # RSI Overbought SIGNAL 259 | if (df[-2:]['RSI'].values[0] <= 70) and (df[-1:]['RSI'].values[0] >= 70): 260 | rsi_up.append(symbol) 261 | return True 262 | return False 263 | 264 | def RSI_signal_down(df, window= 14): 265 | """ 266 | This Function will analyze the SIGNAL DOWN on the RSI asset 267 | Oversold signal, Asset already oversold and asset back to range from oversold 268 | 269 | """ 270 | 271 | from ta.momentum import RSIIndicator 272 | indicator_rsi= RSIIndicator(df['Adj Close'], window= 14) 273 | df['RSI'] = indicator_rsi.rsi() 274 | df['RSI_Oversold'] = False 275 | 276 | # Asset back to range 30-70 from Oversold 277 | if (df[-2:]['RSI'].values[0] <= 30) and (df[-1:]['RSI'].values[0] >= 30): 278 | rsi_bf_d.append(symbol) 279 | 280 | # Asset on RSI < 30 281 | if (df[-1:]['RSI'].values[0] <= 30): 282 | on_rsi_d.append(symbol) 283 | df['RSI_Oversold'][-1] = True 284 | 285 | # RSI just crossed down SIGNAL 286 | if (df[-2:]['RSI'].values[0] >= 30) and (df[-1:]['RSI'].values[0] <= 30): 287 | rsi_d.append(symbol) 288 | return True 289 | return False 290 | 291 | 292 | # Price action Functions (candlesticks patterns, consolidations, breakouts etc.) 293 | 294 | def consolidating_signal(df, perc = 3.5): 295 | """ 296 | This Function will analyze the asset is consolidating within the perc range. 297 | Ex: perc =3.5 means the closing price within the last 15 sessions, hasn't changed 298 | further than 3.5% 299 | 300 | """ 301 | range_of_candlesticks= df[-15:] 302 | max_close_price = range_of_candlesticks['Adj Close'].max() 303 | min_close_price = range_of_candlesticks['Adj Close'].min() 304 | threshold_detection = 1 - (perc / 100) 305 | if min_close_price > (max_close_price * threshold_detection): 306 | cons.append(symbol) 307 | return True 308 | return False 309 | 310 | def breaking_out_signal(df, perc=1,): 311 | """ 312 | This Function will analyze the an asset which is coming out from a consolidation 313 | period. 314 | 315 | [perc] = will be the threshold in % for the closing price to determinate if the asset is 316 | under consolidation. 317 | 318 | On the example perc = 1, the asset will be closing within 1% range on the last 15 sessions and then 319 | on current candle is breaking out. 320 | 321 | """ 322 | last_close = df[-1:]['Adj Close'].values[0] 323 | if consolidating_signal(df[:-1], perc = perc): 324 | recent_close = df[-16:-1] 325 | if last_close > recent_close['Adj Close'].max(): 326 | b_out.append(symbol) 327 | return True 328 | return False 329 | 330 | 331 | # START SCREENER with our data stored in .CSV format 332 | print(f'--------- GENERAL MARKET SCREENER in {country} for {len(stocks)} assets: data analyzed from {start} until {today} --------\n') 333 | 334 | for filename in os.listdir(path): 335 | df = pd.read_csv(path+f'\{filename}') 336 | symbol = filename.split(".")[0] 337 | if breaking_out_signal(df, 3): 338 | pass 339 | if consolidating_signal(df, perc=2): 340 | pass 341 | if RSI_signal_up(df): 342 | pass 343 | if RSI_signal_down(df): 344 | pass 345 | if MACD_signal_up(df): 346 | pass 347 | if MACD_signal_down(df): 348 | pass 349 | if Bollinger_signal_up(df): 350 | pass 351 | if Bollinger_signal_down(df): 352 | pass 353 | 354 | 355 | # OUTPUT 356 | 357 | print('--- BOLLINGER ANALYSIS --- \n') 358 | print(f'The stocks on SIGNAL BOLLINGER UP are:\n==> {bb_up}\n') 359 | print(f'The stocks are already in BOLLINGER UP:\n==> {already_bb_up}\n') 360 | print(f'The stocks on SIGNAL BOLLINGER DOWN are:\n==> {bb_d}\n') 361 | print(f'The stocks are already in BOLLINGER_DOWN:\n==> {already_bb_d}\n') 362 | 363 | print('--- MACD ANALYSIS --- \n') 364 | print(f'The stocks on MACD SIGNAL UP are:\n==> {mcd_up}\n') 365 | print(f'The stocks on MACD SIGNAL UP BELOW 0 are:\n==> {mcd_up0}\n') 366 | print(f'The stocks on MACD SIGNAL DOWN are:\n==> {mcd_d} \n') 367 | print(f'The stocks on MACD SIGNAL DOWN above 0 are:\n==> {mcd_d0}\n') 368 | 369 | print('--- RSI ANALYSIS --- \n') 370 | print(f'The stocks on OVERBOUGHT SIGNAL [RSI] are:\n==> {rsi_up}\n') 371 | print(f'The stocks on OVERSOLD SIGNAL [RSI] are:\n==> {rsi_d}\n') 372 | print(f'The stocks went to RANGE from OVERSOLD are:\n==> {rsi_bf_d}\n') 373 | print(f'The stocks went to RANGE from OVERBOUGHT are:\n==> {rsi_bf_up}\n') 374 | print(f'The stocks on OVERBOUGHT [RSI] are:\n==> {on_rsi_up}\n') 375 | print(f'The stocks on OVERSOLD [RSI] are:\n==> {on_rsi_d}\n') 376 | 377 | print('--- PRICE ACTION ANALYSIS --- \n') 378 | print(f'The stocks on CONSOLIDATION are:\n==> {cons}\n') 379 | print(f'The stocks on BREAKOUT are:\n==> {b_out}\n') 380 | 381 | # Delete stored information UNCOMMENT IF WANT TO REMOVE AFTER SCREENER 382 | shutil.rmtree(path) --------------------------------------------------------------------------------