Skip to content

Commit 4f730b3

Browse files
authored
Merge pull request #128 from kaijensen55/feat/xbbg-features-ci-uv
feat: BQL support + CI workflow improvements (uv venv)
2 parents b9a180d + cef43b9 commit 4f730b3

File tree

12 files changed

+848
-22
lines changed

12 files changed

+848
-22
lines changed

.github/workflows/publish_testpypi.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ jobs:
3838
run: |
3939
iwr https://astral.sh/uv/install.ps1 -UseBasicParsing | iex
4040
"$env:USERPROFILE\.local\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
41+
- name: Create uv virtual environment
42+
run: |
43+
uv venv
4144
- name: Build distributions with uv
4245
run: |
4346
uv build
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: PyPI Pre-Upload Build Test
2+
3+
on:
4+
push:
5+
branches: ['**']
6+
pull_request:
7+
8+
jobs:
9+
build-test:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v5
13+
with:
14+
fetch-depth: 0
15+
16+
- name: Set up Python 3.11
17+
uses: actions/setup-python@v6
18+
with:
19+
python-version: '3.11'
20+
21+
- name: Install uv (Linux/macOS)
22+
if: runner.os != 'Windows'
23+
shell: bash
24+
run: |
25+
curl -LsSf https://astral.sh/uv/install.sh | sh
26+
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
27+
28+
- name: Install uv (Windows)
29+
if: runner.os == 'Windows'
30+
shell: powershell
31+
run: |
32+
iwr https://astral.sh/uv/install.ps1 -UseBasicParsing | iex
33+
"$env:USERPROFILE\.local\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
34+
- name: Create uv virtual environment
35+
run: |
36+
uv venv
37+
38+
- name: Build distributions with uv
39+
run: |
40+
uv build
41+
42+
- name: Verify artifacts exist
43+
run: |
44+
ls -lah dist
45+
- name: Install twine
46+
run: |
47+
uv pip install twine
48+
- name: Metadata check (twine)
49+
run: |
50+
uv run twine check dist/*
51+
52+

.github/workflows/pypi_upload.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ jobs:
3232
run: |
3333
iwr https://astral.sh/uv/install.ps1 -UseBasicParsing | iex
3434
"$env:USERPROFILE\.local\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
35+
- name: Create uv virtual environment
36+
run: |
37+
uv venv
3538
- name: Build distributions with uv
3639
run: |
3740
uv build

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Ignore local PMC mapping and cache files
2+
xbbg/markets/pmc_map.json
3+
xbbg/markets/cached/pmc_cache.pkl
14
# Byte-compiled / optimized / DLL files
25
__pycache__/
36
*.py[cod]

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ dynamic = ["version"]
2525
dependencies = [
2626
"numpy>=2.2.6",
2727
"pandas>=2.3.3",
28+
"pandas-market-calendars>=5.1.3",
2829
"pyarrow>=22.0.0",
2930
"pytest>=8.4.2",
3031
"pytz>=2025.2",

uv.lock

Lines changed: 60 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

xbbg/blp.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
'subscribe',
2828
'adjust_ccy',
2929
'turnover',
30+
'bql',
3031
'active_futures',
3132
'fut_ticker',
3233
'cdx_ticker',
@@ -740,3 +741,45 @@ def turnover(
740741

741742
if data.empty and use_volume.empty: return pd.DataFrame()
742743
return pd.concat([adjust_ccy(data=data, ccy=ccy).div(factor), use_volume], axis=1)
744+
745+
746+
def bql(query: str, params: dict | None = None, overrides: list[tuple[str, object]] | None = None, **kwargs) -> pd.DataFrame:
747+
r"""Execute a BQL (Bloomberg Query Language) request.
748+
749+
Args:
750+
query: BQL query string.
751+
params: Optional request options for BQL (mapped directly to elements).
752+
overrides: Optional list of (field, value) overrides for the BQL request.
753+
**kwargs: Session and logging options.
754+
755+
Returns:
756+
pd.DataFrame: Parsed tabular results when available; otherwise a flattened view.
757+
758+
Examples:
759+
Basic usage (requires Bloomberg session; skipped in doctest):
760+
761+
>>> from xbbg import blp # doctest: +SKIP
762+
>>> df = blp.bql("get(px_last for('AAPL US Equity'))") # doctest: +SKIP
763+
>>> isinstance(df, pd.DataFrame) # doctest: +SKIP
764+
True
765+
"""
766+
logger = logs.get_logger(bql, **kwargs)
767+
768+
# Use BQL sendQuery with 'expression', mirroring common BQL request shape.
769+
settings = [('expression', query)]
770+
if params:
771+
settings.extend([(str(k), v) for k, v in params.items()])
772+
773+
request = process.create_request(
774+
service='//blp/bql',
775+
request='sendQuery',
776+
settings=settings,
777+
ovrds=overrides or [],
778+
**kwargs,
779+
)
780+
781+
logger.debug(f'Sending BQL request ...\n{request}')
782+
handle = conn.send_request(request=request, **kwargs)
783+
784+
rows = list(process.rec_events(func=process.process_bql, event_queue=handle["event_queue"], **kwargs))
785+
return pd.DataFrame(rows) if rows else pd.DataFrame()

xbbg/const.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,12 @@ def market_info(ticker: str) -> pd.Series:
230230
"""
231231
t_info = ticker.split()
232232
exch_only = len(ticker) == 2
233+
# Allow only supported asset types; special-case certain Corp tickers
233234
if (not exch_only) and (t_info[-1] not in ['Equity', 'Comdty', 'Curncy', 'Index']):
235+
# Minimal default for CDX generic CDS tickers (Corp asset)
236+
# Example: 'CDX IG CDSI GEN 5Y Corp' → use IndexUS session as default hours
237+
if t_info[-1] == 'Corp' and len(t_info) >= 2 and t_info[0] == 'CDX':
238+
return pd.Series({'exch': 'IndexUS'})
234239
return pd.Series(dtype=object)
235240

236241
a_info = asset_config(asset='Equity' if exch_only else t_info[-1])
@@ -256,11 +261,11 @@ def market_info(ticker: str) -> pd.Series:
256261
elif t_info[0][-1].isdigit():
257262
end_idx = 2 if t_info[-2].isdigit() else 1
258263
symbol = t_info[0][:-end_idx].strip()
259-
# Special contracts
260-
if (symbol[:2] == 'UX') and (t_info[-1] == 'Index'):
261-
symbol = 'UX'
262264
else:
263265
symbol = t_info[0].split('+')[0]
266+
# Special contracts: map any UX* Index form (e.g., UXA, UX1, UXF1UXG1) to UX root
267+
if (t_info[-1] == 'Index') and symbol.startswith('UX'):
268+
symbol = 'UX'
264269
return take_first(data=a_info, query=f'tickers == "{symbol}"')
265270

266271

0 commit comments

Comments
 (0)