From b070bcdd9fedf0a002e43d3951e5e6017ec65b9c Mon Sep 17 00:00:00 2001 From: Charlie Date: Thu, 3 Oct 2024 10:50:07 -0700 Subject: [PATCH] add dash_auth (use hashed password for security - never store plain text password) --- indicators.py | 247 ++++++++++++++++++++++++++--------------------- requirements.txt | Bin 1962 -> 1998 bytes 2 files changed, 135 insertions(+), 112 deletions(-) diff --git a/indicators.py b/indicators.py index ce0dac3..b2db4fe 100644 --- a/indicators.py +++ b/indicators.py @@ -12,7 +12,7 @@ Subplot3 MACD TODO: validate the plots with online resource -Created on Mon Feb 17 14:50:17 2020 +Updated on Mon Sep. 30, 2024 Use one as buy/sell trigger and verify a bag of indicators to make final decision. Could also come up with a value that ties to the trading volume. @author: thomwang @@ -37,10 +37,24 @@ import json import io from flask_caching import Cache from dash.exceptions import PreventUpdate +import dash_auth import yahoo_fin.stock_info as si +import hashlib pd.options.mode.chained_assignment = None # default='warn' +def hash_password(password): + # Encode the password as bytes + password_bytes = password.encode('utf-8') + + # Use SHA-256 hash function to create a hash object + hash_object = hashlib.sha256(password_bytes) + + # Get the hexadecimal representation of the hash + password_hash = hash_object.hexdigest() + + return password_hash + # def fill_missing_data(df): # df.ffill(inplace=True) # df.bfilln(inplace=True) @@ -337,76 +351,75 @@ class security: return macd_line, macd_sig, macd_hist, norm_hist -def bollinger_sell(stock, wd=200): - """ - Parameters - ---------- - stock : TYPE class 'serurity' - wd : TYPE, int, optional - DESCRIPTION. Moving average windows. The default is 200. +# def bollinger_sell(stock, wd=200): +# """ +# Parameters +# ---------- +# stock : TYPE class 'serurity' +# wd : TYPE, int, optional +# DESCRIPTION. Moving average windows. The default is 200. - Returns - ------- - TYPE DataFrame - DESCRIPTION - +1 when stock price is above bollinger upper band - . -1 when vice versa. transition days are of value +3 and -3 - respectively. A value of -3 is a sell signal - """ - _, bol_up = stock.bollinger(wd) - # bol_up = bol_up[bol_up.columns[-1]].to_frame() - # bol_up = bol_up.iloc[:, [-1]] - sell = np.sign(stock.price.sub(bol_up.values)) - sell_diff = sell.diff() +# Returns +# ------- +# TYPE DataFrame +# DESCRIPTION - +1 when stock price is above bollinger upper band +# . -1 when vice versa. transition days are of value +3 and -3 +# respectively. A value of -3 is a sell signal +# """ +# _, bol_up = stock.bollinger(wd) +# # bol_up = bol_up[bol_up.columns[-1]].to_frame() +# # bol_up = bol_up.iloc[:, [-1]] +# sell = np.sign(stock.price.sub(bol_up.values)) +# sell_diff = sell.diff() - return sell.add(sell_diff.values) +# return sell.add(sell_diff.values) -def bollinger_buy(stock, wd=200): - """ - Parameters - ---------- - stock : TYPE class 'serurity' - wd : TYPE, int, optional - DESCRIPTION. Moving average windows. The default is 200. +# def bollinger_buy(stock, wd=200): +# """ +# Parameters +# ---------- +# stock : TYPE class 'serurity' +# wd : TYPE, int, optional +# DESCRIPTION. Moving average windows. The default is 200. - Returns - ------- - TYPE DataFrame - DESCRIPTION - +1 when stock price is above bollinger lower band - . -1 when vice versa. transition days are of value +3 and -3 - respectively. A value of +3 is a buy signal - """ - bol_low, _ = stock.bollinger(wd) - buy = np.sign(stock.price.sub(bol_low.values)) - buy_diff = buy.diff() +# Returns +# ------- +# TYPE DataFrame +# DESCRIPTION - +1 when stock price is above bollinger lower band +# . -1 when vice versa. transition days are of value +3 and -3 +# respectively. A value of +3 is a buy signal +# """ +# bol_low, _ = stock.bollinger(wd) +# buy = np.sign(stock.price.sub(bol_low.values)) +# buy_diff = buy.diff() - return buy.add(buy_diff.values) +# return buy.add(buy_diff.values) +# def simple_bollinger_strategy(stk): -def simple_bollinger_strategy(stk): +# # buy orders +# buy = bollinger_buy(stk, 190) +# buy_orders = buy[np.any(buy>2, axis=1)] - # buy orders - buy = bollinger_buy(stk, 190) - buy_orders = buy[np.any(buy>2, axis=1)] +# sell = bollinger_sell(stk, 190) +# sell_orders = sell[np.any(sell<-2, axis=1)] - sell = bollinger_sell(stk, 190) - sell_orders = sell[np.any(sell<-2, axis=1)] +# orders = pd.concat([buy_orders, sell_orders]) +# orders = orders.sort_index() - orders = pd.concat([buy_orders, sell_orders]) - orders = orders.sort_index() +# order_list = pd.DataFrame(columns = ['Date', 'Symbol', 'Order', 'Shares']) +# for index, row in orders.iterrows(): +# for sym in orders.columns.values: +# if row[sym] > 2: # buy order +# order_list = order_list.append({'Date' : index, 'Symbol' : sym, +# 'Order' : 'BUY', 'Shares' : 100}, ignore_index = True ) +# elif row[sym] < -2: # sell order +# order_list = order_list.append({'Date' : index, 'Symbol' : sym, +# 'Order' : 'SELL', 'Shares' : 100}, ignore_index = True ) +# order_list = order_list.set_index('Date') - order_list = pd.DataFrame(columns = ['Date', 'Symbol', 'Order', 'Shares']) - for index, row in orders.iterrows(): - for sym in orders.columns.values: - if row[sym] > 2: # buy order - order_list = order_list.append({'Date' : index, 'Symbol' : sym, - 'Order' : 'BUY', 'Shares' : 100}, ignore_index = True ) - elif row[sym] < -2: # sell order - order_list = order_list.append({'Date' : index, 'Symbol' : sym, - 'Order' : 'SELL', 'Shares' : 100}, ignore_index = True ) - order_list = order_list.set_index('Date') - - return order_list +# return order_list # def plot_against_sym(df, sym=['SPY']): # df_temp = df.copy() @@ -476,50 +489,50 @@ def get_sma_slope(stocks, wd = 50): return slope -def modified_bollinger_strategy(stk): +# def modified_bollinger_strategy(stk): - rsi = stk.rsi() - btemp = pd.DataFrame() - # buy orders - buy = bollinger_buy(stk, 190) - buy_orders = buy[np.any(buy>2, axis=1)] - for col in buy_orders.columns: - buy_stk = buy_orders[col] - buy_stk = buy_stk[buy_stk > 2] - buy_stk = buy_stk[rsi[col].loc[buy_stk.index] < 70] - btemp = btemp.join(buy_stk, how='outer') +# rsi = stk.rsi() +# btemp = pd.DataFrame() +# # buy orders +# buy = bollinger_buy(stk, 190) +# buy_orders = buy[np.any(buy>2, axis=1)] +# for col in buy_orders.columns: +# buy_stk = buy_orders[col] +# buy_stk = buy_stk[buy_stk > 2] +# buy_stk = buy_stk[rsi[col].loc[buy_stk.index] < 70] +# btemp = btemp.join(buy_stk, how='outer') - stemp = pd.DataFrame() - sell = bollinger_sell(stk, 190) - sell_orders = sell[np.any(sell<-2, axis=1)] - for col in sell_orders.columns: - sell_stk = sell_orders[col] - sell_stk = sell_stk[sell_stk < -2] - sell_stk = sell_stk[rsi[col].loc[sell_stk.index] > 30] - stemp = stemp.join(sell_stk, how='outer') +# stemp = pd.DataFrame() +# sell = bollinger_sell(stk, 190) +# sell_orders = sell[np.any(sell<-2, axis=1)] +# for col in sell_orders.columns: +# sell_stk = sell_orders[col] +# sell_stk = sell_stk[sell_stk < -2] +# sell_stk = sell_stk[rsi[col].loc[sell_stk.index] > 30] +# stemp = stemp.join(sell_stk, how='outer') - orders = pd.concat([btemp, stemp]) +# orders = pd.concat([btemp, stemp]) - # TODO - refine orders based on slope - # TODO - revine further based on other conditions (RSI, MACD) - # TODO - transaction shares determination +# # TODO - refine orders based on slope +# # TODO - revine further based on other conditions (RSI, MACD) +# # TODO - transaction shares determination - orders = orders.sort_index() +# orders = orders.sort_index() - order_list = pd.DataFrame(columns = ['Date', 'Symbol', 'Order', 'Shares']) - for index, row in orders.iterrows(): - for sym in orders.columns.values: - if row[sym] > 2: # buy order - order_list = order_list.append({'Date' : index, 'Symbol' : sym, - 'Order' : 'BUY', 'Shares' : 100}, ignore_index = True ) - elif row[sym] < -2: # sell order - order_list = order_list.append({'Date' : index, 'Symbol' : sym, - 'Order' : 'SELL', 'Shares' : 100}, ignore_index = True ) - order_list = order_list.set_index('Date') +# order_list = pd.DataFrame(columns = ['Date', 'Symbol', 'Order', 'Shares']) +# for index, row in orders.iterrows(): +# for sym in orders.columns.values: +# if row[sym] > 2: # buy order +# order_list = order_list.append({'Date' : index, 'Symbol' : sym, +# 'Order' : 'BUY', 'Shares' : 100}, ignore_index = True ) +# elif row[sym] < -2: # sell order +# order_list = order_list.append({'Date' : index, 'Symbol' : sym, +# 'Order' : 'SELL', 'Shares' : 100}, ignore_index = True ) +# order_list = order_list.set_index('Date') - return order_list +# return order_list -def intelligent_loop_plots(sym, tmp): +def intelligent_loop_plots(sym, stk_data): # Only plot ones that are standing out meaning: # 1. outside of bollinger bands or recently crossed over (within 9 days) # 2. RSI above 70 or below 30 @@ -533,10 +546,8 @@ def intelligent_loop_plots(sym, tmp): # symbol = ['AMZN', 'SPY', 'GOOG', 'BAC', 'BA', 'XLE', 'CTL', 'ATVI', 'JD',\ # 'COST', 'HD', 'UBER', 'XOM', 'UAL', 'LUV', 'T', 'WMT'] - # matplotlib.rcParams.update({'figure.max_open_warning': 0}) - - price = tmp["adjclose"] - vol = tmp["volume"] + price = stk_data["adjclose"] + vol = stk_data["volume"] stk = security(sym, price, vol) rsi = stk.rsi() @@ -646,24 +657,30 @@ def intelligent_loop_plots(sym, tmp): return data, vol.to_frame('_VOL'), macd, rsi, plot_indicator +VALID_USERNAME_PASSWORD_PAIRS = { + 'ce16559af2caf7bb54bebd57a1602e29ada331b3356004265abeab0e568278cc': + 'b4db36eb3f887ca0bc15de7feb0b41a9589b65dbd5325aedf1c76b3ee63b8871' +} + +def user_login(username, password): + if VALID_USERNAME_PASSWORD_PAIRS.get(hash_password(username)) == hash_password(password): + return True + return False if __name__ == "__main__": lb_year = 3 # years of stock data to retrieve plt_year = 2 # number of years data to plot lb_trigger = 5 # days to lookback for triggering events - end_date = dt.datetime.today() - start_date = end_date - dt.timedelta(days = 365 * lb_year) - plot_sd = end_date - dt.timedelta(days = 365 * plt_year) - plot_ed = end_date - - # test_sec_class() - # test_smooth() - # test_bollinger_sell() - # test_get_orders() # Initialize the app app = Dash() + dash_auth.BasicAuth( + app, + # VALID_USERNAME_PASSWORD_PAIRS, + auth_func=user_login, + secret_key="MK8dyS6PyDDuEuzrmqa7dJTJZ7eH2Jkh", + ) watchlist = get_watchlist() # symbols = watchlist.index.values.tolist() @@ -680,7 +697,7 @@ if __name__ == "__main__": # App layout app.layout = [ html.Div([ - html.Button('Reload Syms', id="reload-button", n_clicks=0, + html.Button('Reload', id="reload-button", n_clicks=0, style={'font-size': '12px', 'width': '120px', 'display': 'inline-block', 'margin-bottom': '10px', 'margin-right': '5px', 'height':'36px', 'verticalAlign': 'top'}), html.Div([ dcc.Dropdown(symbols, symbols[0], id='symbols_dropdown_list',), @@ -700,7 +717,8 @@ if __name__ == "__main__": id='interval-component', interval=3*1000, # in milliseconds n_intervals=0, - # max_intervals=len(symbols), + max_intervals=2*len(symbols), + # max_intervals=3, # for testing disabled=True, ), ] @@ -738,13 +756,14 @@ if __name__ == "__main__": # reload button callback @callback(Output('symbols_dropdown_list', 'options'), + Output('interval-component', 'n_intervals'), Input("reload-button", "n_clicks"),) def reload_syms(n): if n: watchlist = get_watchlist() symbols = (watchlist.index.values + " - " + watchlist["Sub Segment"]).tolist() - return symbols + return symbols, 0 return no_update # interval callback @@ -776,7 +795,11 @@ if __name__ == "__main__": # get data # tmp = si.get_data(sym, start_date=sd)[["adjclose", "volume"]] sym = col_chosen.split()[0] - tmp = fetch_stk_data(sym, start_date) + end_date = dt.datetime.today() + start_date = end_date - dt.timedelta(days = 365 * lb_year) + # plot_sd = end_date - dt.timedelta(days = 365 * plt_year) + # plot_ed = end_date + tmp = fetch_stk_data(sym, start_date) # cached function all data, vol, macd, rsi, plot_ind = intelligent_loop_plots(sym, tmp) fig = make_subplots( diff --git a/requirements.txt b/requirements.txt index b8fc103d671468888776c48e827f182c9bc716ea..87a047fb937a18769959c1047d41bdc0062a4540 100644 GIT binary patch delta 36 qcmZ3*e~y2H64T^Aj2y!842cY-3?&R147NaM#GuDuyjhZIA}av7od}o! delta 12 TcmX@dzlwi@64PckrVdsB9k2uQ