TRADEnosis™ — My Quest to Build a Mythical Stock Trading Platform

Chapter 1: Creating a responsive front-end with Python and Dash

Steve Mayo
8 min readJul 29, 2023

Frustrated with available trading platforms, I undertook a grueling quest to build my own. Unsure if I was following the path of the brave knights in Excalibur or those crazy guys from Monte Python, my half-noble half-absurd adventure, which took two years and thousands of hours climbing difficult learning curves that felt like I was battling dragons and evil wizards, eventually lead to a stock trading platform called TRADEnosis™.

This series of articles is my humble attempt to pay-it-forward for the next glory-seekers, or ambitious stock traders, who might want to travel this arduous path. In this first chapter, I hope to lessen the burden, for my fellow pilgrims, of building a responsive, interactive front-end using Python and Plotly’s Dash.

I won’t be giving you the Holy Grail. TRADEnosis is a commercial product with growing sales, and I intend to keep it that way. It’s value comes from the use of predictive machine learning and the API infrastructure that supports it, not from a trading interface, particularly one as basic as what we’ll be building here, even though this part of the quest still took a lot of code and a lot of research! But one can’t effectively implement an AI-powered algorithm without some way of dynamically generating stock graphs and trade orders. None of the trading platforms I found at the time would take an API feed so I built this first and am sharing it with you now, hopefully to save you from such a challenging task.

Assemble our armaments!

To join in this adventure, you’ll need a basic familiarity with:

  • Python, Pandas, Numpy,
  • Html 5 with Cascading Stylesheets (CSS), and
  • Bootstrap v5.

Setting up a Python environment, installing packages, and learning their documentation are expected skills of anyone hoping to feast at the big round table. In preparation, install these packages:

  • Python v3.9,
  • Pandas v1.5
  • Dash v2.11,
  • Dash Bootstrap Components v 1.4 (DBC),
  • Dash TradingView Lightweight Charts Component 0.1.1 (TVLWC), and
  • Alpaca Markets SDK v2.

Take heed, my liege

Living in that mysterious realm between html and Python, these packages require you write in a strange declarative language while making a lot of trial and error refinements. Like an evil enchantress, they will temp you to abandon your quest and instead waste your life chasing the perfect layout. Don’t get beguiled, oh noble knight, keep your sights on our goal and save the tweaking until you have that golden (wooden?) cup in hand.

Let our quest begin!

Here’s our starting point:

import pandas as pd
import plotly.graph_objs as go
from dash import Dash, dcc, html, callback, Input, Output, State, dash_table, no_update, Patch, ctx
from dash.exceptions import PreventUpdate
import dash_bootstrap_components as dbc
import dash_tvlwc

stock_list = ["AAPL", "MSFT", "GOOG", "TSLA", "NVDA", "AMZN", "META", "ADBE", "AVGO", 'ASML']
df_watchlist = pd.DataFrame(data={"Ticker": stock_list})
test_data = [{'symbol': 'GOOG', 'open': 37.36, 'high': 38.45, 'low': 37.34, 'close': 38.35, 'volume': 42692420.0,
'trade_count': 35176.0, 'vwap': 38.09, 'time': '2015-12-01 00:00:00'},
{'symbol': 'GOOG', 'open': 38.45, 'high': 38.8, 'low': 37.95, 'close': 38.12,
'volume': 44607460.0, 'trade_count': 34959.0, 'vwap': 38.34, 'time': '2015-12-02 00:00:00'},
{'symbol': 'GOOG', 'open': 38.3, 'high': 38.45, 'low': 37.28, 'close': 37.63,
'volume': 51812820.0, 'trade_count': 38195.0, 'vwap': 37.77, 'time': '2015-12-03 00:00:00'},
{'symbol': 'GOOG', 'open': 37.66, 'high': 38.42, 'low': 37.5, 'close': 38.34, 'volume': 55145660.0,
'trade_count': 33245.0, 'vwap': 38.2, 'time': '2015-12-04 00:00:00'},
{'symbol': 'GOOG', 'open': 38.39, 'high': 38.44, 'low': 37.75, 'close': 38.16, 'volume': 36246280.0,
'trade_count': 23534.0, 'vwap': 38.13, 'time': '2015-12-07 00:00:00'}]

app = Dash(
__name__,
external_stylesheets=[
dbc.themes.BOOTSTRAP,
dbc.icons.BOOTSTRAP,
'assets/styles.css',
],
meta_tags=[{"name": "viewport", "content": "width=device-width, initial-scale=1"}]
)

navbar = ""

watchlist = ""

chart = ""

orders_card = ""

positions_card = ""

app.layout = dbc.Container(
[
navbar,
dbc.Row(
[
dbc.Col(watchlist, xs=3, sm=2, md=1),
dbc.Col(chart, xs=9, sm=10, md=11)
],
className="mt-1",
),
dbc.Row(
[
dbc.Col(
dbc.Tabs(
[
dbc.Tab(orders_card, label="Orders",
activeLabelClassName="fw-bold",
),
dbc.Tab(positions_card, label="Positions",
activeLabelClassName="fw-bold"),
],
),
width=12)
],
)
], fluid=False
)

if __name__ == "__main__":
app.run_server(debug=True, port=8000)

Dash uses “components” which are converted into React code for the browser. They are configured with parameters and styled using the ‘style’ parameter, which is written a bit differently than native CSS. You can also add a traditional stylesheet as I’m doing here from an “assets” directory.

Dash Bootstrap Components (dbc) are similar to Dash Core Components (dcc), which we haven’t used yet, and in some cases you can swap dcc with dbc to get a Bootstrap-styled version. But more than likely, you’ll need to consult the documentation for nuanced differences.

One such notable difference is that dbc follows the Bootstrap model of using classes for styling, except written as className parameters. An example is className="mt-1" to set a small margin above the first row. The dbc components can still accept the ‘style’ parameter as well. Just be aware that style will overwrite corresponding className settings.

As with using Bootstrap to lay out html, you must arrange dbc layout components within a Container -> Row -> Col(umn) structure. Follow the way I did it here and you should be fine. One big gotcha is that you must wrap multiple components with square brackets ([…]), like I did with the columns within the rows, and the rows within the container. Ignore that rule and the squires might laugh at you!

Bootstrap, as used by dbc, has a nice 12-column layout. Notice the columns containing the watchlist and chart. These columns will appear in a horizonal row that will dynamically re-size. On extra-small screens like a phone, those components will take 3 and 9 column-widths respectively, spaning the full 12 available column-widths, as set by the XS parameter. As the screen size increases to small (sm), medium (md), and larger (e.g., xl, xxl) the watchlist will fit in less space, leaving more room for the chart.

A somewhat confusing thing is how parameters, such as fluid=False for configuring the Container, can get buried below lines and lines of code. You can add children= ahead of the brackets to help clarify the nesting, but that adds a lot of unnecessary code. Yet, keep in mind the concept of these being children when we start adding interactivity. As a sneak peak, I will say what’s exciting about Dash is that children, along with other parameters, can be hot-swapped at runtime using callback functions. You get the benefits of React without having to slay that fearsome dragon!

You can’t expect to wield supreme executive power just because some watery tart threw a sword at you!

My starting template doesn’t seem very noble. But, notice I’m using variables like navbar and watchlist to isolate parts of the layout to keep the code more readable. Let’s add those components now. First, here’s the navbar code.

navbar = dbc.NavbarSimple(
[
dbc.DropdownMenu(
[
dbc.DropdownMenuItem("Alpaca", id="btn-alpaca-settings", n_clicks=0),
],
nav=True,
in_navbar=True,
label="Tools",
),
],
brand=html.Span(
[
html.Span(
className="<bi bi-graph-up"),
" My TRADEnosis™ Clone"
], className="rfs-large"
),
brand_href="https://www.tradenosis.com",
color="primary",
dark=True,
)

It uses a dbc component, dbc.NavbarSimple, to create a nice Bootstrap-like header. Within that component, I’ve inserted a DropdownMenuItem which we’ll later use to enter our Alpaca SDK keys. I also give you an example of how to incorporate an html.Span (the html package was imported from Dash in our initial code) into the brand parameter, something that can be done with many dcc/dbc components. In this case, I’m inserting an icon. bi bi-graph-up; in the prior code, we added dbc.icons.BOOTSTRAP to the Dash configuration.

Also, notice that I added aclassName=”rfs-large” to our brand parameter. In our initial code, that particular class is defined in our Dash configuration, external_stylesheets=['assets/styles.css'], to make the font size change with differing screen sizes. Here’s the CSS code you should add to that styles.css file:

/*Create a responsive font*/
.rfs-large{
font-size: calc(0.5rem + 0.8vw);
}

Next, here’s the code for the watchlist layout variable. It uses the Dash DataTable component with some styling to make it responsively fit our compact layout and make it scrollable.

watchlist = dash_table.DataTable(
data=df_watchlist.to_dict('records'),
columns=[{"name": i, "id": i} for i in df_watchlist.columns],
style_cell={'textAlign': 'left'},
style_as_list_view=True,
style_header={'borderTop': '0px', 'fontWeight': '100', 'fontSize': '0.8em'},
style_data={'border': '0px'},
style_table={'height': '45vh', 'overflow': 'auto'}
)

In the initial code, I had created a Pandas dataFrame, df_watchlist, with a sampling of stock symbols. To use it within the DataTable, we first need to convert it to a dictionary of records using to_dict(‘records’) and then configure the columns.

Here’s the code for the chart. I’m using a package that wraps the free TradingView Lightweight Chart Javascript package. As an alternative, you could use Plotly, which Dash natively supports.

chart = dash_tvlwc.Tvlwc(
id='stock-chart',
seriesData=[],
seriesTypes=['candlestick'],
width="100%",
height="50vh"
),

The last two missing parts of the layout are the content for the tabs. These also use the DataTable components, but here we define the columns expressly; we’ll load the data from Alpaca later.

orders_card = dbc.Card(
[
dbc.CardBody(
[
dash_table.DataTable(
id='orders_table',
data=[],
columns=[
{"name": 'Ticker', "id": 'ot_ticker', 'type': "text"},
{"name": 'Action', "id": 'ot_action', 'type': "text"},
{"name": 'Status', "id": 'ot_status', 'type': "text"},
{"name": 'Date', "id": 'ot_date', 'type': "text"},
],
style_cell={
'textAlign': 'left',
'height': 'auto',
# all three widths are needed
'minWidth': '100px', 'width': '100px', 'maxWidth': '100px',
'whiteSpace': 'normal'
},
style_as_list_view=True,
style_header={'borderTop': '0px', 'font-weight': '600'},
style_data={'border': '0px'},
style_table={'height': '25vh', 'overflow-y': 'auto'}
)
],
)
]
)

positions_card = dbc.Card(
[
dbc.CardBody(
[
dash_table.DataTable(
id="positions_table",
data=[],
columns=[
{"name": 'Ticker', "id": 'pt_ticker', 'type': "text"},
{"name": 'Price', "id": 'pt_price', 'type': "text"},
{"name": 'Qty', "id": 'ot_status', 'type': "text"},
{"name": 'Mkt Value', "id": 'pt_value', 'type': "text"},
{"name": 'Status', "id": 'pt_status', 'type': "text"},
{"name": 'Date', "id": 'pt_date', 'type': "text"},
],
style_cell={
'textAlign': 'left',
'height': 'auto',
# all three widths are needed
'minWidth': '100px', 'width': '100px', 'maxWidth': '100px',
'whiteSpace': 'normal'
},
style_as_list_view=True,
style_header={'borderTop': '0px', 'font-weight': '600'},
style_data={'border': '0px'},
style_table={'height': '25vh', 'overflow-y': 'auto'}
)
]
)
]
)

A Time to Celebrate

Finally, here’s how our layout will look on a mobile screen. As King Arthur commanded, it will adjust to screen size. When ready, you can hide the Dash Debugger (blue circle at the bottom) by removing “debug=True” from the last line; meanwhile, it will prove very helpful when we start adding interactivity in our next chapter.

A Summary of our Adventure:

  • In this chapter, we developed the user interface for a basic trading webpage using Plotly Dash.
  • In Chapter 2, we will create an Alpaca Settings dialog and write code to load candlestick price data into a stock chart.
  • In Chapter 3, we will review Alpaca’s trading rules.
  • In Chapter 4, we will create the order form and associated code to compile trade instructions.
  • In Chapter 5, we will write the callback needed to submit an order to Alpaca.
  • In Chapter 6, we will compile data for the Orders and Positions tabs.

--

--

Steve Mayo

Steve is a Doctor of Pharmacy, a retired clinical researcher, an avid investor, and the creator of TRADEnosis.com, where he applies AI to stock trading.