egui_charts/services/signals.rs
1//! Markers, levels and zones from the host's analytics.
2
3use super::Events;
4use crate::data::MaybeSend;
5use crate::model::{Signal, SignalId, Timeframe};
6
7/// The host's analytics as markers, levels and zones. The library computes nothing: the terminal
8/// draws what the service pushes, lets the user hide signals by category, and reports clicks.
9/// Connect it through `Chart::set_signal_source` (with the `widget` feature).
10///
11/// Methods are called on the UI thread and must return at once. Compute elsewhere and push from
12/// any thread, runtime or browser task.
13///
14/// ```
15/// use egui_charts::model::{Direction, Signal, SignalId, SignalShape, Timeframe};
16/// use egui_charts::services::{Events, SignalEvent, Signals};
17/// use std::time::Duration;
18///
19/// /// Pushes a marker every second, in place of a host's own analysis.
20/// struct Breakouts;
21///
22/// impl Signals for Breakouts {
23/// fn subscribe(&self, symbol: &str, _: Timeframe, events: Events<SignalEvent>) {
24/// let symbol = symbol.to_owned();
25/// std::thread::spawn(move || {
26/// let mut count = 0;
27/// while events.is_active() {
28/// std::thread::sleep(Duration::from_secs(1));
29/// count += 1;
30/// events.push(SignalEvent::Upsert(Signal {
31/// id: SignalId(format!("{symbol}-breakout-{count}")),
32/// shape: SignalShape::Marker {
33/// time: chrono::Utc::now(),
34/// price: None,
35/// direction: Direction::Up,
36/// },
37/// category: "Breakouts".into(),
38/// label: Some("B".into()),
39/// tooltip: Some(format!("Breakout {count} on {symbol}")),
40/// }));
41/// }
42/// });
43/// }
44/// }
45/// ```
46pub trait Signals: MaybeSend + 'static {
47 /// Starts pushing the signals of `symbol` at `timeframe` into `events` until it is inactive.
48 /// Push the current signals first: each subscription starts from none. The terminal subscribes
49 /// again on every symbol or interval change and after the stream lagged, each time with a new
50 /// stream, and stops the old one.
51 ///
52 /// The stream takes at most 10,000 pushes between two frames, the current signals included:
53 /// send at most that many at once (for example the most recent ones, or those near the loaded
54 /// range), or the stream lags on every subscription and no signal shows.
55 fn subscribe(&self, symbol: &str, timeframe: Timeframe, events: Events<SignalEvent>);
56
57 /// The user clicked the signal `id`, which this service pushed. The default does nothing.
58 fn clicked(&self, id: &SignalId) {
59 let _ = id;
60 }
61}
62
63/// A change to the signals of one subscription.
64#[derive(Clone, Debug, PartialEq)]
65#[non_exhaustive]
66pub enum SignalEvent {
67 /// Adds the signal, or replaces the one with its id.
68 Upsert(Signal),
69 /// Removes the signal with this id, if any.
70 Remove(SignalId),
71 /// Removes every signal of the subscription.
72 Clear,
73}