Skip to main content

egui_charts/strategies/library/
mod.rs

1//! A name-based catalogue of strategies with editable study inputs.
2
3use std::collections::HashMap;
4
5use super::Strategy;
6use crate::studies::{InputKind, InputValue, StudyInput};
7
8mod price_action;
9#[cfg(feature = "studies")]
10mod studies;
11#[cfg(test)]
12mod tests;
13
14type StrategyConstructor = Box<dyn Fn() -> Box<dyn Strategy> + Send + Sync>;
15
16/// Creates built-in and host strategies by name.
17///
18/// Ten price-action strategies need only `strategies`; twelve more use the public study
19/// calculations when `studies` is enabled. Inputs are available through
20/// [`Strategy::study_inputs`] and edited through [`Strategy::set_input`]. Entry size and
21/// pyramiding come from [`super::BacktestSettings`]. Built-ins ignore fill callbacks and decide
22/// at bar close, or on forming-bar updates when tick recalculation is enabled.
23///
24/// ```
25/// use egui_charts::strategies::StrategyFactory;
26/// use egui_charts::studies::InputValue;
27///
28/// let factory = StrategyFactory::new();
29/// let mut strategy = factory.create("Consecutive Up/Down").unwrap();
30/// strategy.set_input("length", InputValue::Length(3)).unwrap();
31/// assert!(!strategy.study_inputs().is_empty());
32/// ```
33pub struct StrategyFactory {
34    constructors: HashMap<String, StrategyConstructor>,
35}
36
37impl StrategyFactory {
38    /// Creates a catalogue with every built-in enabled by the current features.
39    pub fn new() -> Self {
40        let mut factory = Self {
41            constructors: HashMap::new(),
42        };
43        price_action::register(&mut factory);
44        #[cfg(feature = "studies")]
45        studies::register(&mut factory);
46        factory
47    }
48
49    /// Registers a host strategy, replacing an entry with the same name.
50    pub fn register<F>(&mut self, name: impl Into<String>, constructor: F)
51    where
52        F: Fn() -> Box<dyn Strategy> + Send + Sync + 'static,
53    {
54        self.constructors.insert(name.into(), Box::new(constructor));
55    }
56
57    /// Creates an independent instance, or `None` for an unknown name.
58    pub fn create(&self, name: &str) -> Option<Box<dyn Strategy>> {
59        self.constructors.get(name).map(|constructor| constructor())
60    }
61
62    /// Lists names in alphabetical order, including host registrations.
63    pub fn list(&self) -> Vec<&str> {
64        let mut names: Vec<_> = self.constructors.keys().map(String::as_str).collect();
65        names.sort_unstable();
66        names
67    }
68}
69
70impl Default for StrategyFactory {
71    fn default() -> Self {
72        Self::new()
73    }
74}
75
76fn length_input(id: &str, label: &str, value: usize) -> StudyInput {
77    StudyInput::new(
78        id,
79        label,
80        InputValue::Length(value),
81        InputKind::Length(1, usize::MAX),
82    )
83}
84
85fn number_input(id: &str, label: &str, value: f64, min: f64, max: f64) -> StudyInput {
86    StudyInput::new(
87        id,
88        label,
89        InputValue::Number(value),
90        InputKind::Number(min, max),
91    )
92}
93
94fn set_input(inputs: &mut [StudyInput], id: &str, value: InputValue) -> Result<(), &'static str> {
95    let input = inputs
96        .iter_mut()
97        .find(|input| input.id == id)
98        .ok_or("unknown strategy input")?;
99    if !input.accepts(&value) {
100        return Err("strategy input is outside its accepted values");
101    }
102    input.value = value;
103    Ok(())
104}
105
106fn length(inputs: &[StudyInput], id: &str) -> usize {
107    match inputs
108        .iter()
109        .find(|input| input.id == id)
110        .map(|input| &input.value)
111    {
112        Some(InputValue::Length(value)) => *value,
113        _ => unreachable!("declared length input"),
114    }
115}
116
117fn number(inputs: &[StudyInput], id: &str) -> f64 {
118    match inputs
119        .iter()
120        .find(|input| input.id == id)
121        .map(|input| &input.value)
122    {
123        Some(InputValue::Number(value)) => *value,
124        _ => unreachable!("declared numeric input"),
125    }
126}