 
 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()