
Bquant [4] - Building Apps
nbappinator
In addition to unfettered(ish) access to data, BQuant Enterprise allows us to build interactive applications and “Publish” them to other Bloomberg Terminal users.
How? I’m glossing over some technical details: Jupyter is a web application. It has a mechanism for establishing communication between code running in your web browser and the Python kernel running alongside Jupyter. When a Terminal user opens a Published application, it’s opened in Voila. Voila strips out the “Notebook” interface and leaves the user with a web page containing only the application, which can communicate to the Python kernel. This communication path is complicated and not something most developers should ever think about; instead, it’s best to work with frameworks that have figured this out already.
nbappinator is our (Iqmo’s) project to provide a rapid development environment for building Notebook-based applications, and it was built with BQuant in mind. It leverages some great work done by others to bring modern web technologies into Jupyter and streamlines the experience. Our goal is to insulate the developer/Quant/Analyst from the myriad of choices and provide an opinionated application framework
Note: Opinionated just means you can build any application as long as you make it our way.
(Optional) Aside
We took a lot of inspiration from tools like gradio and streamlit. They make it easy to quickly develop applications in Python environments with minimal UI expertise. However, they don’t quite fit into a BQuant environment. With the right tools, Jupyter is a very powerful framework.
BQuant Example
Prerequisite
To run this example, follow the steps in BQuant - Opening Blog Examples to create a Custom Environment that includes nbappinator and the notebook code below.
bqnt_first_app.ipynb
nbappinator - first bqnt app¶
This app runs a BQL query that takes a single parameter, displays the result as an interactive table, and provides a simple charting interface that draws an interactive Plotly chart of the results.
Before Running in BQuant Enterprise¶
In BQuant Enterprise, certain packages* must be loaded before starting your project. This is accomplished through Custom Environments.
To run examples using nbappinator
:
- Create a Custom Environment
- Change the Name of the Custom Environment
- Add
nbappinator
to it - Click
solve
- Save the Custom Environment
- Launch/Relaunch a Project using the Custom Environment
The first time you launch a Custom Environment will take a few minutes to load. Subsequent launches will be much faster, but the environment will rebuild everytime you make a change to the packages or versions. I usually put the minimum required packages into the Custom Environment, and install the rest at runtime.
* Jupyter extensions, primarily UI widget extensions that register Javascript models, must be loaded at Jupyter startup time.
** Other packages that don't require extensions may be installed at runtime, using %package install <xyz>
or %pip install <xyz>
.
Import your packages¶
import nbappinator as nbapp
import jinja2
import logging
import bql
import pandas as pd
import plotly.express as px
Setup any globals¶
Setup static variables used in the global scope (outside a function) that will be used without the function.
Try to only use static globals: global variables that don't change / aren't mutated.
logging.basicConfig(encoding="utf-8", level=logging.INFO)
logger = logging.getLogger(__name__)
bq = bql.Service()
INITIAL_QUERY = """get(
px_last
) for(
{{SECURITY}}
) with(
dates=range(-29d, 0d),
fill=prev,
currency=USD
)"""
PAGES = ["Data", "Chart"]
Define functions¶
Functions to execute the BQL query, apply the template, and draw a chart.
def _exec_bql(query: str) -> pd.DataFrame:
r = bq.execute(query)
df = bql.combined_df(r).reset_index()
return df
def execute_query(
component: str, action: str, args: str, app: nbapp.UiModel, caller: str
):
app.clear_messages()
with app.messages:
try:
app.update_status(name=caller, message="Running Query", running=True)
data_page = app.get_page(PAGES[0])
data_page.clear()
chart_page = app.get_page(PAGES[1])
chart_page.clear()
# Create query from a template
security: str = app.get_valuestr("security")
base_query: str = app.get_valuestr("query")
query = jinja2.Template(base_query).render(SECURITY=security)
print(query)
# Execute the query
df = _exec_bql(query)
# Display the dataframe in a grid
data_page.clear()
data_page.add_df(name="df1", df=df)
df.columns = [col.lower() for col in df.columns]
# Create a menu on the chart_page that lets the user pick columns from the chart.
cols = list(df.columns)
options_container = chart_page.add_box(name="options", horiz=True)
options_container.add_select(
name="x_axis", label="X Axis", options=cols, value="date"
)
options_container.add_select(
name="y_axis", label="Y Axis", options=cols, value=cols[-1]
)
options_container.add_select(
name="z_axis", label="Series", options=cols, value="id"
)
chart_page.add_button(
name="drawchart", label="Draw Chart", action=draw_chart, status=False
)
chart_page.add_container(name="chartcontainer")
app.update_status(name=caller, message="Query Complete", running=False)
except Exception as e:
app.update_status(
name=caller,
message=f"Error {e} running query, see Messages for details",
running=False,
)
def draw_chart(component: str, action: str, args: str, app: nbapp.UiModel, caller: str):
with app.messages:
df = app.widgets["df1"].w.df
display(df.columns)
x = app.get_values("x_axis")
y = app.get_values("y_axis")
color = app.get_values("z_axis")
# Sort by X and Color, so chart doesn't "zig-zag" along the X.
if color is not None:
sorting_fields = [x, color]
else:
sorting_fields = [x]
df_sorted = df.sort_values(by=sorting_fields)
fig = px.line(df_sorted, x=x, y=y, color=color)
app.clear_container("chartcontainer")
chartcontainer = app.get_container("chartcontainer")
chartcontainer.add_plotly_fig(name="f1", fig=fig)
Setup and Display the nbappinator Application¶
The example below is not functional, but shows what it would look like in BQuant.
myapp = nbapp.TabbedUiModel(pages=PAGES, log_footer="Messages", headers=["Config"])
config_page = myapp.get_page("Config")
config_page.add_textfield(
name="security",
label="Enter Security: ['Security1', 'Security2']",
value="['IBM US Equity']",
)
config_page.add_textarea(
name="query",
label="Enter BQL, with {{SECURITY}} for the Universe",
value=INITIAL_QUERY,
)
config_page.add_button(
name="update", label="Run Query", action=execute_query, status=True
)
myapp.display()
Publish the Notebook¶
You can then Publish this application to another user. They'll receive a Message in Bloomberg with a link to the application.