"""Bundled synthetic sample data -- ``fcf.samples.*``.
The single surface for the packaged demo data. Two uses, deliberately distinct:
* **load** -- get an assembled object to play with or feed straight to a
measurement: :func:`basis`, :func:`model_points`, :func:`calculation_methods`,
:func:`inforce_state`, :func:`return_scenarios` (toy fund returns for the VFA
time-value-of-guarantees example) and :func:`rate_scenarios` (toy discount
rates for the stochastic GMM valuation).
* **export** -- write a starter set of input *template files* to a directory
(edit them, then read back with ``fcf.read_model_points`` / ``fcf.read_basis``).
:func:`templates` lists the available template names.
``template="gmm"`` (default) is the protection portfolio; ``template="vfa"``
is the variable (account-value) contract set. The data is synthetic
(calibrated demo figures), never sourced from real portfolios.
"""
from pathlib import Path
import numpy as np
from fastcashflow import io as _io
#: Available sample templates -- see :func:`templates`.
_TEMPLATES = ("gmm", "vfa")
#: Fixed seed for :func:`scenarios` -- a reproducible toy path set, not a
#: calibration parameter.
_SCENARIO_SEED = 20260605
#: ``format=`` choices for :func:`export` -> the data-file extension. The
#: basis is always a multi-sheet ``.xlsx`` workbook regardless of this.
_FORMATS = {"csv": ".csv", "parquet": ".parquet",
"feather": ".feather", "xlsx": ".xlsx"}
[문서]
def templates() -> list[str]:
"""The available :func:`export` / load template names (``["gmm", "vfa"]``)."""
return list(_TEMPLATES)
[문서]
def basis(template: str = "gmm"):
"""Bundled sample basis. ``template="gmm"`` (default) returns the per-segment
``{(product, channel): Basis}`` dict; ``template="vfa"`` returns the
single variable-contract :class:`~fastcashflow.Basis`."""
if template == "vfa":
return _io.load_sample_vfa_basis()
if template == "gmm":
return _io.load_sample_basis()
raise ValueError(f"template must be one of {_TEMPLATES}, got {template!r}")
[문서]
def model_points(template: str = "gmm"):
"""Bundled sample model points (``template="gmm"`` default, ``"vfa"`` for the
variable account-value contracts)."""
if template == "vfa":
return _io.load_sample_vfa_model_points()
if template == "gmm":
return _io.load_sample_model_points()
raise ValueError(f"template must be one of {_TEMPLATES}, got {template!r}")
[문서]
def calculation_methods():
"""Bundled sample coverage-code -> calculation-method taxonomy."""
return _io.load_sample_calculation_methods()
[문서]
def inforce_state():
"""Bundled sample in-force state (elapsed_months / count / prior_csm / ...)."""
return _io.load_sample_inforce_state()
def return_scenarios(template: str = "vfa", n_scenarios: int = 1000):
"""Toy *fund-return* scenarios, shape ``(n_scenarios, n_time)``, for the
variable (VFA) time-value-of-guarantees example -- the ``return_scenarios``
input to :func:`~fastcashflow.vfa.measure`.
Generated in memory (no bundled file): deterministic, modest-volatility
monthly fund returns so the guarantee shows a believable time value (~3% of
account value on the sample). This is NOT a calibrated economic scenario
generator -- the engine *consumes* scenarios, it does not certify
valuation-grade ones. For a real valuation supply your own set via
:func:`~fastcashflow.read_scenarios`.
Each cell is a one-month *fund return* (not an interest-rate path -- that is
the separate ``scenarios`` input to :func:`~fastcashflow.gmm.stochastic`);
``n_time`` matches the bundled VFA sample's term and the fixed seed keeps the
output stable.
"""
if template != "vfa":
raise ValueError(
"return_scenarios are a variable-contract (VFA) input; template "
f"must be 'vfa', got {template!r}"
)
mp = model_points("vfa")
n_time = int(np.asarray(mp.term_months).max())
rng = np.random.default_rng(_SCENARIO_SEED)
central = (1.0 + 0.06) ** (1.0 / 12.0) - 1.0 # ~6% annual, monthly return
vol = 0.005 # modest monthly sd -- a toy
return central + vol * rng.standard_normal((n_scenarios, n_time))
def rate_scenarios(n_scenarios: int = 1000):
"""Toy *discount-rate* scenarios, shape ``(n_scenarios,)``, for the
stochastic GMM valuation -- the ``scenarios`` input to
:func:`~fastcashflow.gmm.stochastic`. The interest-rate counterpart to
:func:`return_scenarios` (which is fund returns).
Generated in memory: one flat annual discount rate per scenario, modest
dispersion around ~3%, deterministic (fixed seed). This is NOT a calibrated
economic scenario generator -- for a real valuation supply your own rate set
(Hull-White / Vasicek / regulator-prescribed) via
:func:`~fastcashflow.read_scenarios`. Flat (1-D) rates so the toy is
portfolio-agnostic; a real run can pass a 2-D ``(n_scenarios, n_time)`` curve
set instead.
"""
rng = np.random.default_rng(_SCENARIO_SEED + 1) # a stream distinct from returns
rates = 0.03 + 0.01 * rng.standard_normal(n_scenarios)
return np.maximum(rates, 1e-4) # keep the discount rate positive
def _export_tree(dest: Path, files: list[str]) -> str:
"""An ASCII tree of the files :func:`export` wrote, expanding the
``basis.xlsx`` workbook into its sheets -- a one-glance map of what landed
in the directory and which assumption sheets the basis carries."""
import openpyxl
lines = [f"{dest}/"]
for i, name in enumerate(files):
last_file = i == len(files) - 1
lines.append(f"{'└── ' if last_file else '├── '}{name}")
if name.endswith(".xlsx"):
wb = openpyxl.load_workbook(dest / name, read_only=True)
sheets = wb.sheetnames
wb.close()
pad = " " if last_file else "│ "
for j, sheet in enumerate(sheets):
last_sheet = j == len(sheets) - 1
lines.append(f"{pad}{'└── ' if last_sheet else '├── '}{sheet}")
return "\n".join(lines)
[문서]
def export(output_dir, template: str = "gmm", format: str = "csv",
*, quiet: bool = False) -> Path:
"""Write a starter set of input template files to ``output_dir``.
``template="gmm"`` writes ``basis.xlsx`` plus ``policies`` / ``coverages``
/ ``calculation_methods`` / ``inforce_state`` and the combined
``inforce_policies`` (the period-close one-file form); ``template="vfa"``
writes the variable-contract ``basis.xlsx`` and ``policies``. Edit them and
read back with :func:`~fastcashflow.read_model_points` /
:func:`~fastcashflow.read_basis`.
``format`` picks the data-file extension -- ``"csv"`` (default),
``"parquet"``, ``"feather"`` or ``"xlsx"``. The basis is always a
multi-sheet ``.xlsx`` workbook (it cannot be a flat table), so ``format``
applies only to the policies / coverages / state files. Use ``"parquet"``
for a portfolio large enough to stream with
:func:`~fastcashflow.gmm.measure_stream`.
Prints a tree of the files written -- expanding ``basis.xlsx`` into its
sheets -- so it is clear what landed where. Pass ``quiet=True`` to suppress
(e.g. in scripts). Returns the destination directory.
"""
if template not in _TEMPLATES:
raise ValueError(f"template must be one of {_TEMPLATES}, got {template!r}")
if format not in _FORMATS:
raise ValueError(
f"format must be one of {tuple(_FORMATS)}, got {format!r}")
ext = _FORMATS[format]
dest = Path(output_dir)
dest.mkdir(parents=True, exist_ok=True)
if template == "gmm":
_io._save_sample_basis(dest / "basis.xlsx")
_io._save_sample_policies(dest / f"policies{ext}")
_io._save_sample_coverages(dest / f"coverages{ext}")
_io._save_sample_calculation_methods(dest / f"calculation_methods{ext}")
_io._save_sample_inforce_state(dest / f"inforce_state{ext}")
_io._save_sample_inforce_policies(dest / f"inforce_policies{ext}")
files = ["basis.xlsx", f"policies{ext}", f"coverages{ext}",
f"calculation_methods{ext}", f"inforce_state{ext}",
f"inforce_policies{ext}"]
else: # vfa
_io._drop_sample_table("sample_vfa_basis.xlsx", dest / "basis.xlsx")
_io._drop_sample_table("sample_vfa_policies.csv", dest / f"policies{ext}")
files = ["basis.xlsx", f"policies{ext}"]
if not quiet:
print(f"fastcashflow sample export -- template={template!r}, "
f"{len(files)} files")
print(_export_tree(dest, files))
return dest
__all__ = ["templates", "basis", "model_points", "calculation_methods",
"inforce_state", "return_scenarios", "rate_scenarios", "export"]