Analytics
Rolling PCA factor models (residual/zscore/multi), rolling statistics, performance metrics (Sharpe/Sortino/Calmar/VaR/CVaR/omega/ulcer/information-ratio), technical indicators, and options payoff helpers.
Orcus.RollingPCA — Type
RollingPCA(window::Int, n_factors::Int, eigvecs::Matrix{Float64}, eigvals::Vector{Float64}, total_var::Float64, fitted::Bool)Rolling PCA factor model. Maintains top n_factors eigenvectors of the return covariance matrix, estimated over a sliding window of bars. Use rolling_pca to build an unfitted one. Fields set after fit!:
eigvecs— [N × K] matrix, columns are factor loadings (descending variance)eigvals— K eigenvalues (descending)fitted— false until firstfit!call
rolling_pca(20, 2)
# output
RollingPCA(window=20, n_factors=2, fitted=false)Orcus.explained_variance — Method
explained_variance(pca::RollingPCA)Fraction of total variance explained by each factor (sums to ≤ 1), length n_factors.
pca=rolling_pca(20, 2);
R=[1.0 2.0 3.0 4.0; 2.0 4.0 6.0 8.0; 1.0 1.5 1.2 1.8];
fit!(pca, R);
explained_variance(pca)
# output
2-element Vector{Float64}:
0.9942561416261809
0.00574385837381898Orcus.fit! — Method
fit!(pca::RollingPCA, R::Matrix{Float64})Fit PCA on return matrix R of shape [N × T], storing the top K eigenvectors (by variance explained) and their eigenvalues. Requires T ≥ 2 and N ≥ n_factors.
pca=rolling_pca(20, 2);
R=[1.0 2.0 3.0 4.0; 2.0 4.0 6.0 8.0; 1.0 1.5 1.2 1.8];
fit!(pca, R);
pca.fitted
# output
trueOrcus.plot_residual_corr — Method
plot_residual_corr(E::Matrix{Float64}, names::Vector{String})Print the residual correlation matrix as a formatted table, and return it.
pca=rolling_pca(20, 2);
R=[1.0 2.0 3.0 4.0; 2.0 4.0 6.0 8.0; 1.0 1.5 1.2 1.8];
fit!(pca, R);
F, E = project(pca, R);
C = redirect_stdout(devnull) do
plot_residual_corr(E, ["AAA","BBB","CCC"])
end;
round.(C; digits=3)
# output
3×3 Matrix{Float64}:
1.0 0.545 0.721
0.545 1.0 -0.182
0.721 -0.182 1.0Orcus.project — Method
project(pca::RollingPCA, R::Matrix{Float64})Batch projection of return matrix R [N × T], returning factor matrix F [K × T] and residual matrix E [N × T].
pca=rolling_pca(20, 2);
R=[1.0 2.0 3.0 4.0; 2.0 4.0 6.0 8.0; 1.0 1.5 1.2 1.8];
fit!(pca, R);
F, E = project(pca, R);
size(F), size(E)
# output
((2, 4), (3, 4))Orcus.project — Method
project(pca::RollingPCA, r::Vector{Float64})Project a single bar's return vector r [N] through the fitted PCA, returning (factors, residuals) — [K] factor returns and [N] idiosyncratic residual.
pca=rolling_pca(20, 2);
R=[1.0 2.0 3.0 4.0; 2.0 4.0 6.0 8.0; 1.0 1.5 1.2 1.8];
fit!(pca, R);
factors, residuals = project(pca, R[:,1]);
size(factors), size(residuals)
# output
((2,), (3,))Orcus.residual_corr — Method
residual_corr(E::Matrix{Float64})[N × N] Pearson correlation matrix of the residual rows. After good PCA factorization this should be close to the identity matrix — off-diagonal entries indicate remaining common structure.
pca=rolling_pca(20, 2);
R=[1.0 2.0 3.0 4.0; 2.0 4.0 6.0 8.0; 1.0 1.5 1.2 1.8];
fit!(pca, R);
F, E = project(pca, R);
round.(residual_corr(E); digits=3)
# output
3×3 Matrix{Float64}:
1.0 0.545 0.721
0.545 1.0 -0.182
0.721 -0.182 1.0Orcus.rolling_pca — Method
rolling_pca(window::Int, n_factors::Int)Build an unfitted RollingPCA — call fit! before using it.
rolling_pca(20, 2)
# output
RollingPCA(window=20, n_factors=2, fitted=false)Orcus.rolling_mean — Method
rolling_mean(v::Vector{Float64}, w::Int)Rolling mean of v with window w. First w-1 entries are NaN.
rolling_mean([1.0,2.0,3.0,4.0,5.0], 3)
# output
5-element Vector{Float64}:
NaN
NaN
2.0
3.0
4.0Orcus.rolling_std — Method
rolling_std(v::Vector{Float64}, w::Int)Rolling standard deviation of v with window w. First w-1 entries are NaN.
rolling_std([1.0,2.0,3.0,4.0,5.0], 3)
# output
5-element Vector{Float64}:
NaN
NaN
1.0
1.0
1.0Orcus.rolling_zscore — Method
rolling_zscore(v::Vector{Float64}, w::Int)Rolling z-score of v with window w: (v[i] - mean(v[i-w+1:i])) / std(...). First w-1 entries are NaN. Bars with zero local standard deviation are NaN.
rolling_zscore([1.0,2.0,3.0,4.0,5.0], 3)
# output
5-element Vector{Float64}:
NaN
NaN
1.0
1.0
1.0Orcus.rolling_zscore — Method
rolling_zscore(data::DataPoint)Single-window z-score of the last entry in data against the whole vector's mean/std — the IndicatorGenerator-compatible form: apply_indicator(IndicatorGenerator(rolling_zscore, 30), asset, "Close", "ZScore30").
rolling_zscore([1.0,2.0,3.0])
# output
1.0Orcus.annualized_return — Method
annualized_return(equity::Vector{<:Real}; periods_per_year::Int=252)Compound Annual Growth Rate (CAGR): the constant yearly return that would produce the same total growth as the equity curve.
round(annualized_return([100.0, 105.0, 102.0, 110.0, 108.0, 115.0]); digits=4)
# output
353.2495Orcus.backtest_summary — Method
backtest_summary(bt::Backtest)Print a concise performance summary for a completed backtest.
Random.seed!(1234);
x=asset();
y=asset();
T=Backtest(market([x,y]),CrossOverStrategy,1000);
run_test(T);
r = redirect_stdout(devnull) do
backtest_summary(T)
end;
isnothing(r)
# output
trueOrcus.bah_equity — Method
bah_equity(market::Market, cash::Real; key::String="Close")Compute the equity curve of an equal-weight buy-and-hold strategy across all assets in market from bar 1 onward. Useful as a benchmark for information_ratio and compare_backtests.
Random.seed!(1);
M=market([asset("AAPL"), asset("GOOG")]);
eq=bah_equity(M, 1000.0);
round(eq[1]; digits=4), length(eq)
# output
(1000.0, 3651)Orcus.calmar_ratio — Method
calmar_ratio(equity::Vector{<:Real}; periods_per_year::Int=252)Annualized return divided by maximum drawdown.
round(calmar_ratio([100.0, 105.0, 102.0, 110.0, 108.0, 115.0]); digits=4)
# output
12363.7339Orcus.compare_backtests — Method
compare_backtests(bts::Vector{<:Backtest}, names::Vector{String}; benchmark::Union{Vector{Float64},Nothing}=nothing)Print a side-by-side performance table for a collection of backtests. Columns: Strategy | CAGR | Sharpe | Sortino | Calmar | Omega | VaR95 | MaxDD | Trades. Pass benchmark (equity curve) to append an Information Ratio column.
Random.seed!(1234);
x=asset();
y=asset();
T=Backtest(market([x,y]),CrossOverStrategy,1000);
run_test(T);
T.broker.equity_history = [100.0, 105.0, 102.0, 110.0, 108.0, 115.0];
r = redirect_stdout(devnull) do
compare_backtests([T], ["CrossOver"])
end;
isnothing(r)
# output
trueOrcus.cross_section_rank — Method
cross_section_rank(v::Vector{Float64})Integer ranks 1…N (1 = smallest value). Useful for factor-based long/short construction.
cross_section_rank([30.0,10.0,20.0])
# output
3-element Vector{Int64}:
3
1
2Orcus.cross_section_zscore — Method
cross_section_zscore(v::Vector{Float64})Normalize a cross-sectional score vector to zero mean and unit variance. Returns zeros if the vector has zero standard deviation.
cross_section_zscore([1.0,2.0,3.0])
# output
3-element Vector{Float64}:
-1.0
0.0
1.0Orcus.cvar — Method
cvar(equity::Vector{<:Real}; confidence::Float64=0.95)Conditional Value at Risk (Expected Shortfall): the mean return on the worst (1-confidence) fraction of days. More conservative than VaR. Returned as a negative number.
round(cvar([100.0, 105.0, 102.0, 110.0, 108.0, 115.0]); digits=4)
# output
-0.0286Orcus.extended_summary — Method
extended_summary(bt::Backtest; benchmark::Union{Vector{Float64},Nothing}=nothing, periods_per_year::Int=infer_periods_per_year(bt.broker.market))Print a detailed performance summary including CAGR, Sortino, Calmar, Omega, VaR, CVaR, and Ulcer Index. Pass a benchmark equity curve (e.g. from bah_equity) to also print the Information Ratio.
Random.seed!(1234);
x=asset();
y=asset();
T=Backtest(market([x,y]),CrossOverStrategy,1000);
run_test(T);
T.broker.equity_history = [100.0, 105.0, 102.0, 110.0, 108.0, 115.0];
r = redirect_stdout(devnull) do
extended_summary(T)
end;
isnothing(r)
# output
trueOrcus.infer_periods_per_year — Method
infer_periods_per_year(M::Market)Bars per year implied by M's time axis (252 if it has none).
Random.seed!(1);
infer_periods_per_year(market([asset()]))
# output
252Orcus.infer_periods_per_year — Method
infer_periods_per_year(axis::Vector{DateTime})Bars per year implied by the median spacing of a time axis: intraday bars scale by bars per trading day, daily → 252, weekly → 52, monthly → 12, coarser → 1.
using Dates;
infer_periods_per_year(DateTime(2000,1,1) .+ Day.(0:1:400))
# output
252Orcus.information_ratio — Method
information_ratio(equity::Vector{<:Real}, benchmark_equity::Vector{<:Real}; periods_per_year::Int=252)Information Ratio: annualised active return divided by tracking error vs a benchmark equity curve. Values above 0.5 are considered good; above 1.0 exceptional.
round(information_ratio([100.0, 105.0, 102.0, 110.0, 108.0, 115.0], [100.0, 104.0, 103.0, 107.0, 106.0, 112.0]); digits=4)
# output
4.2698Orcus.max_drawdown — Method
max_drawdown(equity::Vector{<:Real})Maximum peak-to-trough drawdown as a fraction (0 to 1).
round(max_drawdown([100.0, 105.0, 102.0, 110.0, 108.0, 115.0]); digits=4)
# output
0.0286Orcus.omega_ratio — Method
omega_ratio(equity::Vector{<:Real}; threshold::Float64=0.0, periods_per_year::Int=252)Omega ratio: probability-weighted ratio of gains to losses above/below threshold (annualised). Values > 1 indicate more gain than loss.
round(omega_ratio([100.0, 105.0, 102.0, 110.0, 108.0, 115.0]); digits=4)
# output
4.1333Orcus.profit_factor — Method
profit_factor(equity::Vector{<:Real})Gross profit / gross loss computed from the equity-curve return series. Returns Inf if there are no losing bars.
round(profit_factor([100.0, 105.0, 102.0, 110.0, 108.0, 115.0]); digits=4)
# output
4.0Orcus.sharpe_ratio — Method
sharpe_ratio(equity::Vector{<:Real}; rf::Float64=0.0, periods_per_year::Int=252)Annualized Sharpe ratio computed from an equity curve. rf is the annualized risk-free rate (default 0). Returns NaN if there is insufficient data or zero volatility.
round(sharpe_ratio([100.0, 105.0, 102.0, 110.0, 108.0, 115.0]); digits=4)
# output
9.4412Orcus.sortino_ratio — Method
sortino_ratio(equity::Vector{<:Real}; rf::Float64=0.0, periods_per_year::Int=252)Annualized Sortino ratio: like Sharpe but penalises downside volatility only.
round(sortino_ratio([100.0, 105.0, 102.0, 110.0, 108.0, 115.0]); digits=4)
# output
30.7092Orcus.ulcer_index — Method
ulcer_index(equity::Vector{<:Real})Ulcer Index: root mean square of all percentage drawdowns from peak. Captures both depth and duration of drawdowns. Lower is better.
round(ulcer_index([100.0, 105.0, 102.0, 110.0, 108.0, 115.0]); digits=4)
# output
1.3826Orcus.value_at_risk — Method
value_at_risk(equity::Vector{<:Real}; confidence::Float64=0.95)Daily Value at Risk at the given confidence level: the return threshold such that losses exceed this level on (1-confidence) fraction of days. Returned as a negative number (a loss).
round(value_at_risk([100.0, 105.0, 102.0, 110.0, 108.0, 115.0]); digits=4)
# output
-0.0265Orcus.win_rate_bars — Method
win_rate_bars(equity::Vector{<:Real})Fraction of bars in which the equity curve increased (a rough proxy for trade-level win rate when the strategy has one position at a time).
round(win_rate_bars([100.0, 105.0, 102.0, 110.0, 108.0, 115.0]); digits=4)
# output
0.6Orcus.atr — Function
atr(high::Vector{Float64}, low::Vector{Float64}, close::Vector{Float64}, w::Int=14)Average True Range using Wilder's smoothing: True Range = max(H-L, |H-prevC|, |L-prevC|). Returns NaN for the first w+1 bars. Not directly IndicatorGenerator-compatible (needs three input series) — call directly in strategy logic.
h=[10.0,11.0,10.5,12.0,11.5,13.0,12.5];
l=[9.0,10.0,9.5,11.0,10.5,12.0,11.5];
c=[9.5,10.5,10.0,11.5,11.0,12.5,12.0];
round.(atr(h,l,c,3); digits=4)
# output
7-element Vector{Float64}:
NaN
NaN
NaN
1.5
1.3333
1.5556
1.3704Orcus.ema — Method
ema(v::Vector{Float64}, w::Int)Exponential moving average with span w (α = 2/(w+1)). Initialised with the SMA of the first w bars; NaN for earlier entries. Compatible with IndicatorGenerator: apply_indicator(IndicatorGenerator(ema, 20), asset, "Close", "EMA20").
round.(ema([1.0,2.0,3.0,4.0,5.0,6.0,7.0], 3); digits=4)
# output
7-element Vector{Float64}:
NaN
NaN
2.0
3.0
4.0
5.0
6.0Orcus.rsi — Function
rsi(v::Vector{Float64}, w::Int=14)Relative Strength Index using Wilder's smoothing. Returns values in [0, 100]; NaN for the first w bars. Compatible with IndicatorGenerator: apply_indicator(IndicatorGenerator(rsi, 14), asset, "Close", "RSI14").
v=[1.0,2.0,1.5,2.5,3.0,2.0,3.5,4.0,3.0,5.0,4.5,6.0,5.5,7.0,6.5,8.0];
round.(rsi(v, 5); digits=4)
# output
16-element Vector{Float64}:
NaN
NaN
NaN
NaN
NaN
62.5
74.4681
77.4648
59.8911
74.4065
66.8466
75.9934
68.1582
77.0367
69.0182
77.7161Orcus.bsm_call — Method
bsm_call(S::Float64, K::Float64, T::Float64, r::Float64, sigma::Float64)Black-Scholes-Merton European call price for underlying price S, strike K, time to expiry T in years, annualized risk-free rate r, and annualized volatility sigma. Returns intrinsic value max(S-K, 0) when T ≤ 0.
round(bsm_call(100.0, 100.0, 0.25, 0.04, 0.2); digits=4)
# output
4.4852Orcus.bsm_delta — Method
bsm_delta(S::Float64, K::Float64, T::Float64, r::Float64, sigma::Float64; type::Symbol=:call)Black-Scholes-Merton option delta. type is :call (default) or :put. Call delta ∈ (0, 1); put delta ∈ (-1, 0).
round(bsm_delta(100.0, 100.0, 0.25, 0.04, 0.2); digits=4)
# output
0.5596Orcus.bsm_put — Method
bsm_put(S::Float64, K::Float64, T::Float64, r::Float64, sigma::Float64)Black-Scholes-Merton European put price (via put-call parity).
round(bsm_put(100.0, 100.0, 0.25, 0.04, 0.2); digits=4)
# output
3.4902Orcus.realized_vol — Function
realized_vol(prices::Vector{Float64}, window::Int=20)Annualized realized volatility estimated from the last window log-returns of a price series. Returns NaN if insufficient data.
prices=[100.0,101.0,99.0,102.0,103.0,101.0,104.0,105.0,103.0,106.0,107.0,105.0,108.0,109.0,107.0,110.0,111.0,109.0,112.0,113.0,111.0];
round(realized_vol(prices, 20); digits=4)
# output
0.3143Orcus.RollingPCAOrcus.annualized_returnOrcus.atrOrcus.backtest_summaryOrcus.bah_equityOrcus.bsm_callOrcus.bsm_deltaOrcus.bsm_putOrcus.calmar_ratioOrcus.compare_backtestsOrcus.cross_section_rankOrcus.cross_section_zscoreOrcus.cvarOrcus.emaOrcus.emaOrcus.explained_varianceOrcus.extended_summaryOrcus.fit!Orcus.infer_periods_per_yearOrcus.infer_periods_per_yearOrcus.information_ratioOrcus.max_drawdownOrcus.omega_ratioOrcus.plot_residual_corrOrcus.profit_factorOrcus.projectOrcus.projectOrcus.realized_volOrcus.residual_corrOrcus.rolling_meanOrcus.rolling_pcaOrcus.rolling_stdOrcus.rolling_zscoreOrcus.rolling_zscoreOrcus.rsiOrcus.sharpe_ratioOrcus.sortino_ratioOrcus.ulcer_indexOrcus.value_at_riskOrcus.win_rate_bars