Skip to content

Commit 329960e

Browse files
author
Jason
committed
gcp initial setup, python3 flask.
1 parent c547e14 commit 329960e

File tree

10 files changed

+430
-26
lines changed

10 files changed

+430
-26
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# robinhood-ext
2+
This project is to implement some features that Robinhood lack of, for example, insightful/interactive visualization, news NLP analytics, trade execution/strategies. This is connected to Robinhood via an unofficial python APIs in this [repo](https://github.com/Jamonek/Robinhood).
3+
4+
## Run Locally
5+
Go into the project directory, and start the FLask server by python.
6+
```
7+
python main.py
8+
```
9+
10+
## Deploy to Google Cloud App Enginee
11+
The project is ready for GCP AppEng. You should install cloud SDK in your computer, then push by below command.
12+
```
13+
gcloud app deploy
14+
```

app.yaml

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1-
runtime: python27
2-
api_version: 1
3-
threadsafe: true
1+
runtime: python
2+
env: flex
3+
entrypoint: gunicorn -b :$PORT main:app
44

5-
libraries:
6-
- name: ssl
7-
version: latest
5+
runtime_config:
6+
python_version: 3
87

9-
handlers:
10-
- url: /static
11-
static_dir: static
12-
- url: /.*
13-
script: server.app
8+
# This sample incurs costs to run on the App Engine flexible environment.
9+
# The settings below are to reduce costs during testing and are not appropriate
10+
# for production use. For more information, see:
11+
# https://cloud.google.com/appengine/docs/flexible/python/configuring-your-app-with-app-yaml
12+
manual_scaling:
13+
instances: 1
14+
resources:
15+
cpu: 1
16+
memory_gb: 0.5
17+
disk_size_gb: 10

dashboard.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
from Robinhood import Robinhood
2+
import pandas as pd
3+
from collections import defaultdict
4+
5+
class InfoRetriever:
6+
INTERESTED_STOCK_ATTR = [
7+
'name', 'symbol', 'last_trade_price', 'quantity_held', 'dividend_yield', 'pe_ratio', 'pb_ratio',
8+
'open', 'high', 'low', 'volume', 'float', 'high_52_weeks', 'low_52_weeks', 'description',
9+
'sector', 'industry', 'market_cap'
10+
]
11+
12+
def __init__(self, trader):
13+
self.trader = trader
14+
15+
def get_instruments_quant_owned(self):
16+
securities_owned = self.trader.securities_owned()['results']
17+
instruments_quant = []
18+
for sec in securities_owned:
19+
instrument_id, quantity = sec['instrument'].rsplit('/', 2)[-2], float(sec['quantity'])
20+
instruments_quant.append((instrument_id, quantity))
21+
return instruments_quant
22+
23+
def get_stock_symbols_owned(self):
24+
instruments_quant = self.get_instruments_quant_owned()
25+
instrument_ids = []
26+
for instrument_id, quant in instruments_quant:
27+
instrument_ids.append(instrument_id)
28+
return self.get_stock_symbols(instrument_ids)
29+
30+
def get_stock_symbols(self, instruments):
31+
symbols = []
32+
for instrument in instruments:
33+
symbols.append(self.get_stock_symbol(instrument))
34+
return symbols
35+
36+
def get_stock_symbol(self, instrument_id):
37+
instrument_details = self.trader.instrument(instrument_id)
38+
return (instrument_details['symbol'], instrument_details['simple_name'])
39+
40+
def quotes(self, symbols):
41+
return self.trader.quotes_data(symbols)
42+
43+
def fundamentals(self, symbols):
44+
fundamentals = []
45+
for symbol in symbols:
46+
fundamentals.append(self.trader.fundamentals(symbol))
47+
return fundamentals
48+
49+
def portfolio_holdings(self):
50+
instruments_quant = self.get_instruments_quant_owned()
51+
stocks_owned = self.get_stock_symbols_owned()
52+
stock_symbols = [symbol for symbol, name in stocks_owned]
53+
quotes = self.quotes(stock_symbols)
54+
fundamentals = self.fundamentals(stock_symbols)
55+
56+
df_rows = []
57+
for (symbol, name), (instr, quant), quote, fundamental \
58+
in zip(stocks_owned, instruments_quant, quotes, fundamentals):
59+
merged_quote_fundamental_dict = quote.copy()
60+
merged_quote_fundamental_dict.update(fundamental)
61+
merged_quote_fundamental_dict['instrument_id'] = instr
62+
merged_quote_fundamental_dict['quantity_held'] = quant
63+
merged_quote_fundamental_dict['name'] = name
64+
65+
df_row = {}
66+
for attribute in InfoRetriever.INTERESTED_STOCK_ATTR:
67+
df_row[attribute] = merged_quote_fundamental_dict[attribute]
68+
df_rows.append(df_row)
69+
70+
return pd.DataFrame(df_rows)
71+
72+
def get_dashboard_data(user, password):
73+
my_trader = Robinhood()
74+
logged_in = my_trader.login(username=user, password=password)
75+
76+
retriever = InfoRetriever(my_trader)
77+
portfolio_holdings = retriever.portfolio_holdings()
78+
79+
total_port_value = 0
80+
sector_mkt_val = defaultdict(int)
81+
for _, row in portfolio_holdings.iterrows():
82+
total_port_value += float(row['last_trade_price']) * float(row['quantity_held'])
83+
sector_mkt_val[row['sector']] += float(row['last_trade_price']) * float(row['quantity_held'])
84+
85+
sector_mkt_val_percent = {}
86+
for sector, val in sector_mkt_val.items():
87+
sector_mkt_val_percent[sector] = val / total_port_value * 100
88+
89+
data = []
90+
for _, row in portfolio_holdings.iterrows():
91+
stock_info = {
92+
'name': row['name'],
93+
'symbol': row['symbol'],
94+
'market_cap': float(row['market_cap']),
95+
'pe_ratio': -6 if row['pe_ratio'] is None or float(row['pe_ratio']) < 0 else float(row['pe_ratio']),
96+
'dividend_yield': 0 if row['dividend_yield'] is None else float(row['dividend_yield']),
97+
'holding_val': float(row['last_trade_price']) * float(row['quantity_held']),
98+
'last_trade_price': float(row['last_trade_price']),
99+
'quantity_held': float(row['quantity_held']),
100+
'port_percent': float(row['last_trade_price']) * float(row['quantity_held']) / total_port_value * 100,
101+
'industry': row['industry'],
102+
'sector': row['sector'],
103+
'sector_port_percent': sector_mkt_val_percent[row['sector']],
104+
'description': row['description'],
105+
}
106+
data.append(stock_info)
107+
108+
return data

main.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from flask import Flask, render_template, request, jsonify
2+
from dashboard import get_dashboard_data
3+
4+
app = Flask(__name__)
5+
6+
# Dashboard
7+
@app.route('/api/data/dashboard', methods=["POST"])
8+
def api_dashboard_data():
9+
username = request.form['username']
10+
password = request.form['password']
11+
return jsonify(get_dashboard_data(username, password))
12+
13+
@app.route('/', methods=['GET'])
14+
def dashboard():
15+
return render_template('dashboard.html')
16+
17+
18+
if __name__ == '__main__':
19+
# This is used when running locally. Gunicorn is used to run the
20+
# application on Google App Engine. See entrypoint in app.yaml.
21+
app.run(host='127.0.0.1', port=8080, debug=True)

requirements.txt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
1-
Flask==0.12.4
2-
Werkzeug<0.13.0,>=0.12.0
1+
Flask==0.12.2
2+
gunicorn==19.7.1
3+
six
4+
requests
5+
python-dateutil
6+
pandas
7+
numpy

server.py

Lines changed: 0 additions & 13 deletions
This file was deleted.

static/dashboard.css

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
.chart rect {
2+
fill: steelblue;
3+
}
4+
.chart rect:hover {
5+
fill: turquoise;
6+
}
7+
.chart .rectM {
8+
stroke: green;
9+
stroke-width: 1;
10+
fill: green;
11+
fill-opacity: .2;
12+
}
13+
.chart .rectM:hover {
14+
fill: green;
15+
fill-opacity: .5;
16+
}
17+
.chart text {
18+
font: 10px sans-serif;
19+
}
20+
.chart .title {
21+
font: 15px sans-serif;
22+
}
23+
.axis path,
24+
.axis line {
25+
fill: none;
26+
stroke: #000;
27+
shape-rendering: crispEdges;
28+
}
29+
.chart .tooltip {
30+
display: inline-block;
31+
text-align: center;
32+
color: white;
33+
background-color: darkslategray;
34+
padding: 5px 5px 5px 5px;
35+
}
36+
.tooltip .title {
37+
font-weight: bold;
38+
}
39+
#marker {
40+
fill: red;
41+
font-weight: bold;
42+
font: 13px sans-serif;
43+
}

0 commit comments

Comments
 (0)