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(
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, // time_in_force
91            None, // reduce_only
92            None, // quote_quantity
93            None, // exec_algorithm_id
94            None, // exec_algorithm_params
95            None, // tags
96            None, // client_order_id
97        );
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}