Skip to main content

Options

The fourth argument of both functions is a qs_html_report_options STRUCT — a named type the extension creates at load time. Every field is nullable; keys you omit take their default:

FieldTypeDefaultDescription
titleVARCHAR'Strategy Tearsheet'Report title
strategy_titleVARCHARthe symbolStrategy display name; falls back to the symbol
benchmark_titleVARCHAR[]the benchmark symbolBenchmark display name (presentation only): a list, paired with benchmark by index. An entry that was not given (shorter list, NULL, empty string, or the whole key absent) falls back to the benchmark symbol that report uses; extra entries are ignored
benchmarkVARCHAR[]NULLWhich symbols are benchmarks (a list, whose order is the order of the reports); they are input only and get no report. Even a single benchmark is written ['SPX']
rfDOUBLE0.0Risk-free rate, annualized (0.04 = 4%), matching quantstats' rf convention
periods_per_yearUINTEGER252Periods per year; must be greater than 0
match_datesBOOLEANtrueWhether to align the start dates of strategy and benchmark
langVARCHARNULLTranslate the report into this language (see Translation); must have entries in the translation table. NULL = no translation at all, 'en' = keep the English text and add English notes. Spelled lang because language is a SQL keyword
output_dirVARCHARNULLAlso write every report into this local directory, with file names generated by the function (see Output and browser); a wasm build writes nothing
open_in_browserBOOLEANfalseOpen the reports in the system default browser; with no output_dir it writes a temporary file first (see Output and browser)

Apart from the two display names, the defaults come straight from quantstats-rs' HtmlReportOptions::default(); this extension does not invent a second set.

The two display names are the exception​

They are what makes dozens of reports out of one call usable: the default 'Strategy' is identical for every one of them, so the legend could not tell them apart and the temporary file names would share one useless prefix. They therefore fall back to a name that comes from the data (the symbol, the benchmark symbol), which keeps the report legend, the temporary file name and the returned strategy_title / benchmark_title in agreement. title is yours as well — it is not translated either, so Translation only touches the report's own fixed texts.

The options are a per-row column​

Each symbol uses the copy from its first row, so building the struct out of the symbol column is what gives every instrument its own title and display name — and the benchmark list its own display names, paired by index:

WITH prices AS (
SELECT * FROM read_csv('https://shijianjs.github.io/duckfn-quantstats/demo/prices.csv')
)
SELECT (r).symbol, (r).strategy_title, (r).benchmark, (r).benchmark_title,
length((r).html) AS html_bytes
FROM (
SELECT unnest(qs_html_reports_by_prices(
symbol, date, price,
{'benchmark': ['SPX'],
'benchmark_title': ['S&P 500'],
'title': symbol, -- per-row: the report's own title
'strategy_title': symbol, -- per-row: and the legend's label
'rf': 0.04,
'periods_per_year': 252}::qs_html_report_options)) AS r
FROM prices
)
ORDER BY (r).symbol;

strategy_title and benchmark_title come back in the row, so whatever the legend shows can be read without parsing the HTML — and they are the very names the generated file name uses, too.

One symbol's options have to agree row by row; benchmark additionally has to be the same list for every instrument in the call (same entries, same order — a disagreement is an error), otherwise "which one is the benchmark" would have no single answer. Error paths has a runnable example of each.

Which entries are validated​

benchmark decides who is a benchmark and who gets a report, so it is strict: an element may not be NULL, may not be an empty string and may not repeat inside one list. benchmark_title is presentation only, so it is lenient (missing entries fall back, extra entries are ignored). periods_per_year = 0 and output_dir = '' are reported as configuration errors as well — all of them before anything is rendered or any file-system call happens. The full list is on Error paths.

rf is annualized​

0.04 means 4% and is converted to a per-period rate inside the report; the crate has two conversions that differ slightly — Sharpe (and rolling Sharpe / Sortino) uses (1 + rf)^(1/periods_per_year) - 1, while PSR / Sortino in the metrics table use rf / periods_per_year. Both collapse to 0 when rf = 0 (the default).