Metadata-Version: 2.4
Name: synthera
Version: 0.2.11rc2
Summary: Python client for Synthera AI API
Author: Synthera AI
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: License :: Other/Proprietary License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.9
Requires-Dist: httpx>=0.28.1
Requires-Dist: matplotlib-stubs>=0.3.11
Requires-Dist: matplotlib>=3.8
Requires-Dist: numpy>=1.26.4
Requires-Dist: pandas-stubs>=2.2.2.240807
Requires-Dist: pandas>=2.1.2
Requires-Dist: pyarrow>=15.0.2
Requires-Dist: pydantic>=2.10.6
Requires-Dist: tenacity>=9.0.0
Description-Content-Type: text/markdown

# Synthera AI SDK

A Python SDK for accessing the Synthera AI API.

## Installation

The package is compatible with Python `3.9`-`3.13`.

```bash
pip install synthera
```

## Changelog

### 0.2.11

Added
- Distribution yield views in `views.yields` (`type: distribution`) to condition simulations on distribution-level shift targets (point or range) instead of filter-only constraints.

Changed
- **Macro views** (`views.macro`) now match the API: a map from **macro factor key** to a payload that is either (a) **per-curve** for `GDP` / `CPI` / `UNRATE` — `curve_label -> list of entries`, or (b) **single-series** for other factors — a JSON **array** of entries or a **single** entry object. Use only factor keys supported by the model (see `macro_factors` in model metadata).
- `ModelMetadata` and simulate response metadata include optional **`macro_factors`**: the list of macro factor keys that model accepts (from model tags). `None` means not declared; `[]` means the model declares no macro factors.
- GET endpoints now use retry with exponential backoff for transient failures, matching POST retry behavior.
- Quantile plotting methods now detect distribution views and use view weights when available.

### 0.2.10

Added
- `macro` view support in `views`: condition simulations on macro paths using relative or absolute targets per curve and date (legacy API shape with fixed `GDP` / `CPI` / `UNRATE` fields; superseded in 0.2.11)

Changed
- Increased default `Client` timeout to 90 seconds to accommodate the additional latency introduced by view-conditioned simulations.
- Requests that time out are no longer retried. Previously, a timeout would trigger up to 5 retry attempts (with a 2-second wait between each). Timeouts now immediately raise a `SyntheraClientError`.
- Renamed `country` to `curve` in `SimulateResults` methods: `get_curve_yc_samples`, `get_curve_sample_at_t`, `plot_curve_sample_at_time`, `plot_curve_all_samples_at_time`, `plot_curve_sample_yield_curve_over_time`.

### 0.2.9

Added
- Request `views` parameter: optional yield-view constraints (point, range, min, max per curve/maturity) to filter scenarios by change in bps at end of horizon
- Response metadata `no_samples_returned` and `views_applied` when views are used
- SSL options on `SyntheraClient`: `verify_ssl` (default `True`) and `ca_bundle` (optional path). Environment variables `SYNTHERA_API_VERIFY_SSL` and `SYNTHERA_API_CA_BUNDLE` to override. Supports corporate proxies and custom CA bundles.

Changed
- Model and simulate response metadata: `tenors` renamed to `maturities`.

### 0.2.8

Changed
- Response data format: compressed feather & base64

### 0.2.7

Added
- Request `fallback_on_missing_date` parameter
- Request `conditional_vol_factor` parameter
- `version` to Client and request headers

Changed
- Request `no_of_days` parameter to `no_days`
- Request `no_of_samples` parameter to `no_samples`
- Model metadata `simulation_steps` to `max_simulate_days`
- Model metadata `conditional_steps` to `conditional_days`
- FixedIncome `model_labels` to `get_model_labels`
- FixedIncome `model_metadata` to `get_model_metadata`
- FixedIncome `simulation_past_date` to `simulate`
- FixedIncome `SimulationPastDateRestuls` to `SimulateResults`

## API Key

You are required to use an API key to access the Synthera AI API.

For ease of use, set as an environment variable: `SYNTHERA_API_KEY`:

```
export SYNTHERA_API_KEY=<api_key>
```

Or you can pass directly to the client.

## SSL / TLS

SSL verification is configurable for environments (e.g. corporate proxies or custom CA bundles).

**Constructor options:**

- `verify_ssl` — Enable or disable server certificate verification (default: `True`).
- `ca_bundle` — Path to a CA bundle file for verification (optional). When set, this path is used instead of the default system trust store.

**Environment variables (overridden by explicit constructor args):**

- `SYNTHERA_API_VERIFY_SSL` — Set to `false`, `0`, or `no` (case-insensitive) to disable verification; any other value enables it.
- `SYNTHERA_API_CA_BUNDLE` — Path to your CA bundle file (e.g. `/path/to/ca-bundle.pem`).

**Example:**

```python
# Default: verify with system CA bundle
client = SyntheraClient(api_key="<api_key>")

# Disable verification (e.g. for testing)
client = SyntheraClient(api_key="<api_key>", verify_ssl=False)

# Use a custom CA bundle (e.g. for corporate proxies)
client = SyntheraClient(
    api_key="<api_key>",
    verify_ssl=True,
    ca_bundle="/path/to/your/ca-bundle.pem",
)
```

Or via environment:

```
export SYNTHERA_API_VERIFY_SSL=false
export SYNTHERA_API_CA_BUNDLE=/path/to/ca-bundle.pem
```

## Getting Started

**Import the Synthera client**:
```python
from synthera import SyntheraClient
```

**Create a client**:
```python
client = SyntheraClient()
```

**Show client version**
```python
client.version
# Output: 0.2.8
```

**Check the connection works**:
```python
client.healthy()
# Output: True
```
Note: the health endpoint is open; the API key is not verified.

**For more advanced connection options, pass arguments to the client**:
```python
SyntheraClient(
    api_key="<api_key>",
    host="<host>",
    port=<port>,
    timeout_secs=<timeout>, # default: 90 seconds
    verify_ssl=True,   # default; set False to disable SSL verification
    ca_bundle=None,    # optional path to CA bundle file
)
# Output: <SyntheraClient>
```

## Fixed Income

To run Fixed Income Yield Curve simulation.

### View Available Models

**View the model labels**:

```python
client.fixed_income.get_model_labels()
# Example output: ['YieldGAN-augur-v0.3-Q42019', 'YieldGAN-augur-v0.4-Q42020']
```

| Name | Description |
|------|-------------|
| YieldGAN | Model name |
| augur | Dataset used for training & inference |
| v0.4-Q42020 | Version (including training end period) |

### View Model Metadata

**View the metadata for a model label**:

```python
client.fixed_income.get_model_metadata(model_label="YieldGAN-augur-v0.4-Q42020")
# Example output: ModelMetadata(..., macro_factors=['GDP', 'CPI', 'UNRATE'], ...)
```

| Name | Description |
|------|-------------|
| model_label | Unique identifier for the model |
| dataset | Dataset model is trained on (e.g. Augur Labs) |
| universe | Data universe used for training (e.g., g3_par_curves) |
| curve_labels | List of yield curve identifiers included in the model |
| start_date_training | Start date of the training data period (YYYY-MM-DD) (inclusive) |
| end_date_training | End date of the training data period (YYYY-MM-DD) (exclusive) |
| max_simulate_days | Maximum number of forward simulation days |
| conditional_days | Number of conditional simulation days |
| maturities | List of maturities (in years) for which yields are generated |
| macro_factors | Optional list of macro factor keys this model supports (`None` if not declared; `[]` if none) |


### Simulation

**Prepare input parameters.**

| Parameter | Type | Description | Values                                                    |
|-----------|------|-------------|-----------------------------------------------------------|
| model_label | string | Version of the model to use | Valid model label: "YieldGAN-\<dataset\>-v\<version\>" |
| curve_labels | list[string] | List of yield curves labels to simulate, using ISO 3166-1 alpha-3 country codes | List of curve names (e.g., ["USA", "GBR", "DEU"])         |
| no_days | integer | Number of days to simulate forward from the reference date | > 0 (e.g., 3, 30, 60, 120)                                |
| no_samples | integer | Number of paths to simulate | > 0 (e.g., 100, 1024, 5000)                              |
| reference_date | string | Reference date for the generation (in the past) | YYYY-MM-DD format                                         |
| fallback_on_missing_date | boolean | Whether to fallback to an available date in the dataset | true or false                                        |
| conditional_vol_factor | float | Conditional volatility factor | e.g. 0.1, 1.0, 10.0, 100                                         |
| views | object (optional) | Optional constraints for scenarios | `yields`: dict of curve_maturity (e.g. `"USA10.0Y"`) to yield view (point / range / min / max); `macro`: map of macro factor key → payload (see below; only keys in the model’s `macro_factors` metadata) |

For example:

```python
params = {
    "model_label": "YieldGAN-augur-v0.4-Q42020",
    "curve_labels": ["USA", "GBR"],
    "no_days": 15,
    "no_samples": 100,
    "reference_date": "2010-01-01",
    "fallback_on_missing_date": True,
    "conditional_vol_factor": 1.0,
}
```

With optional **views** to filter scenarios by yield change (in basis points) at the end of the simulation horizon:

```python
params = {
    "model_label": "YieldGAN-augur-v0.4-Q42020",
    "curve_labels": ["USA", "GBR", "DEU"],
    "no_days": 5,
    "no_samples": 1000,
    "reference_date": "2010-01-01",
    "fallback_on_missing_date": True,
    "views": {
        "yields": {
            "USA10.0Y": {"type": "range", "min_bp": 20, "max_bp": 25},
            "DEU2.0Y": {"type": "point", "value_bp": 10, "tolerance_bp": 2},
        },
        "macro": None,
    },
}
# When views are applied, result.ndarray.shape[0] may be less than no_samples;
# use results.metadata["no_samples_returned"] for the actual count.
```

With optional **distribution yield views** to re-weight scenarios toward a terminal-horizon shift target (instead of filtering out scenarios):

```python
params = {
    "model_label": "YieldGAN-augur-v0.4-Q42020",
    "curve_labels": ["USA", "GBR"],
    "no_days": 5,
    "no_samples": 1000,
    "reference_date": "2010-01-01",
    "views": {
        "yields": {
            # Shift mode: center the distribution around +15bp at horizon
            "USA10.0Y": {"type": "distribution", "view": "shift", "shift_bp": 15.0},
            # Range mode: target +5bp with +/-2bp tolerance
            "GBR2.0Y": {
                "type": "distribution",
                "view": "range",
                "shift_bp": 5.0,
                "tolerance_bp": 2.0,
            },
        }
    },
}
```

Distribution yield view fields:
- `type`: must be `distribution`
- `view`: `shift` or `range`
- `shift_bp`: target shift in bps at end horizon
- `tolerance_bp`: required for `range`, omitted for `shift`

When distribution views are applied, `results.metadata` can include `scenario_probabilities` and weighted quantile methods use these probabilities automatically.

With optional **macro views**, first read which factor keys the model supports, then send only those keys under `views["macro"]`:

```python
meta = client.fixed_income.get_model_metadata(model_label="YieldGAN-augur-v0.4-Q42020")
# meta.macro_factors e.g. ["GDP", "CPI", "UNRATE"] — use only these keys in the macro view.

params = {
    "model_label": "YieldGAN-augur-v0.4-Q42020",
    "curve_labels": ["USA", "GBR", "DEU"],
    "no_days": 5,
    "no_samples": 1000,
    "reference_date": "2010-01-01",
    "views": {
        "macro": {
            "GDP": {
                # Relative: USA GDP 10 bp above its conditional value on 2010-01-05
                "USA": [{"date": "2010-01-05", "type": "delta_bp", "value": 10}],
                # Absolute: DEU GDP pinned to a specific level on 2010-01-10
                "DEU": [{"date": "2010-01-10", "type": "target_amount", "value": 3_000_000_000_000.0}],
            },
            "CPI": {
                # Relative: USA CPI index +1.5 points above conditional on 2010-01-05
                "USA": [{"date": "2010-01-05", "type": "delta_ip", "value": 1.5}],
                # Absolute: GBR CPI pinned to a specific index level
                "GBR": [{"date": "2010-01-05", "type": "target_ip", "value": 115.0}],
            },
            "UNRATE": {
                # Relative: USA unemployment rate -0.5 pp below conditional
                "USA": [{"date": "2010-01-05", "type": "delta_pp", "value": -0.5}],
                # Absolute: DEU unemployment rate pinned to 5.0%
                "DEU": [{"date": "2010-01-05", "type": "target_rate", "value": 5.0}],
            },
        }
    },
}
```

For **`GDP`**, **`CPI`**, and **`UNRATE`**, each factor’s value is an object mapping **curve label** → list of dated entries. For **other** macro factors (for example `UK_REAL_GDP`), the value is a **single series**: either a JSON array of entries or one entry object with `date`, `type`, and `value`. Multiple dates can be specified in the list form. Unspecified simulation dates carry forward the last known conditional value. The API rejects factor keys that are not in the model’s allowlist.

You can mix both shapes in one `views["macro"]` object:

```python
"views": {
    "macro": {
        "GDP": {
            "USA": [{"date": "2010-01-05", "type": "delta_bp", "value": 10}],
        },
        # Single-series factor: one entry object (or use a list of entries)
        "UK_REAL_GDP": {"date": "2010-01-05", "type": "delta_bp", "value": 10},
    },
}
```

Example — single-series factor only:

```python
"views": {
    "macro": {
        "UK_REAL_GDP": {"date": "2010-01-05", "type": "delta_bp", "value": 10},
    },
}
```

| Semantic (typical factors) | Entry type | Meaning |
|--------|-----------|----------|
| Amount (e.g. GDP) | `delta_bp` | Relative: `target = cond × (1 + value / 100)`. Value in basis points. |
| Amount | `target_amount` | Absolute: target level in local currency. |
| Index (e.g. CPI) | `delta_ip` | Relative: `target = cond + value`. Value in index points. |
| Index | `target_ip` | Absolute: target index level. |
| Rate (e.g. unemployment) | `delta_pp` | Relative: `target = cond + value`. Value in percentage points. |
| Rate | `target_rate` | Absolute: target rate in % (e.g. `5.0` means 5.0%). |

**Run simulation directly**:

```python
results = client.fixed_income.simulate(params=params)
# Output: SimulateResults
```

**View yield curves labels**:
```python
results.names
# Example output: ['USA', 'GBR']
```

**View yield curve dataframe column names**:
```python
results.column_names
# Output: ['IDX', 'SAMPLE', 'YC_0', 'YC_1', ...]
```

**View a specific yield curve dataframe, e.g. for `USA`**:
```python
results.dataframes["USA"]
# Output: pandas.DataFrame
```

**View all yield curves in a single `numpy` ndarray (order is same as `names`)**:
```python
results.ndarray
# Output: ndarray of shape (samples, countries, days, columns)
```

**View request metadata**:
```python
results.metadata
# Output: dict
```
When `views` are applied, metadata includes `no_samples_returned` (number of scenarios after filtering) and `views_applied`; `results.ndarray.shape[0]` matches `no_samples_returned`.


**Get scenario probabilities (when distribution views are used):**
```python
scenario_probabilities = results.metadata.get("scenario_probabilities")
# Output: list[float] | None
```

For safety, validate they align with returned samples before using them:

```python
probs = results.metadata.get("scenario_probabilities")
if isinstance(probs, list) and len(probs) == results.ndarray.shape[0]:
    print("Using weighted probabilities.")
else:
    print("No valid scenario probabilities available; use equal weights.")
```

## Simulate Results

The `SimulateResults` object provides several utility and plotting methods for analyzing and visualizing outputs.

### Utility Methods

**Get dates:**
```python
results.get_dates()
# Output: [Timestamp('2010-01-01 00:00:00'), ...]
```

**Get yield curve column indices:**
```python
results.get_yc_indices()
# Output: [2, 3, 4, ...]
```

**Get all yield curve samples (as ndarray):**
```python
results.get_yc_samples()
# Output: ndarray of shape (samples, countries, days, maturities)
```

**Get all samples for a specific curve:**
```python
results.get_curve_yc_samples("USA")
# Output: ndarray of shape (samples, days, maturities)
```

**Get a single sample for a curve:**
```python
results.get_yc_sample("USA", sample_num=0)
# Output: ndarray of shape (days, maturities)
```

**Get a single sample at a specific time index:**
```python
results.get_curve_sample_at_t("USA", time_idx=1, sample_num=0)
# Output: ndarray of shape (maturities,)
```

**Get quantiles by curve (uniform vs weighted):**
```python
uniform_quantiles = results.quantiles_uniform_by_curve
weighted_quantiles = results.quantiles_weighted_by_curve
# Output: dict[str, list[list[float]]] for each (curve -> [maturity][quantile])
```

- `quantiles_uniform_by_curve`: baseline equal-weight quantiles from returned scenarios.
- `quantiles_weighted_by_curve`: distribution-weighted quantiles when a distribution yield view is applied; may be empty/`None` for non-distribution runs.


### Plotting Methods

All plotting methods return a `matplotlib` `Figure`. Set `show_plot=True` to display immediately (non-Jupyter environments).

**Plot a single sample for a curve at a specific time:**
```python
results.plot_curve_sample_at_time("USA", time_idx=1, sample_num=0, show_plot=True)
# Output: matplotlib.figure.Figure
```

**Plot all samples for a curve at a specific time:**
```python
results.plot_curve_all_samples_at_time("USA", time_idx=1, show_plot=True)
# Output: matplotlib.figure.Figure
```

**Plot a single sample's yield curve evolution over time (3D):**
```python
results.plot_curve_sample_yield_curve_over_time("USA", sample_num=0, show_plot=True)
# Output: matplotlib.figure.Figure
```

**Plot distribution-aware quantiles across maturities:**
```python
results.plot_quantiles_curve(["USA", "GBR"], show_plot=True)
# Output: matplotlib.figure.Figure
```

`plot_quantiles_curve` behavior:
- Without a distribution view, plots original (equal-weight) quantiles.
- With a distribution view in `views_applied`, overlays weighted quantiles using scenario weights.

**Plot terminal yield distributions for selected points:**
```python
results.plot_quantiles_yield(["USA10.0Y", "GBR2.0Y"], show_plot=True)
# Output: matplotlib.figure.Figure
```

`plot_quantiles_yield` behavior:
- Always plots the equal-weight baseline density.
- When `results.metadata["scenario_probabilities"]` matches returned samples, overlays the weighted density.