Skip to main content

nautilus_trading/examples/strategies/
ema_cross.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Dual-EMA crossover strategy.
17//!
18//! Subscribes to quotes for a single instrument, maintains fast and slow
19//! exponential moving averages, and submits market orders when the fast
20//! EMA crosses above (buy) or below (sell) the slow EMA.
21
22use 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
41/// Dual-EMA crossover strategy.
42///
43/// Generates buy signals when the fast EMA crosses above the slow EMA,
44/// and sell signals when the fast crosses below.
45pub 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    /// Creates a new [`EmaCross`] instance.
56    #[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, // time_in_force
84            None, // reduce_only
85            None, // quote_quantity
86            None, // display_qty
87            None, // expire_time
88            None, // emulation_trigger
89            None, // tags
90        );
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}