Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 58 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,56 @@ jobs:
path: convex-excel-addin-v*.zip
retention-days: 5

publish-crates:
name: Publish to crates.io
runs-on: ubuntu-latest
needs: validate
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.inputs.tag || github.ref }}
Comment on lines +122 to +129

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Drop unnecessary GitHub token exposure from the publish job.

This job only needs repository read access, but checkout currently persists credentials and the job inherits default GITHUB_TOKEN permissions before running cargo publish. Tighten it to contents: read and disable credential persistence so the crates.io token is the only credential available during publish verification.

Suggested hardening diff
   publish-crates:
     name: Publish to crates.io
     runs-on: ubuntu-latest
     needs: validate
+    permissions:
+      contents: read
     steps:
       - uses: actions/checkout@v6
         with:
           ref: ${{ github.event.inputs.tag || github.ref }}
+          persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
publish-crates:
name: Publish to crates.io
runs-on: ubuntu-latest
needs: validate
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.inputs.tag || github.ref }}
publish-crates:
name: Publish to crates.io
runs-on: ubuntu-latest
needs: validate
permissions:
contents: read
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.inputs.tag || github.ref }}
persist-credentials: false
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 127-129: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[warning] 122-166: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 122 - 129, The publish-crates job
is exposing more GitHub credentials than needed, and the checkout step is still
persisting auth before cargo publish runs. Update the publish-crates workflow
job to limit permissions to contents: read and adjust the actions/checkout step
in the release workflow to disable credential persistence. Keep the job focused
on repository read access only so the crates.io token remains the sole
credential used during publish verification.

Source: Linters/SAST tools


- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Cache cargo registry
uses: Swatinem/rust-cache@v2

# Publish the five library crates in dependency order. cargo publish
# waits for each version to land in the index before returning, so the
# next crate can resolve it. The remaining workspace crates are
# publish = false and are skipped automatically.
#
# crates.io versions are immutable, so a crate already at this version is
# skipped (HTTP 200 from the per-version API) rather than erroring. This
# makes the job safe to re-run after a mid-list failure.
- name: Publish library crates
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
VERSION: ${{ needs.validate.outputs.version }}
run: |
set -euo pipefail
ua="convex-release (https://github.com/sujitn/convex)"
for crate in convex-core convex-math convex-curves convex-bonds convex-analytics; do
status=$(curl -s -o /dev/null -w '%{http_code}' \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Add retry and timeout options to this curl call. A transient DNS/5xx failure or slow response will abort the entire release pipeline. Consider --retry 5 --retry-all-errors --connect-timeout 10 --max-time 30 to add resilience.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/release.yml, line 153:

<comment>Add retry and timeout options to this `curl` call. A transient DNS/5xx failure or slow response will abort the entire release pipeline. Consider `--retry 5 --retry-all-errors --connect-timeout 10 --max-time 30` to add resilience.</comment>

<file context>
@@ -119,10 +119,56 @@ jobs:
+          set -euo pipefail
+          ua="convex-release (https://github.com/sujitn/convex)"
+          for crate in convex-core convex-math convex-curves convex-bonds convex-analytics; do
+            status=$(curl -s -o /dev/null -w '%{http_code}' \
+              -H "User-Agent: $ua" \
+              "https://crates.io/api/v1/crates/$crate/$VERSION")
</file context>
Suggested change
status=$(curl -s -o /dev/null -w '%{http_code}' \
status=$(curl -s -o /dev/null -w '%{http_code}' \
--retry 5 --retry-all-errors --connect-timeout 10 --max-time 30 \

-H "User-Agent: $ua" \
"https://crates.io/api/v1/crates/$crate/$VERSION")
if [ "$status" = "200" ]; then
echo "✓ $crate@$VERSION already on crates.io — skipping"
continue
elif [ "$status" != "404" ]; then
echo "Unexpected HTTP $status checking $crate@$VERSION on crates.io" >&2
exit 1
Comment on lines +153 to +161

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add retries and timeouts around the crates.io probe.

A transient DNS/5xx/slow-response failure here aborts the whole release before publishing starts. Bound the call and give it a small retry budget.

Suggested retry/timeout diff
-            status=$(curl -s -o /dev/null -w '%{http_code}' \
+            status=$(curl -s -o /dev/null -w '%{http_code}' \
+              --retry 5 --retry-all-errors --connect-timeout 10 --max-time 30 \
               -H "User-Agent: $ua" \
               "https://crates.io/api/v1/crates/$crate/$VERSION")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
status=$(curl -s -o /dev/null -w '%{http_code}' \
-H "User-Agent: $ua" \
"https://crates.io/api/v1/crates/$crate/$VERSION")
if [ "$status" = "200" ]; then
echo "✓ $crate@$VERSION already on crates.io — skipping"
continue
elif [ "$status" != "404" ]; then
echo "Unexpected HTTP $status checking $crate@$VERSION on crates.io" >&2
exit 1
status=$(curl -s -o /dev/null -w '%{http_code}' \
--retry 5 --retry-all-errors --connect-timeout 10 --max-time 30 \
-H "User-Agent: $ua" \
"https://crates.io/api/v1/crates/$crate/$VERSION")
if [ "$status" = "200" ]; then
echo "✓ $crate@$VERSION already on crates.io — skipping"
continue
elif [ "$status" != "404" ]; then
echo "Unexpected HTTP $status checking $crate@$VERSION on crates.io" >&2
exit 1
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 153 - 161, The crates.io
availability check in the release workflow needs bounded retries and a timeout
so transient DNS, 5xx, or slow responses don’t fail the whole release. Update
the probe around the `status=$(curl ...)` block in the release job to use a
small retry budget and a request timeout, while preserving the existing 200/404
handling and the `Unexpected HTTP $status` failure path.

fi
echo "::group::cargo publish -p $crate ($VERSION)"
cargo publish -p "$crate"
echo "::endgroup::"
done

github-release:
name: Create GitHub Release
runs-on: ubuntu-latest
needs: [validate, build-excel-addin]
needs: [validate, build-excel-addin, publish-crates]
permissions:
contents: write
steps:
Expand All @@ -145,8 +191,17 @@ jobs:
body: |
## Installation

Internal workspace — not published to crates.io. Clone the repo
and depend on the crates via path.
The core library crates are published to crates.io. Depend on the
unified analytics crate (it pulls in core, math, curves and bonds):

```toml
convex-analytics = "${{ needs.validate.outputs.version }}"
```

Individually published crates: `convex-core`, `convex-math`,
`convex-curves`, `convex-bonds`, `convex-analytics`. The `convex`
umbrella plus the FFI / WASM / MCP / server crates are internal —
consume them via a git or path dependency.

### Excel Add-in
Download the `convex-excel-addin` zip from assets below, extract,
Expand Down
21 changes: 15 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,22 @@ keywords = ["finance", "bonds", "fixed-income", "pricing", "quantitative"]
categories = ["finance", "mathematics", "science"]

[workspace.dependencies]
# Internal crates — path-only; this workspace is not published to crates.io.
# Internal crates.
#
# The five library crates (core, math, curves, bonds, analytics) are published
# to crates.io, so they carry both `path` and `version`: cargo uses the path for
# local builds and rewrites it to the version on `cargo publish`. Keep `version`
# in sync with `workspace.package.version` above.
#
# Everything else is `publish = false` (set in each crate's Cargo.toml) and stays
# path-only — the facade `convex` name is taken on crates.io by an unrelated
# project, and the engine/server/bindings crates are applications, not libraries.
convex = { path = "crates/convex" }
convex-core = { path = "crates/convex-core" }
convex-math = { path = "crates/convex-math" }
convex-curves = { path = "crates/convex-curves" }
convex-bonds = { path = "crates/convex-bonds" }
convex-analytics = { path = "crates/convex-analytics" }
convex-core = { path = "crates/convex-core", version = "0.13.0" }
convex-math = { path = "crates/convex-math", version = "0.13.0" }
convex-curves = { path = "crates/convex-curves", version = "0.13.0" }
convex-bonds = { path = "crates/convex-bonds", version = "0.13.0" }
convex-analytics = { path = "crates/convex-analytics", version = "0.13.0" }
convex-portfolio = { path = "crates/convex-portfolio" }
convex-ffi = { path = "crates/convex-ffi" }
convex-wasm = { path = "crates/convex-wasm" }
Expand Down
144 changes: 77 additions & 67 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,69 +22,61 @@ Convex is a production-grade fixed income analytics library providing comprehens
- **High Performance**: Microsecond-level pricing
- **Type Safety**: Leverage Rust's type system to prevent errors
- **WebAssembly**: Full browser support via wasm-pack ([Live Demo](https://convex-demo.pages.dev/) | [WASM Demo](https://sujitn.github.io/convex/))
- **Language Bindings**: C FFI bindings available; Python, Java, C# (coming soon)
- **Language Bindings**: C FFI and Java (published to Maven Central) available; Python and C# planned

## Quick Start

### Installation

Convex is an internal workspace — it is not published to crates.io. Depend on the
crate(s) you need via a git dependency:
The core library crates are published to [crates.io](https://crates.io). Most
users only need `convex-analytics`, which re-exports the lower layers (core,
math, curves, bonds):

```toml
[dependencies]
convex-bonds = { git = "https://github.com/sujitn/convex.git" }
convex-analytics = { git = "https://github.com/sujitn/convex.git" }
convex-curves = { git = "https://github.com/sujitn/convex.git" }
convex-analytics = "0.13"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The dependency snippet tells users to add only convex-analytics = "0.13", but both example snippets directly import from convex_bonds, convex_core, convex_curves, and rust_decimal_macros. In Rust, transitive dependencies cannot be used via use crate_name::... without an explicit Cargo.toml entry. Either expand the dependency block to list all directly imported crates, or rewrite the examples to use only convex_analytics re-export paths.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At README.md, line 37:

<comment>The dependency snippet tells users to add only `convex-analytics = "0.13"`, but both example snippets directly import from `convex_bonds`, `convex_core`, `convex_curves`, and `rust_decimal_macros`. In Rust, transitive dependencies cannot be used via `use crate_name::...` without an explicit `Cargo.toml` entry. Either expand the dependency block to list all directly imported crates, or rewrite the examples to use only `convex_analytics` re-export paths.</comment>

<file context>
@@ -22,69 +22,61 @@ Convex is a production-grade fixed income analytics library providing comprehens
-convex-bonds    = { git = "https://github.com/sujitn/convex.git" }
-convex-analytics = { git = "https://github.com/sujitn/convex.git" }
-convex-curves   = { git = "https://github.com/sujitn/convex.git" }
+convex-analytics = "0.13"

</file context>


</details>

```
Comment on lines +31 to 38

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

The dependency snippet does not match the imports in the examples.

convex-analytics = "0.13" alone is not enough for the code shown here: both snippets also import convex_bonds, convex_core, convex_curves, and rust_decimal_macros directly. The mirrored examples compile only because they live inside the workspace package; a downstream user copying this README will hit unresolved-crate errors. Either expand the dependency block to include every directly imported crate, or rewrite the snippets to use only convex_analytics re-exports.

Also applies to: 59-62, 88-92

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 31 - 38, The dependency example in the README does
not match the crates imported in the code snippets, so a downstream user cannot
compile them as written. Update the examples in the README sections around the
repeated import blocks to either add every directly used crate (`convex_bonds`,
`convex_core`, `convex_curves`, and `rust_decimal_macros`) to the dependency
snippet or rewrite the snippets to use only `convex_analytics` re-exports. Make
sure the imports shown in the examples are consistent with the dependency block
and avoid relying on workspace-only availability.


or clone the repo and use a path dependency.
The full set of published crates: `convex-core`, `convex-math`,
`convex-curves`, `convex-bonds`, `convex-analytics`.

### Example: Pricing a US Treasury Bond
> **Note:** the `convex` umbrella crate is *not* on crates.io — that name
> belongs to an unrelated project ([convex.dev](https://convex.dev)). The
> umbrella facade, along with the FFI, WASM, MCP and pricing-server crates,
> ships in-repo only. Depend on them via git or a path dependency:
>
> ```toml
> convex = { git = "https://github.com/sujitn/convex.git" }
> ```

> The two snippets below are mirrored by compile-checked examples — run them
> with `cargo run -p convex-analytics --example readme_quickstart` and
> `--example readme_zspread`.

### Example: Build a Bond and Compute Yield

```rust
use convex::prelude::*;
use convex_analytics::functions::yield_to_maturity;
use convex_bonds::instruments::FixedRateBond;
use convex_core::types::{Date, Frequency};
use rust_decimal_macros::dec;

fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a US Treasury bond
let bond = Bond::builder()
.isin("US912828Z229")
.coupon_rate(dec!(2.5))
.maturity(Date::from_ymd(2030, 5, 15))
// A 5% semi-annual bond. Coupon is a decimal (0.05 == 5%); day count and
// calendar default to 30/360 US and SIFMA.
let bond = FixedRateBond::builder()
.cusip_unchecked("912828Z29")
.coupon_rate(dec!(0.05))
.issue_date(Date::from_ymd(2020, 5, 15)?)
.maturity(Date::from_ymd(2030, 5, 15)?)
.frequency(Frequency::SemiAnnual)
.day_count(DayCount::ActAct)
.build()?;

// Build a yield curve from market data
let curve = YieldCurve::bootstrap()
.add_deposit(dec!(0.015), Period::Months(3))
.add_deposit(dec!(0.018), Period::Months(6))
.add_bond(dec!(98.50), Date::from_ymd(2027, 5, 15))
.add_bond(dec!(97.25), Date::from_ymd(2032, 5, 15))
.interpolation(Interpolation::CubicSpline)
.build()?;

// Price the bond
let settlement = Date::today();
let price = bond.price(&curve, settlement)?;

println!("Clean Price: {:.4}", price.clean());
println!("Dirty Price: {:.4}", price.dirty());
println!("Accrued Interest: {:.4}", price.accrued());
// Yield to maturity from a clean price of 98.50 (per 100 face).
let settlement = Date::from_ymd(2025, 5, 15)?;
let ytm = yield_to_maturity(&bond, settlement, dec!(98.50), Frequency::SemiAnnual)?;

// Calculate yield
let ytm = bond.yield_to_maturity(price.clean(), settlement)?;
println!("Yield to Maturity: {:.4}%", ytm.as_percentage());

// Calculate risk metrics
let duration = bond.modified_duration(&curve, settlement)?;
let convexity = bond.convexity(&curve, settlement)?;
let dv01 = bond.dv01(&curve, settlement)?;

println!("Modified Duration: {:.4}", duration);
println!("Convexity: {:.4}", convexity);
println!("DV01: ${:.2}", dv01);
println!("Yield to Maturity: {:.4}%", ytm.yield_percent());

Ok(())
}
Expand All @@ -93,28 +85,36 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
### Example: Z-Spread Calculation

```rust
use convex::prelude::*;
use convex_analytics::spreads::z_spread;
use convex_bonds::instruments::FixedRateBond;
use convex_core::types::{Date, Frequency};
use convex_curves::curves::DiscountCurveBuilder;
use rust_decimal_macros::dec;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let bond = Bond::builder()
.isin("US459200KJ18") // IBM Corporate Bond
.coupon_rate(dec!(3.75))
.maturity(Date::from_ymd(2028, 11, 15))
// 3.75% semi-annual corporate bond.
let bond = FixedRateBond::builder()
.cusip_unchecked("459200KJ1")
.coupon_rate(dec!(0.0375))
.issue_date(Date::from_ymd(2018, 11, 15)?)
.maturity(Date::from_ymd(2028, 11, 15)?)
.frequency(Frequency::SemiAnnual)
.day_count(DayCount::Thirty360)
.build()?;

// Government curve for discounting
let gov_curve = YieldCurve::from_market_data(/* ... */)?;

// Market price
let market_price = Price::new(dec!(102.50), Currency::USD);

// Calculate Z-spread
let z_spread = bond.z_spread(market_price, &gov_curve, Date::today())?;

println!("Z-Spread: {:.2} bps", z_spread.as_basis_points());
// A flat 4% continuously-compounded discount curve to spread against.
let settlement = Date::from_ymd(2025, 1, 15)?;
let rate = 0.04_f64;
let curve = DiscountCurveBuilder::new(settlement)
.add_pillar(1.0, (-rate * 1.0).exp())
.add_pillar(2.0, (-rate * 2.0).exp())
.add_pillar(5.0, (-rate * 5.0).exp())
.add_pillar(10.0, (-rate * 10.0).exp())
.with_extrapolation()
.build()?;

// Z-spread that reprices the bond to a dirty price of 102.50.
let spread = z_spread(&bond, dec!(102.50), &curve, settlement)?;
println!("Z-Spread: {:.2} bps", spread.as_bps());

Ok(())
}
Expand Down Expand Up @@ -182,17 +182,27 @@ spread DV01.

## Architecture

Convex is organized into several crates for modularity:
Convex is organized into focused crates. The five **library** crates are
published to crates.io; the rest are internal (the facade, language bindings,
and the pricing-engine/server stack) and ship in-repo only.

```
convex/
├── convex-core # Core types (Date, Price, Yield, etc.)
├── convex-math # Mathematical utilities and solvers
├── convex-curves # Yield curve construction and interpolation
├── convex-bonds # Bond instruments and definitions
├── convex-analytics # Unified analytics (yields, spreads, risk)
├── convex-wasm # WebAssembly bindings
└── convex-ffi # Foreign Function Interface for language bindings
├── convex-core # Core types (Date, Price, Yield, calendars, day counts) [published]
├── convex-math # Solvers and interpolators (Brent, Newton, LM) [published]
├── convex-curves # Yield/credit curves, bootstrapping, multi-curve [published]
├── convex-bonds # Bond instruments (fixed, FRN, callable, zero, sinker) [published]
├── convex-analytics # Unified analytics: pricing, yields, spreads, risk [published]
├── convex # Single-import facade re-exporting the public API (internal)
├── convex-portfolio # Portfolio and ETF analytics (internal)
├── convex-ffi # C-ABI FFI for language bindings (Java, Excel) (internal)
├── convex-wasm # WebAssembly bindings for the browser demo (internal)
├── convex-mcp # MCP server for tool/agent integration (internal)
├── convex-ports # Hexagonal port traits (market/reference data, storage) (internal)
├── convex-engine # Reactive pricing engine with a calculation graph (internal)
├── convex-ext-file # File-backed market/reference data adapter (internal)
├── convex-ext-redb # redb embedded-storage adapter (internal)
└── convex-server # REST + WebSocket pricing server (deployed to Fly.io) (internal)
```
Comment on lines 189 to 206

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language to this fenced block.

This fence triggers markdownlint MD040 as written. text is enough here.

Suggested fix
-```
+```text
 convex/
 ├── convex-core        # Core types (Date, Price, Yield, calendars, day counts)   [published]
 ...
 └── convex-server      # REST + WebSocket pricing server (deployed to Fly.io)      (internal)
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 189-189: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 189 - 206, The fenced architecture block in README.md
is missing a language tag, triggering markdownlint MD040. Update that Markdown
code fence to use a text language hint on the block containing the convex
package tree so the renderer/linter recognizes it correctly.

Source: Linters/SAST tools


## Performance
Expand Down
27 changes: 27 additions & 0 deletions crates/convex-analytics/examples/readme_quickstart.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//! Mirrors the Quick Start snippet in the repository README so it stays
//! compile-checked. Run with: `cargo run -p convex-analytics --example readme_quickstart`.

use convex_analytics::functions::yield_to_maturity;
use convex_bonds::instruments::FixedRateBond;
use convex_core::types::{Date, Frequency};
use rust_decimal_macros::dec;

fn main() -> Result<(), Box<dyn std::error::Error>> {
// A 5% semi-annual bond. Coupon is a decimal (0.05 == 5%); day count and
// calendar default to 30/360 US and SIFMA.
let bond = FixedRateBond::builder()
.cusip_unchecked("912828Z29")
.coupon_rate(dec!(0.05))
.issue_date(Date::from_ymd(2020, 5, 15)?)
.maturity(Date::from_ymd(2030, 5, 15)?)
.frequency(Frequency::SemiAnnual)
.build()?;

// Yield to maturity from a clean price of 98.50 (per 100 face).
let settlement = Date::from_ymd(2025, 5, 15)?;
let ytm = yield_to_maturity(&bond, settlement, dec!(98.50), Frequency::SemiAnnual)?;

println!("Yield to Maturity: {:.4}%", ytm.yield_percent());

Ok(())
}
36 changes: 36 additions & 0 deletions crates/convex-analytics/examples/readme_zspread.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//! Mirrors the Z-Spread snippet in the repository README so it stays
//! compile-checked. Run with: `cargo run -p convex-analytics --example readme_zspread`.

use convex_analytics::spreads::z_spread;
use convex_bonds::instruments::FixedRateBond;
use convex_core::types::{Date, Frequency};
use convex_curves::curves::DiscountCurveBuilder;
use rust_decimal_macros::dec;

fn main() -> Result<(), Box<dyn std::error::Error>> {
// 3.75% semi-annual corporate bond.
let bond = FixedRateBond::builder()
.cusip_unchecked("459200KJ1")
.coupon_rate(dec!(0.0375))
.issue_date(Date::from_ymd(2018, 11, 15)?)
.maturity(Date::from_ymd(2028, 11, 15)?)
.frequency(Frequency::SemiAnnual)
.build()?;

// A flat 4% continuously-compounded discount curve to spread against.
let settlement = Date::from_ymd(2025, 1, 15)?;
let rate = 0.04_f64;
let curve = DiscountCurveBuilder::new(settlement)
.add_pillar(1.0, (-rate * 1.0).exp())
.add_pillar(2.0, (-rate * 2.0).exp())
.add_pillar(5.0, (-rate * 5.0).exp())
.add_pillar(10.0, (-rate * 10.0).exp())
.with_extrapolation()
.build()?;

// Z-spread that reprices the bond to a dirty price of 102.50.
let spread = z_spread(&bond, dec!(102.50), &curve, settlement)?;
println!("Z-Spread: {:.2} bps", spread.as_bps());

Ok(())
}
1 change: 1 addition & 0 deletions crates/convex-engine/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[package]
name = "convex-engine"
publish = false # internal; not published to crates.io
version.workspace = true
edition.workspace = true
rust-version.workspace = true
Expand Down
1 change: 1 addition & 0 deletions crates/convex-ext-file/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[package]
name = "convex-ext-file"
publish = false # internal; not published to crates.io
version.workspace = true
edition.workspace = true
rust-version.workspace = true
Expand Down
1 change: 1 addition & 0 deletions crates/convex-ext-redb/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[package]
name = "convex-ext-redb"
publish = false # internal; not published to crates.io
version.workspace = true
edition.workspace = true
rust-version.workspace = true
Expand Down
1 change: 1 addition & 0 deletions crates/convex-ffi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[package]
name = "convex-ffi"
publish = false # internal; not published to crates.io
description = "C FFI bindings for the Convex fixed income analytics library"
version.workspace = true
edition.workspace = true
Expand Down
Loading
Loading