Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
f974456
Update market_scanner.py
NihilistPenguin Jul 31, 2020
7574904
Add files via upload
NihilistPenguin Jul 31, 2020
6f5cb6c
Update grapher.py
NihilistPenguin Jul 31, 2020
ae0a979
Add files via upload
NihilistPenguin Jul 31, 2020
2f4fd42
Update README.md
NihilistPenguin Jul 31, 2020
29c29bc
Update README.md
NihilistPenguin Jul 31, 2020
4c1770b
Update README.md
NihilistPenguin Jul 31, 2020
cbf1b4e
Update grapher.py
NihilistPenguin Jul 31, 2020
f9e20da
Update grapher.py
NihilistPenguin Jul 31, 2020
eb859ec
Update market_scanner.py
NihilistPenguin Jul 31, 2020
2fd7709
add figures directory with demo images
Jul 31, 2020
1d9ddc8
add figures directory with sample images
Jul 31, 2020
24eca53
Delete .gitignore
NihilistPenguin Jul 31, 2020
e845186
Update README.md
Jul 31, 2020
18b5427
Update README.md
Jul 31, 2020
65c26cf
Update README.md
Jul 31, 2020
9fb4250
Merge pull request #16 from tcosculluela/master
Jul 31, 2020
21933ae
Revert "Parallelization"
SamPom100 Jul 31, 2020
f2b9e36
Merge pull request #20 from SamPom100/revert-16-master
Jul 31, 2020
4893e10
Merge pull request #18 from NihilistPenguin/master
Jul 31, 2020
bd6508b
Revert "Add various functionality "
SamPom100 Jul 31, 2020
1032b62
Merge pull request #21 from SamPom100/revert-18-master
Jul 31, 2020
93270ef
Make Anomaly Finding more Pandas friendly
Jul 31, 2020
3f7b376
Merge branch 'master' into pandas_branch
elizabethshirley Jul 31, 2020
ff600fe
Merge pull request #1 from alexshirley/pandas_branch
elizabethshirley Aug 1, 2020
5c1c0f0
Rebasing off of updated upstream
Aug 1, 2020
79b4f29
Update README.md
elizabethshirley Aug 1, 2020
20274d0
merge success
c0sc0 Aug 1, 2020
ae1c40d
Merge branch 'master' into Pandas-Refactor-updated-to-master
elizabethshirley Aug 1, 2020
ba162bd
Merge branch 'master' into Pandas-Refactor-updated-to-master
elizabethshirley Aug 1, 2020
f292699
Catching up with changes on master
Aug 1, 2020
08e075d
Allow modifiable DAY_CUTOFF
Aug 1, 2020
02ae698
Merge pull request #2 from alexshirley/Pandas-Refactor-updated-to-master
c0sc0 Aug 1, 2020
d6130d1
merge & clean
c0sc0 Aug 1, 2020
fa1c93d
hide secret key, default params, exportList in stocklist
c0sc0 Aug 1, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.DS_Store
__pycache__/
__pycache__
__pycache__
*.pyc
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"python.pythonPath": "C:\\Users\\Cosco\\ANACON~1\\envs\\stonks\\python.exe"
}
32 changes: 32 additions & 0 deletions flask_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import flask
from flask import Flask, request, send_from_directory, render_template
import os
import numpy

from market_scanner import mainObj

app = flask.Flask(__name__, static_url_path='')
app.config["DEBUG"] = False

SECRET_KEY = os.getenv('SECRET_KEY', 'deditated_wam') #pulls SECRET_KEY from env var, else sets as 'detitaded_wam'
app.config['SECRET_KEY'] = SECRET_KEY



@app.after_request
def after_request(response):
response.headers.add('Access-Control-Allow-Origin', 'localhost*,192.168.*')
response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS')
return response

@app.route('/', methods=['GET'])
def home():
return render_template('dynamic.html', stonks=stonks)

if __name__ == "__main__":
stonk_search = mainObj()
stonks = stonk_search.main_func()

app.run(host='0.0.0.0',port='5000') # run the app on LAN
#app.run() # run the app on your machine
120 changes: 31 additions & 89 deletions market_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,75 +11,59 @@
from joblib import Parallel, delayed, parallel_backend
import multiprocessing


###########################
# THIS IS THE MAIN SCRIPT #
###########################

# Change variables to your liking then run the script
MONTH_CUTTOFF = 6
DAY_CUTTOFF = 3
STD_CUTTOFF = 10


class mainObj:

def __init__(self):
pass
def __init__(self,_month_cuttoff=6,_day_cuttoff=3,_std_cuttoff=10): #default params
self.MONTH_CUTTOFF = _month_cuttoff
self.DAY_CUTTOFF = _day_cuttoff
self.STD_CUTTOFF = _std_cuttoff

def getData(self, ticker):
global MONTH_CUTOFF
currentDate = datetime.datetime.strptime(
date.today().strftime("%Y-%m-%d"), "%Y-%m-%d")
pastDate = currentDate - \
dateutil.relativedelta.relativedelta(months=MONTH_CUTTOFF)
dateutil.relativedelta.relativedelta(months=self.MONTH_CUTTOFF)

#potentially fixing an off-by-one bug(yfinance not getting data for currentDate), will test more after market close on monday. Doesn't break anything in the meantime
currentDate += dateutil.relativedelta.relativedelta(days=1)

sys.stdout = open(os.devnull, "w")
data = yf.download(ticker, pastDate, currentDate)
sys.stdout = sys.__stdout__
return data[["Volume"]]

def find_anomalies(self, data):
global STD_CUTTOFF
indexs = []
outliers = []
def find_anomalies(self, data, currentDate):
data_std = np.std(data['Volume'])
data_mean = np.mean(data['Volume'])
anomaly_cut_off = data_std * STD_CUTTOFF
anomaly_cut_off = data_std * self.STD_CUTTOFF
upper_limit = data_mean + anomaly_cut_off
data.reset_index(level=0, inplace=True)
for i in range(len(data)):
temp = data['Volume'].iloc[i]
if temp > upper_limit:
indexs.append(str(data['Date'].iloc[i])[:-9])
outliers.append(temp)
d = {'Dates': indexs, 'Volume': outliers}
return d
is_outlier = data['Volume'] > upper_limit
is_in_three_days = ((currentDate - data['Date']) <= datetime.timedelta(days=self.DAY_CUTTOFF))
return data[is_outlier & is_in_three_days]

def customPrint(self, d, tick):
print("\n\n\n******* " + tick.upper() + " *******")
print("Ticker is: "+tick.upper())
for i in range(len(d['Dates'])):
str1 = str(d['Dates'][i])
str2 = str(d['Volume'][i])
print(str1 + " - " + str2)
print(d)
print("*********************\n\n\n")

def days_between(self, d1, d2):
d1 = datetime.datetime.strptime(d1, "%Y-%m-%d")
d2 = datetime.datetime.strptime(d2, "%Y-%m-%d")
return abs((d2 - d1).days)

def parallel_wrapper(self, x, currentDate, positive_scans):
global DAY_CUTTOFF
d = (self.find_anomalies(self.getData(x)))
if d['Dates']:
for i in range(len(d['Dates'])):
if self.days_between(str(currentDate)[:-9], str(d['Dates'][i])) <= DAY_CUTTOFF:
self.customPrint(d, x)
stonk = dict()
stonk['Ticker'] = x
stonk['TargetDate'] = d['Dates'][0]
stonk['TargetVolume'] = d['Volume'][0]
positive_scans.append(stonk)
def parallel_wrapper(self,x, currentDate, positive_scans):
d = (self.find_anomalies(self.getData(x), currentDate))
if d.empty:
return

self.customPrint(d, x)
stonk = dict()
stonk['Ticker'] = x
stonk['TargetDate'] = d['Date'].iloc[0]
stonk['TargetVolume'] = d['Volume'].iloc[0]
positive_scans.append(stonk)

def main_func(self):
StocksController = NasdaqController(True)
Expand All @@ -93,7 +77,7 @@ def main_func(self):

with parallel_backend('loky', n_jobs=multiprocessing.cpu_count()):
Parallel()(delayed(self.parallel_wrapper)(x, currentDate, positive_scans)
for x in tqdm(list_of_tickers))
for x in tqdm(list_of_tickers, miniters=1))

print("\n\n\n\n--- this took %s seconds to run ---" %
(time.time() - start_time))
Expand All @@ -102,48 +86,6 @@ def main_func(self):


if __name__ == '__main__':
mainObj().main_func()


"""
Some legacy code down below





























def find_anomalies(self, data, cutoff):
data_std = np.std(data['Volume'])
data_mean = np.mean(data['Volume'])
anomaly_cut_off = data_std * cutoff
upper_limit = data_mean + anomaly_cut_off
indexs = data[data['Volume'] > upper_limit].index.tolist()
outliers = data[data['Volume'] > upper_limit].Volume.tolist()
index_clean = [str(x)[:-9] for x in indexs]
d = {'Dates': index_clean, 'Volume': outliers}
return d
"""
results = mainObj(_month_cuttoff=6,_day_cuttoff=3,_std_cuttoff=10).main_func() #customize these params to your liking
for outlier in results:
print(outlier)
12 changes: 6 additions & 6 deletions stocklist.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@
# this is used to get all tickers from the market.


exportList = []


class NasdaqController:
def getList(self):
return exportList

def __init__(self, update=True):

self.exportList = []

self.filenames = {
"otherlisted": "data/otherlisted.txt",
"nasdaqlisted": "data/nasdaqlisted.txt"
Expand Down Expand Up @@ -56,6 +54,8 @@ def __init__(self, update=True):
continue

all_listed.write(line[0] + ",")
global exportList
exportList.append(line[0])
self.exportList.append(line[0])
all_listed.write(line[0] + "|" + line[1] + "\n")

def getList(self):
return self.exportList
55 changes: 55 additions & 0 deletions templates/dynamic.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!DOCTYPE html>
<html>
<head>
<title>Abnormal Volume Scanner</title>
<meta charset="UTF-8">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.21/css/jquery.dataTables.css">

<style>
.dataTables_wrapper{
margin-bottom: auto;
margin-top: auto;
max-height: 75vh;

}
</style>

</head>
<body style="overflow-y: auto; background-color: #e9ecef;">
<div class="d-flex flex-column justify-content-center align-items-center" style="height: 100vh;margin-left:5vw; margin-right:5vw;">
<h1 class="mt-auto display-1">Abnormal Volume Scanner</h1>
<p class="lead">Get alerted when a stock's volume exceeds 10 standard deviations from the mean within the last 3 days. <a href = "https://github.com/SamPom100/UnusualVolumeDetector">Find the next $KODK.</a></p>
<table id="stonkstable" class="table table-hover">
<thead>
<tr>
<th scope="col">Ticker</th>
<th scope="col">Date</th>
<th scope="col">Volume</th>
</tr>
</thead>
<tbody>
{% for stonk in stonks %}
<tr>
<th scope="row"><a href=" {{ 'http://robinhood.com/stocks/{}'.format(stonk['Ticker']) }}">{{stonk['Ticker']}}</a></th>
<td>{{stonk['TargetDate']}}</td>
<td>{{stonk['TargetVolume']}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<script type="text/javascript" charset="utf8" src="https://code.jquery.com/jquery-3.5.1.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/1.10.21/js/jquery.dataTables.min.js"></script>
<script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/1.10.21/js/dataTables.bootstrap4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<script>
$(document).ready( function () {
$('#stonkstable').DataTable();
});
</script>
</body>
</html>
8 changes: 4 additions & 4 deletions website_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@

app = flask.Flask(__name__, static_url_path='')
app.config["DEBUG"] = False
app.config['SECRET_KEY'] = 'deditaded wam'

SECRET_KEY = os.getenv('SECRET_KEY', 'more_deditated_wam') #pulls SECRET_KEY from env var, else sets as 'more_detitaded_wam'
app.config['SECRET_KEY'] = SECRET_KEY

pages = FlatPages(app)
freezer = Freezer(app)

Expand All @@ -38,6 +41,3 @@ def home():
freezer.freeze()
copyfile('build/index.html', 'index.html')
shutil.rmtree('build/')
# print(stonks)
# app.run(host='0.0.0.0', port='5000') # run the app on LAN
# app.run() # run the app on your machine