Skip to main content

qs_backtest/
strategy.rs

1//! Historical strategy contracts and the legacy action-producing strategy trait.
2//!
3//! The validated descriptor, series requirements, decision records, and retention values are additive foundations for FutureQuote historical strategies. The existing [`Strategy`] trait remains the legacy action-producing API.
4
5pub mod analysis;
6pub mod annotation;
7pub mod config;
8pub mod configured;
9pub mod context;
10pub mod domain;
11pub mod experiment;
12pub mod feedback;
13pub mod journal;
14pub mod replay;
15pub mod runtime;
16pub mod series;
17
18pub use analysis::{
19    AnalysisBoundary, AnalysisBoundaryOutput, AnalysisContext, AnalysisError, AnalysisPipeline,
20    ConfirmedPivotAnalyzer, HistoricalAnalyzer, HistoricalObservationView, MAX_ANALYZERS,
21    MAX_OBSERVATION_SOURCE_SERIES, MAX_OBSERVATIONS_PER_BOUNDARY, MAX_PIVOT_SIDE_BARS,
22    MAX_RETAINED_OBSERVATIONS, MAX_ZONE_ID_BYTES, MomentumState, ObservationOrigin,
23    ObservationSelection, ObservationStore, ObservationStoreLimits, ObservationWindow, PivotConfig,
24    PriceZone, RejectionPattern, StrategyObservation, StrategyObservationDraft,
25    StrategyObservationValue, SwingKind, SwingPoint, ZoneId, ZoneSide, ZoneSource, ZoneState,
26};
27pub use annotation::{
28    AnnotationError, AnnotationId, AnnotationLimits, AnnotationTimeline, AnnotationUse,
29    MAX_ANNOTATION_ID_BYTES, MAX_ANNOTATION_NOTE_BYTES, MAX_ANNOTATIONS, StrategyAnnotation,
30};
31pub use config::{
32    MAX_DECISION_RECORDS, MAX_REASON_BYTES, MAX_SERIES_ID_BYTES, MAX_SIGNALS_PER_CALLBACK,
33    MAX_WARMUP_BARS, PriceBasis, SeriesId, StrategyConfigError, StrategyRetentionLimits, Timeframe,
34    WarmupRequirement,
35};
36pub use configured::{
37    BacktestConfiguredStrategyAdapter, ConfiguredHistoricalBindings, ConfiguredNamedInputBinding,
38    ConfiguredSourceBinding, ConfiguredStrategyAdapterBuildError, ConfiguredStrategyAdapterError,
39    ConfiguredStrategyAdapterPreflightError, HistoricalNamedInputProjector,
40    HistoricalVolumeProjection, NamedInputProjectionContext, NamedInputProjectionError,
41    ProjectedNamedInput,
42};
43pub use context::StrategyContext;
44pub use domain::{
45    MAX_DECISION_LATENCY_MS, MAX_INSTRUMENT_BYTES, MAX_STRATEGY_ID_BYTES,
46    MAX_STRATEGY_REVISION_BYTES, MAX_STRATEGY_TITLE_BYTES, MAX_TRADE_ID_BYTES, SeriesRequirement,
47    StrategyBacktestResult, StrategyDecisionKind, StrategyDecisionOutput, StrategyDecisionRecord,
48    StrategyDecisionRecorder, StrategyDecisionRetention, StrategyDescriptor, StrategyDomainError,
49    StrategyId, StrategyRequirements, StrategyResearchOutput,
50};
51pub use experiment::{
52    StrategyComparisonMetrics, StrategyComparisonSnapshot, StrategyExperimentComparison,
53    StrategyExperimentError,
54};
55pub use feedback::{StrategyFeedback, StrategyFeedbackEvent};
56pub use journal::{
57    JournalKind, MAX_CHART_REF_BYTES, MAX_EXPERIMENT_LABEL_BYTES, MAX_JOURNAL_PER_CALLBACK,
58    MAX_JOURNAL_REASON_BYTES, MAX_JOURNAL_RECORDS, MAX_JOURNAL_VALUE_KEY_BYTES, MAX_JOURNAL_VALUES,
59    StrategyJournalDraft, StrategyJournalError, StrategyJournalOutput, StrategyJournalRecord,
60    StrategyJournalRecorder, StrategyJournalRetention, StrategyResearchLimits,
61};
62pub use replay::{StrategyReplayError, StrategyReplayInputError};
63pub use runtime::{
64    HistoricalStrategy, StrategyDecisionDraft, StrategyEvent, StrategyOutput, StrategyRuntimeError,
65};
66pub use series::{
67    BarSeriesSpec, BarWindow, ClosedBar, HistoricalSeriesView, MAX_RETAINED_BARS,
68    MissingIntervalPolicy, MultiTimeframeSeries, SeriesError, SeriesViewError, SeriesWarmupState,
69};
70
71use qs_core::types::Action;
72
73use crate::data_feed::MarketEvent;
74
75/// A trading strategy that reacts to market events.
76///
77/// The backtest runner calls [`on_event`](Strategy::on_event) for every
78/// market event (tick or bar) in the data feed.  The strategy inspects the
79/// event and returns zero or more [`Action`]s that the engine will process.
80///
81/// # Example
82///
83/// ```ignore
84/// use qs_backtest::{Strategy, MarketEvent};
85/// use qs_core::types::{Action, OrderType, Side};
86///
87/// struct BuyAndHold { entered: bool }
88///
89/// impl Strategy for BuyAndHold {
90///     fn on_event(&mut self, event: &MarketEvent) -> Vec<Action> {
91///         if self.entered { return vec![]; }
92///         if let MarketEvent::Tick { symbol, ask, .. } = event {
93///             self.entered = true;
94///             return vec![Action::Open {
95///                 symbol: symbol.clone(),
96///                 side: Side::Buy,
97///                 order_type: OrderType::Market,
98///                 price: Some(*ask),
99///                 size: 1.0,
100///                 stoploss: None,
101///                 targets: vec![],
102///                 rules: vec![],
103///                 group: None,
104///                 trade_id: None,
105///             }];
106///         }
107///         vec![]
108///     }
109///
110///     fn on_finished(&mut self) -> Vec<Action> {
111///         vec![Action::CloseAll]
112///     }
113/// }
114/// ```
115pub trait Strategy {
116    /// Called for every market event in the data feed.
117    ///
118    /// Return an empty `Vec` to take no action on this event.
119    fn on_event(&mut self, event: &MarketEvent) -> Vec<Action>;
120
121    /// Called once after the data feed is exhausted.
122    ///
123    /// Use this to emit final actions such as closing all remaining
124    /// positions.  The default implementation does nothing.
125    fn on_finished(&mut self) -> Vec<Action> {
126        vec![]
127    }
128}