nautilus_trading/examples/strategies/ema_cross/
strategy.rs1use std::fmt::Debug;
19
20use nautilus_common::actor::DataActor;
21use nautilus_indicators::{
22 average::ema::ExponentialMovingAverage,
23 indicator::{Indicator, MovingAverage},
24};
25use nautilus_model::{
26 data::QuoteTick,
27 enums::{OrderSide, PriceType},
28 identifiers::InstrumentId,
29 types::Quantity,
30};
31
32use super::config::EmaCrossConfig;
33use crate::{
34 nautilus_strategy,
35 strategy::{Strategy, StrategyCore},
36};
37
38pub struct EmaCross {
43 pub(super) core: StrategyCore,
44 pub(super) instrument_id: InstrumentId,
45 pub(super) trade_size: Quantity,
46 pub(super) ema_fast: ExponentialMovingAverage,
47 pub(super) ema_slow: ExponentialMovingAverage,
48 pub(super) prev_fast_above: Option<bool>,
49}
50
51impl EmaCross {
52 #[must_use]
54 pub fn from_config(config: EmaCrossConfig) -> Self {
55 Self {
56 core: StrategyCore::new(config.base),
57 instrument_id: config.instrument_id,
58 trade_size: config.trade_size,
59 ema_fast: ExponentialMovingAverage::new(config.fast_period, Some(PriceType::Mid)),
60 ema_slow: ExponentialMovingAverage::new(config.slow_period, Some(PriceType::Mid)),
61 prev_fast_above: None,
62 }
63 }
64
65 #[must_use]
67 pub fn new(
68 instrument_id: InstrumentId,
69 trade_size: Quantity,
70 fast_period: usize,
71 slow_period: usize,
72 ) -> Self {
73 Self::from_config(EmaCrossConfig::new(
74 instrument_id,
75 trade_size,
76 fast_period,
77 slow_period,
78 ))
79 }
80
81 fn enter(&mut self, side: OrderSide) -> anyhow::Result<()> {
82 let order = self.core.order_factory().market(
83 self.instrument_id,
84 side,
85 self.trade_size,
86 None, None, None, None, None, None, None, );
94 self.submit_order(order, None, None)
95 }
96}
97
98nautilus_strategy!(EmaCross);
99
100impl Debug for EmaCross {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 f.debug_struct(stringify!(EmaCross))
103 .field("instrument_id", &self.instrument_id)
104 .field("trade_size", &self.trade_size)
105 .field("fast_period", &self.ema_fast.period)
106 .field("slow_period", &self.ema_slow.period)
107 .finish()
108 }
109}
110
111impl DataActor for EmaCross {
112 fn on_start(&mut self) -> anyhow::Result<()> {
113 self.subscribe_quotes(self.instrument_id, None, None);
114 Ok(())
115 }
116
117 fn on_stop(&mut self) -> anyhow::Result<()> {
118 self.unsubscribe_quotes(self.instrument_id, None, None);
119 Ok(())
120 }
121
122 fn on_quote(&mut self, quote: &QuoteTick) -> anyhow::Result<()> {
123 self.ema_fast.handle_quote(quote);
124 self.ema_slow.handle_quote(quote);
125
126 if !self.ema_fast.initialized() || !self.ema_slow.initialized() {
127 return Ok(());
128 }
129
130 let fast = self.ema_fast.value();
131 let slow = self.ema_slow.value();
132 let fast_above = fast > slow;
133
134 if let Some(prev) = self.prev_fast_above {
135 if fast_above && !prev {
136 self.enter(OrderSide::Buy)?;
137 } else if !fast_above && prev {
138 self.enter(OrderSide::Sell)?;
139 }
140 }
141
142 self.prev_fast_above = Some(fast_above);
143 Ok(())
144 }
145}