Repository navigation
JVM bindings, hedge-advisor FFI, and Excel concurrency/handle fixes #112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
591b170
56c454f
4dd0b16
2034036
580a3fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| name: Java release | ||
|
|
||
| # Cut a release by pushing a tag, e.g.: git tag java-v0.13.0 && git push origin java-v0.13.0 | ||
| on: | ||
| push: | ||
| tags: | ||
| - 'java-v*' | ||
|
|
||
| # Deploy goes to Maven Central via the Sonatype plugin (repo secrets), so the | ||
| # job needs no write scope on the GitHub token — only read to check out. | ||
| permissions: | ||
| contents: read | ||
|
|
||
| env: | ||
| CARGO_TERM_COLOR: always | ||
|
|
||
| jobs: | ||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||
| # Build the convex-ffi cdylib for every platform classifier. | ||
| natives: | ||
| name: cdylib (${{ matrix.classifier }}) | ||
| runs-on: ${{ matrix.runner }} | ||
| strategy: | ||
| fail-fast: true | ||
| matrix: | ||
| include: | ||
| - { runner: ubuntu-latest, target: x86_64-unknown-linux-gnu, classifier: linux-x86_64, lib: libconvex_ffi.so } | ||
| - { runner: ubuntu-latest, target: aarch64-unknown-linux-gnu, classifier: linux-aarch64, lib: libconvex_ffi.so, cross: true } | ||
| - { runner: macos-latest, target: x86_64-apple-darwin, classifier: darwin-x86_64, lib: libconvex_ffi.dylib } | ||
| - { runner: macos-latest, target: aarch64-apple-darwin, classifier: darwin-aarch64, lib: libconvex_ffi.dylib } | ||
| - { runner: windows-latest, target: x86_64-pc-windows-msvc, classifier: windows-x86_64, lib: convex_ffi.dll } | ||
| steps: | ||
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | ||
| with: | ||
| targets: ${{ matrix.target }} | ||
|
|
||
| - name: Install aarch64 cross linker | ||
| if: matrix.cross | ||
| run: | | ||
| sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu | ||
| echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> "$GITHUB_ENV" | ||
|
|
||
| - name: Build cdylib | ||
| run: cargo build -p convex-ffi --release --target ${{ matrix.target }} | ||
|
|
||
| - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | ||
| with: | ||
| name: ${{ matrix.classifier }} | ||
| path: target/${{ matrix.target }}/release/${{ matrix.lib }} | ||
| if-no-files-found: error | ||
|
|
||
| # Stage every cdylib into the jar resources and publish to Maven Central. | ||
| deploy: | ||
| name: Publish to Maven Central | ||
| needs: natives | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: Download native libraries | ||
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | ||
| with: | ||
| path: staging | ||
|
|
||
| - name: Stage natives into resources | ||
| # download-artifact lays out staging/<classifier>/<lib>; mirror that | ||
| # straight into the resources/native tree the loader reads. | ||
| run: | | ||
| mkdir -p bindings/java/src/main/resources/native | ||
| cp -r staging/* bindings/java/src/main/resources/native/ | ||
|
|
||
| - name: Set up JDK 22 | ||
| uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4 | ||
| with: | ||
| distribution: temurin | ||
| java-version: '22' | ||
| cache: maven | ||
| server-id: central | ||
| server-username: MAVEN_CENTRAL_USERNAME | ||
| server-password: MAVEN_CENTRAL_PASSWORD | ||
| gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} | ||
| gpg-passphrase: MAVEN_GPG_PASSPHRASE | ||
|
|
||
| - name: Deploy | ||
| working-directory: bindings/java | ||
| env: | ||
| MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} | ||
| MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} | ||
| MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} | ||
| run: mvn -B -Prelease deploy | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| name: Java bindings | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved
|
||
|
|
||
| on: | ||
| push: | ||
| branches: [main] | ||
| paths: | ||
| - 'bindings/java/**' | ||
| - 'crates/convex-ffi/**' | ||
| - 'crates/convex-analytics/**' | ||
| - 'crates/convex-bonds/**' | ||
| - 'crates/convex-core/**' | ||
| - 'crates/convex-curves/**' | ||
| - 'crates/convex-math/**' | ||
| - '.github/workflows/java.yml' | ||
| pull_request: | ||
| branches: [main] | ||
| paths: | ||
| - 'bindings/java/**' | ||
| - 'crates/convex-ffi/**' | ||
| - 'crates/convex-analytics/**' | ||
| - 'crates/convex-bonds/**' | ||
| - 'crates/convex-core/**' | ||
| - 'crates/convex-curves/**' | ||
| - 'crates/convex-math/**' | ||
| - '.github/workflows/java.yml' | ||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||
|
|
||
| # Build + test only; no repo writes. | ||
| permissions: | ||
| contents: read | ||
|
|
||
| env: | ||
| CARGO_TERM_COLOR: always | ||
|
|
||
| jobs: | ||
| # Linux x86_64 build + test. The release workflow extends this into a full | ||
| # OS/arch matrix and assembles every cdylib into the natives jar. | ||
| verify: | ||
| name: Build native + mvn verify (linux-x86_64) | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: Install Rust toolchain | ||
| uses: dtolnay/rust-toolchain@stable | ||
|
|
||
| - name: Cache cargo | ||
| uses: Swatinem/rust-cache@v2 | ||
|
|
||
| - name: Build convex-ffi cdylib | ||
| run: cargo build -p convex-ffi --release | ||
|
|
||
| - name: Stage native library | ||
| run: | | ||
| dest=bindings/java/src/main/resources/native/linux-x86_64 | ||
| mkdir -p "$dest" | ||
| cp target/release/libconvex_ffi.so "$dest/" | ||
|
|
||
| - name: Set up JDK 22 | ||
| uses: actions/setup-java@v4 | ||
|
Comment on lines
+41
to
+61
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
FILE=".github/workflows/java.yml"
echo "== File exists? =="
ls -l "$FILE"
echo "== Lines 1-120 (for context) =="
cat -n "$FILE" | sed -n '1,120p'
echo "== uses: entries with line numbers =="
rg -n "^\s*-\s*uses:\s*" "$FILE" || true
echo "== All uses: occurrences (broader) =="
rg -n "uses:\s*" "$FILE" || trueRepository: sujitn/convex Length of output: 2210 Pin the third-party GitHub Actions to full commit SHAs.
🧰 Tools🪛 zizmor (1.25.2)[warning] 29-29: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false (artipacked) [error] 29-29: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) [error] 32-32: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) [error] 35-35: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) [error] 47-47: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) 🤖 Prompt for AI Agents |
||
| with: | ||
| distribution: temurin | ||
| java-version: '22' | ||
| cache: maven | ||
|
|
||
| - name: Maven verify | ||
| run: mvn -B -f bindings/java/pom.xml verify | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # Maven build output | ||
| target/ | ||
|
|
||
| # Native libraries are built/staged by CI, not committed. | ||
| src/main/resources/native/**/*.so | ||
| src/main/resources/native/**/*.dylib | ||
| src/main/resources/native/**/*.dll |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| # Convex Java | ||
|
|
||
| JVM bindings for the [Convex](../../README.md) fixed-income analytics library. | ||
|
|
||
| Java users get the same analytics as the Rust API — bonds, curves, pricing, | ||
| risk, spreads, and the hedge advisor — through an idiomatic typed API | ||
| (builders, `BigDecimal`/`LocalDate`, immutable result records, `AutoCloseable` | ||
| handles). The JSON-RPC boundary to the native library is fully hidden. | ||
|
|
||
| ## How it works | ||
|
|
||
| ``` | ||
| com.convex ← typed public API (builders, ConvexAnalytics, HedgeAdvisor) | ||
| com.convex.internal ← Panama FFM binding + Jackson envelope codec (hidden) | ||
| crates/convex-ffi ← the native cdylib (C ABI), built from the Rust workspace | ||
| ``` | ||
|
Comment on lines
+12
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Label the architecture diagram fence. This unlabeled fence triggers markdownlint and is easier to read if marked as plain text. 🧰 Tools🪛 markdownlint-cli2 (0.22.1)[warning] 12-12: Fenced code blocks should have a language specified (MD040, fenced-code-language) 🤖 Prompt for AI Agents |
||
|
|
||
| The binding calls the native library through Java's **Foreign Function & Memory | ||
| API** (Project Panama). There is **no hand-written JNI / C shim** — Java binds | ||
| directly to the cdylib's `char*`-in / `char*`-out C functions by symbol name. | ||
|
|
||
| ## Requirements | ||
|
|
||
| - **JDK 22 or newer** (the FFM API is finalized in 22). | ||
| - At runtime, native access must be granted: | ||
| `--enable-native-access=ALL-UNNAMED` (the Maven build sets this for tests). | ||
| - The native `convex-ffi` cdylib for your platform, bundled under | ||
| `src/main/resources/native/<classifier>/` (see below). Versions track the Rust | ||
| workspace (currently `0.13.0`). | ||
|
|
||
| ## Building | ||
|
|
||
| ```sh | ||
| # 1. Build + stage the native library for your platform classifier | ||
| # (linux-x86_64, linux-aarch64, darwin-x86_64, darwin-aarch64, windows-x86_64) | ||
| cargo build -p convex-ffi --release | ||
| mkdir -p bindings/java/src/main/resources/native/linux-x86_64 | ||
| cp target/release/libconvex_ffi.so \ | ||
| bindings/java/src/main/resources/native/linux-x86_64/ | ||
|
|
||
| # 2. Build + test the jar | ||
| mvn -f bindings/java/pom.xml verify | ||
| ``` | ||
|
|
||
| `NativeLoader` extracts the cdylib matching the running OS/arch from the jar at | ||
| startup, so one published artifact carries every platform. | ||
|
|
||
| ## Usage | ||
|
|
||
| ```java | ||
| import com.convex.*; | ||
| import java.math.BigDecimal; | ||
| import java.time.LocalDate; | ||
| import java.util.List; | ||
|
|
||
| var settle = LocalDate.of(2025, 4, 15); | ||
|
|
||
| try (Bond bond = FixedRateBond.builder() | ||
| .cusip("037833100") | ||
| .couponRate(new BigDecimal("0.05")) | ||
| .frequency(Frequency.SEMI_ANNUAL) | ||
| .issue(LocalDate.of(2025, 1, 15)) | ||
| .maturity(LocalDate.of(2035, 1, 15)) | ||
| .dayCount(DayCount.THIRTY_360_US) | ||
| .currency(Currency.USD) | ||
| .build(); | ||
| YieldCurve curve = YieldCurve.discrete() | ||
| .referenceDate(LocalDate.of(2025, 1, 15)) | ||
| .point(2.0, 0.040).point(5.0, 0.042).point(10.0, 0.045) | ||
| .interpolation(Interpolation.MONOTONE_CONVEX) | ||
| .build()) { | ||
|
|
||
| // Pricing & risk | ||
| var price = ConvexAnalytics.price(bond, settle, Mark.cleanPrice(new BigDecimal("99.5"))); | ||
| var risk = ConvexAnalytics.risk(bond, settle, Mark.cleanPrice(new BigDecimal("99.5")), curve, 2, 5, 10, 30); | ||
| System.out.printf("YTM %.4f, DV01 %.2f%n", price.ytmDecimal(), risk.dv01()); | ||
|
|
||
| // Hedge advisor | ||
| RiskProfile pos = HedgeAdvisor.positionRisk() | ||
| .bond(bond).settlement(settle).mark(Mark.cleanPrice(new BigDecimal("99.5"))) | ||
| .notionalFace(new BigDecimal("10000000")).curve(curve).curveId("USD.SOFR") | ||
| .keyRateTenors(2, 5, 10, 30) | ||
| .compute(); | ||
|
|
||
| HedgeProposal futures = HedgeAdvisor.durationFutures(pos, curve, settle); | ||
| HedgeProposal swap = HedgeAdvisor.swap(pos, curve, settle, Constraints.none()); | ||
| ComparisonReport report = HedgeAdvisor.compare(pos, List.of(futures, swap), Constraints.none(), true); | ||
| System.out.println(report.narrative().orElse("")); | ||
| } | ||
| ``` | ||
|
|
||
| ## Thread-safety | ||
|
|
||
| The native registry and analytics are safe for concurrent use, and the FFM | ||
| method handles are immutable, so the API may be called from multiple threads. | ||
| (The `convex-ffi` registry clones objects out under the lock before running | ||
| analytics, so concurrent calls never deadlock.) | ||
|
|
||
| ## Status / not yet wired | ||
|
|
||
| The native FFI does not yet expose every Rust capability. Currently missing | ||
| (same DTO + dispatch pattern when added — see | ||
| `crates/convex-analytics/src/dto.rs`): | ||
|
|
||
| - Convention/calendar introspection (`get_convention_options` analog) | ||
| - YAS settlement invoices, VaR, Hull-White calibration, piecewise bootstrap | ||
| - FRN projection / cap-floor analytics | ||
|
|
||
| Packaging note: the binding ships as an automatic module (works on the | ||
| classpath). A `module-info.java` is intentionally omitted for now; named-module | ||
| consumers should grant `--enable-native-access` to the unnamed module. | ||
|
|
||
| ## Releasing to Maven Central | ||
|
|
||
| Publishing is driven by the `release` Maven profile (sources + javadoc jars, | ||
| GPG signing, and the Sonatype Central Portal plugin) and the | ||
| `.github/workflows/java-release.yml` workflow, triggered by a `java-v*` tag. | ||
|
|
||
| The workflow builds the cdylib for every platform classifier in a matrix, | ||
| stages them all under `src/main/resources/native/`, then runs | ||
| `mvn -Prelease deploy` so the single jar carries every platform. | ||
|
|
||
| Required repository secrets: | ||
|
|
||
| | Secret | Purpose | | ||
| |---|---| | ||
| | `MAVEN_CENTRAL_USERNAME` / `MAVEN_CENTRAL_PASSWORD` | Central Portal user token | | ||
| | `MAVEN_GPG_PRIVATE_KEY` | ASCII-armored signing key | | ||
| | `MAVEN_GPG_PASSPHRASE` | passphrase for that key | | ||
|
|
||
| To cut a release: `git tag java-v0.13.0 && git push origin java-v0.13.0`. | ||
Uh oh!
There was an error while loading. Please reload this page.