nautilus_trading/examples/strategies/
ema_cross.rs1use std::{
23 fmt::Debug,
24 ops::{Deref, DerefMut},
25};
26
27use nautilus_common::actor::{DataActor, DataActorCore};
28use nautilus_indicators::{
29 average::ema::ExponentialMovingAverage,
30 indicator::{Indicator, MovingAverage},
31};
32use nautilus_model::{
33 data::QuoteTick,
34 enums::{OrderSide, PriceType},
35 identifiers::{InstrumentId, StrategyId},
36 types::Quantity,
37};
38
39use crate::strategy::{Strategy, StrategyConfig, StrategyCore};
40
41pub struct EmaCross {
46 core: StrategyCore,
47 instrument_id: InstrumentId,
48 trade_size: Quantity,
49 ema_fast: ExponentialMovingAverage,
50 ema_slow: ExponentialMovingAverage,
51 prev_fast_above: Option<bool>,
52}
53
54impl EmaCross {
55 #[must_use]
57 pub fn new(
58 instrument_id: InstrumentId,
59 trade_size: Quantity,
60 fast_period: usize,
61 slow_period: usize,
62 ) -> Self {
63 let config = StrategyConfig {
64 strategy_id: Some(StrategyId::from("EMA_CROSS-001")),
65 order_id_tag: Some("001".to_string()),
66 ..Default::default()
67 };
68 Self {
69 core: StrategyCore::new(config),
70 instrument_id,
71 trade_size,
72 ema_fast: ExponentialMovingAverage::new(fast_period, Some(PriceType::Mid)),
73 ema_slow: ExponentialMovingAverage::new(slow_period, Some(PriceType::Mid)),
74 prev_fast_above: None,
75 }
76 }
77
78 fn enter(&mut self, side: OrderSide) -> anyhow::Result<()> {
79 let order = self.core.order_factory().market(
80 self.instrument_id,
81 side,
82 self.trade_size,
83 None, None, None, None, None, None, None, );
91 self.submit_order(order, None, None)
92 }
93}
94
95impl Deref for EmaCross {
96 type Target = DataActorCore;
97 fn deref(&self) -> &Self::Target {
98 &self.core
99 }
100}
101
102impl DerefMut for EmaCross {
103 fn deref_mut(&mut self) -> &mut Self::Target {
104 &mut self.core
105 }
106}
107
108impl Debug for EmaCross {
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 f.debug_struct(stringify!(EmaCross))
111 .field("instrument_id", &self.instrument_id)
112 .field("trade_size", &self.trade_size)
113 .field("fast_period", &self.ema_fast.period)
114 .field("slow_period", &self.ema_slow.period)
115 .finish()
116 }
117}
118
119impl DataActor for EmaCross {
120 fn on_start(&mut self) -> anyhow::Result<()> {
121 self.subscribe_quotes(self.instrument_id, None, None);
122 Ok(())
123 }
124
125 fn on_stop(&mut self) -> anyhow::Result<()> {
126 self.unsubscribe_quotes(self.instrument_id, None, None);
127 Ok(())
128 }
129
130 fn on_quote(&mut self, quote: &QuoteTick) -> anyhow::Result<()> {
131 self.ema_fast.handle_quote(quote);
132 self.ema_slow.handle_quote(quote);
133
134 if !self.ema_fast.initialized() || !self.ema_slow.initialized() {
135 return Ok(());
136 }
137
138 let fast = self.ema_fast.value();
139 let slow = self.ema_slow.value();
140 let fast_above = fast > slow;
141
142 if let Some(prev) = self.prev_fast_above {
143 if fast_above && !prev {
144 self.enter(OrderSide::Buy)?;
145 } else if !fast_above && prev {
146 self.enter(OrderSide::Sell)?;
147 }
148 }
149
150 self.prev_fast_above = Some(fast_above);
151 Ok(())
152 }
153}
154
155impl Strategy for EmaCross {
156 fn core(&self) -> &StrategyCore {
157 &self.core
158 }
159
160 fn core_mut(&mut self) -> &mut StrategyCore {
161 &mut self.core
162 }
163}