Skip to content

Commit 50f8d33

Browse files
Merge pull request #7 from zachessesjohnson/copilot/add-plot-udfs-for-excel
docs: add Inline Charts (Plotting) section to README
2 parents 6bfb626 + 218e5ac commit 50f8d33

File tree

7 files changed

+664
-2
lines changed

7 files changed

+664
-2
lines changed

README.md

Lines changed: 95 additions & 2 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. [Extracting sovereign\_debt\_py](#extracting-sovereign_debt_py)
@@ -48,14 +49,15 @@ The library covers the full workflow of a sovereign debt analyst: from descripti
4849
| scipy | any recent |
4950
| statsmodels | any recent |
5051
| scikit-learn | any recent |
52+
| matplotlib | any recent |
5153

5254
---
5355

5456
## Installation & PyXLL Setup
5557

5658
```bash
5759
# 1. Install Python dependencies
58-
pip install numpy pandas scipy statsmodels scikit-learn pyxll
60+
pip install numpy pandas scipy statsmodels scikit-learn matplotlib pyxll
5961

6062
# 2. Install this package
6163
pip install -e .
@@ -86,6 +88,7 @@ modules =
8688
sovereign_debt_xl.market_microstructure
8789
sovereign_debt_xl.imf_framework
8890
sovereign_debt_xl.event_studies
91+
sovereign_debt_xl.plots
8992
```
9093

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

16851688
---
16861689

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

16891782
Every function returns an `#ERR: <message>` string (rather than crashing Excel) when inputs are invalid. Common error messages:
@@ -1713,7 +1806,7 @@ pip install pytest
17131806
python -m pytest
17141807
```
17151808

1716-
All 154 tests should pass in under 5 seconds.
1809+
All 176 tests should pass in under 10 seconds.
17171810

17181811
---
17191812

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

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ dependencies = [
1313
"scipy",
1414
"statsmodels",
1515
"scikit-learn",
16+
"matplotlib",
1617
]
1718

1819
[project.optional-dependencies]

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ pandas
33
scipy
44
statsmodels
55
scikit-learn
6+
matplotlib
67
pytest

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)