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(
74 EmaCrossConfig::builder()
75 .instrument_id(instrument_id)
76 .trade_size(trade_size)
77 .fast_period(fast_period)
78 .slow_period(slow_period)
79 .build(),
80 )
81 }
82
83 fn enter(&mut self, side: OrderSide) -> anyhow::Result<()> {
84 let instrument_id = self.instrument_id;
85 let trade_size = self.trade_size;
86 let order = self.order().market(
87 instrument_id,
88 side,
89 trade_size,
90 None, None, None, None, None, None, None, );
98 self.submit_order(order, None, None, None)
99 }
100}
101
102nautilus_strategy!(EmaCross);
103
104impl Debug for EmaCross {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 f.debug_struct(stringify!(EmaCross))
107 .field("instrument_id", &self.instrument_id)
108 .field("trade_size", &self.trade_size)
109 .field("fast_period", &self.ema_fast.period)
110 .field("slow_period", &self.ema_slow.period)
111 .finish()
112 }
113}
114
115impl DataActor for EmaCross {
116 fn on_start(&mut self) -> anyhow::Result<()> {
117 self.subscribe_quotes(self.instrument_id, None, None);
118 Ok(())
119 }
120
121 fn on_stop(&mut self) -> anyhow::Result<()> {
122 self.unsubscribe_quotes(self.instrument_id, None, None);
123 Ok(())
124 }
125
126 fn on_quote(&mut self, quote: &QuoteTick) -> anyhow::Result<()> {
127 self.ema_fast.handle_quote(quote);
128 self.ema_slow.handle_quote(quote);
129
130 if !self.ema_fast.initialized() || !self.ema_slow.initialized() {
131 return Ok(());
132 }
133
134 let fast = self.ema_fast.value();
135 let slow = self.ema_slow.value();
136 let fast_above = fast > slow;
137
138 if let Some(prev) = self.prev_fast_above {
139 if fast_above && !prev {
140 self.enter(OrderSide::Buy)?;
141 } else if !fast_above && prev {
142 self.enter(OrderSide::Sell)?;
143 }
144 }
145
146 self.prev_fast_above = Some(fast_above);
147 Ok(())
148 }
149}