Exportable Reports

AbaQuant’s reporting layer (abaquant.reports) turns option models, portfolio allocators, backtests, credit-proxy assessments, and integrated risk dashboards into Markdown, HTML, or PDF deliverables — using only the standard library plus pandas (already a dependency). The PDF exporter is a lightweight, pure-Python writer; it doesn’t need reportlab, weasyprint, wkhtmltopdf, LaTeX, or a browser runtime.

Sections:

  1. Build the underlying objects (option model, credit assessment, allocator, backtest, dashboard)

  2. Export reports in each format

Setup

import abaquant
print(f"AbaQuant version: {abaquant.__version__}")
AbaQuant version: 1.0.0rc1
from pathlib import Path

import numpy as np
import pandas as pd

from abaquant import RiskDashboard
from abaquant.credit import (
    BalanceSheetInputs,
    CashFlowInputs,
    CreditAnalysisInputs,
    CreditHistoricalSeries,
    IncomeStatementInputs,
    MarketEquityObservation,
    PriorPeriodInputs,
    calculate_credit_proxy_metrics,
)
from abaquant.derivatives.models import BlackScholesMertonModel
from abaquant.portfolio import PortfolioAllocator

REPORT_DIR = Path("generated_reports")
REPORT_DIR.mkdir(parents=True, exist_ok=True)

def sample_returns() -> pd.DataFrame:
    dates = pd.date_range("2025-01-02", periods=36, freq="B")
    trend = np.linspace(0.0, 1.0, len(dates))
    seasonal = np.sin(np.linspace(0.0, 4.0 * np.pi, len(dates)))
    prices = pd.DataFrame(
        {
            "ALPHA": 100.0 + 9.0 * trend + 1.8 * seasonal,
            "BETA": 82.0 + 6.0 * trend - 1.2 * seasonal,
            "GAMMA": 54.0 + 3.0 * trend + 0.7 * np.cos(np.linspace(0.0, 3.0 * np.pi, len(dates))),
        },
        index=dates,
    )
    return prices.pct_change().dropna()

1. Build the underlying objects

option_model = BlackScholesMertonModel(
    spot_price=100.0, strike_price=105.0, maturity_years=1.0,
    risk_free_rate=0.04, volatility=0.22, dividend_yield=0.01,
)

credit_assessment = calculate_credit_proxy_metrics(
    CreditAnalysisInputs(
        balance_sheet=BalanceSheetInputs(
            total_debt=120.0, total_equity=300.0, current_assets=250.0, inventory=40.0,
            current_liabilities=100.0, cash_and_cash_equivalents=50.0, total_assets=500.0,
            total_liabilities=200.0, retained_earnings=110.0, long_term_debt=80.0,
        ),
        income_statement=IncomeStatementInputs(
            revenue=450.0, gross_profit=200.0, ebit=75.0, ebitda=90.0,
            interest_expense=10.0, net_income=60.0,
        ),
        cash_flow_statement=CashFlowInputs(operating_cash_flow=70.0),
        prior_period=PriorPeriodInputs(
            total_assets=470.0, net_income=55.0, long_term_debt=90.0, current_assets=220.0,
            current_liabilities=105.0, shares_outstanding=100.0, gross_profit=180.0, revenue=420.0,
        ),
        market_equity=MarketEquityObservation(market_value_equity=650.0),
        historical_series=CreditHistoricalSeries(
            earnings_history=(42.0, 47.0, 53.0, 60.0),
            leverage_history=(0.60, 0.54, 0.48, 0.42),
        ),
        reporting_currency="USD",
        reporting_period="FY2025",
    )
)

allocator = PortfolioAllocator(sample_returns(), annual_risk_free_rate=0.02)
backtest = allocator.backtest(
    weights="inverse_volatility",
    rebalance="monthly",
    transaction_cost_bps=5.0,
    slippage_bps=1.0,
    benchmark="equal_weight",
    lookback=10,
)
dashboard = RiskDashboard(
    allocator,
    credit_assessments={"ALPHA": credit_assessment},
    weights=backtest.weights_history.iloc[-1],
    backtest=backtest,
)

2. Export reports in each format

Every high-level object exposes .report(), which returns an ExportableReport. Call .save(directory, stem, formats=...) to write one or more formats in a single call.

reports = {
    "option_markdown": option_model.report(option_type="call").save(
        REPORT_DIR, "option_report", formats=("markdown",)
    )["markdown"],
    "option_html": option_model.report(option_type="call").save(
        REPORT_DIR, "option_report", formats=("html",)
    )["html"],
    "option_pdf": option_model.report(option_type="call").save(
        REPORT_DIR, "option_report", formats=("pdf",)
    )["pdf"],
    "portfolio_html": allocator.report().save(
        REPORT_DIR, "portfolio_report", formats=("html",)
    )["html"],
    "backtest_markdown": backtest.report().save(
        REPORT_DIR, "backtest_report", formats=("markdown",)
    )["markdown"],
    "credit_markdown": credit_assessment.report().save(
        REPORT_DIR, "credit_proxy_report", formats=("markdown",)
    )["markdown"],
    "risk_dashboard_html": dashboard.report().save(
        REPORT_DIR, "risk_dashboard_report", formats=("html",)
    )["html"],
}
for name, path in reports.items():
    print(f"{name:24s}: {path}")
option_markdown         : generated_reports/option_report.md
option_html             : generated_reports/option_report.html
option_pdf              : generated_reports/option_report.pdf
portfolio_html          : generated_reports/portfolio_report.html
backtest_markdown       : generated_reports/backtest_report.md
credit_markdown         : generated_reports/credit_proxy_report.md
risk_dashboard_html     : generated_reports/risk_dashboard_report.html

Takeaway

Reports are ReportSection/ReportTable objects under the hood, so you can also build custom sections directly from abaquant.reports if the built-in .report() shape doesn’t match your needs. Numerical result objects remain the source of truth — reports are a downstream presentation layer.