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.AssetType
Asset

A 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

:base
source
Orcus.assetFunction
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"
source
Base.namesFunction
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"
source
Orcus.rowindexFunction
rowindex(A::Asset, name::String) -> Int

Row 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.

source
Orcus.heightFunction
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

4
source
height(M::Market)

Number of assets in the market.

Random.seed!(1);
M=market([asset(),asset()]);
height(M)
# output

2
source
Orcus.valueMethod
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.455734786039033
source
Orcus.apply_indicatorFunction
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"
source
Orcus.IndicatorGeneratorType
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

10
source
Orcus.simple_averageFunction
simple_average(data::DataPoint)

Mean of data, ignoring NaN entries.

simple_average([1.0,NaN,3.0,4.0])
# output

2.6666666666666665
source
Orcus.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)
source
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

10
source
Orcus.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)
source

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_ohlcFunction
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.

source
Orcus.available_stocksFunction
available_stocks()

List all stock tickers available in the data directory.

length(available_stocks())
# output

34
source
Orcus.load_stocksFunction
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

2
source
Orcus.load_stockFunction
load_stock(name::String)

Load the stock data from the CSV file data/<name>.csv.

load_stock("GOOG")
# output

Asset 'GOOG' with 6 datasets
source
Orcus.load_csvsFunction
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

2
source
Orcus.load_csvFunction
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 datasets
source
Orcus.GOOGConstant
GOOG

Shared sample Asset fixture loaded from data/GOOG.csv. Wrap a copy before running a strategy against it: market([copy(GOOG)]).

source
Orcus.AAPLConstant
AAPL

Shared sample Asset fixture loaded from data/AAPL.csv. Wrap a copy before running a strategy against it: market([copy(AAPL)]).

source

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.DataPointType
DataPoint

A 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.0
source
Orcus.DataSeriesType
DataSeries

A row × bar price/indicator matrix (Matrix{Float64}).

DataSeries(undef,1,1) isa DataSeries
# output

true
source
Orcus.data_seriesFunction
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.0
source
Orcus.gbm_pathFunction
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.22904553825927
source
gbm_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.2823
source
Orcus.gbm_path_segmentsFunction
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.67434009011494
source
gbm_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.2823
source
Orcus.gbm_stepFunction
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.83896352519557
source
gbm_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.61876864985363
source

Market

The Market type is a collection of Assets. It allows you to manage multiple assets and their data in a single structure.

Orcus.MarketType
Market

A 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

2
source
Orcus.marketFunction
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

2
source
Orcus.heightMethod
height(M::Market)

Number of assets in the market.

Random.seed!(1);
M=market([asset(),asset()]);
height(M)
# output

2
source
Orcus.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

1
source
Orcus.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

5
source
Orcus.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

10
source
Orcus.asset_namesFunction
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"
source
Orcus.returns_matrixFunction
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)
source
Orcus.trim_to_lengthMethod
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

10
source
Orcus.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

2
source

Time 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

true
source
Orcus.timestampFunction
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:00
source
Orcus.bar_ofFunction
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

10
source
Orcus.has_axisFunction
has_axis(M::Market)

Whether the market has a shared time axis attached.

M=market([asset("AAPL")]);
has_axis(M)
# output

false
source
Orcus.resampleFunction
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

731
source

Index