egui_charts/studies/builtin/
time_price_opportunity.rs1use crate::model::{Bar, Instrument, TPOConfig, TPODisplayMode};
4use crate::studies::{Indicator, IndicatorValues, InputKind, InputValue, StudyInput};
5use egui::Color32;
6
7const DISPLAYS: [TPODisplayMode; 3] = [
9 TPODisplayMode::Letters,
10 TPODisplayMode::Blocks,
11 TPODisplayMode::Both,
12];
13
14fn display_choice(mode: TPODisplayMode) -> InputValue {
16 InputValue::Choice(DISPLAYS.iter().position(|d| *d == mode).unwrap())
17}
18
19#[derive(Clone)]
27pub struct TimePriceOpportunity {
28 row_ticks: usize,
29 instrument_tick: Option<f64>,
30 config: TPOConfig,
31 values: IndicatorValues,
32 visible: bool,
33}
34
35impl TimePriceOpportunity {
36 pub fn new(period_minutes: u32) -> Self {
38 Self {
39 row_ticks: 0,
40 instrument_tick: None,
41 config: TPOConfig {
42 tick_size: 0.0,
44 period_minutes: period_minutes.max(1),
45 ..Default::default()
46 },
47 values: IndicatorValues::new(),
48 visible: true,
49 }
50 }
51
52 fn update_tick_size(&mut self) {
53 self.config.tick_size = self
54 .instrument_tick
55 .map_or(0.0, |tick| tick * self.row_ticks as f64);
56 self.config.instrument_tick = self.instrument_tick;
57 }
58}
59
60impl Default for TimePriceOpportunity {
61 fn default() -> Self {
62 Self::new(30)
63 }
64}
65
66impl Indicator for TimePriceOpportunity {
67 fn name(&self) -> &str {
68 "Time Price Opportunity"
69 }
70 fn desc(&self) -> &str {
71 "Market Profile of each session beside its bars"
72 }
73 fn inputs(&self) -> Vec<StudyInput> {
74 let defaults = TPOConfig::default();
75 vec![
76 StudyInput::new(
77 "period",
78 "Period (minutes)",
79 InputValue::Length(self.config.period_minutes as usize),
80 InputKind::Length(1, 1440),
81 )
82 .with_default(InputValue::Length(30)),
83 StudyInput::new(
84 "row_ticks",
85 "Ticks per row (0 for auto)",
86 InputValue::Length(self.row_ticks),
87 InputKind::Length(0, 100_000),
88 )
89 .with_default(InputValue::Length(0)),
90 StudyInput::new(
91 "value_area",
92 "Value area (%)",
93 InputValue::Number(self.config.value_area_pct * 100.0),
94 InputKind::Number(1.0, 100.0),
95 )
96 .with_default(InputValue::Number(defaults.value_area_pct * 100.0)),
97 StudyInput::new(
98 "split_sessions",
99 "Split extended hours",
100 InputValue::Toggle(self.config.split_sessions),
101 InputKind::Toggle,
102 )
103 .with_default(InputValue::Toggle(defaults.split_sessions)),
104 StudyInput::new(
105 "single_prints",
106 "Single prints",
107 InputValue::Toggle(self.config.show_single_prints),
108 InputKind::Toggle,
109 )
110 .with_default(InputValue::Toggle(defaults.show_single_prints)),
111 StudyInput::new(
112 "display",
113 "Display",
114 display_choice(self.config.display_mode),
115 InputKind::Choice(vec![
116 "Letters".into(),
117 "Blocks".into(),
118 "Letters and blocks".into(),
119 ]),
120 )
121 .with_default(display_choice(defaults.display_mode)),
122 StudyInput::new(
123 "sessions",
124 "Sessions",
125 InputValue::Choice(usize::from(self.config.latest_session_only)),
126 InputKind::Choice(vec!["Visible".into(), "Latest".into()]),
127 )
128 .with_default(InputValue::Choice(usize::from(
129 defaults.latest_session_only,
130 ))),
131 ]
132 }
133 fn set_input(&mut self, id: &str, value: InputValue) -> Result<(), crate::studies::StudyError> {
134 match (id, value) {
135 ("period", InputValue::Length(minutes @ 1..=1440)) => {
136 self.config.period_minutes = minutes as u32
137 }
138 ("row_ticks", InputValue::Length(ticks @ 0..=100_000)) => {
139 self.row_ticks = ticks;
140 self.update_tick_size();
141 }
142 ("value_area", InputValue::Number(percent)) if (1.0..=100.0).contains(&percent) => {
143 self.config.value_area_pct = percent / 100.0
144 }
145 ("split_sessions", InputValue::Toggle(split)) => self.config.split_sessions = split,
146 ("single_prints", InputValue::Toggle(show)) => self.config.show_single_prints = show,
147 ("display", InputValue::Choice(choice @ 0..=2)) => {
148 self.config.display_mode = DISPLAYS[choice]
149 }
150 ("sessions", InputValue::Choice(choice @ 0..=1)) => {
151 self.config.latest_session_only = choice == 1
152 }
153 _ => return Err(crate::studies::StudyError::rejected(&self.inputs(), id)),
154 }
155 Ok(())
156 }
157 fn set_instrument(&mut self, instrument: Option<&Instrument>) -> bool {
158 self.instrument_tick = instrument.and_then(|instrument| instrument.tick_size);
159 self.update_tick_size();
160 false
161 }
162 fn tpo(&self) -> Option<&TPOConfig> {
163 Some(&self.config)
164 }
165 fn calculate(&mut self, data: &[Bar]) {
166 self.values = IndicatorValues::missing(data.len());
167 }
168
169 fn update(&mut self, data: &[Bar], _from: usize) {
170 self.calculate(data);
171 }
172 fn is_incremental(&self) -> bool {
173 true
174 }
175
176 fn values(&self) -> &IndicatorValues {
177 &self.values
178 }
179 fn line_names(&self) -> Vec<String> {
180 Vec::new()
181 }
182 fn colors(&self) -> Vec<Color32> {
183 Vec::new()
184 }
185 fn set_colors(&mut self, _colors: Vec<Color32>) {}
186 fn is_visible(&self) -> bool {
187 self.visible
188 }
189 fn set_visible(&mut self, visible: bool) {
190 self.visible = visible;
191 }
192 fn clone_box(&self) -> Box<dyn Indicator> {
193 Box::new(self.clone())
194 }
195}