Backtesting an AUD/USD EMA Crossover Strategy from Tick Data
Summary
This example sets up a foreign-exchange backtest for an AUD/USD exponential moving average crossover strategy. It loads historical quote ticks, configures a simulated margin account with a USD balance and maker-taker fees, and supplies an FX rollover interest module populated from short-term interest-rate records. The strategy uses one-minute internally generated midpoint bars, a trade size of one million units, and fast and slow EMA periods of 10 and 20.
After running the engine, the script prints account, order-fill, and position reports, then resets and disposes of the engine. These settings illustrate how a backtest can include transaction fees, rollover interest, and tick-derived bars rather than treating signals in isolation. The document gives no report output, returns, benchmark, or robustness analysis, so it does not establish whether the crossover is profitable. Results would also depend on the sample data, execution assumptions, and the referenced strategy implementation.
Key ideas
- The example tests an AUD/USD EMA crossover using one-minute midpoint bars generated from quote ticks.
- The strategy configuration uses fast and slow EMA periods of 10 and 20.
- The simulated account includes margin, maker-taker fees, and FX rollover interest records.
- The backtest reports account status, order fills, and positions after execution.
- No performance results or robustness checks are provided, so profitability cannot be inferred.
Tags
Full text
# fx_ema_cross_audusd_bars_from_ticks.py
```py
#!/usr/bin/env python3
# -------------------------------------------------------------------------------------------------
# Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
# https://nautechsystems.io
#
# Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
# You may not use this file except in compliance with the License.
# You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -------------------------------------------------------------------------------------------------
"""
Example of fx ema cross audusd bars from ticks.
"""
import sys
from decimal import Decimal
from pathlib import Path
import pandas as pd
from nautilus_trader.backtest import BacktestEngine
from nautilus_trader.backtest import FXRolloverInterestModule
from nautilus_trader.backtest import InterestRateRecord
from nautilus_trader.config import BacktestEngineConfig
from nautilus_trader.execution import MakerTakerFeeModel
from nautilus_trader.model import AccountType
from nautilus_trader.model import BarType
from nautilus_trader.model import Currency
from nautilus_trader.model import Money
from nautilus_trader.model import OmsType
from nautilus_trader.model import TraderId
from nautilus_trader.model import Venue
from nautilus_trader.testkit.providers import TestDataProvider
from nautilus_trader.testkit.providers import TestInstrumentProvider
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "docs" / "tutorials"))
from ema_cross import EMACross
from ema_cross import EMACrossConfig
if __name__ == "__main__":
provider = TestDataProvider()
rates = provider.read_csv("short-term-interest.csv")
rollover = FXRolloverInterestModule(
records=[
InterestRateRecord(location=row[0], time=row[1], value=row[2])
for row in rates.itertuples(index=False, name=None)
],
)
engine = BacktestEngine(
BacktestEngineConfig(trader_id=TraderId.from_str("BACKTESTER-001")),
)
SIM = Venue("SIM")
USD = Currency.from_str("USD")
engine.add_venue(
venue=SIM,
oms_type=OmsType.HEDGING,
account_type=AccountType.MARGIN,
base_currency=USD,
starting_balances=[Money(1_000_000, USD)],
fee_model=MakerTakerFeeModel(
maker_rate=Decimal("0.00002"),
taker_rate=Decimal("0.00002"),
),
modules=[rollover],
)
AUDUSD_SIM = TestInstrumentProvider.default_fx_ccy("AUD/USD", SIM)
engine.add_instrument(AUDUSD_SIM)
ticks = provider.quotes_from_truefx_csv(
instrument=AUDUSD_SIM,
csv_name="truefx/audusd-ticks.csv",
)
engine.add_data(ticks)
strategy = EMACross(
EMACrossConfig(
instrument_id=AUDUSD_SIM.id,
bar_type=BarType.from_str("AUD/USD.SIM-1-MINUTE-MID-INTERNAL"),
trade_size=Decimal(1_000_000),
fast_ema_period=10,
slow_ema_period=20,
),
)
engine.add_strategy(strategy)
engine.run()
with pd.option_context(
"display.max_rows",
100,
"display.max_columns",
None,
"display.width",
300,
):
print(engine.generate_account_report(SIM))
print(engine.generate_order_fills_report())
print(engine.generate_positions_report())
engine.reset()
engine.dispose()
```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.