Generate code in 3 minutes
Enter your idea and AI generates runnable code.
To build a simple Forex trading bot on QuantConnect, we will implement a classic Trend Following Strategy using an Exponential Moving Average (EMA) Crossover.
Here is the complete, runnable code for the QuantConnect Lean engine.
# region imports
from AlgorithmImports import *
# endregion
class SimpleForexBot(QCAlgorithm):
def initialize(self):
# 1. Set Strategy Settings
self.set_start_date(2022, 1, 1) # Backtest Start Date
self.set_end_date(2024, 1, 1) # Backtest End Date
self.set_cash(100000) # Starting Capital (USD)
# 2. Add Forex Pair
# We use OANDA as the brokerage model for realistic spreads
self.pair = self.add_forex("EURUSD", Resolution.HOUR, Market.OANDA).symbol
# 3. Define Indicators
self.fast_ema = self.ema(self.pair, 20, Resolution.HOUR)
self.slow_ema = self.ema(self.pair, 50, Resolution.HOUR)
# 4. Warm Up
# Automatically feed historical data to indicators before the start date
# so they are ready to trade immediately.
self.set_warm_up(50, Resolution.HOUR)
# 5. Setup Plotting (Optional, for visualization)
chart = Chart("Trade Plot")
chart.add_series(Series("Price", SeriesType.LINE, 0))
chart.add_series(Series("Fast EMA", SeriesType.LINE, 0))
chart.add_series(Series("Slow EMA", SeriesType.LINE, 0))
self.add_chart(chart)
def on_data(self, data: Slice):
# Ensure data exists for our pair before processing
if self.pair not in data:
return
# Ensure indicators are ready (handled by WarmUp, but good practice)
if not self.fast_ema.is_ready or not self.slow_ema.is_ready:
return
# Get current indicator values
fast = self.fast_ema.current.value
slow = self.slow_ema.current.value
price = data[self.pair].close
# Plotting
self.plot("Trade Plot", "Price", price)
self.plot("Trade Plot", "Fast EMA", fast)
self.plot("Trade Plot", "Slow EMA", slow)
# Current Holdings
# quantity > 0 means Long, quantity < 0 means Short, 0 means Flat
holdings = self.portfolio[self.pair].quantity
# --- Trading Logic ---
# Signal: Fast EMA crosses ABOVE Slow EMA (Bullish)
if fast > slow:
# If we are not already Long
if holdings <= 0:
self.set_holdings(self.pair, 1.0) # Allocate 100% of buying power to Long
self.debug(f"BUY Signal at {self.time} | Price: {price}")
# Signal: Fast EMA crosses BELOW Slow EMA (Bearish)
elif fast < slow:
# If we are not already Short
if holdings >= 0:
self.set_holdings(self.pair, -1.0) # Allocate 100% of buying power to Short
self.debug(f"SELL Signal at {self.time} | Price: {price}")
self.add_forex: This subscribes to the data feed. We chose Resolution.HOUR to reduce noise compared to minute data. Market.OANDA is specified to ensure the backtest uses realistic Forex pricing and volume data.self.set_warm_up: Indicators like EMA require previous data to calculate their current value. Warming up ensures the algorithm doesn't wait 50 hours after the start date to place its first trade.self.set_holdings: This is a helper method that calculates how many units to buy or sell based on your total buying power.
1.0 uses 100% of equity to go Long.-1.0 uses 100% of equity to go Short.self.plot: This draws the chart in the backtest results, allowing you to visually verify that trades are happening exactly when the lines cross.Once you have this running, consider these next steps to make it production-ready:
self.stop_market_order or self.limit_order.Q: Why use OANDA as the market in the code?
A: In QuantConnect, specifying the market (e.g., Market.OANDA or Market.FXCM) ensures that the backtest uses the specific historical price data, time zone conventions, and margin models of that broker. This produces more accurate results than generic data.
Q: Can I trade multiple pairs with this code?
A: The code above is designed for a single pair. To trade multiple pairs (e.g., EURUSD and GBPUSD), you would need to create a list of symbols in initialize, store indicators in a dictionary keyed by symbol, and loop through them in on_data.
Q: How does leverage work in this script?
A: Forex accounts usually have high leverage (e.g., 20x or 50x). self.set_holdings(self.pair, 1.0) uses 1x leverage (no borrowed money). To use leverage, you would increase the value, for example, self.set_holdings(self.pair, 10.0) would use 10x leverage. Be careful, as this significantly increases risk.
Q: Why use Hourly resolution instead of Minute?
A: For a "simple" moving average strategy, Minute data often contains too much "noise" (random price fluctuations), leading to false signals (whipsaws). Hourly data smooths out the price action, making trend identification easier for beginners.