Expand description
§volas
English | 简体中文
A Rust-backed, OHLCV-shaped DataFrame for candlestick / market time-series, with a
technical-indicator directive engine.
volas is intentionally narrow — not a general-purpose DataFrame. It targets live OHLCV
pipelines: compute an indicator by naming its directive ("ma:20", "macd.signal",
"close > open"), and resample / cumulate bars to a coarser time frame.
[dependencies]
volas = "1"There is also a Python package named
volas— the same Rust kernels exposed via PyO3. See the volas Python project on GitHub andvolason PyPI. It is a separate distribution from this crate; the directive vocabulary is identical across both.
§Quick start
use volas::{Column, DataFrame};
use volas::directive::{execute, parse};
// Build a frame from named columns (here just `close`).
let df = DataFrame::new(
vec!["close".to_string()],
vec![Column::f64(vec![1.0, 2.0, 3.0, 4.0])],
None,
).unwrap();
// Compute an indicator by its directive string. `ma:2` is the 2-period SMA.
let directive = parse("ma:2").unwrap();
let ma = execute(&df, &directive).unwrap();
assert!(!ma.is_valid(0)); // the warm-up row is NA (no value yet)
assert_eq!(ma.to_f64_vec()[3], 3.5); // (3.0 + 4.0) / 2§The data model
A DataFrame is a set of equal-length, named, typed Columns over a shared row
Index. A Column is a contiguous typed buffer; any cell can be NA (missing)
independently of its value — see Missing values below.
use volas::{Column, DataFrame, DType};
let df = DataFrame::new(
vec!["open".to_string(), "close".to_string()],
vec![
Column::f64(vec![10.0, 11.0, 12.0]),
Column::f64(vec![10.5, 10.5, 13.0]),
],
None, // None => a default 0..n RangeIndex
).unwrap();
assert_eq!(df.height(), 3);
assert_eq!(df.width(), 2);
assert_eq!(df.names(), &["open".to_string(), "close".to_string()]);
let close = df.column("close").unwrap(); // -> &Column
assert_eq!(close.dtype(), DType::F64);
assert_eq!(close.to_f64_vec()[2], 13.0); // owned Vec<f64>
assert_eq!(close.as_f64(), Some(&[10.5, 10.5, 13.0][..])); // borrowed slice (F64 only)Column constructors: Column::f64, Column::i64, Column::bool,
Column::str. Read values with Column::to_f64_vec (owned), Column::as_f64
(borrowed, None for non-f64), Column::is_valid (per-cell NA check),
Column::len, and Column::dtype.
§Missing values (NA)
A cell can be missing independently of its value. Float columns use NaN as the NA
marker; integer / bool / string columns carry an explicit validity mask. Check
missingness per cell with is_valid, and count it with null_count.
use volas::Column;
use volas::core::Validity;
// Float columns: NaN is the missing marker.
let prices = Column::f64(vec![1.0, f64::NAN, 3.0]);
assert_eq!(prices.null_count(), 1);
assert!(prices.is_valid(0));
assert!(!prices.is_valid(1)); // cell 1 is NA
assert!(prices.get_f64(1).is_nan()); // reading an NA f64 yields NaN
// Integer / bool / string columns carry a validity mask instead of a sentinel.
let volume = Column::i64_with(
vec![100, 0, 300],
Validity::from_valid_iter(3, [true, false, true]), // cell 1 is NA
);
assert_eq!(volume.null_count(), 1);
assert!(!volume.is_valid(1));Indicator warm-up rows are NA too — e.g. a 2-period SMA has one NA row at the head:
use volas::{Column, DataFrame};
use volas::directive::{execute, parse};
let df = DataFrame::new(
vec!["close".to_string()],
vec![Column::f64(vec![1.0, 2.0, 3.0])],
None,
).unwrap();
let ma = execute(&df, &parse("ma:2").unwrap()).unwrap();
assert!(!ma.is_valid(0)); // the warm-up row is NA
assert_eq!(ma.null_count(), 1);§Indicators via directives
The directive engine is the primary way to compute indicators: parse a directive
string into an Ast, then execute it against a DataFrame to get a Column. This
covers every built-in indicator uniformly — see the full reference of all
directives in
INDICATORS.md.
use volas::{Column, DataFrame};
use volas::directive::{execute, parse, stringify};
let n = 40;
let df = DataFrame::new(
vec!["open".to_string(), "close".to_string()],
vec![
Column::f64((1..=n).map(|x| x as f64).collect()),
Column::f64((1..=n).map(|x| x as f64 + 0.5).collect()),
],
None,
).unwrap();
// Single-output directive -> one Column.
let rsi = execute(&df, &parse("rsi:14").unwrap()).unwrap();
assert_eq!(rsi.len(), 40);
// Multi-output indicator: address each line by its sub-command.
let macd = execute(&df, &parse("macd").unwrap()).unwrap();
let signal = execute(&df, &parse("macd.signal").unwrap()).unwrap();
let hist = execute(&df, &parse("macd.histogram").unwrap()).unwrap();
assert_eq!((macd.len(), signal.len(), hist.len()), (40, 40, 40));
// A comparison directive -> a Bool column (usable as a row mask).
let bullish = execute(&df, &parse("close > open").unwrap()).unwrap();
assert_eq!(bullish.len(), 40);
// `@`-suffix picks input columns; canonical form drops a default input.
let ast = parse("ma:20@close").unwrap();
assert_eq!(stringify(&ast), "ma:20");A directive has the shape name:arg0,arg1,...@col0,col1,... — name is the indicator,
: arguments are its parameters, and @ columns are the inputs (each with a sensible
default, e.g. close). Multi-output indicators expose each line as name.subcommand
(macd.signal, boll.upper, kdj.k). The exact arguments, defaults, and sub-commands
for all directives are in
INDICATORS.md.
Warm-up length (rows before the first valid value) is available without computing:
use volas::directive::{lookback, parse};
let ast = parse("ma:20").unwrap();
assert_eq!(lookback(&ast), 19); // a 20-period SMA has 19 warm-up rows§Raw kernels (advanced)
The directive engine is the recommended API. For direct, DataFrame-free access, the
compute module exposes the numeric kernels as pure functions over slices
(&[f64] -> Vec<f64>); most users should prefer directives, which are stable, complete,
and identical to the Python surface.
§Reading CSV
use volas::{read_csv, ReadCsvOptions};
// Defaults: comma-separated, the first row is the header, standard NA tokens.
let df = read_csv("ohlcv.csv", &ReadCsvOptions::default()).unwrap();
println!("{} rows x {} cols", df.height(), df.width());path is any AsRef<Path> (&str, String, PathBuf). Tune parsing via
ReadCsvOptions (delimiter, has_header, na_values, keep_default_na).
§Time-frame cumulation (OHLCV resampling)
Aggregate finer bars into a coarser TimeFrame (the source must have a
DatetimeIndex). The default OHLCV spec is open=first, high=max, low=min,
close=last, volume=sum.
use volas::{Column, DataFrame, TimeFrame};
use volas::time::{cumulate, AggSpec};
// In practice `df` has a 1-minute DatetimeIndex; this is a placeholder frame.
let df = DataFrame::new(vec!["close".to_string()], vec![Column::f64(vec![1.0])], None).unwrap();
// Resample to 5-minute bars.
let five_min = cumulate(&df, TimeFrame::Min5, &AggSpec::ohlcv()).unwrap();
let _ = five_min;TimeFrame is an enum (Min1, Min5, Hour1, Day1, …) and also parses a label via
TimeFrame::from_label("5m").
§Error handling
Fallible functions return Result<_, VolasError> (a single error enum) — no panics on
bad input. An unknown or malformed directive is reported at parse:
use volas::VolasError;
use volas::directive::parse;
let ok = parse("ma:2");
assert!(ok.is_ok());
let bad: Result<_, VolasError> = parse("definitely_not_an_indicator:5");
assert!(bad.is_err());§Crate layout
- top level — the data model (
DataFrame,Series,Column,Index,DType,Scalar,Tz,Result,VolasError), plusread_csvandTimeFrame; directive—parse/execute/stringify/lookback, and theAst;compute— numeric kernels and technical indicators (pure functions);time— time-frame cumulation (OHLCV resampling);core— the fullvolas-coresurface, for the less-common types.
This crate is a thin facade re-exporting the volas workspace (volas-core,
volas-compute, volas-directive, volas-time, volas-io) behind one dependency.
§License
Re-exports§
pub use volas_core as core;
Modules§
- compute
- Numeric kernels and technical indicators — pure functions over slices, usable
without a
DataFrame. - directive
- The directive engine: parse a directive string (e.g.
"ma:20","macd.signal","close > open") into anAst, then [execute] it against aDataFrameto get aColumn. - time
- Time-frame cumulation (OHLCV resampling): aggregate finer bars into a coarser
TimeFrame.
Structs§
- Data
Frame - A 2-D, column-oriented, time-indexed table. All columns share one index and
have equal length (
height). - Index
- Row labels plus an optional name.
- Read
CsvOptions - Options controlling
read_csv. - Series
- A one-dimensional, named, indexed column.
Enums§
- Column
- A typed, contiguous column of values. The value buffer is a
Buffer— owned (Arc-shared, cheap clone, copy-on-write mutation) or a zero-copy borrow of foreign (Arrow / NumPy) memory. - DType
- The logical data type of a
crate::Column. - Index
Kind - The label storage backing an
Index. - Label
- A single row-index label: an integer / datetime (
I64, ns) or a string (Str). It decouples label lookup from the index’s storage so the same.loc/.at/.loc[a:b]/droppaths serve every index kind. - Scalar
- The result of a dtype-preserving scalar reduction (
Column::sumetc.), carrying the value in its pandas result dtype so the binding can box it as the matching numpy scalar (np.int64/np.float64/np.bool_). - Time
Frame - An OHLCV sampling period.
- Tz
- The timezone attached to a
DatetimeIndex. Storage is always UTC epoch-ns; this drives wall-clock conversion only. - Volas
Error - Errors raised by the volas core. The Python binding maps each variant to a
typed exception (see
volas-python).
Functions§
- read_
csv - Read a CSV file into a
DataFrame, inferring each column’s dtype (i64->f64->bool->str).
Type Aliases§
- Result
- Convenience result alias used throughout the core.