BQL
Use Case: Run more advanced queries to screen securities, calculate analytics (like moving averages), or pull fundamental data with complex conditions.
Returns: The bql()
method returns a BqlResult
object, which:
- Acts like a list of Polars DataFrames (one for each item in BQL
get
statement). - Provides a
.combine()
method to merge DataFrames on common columns.
Basic Example
# Fetch the last price of IBM stock
with BQuery() as bq:
results = bq.bql("get(px_last) for(['IBM US Equity'])")
print(results[0]) # Access the first DataFrame
Output:
┌───────────────┬─────────┬────────────┬──────────┐
│ ID ┆ px_last ┆ DATE ┆ CURRENCY │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ f64 ┆ date ┆ str │
╞═══════════════╪═════════╪════════════╪══════════╡
│ IBM US Equity ┆ 230.82 ┆ 2024-12-14 ┆ USD │
└───────────────┴─────────┴────────────┴──────────┘
Multiple Securities
# Fetch the last price for IBM and SEB
with BQuery() as bq:
results = bq.bql("get(px_last) for(['IBM US Equity', 'SEBA SS Equity'])")
print(results[0])
Output:
┌────────────────┬─────────┬────────────┬──────────┐
│ ID ┆ px_last ┆ DATE ┆ CURRENCY │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ f64 ┆ date ┆ str │
╞════════════════╪═════════╪════════════╪══════════╡
│ IBM US Equity ┆ 230.82 ┆ 2024-12-14 ┆ USD │
│ SEBA SS Equity ┆ 155.2 ┆ 2024-12-14 ┆ SEK │
└────────────────┴─────────┴────────────┴──────────┘
Multiple Items - combine()
When querying for multiple items, bql()
returns BqlResult
object which is actually just a list of polars dataframes with extra method combine()
# Fetch name and last price of IBM (two items)
with BQuery() as bq:
results = bq.bql("get(name, px_last) for(['IBM US Equity'])")
Output:
>>> print(len(results)) # 2 DataFrames
n=2
>>> print(results[0]) # First DataFrame: 'name'
┌───────────────┬────────────────────────────────┐
│ ID ┆ name │
│ --- ┆ --- │
│ str ┆ str │
╞═══════════════╪════════════════════════════════╡
│ IBM US Equity ┆ International Business Machine │
└───────────────┴────────────────────────────────┘
>>> print(results[1]) # Second DataFrame: 'px_last'
┌───────────────┬─────────┬────────────┬──────────┐
│ ID ┆ px_last ┆ DATE ┆ CURRENCY │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ f64 ┆ date ┆ str │
╞═══════════════╪═════════╪════════════╪══════════╡
│ IBM US Equity ┆ 230.82 ┆ 2024-12-14 ┆ USD │
└───────────────┴─────────┴────────────┴──────────┘
We can use combine()
method to join the results into single dataframe:
>>> combined_df = results.combine()
>>> print(combined_df)
Output:
┌───────────────┬────────────────────────────────┬─────────┬────────────┬──────────┐
│ ID ┆ name ┆ px_last ┆ DATE ┆ CURRENCY │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ f64 ┆ date ┆ str │
╞═══════════════╪════════════════════════════════╪═════════╪════════════╪══════════╡
│ IBM US Equity ┆ International Business Machine ┆ 230.82 ┆ 2024-12-14 ┆ USD │
└───────────────┴────────────────────────────────┴─────────┴────────────┴──────────┘
Screening
Example of using saved SRCH search and filtering on ticker:
Find list of SEB and Handelsbanken's AT1 bonds and print their names, duration and Z-Spread. The result of the query is list of 3 polars dataframes and can be conveniently combined using combine()
method.
query="""
let(#dur=duration(duration_type=MODIFIED);
#zsprd=spread(spread_type=Z);)
get(name(), #dur, #zsprd)
for(filter(screenresults(type=SRCH, screen_name='@COCO'),
ticker in ['SEB', 'SHBASS']))
"""
with BQuery() as bq:
results = bq.bql(query)
combined_df = results.combine()
print(combined_df)
Output:
┌───────────────┬─────────────────┬──────┬────────────┬────────┐
│ ID ┆ name() ┆ #dur ┆ DATE ┆ #zsprd │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ f64 ┆ date ┆ f64 │
╞═══════════════╪═════════════════╪══════╪════════════╪════════╡
│ BW924993 Corp ┆ SEB 6 ⅞ PERP ┆ 2.23 ┆ 2024-12-16 ┆ 212.0 │
│ YV402592 Corp ┆ SEB Float PERP ┆ 0.21 ┆ 2024-12-16 ┆ 233.0 │
│ ZQ349286 Corp ┆ SEB 5 ⅛ PERP ┆ 0.39 ┆ 2024-12-16 ┆ 186.0 │
│ ZO703315 Corp ┆ SHBASS 4 ⅜ PERP ┆ 1.95 ┆ 2024-12-16 ┆ 213.0 │
│ ZO703956 Corp ┆ SHBASS 4 ¾ PERP ┆ 4.94 ┆ 2024-12-16 ┆ 256.0 │
│ YU819930 Corp ┆ SEB 6 ¾ PERP ┆ 5.37 ┆ 2024-12-16 ┆ 309.0 │
└───────────────┴─────────────────┴──────┴────────────┴────────┘
Aggregation
Average PE per Index Sector
This example shows aggregation (average) per group (sector) for members of an index.
The resulting list has only one element since there is only one data-item in get
query = """
let(#avg_pe=avg(group(pe_ratio(), gics_sector_name()));)
get(#avg_pe)
for(members('OMX Index'))
"""
with BQuery() as bq:
results = bq.bql(query)
print(results[0].head(5))
Output:
┌──────────────┬───────────┬──────────────┬────────────┬──────────────┬──────────────┬─────────────┐
│ ID ┆ #avg_pe ┆ REVISION_DAT ┆ AS_OF_DATE ┆ PERIOD_END_D ┆ ORIG_IDS ┆ GICS_SECTOR │
│ --- ┆ --- ┆ E ┆ --- ┆ ATE ┆ --- ┆ _NAME() │
│ str ┆ f64 ┆ --- ┆ date ┆ --- ┆ str ┆ --- │
│ ┆ ┆ date ┆ ┆ date ┆ ┆ str │
╞══════════════╪═══════════╪══════════════╪════════════╪══════════════╪══════════════╪═════════════╡
│ Communicatio ┆ 19.561754 ┆ 2024-10-24 ┆ 2024-12-14 ┆ 2024-09-30 ┆ null ┆ Communicati │
│ n Services ┆ ┆ ┆ ┆ ┆ ┆ on Services │
│ Consumer Dis ┆ 19.117295 ┆ 2024-10-24 ┆ 2024-12-14 ┆ 2024-09-30 ┆ null ┆ Consumer │
│ cretionary ┆ ┆ ┆ ┆ ┆ ┆ Discretiona │
│ ┆ ┆ ┆ ┆ ┆ ┆ ry │
│ Consumer ┆ 15.984743 ┆ 2024-10-24 ┆ 2024-12-14 ┆ 2024-09-30 ┆ ESSITYB SS ┆ Consumer │
│ Staples ┆ ┆ ┆ ┆ ┆ Equity ┆ Staples │
│ Financials ┆ 6.815895 ┆ 2024-10-24 ┆ 2024-12-14 ┆ 2024-09-30 ┆ null ┆ Financials │
│ Health Care ┆ 22.00628 ┆ 2024-11-12 ┆ 2024-12-14 ┆ 2024-09-30 ┆ null ┆ Health Care │
└──────────────┴───────────┴──────────────┴────────────┴──────────────┴──────────────┴─────────────┘
HY Maturity Wall
And another example on the same topic. Calculate amount outstanding bonds in HY index per maturity year
query = """
let(#mv=sum(group(amt_outstanding(currency=USD),
by=[year(maturity()), industry_sector()]));)
get(#mv)
for(members('LF98TRUU Index'))
"""
with BQuery() as bq:
results = bq.bql(query)
df = results.combine().rename(
{"YEAR(MATURITY())": "maturity", "INDUSTRY_SECTOR()": "sector", "#mv": "mv"}
)
print(df.pivot(index="maturity", on="sector", values="mv").head())
Output:
shape: (5, 11)
┌──────────┬───────────┬───────────┬───────────┬───┬───────────┬───────────┬───────────┬───────────┐
│ maturity ┆ Basic ┆ Consumer, ┆ Energy ┆ … ┆ Financial ┆ Technolog ┆ Utilities ┆ Diversifi │
│ --- ┆ Materials ┆ Non-cycli ┆ --- ┆ ┆ --- ┆ y ┆ --- ┆ ed │
│ i64 ┆ --- ┆ cal ┆ f64 ┆ ┆ f64 ┆ --- ┆ f64 ┆ --- │
│ ┆ f64 ┆ --- ┆ ┆ ┆ ┆ f64 ┆ ┆ f64 │
│ ┆ ┆ f64 ┆ ┆ ┆ ┆ ┆ ┆ │
╞══════════╪═══════════╪═══════════╪═══════════╪═══╪═══════════╪═══════════╪═══════════╪═══════════╡
│ 2025 ┆ 1.5e8 ┆ 5.34916e8 ┆ 5e8 ┆ … ┆ null ┆ null ┆ null ┆ null │
│ 2026 ┆ 4.4013e9 ┆ 9.3293e9 ┆ 8.2931e9 ┆ … ┆ 1.3524e10 ┆ 4.0608e9 ┆ 2.5202e9 ┆ null │
│ 2027 ┆ 8.3921e9 ┆ 2.3409e10 ┆ 1.2427e10 ┆ … ┆ 1.9430e10 ┆ 4.3367e9 ┆ 3.6620e9 ┆ null │
│ 2028 ┆ 1.4701e10 ┆ 3.7457e10 ┆ 2.2442e10 ┆ … ┆ 2.3341e10 ┆ 9.9143e9 ┆ 7.6388e9 ┆ 5e8 │
│ 2029 ┆ 1.6512e10 ┆ 5.7381e10 ┆ 3.9286e10 ┆ … ┆ 4.2337e10 ┆ 2.2660e10 ┆ 5.8558e9 ┆ null │
└──────────┴───────────┴───────────┴───────────┴───┴───────────┴───────────┴───────────┴───────────┘
Axes
Get current axes of all Swedish USD AT1 bonds
# Get current axes for Swedish AT1 bonds in USD
query="""
let(#ax=axes();)
get(security_des, #ax)
for(filter(bondsuniv(ACTIVE),
crncy()=='USD' and
basel_iii_designation() == 'Additional Tier 1' and
country_iso() == 'SE'))
"""
with BQuery() as bq:
results = bq.bql(query)
print(results.combine())
┌───────────────┬─────────────────┬─────┬───────────┬───────────┬────────────────┬────────────────┐
│ ID ┆ security_des ┆ #ax ┆ ASK_DEPTH ┆ BID_DEPTH ┆ ASK_TOTAL_SIZE ┆ BID_TOTAL_SIZE │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ str ┆ i64 ┆ i64 ┆ f64 ┆ f64 │
╞═══════════════╪═════════════════╪═════╪═══════════╪═══════════╪════════════════╪════════════════╡
│ YU819930 Corp ┆ SEB 6 ¾ PERP ┆ Y ┆ 2 ┆ null ┆ 5.6e6 ┆ null │
│ ZO703315 Corp ┆ SHBASS 4 ⅜ PERP ┆ Y ┆ 1 ┆ 2 ┆ 5e6 ┆ 6e6 │
│ BR069680 Corp ┆ SWEDA 4 PERP ┆ Y ┆ null ┆ 1 ┆ null ┆ 3e6 │
│ ZL122341 Corp ┆ SWEDA 7 ⅝ PERP ┆ Y ┆ null ┆ 6 ┆ null ┆ 2.04e7 │
│ ZQ349286 Corp ┆ SEB 5 ⅛ PERP ┆ Y ┆ 2 ┆ 4 ┆ 5.5e6 ┆ 3e7 │
│ ZF859199 Corp ┆ SWEDA 7 ¾ PERP ┆ Y ┆ 1 ┆ 1 ┆ 2e6 ┆ 2e6 │
│ ZO703956 Corp ┆ SHBASS 4 ¾ PERP ┆ Y ┆ 1 ┆ 3 ┆ 1.2e6 ┆ 1.1e7 │
│ BW924993 Corp ┆ SEB 6 ⅞ PERP ┆ Y ┆ 1 ┆ 3 ┆ 5e6 ┆ 1.1e7 │
└───────────────┴─────────────────┴─────┴───────────┴───────────┴────────────────┴────────────────┘
Segments
The following example shows handling of two data-items with different length. The first dataframe describes the segments (and has length 5 in this case), while the second dataframe contains time series. One can join the dataframes on common columns and pivot the segments into columns as shown below:
# revenue per segment
query = """
let(#segment=segment_name();
#revenue=sales_Rev_turn(fpt=q, fpr=range(2023Q3, 2024Q3));
)
get(#segment, #revenue)
for(segments('GTN US Equity',type=reported,hierarchy=PRODUCT, level=1))
"""
with BQuery() as bq:
results = bq.bql(query)
df = results.combine().pivot(
index="PERIOD_END_DATE", on="#segment", values="#revenue"
)
print(df)
Output:
┌─────────────────┬──────────────┬──────────────────────┬────────┬────────────┐
│ PERIOD_END_DATE ┆ Broadcasting ┆ Production Companies ┆ Other ┆ Adjustment │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ date ┆ f64 ┆ f64 ┆ f64 ┆ f64 │
╞═════════════════╪══════════════╪══════════════════════╪════════╪════════════╡
│ 2023-09-30 ┆ 7.83e8 ┆ 2e7 ┆ 1.6e7 ┆ null │
│ 2023-12-31 ┆ 8.13e8 ┆ 3.2e7 ┆ 1.9e7 ┆ null │
│ 2024-03-31 ┆ 7.8e8 ┆ 2.4e7 ┆ 1.9e7 ┆ null │
│ 2024-06-30 ┆ 8.08e8 ┆ 1.8e7 ┆ 0.0 ┆ null │
│ 2024-09-30 ┆ 9.24e8 ┆ 2.6e7 ┆ 1.7e7 ┆ null │
└─────────────────┴──────────────┴──────────────────────┴────────┴────────────┘
Time Series
This is example of a single-item query returning total return for all GTN bonds in a long dataframe. We can easily pivot it into wide format, as in the example below
# Total Return of GTN Bonds
query = """
let(#rng = range(-1M, 0D);
#rets = return_series(calc_interval=#rng,per=W);)
get(#rets)
for(filter(bonds('GTN US Equity'), series() == '144A'))
"""
with BQuery() as bq:
results = bq.bql(query)
df = results[0].pivot(on="ID", index="DATE", values="#rets")
print(df)
Output:
shape: (6, 6)
┌────────────┬───────────────┬───────────────┬───────────────┬───────────────┬───────────────┐
│ DATE ┆ YX231113 Corp ┆ BS116983 Corp ┆ AV438089 Corp ┆ ZO860846 Corp ┆ LW375188 Corp │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ date ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ f64 │
╞════════════╪═══════════════╪═══════════════╪═══════════════╪═══════════════╪═══════════════╡
│ 2024-11-17 ┆ null ┆ null ┆ null ┆ null ┆ null │
│ 2024-11-24 ┆ 0.001653 ┆ 0.051179 ┆ 0.020363 ┆ 0.001371 ┆ -0.002939 │
│ 2024-12-01 ┆ 0.002837 ┆ 0.010405 ┆ -0.001466 ┆ 0.007275 ┆ 0.000581 │
│ 2024-12-08 ┆ -0.000041 ┆ 0.016145 ┆ 0.000766 ┆ 0.024984 ┆ 0.000936 │
│ 2024-12-15 ┆ 0.001495 ┆ -0.047 ┆ -0.000233 ┆ -0.043509 ┆ 0.002241 │
│ 2024-12-17 ┆ 0.00008 ┆ -0.000004 ┆ -0.0035 ┆ -0.007937 ┆ 0.000064 │
└────────────┴───────────────┴───────────────┴───────────────┴───────────────┴───────────────┘
Technical Analysis
with BQuery() as bq:
results = bq.bql(
"""
let(#ema20=emavg(period=20);
#ema200=emavg(period=200);
#rsi=rsi(close=px_last());)
get(name(), #ema20, #ema200, #rsi)
for(filter(members('OMX Index'),
and(#ema20 > #ema200, #rsi > 53)))
with(fill=PREV)
"""
)
print(results.combine())
Output:
┌─────────────────┬──────────────────┬────────────┬────────────┬──────────┬────────────┬───────────┐
│ ID ┆ name() ┆ #ema20 ┆ DATE ┆ CURRENCY ┆ #ema200 ┆ #rsi │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ str ┆ f64 ┆ date ┆ str ┆ f64 ┆ f64 │
╞═════════════════╪══════════════════╪════════════╪════════════╪══════════╪════════════╪═══════════╡
│ ERICB SS Equity ┆ Telefonaktiebola ┆ 90.152604 ┆ 2024-12-16 ┆ SEK ┆ 75.072151 ┆ 56.010028 │
│ ┆ get LM Ericsso ┆ ┆ ┆ ┆ ┆ │
│ ABB SS Equity ┆ ABB Ltd ┆ 630.622469 ┆ 2024-12-16 ┆ SEK ┆ 566.571183 ┆ 53.763102 │
│ SEBA SS Equity ┆ Skandinaviska ┆ 153.80595 ┆ 2024-12-16 ┆ SEK ┆ 150.742394 ┆ 56.460733 │
│ ┆ Enskilda Banken ┆ ┆ ┆ ┆ ┆ │
│ ASSAB SS Equity ┆ Assa Abloy AB ┆ 339.017591 ┆ 2024-12-16 ┆ SEK ┆ 317.057573 ┆ 53.351619 │
└─────────────────┴──────────────────┴────────────┴────────────┴──────────┴────────────┴───────────┘