From c3dcd2978b044f969a98e18fd48d269c9cdbf51e Mon Sep 17 00:00:00 2001 From: Matt Maeda Date: Fri, 16 Mar 2018 15:19:46 -0700 Subject: [PATCH] submission for final class project add default url --- students/mattmaeda/stockscreener/LICENSE.txt | 0 students/mattmaeda/stockscreener/README.md | 16 + students/mattmaeda/stockscreener/bin/screener | 0 .../stockscreener/exchange/__init__.py | 8 + .../mattmaeda/stockscreener/exchange/admin.py | 6 + .../mattmaeda/stockscreener/exchange/apps.py | 5 + .../fixtures/initial_exchange_data.json | 1 + .../exchange/migrations/0001_initial.py | 29 + .../exchange/migrations/__init__.py | 0 .../stockscreener/exchange/models.py | 23 + .../exchange/templates/index.html | 29 + .../testdata/test_historical_data.json | 1768 +++++++++++++++++ .../mattmaeda/stockscreener/exchange/tests.py | 325 +++ .../mattmaeda/stockscreener/exchange/urls.py | 7 + .../mattmaeda/stockscreener/exchange/views.py | 266 +++ students/mattmaeda/stockscreener/manage.py | 22 + .../mattmaeda/stockscreener/requirements.txt | 15 + .../mattmaeda/stockscreener/screener.sqlite3 | Bin 0 -> 163840 bytes students/mattmaeda/stockscreener/setup.py | 34 + .../stockscreener/stockscreener/__init__.py | 8 + .../stockscreener/stockscreener/settings.py | 121 ++ .../stockscreener/stockscreener/urls.py | 23 + .../stockscreener/stockscreener/wsgi.py | 16 + 23 files changed, 2722 insertions(+) create mode 100644 students/mattmaeda/stockscreener/LICENSE.txt create mode 100644 students/mattmaeda/stockscreener/README.md create mode 100644 students/mattmaeda/stockscreener/bin/screener create mode 100644 students/mattmaeda/stockscreener/exchange/__init__.py create mode 100644 students/mattmaeda/stockscreener/exchange/admin.py create mode 100644 students/mattmaeda/stockscreener/exchange/apps.py create mode 100644 students/mattmaeda/stockscreener/exchange/fixtures/initial_exchange_data.json create mode 100644 students/mattmaeda/stockscreener/exchange/migrations/0001_initial.py create mode 100644 students/mattmaeda/stockscreener/exchange/migrations/__init__.py create mode 100644 students/mattmaeda/stockscreener/exchange/models.py create mode 100644 students/mattmaeda/stockscreener/exchange/templates/index.html create mode 100644 students/mattmaeda/stockscreener/exchange/testdata/test_historical_data.json create mode 100644 students/mattmaeda/stockscreener/exchange/tests.py create mode 100644 students/mattmaeda/stockscreener/exchange/urls.py create mode 100644 students/mattmaeda/stockscreener/exchange/views.py create mode 100755 students/mattmaeda/stockscreener/manage.py create mode 100644 students/mattmaeda/stockscreener/requirements.txt create mode 100644 students/mattmaeda/stockscreener/screener.sqlite3 create mode 100644 students/mattmaeda/stockscreener/setup.py create mode 100644 students/mattmaeda/stockscreener/stockscreener/__init__.py create mode 100644 students/mattmaeda/stockscreener/stockscreener/settings.py create mode 100644 students/mattmaeda/stockscreener/stockscreener/urls.py create mode 100644 students/mattmaeda/stockscreener/stockscreener/wsgi.py diff --git a/students/mattmaeda/stockscreener/LICENSE.txt b/students/mattmaeda/stockscreener/LICENSE.txt new file mode 100644 index 00000000..e69de29b diff --git a/students/mattmaeda/stockscreener/README.md b/students/mattmaeda/stockscreener/README.md new file mode 100644 index 00000000..23ceaf57 --- /dev/null +++ b/students/mattmaeda/stockscreener/README.md @@ -0,0 +1,16 @@ +# Stock Screener app + +## Setup +1. In directory root, run `python -m venv venv` +2. Activate virtual environment `source venv/bin/activate` +3. Install required libraries `pip install -r requirements.txt` +4. Export Django application settings to environment `export DJANGO_SETTINGS_MODULE=stockscreener.settings` +5. Setup database `django-admin migrate` +6. Load initial rules and screen data `django-admin loaddata exchange/fixtures/initial_exchange_data.json` + +## Run Application +1. `django-admin runserver` +2. Go to [http://127.0.0.1:8000](http://127.0.0.1:8000) + +## Run Tests +1. `django-admin test exchange diff --git a/students/mattmaeda/stockscreener/bin/screener b/students/mattmaeda/stockscreener/bin/screener new file mode 100644 index 00000000..e69de29b diff --git a/students/mattmaeda/stockscreener/exchange/__init__.py b/students/mattmaeda/stockscreener/exchange/__init__.py new file mode 100644 index 00000000..2cdb3a3e --- /dev/null +++ b/students/mattmaeda/stockscreener/exchange/__init__.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python +""" +stockscreener exchange package +""" + +from pathlib import Path + +__version__ = "0.0.1" diff --git a/students/mattmaeda/stockscreener/exchange/admin.py b/students/mattmaeda/stockscreener/exchange/admin.py new file mode 100644 index 00000000..ebe4d185 --- /dev/null +++ b/students/mattmaeda/stockscreener/exchange/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from .models import Rule, Screen + +# Register your models here. +admin.site.register(Rule) +admin.site.register(Screen) diff --git a/students/mattmaeda/stockscreener/exchange/apps.py b/students/mattmaeda/stockscreener/exchange/apps.py new file mode 100644 index 00000000..b0168d0d --- /dev/null +++ b/students/mattmaeda/stockscreener/exchange/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ExchangeConfig(AppConfig): + name = 'exchange' diff --git a/students/mattmaeda/stockscreener/exchange/fixtures/initial_exchange_data.json b/students/mattmaeda/stockscreener/exchange/fixtures/initial_exchange_data.json new file mode 100644 index 00000000..c5f9b0ae --- /dev/null +++ b/students/mattmaeda/stockscreener/exchange/fixtures/initial_exchange_data.json @@ -0,0 +1 @@ +[{"model": "exchange.rule", "pk": "MA100 GT CP", "fields": {"criteria": "100 Day Moving Average Above Closing Price"}}, {"model": "exchange.rule", "pk": "MA100 GT MA200", "fields": {"criteria": "100 Day Moving Average Above 200 Day Moving Average"}}, {"model": "exchange.rule", "pk": "MA100 LT CP", "fields": {"criteria": "100 Day Moving Average Below Closing Price"}}, {"model": "exchange.rule", "pk": "MA100 LT MA200", "fields": {"criteria": "100 Day Moving Average Below 200 Day Moving Average"}}, {"model": "exchange.rule", "pk": "MA50 GT CP", "fields": {"criteria": "50 Day Moving Average Above Closing Price"}}, {"model": "exchange.rule", "pk": "MA50 GT MA200", "fields": {"criteria": "50 Day Moving Average Above 200 Day Moving Average"}}, {"model": "exchange.rule", "pk": "MA50 LT CP", "fields": {"criteria": "50 Day Moving Average Below Closing Price"}}, {"model": "exchange.rule", "pk": "MA50 LT MA200", "fields": {"criteria": "50 Day Moving Average Below 200 Day Moving Average"}}, {"model": "exchange.rule", "pk": "MA50 XOVER MA100", "fields": {"criteria": "50 Day Moving Average Crosses Over 100 Day Moving Average"}}, {"model": "exchange.rule", "pk": "MA50 XUNDER MA100", "fields": {"criteria": "50 Moving Average Crosses Under 100 Day Moving Average"}}, {"model": "exchange.screen", "pk": "Death Cross", "fields": {"description": "50 Day Moving Average Cross Under 100 Day Moving Average", "rules": ["MA50 XUNDER MA100"]}}, {"model": "exchange.screen", "pk": "Death Cross Plus", "fields": {"description": "50 Day Moving Average Cross Under 100 Day Moving Average and Closing Price Above 50 Day Moving Average", "rules": ["MA100 GT CP", "MA100 GT MA200", "MA50 GT CP", "MA50 GT MA200", "MA50 XUNDER MA100"]}}, {"model": "exchange.screen", "pk": "Golden Cross", "fields": {"description": "50 Day Moving Average Cross Over 100 Day Moving Average", "rules": ["MA50 XOVER MA100"]}}, {"model": "exchange.screen", "pk": "Golden Cross Plus", "fields": {"description": "50 Day Moving Average Cross Over 100 Day Moving Average and Closing Price Above 50 Day Moving Average", "rules": ["MA100 LT CP", "MA100 LT MA200", "MA50 LT CP", "MA50 LT MA200", "MA50 XOVER MA100"]}}] \ No newline at end of file diff --git a/students/mattmaeda/stockscreener/exchange/migrations/0001_initial.py b/students/mattmaeda/stockscreener/exchange/migrations/0001_initial.py new file mode 100644 index 00000000..d75c06ef --- /dev/null +++ b/students/mattmaeda/stockscreener/exchange/migrations/0001_initial.py @@ -0,0 +1,29 @@ +# Generated by Django 2.0.3 on 2018-03-14 04:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Rule', + fields=[ + ('formula', models.CharField(max_length=100, primary_key=True, serialize=False)), + ('criteria', models.CharField(max_length=250)), + ], + ), + migrations.CreateModel( + name='Screen', + fields=[ + ('name', models.CharField(max_length=100, primary_key=True, serialize=False)), + ('description', models.CharField(max_length=250)), + ('rules', models.ManyToManyField(to='exchange.Rule')), + ], + ), + ] diff --git a/students/mattmaeda/stockscreener/exchange/migrations/__init__.py b/students/mattmaeda/stockscreener/exchange/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/students/mattmaeda/stockscreener/exchange/models.py b/students/mattmaeda/stockscreener/exchange/models.py new file mode 100644 index 00000000..f3a02b9f --- /dev/null +++ b/students/mattmaeda/stockscreener/exchange/models.py @@ -0,0 +1,23 @@ +""" +Database objects for screener application +""" +from django.db import models + +# Create your models here. +class Rule(models.Model): + """ Holds rule information """ + formula = models.CharField(max_length=100, primary_key=True) + criteria = models.CharField(max_length=250) + + def __str__(self): + return self.formula + + +class Screen(models.Model): + """ Holds screen information """ + name = models.CharField(max_length=100, primary_key=True) + description = models.CharField(max_length=250) + rules = models.ManyToManyField(Rule) + + def __str__(self): + return self.name diff --git a/students/mattmaeda/stockscreener/exchange/templates/index.html b/students/mattmaeda/stockscreener/exchange/templates/index.html new file mode 100644 index 00000000..1bd0b0ac --- /dev/null +++ b/students/mattmaeda/stockscreener/exchange/templates/index.html @@ -0,0 +1,29 @@ +

Stock Screener

+{% if message %}

{{ message }}

{% endif %} +
+{% csrf_token %} +Stock Symbol: + +
+ +{% if response %} +{% for screen in response %} +Name: {{ screen.name }}
+Description: {{ screen.desc }}
+Pass?: {{ screen.pass }}
+ + + + + +{% for rule in screen.rules %} + + + + +{% endfor %} +
CriteriaPass?
{{ rule.criteria }}{{ rule.pass }}
+
+
+{% endfor %} +{% endif %} diff --git a/students/mattmaeda/stockscreener/exchange/testdata/test_historical_data.json b/students/mattmaeda/stockscreener/exchange/testdata/test_historical_data.json new file mode 100644 index 00000000..41607ed9 --- /dev/null +++ b/students/mattmaeda/stockscreener/exchange/testdata/test_historical_data.json @@ -0,0 +1,1768 @@ +{ + "GOOG": { + "2017-03-16": { + "open": 849.03, + "high": 850.85, + "low": 846.13, + "close": 848.78, + "volume": 977560.0 + }, + "2017-03-17": { + "open": 851.61, + "high": 853.4, + "low": 847.11, + "close": 852.12, + "volume": 1716471.0 + }, + "2017-03-20": { + "open": 850.01, + "high": 850.22, + "low": 845.15, + "close": 848.4, + "volume": 1231521.0 + }, + "2017-03-21": { + "open": 851.4, + "high": 853.5, + "low": 829.02, + "close": 830.46, + "volume": 2463484.0 + }, + "2017-03-22": { + "open": 831.91, + "high": 835.55, + "low": 827.1801, + "close": 829.59, + "volume": 1401465.0 + }, + "2017-03-23": { + "open": 821.0, + "high": 822.57, + "low": 812.257, + "close": 817.58, + "volume": 3487056.0 + }, + "2017-03-24": { + "open": 820.08, + "high": 821.93, + "low": 808.89, + "close": 814.43, + "volume": 1981006.0 + }, + "2017-03-27": { + "open": 806.95, + "high": 821.63, + "low": 803.37, + "close": 819.51, + "volume": 1894990.0 + }, + "2017-03-28": { + "open": 820.41, + "high": 825.99, + "low": 814.027, + "close": 820.92, + "volume": 1620542.0 + }, + "2017-03-29": { + "open": 825.0, + "high": 832.765, + "low": 822.3801, + "close": 831.41, + "volume": 1786321.0 + }, + "2017-03-30": { + "open": 833.5, + "high": 833.68, + "low": 829.0, + "close": 831.5, + "volume": 1055339.0 + }, + "2017-03-31": { + "open": 828.97, + "high": 831.64, + "low": 827.39, + "close": 829.56, + "volume": 1401893.0 + }, + "2017-04-03": { + "open": 829.22, + "high": 840.85, + "low": 829.22, + "close": 838.55, + "volume": 1671503.0 + }, + "2017-04-04": { + "open": 831.36, + "high": 835.18, + "low": 829.0363, + "close": 834.57, + "volume": 1045363.0 + }, + "2017-04-05": { + "open": 835.51, + "high": 842.45, + "low": 830.72, + "close": 831.41, + "volume": 1555328.0 + }, + "2017-04-06": { + "open": 832.4, + "high": 836.39, + "low": 826.46, + "close": 827.88, + "volume": 1254433.0 + }, + "2017-04-07": { + "open": 827.96, + "high": 828.485, + "low": 820.5127, + "close": 824.67, + "volume": 1056692.0 + }, + "2017-04-10": { + "open": 825.39, + "high": 829.35, + "low": 823.77, + "close": 824.73, + "volume": 978905.0 + }, + "2017-04-11": { + "open": 824.71, + "high": 827.4267, + "low": 817.0201, + "close": 823.35, + "volume": 1079732.0 + }, + "2017-04-12": { + "open": 821.93, + "high": 826.66, + "low": 821.02, + "close": 824.32, + "volume": 900480.0 + }, + "2017-04-13": { + "open": 822.14, + "high": 826.38, + "low": 821.44, + "close": 823.56, + "volume": 1122362.0 + }, + "2017-04-17": { + "open": 825.01, + "high": 837.75, + "low": 824.47, + "close": 837.17, + "volume": 895015.0 + }, + "2017-04-18": { + "open": 834.22, + "high": 838.93, + "low": 832.71, + "close": 836.82, + "volume": 836722.0 + }, + "2017-04-19": { + "open": 839.79, + "high": 842.22, + "low": 836.29, + "close": 838.21, + "volume": 954330.0 + }, + "2017-04-20": { + "open": 841.44, + "high": 845.2, + "low": 839.32, + "close": 841.65, + "volume": 959031.0 + }, + "2017-04-21": { + "open": 842.88, + "high": 843.88, + "low": 840.6, + "close": 843.19, + "volume": 1323583.0 + }, + "2017-04-24": { + "open": 851.2, + "high": 863.45, + "low": 849.86, + "close": 862.76, + "volume": 1372541.0 + }, + "2017-04-25": { + "open": 865.0, + "high": 875.0, + "low": 862.81, + "close": 872.3, + "volume": 1671972.0 + }, + "2017-04-26": { + "open": 874.23, + "high": 876.05, + "low": 867.7481, + "close": 871.73, + "volume": 1237167.0 + }, + "2017-04-27": { + "open": 873.6, + "high": 875.4, + "low": 870.38, + "close": 874.25, + "volume": 2026816.0 + }, + "2017-04-28": { + "open": 910.66, + "high": 916.85, + "low": 905.77, + "close": 905.96, + "volume": 3276255.0 + }, + "2017-05-01": { + "open": 901.94, + "high": 915.68, + "low": 901.45, + "close": 912.57, + "volume": 2115993.0 + }, + "2017-05-02": { + "open": 909.62, + "high": 920.77, + "low": 909.4526, + "close": 916.44, + "volume": 1587219.0 + }, + "2017-05-03": { + "open": 914.86, + "high": 928.1, + "low": 912.5426, + "close": 927.04, + "volume": 1499532.0 + }, + "2017-05-04": { + "open": 926.07, + "high": 935.93, + "low": 924.59, + "close": 931.66, + "volume": 1422144.0 + }, + "2017-05-05": { + "open": 933.54, + "high": 934.9, + "low": 925.2, + "close": 927.13, + "volume": 1911275.0 + }, + "2017-05-08": { + "open": 926.12, + "high": 936.925, + "low": 925.26, + "close": 934.3, + "volume": 1329825.0 + }, + "2017-05-09": { + "open": 936.95, + "high": 937.5, + "low": 929.53, + "close": 932.17, + "volume": 1581809.0 + }, + "2017-05-10": { + "open": 931.98, + "high": 932.0, + "low": 925.16, + "close": 928.78, + "volume": 1173925.0 + }, + "2017-05-11": { + "open": 925.32, + "high": 932.53, + "low": 923.0301, + "close": 930.6, + "volume": 835386.0 + }, + "2017-05-12": { + "open": 931.53, + "high": 933.44, + "low": 927.85, + "close": 932.22, + "volume": 1050601.0 + }, + "2017-05-15": { + "open": 932.95, + "high": 938.25, + "low": 929.34, + "close": 937.08, + "volume": 1108496.0 + }, + "2017-05-16": { + "open": 940.0, + "high": 943.11, + "low": 937.58, + "close": 943.0, + "volume": 969479.0 + }, + "2017-05-17": { + "open": 935.67, + "high": 939.3325, + "low": 918.14, + "close": 919.62, + "volume": 2362072.0 + }, + "2017-05-18": { + "open": 921.0, + "high": 933.17, + "low": 918.75, + "close": 930.24, + "volume": 1596897.0 + }, + "2017-05-19": { + "open": 931.47, + "high": 937.755, + "low": 931.0, + "close": 934.01, + "volume": 1393024.0 + }, + "2017-05-22": { + "open": 935.0, + "high": 941.8828, + "low": 935.0, + "close": 941.86, + "volume": 1120385.0 + }, + "2017-05-23": { + "open": 947.92, + "high": 951.4666, + "low": 942.575, + "close": 948.82, + "volume": 1270817.0 + }, + "2017-05-24": { + "open": 952.98, + "high": 955.09, + "low": 949.5, + "close": 954.96, + "volume": 1034199.0 + }, + "2017-05-25": { + "open": 957.33, + "high": 972.629, + "low": 955.47, + "close": 969.54, + "volume": 1660474.0 + }, + "2017-05-26": { + "open": 969.7, + "high": 974.98, + "low": 965.03, + "close": 971.47, + "volume": 1252010.0 + }, + "2017-05-30": { + "open": 970.31, + "high": 976.2, + "low": 969.49, + "close": 975.88, + "volume": 1466654.0 + }, + "2017-05-31": { + "open": 975.02, + "high": 979.27, + "low": 960.18, + "close": 964.86, + "volume": 2448067.0 + }, + "2017-06-01": { + "open": 968.95, + "high": 971.5, + "low": 960.01, + "close": 966.95, + "volume": 1410458.0 + }, + "2017-06-02": { + "open": 969.46, + "high": 975.88, + "low": 966.0, + "close": 975.6, + "volume": 1750955.0 + }, + "2017-06-05": { + "open": 976.55, + "high": 986.91, + "low": 975.1, + "close": 983.68, + "volume": 1252106.0 + }, + "2017-06-06": { + "open": 983.16, + "high": 988.25, + "low": 975.14, + "close": 976.57, + "volume": 1814624.0 + }, + "2017-06-07": { + "open": 979.65, + "high": 984.15, + "low": 975.77, + "close": 981.08, + "volume": 1453874.0 + }, + "2017-06-08": { + "open": 982.35, + "high": 984.57, + "low": 977.2, + "close": 983.41, + "volume": 1481916.0 + }, + "2017-06-09": { + "open": 984.5, + "high": 984.5, + "low": 935.63, + "close": 949.83, + "volume": 3309389.0 + }, + "2017-06-12": { + "open": 939.56, + "high": 949.355, + "low": 915.2328, + "close": 942.9, + "volume": 3763529.0 + }, + "2017-06-13": { + "open": 951.91, + "high": 959.98, + "low": 944.09, + "close": 953.4, + "volume": 2013337.0 + }, + "2017-06-14": { + "open": 959.92, + "high": 961.15, + "low": 942.25, + "close": 950.76, + "volume": 1489715.0 + }, + "2017-06-15": { + "open": 933.97, + "high": 943.339, + "low": 924.44, + "close": 942.31, + "volume": 2133050.0 + }, + "2017-06-16": { + "open": 940.0, + "high": 942.04, + "low": 931.595, + "close": 939.78, + "volume": 3094711.0 + }, + "2017-06-19": { + "open": 949.96, + "high": 959.99, + "low": 949.05, + "close": 957.37, + "volume": 1533336.0 + }, + "2017-06-20": { + "open": 957.52, + "high": 961.62, + "low": 950.01, + "close": 950.63, + "volume": 1125990.0 + }, + "2017-06-21": { + "open": 953.64, + "high": 960.1, + "low": 950.76, + "close": 959.45, + "volume": 1202233.0 + }, + "2017-06-22": { + "open": 958.7, + "high": 960.72, + "low": 954.55, + "close": 957.09, + "volume": 941958.0 + }, + "2017-06-23": { + "open": 956.83, + "high": 966.0, + "low": 954.2, + "close": 965.59, + "volume": 1527856.0 + }, + "2017-06-26": { + "open": 969.9, + "high": 973.31, + "low": 950.79, + "close": 952.27, + "volume": 1598355.0 + }, + "2017-06-27": { + "open": 942.46, + "high": 948.29, + "low": 926.85, + "close": 927.33, + "volume": 2579930.0 + }, + "2017-06-28": { + "open": 929.0, + "high": 942.75, + "low": 916.0, + "close": 940.49, + "volume": 2721406.0 + }, + "2017-06-29": { + "open": 929.92, + "high": 931.26, + "low": 910.62, + "close": 917.79, + "volume": 3299176.0 + }, + "2017-06-30": { + "open": 926.05, + "high": 926.05, + "low": 908.31, + "close": 908.73, + "volume": 2090226.0 + }, + "2017-07-03": { + "open": 912.18, + "high": 913.94, + "low": 894.79, + "close": 898.7, + "volume": 1710373.0 + }, + "2017-07-05": { + "open": 901.76, + "high": 914.51, + "low": 898.5, + "close": 911.71, + "volume": 1813884.0 + }, + "2017-07-06": { + "open": 904.12, + "high": 914.9444, + "low": 899.7, + "close": 906.69, + "volume": 1424503.0 + }, + "2017-07-07": { + "open": 908.85, + "high": 921.54, + "low": 908.85, + "close": 918.59, + "volume": 1637785.0 + }, + "2017-07-10": { + "open": 921.77, + "high": 930.38, + "low": 919.59, + "close": 928.8, + "volume": 1192825.0 + }, + "2017-07-11": { + "open": 929.54, + "high": 931.43, + "low": 922.0, + "close": 930.09, + "volume": 1113235.0 + }, + "2017-07-12": { + "open": 938.68, + "high": 946.3, + "low": 934.47, + "close": 943.83, + "volume": 1532144.0 + }, + "2017-07-13": { + "open": 946.29, + "high": 954.45, + "low": 943.01, + "close": 947.16, + "volume": 1294687.0 + }, + "2017-07-14": { + "open": 952.0, + "high": 956.91, + "low": 948.005, + "close": 955.99, + "volume": 1053774.0 + }, + "2017-07-17": { + "open": 957.0, + "high": 960.74, + "low": 949.2407, + "close": 953.42, + "volume": 1165537.0 + }, + "2017-07-18": { + "open": 953.0, + "high": 968.04, + "low": 950.6, + "close": 965.4, + "volume": 1153964.0 + }, + "2017-07-19": { + "open": 967.84, + "high": 973.04, + "low": 964.03, + "close": 970.89, + "volume": 1224540.0 + }, + "2017-07-20": { + "open": 975.0, + "high": 975.9, + "low": 961.51, + "close": 968.15, + "volume": 1624463.0 + }, + "2017-07-21": { + "open": 962.25, + "high": 973.23, + "low": 960.15, + "close": 972.92, + "volume": 1711000.0 + }, + "2017-07-24": { + "open": 972.22, + "high": 986.2, + "low": 970.77, + "close": 980.34, + "volume": 3248347.0 + }, + "2017-07-25": { + "open": 953.81, + "high": 959.7, + "low": 945.4, + "close": 950.7, + "volume": 4660979.0 + }, + "2017-07-26": { + "open": 954.68, + "high": 955.0, + "low": 942.2788, + "close": 947.8, + "volume": 2088256.0 + }, + "2017-07-27": { + "open": 951.78, + "high": 951.78, + "low": 920.0, + "close": 934.09, + "volume": 3212996.0 + }, + "2017-07-28": { + "open": 929.4, + "high": 943.83, + "low": 927.5, + "close": 941.53, + "volume": 1846351.0 + }, + "2017-07-31": { + "open": 941.89, + "high": 943.59, + "low": 926.04, + "close": 930.5, + "volume": 1970095.0 + }, + "2017-08-01": { + "open": 932.38, + "high": 937.447, + "low": 929.26, + "close": 930.83, + "volume": 1277734.0 + }, + "2017-08-02": { + "open": 928.61, + "high": 932.6, + "low": 916.68, + "close": 930.39, + "volume": 1824448.0 + }, + "2017-08-03": { + "open": 930.34, + "high": 932.24, + "low": 922.24, + "close": 923.65, + "volume": 1202512.0 + }, + "2017-08-04": { + "open": 926.75, + "high": 930.3068, + "low": 923.03, + "close": 927.96, + "volume": 1082267.0 + }, + "2017-08-07": { + "open": 929.06, + "high": 931.7, + "low": 926.5, + "close": 929.36, + "volume": 1032239.0 + }, + "2017-08-08": { + "open": 927.09, + "high": 935.814, + "low": 925.6095, + "close": 926.79, + "volume": 1061579.0 + }, + "2017-08-09": { + "open": 920.61, + "high": 925.98, + "low": 917.2501, + "close": 922.9, + "volume": 1192081.0 + }, + "2017-08-10": { + "open": 917.55, + "high": 919.26, + "low": 906.13, + "close": 907.24, + "volume": 1823967.0 + }, + "2017-08-11": { + "open": 907.97, + "high": 917.78, + "low": 905.58, + "close": 914.39, + "volume": 1206782.0 + }, + "2017-08-14": { + "open": 922.53, + "high": 924.668, + "low": 918.19, + "close": 922.67, + "volume": 1064530.0 + }, + "2017-08-15": { + "open": 924.23, + "high": 926.5499, + "low": 919.82, + "close": 922.22, + "volume": 883369.0 + }, + "2017-08-16": { + "open": 925.29, + "high": 932.7, + "low": 923.445, + "close": 926.96, + "volume": 1006711.0 + }, + "2017-08-17": { + "open": 925.78, + "high": 926.86, + "low": 910.98, + "close": 910.98, + "volume": 1277238.0 + }, + "2017-08-18": { + "open": 910.31, + "high": 915.275, + "low": 907.1543, + "close": 910.67, + "volume": 1342689.0 + }, + "2017-08-21": { + "open": 910.0, + "high": 913.0, + "low": 903.4, + "close": 906.66, + "volume": 943441.0 + }, + "2017-08-22": { + "open": 912.72, + "high": 925.86, + "low": 911.4751, + "close": 924.69, + "volume": 1166737.0 + }, + "2017-08-23": { + "open": 921.93, + "high": 929.93, + "low": 919.36, + "close": 927.0, + "volume": 1090248.0 + }, + "2017-08-24": { + "open": 928.66, + "high": 930.84, + "low": 915.5, + "close": 921.28, + "volume": 1270306.0 + }, + "2017-08-25": { + "open": 923.49, + "high": 925.555, + "low": 915.5, + "close": 915.89, + "volume": 1053376.0 + }, + "2017-08-28": { + "open": 916.0, + "high": 919.245, + "low": 911.87, + "close": 913.81, + "volume": 1086484.0 + }, + "2017-08-29": { + "open": 905.1, + "high": 923.33, + "low": 905.0, + "close": 921.29, + "volume": 1185564.0 + }, + "2017-08-30": { + "open": 920.05, + "high": 930.819, + "low": 919.65, + "close": 929.57, + "volume": 1301225.0 + }, + "2017-08-31": { + "open": 931.76, + "high": 941.98, + "low": 931.76, + "close": 939.33, + "volume": 1582579.0 + }, + "2017-09-01": { + "open": 941.13, + "high": 942.48, + "low": 935.15, + "close": 937.34, + "volume": 947374.0 + }, + "2017-09-05": { + "open": 933.08, + "high": 937.0, + "low": 921.96, + "close": 928.45, + "volume": 1348292.0 + }, + "2017-09-06": { + "open": 930.15, + "high": 930.915, + "low": 919.27, + "close": 927.81, + "volume": 1527650.0 + }, + "2017-09-07": { + "open": 931.73, + "high": 936.41, + "low": 923.62, + "close": 935.95, + "volume": 1212743.0 + }, + "2017-09-08": { + "open": 936.49, + "high": 936.99, + "low": 924.88, + "close": 926.5, + "volume": 1011538.0 + }, + "2017-09-11": { + "open": 934.25, + "high": 938.38, + "low": 926.92, + "close": 929.08, + "volume": 1266991.0 + }, + "2017-09-12": { + "open": 932.59, + "high": 933.48, + "low": 923.861, + "close": 932.07, + "volume": 1134397.0 + }, + "2017-09-13": { + "open": 930.66, + "high": 937.25, + "low": 929.86, + "close": 935.09, + "volume": 1102631.0 + }, + "2017-09-14": { + "open": 931.25, + "high": 932.77, + "low": 924.0, + "close": 925.11, + "volume": 1397644.0 + }, + "2017-09-15": { + "open": 924.66, + "high": 926.49, + "low": 916.36, + "close": 920.29, + "volume": 2505430.0 + }, + "2017-09-18": { + "open": 920.01, + "high": 922.08, + "low": 910.6, + "close": 915.0, + "volume": 1306922.0 + }, + "2017-09-19": { + "open": 917.42, + "high": 922.4199, + "low": 912.55, + "close": 921.81, + "volume": 936654.0 + }, + "2017-09-20": { + "open": 922.98, + "high": 933.88, + "low": 922.0, + "close": 931.58, + "volume": 1669763.0 + }, + "2017-09-21": { + "open": 933.0, + "high": 936.53, + "low": 923.83, + "close": 932.45, + "volume": 1290607.0 + }, + "2017-09-22": { + "open": 927.75, + "high": 934.73, + "low": 926.48, + "close": 928.53, + "volume": 1052704.0 + }, + "2017-09-25": { + "open": 925.45, + "high": 926.4, + "low": 909.7, + "close": 920.97, + "volume": 1856822.0 + }, + "2017-09-26": { + "open": 923.72, + "high": 930.82, + "low": 921.14, + "close": 924.86, + "volume": 1666861.0 + }, + "2017-09-27": { + "open": 927.74, + "high": 949.9, + "low": 927.74, + "close": 944.49, + "volume": 2239441.0 + }, + "2017-09-28": { + "open": 941.36, + "high": 950.69, + "low": 940.55, + "close": 949.5, + "volume": 1020312.0 + }, + "2017-09-29": { + "open": 952.0, + "high": 959.7864, + "low": 951.51, + "close": 959.11, + "volume": 1580994.0 + }, + "2017-10-02": { + "open": 959.98, + "high": 962.54, + "low": 947.84, + "close": 953.27, + "volume": 1283444.0 + }, + "2017-10-03": { + "open": 954.0, + "high": 958.0, + "low": 949.14, + "close": 957.79, + "volume": 888346.0 + }, + "2017-10-04": { + "open": 957.0, + "high": 960.39, + "low": 950.69, + "close": 951.68, + "volume": 952391.0 + }, + "2017-10-05": { + "open": 955.49, + "high": 970.91, + "low": 955.18, + "close": 969.96, + "volume": 1213816.0 + }, + "2017-10-06": { + "open": 966.7, + "high": 979.46, + "low": 963.36, + "close": 978.89, + "volume": 1173882.0 + }, + "2017-10-09": { + "open": 980.0, + "high": 985.425, + "low": 976.11, + "close": 977.0, + "volume": 891355.0 + }, + "2017-10-10": { + "open": 980.0, + "high": 981.57, + "low": 966.0801, + "close": 972.6, + "volume": 968362.0 + }, + "2017-10-11": { + "open": 973.72, + "high": 990.71, + "low": 972.25, + "close": 989.25, + "volume": 1693274.0 + }, + "2017-10-12": { + "open": 987.45, + "high": 994.12, + "low": 985.0, + "close": 987.83, + "volume": 1262793.0 + }, + "2017-10-13": { + "open": 992.0, + "high": 997.21, + "low": 989.0, + "close": 989.68, + "volume": 1169777.0 + }, + "2017-10-16": { + "open": 992.1, + "high": 993.9065, + "low": 984.0, + "close": 992.0, + "volume": 910543.0 + }, + "2017-10-17": { + "open": 990.29, + "high": 996.44, + "low": 988.59, + "close": 992.18, + "volume": 1290186.0 + }, + "2017-10-18": { + "open": 991.77, + "high": 996.72, + "low": 986.9747, + "close": 992.81, + "volume": 1057581.0 + }, + "2017-10-19": { + "open": 986.0, + "high": 988.88, + "low": 978.39, + "close": 984.45, + "volume": 1313575.0 + }, + "2017-10-20": { + "open": 989.44, + "high": 991.0, + "low": 984.58, + "close": 988.2, + "volume": 1183186.0 + }, + "2017-10-23": { + "open": 989.52, + "high": 989.52, + "low": 966.12, + "close": 968.45, + "volume": 1478448.0 + }, + "2017-10-24": { + "open": 970.0, + "high": 972.23, + "low": 961.0, + "close": 970.54, + "volume": 1212153.0 + }, + "2017-10-25": { + "open": 968.37, + "high": 976.09, + "low": 960.5201, + "close": 973.33, + "volume": 1211262.0 + }, + "2017-10-26": { + "open": 980.0, + "high": 987.6, + "low": 972.2, + "close": 972.56, + "volume": 2042149.0 + }, + "2017-10-27": { + "open": 1009.19, + "high": 1048.39, + "low": 1008.2, + "close": 1019.27, + "volume": 5167689.0 + }, + "2017-10-30": { + "open": 1014.0, + "high": 1024.97, + "low": 1007.5, + "close": 1017.11, + "volume": 2085062.0 + }, + "2017-10-31": { + "open": 1015.22, + "high": 1024.0, + "low": 1010.42, + "close": 1016.64, + "volume": 1331391.0 + }, + "2017-11-01": { + "open": 1017.21, + "high": 1029.67, + "low": 1016.95, + "close": 1025.5, + "volume": 1373444.0 + }, + "2017-11-02": { + "open": 1021.76, + "high": 1028.09, + "low": 1013.01, + "close": 1025.58, + "volume": 1048970.0 + }, + "2017-11-03": { + "open": 1022.11, + "high": 1032.65, + "low": 1020.31, + "close": 1032.48, + "volume": 1076350.0 + }, + "2017-11-06": { + "open": 1028.99, + "high": 1034.87, + "low": 1025.0, + "close": 1025.9, + "volume": 1125185.0 + }, + "2017-11-07": { + "open": 1027.27, + "high": 1033.97, + "low": 1025.13, + "close": 1033.33, + "volume": 1112331.0 + }, + "2017-11-08": { + "open": 1030.52, + "high": 1043.52, + "low": 1028.45, + "close": 1039.85, + "volume": 1088716.0 + }, + "2017-11-09": { + "open": 1033.99, + "high": 1033.99, + "low": 1019.67, + "close": 1031.26, + "volume": 1242465.0 + }, + "2017-11-10": { + "open": 1026.46, + "high": 1030.76, + "low": 1025.28, + "close": 1028.07, + "volume": 720676.0 + }, + "2017-11-13": { + "open": 1023.42, + "high": 1031.58, + "low": 1022.57, + "close": 1025.75, + "volume": 885779.0 + }, + "2017-11-14": { + "open": 1022.59, + "high": 1026.81, + "low": 1014.15, + "close": 1026.0, + "volume": 959222.0 + }, + "2017-11-15": { + "open": 1019.21, + "high": 1024.09, + "low": 1015.42, + "close": 1020.91, + "volume": 853992.0 + }, + "2017-11-16": { + "open": 1022.52, + "high": 1035.92, + "low": 1022.52, + "close": 1032.5, + "volume": 1129688.0 + }, + "2017-11-17": { + "open": 1034.01, + "high": 1034.42, + "low": 1017.75, + "close": 1019.09, + "volume": 1397064.0 + }, + "2017-11-20": { + "open": 1020.26, + "high": 1022.61, + "low": 1017.5, + "close": 1018.38, + "volume": 953470.0 + }, + "2017-11-21": { + "open": 1023.31, + "high": 1035.11, + "low": 1022.65, + "close": 1034.49, + "volume": 1096999.0 + }, + "2017-11-22": { + "open": 1035.0, + "high": 1039.71, + "low": 1031.43, + "close": 1035.96, + "volume": 746878.0 + }, + "2017-11-24": { + "open": 1035.87, + "high": 1043.18, + "low": 1035.0, + "close": 1040.61, + "volume": 536996.0 + }, + "2017-11-27": { + "open": 1040.0, + "high": 1055.46, + "low": 1038.44, + "close": 1054.21, + "volume": 1307881.0 + }, + "2017-11-28": { + "open": 1055.09, + "high": 1062.38, + "low": 1040.0, + "close": 1047.41, + "volume": 1424394.0 + }, + "2017-11-29": { + "open": 1042.68, + "high": 1044.08, + "low": 1015.65, + "close": 1021.66, + "volume": 2459426.0 + }, + "2017-11-30": { + "open": 1022.37, + "high": 1028.49, + "low": 1015.0, + "close": 1021.41, + "volume": 1724031.0 + }, + "2017-12-01": { + "open": 1015.8, + "high": 1022.49, + "low": 1002.02, + "close": 1010.17, + "volume": 1909566.0 + }, + "2017-12-04": { + "open": 1012.66, + "high": 1016.1, + "low": 995.57, + "close": 998.68, + "volume": 1906439.0 + }, + "2017-12-05": { + "open": 995.94, + "high": 1020.61, + "low": 988.28, + "close": 1005.15, + "volume": 2067318.0 + }, + "2017-12-06": { + "open": 1001.5, + "high": 1024.97, + "low": 1001.14, + "close": 1018.38, + "volume": 1271964.0 + }, + "2017-12-07": { + "open": 1020.43, + "high": 1034.24, + "low": 1018.07, + "close": 1030.93, + "volume": 1458242.0 + }, + "2017-12-08": { + "open": 1037.49, + "high": 1042.05, + "low": 1032.52, + "close": 1037.05, + "volume": 1290774.0 + }, + "2017-12-11": { + "open": 1035.5, + "high": 1043.8, + "low": 1032.05, + "close": 1041.1, + "volume": 1192838.0 + }, + "2017-12-12": { + "open": 1039.63, + "high": 1050.31, + "low": 1033.69, + "close": 1040.48, + "volume": 1279659.0 + }, + "2017-12-13": { + "open": 1046.12, + "high": 1046.66, + "low": 1038.38, + "close": 1040.61, + "volume": 1282677.0 + }, + "2017-12-14": { + "open": 1045.0, + "high": 1058.5, + "low": 1043.11, + "close": 1049.15, + "volume": 1558835.0 + }, + "2017-12-15": { + "open": 1054.61, + "high": 1067.62, + "low": 1049.5, + "close": 1064.19, + "volume": 3275931.0 + }, + "2017-12-18": { + "open": 1066.08, + "high": 1078.49, + "low": 1062.0, + "close": 1077.14, + "volume": 1554552.0 + }, + "2017-12-19": { + "open": 1075.2, + "high": 1076.84, + "low": 1063.55, + "close": 1070.68, + "volume": 1338725.0 + }, + "2017-12-20": { + "open": 1071.78, + "high": 1073.38, + "low": 1061.52, + "close": 1064.95, + "volume": 1268582.0 + }, + "2017-12-21": { + "open": 1064.95, + "high": 1069.33, + "low": 1061.79, + "close": 1063.63, + "volume": 995703.0 + }, + "2017-12-22": { + "open": 1061.11, + "high": 1064.2, + "low": 1059.44, + "close": 1060.12, + "volume": 755095.0 + }, + "2017-12-26": { + "open": 1058.07, + "high": 1060.12, + "low": 1050.2, + "close": 1056.74, + "volume": 761237.0 + }, + "2017-12-27": { + "open": 1057.39, + "high": 1058.37, + "low": 1048.05, + "close": 1049.37, + "volume": 1271911.0 + }, + "2017-12-28": { + "open": 1051.6, + "high": 1054.75, + "low": 1044.77, + "close": 1048.14, + "volume": 837121.0 + }, + "2017-12-29": { + "open": 1046.72, + "high": 1049.7, + "low": 1044.9, + "close": 1046.4, + "volume": 887511.0 + }, + "2018-01-02": { + "open": 1048.34, + "high": 1066.94, + "low": 1045.23, + "close": 1065.0, + "volume": 1237564.0 + }, + "2018-01-03": { + "open": 1064.31, + "high": 1086.29, + "low": 1063.21, + "close": 1082.48, + "volume": 1430170.0 + }, + "2018-01-04": { + "open": 1088.0, + "high": 1093.57, + "low": 1084.0, + "close": 1086.4, + "volume": 1004605.0 + }, + "2018-01-05": { + "open": 1094.0, + "high": 1104.25, + "low": 1092.0, + "close": 1102.23, + "volume": 1279123.0 + }, + "2018-01-08": { + "open": 1102.23, + "high": 1111.27, + "low": 1101.62, + "close": 1106.94, + "volume": 1047603.0 + }, + "2018-01-09": { + "open": 1109.4, + "high": 1110.57, + "low": 1101.23, + "close": 1106.26, + "volume": 902541.0 + }, + "2018-01-10": { + "open": 1097.1, + "high": 1104.6, + "low": 1096.11, + "close": 1102.61, + "volume": 1042793.0 + }, + "2018-01-11": { + "open": 1106.3, + "high": 1106.53, + "low": 1099.59, + "close": 1105.52, + "volume": 978292.0 + }, + "2018-01-12": { + "open": 1102.41, + "high": 1124.29, + "low": 1101.15, + "close": 1122.26, + "volume": 1720533.0 + }, + "2018-01-16": { + "open": 1132.51, + "high": 1139.91, + "low": 1117.83, + "close": 1121.76, + "volume": 1575261.0 + }, + "2018-01-17": { + "open": 1126.22, + "high": 1132.6, + "low": 1117.01, + "close": 1131.98, + "volume": 1202639.0 + }, + "2018-01-18": { + "open": 1131.41, + "high": 1132.51, + "low": 1117.5, + "close": 1129.79, + "volume": 1198234.0 + }, + "2018-01-19": { + "open": 1131.83, + "high": 1137.86, + "low": 1128.3, + "close": 1137.51, + "volume": 1778229.0 + }, + "2018-01-22": { + "open": 1137.49, + "high": 1159.88, + "low": 1135.11, + "close": 1155.81, + "volume": 1617975.0 + }, + "2018-01-23": { + "open": 1159.85, + "high": 1171.63, + "low": 1158.75, + "close": 1169.97, + "volume": 1333056.0 + }, + "2018-01-24": { + "open": 1177.33, + "high": 1179.86, + "low": 1161.05, + "close": 1164.24, + "volume": 1416625.0 + }, + "2018-01-25": { + "open": 1172.53, + "high": 1175.94, + "low": 1162.76, + "close": 1170.37, + "volume": 1480540.0 + }, + "2018-01-26": { + "open": 1175.08, + "high": 1175.84, + "low": 1158.11, + "close": 1175.84, + "volume": 2018755.0 + }, + "2018-01-29": { + "open": 1176.48, + "high": 1186.89, + "low": 1171.98, + "close": 1175.58, + "volume": 1378913.0 + }, + "2018-01-30": { + "open": 1167.83, + "high": 1176.52, + "low": 1163.52, + "close": 1163.69, + "volume": 1556346.0 + }, + "2018-01-31": { + "open": 1170.57, + "high": 1173.0, + "low": 1159.13, + "close": 1169.94, + "volume": 1538688.0 + }, + "2018-02-01": { + "open": 1162.61, + "high": 1174.0, + "low": 1157.52, + "close": 1167.7, + "volume": 2412114.0 + }, + "2018-02-02": { + "open": 1122.0, + "high": 1123.07, + "low": 1107.28, + "close": 1111.9, + "volume": 4857943.0 + }, + "2018-02-05": { + "open": 1090.6, + "high": 1110.0, + "low": 1052.03, + "close": 1055.8, + "volume": 3798301.0 + }, + "2018-02-06": { + "open": 1027.18, + "high": 1081.71, + "low": 1023.14, + "close": 1080.6, + "volume": 3447956.0 + }, + "2018-02-07": { + "open": 1081.54, + "high": 1081.78, + "low": 1048.26, + "close": 1048.58, + "volume": 2369232.0 + }, + "2018-02-08": { + "open": 1055.41, + "high": 1058.62, + "low": 1000.66, + "close": 1001.52, + "volume": 2859136.0 + }, + "2018-02-09": { + "open": 1017.25, + "high": 1043.97, + "low": 992.56, + "close": 1037.78, + "volume": 3505862.0 + }, + "2018-02-12": { + "open": 1048.0, + "high": 1061.5, + "low": 1040.93, + "close": 1051.94, + "volume": 2057718.0 + }, + "2018-02-13": { + "open": 1045.0, + "high": 1058.37, + "low": 1044.09, + "close": 1052.1, + "volume": 1265054.0 + }, + "2018-02-14": { + "open": 1048.95, + "high": 1071.72, + "low": 1046.75, + "close": 1069.7, + "volume": 1555787.0 + }, + "2018-02-15": { + "open": 1079.07, + "high": 1091.48, + "low": 1064.34, + "close": 1089.52, + "volume": 1843442.0 + }, + "2018-02-16": { + "open": 1088.41, + "high": 1104.67, + "low": 1088.31, + "close": 1094.8, + "volume": 1681612.0 + }, + "2018-02-20": { + "open": 1090.57, + "high": 1113.95, + "low": 1088.52, + "close": 1102.46, + "volume": 1423145.0 + }, + "2018-02-21": { + "open": 1106.47, + "high": 1133.97, + "low": 1106.33, + "close": 1111.34, + "volume": 1512910.0 + }, + "2018-02-22": { + "open": 1116.19, + "high": 1122.82, + "low": 1102.59, + "close": 1106.63, + "volume": 1317166.0 + }, + "2018-02-23": { + "open": 1112.64, + "high": 1127.28, + "low": 1104.71, + "close": 1126.79, + "volume": 1260968.0 + }, + "2018-02-26": { + "open": 1127.8, + "high": 1143.96, + "low": 1126.69, + "close": 1143.75, + "volume": 1559079.0 + }, + "2018-02-27": { + "open": 1141.24, + "high": 1144.04, + "low": 1118.0, + "close": 1118.29, + "volume": 1774080.0 + }, + "2018-02-28": { + "open": 1123.03, + "high": 1127.53, + "low": 1103.24, + "close": 1104.73, + "volume": 1882600.0 + }, + "2018-03-01": { + "open": 1107.87, + "high": 1110.12, + "low": 1067.0, + "close": 1069.52, + "volume": 2515910.0 + }, + "2018-03-02": { + "open": 1053.08, + "high": 1082.0, + "low": 1048.12, + "close": 1078.92, + "volume": 2271551.0 + }, + "2018-03-05": { + "open": 1075.14, + "high": 1097.1, + "low": 1069.0, + "close": 1090.93, + "volume": 1202174.0 + }, + "2018-03-06": { + "open": 1099.22, + "high": 1101.85, + "low": 1089.78, + "close": 1095.06, + "volume": 1532783.0 + }, + "2018-03-07": { + "open": 1089.19, + "high": 1112.22, + "low": 1085.48, + "close": 1109.64, + "volume": 1292537.0 + }, + "2018-03-08": { + "open": 1115.32, + "high": 1127.6, + "low": 1112.8, + "close": 1126.0, + "volume": 1355125.0 + }, + "2018-03-09": { + "open": 1136.0, + "high": 1160.8, + "low": 1132.46, + "close": 1160.04, + "volume": 2128038.0 + }, + "2018-03-12": { + "open": 1163.85, + "high": 1177.05, + "low": 1157.42, + "close": 1164.5, + "volume": 2172272.0 + }, + "2018-03-13": { + "open": 1170.0, + "high": 1176.76, + "low": 1133.33, + "close": 1138.17, + "volume": 1907171.0 + }, + "2018-03-14": { + "open": 1145.21, + "high": 1158.59, + "low": 1141.44, + "close": 1149.49, + "volume": 1291415.0 + }, + "2018-03-15": { + "open": 1149.96, + "high": 1161.08, + "low": 1134.54, + "close": 1149.58, + "volume": 1472226.0 + } + } +} \ No newline at end of file diff --git a/students/mattmaeda/stockscreener/exchange/tests.py b/students/mattmaeda/stockscreener/exchange/tests.py new file mode 100644 index 00000000..e143025f --- /dev/null +++ b/students/mattmaeda/stockscreener/exchange/tests.py @@ -0,0 +1,325 @@ +""" +Test for exchange +""" +import datetime +import json +import mock +import os +import iexfinance +from django.urls import reverse +from django.test import TestCase, RequestFactory +from requests import ConnectionError +from exchange.views import * +from exchange.models import Rule, Screen + +MY_PATH = os.path.abspath(os.path.dirname(__file__)) +TEST_DATA = os.path.join(MY_PATH, "testdata", "test_historical_data.json") + +BOGUS_SYMBOL = "BOGUSCOMPANY" +NON_EXISTENT = "IDONOTEXIST" +CONN_ERROR = "CONNERROR" + +XOVER_TEST = [ + { + 'name': 'Screen1', + 'desc': 'Screen 1', + 'pass': True, + 'rules': [ + { + 'criteria': '50MA crossover 200MA', + 'pass': True + } + ] + }, + { + 'name': 'Screen2', + 'desc': 'Screen 2', + 'pass': False, + 'rules': [ + { + 'criteria': '50MA crossunder 200MA', + 'pass': False + } + ] + } +] + + +# Create your tests here. +class IEXFinanceStock(object): + """ Mock object for iexfinance.Stock """ + def __init__(self, ticker): + self.ticker = ticker + + if ticker == CONN_ERROR: + raise ConnectionError("Test bad connection") + + + def get_company(self): + if self.ticker == BOGUS_SYMBOL: + return {"companyName": "Bogus Company"} + elif self.ticker == NON_EXISTENT: + raise iexfinance.utils.exceptions.IEXSymbolError("INVALID") + else: + raise ValueError("Ticker empty") + + +class IEXFinanceMock(object): + """ Mock object for iexfinance.get_historical_data """ + def __init__(self): + self.return_value = json.load(open(TEST_DATA)) + self.start = None + self.end = None + self.format = None + + + def get_historical_data(self, ticker, start=None, end=None, + output_format=None): + """ Returns the canned response data """ + self.start = start + self.end = end + self.format = output_format + return self.return_value + + +class ExchangeViewsTests(TestCase): + """ Validates exchange view functions """ + + + def setUp(self): + """ Setup variables for test """ + self.factory = RequestFactory() + self.crossunder = [] + self.crossover = [] + + for i in range(199): + self.crossunder.append({"close": 25.00}) + self.crossover.append({"close": 25.00}) + + self.crossunder.append({"close": 27.00}) + self.crossunder.append({"close": 10.00}) + self.crossover.append({"close": 23.00}) + self.crossover.append({"close": 40.00}) + + r1 = Rule(formula="MA50 XOVER MA200", criteria="50MA crossover 200MA") + r1.save() + + r2 = Rule(formula="MA50 XUNDER MA200", criteria="50MA crossunder 200MA") + r2.save() + + s1 = Screen(name="Screen1", description="Screen 1") + s1.save() + s1.rules.add(r1) + + s2 = Screen(name="Screen2", description="Screen 2") + s2.save() + s2.rules.add(r2) + + + def test_cross_under_valid(self): + """ Validate cross under check """ + self.assertIs(cross_under(self.crossunder, 50, 200), True) + + + def test_cross_under_invvalid(self): + """ Validate cross under check """ + self.assertIs(cross_under(self.crossover, 50, 200), False) + + + def test_cross_over_valid(self): + """ Validate cross over check """ + self.assertIs(cross_over(self.crossover, 50, 200), True) + + + def test_cross_over_invalid(self): + """ Validate cross over check """ + self.assertIs(cross_over(self.crossunder, 50, 200), False) + + + def test_prev_moving_average(self): + """ Validate previous moving average """ + self.assertEqual(prev_moving_average(self.crossover, 50), 24.96) + + + def test_last_moving_average(self): + """ Validate last moving average """ + self.assertEqual(last_moving_average(self.crossover, 50), 25.26) + + + def test_last_closing_price(self): + """ Validate last closing price """ + self.assertEqual(last_closing_price(self.crossover), 40.00) + + + def test_convert_variable_value_moving_average(self): + """ Validate conversion of variable value for moving average""" + self.assertEqual(convert_variable_value("MA50", self.crossover), 25.26) + + + def test_convert_variable_value_closing_price(self): + """ Validate conversion of variable value for closing price""" + self.assertEqual(convert_variable_value("CP", self.crossover), 40.00) + + + def test_convert_variable_value_unknown(self): + """ Validate conversion of variable value for invalid var""" + with self.assertRaises(Exception): + convert_variable_value("BAD50", self.crossover) + + + def test_get_variable_values_crossover(self): + """ Validate get_variable_values with crossover """ + (val1, val2) = get_variable_values("XOVER", "MA50", "MA200", + self.crossover) + self.assertEqual(val1, 50) + self.assertEqual(val2, 200) + + + def test_get_variable_values_crossunder(self): + """ Validate get_variable_values with crossunder """ + (val1, val2) = get_variable_values("XUNDER", "MA50", "MA200", + self.crossover) + self.assertEqual(val1, 50) + self.assertEqual(val2, 200) + + + def test_get_variable_values_non_crossing(self): + """ Validate get_variable_values for non crossing operators """ + (val1, val2) = get_variable_values("GT", "CP", "MA50", self.crossover) + self.assertEqual(val1, 40.00) + self.assertEqual(val2, 25.26) + + + def test_evaluate_formula_xover_valid(self): + """ Validate evaluate_formula with valid xover """ + self.assertIs(evaluate_formula("MA50 XOVER MA200", self.crossover), + True) + + + def test_evaluate_formula_xover_invalid(self): + """ Validate evaluate_formula with invalid xover """ + self.assertIs(evaluate_formula("MA50 XOVER MA200", self.crossunder), + False) + + + def test_evaluate_formula_xunder_valid(self): + """ Validate evaluate_formula with valid xunder """ + self.assertIs(evaluate_formula("MA50 XUNDER MA200", self.crossunder), + True) + + + def test_evaluate_formula_xunder_invalid(self): + """ Validate evaluate_formula with invalid xunder """ + self.assertIs(evaluate_formula("MA50 XUNDER MA200", self.crossover), + False) + + + def test_evaluate_formala_gt_valid(self): + """ Validate evaluate_formula with valid GT """ + self.assertIs(evaluate_formula("MA50 GT MA200", self.crossover), True) + + + def test_evaluate_formala_gt_invalid(self): + """ Validate evaluate_formula with invalid GT """ + self.assertIs(evaluate_formula("MA50 GT MA200", self.crossunder), False) + + + def test_evaluate_formala_gte_valid(self): + """ Validate evaluate_formula with valid GTE """ + self.assertIs(evaluate_formula("MA50 GTE MA200", self.crossover), True) + + + def test_evaluate_formala_gte_invalid(self): + """ Validate evaluate_formula with invalid GTE """ + self.assertIs(evaluate_formula("MA50 GTE MA200", self.crossunder), + False) + + + def test_evaluate_formala_gte_equal(self): + """ Validate evaluate_formula for GTE equality""" + self.assertIs(evaluate_formula("MA50 GTE MA50", self.crossunder), True) + + + def test_evaluate_formala_lt_valid(self): + """ Validate evaluate_formula with valid LT """ + self.assertIs(evaluate_formula("MA200 LT MA50", self.crossover), True) + + + def test_evaluate_formala_lt_invalid(self): + """ Validate evaluate_formula with invalid LT """ + self.assertIs(evaluate_formula("MA50 LT MA200", self.crossover), False) + + + def test_evaluate_formala_lte_valid(self): + """ Validate evaluate_formula with valid LTE """ + self.assertIs(evaluate_formula("MA200 LTE MA50", self.crossover), True) + + + def test_evaluate_formala_lte_invalid(self): + """ Validate evaluate_formula with invalid LTE """ + self.assertIs(evaluate_formula("MA200 LTE MA50", self.crossunder), + False) + + + def test_evaluate_formala_lte_equal(self): + """ Validate evaluate_formula for LTE equality""" + self.assertIs(evaluate_formula("MA50 LTE MA50", self.crossunder), True) + + + def test_get_stock_info_valid(self): + """ Validates that valid stock return value returns correct info """ + with mock.patch('exchange.views.Stock', IEXFinanceStock) as m: + response = get_stock_info(BOGUS_SYMBOL) + self.assertEqual("Bogus Company", response) + + + def test_get_stock_info_nonexistent(self): + """ Validates that valid stock return value returns correct info """ + with mock.patch('exchange.views.Stock', IEXFinanceStock) as m: + response = get_stock_info(NON_EXISTENT) + self.assertEqual(None, response) + + + def test_get_stock_info_empty(self): + """ Validates that valid stock return value returns correct info """ + with mock.patch('exchange.views.Stock', IEXFinanceStock) as m: + response = get_stock_info("") + self.assertEqual(None, response) + + + def test_get_price_history(self): + """ Validate the get_price_history function """ + with mock.patch('exchange.views.iexfinance', IEXFinanceMock()) as m: + history = get_price_history("GOOG") + self.assertEqual(len(history), 252) + today = datetime.date.today() + start = today - datetime.timedelta(days=365) + self.assertEqual(m.start, start) + self.assertEqual(m.end, today) + self.assertEqual(m.format, "json") + + + def test_index_connection_error(self): + """ Validates that index view handles connection error """ + with mock.patch('exchange.views.Stock', IEXFinanceStock) as m: + request = self.factory.post('exchange', {"ticker": CONN_ERROR}) + output = index(request) + self.assertContains(output, + "Currently experiencing connection problems") + + + def test_run_screens(self): + """ Validate run screens """ + res = run_screens(self.crossover) + self.assertEqual(res, XOVER_TEST) + + + @mock.patch('exchange.views.get_price_history') + def test_the_whole_enchilada(self, mock_history): + """ Test the whole thing """ + mock_history.return_value = self.crossover + with mock.patch('exchange.views.Stock', IEXFinanceStock) as m: + request = self.factory.post('exchange', {"ticker": BOGUS_SYMBOL}) + output = index(request) + self.assertContains(output, + "Here are your results for BOGUSCOMPANY") diff --git a/students/mattmaeda/stockscreener/exchange/urls.py b/students/mattmaeda/stockscreener/exchange/urls.py new file mode 100644 index 00000000..88a9caca --- /dev/null +++ b/students/mattmaeda/stockscreener/exchange/urls.py @@ -0,0 +1,7 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path('', views.index, name='index'), +] diff --git a/students/mattmaeda/stockscreener/exchange/views.py b/students/mattmaeda/stockscreener/exchange/views.py new file mode 100644 index 00000000..cc5886ff --- /dev/null +++ b/students/mattmaeda/stockscreener/exchange/views.py @@ -0,0 +1,266 @@ +""" +Contains the view logic for stockscreener app +""" +import datetime +import iexfinance +from django.shortcuts import render +from django.http import HttpResponse +from django.urls import reverse +from requests import ConnectionError +from iexfinance import Stock +from .models import Screen + +# Create your views here. +def index(request): + """ Main page """ + message ="Pleast enter a stock symbol (e.g., GOOG)" + response = None + + if request.method == "POST": + + try: + ticker = request.POST.get("ticker", "") + symbol = ticker.upper() + company = get_stock_info(symbol) + + if company is not None: + history = get_price_history(symbol) + response = run_screens(history) + message = "Here are your results for {}".format(symbol) + else: + message = "Invalid stock symbol '{}'".format(symbol) + + except ConnectionError as cxn: + message = ("Currently experiencing connection problems.\n" + "Please try again later.") + + return render(request, 'index.html', {"message": message, + "response": response}) + + +def get_stock_info(ticker): + """ Checks if the ticker is valid + + :param ticker String: the stock ticker + + :return: company name or None if invalid + :rtype: String + + """ + # Let connection errors percolate to the calling function + try: + stock = Stock(ticker.upper()) + return stock.get_company().get("companyName") + + except iexfinance.utils.exceptions.IEXSymbolError as exc: + return None + + except ValueError as ve: + # If empty + return None + + +def get_price_history(ticker): + """ Loads the price history of a stock for the last 252 days. Note, 252 + days is a limit of iexfinance function. + + :param ticker: the stock ticker symbol + :type: String + + :return: list of price objects + :rtype: list + + """ + today = datetime.date.today() + start = today - datetime.timedelta(days=365) + data = iexfinance.get_historical_data(ticker, start=start, end=today, + output_format="json") + quotes = data.get(ticker) + + history = [] + for date in quotes.keys(): + quote = quotes.get(date) + history.append({"date": date, + "open": quote.get("open"), + "close": quote.get("close"), + "high": quote.get("high"), + "low": quote.get("low")}) + + return history + + +def run_screens(history): + """ Given a stocks's price history, run screens to find any matches + + :param history list: list of price objects + + :return: screen results + :rtype: String + + """ + screen_results = [] + screens = Screen.objects.all() + + for screen in screens: + scr_res = { + "name": screen.name, + "desc": screen.description, + "pass": True, + "rules": [] + } + + for rule in screen.rules.all(): + res = evaluate_formula(rule.formula, history) + + scr_res["rules"].append({ + "criteria": rule.criteria, + "pass": res + }) + + if not res: + scr_res["pass"] = False + + screen_results.append(scr_res) + + return screen_results + + +def evaluate_formula(formula, history): + """ Evaluate rule formula + + :param formula String: var1 operator var2 format + + :return: if formula is true or not + :rtype: boolean + + """ + # Rule formula syntax var1 operator var2 + (var1, operator, var2) = formula.split(" ") + (value1, value2) = get_variable_values(operator, var1, var2, history) + + # For XOVER and XUNDER rules, assume var1 < var2 + if operator == "XOVER": + return cross_over(history, value1, value2) + elif operator == "XUNDER": + return cross_under(history, value1, value2) + elif operator == "GT": + return value1 > value2 + elif operator == "GTE": + return value1 >= value2 + elif operator == "LT": + return value1 < value2 + elif operator == "LTE": + return value1 <= value2 + else: + raise Exception("Unrecognized rule operator {}".format(operator)) + + +def get_variable_values(operator, var1, var2, history): + """ Depending on the operator, set var1 and var2 accordingly + + :param operator String: the operator + :param var1 String: first variable + :param var2 String: second variable + + :return: tuple of var1 and var2 value + :rtype: tuple + + """ + if operator in ["XOVER", "XUNDER"]: + return (int(var1.replace("MA", "")), int(var2.replace("MA", ""))) + else: + return(convert_variable_value(var1, history), + convert_variable_value(var2, history)) + + +def convert_variable_value(var, history): + """ Converts variable depending on type + + :param var String: the variable + + :return: the int or float value of the result + :rtype: int or float + + """ + if "MA" in var: + return last_moving_average(history, int(var.replace("MA", ""))) + elif var == "CP": + return last_closing_price(history) + else: + raise Exception("Unrecognized rule operator {}".format(var)) + + +def last_closing_price(history): + """ Gets the last closing price from stock's history + + :param history list: list of price objects + + :return: last closing price + :rtype: float + + """ + return history[-1].get("close") + + +def last_moving_average(history, days): + """ Returns the simple moving average of a stock over n days + + :param history list: list of price objects + :param days int: number of days + + :return: moving average + :rtype: float + + """ + return sum([p["close"] for p in history[(-1 * days):]])/days + + +def prev_moving_average(history, days): + """ Returns the simple moving average of stock over n days for previous day + + :param history list: list of price objects + :param days int: number of days + + :return: moving average + :rtype: float + + """ + return sum([p["close"] for p in history[(-1 * days) -1:-1]])/days + + +def cross_over(history, fast_moving_average, slow_moving_average): + """ Does faster moving average move from being less than slower moving + average yesterday to greater than today + + :param fast_moving_average int: fast moving average days modifier + :param slow_moving_average int: slow moving average days modifier + + :return: if there is a cross over + :rtype: boolean + + """ + last_fast = last_moving_average(history, fast_moving_average) + last_slow = last_moving_average(history, slow_moving_average) + prev_fast = prev_moving_average(history, fast_moving_average) + prev_slow = prev_moving_average(history, slow_moving_average) + + return prev_fast < prev_slow and last_fast > last_slow + + +def cross_under(history, fast_moving_average, slow_moving_average): + """ Does faster moving average move from being greather than slower moving + average yesterday to less than today + + :param fast_moving_average int: fast moving average days modifier + :param slow_moving_average int: slow moving average days modifier + + :return: if there is a cross under + :rtype: boolean + + """ + last_fast = last_moving_average(history, fast_moving_average) + last_slow = last_moving_average(history, slow_moving_average) + prev_fast = prev_moving_average(history, fast_moving_average) + prev_slow = prev_moving_average(history, slow_moving_average) + + return prev_fast > prev_slow and last_fast < last_slow diff --git a/students/mattmaeda/stockscreener/manage.py b/students/mattmaeda/stockscreener/manage.py new file mode 100755 index 00000000..434dc406 --- /dev/null +++ b/students/mattmaeda/stockscreener/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "stockscreener.settings") + try: + from django.core.management import execute_from_command_line + except ImportError: + # The above import may fail for some other reason. Ensure that the + # issue is really that Django is missing to avoid masking other + # exceptions on Python 2. + try: + import django + except ImportError: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) + raise + execute_from_command_line(sys.argv) diff --git a/students/mattmaeda/stockscreener/requirements.txt b/students/mattmaeda/stockscreener/requirements.txt new file mode 100644 index 00000000..5af872e3 --- /dev/null +++ b/students/mattmaeda/stockscreener/requirements.txt @@ -0,0 +1,15 @@ +certifi==2018.1.18 +chardet==3.0.4 +Django==2.0.3 +idna==2.6 +iexfinance==0.3.1 +mock==2.0.0 +numpy==1.14.2 +pandas==0.21.0 +pbr==3.1.1 +python-dateutil==2.7.0 +pytz==2018.3 +requests==2.18.4 +-e git+git@github.com:mattmaeda/IntroPython-2017.git@17c3eba63e2cf7b181adfcd18ed5290733c4e7cd#egg=screener&subdirectory=students/mattmaeda/stockscreener +six==1.11.0 +urllib3==1.22 diff --git a/students/mattmaeda/stockscreener/screener.sqlite3 b/students/mattmaeda/stockscreener/screener.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..f8eab01ef46ce98b5fdb1e25fb8e6fc28567004d GIT binary patch literal 163840 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 literal 0 HcmV?d00001 diff --git a/students/mattmaeda/stockscreener/setup.py b/students/mattmaeda/stockscreener/setup.py new file mode 100644 index 00000000..239743e0 --- /dev/null +++ b/students/mattmaeda/stockscreener/setup.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +""" + +setup.py for stock screener app + +""" + +import os + +from setuptools import setup + +def get_version(): + """ + Reads and returns the version string the from package __init__ + """ + with open(os.path.join("stockscreener", "__init__.py")) as init: + for line in init: + parts = line.strip().split("=") + if parts[0].strip() == "__version__": + return parts[-1].strip().strip("'").strip('"') + return None + +setup( + name="screener", + version=get_version(), + author="Matt Maeda", + author_email="matt@casamaeda.com", + packages=['stockscreener', + 'exchange'], + scripts=['bin/screener'], + license="LICENSE.txt", + description="Stock screener app", + long_description=open("README.md").read() +) diff --git a/students/mattmaeda/stockscreener/stockscreener/__init__.py b/students/mattmaeda/stockscreener/stockscreener/__init__.py new file mode 100644 index 00000000..58be9ed6 --- /dev/null +++ b/students/mattmaeda/stockscreener/stockscreener/__init__.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python +""" +stockscreener package +""" + +from pathlib import Path + +__version__ = "0.0.1" diff --git a/students/mattmaeda/stockscreener/stockscreener/settings.py b/students/mattmaeda/stockscreener/stockscreener/settings.py new file mode 100644 index 00000000..11633491 --- /dev/null +++ b/students/mattmaeda/stockscreener/stockscreener/settings.py @@ -0,0 +1,121 @@ +""" +Django settings for stockscreener project. + +Generated by 'django-admin startproject' using Django 1.11.7. + +For more information on this file, see +https://docs.djangoproject.com/en/1.11/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.11/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = ')1hxz_p-!(!k_ykricqm3i+siukcx%z^5s4%kp!60%4_v6=dse' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'exchange.apps.ExchangeConfig' +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'stockscreener.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'stockscreener.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.11/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'screener.sqlite3'), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/1.11/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.11/howto/static-files/ + +STATIC_URL = '/static/' diff --git a/students/mattmaeda/stockscreener/stockscreener/urls.py b/students/mattmaeda/stockscreener/stockscreener/urls.py new file mode 100644 index 00000000..cc97876c --- /dev/null +++ b/students/mattmaeda/stockscreener/stockscreener/urls.py @@ -0,0 +1,23 @@ +"""stockscreener URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.11/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(https://github.com/r'%5E'),%20views.home,%20name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(https://github.com/r'%5E),%20Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.conf.urls import url, include + 2. Add a URL to urlpatterns: url(https://github.com/r'%5Eblog/',%20include('blog.urls')) +""" +from django.conf.urls import url, include +from django.contrib import admin + +urlpatterns = [ + url(https://github.com/r'',%20include('exchange.urls')), + url(https://github.com/r'%5Eexchange/',%20include('exchange.urls')), + url(https://github.com/r'%5Eadmin/',%20admin.site.urls), +] diff --git a/students/mattmaeda/stockscreener/stockscreener/wsgi.py b/students/mattmaeda/stockscreener/stockscreener/wsgi.py new file mode 100644 index 00000000..00e7847a --- /dev/null +++ b/students/mattmaeda/stockscreener/stockscreener/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for stockscreener project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "stockscreener.settings") + +application = get_wsgi_application()