use chrono::NaiveDateTime;
use super::{
AnalysisError, BarSeriesSpec, ConfiguredEntryProfileError,
ConfiguredStrategyAdapterPreflightError, SeriesError, SeriesViewError, StrategyRequirements,
StrategyRuntimeError,
};
#[derive(Debug, thiserror::Error)]
pub enum StrategyReplayError<FeedError, StrategyError> {
#[error("market-data stream failed: {0}")]
Feed(FeedError),
#[error("strategy replay was cancelled")]
Cancelled,
#[error("strategy replay input is invalid: {0}")]
Input(#[from] StrategyReplayInputError),
#[error("historical series failed: {0}")]
Series(#[from] SeriesError),
#[error("historical series view failed: {0}")]
SeriesView(#[from] SeriesViewError),
#[error("historical analysis failed: {0}")]
Analysis(#[from] AnalysisError),
#[error("strategy callback failed: {0}")]
Strategy(StrategyError),
#[error("strategy output failed validation: {0}")]
Runtime(#[from] StrategyRuntimeError),
#[error("strategy emitted economic signals during warmup at {timestamp}")]
WarmupSignals { timestamp: NaiveDateTime },
#[error("generated signal {signal_index} is invalid: {reason}")]
InvalidGeneratedSignal { signal_index: usize, reason: String },
#[error(
"strategy requires tick execution but received a primary bar for {symbol} at {timestamp}"
)]
TickExecutionRequired {
symbol: String,
timestamp: NaiveDateTime,
},
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum StrategyReplayInputError {
#[error("FutureQuote configuration is invalid: {0}")]
FutureQuote(String),
#[error("management profile is invalid: {0}")]
ManagementProfile(String),
#[error("configured strategy entry cannot be routed to a management profile: {0}")]
ConfiguredEntryProfile(#[from] ConfiguredEntryProfileError),
#[error("configured historical adapter is incompatible with replay limits: {0}")]
ConfiguredAdapter(#[from] ConfiguredStrategyAdapterPreflightError),
#[error("series '{series_id}' is required but was not supplied")]
MissingSeries { series_id: String },
#[error("series '{series_id}' does not match the strategy requirement")]
ConflictingSeries { series_id: String },
#[error("series '{series_id}' was supplied but is not declared by the strategy")]
UndeclaredSeries { series_id: String },
}
pub(crate) fn validate_series_specs(
requirements: &StrategyRequirements,
specs: &[BarSeriesSpec],
) -> Result<(), StrategyReplayInputError> {
for requirement in requirements.series() {
let Some(spec) = specs
.iter()
.find(|spec| spec.requirement().id() == requirement.id())
else {
return Err(StrategyReplayInputError::MissingSeries {
series_id: requirement.id().to_string(),
});
};
if spec.requirement() != requirement {
return Err(StrategyReplayInputError::ConflictingSeries {
series_id: requirement.id().to_string(),
});
}
}
for spec in specs {
if !requirements
.series()
.iter()
.any(|requirement| requirement.id() == spec.requirement().id())
{
return Err(StrategyReplayInputError::UndeclaredSeries {
series_id: spec.requirement().id().to_string(),
});
}
}
Ok(())
}