Market data
Asset and Market are the price-data containers: an Asset holds one instrument's OHLC history plus any attached indicators, a Market collects several assets, and advance_to! moves the shared bar cursor by re-slicing each asset's visible window as a SubArray — no copy per bar.
Asset
The Asset type is the core data structure in Orcus. It represents a single financial instrument and contains all of its historical market data.
Orcus.Asset — Type
AssetA single instrument's price/indicator data.
data is an AbstractMatrix{Float64} with shape [n_datasets × n_bars]. During a backtest the broker's assets hold a SubArray view into the base market data. Outside the loop it is always a concrete Matrix{Float64}.
NaN is used as the sentinel for missing/gap bars.
currency labels the asset's price units; :base means the broker's base currency. fx references the converting rate asset (wired by set_fx!), and fx_rate is the last known rate from that asset's visible window.
Random.seed!(1);
A=asset();
A.currency
# output
:baseOrcus.asset — Function
asset(ticker::String, data::AbstractMatrix{Float64}, data_id::Vector{String})
asset(ticker::String, interval::StepRange{Int,Int}, mu::Real, sigma::Real, base::Real=100, precision::Int=10)
asset(ticker::String)
asset()Build an asset. With a ticker, data, and data_id, wraps them directly. The other methods build one with synthetic OHLC data.
Random.seed!(1);
A=asset();
A.ticker
# output
"BJSQ"Base.names — Function
names(A::Asset)Row names (dataset ids) on the asset, in row-index order.
Random.seed!(1);
A=asset();
names(A)
# output
4-element Vector{String}:
"Open"
"High"
"Low"
"Close"Orcus.rowindex — Function
rowindex(A::Asset, name::String) -> IntRow index of the named series (0 if absent). Resolve once (e.g. in a strategy's init) and read with the integer accessor A[row, col] to skip the per-call String hash on the hot path.
Orcus.height — Function
height(A::Asset)Number of data rows (datasets) on the asset, e.g. 4 for plain OHLC.
Random.seed!(1);
A=asset();
height(A)
# output
4height(M::Market)Number of assets in the market.
Random.seed!(1);
M=market([asset(),asset()]);
height(M)
# output
2Orcus.value — Method
value(A::Asset, data_key::String="Close")Current price: last non-NaN value in the named row.
Random.seed!(1);
A=asset();
value(A)
# output
9.455734786039033Orcus.apply_indicator — Function
apply_indicator(Ind::IndicatorGenerator, asset::Asset, data_key::String, name::String)Compute and attach a named indicator row to the asset. Must be called in init, not next.
Random.seed!(1);
A=asset();
SMA10=IndicatorGenerator(simple_average, 10);
apply_indicator(SMA10, A, "Close", "SMA10");
names(A)
# output
5-element Vector{String}:
"Open"
"High"
"Low"
"Close"
"SMA10"Orcus.calculate_indicator — Function
calculate_indicator(Ind, asset, data_key)Compute an indicator series over the asset's named column.
Orcus.IndicatorGenerator — Type
IndicatorGenerator(f::Function, window::Int)A rolling-window indicator: applies f to each window-length slice of a data series.
SMA10=IndicatorGenerator(simple_average,10)
SMA10.window
# output
10Orcus.simple_average — Function
simple_average(data::DataPoint)Mean of data, ignoring NaN entries.
simple_average([1.0,NaN,3.0,4.0])
# output
2.6666666666666665Orcus.shorten! — Function
shorten!(A::Asset, u::UnitRange{Int})Trim asset data to the given column range.
Random.seed!(1);
A=asset();
shorten!(A, 1:10);
size(A)
# output
(4, 10)shorten!(M::Market, U::UnitRange{Int})Trim every asset in the market to the given column range.
Random.seed!(1);
M=market([asset(),asset()]);
shorten!(M, 1:10);
length(M)
# output
10Orcus.add_datapoint! — Function
add_datapoint!(A::Asset, dp::DataPoint)Append a new bar (column) to the asset, recomputing indicator rows.
Random.seed!(1);
A=asset();
shorten!(A, 1:10);
add_datapoint!(A, [10.0, 11.0, 9.5, 10.5]);
size(A)
# output
(4, 11)Examples
There are multiple ways to load data into Orcus. You can also use randomly generated data provided via rand_ohlc, or the sample data in the data folder.
Orcus.rand_ohlc — Function
rand_ohlc(base, mu, sigma, interval, precision) -> (DataSeries, Vector{String})Generate synthetic OHLC bars via Geometric Brownian Motion (drift mu, volatility sigma, both per intrabar substep). Non-sampled bars are filled with NaN.
Orcus.available_stocks — Function
available_stocks()List all stock tickers available in the data directory.
length(available_stocks())
# output
34Orcus.load_stocks — Function
load_stocks(names::Vector{String})Load multiple stocks onto a shared bar grid and attach it as the market's time axis. Dates missing for a ticker are NaN bars. All tickers must exist in the data directory (see available_stocks).
M=load_stocks(["AAPL","GOOG"]);
length(M.data)
# output
2Orcus.load_stock — Function
load_stock(name::String)Load the stock data from the CSV file data/<name>.csv.
load_stock("GOOG")
# output
Asset 'GOOG' with 6 datasetsOrcus.load_csvs — Function
load_csvs(paths::Vector{String}; tickers::Vector{String}=[splitext(basename(p))[1] for p in paths], date::Symbol=:date, columns::Dict{Symbol,Symbol}=Dict{Symbol,Symbol}())Load multiple OHLC CSVs from arbitrary file paths onto a shared bar grid and attach it as the market's time axis. Dates missing for a ticker are NaN bars. tickers defaults to each path's filename stem; date/columns are shared across all paths (see load_csv).
dir=joinpath(pkgdir(Orcus), "src", "Lib", "data");
M=load_csvs([joinpath(dir, "AAPL.csv"), joinpath(dir, "GOOG.csv")]);
length(M.data)
# output
2Orcus.load_csv — Function
load_csv(path::String; ticker::String=splitext(basename(path))[1], date::Symbol=:date, columns::Dict{Symbol,Symbol}=Dict{Symbol,Symbol}())Load an OHLC CSV from an arbitrary file path. date names the source date column; columns maps source header names to Orcus's canonical names (:open/:high/:low/:close/:volume), e.g. columns=Dict(:adjclose => :close) for a Yahoo-style export. ticker defaults to the filename stem.
path=joinpath(pkgdir(Orcus), "src", "Lib", "data", "GOOG.csv");
load_csv(path)
# output
Asset 'GOOG' with 6 datasetsOrcus.GOOG — Constant
GOOGShared sample Asset fixture loaded from data/GOOG.csv. Wrap a copy before running a strategy against it: market([copy(GOOG)]).
Orcus.AAPL — Constant
AAPLShared sample Asset fixture loaded from data/AAPL.csv. Wrap a copy before running a strategy against it: market([copy(AAPL)]).
Synthetic data
Lower-level building blocks behind rand_ohlc, useful for stitching together custom price paths (e.g. calm → crash → recovery regimes) for strategy stress-testing.
Orcus.DataPoint — Type
DataPointA single bar's worth of row values, one per dataset (Vector{Float64}).
DataPoint([1.0,2.0])
# output
2-element Vector{Float64}:
1.0
2.0Orcus.DataSeries — Type
DataSeriesA row × bar price/indicator matrix (Matrix{Float64}).
DataSeries(undef,1,1) isa DataSeries
# output
trueOrcus.data_series — Function
data_series(x::Vector{DataPoint})Stack ragged data points into a DataSeries, right-padding shorter rows with NaN.
data_series([[1.0,2.0],[1.0,2.0,3.0]])
# output
2×3 Matrix{Float64}:
1.0 2.0 NaN
1.0 2.0 3.0Orcus.gbm_path — Function
gbm_path(x0::Real, mu::Real, sigma::Real, n::Int; rng::AbstractRNG=Random.default_rng())Length-n GBM path starting at x0 (path[1] == x0, each subsequent value via gbm_step).
Random.seed!(1);
gbm_path(100.0, 0.0, 0.02, 5)
# output
5-element Vector{Float64}:
100.0
99.83896352519557
100.8856864256948
99.25090240467883
104.22904553825927gbm_path(x0::AbstractVector{<:Real}, mu::AbstractVector{<:Real}, sigma::AbstractVector{<:Real}, rho::AbstractMatrix{<:Real}, n::Int; rng::AbstractRNG=Random.default_rng())Correlated GBM paths for length(x0) assets over n bars ([n_assets × n_bars]).
Random.seed!(1);
gbm_path([100.0,50.0], [0.0,0.0], [0.02,0.03], [1.0 0.5; 0.5 1.0], 3)
# output
2×3 Matrix{Float64}:
100.0 99.839 98.2211
50.0 50.6188 53.2823Orcus.gbm_path_segments — Function
gbm_path_segments(x0::Real, segments; rng::AbstractRNG=Random.default_rng())Chain gbm_path across a list of regimes into one continuous series. Each element of segments is a (mu, sigma, n) NamedTuple; each regime continues from the previous regime's last price.
Random.seed!(1);
gbm_path_segments(100.0, [(mu=0.0,sigma=0.02,n=3),(mu=0.1,sigma=0.3,n=2)])
# output
4-element Vector{Float64}:
100.0
99.83896352519557
100.8856864256948
83.67434009011494gbm_path_segments(x0::AbstractVector{<:Real}, segments; rng::AbstractRNG=Random.default_rng())Multivariate counterpart of gbm_path_segments. Each element of segments is a (mu, sigma, rho, n) NamedTuple — rho may change between segments.
Random.seed!(1);
gbm_path_segments([100.0,50.0], [(mu=[0.0,0.0],sigma=[0.02,0.03],rho=[1.0 0.5; 0.5 1.0],n=3)])
# output
2×3 Matrix{Float64}:
100.0 99.839 98.2211
50.0 50.6188 53.2823Orcus.gbm_step — Function
gbm_step(x::Real, mu::Real, sigma::Real; rng::AbstractRNG=Random.default_rng())Next Geometric Brownian Motion price given current price x, drift mu, and volatility sigma (both per-step). Strictly positive for x > 0.
Random.seed!(1);
gbm_step(100.0, 0.0, 0.02)
# output
99.83896352519557gbm_step(x::AbstractVector{<:Real}, mu::AbstractVector{<:Real}, sigma::AbstractVector{<:Real}, rho::AbstractMatrix{<:Real}; rng::AbstractRNG=Random.default_rng())Next correlated GBM prices for a vector of assets: drift mu, volatility sigma, and correlation matrix rho (all per-step). Use gbm_path for a loop over multiple steps.
Random.seed!(1);
gbm_step([100.0,50.0], [0.0,0.0], [0.02,0.03], [1.0 0.5; 0.5 1.0])
# output
2-element Vector{Float64}:
99.83896352519557
50.61876864985363Market
The Market type is a collection of Assets. It allows you to manage multiple assets and their data in a single structure.
Orcus.Market — Type
MarketA collection of Assets keyed by ticker. Carries an optional shared time axis: axis[j] labels bar j of every asset; nothing means bars are abstract integer indices.
Random.seed!(1);
M=market([asset(),asset()]);
length(M.data)
# output
2Orcus.market — Function
market(assets::Vector{Asset})
market(asset::Asset)
market(n_assets::Int)
market()Construct a new Market from the given assets, a single asset, or an empty market. The n_assets constructor creates n_assets random assets with default parameters.
Random.seed!(1);
M=market([asset(),asset()]);
length(M.data)
# output
2Orcus.height — Method
height(M::Market)Number of assets in the market.
Random.seed!(1);
M=market([asset(),asset()]);
height(M)
# output
2Orcus.add_asset! — Function
add_asset!(M::Market, A::Asset)Add or replace an asset in the market, keyed by its ticker.
M=market();
add_asset!(M, asset("AAPL"));
length(M.data)
# output
1Orcus.advance_to! — Function
advance_to!(M::Market, i::Int)Reveal bars 1:i of every asset in the market — what the backtest loop does once per bar.
Random.seed!(1);
M=market([asset(),asset()]);
advance_to!(M, 5);
length(M)
# output
5Orcus.shorten! — Method
shorten!(M::Market, U::UnitRange{Int})Trim every asset in the market to the given column range.
Random.seed!(1);
M=market([asset(),asset()]);
shorten!(M, 1:10);
length(M)
# output
10Orcus.asset_names — Function
asset_names(M::Market)Sorted ticker names — stable ordering for cross-sectional matrix rows.
Random.seed!(1);
M=market([asset("BBB"), asset("AAA")]);
asset_names(M)
# output
2-element Vector{String}:
"AAA"
"BBB"Orcus.returns_matrix — Function
returns_matrix(M::Market, window::UnitRange{Int}; key::String="Close")[N × (T-1)] log-return matrix. Rows = assets (alpha order), cols = bars. NaN/zero prices fill the corresponding column with 0.0.
Random.seed!(1);
M=market([asset("AAPL")]);
size(returns_matrix(M, 1:5))
# output
(1, 4)Orcus.trim_to_length — Method
trim_to_length(M::Market, n::Int)New Market where every asset is trimmed to its last n bars.
Random.seed!(1);
M=market([asset("AAPL")]);
length(trim_to_length(M, 10))
# output
10Orcus.set_fx! — Function
set_fx!(M::Market, ccy::Symbol, fx_asset::Asset)Register fx_asset as the conversion rate for assets priced in ccy. Its Close must be base-currency units per 1 unit of ccy. The rate asset joins the market and every current and future asset with currency == ccy converts through it.
Random.seed!(1);
A=asset("AAPL");
A.currency=:EUR;
M=market([A]);
fx=asset("EURUSD");
set_fx!(M, :EUR, fx);
length(M.data)
# output
2Time axis
An optional shared Market.axis::Vector{DateTime} labels bars for loaders, collectors, and annualization; the engine clock itself stays an integer bar index and never reads it.
Orcus.set_axis! — Function
set_axis!(M::Market, axis::Vector{DateTime})Attach a shared time axis: axis[j] labels bar j of every asset. Must be sorted and match the full data width of the market's widest asset.
using Dates;
Random.seed!(1);
M=market([asset("AAPL")]);
axis=DateTime(2000,1,1) .+ Day.(0:3650);
set_axis!(M, axis);
has_axis(M)
# output
trueOrcus.timestamp — Function
timestamp(M::Market, i::Int)Timestamp of bar i. Errors when the market has no axis.
using Dates;
Random.seed!(1);
M=market([asset("AAPL")]);
set_axis!(M, DateTime(2000,1,1) .+ Day.(0:3650));
timestamp(M, 1)
# output
2000-01-01T00:00:00Orcus.bar_of — Function
bar_of(M::Market, t::DateTime)Index of the last bar at or before t (0 if t precedes the axis). Errors when the market has no axis.
using Dates;
Random.seed!(1);
M=market([asset("AAPL")]);
set_axis!(M, DateTime(2000,1,1) .+ Day.(0:3650));
bar_of(M, DateTime(2000,1,10))
# output
10Orcus.has_axis — Function
has_axis(M::Market)Whether the market has a shared time axis attached.
M=market([asset("AAPL")]);
has_axis(M)
# output
falseOrcus.resample — Function
resample(M::Market, k::Int)
resample(M::Market, p::Dates.Period)New Market with bars aggregated k-to-1 (or grouped by calendar period, which requires a time axis): Open = first, High = max, Low = min, Volume = sum, everything else (Close, indicators) = last. Attached indicator generators are not carried over — re-apply indicators on the resampled market.
M=market([asset("AAPL")]);
length(resample(M, 5))
# output
731Index
Orcus.AAPLOrcus.GOOGOrcus.AssetOrcus.DataPointOrcus.DataSeriesOrcus.IndicatorGeneratorOrcus.MarketBase.namesOrcus.add_asset!Orcus.add_datapoint!Orcus.advance_to!Orcus.apply_indicatorOrcus.assetOrcus.asset_namesOrcus.available_stocksOrcus.bar_ofOrcus.calculate_indicatorOrcus.data_seriesOrcus.gbm_pathOrcus.gbm_path_segmentsOrcus.gbm_stepOrcus.has_axisOrcus.heightOrcus.heightOrcus.load_csvOrcus.load_csvsOrcus.load_stockOrcus.load_stocksOrcus.marketOrcus.rand_ohlcOrcus.resampleOrcus.returns_matrixOrcus.rowindexOrcus.set_axis!Orcus.set_fx!Orcus.shorten!Orcus.shorten!Orcus.simple_averageOrcus.timestampOrcus.trim_to_lengthOrcus.value