1use std::collections::{HashMap, HashSet};
4use std::sync::Arc;
5
6use chrono::{DateTime, Utc};
7
8use super::{AlertData, AlertEngine, AlertFeed};
9use crate::data::{Datafeed, FeedError, HistoryRequest};
10use crate::model::{
11 Alert, AlertId, AlertInputs, AlertSource, Bar, BarClock, Instrument, Timeframe,
12};
13use crate::studies::{
14 ConfiguredStudy, Indicator, IndicatorFactory, StudyData, StudyDataRequest, StudySource,
15};
16
17pub struct AlertEvaluator {
21 feed: Arc<dyn Datafeed>,
22 runs: HashMap<(AlertId, String), Evaluation>,
23 feeds: HashMap<Subject, AlertFeed>,
24 #[cfg(feature = "ui")]
25 storage: Option<Arc<dyn super::Storage>>,
26}
27
28#[derive(Clone, PartialEq, Eq, Hash)]
29struct Subject {
30 symbol: String,
31 timeframe: Timeframe,
32 metric: Option<String>,
33 timezone: Option<crate::model::TimezoneId>,
34 extended_hours: bool,
35 conversion: Option<String>,
36 count: usize,
37 start: Option<DateTime<Utc>>,
38 owner: Option<(AlertId, String)>,
40 calendar: Option<crate::model::AlertCalendar>,
41}
42
43struct Evaluation {
44 alert: Alert,
45 inputs: AlertInputs,
46 studies: Vec<(usize, Box<dyn Indicator>)>,
47 error: Option<String>,
48 initialized: bool,
49 start: Option<DateTime<Utc>>,
50}
51
52impl Evaluation {
53 fn new(alert: &Alert, factory: &IndicatorFactory) -> Self {
54 let inputs = alert.inputs.as_deref().cloned().unwrap_or_default();
55 let mut required: HashSet<_> = alert
56 .sources()
57 .filter_map(|source| match source {
58 AlertSource::Study { id, .. } => id.parse::<usize>().ok(),
59 _ => None,
60 })
61 .collect();
62 loop {
63 let before = required.len();
64 for study in &inputs.studies {
65 if !required.contains(&study.id) {
66 continue;
67 }
68 for (_, input) in &study.inputs {
69 if let crate::model::InputValue::Source(StudySource::Study { id, .. }) = input {
70 required.insert(*id);
71 }
72 }
73 }
74 if required.len() == before {
75 break;
76 }
77 }
78 let mut result = Self {
79 alert: alert.clone(),
80 start: inputs.start,
81 inputs,
82 studies: Vec::new(),
83 error: None,
84 initialized: false,
85 };
86 for id in required {
87 let restored = (|| {
88 let saved = result
89 .inputs
90 .studies
91 .iter()
92 .find(|study| study.id == id)
93 .ok_or_else(|| format!("Alert has no saved inputs for study {id}"))?;
94 let study = factory
95 .create(&saved.name)
96 .ok_or_else(|| format!("Unknown alert study: {}", saved.name))?;
97 let mut study = ConfiguredStudy::new(study);
98 for (id, value) in &saved.inputs {
99 study
100 .set_input(id, value.clone())
101 .map_err(|error| error.to_string())?;
102 }
103 study.set_timeframe(alert.timeframe.unwrap_or_default());
104 Ok::<_, String>((id, Box::new(study) as Box<dyn Indicator>))
105 })();
106 match restored {
107 Ok(study) => result.studies.push(study),
108 Err(error) => result.error = Some(error),
109 }
110 }
111 result
112 }
113
114 fn subject(&self, symbol: &str, request: Option<&StudyDataRequest>) -> Subject {
115 Subject {
116 symbol: request
117 .filter(|request| !request.symbol.is_empty())
118 .map_or(symbol, |request| &request.symbol)
119 .into(),
120 timeframe: request
121 .and_then(|request| request.timeframe)
122 .or(self.alert.timeframe)
123 .unwrap_or_default(),
124 metric: request.and_then(|request| request.metric.clone()),
125 timezone: self.inputs.timezone.clone(),
126 extended_hours: self.inputs.extended_hours,
127 conversion: self.inputs.conversion.clone(),
128 count: self.inputs.count.max(1),
129 start: self.inputs.start,
130 calendar: self.inputs.calendar.clone(),
131 owner: self
132 .studies
133 .iter()
134 .any(|(_, study)| !study.order_flow_inputs().is_empty())
135 .then(|| (self.alert.id.clone().unwrap(), symbol.to_owned())),
136 }
137 }
138
139 fn event_inputs(&self, request: Option<&StudyDataRequest>) -> Vec<crate::data::OrderFlowInput> {
140 self.studies
141 .iter()
142 .filter(|(_, study)| {
143 let requests = study.data_requests();
144 request.map_or(requests.is_empty(), |request| requests.contains(request))
145 })
146 .flat_map(|(_, study)| study.order_flow_inputs().iter().cloned())
147 .collect()
148 }
149
150 fn calculate(
151 &mut self,
152 bars: &[Bar],
153 clock: &BarClock,
154 instrument: Option<&Instrument>,
155 data: &[StudyData<'_>],
156 ) {
157 let mut pending: Vec<_> = self.studies.iter().map(|(id, _)| *id).collect();
158 while !pending.is_empty() {
159 let Some(at) = pending.iter().position(|id| {
160 let study = &self.studies.iter().find(|(key, _)| key == id).unwrap().1;
161 match study.source() {
162 Some(StudySource::Study { id, .. }) => !pending.contains(&id),
163 _ => true,
164 }
165 }) else {
166 break;
167 };
168 let id = pending.remove(at);
169 let source = self
170 .studies
171 .iter()
172 .find(|(key, _)| *key == id)
173 .unwrap()
174 .1
175 .source();
176 let values = match source {
177 Some(StudySource::Study { id, plot }) => self
178 .studies
179 .iter()
180 .find(|(key, _)| *key == id)
181 .and_then(|(_, study)| study.values().columns().get(plot))
182 .cloned(),
183 _ => None,
184 };
185 let study = &mut self
186 .studies
187 .iter_mut()
188 .find(|(key, _)| *key == id)
189 .unwrap()
190 .1;
191 study.set_timeframe(self.alert.timeframe.unwrap_or_default());
192 study.set_bar_clock(clock);
193 study.set_instrument(instrument);
194 study.set_visible_range(self.inputs.visible.map_or(0..bars.len(), |(start, end)| {
195 bars.partition_point(|bar| bar.time < start)
196 ..bars.partition_point(|bar| bar.time <= end)
197 }));
198 if matches!(source, Some(StudySource::Study { .. })) {
199 study.update_source(bars, 0, values.as_deref().unwrap_or(&[]));
200 } else {
201 study.update_data(bars, 0, data);
202 }
203 }
204 }
205
206 fn requests(&self, first: DateTime<Utc>) -> Vec<(StudyDataRequest, DateTime<Utc>)> {
207 self.studies
208 .iter()
209 .flat_map(|(_, study)| {
210 study.data_requests().into_iter().map(|request| {
211 let start = study
212 .data_start(&request)
213 .map_or(first, |start| start.max(first));
214 (request, start)
215 })
216 })
217 .collect()
218 }
219}
220
221impl Subject {
222 fn open(&self, feed: Arc<dyn Datafeed>) -> AlertFeed {
223 let mut reader = AlertFeed::new(feed, self.request(), self.metric.clone());
224 reader.set_calendar(self.calendar.clone());
225 reader
226 }
227
228 fn request(&self) -> HistoryRequest {
229 let mut request = AlertFeed::history_request(&self.symbol, self.timeframe);
230 request.clock = match &self.timezone {
231 Some(zone) => request.clock.with_timezone(zone.clone()),
232 None => request.clock.with_local_timezone(),
233 };
234 request.extended_hours = self.extended_hours;
235 request.conversion = self.conversion.clone();
236 request.count = self.count.max(1000);
237 request
238 }
239}
240
241impl AlertEvaluator {
242 pub fn new(feed: Arc<dyn Datafeed>) -> Self {
244 Self {
245 feed,
246 runs: HashMap::new(),
247 feeds: HashMap::new(),
248 #[cfg(feature = "ui")]
249 storage: None,
250 }
251 }
252
253 #[cfg(feature = "ui")]
255 pub fn set_storage(&mut self, storage: Arc<dyn super::Storage>) {
256 self.storage = Some(storage);
257 }
258
259 pub fn poll(
264 &mut self,
265 engine: &AlertEngine,
266 factory: &IndicatorFactory,
267 live: &[AlertData<'_>],
268 #[cfg(feature = "ui")] ctx: &egui::Context,
269 now: DateTime<Utc>,
270 ) -> Vec<FeedError> {
271 engine.tick(now);
272 let mut armed = HashSet::new();
273 let mut errors = Vec::new();
274 for alert in engine.alerts() {
275 let Some(id) = &alert.id else {
276 continue;
277 };
278 for symbol in engine.subjects(id) {
279 let key = (id.clone(), symbol.clone());
280 armed.insert(key.clone());
281 if self.runs.get(&key).is_none_or(|run| run.alert != alert) {
282 self.feeds
284 .retain(|subject, _| subject.owner.as_ref() != Some(&key));
285 self.runs
286 .insert(key.clone(), Evaluation::new(&alert, factory));
287 }
288 let run = self.runs.get_mut(&key).unwrap();
289 if let Some(error) = run.error.take() {
290 errors.push(FeedError::InvalidData(error));
291 run.studies.clear();
292 }
293 }
294 }
295 self.runs.retain(|key, _| armed.contains(key));
296 for ((_, symbol), run) in &self.runs {
297 let subject = run.subject(symbol, None);
298 let reader = self.feeds.entry(subject.clone()).or_insert_with(|| {
299 let mut reader = subject.open(self.feed.clone());
300 if let Some(start) = subject.start {
301 reader.require_start(start);
302 }
303 reader
304 });
305 if let Some(start) = run.start {
306 reader.require_start(start);
307 }
308 reader.set_inputs(run.event_inputs(None));
309 #[cfg(feature = "ui")]
310 if run
311 .alert
312 .sources()
313 .any(|source| matches!(source, AlertSource::Drawing(_)))
314 && let Some(storage) = &self.storage
315 {
316 reader.set_storage(storage.clone());
317 }
318 }
319 for (subject, reader) in &mut self.feeds {
320 reader.follow(if subject.owner.is_none() { live } else { &[] });
321 errors.extend(reader.poll(
322 #[cfg(feature = "ui")]
323 ctx,
324 now,
325 ));
326 }
327 for ((id, symbol), run) in &mut self.runs {
328 let subject = run.subject(symbol, None);
329 let reader = &self.feeds[&subject];
330 let all = reader.bars();
331 let bars =
332 &all[all.partition_point(|bar| run.start.is_some_and(|start| bar.time < start))..];
333 let Some(first) = bars.first() else {
334 continue;
335 };
336 run.start.get_or_insert(first.time);
337 if !run.initialized {
338 run.calculate(bars, reader.clock(), reader.instrument(), &[]);
340 run.initialized = true;
341 }
342 let requests = run.requests(first.time);
343 for (request, start) in &requests {
344 let subject = run.subject(symbol, Some(request));
345 let reader = self
346 .feeds
347 .entry(subject.clone())
348 .or_insert_with(|| subject.open(self.feed.clone()));
349 reader.require_start(*start);
350 let mut inputs = run.event_inputs(Some(request));
351 if subject == run.subject(symbol, None) {
352 inputs.extend(run.event_inputs(None));
353 }
354 reader.set_inputs(inputs);
355 }
356 if requests
357 .iter()
358 .any(|(request, _)| !self.feeds[&run.subject(symbol, Some(request))].ready())
359 {
360 continue;
361 }
362 let reader = &self.feeds[&subject];
363 let all = reader.bars();
364 let bars =
365 &all[all.partition_point(|bar| run.start.is_some_and(|start| bar.time < start))..];
366 let data: Vec<_> = requests
367 .iter()
368 .map(|(request, _)| {
369 let reader = &self.feeds[&run.subject(symbol, Some(request))];
370 StudyData {
371 request: request.clone(),
372 bars: reader.bars(),
373 clock: reader.clock().clone(),
374 instrument: reader.instrument(),
375 timeframe: run.subject(symbol, Some(request)).timeframe,
376 }
377 })
378 .collect();
379 run.calculate(bars, reader.clock(), reader.instrument(), &data);
380 let reaches = run.requests(bars[0].time);
382 for (request, start) in &reaches {
383 let subject = run.subject(symbol, Some(request));
384 self.feeds
385 .entry(subject.clone())
386 .or_insert_with(|| subject.open(self.feed.clone()))
387 .require_start(*start);
388 }
389 if reaches
390 .iter()
391 .any(|(request, _)| !self.feeds[&run.subject(symbol, Some(request))].ready())
392 {
393 continue;
394 }
395 let reader = &self.feeds[&subject];
396 let all = reader.bars();
397 let from = all.partition_point(|bar| run.start.is_some_and(|start| bar.time < start));
398 let bars = &all[from..];
399 engine.update_alert(
400 id,
401 symbol,
402 subject.timeframe,
403 bars,
404 now,
405 |source, at| match source {
406 AlertSource::Study { id, output } => run
407 .studies
408 .iter()
409 .find(|(key, _)| key.to_string() == *id)
410 .and_then(|(_, study)| study.plot_value(*output, at)),
411 #[cfg(feature = "ui")]
412 AlertSource::Drawing(_) => {
413 let scale = if run.inputs.logarithmic {
414 crate::scales::PriceScaleMode::Logarithmic
415 } else {
416 crate::scales::PriceScaleMode::Normal
417 };
418 match live.iter().find(|data| reader.matches(data)) {
419 Some(data) => data.drawings.iter().find_map(|drawing| {
420 let level = drawing.alert_level(source)?;
421 let index = crate::model::index_at_with_timeframe(
422 data.bars,
423 bars[at].time,
424 Some(subject.timeframe),
425 reader
426 .instrument()
427 .and_then(|instrument| instrument.time_zone.as_ref()),
428 )?;
429 drawing
430 .alert_levels_with_bars(index, scale, data.bars)
431 .into_iter()
432 .find(|(index, ..)| *index == level)
433 .map(|(.., value)| value)
434 }),
435 None => reader.drawing_value(source, from + at, scale),
436 }
437 }
438 _ => None,
439 },
440 );
441 }
442 self.runs
444 .retain(|(id, symbol), _| engine.subjects(id).contains(symbol));
445 let active: HashSet<_> = self
446 .runs
447 .iter()
448 .flat_map(|((_, symbol), run)| {
449 std::iter::once(run.subject(symbol, None)).chain(
450 run.studies
451 .iter()
452 .flat_map(|(_, study)| study.data_requests())
453 .map(|request| run.subject(symbol, Some(&request))),
454 )
455 })
456 .collect();
457 self.feeds.retain(|subject, _| active.contains(subject));
458 errors
459 }
460}