Skip to main content

Overview

The ank-oracle module provides minimal price oracle types for querying time-series price data in backtests and agent-based models. Key features:
  • In-memory time-series oracle with 1e18-scaled integer storage
  • CSV-based price feed loading
  • Float and high-precision e18 integer views

Types

Oracle (trait)

Trait for querying prices from an oracle. Implementations may store higher-precision values (e.g., e18 integers) and convert on the fly.

Methods

get
fn(&self, ts: Timestamp, key: &str) -> Option<f64>
Return the most recent price at or before ts for key, as f64.
latest
fn(&self, key: &str) -> Option<(Timestamp, f64)>
Return the latest available (ts, price) for key, as f64.

SimpleOracle

An in-memory oracle with both float and e18 integer storage. Canonical storage: The series field stores prices as 1e18-scaled integers in BTreeMaps, providing exact arithmetic and efficient lookups. Legacy storage: The optional data field provides a float view for backward compatibility.

Fields

data
IndexMap<String, Vec<DataPoint>>
Optional float view: key → [DataPoint] (should be sorted by ts).
series
IndexMap<String, BTreeMap<u64, U128S>>
Canonical e18 time series: key → { ts → price_e18 }.Prices are stored as 1e18-scaled integers (U128S wrapper for JSON string serialization).

Methods

insert
fn(&mut self, key: String, ts: u64, price_e18: u128)
Insert a (ts, price_e18) sample for key.
latest_at
fn(&self, key: &str, ts: u64) -> Option<(u64, u128)>
Latest (ts, price_e18) at or before ts for key.
latest_e18
fn(&self, key: &str) -> Option<(u64, u128)>
Latest (ts, price_e18) available for key.
get_f64
fn(&self, ts: u64, key: &str) -> Option<f64>
Convenience: f64 price at or before ts, derived from e18 storage.
latest_f64
fn(&self, key: &str) -> Option<(u64, f64)>
Convenience: latest f64 price derived from e18 storage.

DataPoint

A single floating-point datapoint (legacy / optional).
ts
Timestamp
Timestamp of the datapoint.
value
f64
Price as f64 (informational; prefer series for canonical values).

Functions

load_prices_csv

Load {ts,key,price_e18} CSV into a SimpleOracle. Signature:
pub fn load_prices_csv(path: &str) -> Result<SimpleOracle, Box<dyn std::error::Error>>
CSV Format: The CSV must have headers: ts,key,price_e18
ts
u64
Timestamp in seconds or milliseconds
key
String
Asset identifier (e.g., “ETH”, “BTC”, “USDC”)
price_e18
String
Decimal string representing the price scaled by 1e18. Underscores are allowed and will be stripped.
Behavior:
  • Rows with price_e18 == 0 or unparsable values are skipped
  • Prices are stored as 1e18-scaled integers for exact arithmetic

Usage Examples

Loading prices from CSV

use ank_oracle::load_prices_csv;

let oracle = load_prices_csv("data/ohlc/prices.csv")?;

// Get latest ETH price
if let Some((ts, p_e18)) = oracle.latest_e18("ETH") {
    println!("Latest ETH @ {} = {} e18", ts, p_e18);
    
    // Convert to float for display
    let price_usd = p_e18 as f64 / 1e18;
    println!("ETH price: ${:.2}", price_usd);
}

Querying historical prices

use ank_oracle::{Oracle, SimpleOracle};

let mut oracle = SimpleOracle::default();

// Insert prices manually
oracle.insert("ETH".into(), 1000, 2000_000_000_000_000_000_000); // $2000 at t=1000
oracle.insert("ETH".into(), 2000, 2100_000_000_000_000_000_000); // $2100 at t=2000

// Query at specific timestamp
if let Some(price) = oracle.get(1500, "ETH") {
    println!("ETH price at t=1500: ${:.2}", price);
}

// Get latest price
if let Some((ts, price)) = oracle.latest("ETH") {
    println!("Latest ETH @ {}: ${:.2}", ts, price);
}

Creating a CSV price feed

Create a CSV file with the following format:
ts,key,price_e18
1640000000,ETH,3800_000_000_000_000_000_000
1640003600,ETH,3825_000_000_000_000_000_000
1640007200,ETH,3810_000_000_000_000_000_000
1640000000,BTC,47000_000_000_000_000_000_000
1640003600,BTC,47200_000_000_000_000_000_000
Load and use:
let oracle = load_prices_csv("prices.csv")?;

// Query both assets
let eth_price = oracle.get(1640001800, "ETH");
let btc_price = oracle.get(1640001800, "BTC");

Implementation Notes

1e18 Scaling

All prices are stored internally as u128 values scaled by 1e18 (WAD scale):
  • $1.001_000_000_000_000_000_000 (1e18)
  • $3,847.523_847_520_000_000_000_000_000 (~3.85e21)
This provides:
  • ✅ Exact arithmetic (no floating-point errors)
  • ✅ High precision for small values
  • ✅ Compatibility with on-chain representations

Time Series Lookup

The SimpleOracle uses BTreeMap for ordered timestamp storage, enabling efficient:
  • Binary search for “latest at or before” queries: O(log n)
  • Range queries for batch processing
  • Guaranteed ordering for reproducible backtests

Oracle Trait Implementation

SimpleOracle implements the Oracle trait with a fallback strategy:
  1. First, try to query the e18 series (preferred)
  2. If not found, fall back to legacy float data
This ensures compatibility while encouraging migration to high-precision storage.

Build docs developers (and LLMs) love