Expose strategy runtime data APIs

This commit is contained in:
boris
2026-04-23 19:29:12 -07:00
parent 1760fc6cd1
commit c3ef0bd49a
9 changed files with 678 additions and 6 deletions

View File

@@ -776,6 +776,12 @@ impl DataSet {
&self.instruments
}
pub fn all_instruments(&self) -> Vec<&Instrument> {
let mut instruments = self.instruments.values().collect::<Vec<_>>();
instruments.sort_by(|left, right| left.symbol.cmp(&right.symbol));
instruments
}
pub fn instrument(&self, symbol: &str) -> Option<&Instrument> {
self.instruments.get(symbol)
}
@@ -829,6 +835,118 @@ impl DataSet {
self.benchmark_by_date.values().cloned().collect()
}
pub fn history_bars(
&self,
date: NaiveDate,
symbol: &str,
bar_count: usize,
frequency: &str,
field: &str,
include_now: bool,
) -> Vec<f64> {
self.history_bars_at(date, None, symbol, bar_count, frequency, field, include_now)
}
pub fn history_bars_at(
&self,
date: NaiveDate,
active_datetime: Option<NaiveDateTime>,
symbol: &str,
bar_count: usize,
frequency: &str,
field: &str,
include_now: bool,
) -> Vec<f64> {
if bar_count == 0 {
return Vec::new();
}
match normalize_history_frequency(frequency).as_deref() {
Some("1d") => self.history_daily_values(date, symbol, bar_count, field, include_now),
Some("1m") | Some("tick") => self.history_intraday_values(
date,
active_datetime,
symbol,
bar_count,
field,
include_now,
),
_ => Vec::new(),
}
}
pub fn history_daily_snapshots(
&self,
date: NaiveDate,
symbol: &str,
bar_count: usize,
include_now: bool,
) -> Vec<DailyMarketSnapshot> {
if bar_count == 0 {
return Vec::new();
}
let mut snapshots = self
.market_by_date
.iter()
.filter(|(day, _)| {
if include_now {
**day <= date
} else {
**day < date
}
})
.flat_map(|(_, rows)| rows.iter())
.filter(|row| row.symbol == symbol)
.cloned()
.collect::<Vec<_>>();
snapshots.sort_by_key(|row| row.date);
take_last(snapshots, bar_count)
}
pub fn history_intraday_quotes(
&self,
date: NaiveDate,
symbol: &str,
bar_count: usize,
include_now: bool,
) -> Vec<IntradayExecutionQuote> {
self.history_intraday_quotes_at(date, None, symbol, bar_count, include_now)
}
pub fn history_intraday_quotes_at(
&self,
date: NaiveDate,
active_datetime: Option<NaiveDateTime>,
symbol: &str,
bar_count: usize,
include_now: bool,
) -> Vec<IntradayExecutionQuote> {
if bar_count == 0 {
return Vec::new();
}
let mut quotes = self
.execution_quotes_index
.iter()
.filter(|((_, quote_symbol), _)| quote_symbol == symbol)
.flat_map(|(_, rows)| rows.iter())
.filter(|quote| intraday_quote_visible(quote, date, active_datetime, include_now))
.cloned()
.collect::<Vec<_>>();
quotes.sort_by_key(|quote| quote.timestamp);
take_last(quotes, bar_count)
}
pub fn trading_dates(&self, start: NaiveDate, end: NaiveDate) -> Vec<NaiveDate> {
self.calendar.trading_dates(start, end)
}
pub fn previous_trading_date(&self, date: NaiveDate, n: usize) -> Option<NaiveDate> {
self.calendar.previous_trading_date(date, n)
}
pub fn next_trading_date(&self, date: NaiveDate, n: usize) -> Option<NaiveDate> {
self.calendar.next_trading_date(date, n)
}
pub fn price(&self, date: NaiveDate, symbol: &str, field: PriceField) -> Option<f64> {
let snapshot = self.market(date, symbol)?;
Some(snapshot.price(field))
@@ -900,6 +1018,35 @@ impl DataSet {
.unwrap_or_default()
}
fn history_daily_values(
&self,
date: NaiveDate,
symbol: &str,
bar_count: usize,
field: &str,
include_now: bool,
) -> Vec<f64> {
self.history_daily_snapshots(date, symbol, bar_count, include_now)
.into_iter()
.filter_map(|row| daily_market_numeric_value(&row, field))
.collect()
}
fn history_intraday_values(
&self,
date: NaiveDate,
active_datetime: Option<NaiveDateTime>,
symbol: &str,
bar_count: usize,
field: &str,
include_now: bool,
) -> Vec<f64> {
self.history_intraday_quotes_at(date, active_datetime, symbol, bar_count, include_now)
.into_iter()
.filter_map(|row| intraday_quote_numeric_value(&row, field))
.collect()
}
pub fn market_decision_close(&self, date: NaiveDate, symbol: &str) -> Option<f64> {
self.market_series_by_symbol
.get(symbol)
@@ -1170,6 +1317,88 @@ fn factor_numeric_value(snapshot: &DailyFactorSnapshot, field: &str) -> Option<f
}
}
fn daily_market_numeric_value(snapshot: &DailyMarketSnapshot, field: &str) -> Option<f64> {
match normalize_field(field).as_str() {
"day_open" | "dayopen" => Some(snapshot.day_open),
"open" => Some(snapshot.open),
"high" => Some(snapshot.high),
"low" => Some(snapshot.low),
"close" | "price" => Some(snapshot.close),
"last" | "last_price" => Some(snapshot.last_price),
"prev_close" | "pre_close" => Some(snapshot.prev_close),
"volume" => Some(snapshot.volume as f64),
"tick_volume" => Some(snapshot.tick_volume as f64),
"bid1" => Some(snapshot.bid1),
"ask1" => Some(snapshot.ask1),
"bid1_volume" => Some(snapshot.bid1_volume as f64),
"ask1_volume" => Some(snapshot.ask1_volume as f64),
"upper_limit" => Some(snapshot.upper_limit),
"lower_limit" => Some(snapshot.lower_limit),
"price_tick" => Some(snapshot.price_tick),
_ => None,
}
}
fn intraday_quote_numeric_value(snapshot: &IntradayExecutionQuote, field: &str) -> Option<f64> {
match normalize_field(field).as_str() {
"last" | "last_price" | "close" | "price" => Some(snapshot.last_price),
"bid1" => Some(snapshot.bid1),
"ask1" => Some(snapshot.ask1),
"bid1_volume" => Some(snapshot.bid1_volume as f64),
"ask1_volume" => Some(snapshot.ask1_volume as f64),
"volume" | "volume_delta" => Some(snapshot.volume_delta as f64),
"amount" | "amount_delta" | "total_turnover" => Some(snapshot.amount_delta),
_ => None,
}
}
fn intraday_quote_visible(
quote: &IntradayExecutionQuote,
date: NaiveDate,
active_datetime: Option<NaiveDateTime>,
include_now: bool,
) -> bool {
if quote.date < date {
return true;
}
if quote.date > date {
return false;
}
let Some(active_datetime) = active_datetime.filter(|value| value.date() == date) else {
return include_now;
};
if include_now {
quote.timestamp <= active_datetime
} else {
quote.timestamp < active_datetime
}
}
fn normalize_field(field: &str) -> String {
field
.trim()
.trim_matches('"')
.trim_matches('\'')
.to_ascii_lowercase()
}
fn normalize_history_frequency(frequency: &str) -> Option<String> {
let normalized = normalize_field(frequency);
match normalized.as_str() {
"1d" | "d" | "day" | "daily" => Some("1d".to_string()),
"1m" | "m" | "minute" | "min" => Some("1m".to_string()),
"tick" | "t" => Some("tick".to_string()),
_ => None,
}
}
fn take_last<T>(mut rows: Vec<T>, count: usize) -> Vec<T> {
if rows.len() <= count {
return rows;
}
rows.split_off(rows.len() - count)
}
fn read_candidates(path: &Path) -> Result<Vec<CandidateEligibility>, DataSetError> {
let rows = read_rows(path)?;
let mut snapshots = Vec::new();