Skip to content
All library documents

EMA Crossover Strategy for Bar-Based FX Trading

Code NautilusTrader

Summary

This strategy uses a fast and a slow exponential moving average calculated from bars for a configured instrument. After both indicators are initialized, it takes a long position when the fast average is at or above the slow average and a short position when it is below. If the portfolio holds the opposite exposure, the strategy closes those positions before submitting a market order in the new direction.

Trade size and both EMA periods are configurable, with periods of 10 and 20 as defaults. The strategy subscribes to its selected bar stream and closes all positions when it stops. The document provides implementation logic but no backtest, transaction cost assumptions, or performance evidence. It does not describe protective stops, position sizing beyond a fixed trade quantity, or safeguards against repeated orders while orders are pending, so these operational details would need review before live use.

Key ideas

  • The strategy compares fast and slow EMAs calculated on bars.
  • It enters long when the fast EMA is at or above the slow EMA and short when it is below.
  • An opposite position is closed before the strategy submits a market order in the new direction.
  • The instrument, bar type, trade size, and EMA periods are configurable.
  • The document gives no performance results or transaction cost analysis.

Tags

Full text
# ema_cross.py


```py
# %% [markdown]
# # EMA cross
#
# Define the reusable bar-based EMA cross strategy used by the FX bars tutorial.

# %%
from __future__ import annotations

from decimal import Decimal

from nautilus_trader.config import StrategyConfig
from nautilus_trader.indicators import ExponentialMovingAverage
from nautilus_trader.model import Bar
from nautilus_trader.model import BarType
from nautilus_trader.model import InstrumentId
from nautilus_trader.model import OrderSide
from nautilus_trader.trading import Strategy


class EMACrossConfig(StrategyConfig):
    def __init__(
        self,
        *,
        instrument_id: InstrumentId,
        bar_type: BarType,
        trade_size: Decimal,
        fast_ema_period: int = 10,
        slow_ema_period: int = 20,
        **_kwargs: object,
    ) -> None:
        super().__init__()
        self.instrument_id = instrument_id
        self.bar_type = bar_type
        self.trade_size = trade_size
        self.fast_ema_period = fast_ema_period
        self.slow_ema_period = slow_ema_period


class EMACross(Strategy):
    def __init__(self, config: EMACrossConfig) -> None:
        super().__init__(config)
        self.fast_ema = ExponentialMovingAverage(config.fast_ema_period)
        self.slow_ema = ExponentialMovingAverage(config.slow_ema_period)

    def on_start(self) -> None:
        self.register_indicator_for_bars(self.config.bar_type, self.fast_ema)
        self.register_indicator_for_bars(self.config.bar_type, self.slow_ema)
        self.subscribe_bars(self.config.bar_type)

    def on_bar(self, _bar: Bar) -> None:
        if not self.indicators_initialized():
            return

        if self.fast_ema.value >= self.slow_ema.value:
            if self.portfolio.is_net_flat(self.config.instrument_id):
                self.buy()
            elif self.portfolio.is_net_short(self.config.instrument_id):
                self.close_all_positions(self.config.instrument_id)
                self.buy()
        elif self.fast_ema.value < self.slow_ema.value:
            if self.portfolio.is_net_flat(self.config.instrument_id):
                self.sell()
            elif self.portfolio.is_net_long(self.config.instrument_id):
                self.close_all_positions(self.config.instrument_id)
                self.sell()

    def buy(self) -> None:
        instrument = self.cache.instrument(self.config.instrument_id)
        order = self.order_factory.market(
            self.config.instrument_id,
            OrderSide.BUY,
            instrument.make_qty(self.config.trade_size),
        )
        self.submit_order(order)

    def sell(self) -> None:
        instrument = self.cache.instrument(self.config.instrument_id)
        order = self.order_factory.market(
            self.config.instrument_id,
            OrderSide.SELL,
            instrument.make_qty(self.config.trade_size),
        )
        self.submit_order(order)

    def on_stop(self) -> None:
        self.close_all_positions(self.config.instrument_id)

```

Shown in full with attribution under the source's licence. Licence: LGPL-3.0

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.