
BQuant [6] - App Example 2
Fund Information
This app demonstrates a few data types you can query in BQL relating to funds.
In this case, we’ll query and display fund-level information, along with the reported holdings (13F) and day/day returns.
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_fund_app.ipynb
import nbappinator as nbapp
import jinja2
import logging
import bql
import pandas as pd
import plotly.express as px
Define globals¶
PAGES = ["Data", "Chart", "Returns"]
logging.basicConfig(encoding="utf-8", level=logging.INFO)
logger = logging.getLogger(__name__)
bq = bql.Service()
Setting up the queries¶
It's a good practice to keep your queries near your code, but not necessarily embedded in it. This way, you can test your queries independent of the code.
In this case, I use Jinja2 templates to parameter the fields. This is optional, you can just use f-strings or string replacement.
hm_query = """get(
id().position as #position,
id().reported_mkt_val/1M as #report_mkt_val_m
) for(
holdings('{{security}}')
) with(
fill=prev,
currency=USD
)"""
fund_query = """
get(
FUND_MGMT_COMPANY,MGR_CITY_NAME,MGR_COUNTRY_NAME,FUND_MGMT_STYLE,FUND_DOMICILE_TYP,FUND_TYP ,
FUND_TOTAL_ASSETS /1M,FUND_NET_ASSET_VAL, FUND_EXPENSE_RATIO,FUND_ASSET_CLASS_FOCUS,FUND_GEO_FOCUS,FUND_STRATEGY,FUND_INDUSTRY_FOCUS,
FUND_FLOW,FUND_RTG_CLASS_FOCUS,FUND_MKT_CAP_FOCUS,FUND_LEVERAGE_TYPE,
FUND_OPEN_INVESTOR_SHR,
FUND_BENCHMARK, ACTIVELY_MANAGED,INDEX_WEIGHTING_METHODOLOGY,REPLICATION_STRATEGY,ECONOMIC_ASSOCIATION
)
for(
['{{security}}']
)
with(
fill=prev,
currency=USD
)
"""
m_query = """get(
sum(group(id().reported_mkt_val/1M as #report_mkt_val_m, BICS_LEVEL_1_SECTOR_NAME)) as #total_mkt_val_by_sector
) for(
holdings('{{security}}', dates={{offset}}Y)
) with(
fill=prev,
currency=USD
)
preferences(
addcols=all
)
"""
returns_query = """
get(
DAY_TO_DAY_TOT_RETURN_GROSS_DVDS
)
for(
['{{security}}']
) with(
dates=range(-1Y, 0D),
fill=prev,
currency=USD
)
"""
def _exec_bql(query: str, **kwargs) -> pd.DataFrame:
query_string = jinja2.Template(query).render(**kwargs)
logger.info(f"Executing {query_string}")
r = bq.execute(query_string)
df = bql.combined_df(r).reset_index()
return df
The Working Functions¶
These functions do the heavy lifting: executing a query, and processing the result, and displaying the result in the app.
def add_data(app: nbapp.UiModel, security: str):
ddf = _exec_bql(fund_query, security=security)
p = app.get_page(PAGES[0])
p.clear()
df_combined = ddf.bfill().iloc[0:1].transpose().reset_index()
df_combined.columns = ["Field", "Value"]
p.add_df(name="f1", df=df_combined)
def draw_heatmap(app: nbapp.UiModel, security: str):
hmdf = _exec_bql(hm_query, security=security)
hmdf = hmdf.sort_values(by="#report_mkt_val_m", ascending=False)
fig = px.histogram(hmdf, x="ID", y="#report_mkt_val_m")
p = app.get_page(PAGES[1])
p.clear()
p.add_plotly_fig(name="f1", fig=fig)
def add_historical(app: nbapp.UiModel, security: str):
p = app.get_page(PAGES[1])
dfs = []
for i in range(-4, 1):
d = _exec_bql(m_query, security="QQQ US Equity", offset=i)
dfs.append(d)
all_df = pd.concat(dfs)
fig = px.histogram(
all_df,
x="DATE",
y="#total_mkt_val_by_sector",
color="ID",
barmode="stack",
color_discrete_sequence=px.colors.qualitative.Antique,
)
fig.update_layout(bargap=0.2)
p.add_plotly_fig(name="f2", fig=fig)
def add_returns(app: nbapp.UiModel, security: str):
p = app.get_page(PAGES[2])
p.clear()
df = _exec_bql(returns_query, security=security)
df["CUMULATIVE_RETURN"] = (1 + df["DAY_TO_DAY_TOT_RETURN_GROSS_DVDS"]).cumprod()
fig1 = px.line(df, x="DATE", y="CUMULATIVE_RETURN")
fig2 = px.line(df, x="DATE", y="DAY_TO_DAY_TOT_RETURN_GROSS_DVDS")
p.add_plotly_fig(name="fr_1", fig=fig1)
p.add_plotly_fig(name="fr_1", fig=fig2)
def execute_click(
component: str, action: str, args: str, app: nbapp.UiModel, caller: str
):
with app.messages:
try:
app.update_status(caller, message="Executing", running=True)
security = app.get_valuestr("security")
print(f"Querying {security}")
add_data(app, security)
add_returns(app, security)
draw_heatmap(app, security)
app.update_status(
caller, message="Executing historical query", running=True
)
add_historical(app, security)
app.update_status(caller, message="Done", running=False)
except Exception as e:
logger.exception("Error executing")
app.update_status(caller, message=f"Error {e}", running=False)
The App¶
When designing apps, simplicity and clarity is key. This app demonstrates a linear, top down flow:
- Config: The top section where settings are modified before any action is taken.
- Execution: Usually the bottom of the Config section is a Button, like "Execute". When clicked, this triggers the main execution: Queries, Data Processing, Model Evaluation, etc.
- Rendering: Rendering happens automatically when Execution is complete. Any visualization is display in a tabbed menu below.
- Additional interactions can be done within the tabs.
myapp = nbapp.TabbedUiModel(pages=PAGES, log_footer="Messages", headers=["Config"])
config_page = myapp.get_page("Config")
config_page.add_textfield(name="security", label="Enter Fund: ", value="QQQ US Equity")
config_page.add_button(
name="update", label="Execute", action=execute_click, status=True
)
myapp.display()