Cleanup (no new features)

This commit is contained in:
George 2024-10-03 21:05:39 -07:00
parent b070bcdd9f
commit f25156896d

View File

@ -21,20 +21,13 @@ Could also come up with a value that ties to the trading volume.
import pandas as pd import pandas as pd
import numpy as np import numpy as np
import datetime as dt import datetime as dt
# from util import get_data, get_price_volume, plot_data, get_watchlist from util import get_watchlist
from util import get_price_volume, plot_data, get_watchlist
# from marketsim import compute_portvals, compute_portfolio_stats, normalize_data
# import matplotlib.pyplot as plt
# import matplotlib
from numpy.fft import fft, ifft from numpy.fft import fft, ifft
import scipy.signal as sig import scipy.signal as sig
import plotly.express as px import plotly.express as px
from plotly.subplots import make_subplots from plotly.subplots import make_subplots
# import plotly.graph_objects as go
from dash import Dash, html, dcc, callback, Output, Input, State, no_update from dash import Dash, html, dcc, callback, Output, Input, State, no_update
from waitress import serve from waitress import serve
import json
import io
from flask_caching import Cache from flask_caching import Cache
from dash.exceptions import PreventUpdate from dash.exceptions import PreventUpdate
import dash_auth import dash_auth
@ -106,7 +99,6 @@ def smooth(price, hsize=10, sigma=3):
return data return data
class security: class security:
""" """
This can be a list of stocks, bonds, or otherinvestment vehicles. This can be a list of stocks, bonds, or otherinvestment vehicles.
@ -350,100 +342,6 @@ class security:
norm_hist = macd_hist.div(macd_long.values) norm_hist = macd_hist.div(macd_long.values)
return macd_line, macd_sig, macd_hist, norm_hist 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.
# 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)
# 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()
# return buy.add(buy_diff.values)
# def simple_bollinger_strategy(stk):
# # 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)]
# 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')
# return order_list
# def plot_against_sym(df, sym=['SPY']):
# df_temp = df.copy()
# df_sym = get_data(sym, pd.to_datetime(df_temp.index.values), addSPY=False)
# df_temp[sym[0]] = df_sym.values
# df_temp = normalize_data(df_temp)
# plot_data(df_temp)
# return df_sym
# def test_bollinger_sell():
# sd = dt.datetime(2010,1,1)
# # ed = dt.datetime.today()
# ed = dt.datetime(2012,12,31)
# symbol = ['XOM']
# dates = dates = pd.date_range(sd, ed)
# prices = get_data(symbol, dates, addSPY=False)
# # prices = prices.dropna()
# stk = security(prices)
# sell = bollinger_sell(stk)
# plot_data(sell)
# buy = bollinger_buy(stk, 190)
# plot_data(buy)
def get_crossing(stocks): def get_crossing(stocks):
""" """
Parameters Parameters
@ -488,49 +386,9 @@ def get_sma_slope(stocks, wd = 50):
return slope return slope
LB_YEAR = 3 # years of stock data to retrieve
# def modified_bollinger_strategy(stk): # PLT_YEAR = 2 # number of years data to plot
LB_TRIGGER = 5 # days to lookback for triggering events
# 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')
# orders = pd.concat([btemp, stemp])
# # TODO - refine orders based on slope
# # TODO - revine further based on other conditions (RSI, MACD)
# # TODO - transaction shares determination
# 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')
# return order_list
def intelligent_loop_plots(sym, stk_data): def intelligent_loop_plots(sym, stk_data):
# Only plot ones that are standing out meaning: # Only plot ones that are standing out meaning:
@ -563,25 +421,25 @@ def intelligent_loop_plots(sym, stk_data):
# print('{:5}: '.format(sym), end = '') # print('{:5}: '.format(sym), end = '')
# RSI outside window (over bought / over sold) # RSI outside window (over bought / over sold)
rsi_tail = rsi.tail(lb_trigger) rsi_tail = rsi.tail(LB_TRIGGER)
if (rsi_tail >= 70).any() or (rsi_tail <= 30).any(): if (rsi_tail >= 70).any() or (rsi_tail <= 30).any():
# print('--RSI', end = '') # print('--RSI', end = '')
plot_indicator += 'RSI, ' plot_indicator += 'RSI, '
# VoRSI outside window (over bought / over sold) # VoRSI outside window (over bought / over sold)
vorsi_tail = vorsi.tail(lb_trigger) vorsi_tail = vorsi.tail(LB_TRIGGER)
if (vorsi_tail >= 70).any() or (vorsi_tail <= 30).any(): if (vorsi_tail >= 70).any() or (vorsi_tail <= 30).any():
# print('--VoRSI', end = '') # print('--VoRSI', end = '')
plot_indicator += 'VoRSI, ' plot_indicator += 'VoRSI, '
# Normalized MACD histogram out of 3% range # Normalized MACD histogram out of 3% range
norm_hist_tail = abs(norm_hist.tail(lb_trigger)) norm_hist_tail = abs(norm_hist.tail(LB_TRIGGER))
if (abs(norm_hist_tail) >= 0.02).any(): if (abs(norm_hist_tail) >= 0.02).any():
# print('--MACD/R', end = '') # outside normal range # print('--MACD/R', end = '') # outside normal range
plot_indicator += 'MACD/R, ' plot_indicator += 'MACD/R, '
# MACD histogram zero crossing # MACD histogram zero crossing
macd_hist_tail = macd_hist.tail(lb_trigger) macd_hist_tail = macd_hist.tail(LB_TRIGGER)
macd_hist_sign = np.sign(macd_hist_tail) macd_hist_sign = np.sign(macd_hist_tail)
macd_hist_diff = macd_hist_sign.diff() macd_hist_diff = macd_hist_sign.diff()
if (abs(macd_hist_diff) > 1).any(): if (abs(macd_hist_diff) > 1).any():
@ -589,7 +447,7 @@ def intelligent_loop_plots(sym, stk_data):
plot_indicator += 'MACD, ' plot_indicator += 'MACD, '
# Stock price crosses SMA50 # Stock price crosses SMA50
sma50_cross_tail = sma50.tail(lb_trigger) - price.tail(lb_trigger) sma50_cross_tail = sma50.tail(LB_TRIGGER) - price.tail(LB_TRIGGER)
sma50_cross_sign = np.sign(sma50_cross_tail) sma50_cross_sign = np.sign(sma50_cross_tail)
sma50_cross_diff = sma50_cross_sign.diff() sma50_cross_diff = sma50_cross_sign.diff()
if (abs(sma50_cross_diff) > 1).any(): if (abs(sma50_cross_diff) > 1).any():
@ -597,7 +455,7 @@ def intelligent_loop_plots(sym, stk_data):
plot_indicator += 'SMA50, ' plot_indicator += 'SMA50, '
# Death cross or golden cross - SMA50 vs SMA200 # Death cross or golden cross - SMA50 vs SMA200
sma_cross_tail = sma50.tail(lb_trigger) - sma200.tail(lb_trigger).values sma_cross_tail = sma50.tail(LB_TRIGGER) - sma200.tail(LB_TRIGGER).values
sma_cross_sign = np.sign(sma_cross_tail) sma_cross_sign = np.sign(sma_cross_tail)
sma_cross_diff = sma_cross_sign.diff() sma_cross_diff = sma_cross_sign.diff()
if (abs(sma_cross_diff) > 1).any(): if (abs(sma_cross_diff) > 1).any():
@ -605,9 +463,9 @@ def intelligent_loop_plots(sym, stk_data):
plot_indicator += 'Golden/Death, ' plot_indicator += 'Golden/Death, '
# Price outside bollinger band or crossing # Price outside bollinger band or crossing
price_tail = price.tail(lb_trigger) price_tail = price.tail(LB_TRIGGER)
bol_low_tail = bol_low.tail(lb_trigger) bol_low_tail = bol_low.tail(LB_TRIGGER)
bol_up_tail = bol_up.tail(lb_trigger) bol_up_tail = bol_up.tail(LB_TRIGGER)
price_high = price_tail - bol_up_tail.values price_high = price_tail - bol_up_tail.values
price_low = price_tail - bol_low_tail.values price_low = price_tail - bol_low_tail.values
if (price_high >= 0).any() or (price_low <= 0).any(): if (price_high >= 0).any() or (price_low <= 0).any():
@ -615,7 +473,7 @@ def intelligent_loop_plots(sym, stk_data):
plot_indicator += 'Bollinger, ' plot_indicator += 'Bollinger, '
# Price cross 200 day moving average # Price cross 200 day moving average
sma200_tail = sma200.tail(lb_trigger) sma200_tail = sma200.tail(LB_TRIGGER)
sma200_cross = price_tail - sma200_tail.values sma200_cross = price_tail - sma200_tail.values
sma200_cross_sign = np.sign(sma200_cross) sma200_cross_sign = np.sign(sma200_cross)
sma200_cross_diff = sma200_cross_sign.diff() sma200_cross_diff = sma200_cross_sign.diff()
@ -624,7 +482,7 @@ def intelligent_loop_plots(sym, stk_data):
plot_indicator += 'SMA200, ' plot_indicator += 'SMA200, '
# Large trading volume trigger # Large trading volume trigger
volume_tail = vol.tail(lb_trigger) volume_tail = vol.tail(LB_TRIGGER)
vol_mean = vol.tail(50).mean() vol_mean = vol.tail(50).mean()
vol_std = vol.tail(50).std() vol_std = vol.tail(50).std()
if ((volume_tail[1] - vol_mean - 2*vol_std) > 0).any(): if ((volume_tail[1] - vol_mean - 2*vol_std) > 0).any():
@ -667,214 +525,206 @@ def user_login(username, password):
return True return True
return False return False
if __name__ == "__main__": # Initialize the app
app = Dash()
dash_auth.BasicAuth(
app,
# VALID_USERNAME_PASSWORD_PAIRS,
auth_func=user_login,
secret_key="MK8dyS6PyDDuEuzrmqa7dJTJZ7eH2Jkh",
)
lb_year = 3 # years of stock data to retrieve watchlist = get_watchlist()
plt_year = 2 # number of years data to plot # symbols = watchlist.index.values.tolist()
lb_trigger = 5 # days to lookback for triggering events symbols = (watchlist.index.values + " - " + watchlist["Sub Segment"]).tolist()
# Initialize the app CACHE_CONFIG = {'CACHE_TYPE': 'SimpleCache'}
app = Dash() cache = Cache()
dash_auth.BasicAuth( cache.init_app(app.server, config=CACHE_CONFIG)
app,
# VALID_USERNAME_PASSWORD_PAIRS,
auth_func=user_login,
secret_key="MK8dyS6PyDDuEuzrmqa7dJTJZ7eH2Jkh",
)
watchlist = get_watchlist() @cache.memoize(timeout=14400) # cache timeout set to 4 hours
# symbols = watchlist.index.values.tolist() def fetch_stk_data(sym, sd):
symbols = (watchlist.index.values + " - " + watchlist["Sub Segment"]).tolist() return si.get_data(sym, start_date=sd)[["adjclose", "volume"]]
CACHE_CONFIG = {'CACHE_TYPE': 'SimpleCache'} # App layout
cache = Cache() app.layout = [
cache.init_app(app.server, config=CACHE_CONFIG) html.Div([
html.Button('Reload', id="reload-button", n_clicks=0,
@cache.memoize(timeout=14400) # cache timeout set to 4 hours style={'font-size': '12px', 'width': '120px', 'display': 'inline-block', 'margin-bottom': '10px', 'margin-right': '5px', 'height':'36px', 'verticalAlign': 'top'}),
def fetch_stk_data(sym, sd):
return si.get_data(sym, start_date=sd)[["adjclose", "volume"]]
# App layout
app.layout = [
html.Div([ html.Div([
html.Button('Reload', id="reload-button", n_clicks=0, dcc.Dropdown(symbols, symbols[0], id='symbols_dropdown_list',),
style={'font-size': '12px', 'width': '120px', 'display': 'inline-block', 'margin-bottom': '10px', 'margin-right': '5px', 'height':'36px', 'verticalAlign': 'top'}), ], style={'width': '330px', 'text-align': 'center'}),
html.Div([ html.Button('Auto Play', id="start-button", n_clicks=0,
dcc.Dropdown(symbols, symbols[0], id='symbols_dropdown_list',), style={'font-size': '12px', 'width': '120px', 'display': 'inline-block', 'margin-bottom': '10px', 'margin-right': '5px', 'height':'36px', 'verticalAlign': 'top'}),
], style={'width': '330px', 'text-align': 'center'}), ], style={'display':'flex', 'justify-content':'center'}),
html.Button('Auto Play', id="start-button", n_clicks=0,
style={'font-size': '12px', 'width': '120px', 'display': 'inline-block', 'margin-bottom': '10px', 'margin-right': '5px', 'height':'36px', 'verticalAlign': 'top'}),
], style={'display':'flex', 'justify-content':'center'}),
dcc.Graph( dcc.Graph(
figure={}, figure={},
id='controls-and-graph', id='controls-and-graph',
style={'height':'85vh'} style={'height':'85vh'}
), ),
# dcc.Store(id="signal"), # dcc.Store(id="signal"),
# dcc.Store(id="dropdown_index", data='0'), # dcc.Store(id="dropdown_index", data='0'),
dcc.Interval( dcc.Interval(
id='interval-component', id='interval-component',
interval=3*1000, # in milliseconds interval=3*1000, # in milliseconds
n_intervals=0, n_intervals=0,
max_intervals=2*len(symbols), max_intervals=2*len(symbols),
# max_intervals=3, # for testing # max_intervals=3, # for testing
disabled=True, disabled=True,
), ),
] ]
app.clientside_callback( app.clientside_callback(
""" """
function(id) { function(id) {
document.addEventListener("keyup", function(event) { document.addEventListener("keyup", function(event) {
if (event.key == ' ') { if (event.key == ' ') {
document.getElementById('start-button').click() document.getElementById('start-button').click()
event.stopPropogation() event.stopPropogation()
} }
}); });
return window.dash_clientside.no_update return window.dash_clientside.no_update
} }
""", """,
Output("start-button", "id"), Output("start-button", "id"),
Input("start-button", "id") Input("start-button", "id")
) )
# start / stop button callback # start / stop button callback
@callback(Output('interval-component', 'disabled'), @callback(Output('interval-component', 'disabled'),
Output("start-button", "children"), Output("start-button", "children"),
Output('symbols_dropdown_list', 'disabled'), Output('symbols_dropdown_list', 'disabled'),
Input("start-button", "n_clicks"), Input("start-button", "n_clicks"),
State("start-button", "children"),) State("start-button", "children"),)
def start_cycle(n, value): def start_cycle(n, value):
if n: if n:
if value == "Pause": if value == "Pause":
return True, "Auto Play", False return True, "Auto Play", False
else:
return False, "Pause", True
return no_update
# 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, 0
return no_update
# interval callback
@callback(Output('symbols_dropdown_list', 'value'),
Input("interval-component", "n_intervals"),
State('symbols_dropdown_list', 'options'),
# State('dropdown-index', 'data'),
)
def cycle_syms(n, syms):
if syms:
return syms[n % len(syms)]
else: else:
return no_update return False, "Pause", True
return no_update
# dropdown callback # reload button callback
@callback( @callback(Output('symbols_dropdown_list', 'options'),
Output(component_id='controls-and-graph', component_property='figure'), Output('interval-component', 'n_intervals'),
# Output("dropdown-index", "data"), Input("reload-button", "n_clicks"),)
Input(component_id='symbols_dropdown_list', component_property='value'),
# State('symbols_dropdown_list', 'options') def reload_syms(n):
# Input(component_id='signal', component_property='data'), if n:
watchlist = get_watchlist()
symbols = (watchlist.index.values + " - " + watchlist["Sub Segment"]).tolist()
return symbols, 0
return no_update
# interval callback
@callback(Output('symbols_dropdown_list', 'value'),
Input("interval-component", "n_intervals"),
State('symbols_dropdown_list', 'options'),
# State('dropdown-index', 'data'),
)
def cycle_syms(n, syms):
if syms:
return syms[n % len(syms)]
else:
return no_update
# dropdown callback
@callback(
Output(component_id='controls-and-graph', component_property='figure'),
# Output("dropdown-index", "data"),
Input(component_id='symbols_dropdown_list', component_property='value'),
# State('symbols_dropdown_list', 'options')
# Input(component_id='signal', component_property='data'),
)
def update_graph(col_chosen):
if not col_chosen:
raise PreventUpdate
# return no_update
sym = col_chosen.split()[0]
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(
rows=3,
cols=1,
shared_xaxes=True,
row_heights=[0.6, 0.2, 0.2],
vertical_spacing=0.02,
specs=[[{"secondary_y": True}],[{"secondary_y": False}],[{"secondary_y": False}]],
) )
def update_graph(col_chosen): price_line = px.line(
if not col_chosen: data,
raise PreventUpdate x=data.index,
# return no_update y=data.columns.to_list(),
)
# get data volume_line = px.bar(
# tmp = si.get_data(sym, start_date=sd)[["adjclose", "volume"]] vol,
sym = col_chosen.split()[0] x=vol.index,
end_date = dt.datetime.today() y=vol.columns.to_list(),
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( macd_line = px.line(
rows=3, macd,
cols=1, x=macd.index,
shared_xaxes=True, y=['_MACD', '_SIG'],
row_heights=[0.6, 0.2, 0.2], )
vertical_spacing=0.02,
specs=[[{"secondary_y": True}],[{"secondary_y": False}],[{"secondary_y": False}]],
)
price_line = px.line( macd_neg = macd.copy()
data, macd_pos = macd.copy()
x=data.index, macd_neg[macd_neg>0] = 0
y=data.columns.to_list(), macd_pos[macd_pos<0] = 0
# y=[sym, sym+'_BOL200L', sym+'_BOL200U', sym+'_SMA200', sym+'_SMA50', sym+'_WVMA50'],
)
volume_line = px.bar( macd_hist_pos = px.line(
vol, macd_pos,
x=vol.index, x=macd_pos.index,
y=vol.columns.to_list(), y=['_HIST'],
) )
macd_hist_pos.update_traces(fill='tozeroy', line_color='rgba(0,100,0,0.5)', showlegend=False)
macd_line = px.line( macd_hist_neg = px.line(
macd, macd_neg,
x=macd.index, x=macd_neg.index,
y=['_MACD', '_SIG'], y=['_HIST'],
) )
macd_hist_neg.update_traces(fill='tozeroy', line_color='rgba(100,0,0,0.5)', showlegend=False)
macd_neg = macd.copy() rsi_line = px.line(
macd_pos = macd.copy() rsi,
macd_neg[macd_neg>0] = 0 x=rsi.index,
macd_pos[macd_pos<0] = 0 y=['_RSI', '_VoRSI'],
)
fig.add_traces(price_line.data + volume_line.data, rows=1, cols=1, secondary_ys=[True, True, True, True, True, True, False])
fig.add_traces(macd_line.data + macd_hist_pos.data + macd_hist_neg.data, rows=2, cols=1)
fig.add_traces(rsi_line.data, rows=3, cols=1)
macd_hist_pos = px.line( # fig.update_traces(marker_color = 'rgba(0,0,250,0.5)',
macd_pos, # marker_line_width = 0,
x=macd_pos.index, # selector=dict(type="bar"),
y=['_HIST'],
)
macd_hist_pos.update_traces(fill='tozeroy', line_color='rgba(0,100,0,0.5)', showlegend=False)
macd_hist_neg = px.line( fig.layout.yaxis.title="Volume"
macd_neg, fig.layout.yaxis2.title="Price ($)"
x=macd_neg.index, fig.layout.yaxis2.type="log"
y=['_HIST'], fig.layout.yaxis3.title="MACD"
) fig.layout.yaxis4.title="RSI/VoRSI"
macd_hist_neg.update_traces(fill='tozeroy', line_color='rgba(100,0,0,0.5)', showlegend=False) fig.layout.yaxis.griddash="dash"
rsi_line = px.line( fig.update_layout(title_text=sym+' - '+plot_ind, title_x=0.5,
rsi, margin=dict(l=30, r=20, t=50, b=20),
x=rsi.index, )
y=['_RSI', '_VoRSI'], # fig.update_layout(showlegend=False)
)
fig.add_traces(price_line.data + volume_line.data, rows=1, cols=1, secondary_ys=[True, True, True, True, True, True, False])
fig.add_traces(macd_line.data + macd_hist_pos.data + macd_hist_neg.data, rows=2, cols=1)
fig.add_traces(rsi_line.data, rows=3, cols=1)
# fig.update_traces(marker_color = 'rgba(0,0,250,0.5)', return fig
# marker_line_width = 0,
# selector=dict(type="bar"),
fig.layout.yaxis.title="Volume" if __name__ == "__main__":
fig.layout.yaxis2.title="Price ($)" serve(app.server, host="0.0.0.0", port=8050, threads=7) # using production quality WSGI server Waitress
fig.layout.yaxis2.type="log"
fig.layout.yaxis3.title="MACD"
fig.layout.yaxis4.title="RSI/VoRSI"
fig.layout.yaxis.griddash="dash"
fig.update_layout(title_text=sym+' - '+plot_ind, title_x=0.5,
margin=dict(l=30, r=20, t=50, b=20),
)
# fig.update_layout(showlegend=False)
return fig
serve(app.server, host="0.0.0.0", port=8050, threads=7)
# app.run(debug=True) # app.run(debug=True)