Skip to main content

nautilus_trading/examples/strategies/ema_cross/
strategy.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 implementation.
17
18use 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
38/// Dual-EMA crossover strategy.
39///
40/// Generates buy signals when the fast EMA crosses above the slow EMA,
41/// and sell signals when the fast crosses below.
42pub 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    /// Creates a new [`EmaCross`] instance from config.
53    #[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    /// Creates a new [`EmaCross`] instance.
66    #[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, // time_in_force
87            None, // reduce_only
88            None, // quote_quantity
89            None, // display_qty
90            None, // expire_time
91            None, // emulation_trigger
92            None, // tags
93        );
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}