Skip to content

Commit 8c2e74d

Browse files
Merge branch 'main' into copilot/add-plotting-capability
2 parents 8ade995 + 50f8d33 commit 8c2e74d

File tree

5 files changed

+660
-0
lines changed

5 files changed

+660
-0
lines changed

README.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ The library covers the full workflow of a sovereign debt analyst: from descripti
3030
- [Market Microstructure](#market-microstructure)
3131
- [IMF Framework](#imf-framework)
3232
- [Event Studies & Early Warning](#event-studies--early-warning)
33+
- [Inline Charts (Plotting)](#inline-charts-plotting)
3334
5. [Error Handling](#error-handling)
3435
6. [Running the Tests](#running-the-tests)
3536
7. [sovereign\_debt\_py plotting](#sovereign_debt_py-plotting)
@@ -88,6 +89,7 @@ modules =
8889
sovereign_debt_xl.market_microstructure
8990
sovereign_debt_xl.imf_framework
9091
sovereign_debt_xl.event_studies
92+
sovereign_debt_xl.plots
9193
```
9294

9395
Restart Excel. All `SOV_*` functions will appear in the function wizard.
@@ -1686,6 +1688,96 @@ Kaminsky-Lizondo-Reinhart (1999) signal extraction approach. Issues a signal for
16861688

16871689
---
16881690

1691+
### Inline Charts (Plotting)
1692+
1693+
These functions render Matplotlib charts to PNG and return them as **PyXLL inline images**, so the chart appears directly inside the Excel cell — no VBA, no popup windows. Results are LRU-cached (up to 128 entries) so recalculation skips re-rendering when inputs haven't changed.
1694+
1695+
---
1696+
1697+
#### `SDXL_PLOT_YIELD_CURVE`
1698+
**Python:** `sdxl_plot_yield_curve(tenors, yields, title, x_label, y_label, width_px, height_px, style)`
1699+
1700+
Plots a yield curve (tenor on x-axis, yield on y-axis) and returns an inline chart image.
1701+
If all yield values are ≤ 1.0, they are treated as decimal fractions and the y-axis is automatically formatted as a percentage.
1702+
1703+
| Parameter | Type | Default | Description |
1704+
|---|---|---|---|
1705+
| `tenors` | float range || Tenor values (numeric years or labels) |
1706+
| `yields` | float range || Yield values — must be the same length as `tenors` |
1707+
| `title` | string | `"Yield Curve"` | Chart title |
1708+
| `x_label` | string | `"Tenor"` | X-axis label |
1709+
| `y_label` | string | `"Yield"` | Y-axis label (auto-appended with `(%)` when decimal yields detected) |
1710+
| `width_px` | integer | `800` | Output image width in pixels |
1711+
| `height_px` | integer | `450` | Output image height in pixels |
1712+
| `style` | string | `"line"` | `"line"` for a plain line; `"markers"` to add circle markers |
1713+
1714+
**Returns:** A PyXLL inline image (PNG rendered at `width_px × height_px`).
1715+
1716+
**Excel example:**
1717+
```
1718+
=SDXL_PLOT_YIELD_CURVE(A2:A10, B2:B10, "UST Curve", "Tenor (yrs)", "Yield", 900, 500)
1719+
```
1720+
Plots the on-the-run US Treasury curve stored in columns A and B.
1721+
1722+
---
1723+
1724+
#### `SDXL_PLOT_TIMESERIES`
1725+
**Python:** `sdxl_plot_timeseries(dates, values, title, width_px, height_px)`
1726+
1727+
Plots a single time series (date on x-axis, value on y-axis) with automatic date-tick formatting and returns an inline chart image.
1728+
`dates` can be Excel serial date numbers, ISO date strings (`"2024-01-31"`), or Python `datetime.date` objects.
1729+
1730+
| Parameter | Type | Default | Description |
1731+
|---|---|---|---|
1732+
| `dates` | date range || Date values for the x-axis |
1733+
| `values` | float range || Numeric values — must be the same length as `dates` |
1734+
| `title` | string | `"Time Series"` | Chart title |
1735+
| `width_px` | integer | `800` | Output image width in pixels |
1736+
| `height_px` | integer | `450` | Output image height in pixels |
1737+
1738+
**Returns:** A PyXLL inline image (PNG rendered at `width_px × height_px`).
1739+
1740+
**Excel example:**
1741+
```
1742+
=SDXL_PLOT_TIMESERIES(A2:A300, B2:B300, "10yr Yield")
1743+
```
1744+
Plots 300 daily observations of the 10-year benchmark yield.
1745+
1746+
---
1747+
1748+
#### `SDXL_PLOT_ROLLING_AVG`
1749+
**Python:** `sdxl_plot_rolling_avg(dates, values, window, title, width_px, height_px)`
1750+
1751+
Plots the original series (light blue, semi-transparent) overlaid with a trailing rolling mean (bold blue) and returns an inline chart image.
1752+
The rolling mean is `NaN` for the first `window − 1` positions (insufficient history) so the overlay begins only once a full window of data is available.
1753+
1754+
| Parameter | Type | Default | Description |
1755+
|---|---|---|---|
1756+
| `dates` | date range || Date values for the x-axis |
1757+
| `values` | float range || Numeric values — must be the same length as `dates` |
1758+
| `window` | integer | `20` | Number of periods in the rolling window (must be ≥ 1) |
1759+
| `title` | string | `"Rolling Average"` | Chart title |
1760+
| `width_px` | integer | `800` | Output image width in pixels |
1761+
| `height_px` | integer | `450` | Output image height in pixels |
1762+
1763+
**Returns:** A PyXLL inline image (PNG rendered at `width_px × height_px`).
1764+
1765+
**Excel example:**
1766+
```
1767+
=SDXL_PLOT_ROLLING_AVG(A2:A300, B2:B300, 20, "Rolling Avg (20)")
1768+
```
1769+
Plots daily CDS spreads with a 20-day rolling mean overlay.
1770+
1771+
**Error codes returned by all plotting functions:**
1772+
1773+
| Error | Cause |
1774+
|---|---|
1775+
| `#SDXL: no data` | Input range is empty |
1776+
| `#SDXL: length mismatch (x=N, y=M)` | Date and value ranges have different lengths |
1777+
| `#SDXL: window must be >= 1` | `SDXL_PLOT_ROLLING_AVG` window parameter is zero or negative |
1778+
1779+
---
1780+
16891781
## Error Handling
16901782

16911783
Every function returns an `#ERR: <message>` string (rather than crashing Excel) when inputs are invalid. Common error messages:
@@ -1808,6 +1900,7 @@ png: bytes = fig_to_png_bytes(fig, width_px=800, height_px=450, dpi=120)
18081900
with open("chart.png", "wb") as f:
18091901
f.write(png)
18101902
```
1903+
All 176 tests should pass in under 10 seconds.
18111904

18121905
---
18131906

conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@
44
# Stub out pyxll so xl_func acts as a pass-through decorator during tests.
55
pyxll_mock = MagicMock()
66
pyxll_mock.xl_func.side_effect = lambda *args, **kwargs: (lambda fn: fn)
7+
# xl_image: return the raw bytes from the BytesIO so tests can inspect PNG output.
8+
pyxll_mock.xl_image.side_effect = lambda data=None, **kw: (data.read() if hasattr(data, "read") else data)
79
sys.modules["pyxll"] = pyxll_mock

sovereign_debt_xl/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@
1616
from .market_microstructure import * # noqa: F401,F403
1717
from .imf_framework import * # noqa: F401,F403
1818
from .event_studies import * # noqa: F401,F403
19+
from .plots import * # noqa: F401,F403

0 commit comments

Comments
 (0)