In [1]:
#!/usr/bin/env python
%matplotlib inline
from bs4 import BeautifulSoup
from glob import glob
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import cookielib
import json
import math
import matplotlib.pyplot as plt
import mechanize
import numpy as np
import os
import re
import requests
import string
import sys
import time
import unidecode
import unicodedata
import urllib
import urllib2
import pandas as pd
from pandas.io.data import get_quote_yahoo
import locale
locale.setlocale( locale.LC_ALL, '' )
Out[1]:
In [2]:
# codes = {}
# codes["AAPL"] = "individual"
# codes["EEM"] = "Emerging"
# codes["EFA"] = "Foreign"
# codes["GOOG"] = "individual"
# codes["GOOGL"] = "individual"
# codes["IVV"] = "Domestic"
# codes["RWR"] = "RealEstate"
# codes["SHY"] = "BndsShrt"
# codes["TIP"] = "USTIPS"
# codes["TLT"] = "BndsLng"
# with open("assetclass.json", 'w') as file_handle:
# json.dump(codes, file_handle)
In [3]:
# examples = [{"username": "".join(np.random.choice([letter for letter in string.digits], 8)),
# "passwd": "".join(np.random.choice([letter for letter in string.ascii_letters], 16)),
# "url": "https://trading.scottrade.com/default.aspx"},
# {"username": "".join(np.random.choice([letter for letter in string.digits], 8)),
# "passwd": "".join(np.random.choice([letter for letter in string.ascii_letters], 16)),
# "url": "https://trading.scottrade.com/default.aspx"}]
# with open("config-example.json", 'w') as file_handle:
# json.dump(examples, file_handle)
In [4]:
with open("config.json", 'r') as file_handle:
credentials = json.load(file_handle)
with open("assetclass.json", 'r') as file_handle:
codes = json.load(file_handle)
In [6]:
records = {}
for credential in credentials:
time.sleep(5)
driver = webdriver.Chrome('/usr/bin/chromedriver')
driver.get(credential['url'])
elem = driver.find_element_by_id("ctl00_body_Login1_txtAccountNumber")
elem.send_keys(credential['username'])
elem = driver.find_element_by_id("ctl00_body_Login1_txtPassword")
elem.send_keys(credential['passwd'])
elem = driver.find_element_by_id("ctl00_body_sibLogOn")
elem.click()
content = driver.page_source
soup = BeautifulSoup(content)
driver.close()
tabulka = soup.find("table", {"class" : "hpc-table w-Positions"})
rows = tabulka.findAll('tr')
for tr in rows[1:]:
cols = tr.findAll('td')
symbol, qty, last, pchg, value, dchg = [c.text for c in cols]
qty = float(qty)
last = float(last)
value = float("".join([number for number in unidecode.unidecode(value) if number in string.digits or number in "."]))
if symbol not in records:
records[symbol] = {}
records[symbol]['shares'] = qty
records[symbol]['assetClass'] = codes[symbol]
else:
records[symbol]['shares'] += qty
tabtop = soup.find("table", {"class" : "hpc-table hpc-balances"})
row = tabtop.find('tr')
cash = float("".join([number for number in unidecode.unidecode(row.findAll('td')[1].text) if number in string.digits or number in "."]))
if "CASH" not in records:
records["CASH"] = cash
else:
records["CASH"] += cash
In [22]:
# row = tabtop.find('tr')
# for index, row in enumerate(tabtop.findAll('tr')):
# print index, row
row2 = tabtop.findAll('tr')[1]
print row2.findAll('td')[1].text
# print row2[0].findAll('td')
In [ ]:
In [ ]:
In [6]:
class Portfolio(object):
def __init__(self):
self.ideal_allocation = {}
self.stocks_owned = {}
self.class_total = {}
self.cash = 0.0
self.classification = {}
self.current_asset_percentages = []
self.core_total = 0.0
self.total = 0.0
self.tolerance = 3.5 # percentage off ideal before recommended action
pass
def get_ideal_allocation(self, infile):
"""Reads in file of ideal portfolio allocation.
Use 1-word (no spaces) for asset class.
"tolerance" is a special word which gives the tolerance level
before a rebalance is recommended."""
with open(infile, 'r') as fh:
for line in fh:
if line.split()[0] == "tolerance":
self.tolerance = float(line.split()[1])
else:
self.ideal_allocation[line.split()[0]] = float(line.split()[1])
self.class_total[line.split()[0]] = 0.0
def get_account_details(self, infiles):
for infile in infiles:
with open(infile, 'r') as fh:
for line in fh:
name = line.split()[0]
if name == 'CASH':
self.cash += float(line.split()[1].strip("$"))
else:
if name not in self.stocks_owned:
self.stocks_owned[name] = {}
self.stocks_owned[name]['shares'] = 0.0
self.stocks_owned[name]['shares'] += float(line.split()[1])
self.stocks_owned[name]['assetClass'] = line.split()[2]
else:
self.stocks_owned[name]['shares'] += float(line.split()[1])
self.stocks_owned[name]['assetClass'] = line.split()[2]
def parse_account_details(self, webdict):
for name in webdict:
if name == 'CASH':
self.cash += webdict[name]
else:
if name not in self.stocks_owned:
self.stocks_owned[name] = {}
self.stocks_owned[name]['shares'] = 0.0
self.stocks_owned[name]['shares'] += webdict[name]['shares']
self.stocks_owned[name]['assetClass'] = webdict[name]['assetClass']
else:
self.stocks_owned[name]['shares'] += webdict[name]['shares']
self.stocks_owned[name]['assetClass'] = webdict[name]['assetClass']
def get_stock_prices(self):
dataframe = get_quote_yahoo([stock for stock in self.stocks_owned])
for stock in self.stocks_owned:
self.stocks_owned[stock]['price'] = dataframe.ix[stock]['last']
def get_core_total(self):
self.core_total = 0.0
self.total = 0.0
self.core_total += self.cash
self.total += self.cash
for stock in self.stocks_owned:
temp_amount = self.stocks_owned[stock]['price'] * self.stocks_owned[stock]['shares']
if self.stocks_owned[stock]['assetClass'] in self.ideal_allocation:
self.core_total += temp_amount
self.class_total[self.stocks_owned[stock]['assetClass']] += temp_amount
self.total += temp_amount
else:
self.total += temp_amount
pass
def get_current_allocation(self):
"""Remember same stock can't have two assetClasses."""
for stock in self.stocks_owned:
if self.stocks_owned[stock]['assetClass'] in self.ideal_allocation:
temp_asset = self.stocks_owned[stock]['assetClass']
self.current_asset_percentages.append((stock, self.class_total[temp_asset] / self.core_total * 100. - self.ideal_allocation[temp_asset], temp_asset))
def get_recommendations(self):
"""Print recommendations."""
print "Recommended actions:"
for st, perc, asset in sorted(self.current_asset_percentages, key=lambda x: np.abs(x[1]), reverse=True):
shares = round(self.core_total * perc / 100. / self.stocks_owned[st]['price'], 0)
if np.abs(perc) >= self.tolerance:
if shares > 0:
print "Sell:", int(np.abs(shares)), st, asset, round(perc,1)
if shares < 0:
print "Buy:", int(np.abs(shares)), st, asset, round(perc,1)
else:
print "W/in tol:",
if shares > 0.0:
print "Sell", int(np.abs(shares)), st, asset, round(perc,1)
else:
print "Buy", int(np.abs(shares)), st, asset, round(perc,1)
pass
def push_recommendations(self):
"""Pushover recommendations."""
priority = 0
return_string = ""
return_string = '\n'.join([return_string, "Recommended actions:", '\n'])
for st, perc, asset in sorted(self.current_asset_percentages, key=lambda x: x[1], reverse=True):
shares = round(self.core_total * perc / 100. / self.stocks_owned[st]['price'], 0)
if np.abs(perc) >= self.tolerance:
priority = 1
if shares > 0:
return_string = ' '.join([return_string, "Sell:", str(int(np.abs(shares))), str(st), str(asset), str(round(perc,1)), '\n'])
if shares < 0:
return_string = ' '.join([return_string, "Buy:", str(int(np.abs(shares))), str(st), str(asset), str(round(perc,1)), '\n'])
else:
return_string = ' '.join([return_string, "W/in tol:", ])
if shares > 0.0:
return_string = ' '.join([return_string, "Sell", str(int(np.abs(shares))), str(st), str(asset), str(round(perc,1)), '\n'])
else:
return_string = ' '.join([return_string, "Buy", str(int(np.abs(shares))), str(st), str(asset), str(round(perc,1)), '\n'])
return return_string, priority
def get_summary(self):
print "Cash:", locale.currency(self.cash, grouping=True)
print "Core Total:", locale.currency(self.core_total, grouping=True)
print "Total:", locale.currency(self.total, grouping=True)
pass
def push_summary(self):
"""Pushover summary."""
return_string = ""
return_string = ''.join([return_string, "Cash: ", locale.currency(self.cash, grouping=True), "\n"])
return_string = ''.join([return_string, "Core Total: ", locale.currency(self.core_total, grouping=True), "\n"])
return_string = ''.join([return_string, "Total: ", locale.currency(self.total, grouping=True), "\n"])
return return_string
def detailed_summary(self):
for stock in self.stocks_owned:
print stock, locale.currency(self.stocks_owned[stock]['price'] * self.stocks_owned[stock]['shares'], grouping=True)
pass
In [7]:
whatever = Portfolio()
whatever.parse_account_details(records)
whatever.get_ideal_allocation("/Users/jwhitmore/github/RebalanceAssetAllocation/ideal-allocation.txt")
whatever.get_stock_prices()
whatever.get_core_total()
whatever.get_current_allocation()
In [8]:
whatever.get_summary()
In [9]:
whatever.get_recommendations()
In [10]:
whatever.detailed_summary()
In [11]:
records
Out[11]:
In [12]:
codes
Out[12]:
In [13]:
whatever.current_asset_percentages
Out[13]:
In [ ]: