Documentation
¶
Overview ¶
Package quant provides quantitative-finance tools for evaluating trading strategies and portfolios: performance metrics (Sharpe ratio, maximum drawdown, annualized return), backtest-overfitting diagnostics (CSCV PBO, Deflated Sharpe Ratio), and time-series walk-forward out-of-sample validation.
Unlike the finance package (which uses high-precision decimals for TVM, NPV/IRR, and bond pricing), quant operates on plain float64 series — the industry convention for return/equity analytics, where statistical noise dwarfs floating-point error. Inputs are []float64 (a return series or an equity/NAV curve); exported functions follow an error-first convention, returning an error for invalid input rather than logging or panicking.
Index ¶
- func AnnualizedReturn(equity insyra.IDataList, days int) (float64, error)
- func DeflatedSharpeRatio(observedSR float64, n int, skew, kurt float64, trialSharpes insyra.IDataList) (float64, error)
- func ExpectedMaxSharpe(sharpeVariance float64, nTrials int) (float64, error)
- func MaxDrawdown(equity insyra.IDataList) (float64, error)
- func PBO(perf insyra.IDataTable, nSplits int) (float64, error)
- func ProbabilisticSharpeRatio(observedSR, benchmarkSR float64, n int, skew, kurt float64) (float64, error)
- func SharpeRatio(returns insyra.IDataList, riskFreeRate, periodsPerYear float64) (float64, error)
- type WalkForwardConfig
- type WalkForwardFold
- type WalkForwardResult
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AnnualizedReturn ¶
AnnualizedReturn returns the annualized (CAGR-style) return implied by an equity curve spanning days calendar days:
(equity[last] / equity[0]) ^ (365 / days) - 1
equity is a value/NAV curve (only its first and last points matter); days is the calendar-day span the curve covers.
Returns an error if fewer than 2 points are given, days is non-positive, or the first/last equity value is non-positive (the growth ratio would be undefined).
func DeflatedSharpeRatio ¶
func DeflatedSharpeRatio(observedSR float64, n int, skew, kurt float64, trialSharpes insyra.IDataList) (float64, error)
DeflatedSharpeRatio returns the Deflated Sharpe Ratio (DSR): the PSR of the selected strategy measured against the deflation benchmark SR₀ derived from the whole set of trial Sharpe ratios. It corrects the observed Sharpe for selection bias from multiple testing, non-normality, and sample length in one number — DSR ≈ 1 means the result survives deflation; DSR near 0 means it is likely a false discovery.
observedSR is the selected strategy's per-period (non-annualized) Sharpe (typically the maximum of trialSharpes). n is its number of return observations; skew and kurt are that strategy's skewness and non-excess kurtosis. trialSharpes holds the per-period Sharpe ratios of ALL trials considered during the search; their count and (population) variance feed SR₀.
Returns an error if trialSharpes is empty or any downstream computation fails.
func ExpectedMaxSharpe ¶
ExpectedMaxSharpe returns SR₀, the expected maximum (per-period, non-annualized) Sharpe ratio obtained by chance after nTrials independent backtests whose Sharpe ratios have variance sharpeVariance. This is the deflation benchmark used by DeflatedSharpeRatio:
SR₀ = √V · [ (1-γ)·Z⁻¹(1 - 1/N) + γ·Z⁻¹(1 - 1/(N·e)) ]
where V = sharpeVariance, N = nTrials, γ is the Euler-Mascheroni constant, e is Euler's number, and Z⁻¹ is the standard-normal quantile.
With nTrials ≤ 1 there is no selection bias, so SR₀ = 0. Returns an error if sharpeVariance is negative.
func MaxDrawdown ¶
MaxDrawdown returns the maximum drawdown of an equity (cumulative value / NAV) curve as a non-negative fraction: 0.2 means the curve fell 20% below a prior running peak at its worst point. A monotonically non-decreasing curve has a drawdown of 0.
equity should be a positive value series; points where the running peak is non-positive are skipped (drawdown is undefined there).
Returns an error if equity is empty.
func PBO ¶
func PBO(perf insyra.IDataTable, nSplits int) (float64, error)
PBO estimates the Probability of Backtest Overfitting via Combinatorially Symmetric Cross-Validation (CSCV), per Bailey, Borwein, López de Prado & Zhu.
perf is a T×N performance DataTable: column j is candidate strategy j and row i is period i, so perf[i][j] is strategy j's period-i return (T rows, N ≥ 2 columns). nSplits (S) is the number of equal, contiguous time blocks the rows are cut into; it must be even. CSCV enumerates every way to split the S blocks into an in-sample half (IS) and an out-of-sample half (OOS). For each split it picks the IS-best strategy (by Sharpe ratio) and records its OOS rank; PBO is the fraction of splits where that strategy's OOS performance lands in the bottom half (logit ω ≤ 0). A high PBO means in-sample winners tend to be out-of-sample losers — the signature of overfitting.
If T is not a multiple of nSplits, the trailing T mod nSplits rows are dropped (each block has T/nSplits rows). Per-block Sharpe uses the sample standard deviation; a zero-volatility series contributes a Sharpe of 0.
Returns an error for an empty matrix, columns of unequal length, fewer than 2 strategies, an odd or non-positive nSplits, or nSplits greater than T.
func ProbabilisticSharpeRatio ¶
func ProbabilisticSharpeRatio(observedSR, benchmarkSR float64, n int, skew, kurt float64) (float64, error)
ProbabilisticSharpeRatio returns the Probabilistic Sharpe Ratio (PSR) of Bailey & López de Prado: the probability that the true Sharpe ratio exceeds a benchmark, given the estimate's standard error under non-normal returns.
PSR = Φ[ (SR̂ - SR*)·√(n-1) / √(1 - γ₃·SR̂ + ((γ₄-1)/4)·SR̂²) ]
observedSR (SR̂) and benchmarkSR (SR*) are per-period, NON-annualized Sharpe ratios. n is the number of return observations. skew (γ₃) and kurt (γ₄) are the skewness and the NON-excess kurtosis of the returns (a normal distribution has skew 0 and kurt 3).
Returns an error if n < 2 or the variance term in the denominator is non-positive (which can happen under extreme skew/kurtosis combinations).
func SharpeRatio ¶
SharpeRatio returns the annualized Sharpe ratio of a periodic return series:
Sharpe = mean(returns - riskFreeRate) / stddev(returns) · √periodsPerYear
returns are per-period simple returns (e.g. daily returns as 0.012 for +1.2%). riskFreeRate is the risk-free rate expressed in the SAME period as returns (pass 0 for an excess-return series or to ignore it). periodsPerYear is the annualization factor — 252 for daily Taiwan-stock returns, 12 for monthly, 52 for weekly. Pass periodsPerYear = 1 to obtain the per-period (non-annualized) Sharpe used by the overfitting diagnostics.
The standard deviation is the sample (n-1) standard deviation, matching the common convention used by most backtesting tools and by gonum.
Returns an error if fewer than 2 returns are given, periodsPerYear is non-positive, or the return series has zero volatility (Sharpe undefined).
Types ¶
type WalkForwardConfig ¶
type WalkForwardConfig struct {
// TrainSize is the number of in-sample periods used to pick parameters
// for each fold.
TrainSize int
// TestSize is the number of out-of-sample periods evaluated per fold.
// Folds advance by TestSize, so out-of-sample windows never overlap.
TestSize int
// Anchored controls the training window: false (default) uses a fixed
// TrainSize rolling window; true uses an expanding window anchored at
// period 0 (training always starts at 0 and grows each fold).
Anchored bool
}
WalkForwardConfig configures the sliding train/test windows of a walk-forward analysis.
type WalkForwardFold ¶
type WalkForwardFold struct {
TrainStart int
TrainEnd int
TestStart int
TestEnd int
// OOSReturns is the per-period out-of-sample return series returned by
// evaluate for this fold.
OOSReturns []float64
}
WalkForwardFold records the index ranges and out-of-sample result of one fold. All ranges are half-open [Start, End).
type WalkForwardResult ¶
type WalkForwardResult struct {
Folds []WalkForwardFold
// OOSReturns concatenates the out-of-sample returns of every fold in
// chronological order — the stitched out-of-sample track record.
OOSReturns []float64
// Equity is the compounded out-of-sample equity curve starting at 1.0,
// so len(Equity) == len(OOSReturns)+1.
Equity []float64
}
WalkForwardResult aggregates every fold of a walk-forward run.
func WalkForward ¶
func WalkForward[P any]( n int, cfg WalkForwardConfig, optimize func(trainStart, trainEnd int) P, evaluate func(p P, testStart, testEnd int) []float64, ) (*WalkForwardResult, error)
WalkForward runs a time-series walk-forward (out-of-sample) validation over n periods. For each fold it calls optimize on the in-sample window to pick parameters of type P, then evaluate on the out-of-sample window to obtain that fold's per-period returns; the out-of-sample returns are stitched together and compounded into a single equity curve. This is the standard guard against optimizing and evaluating on the same data (Pardo).
Both callbacks receive half-open [start, end) index ranges into the caller's own data (the caller closes over the actual series), so any data layout works. evaluate should return one return per out-of-sample period (typically TestEnd-TestStart values).
Windows advance by TestSize starting at TrainSize. With a rolling window fold k is train [TestStart-TrainSize, TestStart), test [TestStart, TestStart+TestSize); Anchored fixes the training start at 0 (expanding). If n-TrainSize is not a multiple of TestSize, the final out-of-sample window is shorter than TestSize rather than dropped, so all data is used.
Returns an error if n, TrainSize, or TestSize is non-positive, TrainSize leaves no room for testing (TrainSize >= n), or either callback is nil.
func (*WalkForwardResult) AnnualizedReturn ¶
func (r *WalkForwardResult) AnnualizedReturn(days int) (float64, error)
AnnualizedReturn returns the annualized (CAGR-style) return of the out-of-sample equity curve over the given calendar-day span. See AnnualizedReturn for details.
func (*WalkForwardResult) MaxDrawdown ¶
func (r *WalkForwardResult) MaxDrawdown() (float64, error)
MaxDrawdown returns the maximum drawdown of the out-of-sample equity curve. See MaxDrawdown for details.