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 calendar;
8pub mod config;
9pub mod configured;
10pub mod context;
11pub mod domain;
12pub mod experiment;
13pub mod feedback;
14pub mod geometry;
15pub mod journal;
16pub mod portfolio;
17pub mod replay;
18pub mod runtime;
19pub mod series;
20pub mod session;
21
22pub use analysis::{
23    AnalysisBoundary, AnalysisBoundaryOutput, AnalysisContext, AnalysisError, AnalysisPipeline,
24    ConfirmedPivotAnalyzer, HistoricalAnalyzer, HistoricalObservationView, MAX_ANALYZERS,
25    MAX_OBSERVATION_SOURCE_SERIES, MAX_OBSERVATIONS_PER_BOUNDARY, MAX_PIVOT_SIDE_BARS,
26    MAX_RETAINED_OBSERVATIONS, MAX_ZONE_ID_BYTES, MomentumState, ObservationOrigin,
27    ObservationSelection, ObservationStore, ObservationStoreLimits, ObservationWindow, PivotConfig,
28    PriceZone, RejectionPattern, StrategyObservation, StrategyObservationDraft,
29    StrategyObservationValue, SwingKind, SwingPoint, ZoneId, ZoneSide, ZoneSource, ZoneState,
30};
31pub use annotation::{
32    AnnotationError, AnnotationId, AnnotationLimits, AnnotationTimeline, AnnotationUse,
33    MAX_ANNOTATION_ID_BYTES, MAX_ANNOTATION_NOTE_BYTES, MAX_ANNOTATIONS, StrategyAnnotation,
34};
35pub use calendar::{
36    CalendarAdmissionLimits, CalendarBar, CalendarError, CalendarFeatureKind,
37    CalendarFeatureProjector, CalendarInputSpec, CalendarTimeBasis,
38    ConfiguredCalendarFeatureProjector, ConfiguredCalendarInput, ConfiguredTradingCalendar,
39    DEFAULT_CALENDAR_SESSION_ID, IanaTradingCalendar, LocalMarketIntervalSpec, MarketScheduleSpec,
40    NamedSessionSpec, OpeningRange, ResolvedSession, ResolvedSessionOccurrence, ResolvedTradingDay,
41    SessionOccurrenceId, SessionScheduleSpec, SessionSpanSpec, TradingCalendarSpec,
42    WeeklyMarketIntervalSpec,
43};
44pub use config::{
45    MAX_DECISION_RECORDS, MAX_REASON_BYTES, MAX_SERIES_ID_BYTES, MAX_SIGNALS_PER_CALLBACK,
46    MAX_WARMUP_BARS, PriceBasis, SeriesId, StrategyConfigError, StrategyRetentionLimits, Timeframe,
47    WarmupRequirement,
48};
49pub use configured::{
50    BacktestConfiguredStrategyAdapter, ConfiguredEntryProfileError, ConfiguredHistoricalBindings,
51    ConfiguredNamedInputBinding, ConfiguredSourceBinding, ConfiguredStrategyAdapterBuildError,
52    ConfiguredStrategyAdapterError, ConfiguredStrategyAdapterPreflightError,
53    ConfirmedSwingFactKind, ConfirmedSwingFactProjector, HistoricalNamedInputProjector,
54    HistoricalVolumeProjection, NamedInputProjectionContext, NamedInputProjectionError,
55    ProjectedNamedInput, SourceBarFactKind, SourceBarFactProjector,
56};
57pub use context::StrategyContext;
58pub use domain::{
59    MAX_DECISION_LATENCY_MS, MAX_INSTRUMENT_BYTES, MAX_STRATEGY_ID_BYTES,
60    MAX_STRATEGY_REVISION_BYTES, MAX_STRATEGY_TITLE_BYTES, MAX_TRADE_ID_BYTES, SeriesRequirement,
61    StrategyBacktestResult, StrategyDecisionKind, StrategyDecisionOutput, StrategyDecisionRecord,
62    StrategyDecisionRecorder, StrategyDecisionRetention, StrategyDescriptor, StrategyDomainError,
63    StrategyId, StrategyRequirements, StrategyResearchOutput,
64};
65pub use experiment::{
66    StrategyComparisonMetrics, StrategyComparisonSnapshot, StrategyExperimentComparison,
67    StrategyExperimentError,
68};
69pub use feedback::{StrategyFeedback, StrategyFeedbackEvent};
70pub use geometry::{SeriesGeometry, SeriesGeometryError};
71pub use journal::{
72    JournalKind, MAX_CHART_REF_BYTES, MAX_EXPERIMENT_LABEL_BYTES, MAX_JOURNAL_PER_CALLBACK,
73    MAX_JOURNAL_REASON_BYTES, MAX_JOURNAL_RECORDS, MAX_JOURNAL_VALUE_KEY_BYTES, MAX_JOURNAL_VALUES,
74    StrategyJournalDraft, StrategyJournalError, StrategyJournalOutput, StrategyJournalRecord,
75    StrategyJournalRecorder, StrategyJournalRetention, StrategyResearchLimits,
76};
77pub use portfolio::{
78    ConfiguredInstance, DirectPortfolioInstance, INSTANCE_POSITION_TAG, MAX_PORTFOLIO_INSTANCES,
79    MixedPortfolioBacktestResult, MixedPortfolioReplayError, PortfolioBacktestResult,
80    PortfolioInstanceOutput, PortfolioReplayError, SupervisorEvent, SupervisorHaltAction,
81    SupervisorOutput,
82};
83pub use replay::{StrategyReplayError, StrategyReplayInputError};
84pub use runtime::{
85    HistoricalStrategy, StrategyDecisionDraft, StrategyEvent, StrategyOutput, StrategyRuntimeError,
86};
87pub use series::{
88    BarSeriesSpec, BarWindow, ClosedBar, HistoricalSeriesView, MAX_RETAINED_BARS,
89    MissingIntervalPolicy, MultiTimeframeSeries, SeriesError, SeriesViewError, SeriesWarmupState,
90};
91pub use session::{FixedUtcSessionError, FixedUtcSessionProjector, MAX_SESSION_WINDOWS};
92
93use qs_core::types::Action;
94
95use crate::data_feed::MarketEvent;
96
97/// A trading strategy that reacts to market events.
98///
99/// The backtest runner calls [`on_event`](Strategy::on_event) for every
100/// market event (tick or bar) in the data feed.  The strategy inspects the
101/// event and returns zero or more [`Action`]s that the engine will process.
102///
103/// # Example
104///
105/// ```ignore
106/// use qs_backtest::{Strategy, MarketEvent};
107/// use qs_core::types::{Action, OrderType, Side};
108///
109/// struct BuyAndHold { entered: bool }
110///
111/// impl Strategy for BuyAndHold {
112///     fn on_event(&mut self, event: &MarketEvent) -> Vec<Action> {
113///         if self.entered { return vec![]; }
114///         if let MarketEvent::Tick { symbol, ask, .. } = event {
115///             self.entered = true;
116///             return vec![Action::Open {
117///                 symbol: symbol.clone(),
118///                 side: Side::Buy,
119///                 order_type: OrderType::Market,
120///                 price: Some(*ask),
121///                 size: 1.0,
122///                 stoploss: None,
123///                 targets: vec![],
124///                 rules: vec![],
125///                 group: None,
126///                 trade_id: None,
127///             }];
128///         }
129///         vec![]
130///     }
131///
132///     fn on_finished(&mut self) -> Vec<Action> {
133///         vec![Action::CloseAll]
134///     }
135/// }
136/// ```
137pub trait Strategy {
138    /// Called for every market event in the data feed.
139    ///
140    /// Return an empty `Vec` to take no action on this event.
141    fn on_event(&mut self, event: &MarketEvent) -> Vec<Action>;
142
143    /// Called once after the data feed is exhausted.
144    ///
145    /// Use this to emit final actions such as closing all remaining
146    /// positions.  The default implementation does nothing.
147    fn on_finished(&mut self) -> Vec<Action> {
148        vec![]
149    }
150}