mirror of
https://github.com/yongghongg/stock-screener.git
synced 2022-03-09 00:26:31 +03:00
204 lines
7.7 KiB
Plaintext
204 lines
7.7 KiB
Plaintext
{
|
|
"nbformat": 4,
|
|
"nbformat_minor": 0,
|
|
"metadata": {
|
|
"colab": {
|
|
"name": "automate-stock-screener-demo.ipynb",
|
|
"provenance": [],
|
|
"collapsed_sections": [],
|
|
"authorship_tag": "ABX9TyPclecTAnBBunNXKyPKq9Lh",
|
|
"include_colab_link": true
|
|
},
|
|
"kernelspec": {
|
|
"name": "python3",
|
|
"display_name": "Python 3"
|
|
},
|
|
"language_info": {
|
|
"name": "python"
|
|
}
|
|
},
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "view-in-github",
|
|
"colab_type": "text"
|
|
},
|
|
"source": [
|
|
"<a href=\"https://colab.research.google.com/github/yongghongg/stock-screener/blob/main/automate_stock_screener_demo.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"id": "4YGeoERrc3Lb"
|
|
},
|
|
"source": [
|
|
"A demo Colab Notebook for my article: \n",
|
|
"https://levelup.gitconnected.com/automate-your-stock-screening-using-python-9107dda724c3"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {
|
|
"id": "9EmMPYxT_iXX"
|
|
},
|
|
"source": [
|
|
"# install required libraries (on colab)\n",
|
|
"!pip install yfinance\n",
|
|
"!pip install bs4\n",
|
|
"!pip install requests\n",
|
|
"# import required libraries \n",
|
|
"import requests\n",
|
|
"from bs4 import BeautifulSoup\n",
|
|
"import yfinance as yf\n",
|
|
"import email\n",
|
|
"import pandas as pd"
|
|
],
|
|
"execution_count": null,
|
|
"outputs": []
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {
|
|
"id": "qG87rhOC-xih"
|
|
},
|
|
"source": [
|
|
"def get_stock_list():\n",
|
|
" # this is the website we're going to scrape from\n",
|
|
" url = \"https://www.malaysiastock.biz/Stock-Screener.aspx\"\n",
|
|
" response = requests.get(url, headers={'User-Agent':'test'})\n",
|
|
" soup = BeautifulSoup(response.content, \"html.parser\")\n",
|
|
" table = soup.find(id = \"MainContent2_tbAllStock\")\n",
|
|
" # return the result (only ticker code) in a list\n",
|
|
" stock_list = table.find_all('a')\n",
|
|
" return [stock_code.get('href')[-4:] for stock_code in stock_list]\n",
|
|
"\n",
|
|
"def get_stock_price(code):\n",
|
|
" # you can change the start date\n",
|
|
" data = yf.download(code, start=\"2021-01-01\", threads= False)\n",
|
|
" return data\n",
|
|
"\n",
|
|
"def add_EMA(price, day):\n",
|
|
" return price.ewm(span=day).mean()\n",
|
|
"\n",
|
|
"def add_STOCH(close, low, high, period, k, d=0): \n",
|
|
" STOCH_K = ((close - low.rolling(window=period).min()) / (high.rolling(window=period).max() - low.rolling(window=period).min())) * 100\n",
|
|
" STOCH_K = STOCH_K.rolling(window=k).mean()\n",
|
|
" if d == 0:\n",
|
|
" return STOCH_K\n",
|
|
" else:\n",
|
|
" STOCH_D = STOCH_K.rolling(window=d).mean()\n",
|
|
" return STOCH_D\n",
|
|
"\n",
|
|
"def check_bounce_EMA(df):\n",
|
|
" candle1 = df.iloc[-1]\n",
|
|
" candle2 = df.iloc[-2]\n",
|
|
" cond1 = candle1['EMA18'] > candle1['EMA50'] > candle1['EMA100']\n",
|
|
" cond2 = candle1['STOCH_%K(5,3,3)'] <= 30 or candle1['STOCH_%D(5,3,3)'] <= 30\n",
|
|
" cond3 = candle2['Low'] < candle2['EMA50'] and \\\n",
|
|
" candle2['Close'] > candle2['EMA50'] and \\\n",
|
|
" candle1['Low'] > candle1 ['EMA50']\n",
|
|
" return cond1 and cond2 and cond3\n",
|
|
"\n",
|
|
"# a list to store the screened results\n",
|
|
"screened_list = [] \n",
|
|
"# get the full stock list\n",
|
|
"stock_list = get_stock_list()\n",
|
|
"for stock_code in stock_list:\n",
|
|
"\n",
|
|
" print(stock_code) # remove this if you dont want the ticker to be printed\n",
|
|
" try: \n",
|
|
" # Step 1: get stock price for each stock\n",
|
|
" price_chart_df = get_stock_price(stock_code + \".KL\") # edit/remove \".KL\" for other exchange market\n",
|
|
"\n",
|
|
" # Step 2: add technical indicators (in this case EMA)\n",
|
|
" close = price_chart_df['Close']\n",
|
|
" low = price_chart_df['Low']\n",
|
|
" open = price_chart_df['Open']\n",
|
|
" high = price_chart_df['High']\n",
|
|
" price_chart_df['EMA18'] = add_EMA(close,18)\n",
|
|
" price_chart_df['EMA50'] = add_EMA(close,50)\n",
|
|
" price_chart_df['EMA100'] = add_EMA(close,100)\n",
|
|
" price_chart_df['STOCH_%K(5,3,3)'] = add_STOCH(close, low, high, 5, 3)\n",
|
|
" price_chart_df['STOCH_%D(5,3,3)'] = add_STOCH(close, low, high, 5, 3, 3)\n",
|
|
"\n",
|
|
" # if all 3 conditions are met, add stock into screened list\n",
|
|
" if check_bounce_EMA(price_chart_df):\n",
|
|
" screened_list.append(stock_code)\n",
|
|
" print(screened_list)\n",
|
|
" except Exception as e:\n",
|
|
" print(e)\n",
|
|
"\n",
|
|
"## this cell may take a few minutes to finish"
|
|
],
|
|
"execution_count": null,
|
|
"outputs": []
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {
|
|
"id": "TD3iwcVT_o19"
|
|
},
|
|
"source": [
|
|
"# configure email and message\n",
|
|
"msg = email.message_from_string(\", \".join(screened_list))\n",
|
|
"msg['From'] = 'YOUR_EMAIL@gmail.com' # change to your email \n",
|
|
"msg['To'] = 'YOUR_EMAIL@gmail.com'\n",
|
|
"msg['Subject'] = \"EMA Bounce Result for Today!\"\n",
|
|
"\n",
|
|
"s = smtplib.SMTP(\"smtp.gmail.com\",587)\n",
|
|
"## for yahoo mail user: s = smtplib.SMTP(\"smtp.mail.yahoo.com\",587) \n",
|
|
"## for hotmail user: s = smtplib.SMTP(\"smtp.live.com\",587)\n",
|
|
"s.ehlo() \n",
|
|
"s.starttls()\n",
|
|
"s.ehlo()\n",
|
|
"s.login(email_from,\"YOUR_PASSWORD\") # change to your password\n",
|
|
"s.sendmail(email_from, [email_to] + [email_cc], msg.as_string())\n",
|
|
"s.quit()"
|
|
],
|
|
"execution_count": null,
|
|
"outputs": []
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"metadata": {
|
|
"id": "vcIveOlyI3h_"
|
|
},
|
|
"source": [
|
|
"# for US stock list\n",
|
|
"# credit to https://github.com/shilewenuw/get_all_tickers/issues/12\n",
|
|
"def get_US_stock_list(exchange):\n",
|
|
" headers = {\n",
|
|
" 'authority': 'api.nasdaq.com',\n",
|
|
" 'accept': 'application/json, text/plain, */*',\n",
|
|
" 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',\n",
|
|
" 'origin': 'https://www.nasdaq.com',\n",
|
|
" 'sec-fetch-site': 'same-site',\n",
|
|
" 'sec-fetch-mode': 'cors',\n",
|
|
" 'sec-fetch-dest': 'empty',\n",
|
|
" 'referer': 'https://www.nasdaq.com/',\n",
|
|
" 'accept-language': 'en-US,en;q=0.9',\n",
|
|
" }\n",
|
|
"\n",
|
|
" params = (\n",
|
|
" ('tableonly', 'true'),\n",
|
|
" ('limit', '25'),\n",
|
|
" ('offset', '0'),\n",
|
|
" ('download', 'true'),\n",
|
|
" ('exchange', exchange)\n",
|
|
" )\n",
|
|
"\n",
|
|
" r = requests.get('https://api.nasdaq.com/api/screener/stocks', headers=headers, params=params)\n",
|
|
" data = r.json()['data']\n",
|
|
" df = pd.DataFrame(data['rows'], columns=data['headers'])\n",
|
|
" df_filtered = df[~df['symbol'].str.contains(\"\\.|\\^|\\s\")]\n",
|
|
" return df_filtered['symbol'].tolist()\n",
|
|
"\n",
|
|
"stock_list = get_US_stock_list('nyse') # or 'nasdaq' or 'amex'"
|
|
],
|
|
"execution_count": null,
|
|
"outputs": []
|
|
}
|
|
]
|
|
} |