-
Notifications
You must be signed in to change notification settings - Fork 3
/
screen_stocks.py
executable file
·380 lines (278 loc) · 11.5 KB
/
screen_stocks.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
#!/home/coil/anaconda3/bin/python3
# --------------------------------------------------------------
# this script only generates signals to go long
# it is intended to generate buy signals for long term investing
# --------------------------------------------------------------
# my program to run on google free tier VM
# stock screener
#Installing collected packages: multitasking, numpy, python-dateutil, pandas, yfinance
# WARNING: The scripts f2py, f2py3 and f2py3.8 are installed in '/home/coil/.local/bin' which is not on PATH.
# Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
# WARNING: The script sample is installed in '/home/coil/.local/bin' which is not on PATH.
#optional installations:
#!pip install yfinance --upgrade --no-cache-dir
# or maybe
#!pip3 install yfinance --upgrade --no-cache-dir
#!pip3 install pandas_datareader
# create lock file quickly to prevent cron spinning infinite number of
# script instances
import sys
import os
lockfile = '/home/coil/scripts/script.lock'
if not os.path.exists(lockfile):
with open(lockfile, 'w'): pass
else:
print('lockfile exists, exiting')
sys.exit(1)
# ___library_import_statements___
import pandas as pd
# for pandas_datareader, otherwise it might have issues, sometimes there is some version mismatch
pd.core.common.is_list_like = pd.api.types.is_list_like
# make pandas to print dataframes nicely
pd.set_option('expand_frame_repr', False)
import pandas_datareader.data as web
import numpy as np
import matplotlib.pyplot as plt
import datetime
import time
#newest yahoo API
import yfinance as yahoo_finance
import smtplib
from email.mime.text import MIMEText
# ___variables___
# ------------------------------------------------------------------------------
#tickers = ['AAPL','AMZN', 'GOOG', 'FB', 'MSFT', 'BPY', 'XOM']
#ticker = 'AAPL'
with open('/home/coil/scripts/stock_list.txt', 'r') as f:
# list in following format
# MMM 3M Company Industrials Industrial Conglomerates
#tickers = [f.read().splitlines()[0] for line in f]
tickers = [line.split()[0] for line in f]
start_time = datetime.datetime(2017, 10, 1)
#end_time = datetime.datetime(2019, 1, 20)
end_time = datetime.datetime.now().date().isoformat() # today
username = '[email protected]'
password = ''
# __function_definitions__
# ------------------------------------------------------------------------------
def get_data(ticker):
# yahoo gives only daily historical data
attempts = 0
connected = False
while not connected:
try:
ticker_df = web.get_data_yahoo(ticker, start=start_time, end=end_time)
connected = True
print('connected to yahoo')
except Exception as e:
print("type error: " + str(e))
time.sleep( 5 )
attempts += 1
if attempts >= 10:
connected = True
pass
# use numerical integer index instead of date
#ticker_df = ticker_df.reset_index()
print(ticker_df.head(5))
return ticker_df
# compute RSI values
def computeRSI (data, time_window):
diff = data.diff(1).dropna() # diff in one field(one day)
#this preservers dimensions off diff values
up_chg = 0 * diff
down_chg = 0 * diff
# up change is equal to the positive difference, otherwise equal to zero
up_chg[diff > 0] = diff[ diff>0 ]
# down change is equal to negative deifference, otherwise equal to zero
down_chg[diff < 0] = diff[ diff < 0 ]
# check pandas documentation for ewm
# https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.ewm.html
# values are related to exponential decay
# we set com=time_window-1 so we get decay alpha=1/time_window
up_chg_avg = up_chg.ewm(com=time_window-1 , min_periods=time_window).mean()
down_chg_avg = down_chg.ewm(com=time_window-1 , min_periods=time_window).mean()
rs = abs(up_chg_avg/down_chg_avg)
rsi = 100 - 100/(1+rs)
return rsi
def computeSMA(data, window):
# simple moving average
sma = data.rolling(window=window).mean()
return sma
def computeEMA(data, span):
# simple moving average
ema = data.ewm(span=span, adjust=False).mean()
return ema
def construct_df(ticker):
#get data from yahoo API
df = get_data(ticker)
# compute both types of moving averages
for i in range(50, 250, 50):
#print(i)
df['SMA_{}'.format(i)] = computeSMA(df['Adj Close'], i)
for i in range(50, 250, 50):
#print(i)
df['EMA_{}'.format(i)] = computeEMA(df['Adj Close'], i)
return df
def stochastic(data, k_window, d_window, window):
# input to function is one column from df
# containing closing price or whatever value we want to extract K and D from
min_val = data.rolling(window=window, center=False).min()
max_val = data.rolling(window=window, center=False).max()
stoch = ( (data - min_val) / (max_val - min_val) ) * 100
K = stoch.rolling(window=k_window, center=False).mean()
#K = stoch
D = K.rolling(window=d_window, center=False).mean()
return K, D
def resample(df):
# weekly timeframe aggregation
agg_dict = {'Open': 'first',
'High': 'max',
'Low': 'min',
'Close': 'last',
'Adj Close': 'last',
'Volume': 'mean'}
# resampled dataframe
# 'W' means weekly aggregation
df_res = df.resample('W').agg(agg_dict)
return df_res
def send_email(data_rsi, data_200_ema, data_50_ema, data_200_ema_vicinity, data_weekly_stochRSI, username, password):
smtp_ssl_host = 'smtp.gmail.com'
smtp_ssl_port = 465
sender = '[email protected]'
receiver = '[email protected]'
# implicitly joined string
msg_body_rsi = ("stock ticker RSI around 30 \n"
"possible long entry \n"
"ticker/s: \n"
+ data_rsi + "\n\n")
msg_body_200_ema = ("went above 200 EMA recently \n"
"possible long entry \n"
"ticker/s: \n"
+ data_200_ema + "\n\n")
msg_body_50_ema = ("in vicinity of 50 EMA \n"
"alerting \n"
"ticker/s: \n"
+ data_50_ema + "\n\n")
msg_body_200_ema_vicinity = ("in vicinity of 200 EMA \n"
"strong alert - current/upcoming support (x resistance) \n"
"ticker/s: \n"
+ data_200_ema_vicinity + "\n\n")
msg__body_weekly_stochRSI = ("weekly_stochRSI long \n"
"rather strong alert \n"
"ticker/s: \n"
+ data_weekly_stochRSI + "\n\n")
msg_body = msg_body_rsi + msg_body_200_ema + msg_body_50_ema \
+ msg_body_200_ema_vicinity + msg__body_weekly_stochRSI
message = MIMEText(msg_body, "plain")
# treat message as dictionary
message['subject'] = 'stock event'
message['from'] = sender
message['to'] = receiver
# contact gmail server and send mail via my gmail dummy account
try:
server = smtplib.SMTP_SSL(smtp_ssl_host, smtp_ssl_port)
server.login(username, password)
server.sendmail(sender, receiver, message.as_string())
server.quit()
print("Successfully sent email")
except:
print("Error: unable to send email")
def support_forming(df, n=14):
# if 70% of datapoints for last n days are above EMA
# we conclude that EMA is or soon will be forming support
cnt = 0
for i in range(0, n):
if df['Adj Close'].iloc[-i] >= df['EMA_200'].iloc[-i]:
cnt += 1
# mostly above EMA and today above or equal
# if it falls through support, we skip it
if cnt/n >= 0.7 and (df['Adj Close'].iloc[-1] >= df['EMA_200'].iloc[-1]):
return True
else:
return False
def conditions(df, df_res):
## __ EMAILING CONDITIONS __
## RSI day before <= threshold and RSI today above - long signal
##if (df['RSI'].iloc[-2] < 30 and df['RSI'].iloc[-1] >= 30):
## long_list.append(ticker)
#s __signal_conditions__
if (df['RSI'].iloc[-1] <= 30):
signal['RSI'].append(ticker)
# was below 200 EMA few days ago but today is above 200 EMA
# possible long
if (
(df['EMA_200'].iloc[-5] > df['Adj Close'].iloc[-5]) and
(df['EMA_200'].iloc[-1] < df['Adj Close'].iloc[-1])
):
signal['EMA_200'].append(ticker)
# price in vicinity 50 EMA
# possible long or at least alert
if (
((df['EMA_50'].iloc[-1] / df['Adj Close'].iloc[-1]) >= 0.98) and
((df['EMA_50'].iloc[-1] / df['Adj Close'].iloc[-1]) <= 1.02)
):
signal['EMA_50'].append(ticker)
# price in vicinity 200 EMA
# possible long or at least alert
if (
((df['EMA_200'].iloc[-1] / df['Adj Close'].iloc[-1]) >= 0.98) and
((df['EMA_200'].iloc[-1] / df['Adj Close'].iloc[-1]) <= 1.02) and
support_forming(df, 14)
):
signal['EMA_200_vicinity'].append(ticker)
# weekly stochastic RSI oversold signal
thres = 20 # oversold condition for stochRSI
# setting benevolent thresholds
if (
df_res['K'].iloc[-1] <= thres and
df_res['D'].iloc[-1] <= thres and
((df_res['K'].iloc[-1] / df_res['D'].iloc[-1]) >= 0.80) and
((df_res['K'].iloc[-1] / df_res['D'].iloc[-1]) <= 1.20)
):
#print('found something', df_res['K'].iloc[-i], df_res['D'].iloc[-i] )
signal['weekly_stochRSI'].append(ticker)
elif ( (df_res['K'].iloc[-1] == 0 ) or ( df_res['D'].iloc[-1] == 0 ) ):
#print('indicators are zeros', df_res['K'].iloc[-i])
signal['weekly_stochRSI'].append(ticker)
return None
## __main_code_part__
# ------------------------------------------------------------------------------
# implement lists as dictionaries for clarity
signal = {}
signal['RSI'] = []
signal['EMA_200'] = []
signal['EMA_50'] = []
signal['EMA_200_vicinity'] = []
signal['weekly_stochRSI'] = []
for ticker in tickers:
try:
# df = get_data(ticker) #just gets data
df = construct_df(ticker) #gets data and adds MAs to the df (implement RSI later)
#adds RSI column to dataframe
df['RSI'] = computeRSI(df['Adj Close'], 14)
# RSI <= 30 is long signal
# if last day RSI data (today) is oversold, send mail
print('ticker:', ticker)
print('rsi today', df['RSI'].iloc[-1])
df_res = resample(df)
df_res['RSI'] = computeRSI(df_res['Adj Close'], 14)
df_res['K'], df_res['D'] = stochastic(df_res['RSI'], 3, 3, 14)
## __ EMAILING CONDITIONS __
conditions(df, df_res)
except Exception as e:
print("type error: " + str(e))
##if ( len(signal['RSI']) > 0 ) or \
## ( len(signal['EMA_200']) > 0 ) or \
## ( len(signal['EMA_50']) > 0 ) or \
## ( len(signal['EMA_200_vicinity']) > 0 ) or \
## ( len(signal['weekly_stochRSI']) > 0 ) :
# if at least one dict value is non empty list
if sum( 1 for i in signal.values() if len(i) > 0 ) > 0:
rsi_str = ' '.join(map(str, signal['RSI']))
ema_200_str = ' '.join(map(str, signal['EMA_200']))
ema_50_str = ' '.join(map(str, signal['EMA_50']))
ema_200_vicinity_str = ' '.join(map(str, signal['EMA_200_vicinity']))
weekly_stochRSI_str = ' '.join(map(str, signal['weekly_stochRSI']))
send_email(rsi_str, ema_200_str, ema_50_str, ema_200_vicinity_str, weekly_stochRSI_str, username, password)
# lockfile cleanup
os.remove(lockfile)