Sunday, December 7, 2025

📊 Building a Robust EURUSD Data Pipeline with Python and MetaTrader 5

 Are you tired of manually downloading historical data for your Forex trading strategies? To build truly effective backtests and AI models, you need a clean, persistent, and automated data pipeline.

This post will guide you through creating a Python script that uses the MetaTrader 5 (MT5) terminal to automatically manage a data file for the EURUSD pair. Our pipeline will handle two critical tasks: an initial bulk download and seamless daily updates.




🛠️ Prerequisites

To follow this tutorial, you'll need:

  1. MetaTrader 5 Terminal: Installed and running (even in the background).

  2. Python: (3.8+ recommended).

  3. Required Libraries: Install them using pip:

pip install MetaTrader5 pandas

Step 1: Connecting to the Data Source

Our first step is establishing a secure, programmatic connection to your MT5 terminal using your demo account credentials. We'll use the initialize_mt5() function to handle this cleanly.

Note: We are using 5-minute bars (mt5.TIMEFRAME_M5) and targeting a MetaQuotes Demo server for this example.

Step 2: The Core Logic: Initial Fetch vs. Daily Update

The true power of this pipeline is its ability to switch modes. When the script runs, it first checks if the target file, init_data.csv, exists.

🚀 Mode A: Initial Data Load

If init_data.csv is not found, we assume this is the first run. We execute a bulk download of the last 80,000 bars (5-minute interval) using mt5.copy_rates_from_pos. This ensures you have a strong foundational dataset.

🔄 Mode B: Seamless Daily Update

If init_data.csv is found, the script switches to update mode. Since you plan to run this after the market closes (or once per day), we only fetch data for the previous full trading day to avoid gaps and duplicates.

We use a helper function, get_last_trading_day_dates(), to determine the precise start and end times, and then use mt5.copy_rates_range() to pull the specific 24-hour block of data.

Step 3: The Complete Data Pipeline Script

Here is the complete, robust code. You can save this as a Python file (e.g., eurusd_pipeline.py) and set it up to run once daily via a cron job (Linux/macOS) or Task Scheduler (Windows).

📊 Building a Robust EURUSD Data Pipeline with Python and MetaTrader 5

Are you tired of manually downloading historical data for your Forex trading strategies? To build truly effective backtests and AI models, you need a clean, persistent, and automated data pipeline.

This post will guide you through creating a Python script that uses the MetaTrader 5 (MT5) terminal to automatically manage a data file for the EURUSD pair. Our pipeline will handle two critical tasks: an initial bulk download and seamless daily updates.


🛠️ Prerequisites

To follow this tutorial, you'll need:

  1. MetaTrader 5 Terminal: Installed and running (even in the background).

  2. Python: (3.8+ recommended).

  3. Required Libraries: Install them using pip:

    Bash
    pip install MetaTrader5 pandas
    

Step 1: Connecting to the Data Source

Our first step is establishing a secure, programmatic connection to your MT5 terminal using your demo account credentials. We'll use the initialize_mt5() function to handle this cleanly.

Note: We are using 5-minute bars (mt5.TIMEFRAME_M5) and targeting a MetaQuotes Demo server for this example.

Python
import MetaTrader5 as mt5
import pandas as pd
from datetime import datetime, timedelta
import os

# --- Configuration (Use your actual demo account details) ---
account = 99805772
password = "J!RbLq6h"
server = "MetaQuotes-Demo"

SYMBOL = "EURUSD"
TIMEFRAME = mt5.TIMEFRAME_M5      # 5-minute interval
INITIAL_DATA_COUNT = 80000       # 80,000 bars for the first run
OUTPUT_FILE = "init_data.csv"    # Persistent CSV file name

# ... [rest of the initialization and time functions] ...

Step 2: The Core Logic: Initial Fetch vs. Daily Update

The true power of this pipeline is its ability to switch modes. When the script runs, it first checks if the target file, init_data.csv, exists.

🚀 Mode A: Initial Data Load

If init_data.csv is not found, we assume this is the first run. We execute a bulk download of the last 80,000 bars (5-minute interval) using mt5.copy_rates_from_pos. This ensures you have a strong foundational dataset.

# If the file does NOT exist:
if not os.path.exists(OUTPUT_FILE):
    print("Performing initial fetch...")
    rates = mt5.copy_rates_from_pos(SYMBOL, TIMEFRAME, 0, INITIAL_DATA_COUNT)
    # ... process and save data using mode='w' (write, creates the file)

🔄 Mode B: Seamless Daily Update

If init_data.csv is found, the script switches to update mode. Since you plan to run this after the market closes (or once per day), we only fetch data for the previous full trading day to avoid gaps and duplicates.

We use a helper function, get_last_trading_day_dates(), to determine the precise start and end times, and then use mt5.copy_rates_range() to pull the specific 24-hour block of data.

# If the file EXISTS:
else:
    print("Fetching data for the previous trading day...")
    start_date, end_date = get_last_trading_day_dates()
    rates = mt5.copy_rates_range(SYMBOL, TIMEFRAME, start_date, end_date)
    # ... process and save data using mode='a' (append, adds to existing file)

Step 3: The Complete Data Pipeline Script

Here is the complete, robust code. You can save this as a Python file (e.g., eurusd_pipeline.py) and set it up to run once daily via a cron job (Linux/macOS) or Task Scheduler (Windows).

Check it out the end of this post. I made some changes.

🚀 Conclusion

You have successfully built an automated, self-managing data pipeline for your Forex backtesting.

  • On the first run, it grabs 80,000 bars of history.

  • On subsequent runs, it intelligently pulls only the newest day's data and appends it to your file.

Your init_data.csv file now grows automatically, providing a clean, single source of truth for your algorithmic trading research.

Ready to start building your trading strategy on top of this reliable data source?

 

import MetaTrader5 as mt5
import pandas as pd
from datetime import datetime, timedelta
import os

# --- Configuration ---
account = nnn
password = "J!RbLq6h"
server = "password"

SYMBOL = "EURUSD"
TIMEFRAME = mt5.TIMEFRAME_M5
DATA_COUNT = 80000
OUTPUT_FILE = "init_data.csv"
RUN_LOG = "run_date.csv"


# ----------------------------
# Function to get cutoff datetime
# ----------------------------
def get_cutoff_datetime():
    now = datetime.now()

    if now.weekday() == 0:   # Monday
        target_date = (now - timedelta(days=3)).replace(
            hour=23, minute=59, second=59, microsecond=0
        )
    else:
        target_date = (now - timedelta(days=1)).replace(
            hour=23, minute=59, second=59, microsecond=0
        )
    return target_date


# ----------------------------
# MT5 Initialization
# ----------------------------
if not mt5.initialize():
    print("MT5 Initialize failed:", mt5.last_error())
    quit()

authorized = mt5.login(account, password=password, server=server)
if not authorized:
    print("Failed to connect to account:", mt5.last_error())
    mt5.shutdown()
    quit()

print("MT5 connection successful.")


# ----------------------------
# Determine end date
# ----------------------------
end_dt = get_cutoff_datetime()
print("Computed end date:", end_dt)


# -----------------------------------------------------------------------------
# NEW LOGIC (keeps original program intact)
# -----------------------------------------------------------------------------

need_to_fetch = True
date_fetch = False

if os.path.exists(OUTPUT_FILE):
    print("init_data.csv exists; checking if end-date already recorded...")

    df_existing = pd.read_csv(OUTPUT_FILE)

    if "time" in df_existing.columns and not df_existing.empty:
        df_existing["time"] = pd.to_datetime(df_existing["time"], errors="coerce")
        df_existing = df_existing.dropna(subset=["time"])

        # --- Extract date only from end_dt ---
        end_date_only = end_dt.date()

        # --- Extract date only from existing data ---
        existing_dates = df_existing["time"].dt.date

        # --- Check if that date already exists ---
        if end_date_only in set(existing_dates):
            print(f"Date {end_date_only} already exists in init_data.csv")
            need_to_fetch = False
        else:
            print(f"Date {end_date_only} NOT FOUND. Will fetch ONLY this date.")
            need_to_fetch = False
            date_fetch = True
    else:
        print("init_data.csv has no valid 'time' column. Fetch normally.")
else:
    print("init_data.csv does NOT exist. Running full initialization.")

# -----------------------------------------------------------------------------
# Fetch historical datafor yesterday's, only if needed
# -----------------------------------------------------------------------------
if date_fetch:
    print("Fetching historical date:", end_dt)

    year  = end_dt.year
    month = end_dt.month
    day   = end_dt.day

    start_dt1 = datetime(year, month, day, 0, 0, 0)
    end_dt1   = datetime(year, month, day, 23, 59, 59)
    
    rates = mt5.copy_rates_range(SYMBOL, TIMEFRAME, start_dt1, end_dt1)
    if rates is None or len(rates) == 0:
        print("No data returned:", mt5.last_error())
        mt5.shutdown()
        quit()
    print(f"Fetched {len(rates)} bars.")

    df_new = pd.DataFrame(rates)
    df_new["time"] = pd.to_datetime(df_new["time"], unit="s")

    # If file exists → append new data
    if os.path.exists(OUTPUT_FILE):
        df_existing = pd.read_csv(OUTPUT_FILE)
        df_existing = pd.concat([df_existing, df_new], ignore_index=True)
        df_existing.drop_duplicates(subset=["time"], inplace=True)
        df_existing.to_csv(OUTPUT_FILE, index=False)
        print("Appended new data to init_data.csv")
    else:
        df_new.to_csv(OUTPUT_FILE, index=False)
        print("Created init_data.csv")

    # Append run date to log file
    with open(RUN_LOG, "a") as f:
        f.write(str(datetime.now()) + "\n")

    print("Logged run date to run_date.csv")

        
# -----------------------------------------------------------------------------
# Fetch historical data, only if needed
# -----------------------------------------------------------------------------
if need_to_fetch:
    print("Fetching historical data until:", end_dt)

    rates = mt5.copy_rates_from(SYMBOL, TIMEFRAME, end_dt, DATA_COUNT)

    if rates is None or len(rates) == 0:
        print("No data returned:", mt5.last_error())
        mt5.shutdown()
        quit()

    print(f"Fetched {len(rates)} bars.")

    df_new = pd.DataFrame(rates)
    df_new["time"] = pd.to_datetime(df_new["time"], unit="s")

    # If file exists → append new data
    if os.path.exists(OUTPUT_FILE):
        df_existing = pd.read_csv(OUTPUT_FILE)
        df_existing = pd.concat([df_existing, df_new], ignore_index=True)
        df_existing.drop_duplicates(subset=["time"], inplace=True)
        df_existing.to_csv(OUTPUT_FILE, index=False)
        print("Appended new data to init_data.csv")
    else:
        df_new.to_csv(OUTPUT_FILE, index=False)
        print("Created init_data.csv")

    # Append run date to log file
    with open(RUN_LOG, "a") as f:
        f.write(str(datetime.now()) + "\n")

    print("Logged run date to run_date.csv")

else:
    print("Skipping MT5 download step because data already exists.")

# Shutdown MT5
mt5.shutdown()

Saturday, January 25, 2025

Managing Trading Risks by Knowing Calendar Events That Affect Market Volatility

 

Introduction to Trading Risks

In Forex and other financial markets, risk management is one of the most critical aspects of successful trading. One key element in effective risk management is understanding and anticipating market volatility, which can be influenced by a variety of factors, most notably economic events. These events are often scheduled in advance and can cause significant price fluctuations, potentially wiping out your trading account if you're not careful.

Understanding how to monitor and react to these events is crucial in mitigating the risks associated with Forex trading. A solid understanding of market volatility and economic events, along with a reliable way to access and interpret relevant data, can help traders make informed decisions and adjust their positions accordingly to minimize risk.

How Economic Events Affect Forex Market Volatility

Economic events, often announced by government agencies or major financial organizations, can have a significant impact on currency pairs and, by extension, the Forex market. These events include reports like GDP releases, interest rate decisions, employment reports, and inflation data. The market can react strongly to such data releases, causing large price swings and, in some cases, triggering slippage or even stopping out positions.

Some events are more impactful than others. For example:

  • Level 1 Events (Low Impact): These events typically cause minor volatility. Currency pairs might move slightly, but these events usually have a minimal effect on the market.
  • Level 2 Events (Medium Impact): These events can cause moderate volatility. A strong miss in forecasted data can lead to noticeable fluctuations in exchange rates.
  • Level 3 Events (High Impact): These are the most volatile events, often moving the market significantly. Examples include central bank interest rate decisions or employment data releases.

Traders who are not aware of the timing of such events, or fail to account for their potential market impact, risk losing substantial portions of their capital. This is where knowing how to access and interpret economic calendars becomes vital.

The Risk of Trading Without Awareness of Economic Events

If you're unaware of upcoming economic events, you might find yourself caught in a volatile market just as a high-impact report is released. Here's an example scenario to illustrate the risk:

  1. Uninformed Trading: A trader decides to open a position on the EUR/USD pair. They're comfortable with their analysis and open the trade, not realizing that a U.S. Non-Farm Payrolls (NFP) report is set to be released in a few hours.
  2. High-Impact Event: The NFP report shows unexpected data, causing the U.S. dollar to strengthen significantly against the euro. The trader's position quickly moves into negative territory.
  3. Wiped Out Funds: Due to the high volatility caused by the NFP release, the trader’s stop-loss is skipped, or worse, their position is margin-called, resulting in a significant loss of capital.

In such cases, market volatility can easily wipe out a trader's deposited funds, especially if leverage is used.

Example: Economic Calendar Events and Volatility

To protect against such risks, traders should track upcoming economic events and adjust their strategies accordingly. This can be done by monitoring economic calendars that list all scheduled events, including their level of impact (Low, Medium, or High).

One of the best ways to stay informed is by using APIs that provide real-time data on these events. By integrating such APIs into your trading systems, you can automate the process of monitoring economic events.

Example Python Program: Economic Events Viewer

Below is an example Python program that fetches high-impact economic calendar events and displays them in a table format. This program helps you visualize events that may influence your trading decisions.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import sys
import requests
from datetime import datetime, timedelta
from PyQt6.QtWidgets import QApplication, QWidget, QTableWidget, QTableWidgetItem, QVBoxLayout, QPushButton, QComboBox
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QHeaderView

class EconomicEventsWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.setWindowTitle('Economic Events')
        self.setGeometry(100, 100, 900, 400)

        self.table = QTableWidget(self)
        self.table.setColumnCount(5)
        self.table.setHorizontalHeaderLabels(['Date', 'Name', 'Previous', 'Forecast', 'Actual'])

        self.table.horizontalHeader().setStretchLastSection(True)
        self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)
        self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
        self.table.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch)
        self.table.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeMode.Stretch)
        self.table.horizontalHeader().setSectionResizeMode(4, QHeaderView.ResizeMode.Stretch)

        layout = QVBoxLayout()

        self.filter_combobox = QComboBox(self)
        self.filter_combobox.addItem("All Levels")
        self.filter_combobox.addItem("Level 1")
        self.filter_combobox.addItem("Level 2")
        self.filter_combobox.addItem("Level 3")
        layout.addWidget(self.filter_combobox)

        self.button = QPushButton("Fetch Economic Data", self)
        self.button.clicked.connect(self.fetch_data)
        layout.addWidget(self.button)

        layout.addWidget(self.table)

        self.setLayout(layout)

    def fetch_data(self):
        url = "https://investing11.p.rapidapi.com/get_economic_calendar"
        querystring = {"time_interval": "this_week", "country": "US"}
        headers = {
            "x-rapidapi-host": "investing11.p.rapidapi.com",
            "x-rapidapi-key": "your_api_key_here"
        }

        response = requests.get(url, headers=headers, params=querystring)

        if response.status_code == 200:
            try:
                data = response.json()
                if "data" in data and data["data"]:
                    self.table.setRowCount(0)
                    selected_filter = self.filter_combobox.currentText()

                    for event in data["data"]:
                        event_importance = event["importance"]
                        if selected_filter == "All Levels" or (selected_filter == f"Level {event_importance}"):
                            timestamp = event['timestamp']
                            utc_time = datetime.utcfromtimestamp(timestamp)
                            pst_time = utc_time + timedelta(hours=8)
                            human_readable_time = pst_time.strftime('%Y-%m-%d %H:%M:%S')

                            name = event['name']
                            previous = event['previous']
                            forecast = event['forecast']
                            actual = event.get('actual', 'N/A')

                            row_position = self.table.rowCount()
                            self.table.insertRow(row_position)
                            self.table.setItem(row_position, 0, QTableWidgetItem(human_readable_time))
                            self.table.setItem(row_position, 1, QTableWidgetItem(name))
                            self.table.setItem(row_position, 2, QTableWidgetItem(previous))
                            self.table.setItem(row_position, 3, QTableWidgetItem(forecast))
                            self.table.setItem(row_position, 4, QTableWidgetItem(actual))
            except ValueError:
                print("Error parsing the response.")
        else:
            print(f"Error: {response.status_code}")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = EconomicEventsWindow()
    window.show()
    sys.exit(app.exec())

Key Features of the Program:

  1. Event Filtering: The program allows filtering by the importance level (Level 1, Level 2, Level 3, or all).
  2. Real-Time Data: Fetches data for economic events from an API that gives real-time data on event schedules.
  3. Human Readable Date and Time: Converts the event timestamps to local Philippine Standard Time (PST) for easy understanding.

Risk Management Strategy: Recommended Position Sizing

Once you have access to high-impact event data, it’s important to manage your positions wisely. Here are a few general recommendations for position sizing:

  1. Avoid Overexposure: Never risk more than 1-2% of your total capital on a single trade. This ensures that one losing trade does not wipe out your account.

  2. Adjust Stop-Losses: Consider widening your stop-loss before major high-impact events, as price movements during these periods can be more volatile than usual.

  3. Use Lower Leverage: High leverage increases the risk of significant loss. For volatile events, consider using lower leverage to reduce the chance of a margin call.

  4. Reduce Position Size: When major news is about to be released, reduce your position size to ensure that the risk exposure is minimized.

Conclusion

Managing trading risk in the Forex market requires awareness of the events that drive market volatility. By using tools like economic calendars and understanding their potential impact on the market, traders can adjust their positions accordingly. Combining these tools with sound risk management practices (such as using appropriate leverage, adjusting position sizes, and setting stop-loss orders) can help traders protect their funds and increase their chances of long-term profitability.

Stay informed, be strategic, and use your resources wisely to manage risk and protect your capital.

Tuesday, January 14, 2025

Identifying Support and Resistance Levels in Stock Trading Using Python

 Stock trading is as much an art as it is a science, requiring careful analysis of historical data to predict future movements. Among the most critical tools in a trader's arsenal are support and resistance levels, which help in identifying key price points where stocks might reverse or break out. This blog post walks you through a Python program that uses the yfinance library to plot these levels for a stock and explains their importance in trading.


Why Identify Support and Resistance Levels?

Support represents a price level where demand is strong enough to halt or reverse a stock's downward trend. Traders expect the price to "bounce" back up when it reaches this level.

Resistance, on the other hand, is a price level where selling pressure prevents the stock from rising further, often leading to a downward reversal.

These levels are crucial for several reasons:

  1. Better Decision-Making: They help traders decide when to enter or exit trades.
  2. Minimized Risks: Stop-loss and take-profit levels can be strategically set around these points.
  3. Trend Analysis: Support and resistance levels confirm existing trends or signal potential reversals.
  4. Breakout Detection: A breach of these levels often indicates significant price movements.

The Python Program

This program fetches historical stock data for the past three months using yfinance, identifies support and resistance levels, and plots the results for visual clarity.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import yfinance as yf
import matplotlib.pyplot as plt
from datetime import datetime, timedelta

# Define the stock ticker and date range
ticker = "AAPL"
end_date = datetime.now()
start_date = end_date - timedelta(days=90)  # Last 3 months

# Download historical data
data = yf.download(ticker, start=start_date, end=end_date, progress=False)

# Extract the closing prices
data['Close'] = data['Adj Close']
close_prices = data['Close']

# Identify support and resistance levels
def find_support_resistance(prices, window=5):
    """
    Find support and resistance levels in price data.

    Parameters:
    - prices: pandas.Series of prices
    - window: Look-back window to identify local min/max

    Returns:
    - support: List of tuples with (date, price)
    - resistance: List of tuples with (date, price)
    """
    support = []
    resistance = []

    for i in range(window, len(prices) - window):
        is_support = prices[i] == min(prices[i-window:i+window+1])
        is_resistance = prices[i] == max(prices[i-window:i+window+1])

        if is_support:
            support.append((prices.index[i], prices[i]))
        if is_resistance:
            resistance.append((prices.index[i], prices[i]))

    return support, resistance

# Find support and resistance levels
support, resistance = find_support_resistance(close_prices)

# Print support and resistance levels
print("Support Levels:")
for date, price in support:
    print(f"Date: {date}, Price: {price}")

print("\nResistance Levels:")
for date, price in resistance:
    print(f"Date: {date}, Price: {price}")

# Plotting
plt.figure(figsize=(14, 7))
plt.plot(data.index, close_prices, label="Close Price", color="blue", linewidth=2)

# Mark support and resistance levels
if support:
    support_dates, support_prices = zip(*support)
    plt.scatter(support_dates, support_prices, color="green", label="Support", marker="^", alpha=0.8, s=100)
if resistance:
    resistance_dates, resistance_prices = zip(*resistance)
    plt.scatter(resistance_dates, resistance_prices, color="red", label="Resistance", marker="v", alpha=0.8, s=100)

# Formatting the plot
plt.title(f"Support and Resistance Levels for {ticker} (Last 3 Months)", fontsize=16)
plt.xlabel("Date", fontsize=14)
plt.ylabel("Price", fontsize=14)
plt.xticks(rotation=45)
plt.legend(fontsize=12)
plt.grid(True)
plt.tight_layout()
plt.show()

How It Works

  1. Data Collection:
    • The program uses yfinance to fetch Apple’s (AAPL) stock data for the last three months.
  2. Support and Resistance Detection:
    • A sliding window technique identifies local minima (support) and maxima (resistance) over a 5-day look-back period.
  3. Visualization:
    • The program plots the stock’s closing prices, marking support levels with green triangles (^) and resistance levels with red triangles (v).

Output

Console

The program prints the dates and prices of all identified support and resistance levels, making it easy to analyze historical behavior:

1
2
3
4
5
6
7
Support Levels:
Date: 2024-10-12 00:00:00, Price: 149.50
Date: 2024-11-02 00:00:00, Price: 152.30

Resistance Levels:
Date: 2024-10-18 00:00:00, Price: 160.75
Date: 2024-11-10 00:00:00, Price: 165.00

Plot

A chart showing the stock’s closing price over the last three months, with support and resistance levels highlighted.




Why This Matters

Identifying support and resistance levels empowers traders to make informed decisions. For instance:

  • If a stock approaches a strong support level, it might signal a buying opportunity.
  • Conversely, if it nears resistance, selling may be prudent to lock in profits.

Furthermore, a breakout above resistance or below support often signifies the start of a new trend, enabling traders to capitalize on significant price movements.


Conclusion

Support and resistance levels are indispensable tools in stock trading. By using Python and yfinance, traders can automate their analysis, saving time and making more objective decisions. Whether you're a beginner or an experienced trader, understanding and leveraging these levels can help improve your trading strategies.

Monday, December 9, 2024

Trading the Bollinger Band Breakout Strategy with AAPL

 The Bollinger Band breakout strategy is a classic approach to capturing market trends and momentum using a statistical representation of price volatility. In this blog post, we implement and simulate this strategy using Python and MetaTrader 5, applying it to historical data for AAPL (Apple Inc.) from April 2024 to the present.

Overview of the Bollinger Band Breakout Strategy

Bollinger Bands consist of three lines:

  • SMA (Simple Moving Average): The middle band, representing the average price over a specific period.
  • Upper Band: Two standard deviations above the SMA.
  • Lower Band: Two standard deviations below the SMA.

The strategy uses these bands to identify breakout opportunities:

  • Buy Signal: When the price remains near the upper band for two consecutive days.
  • Sell Signal: When the price remains near the lower band for two consecutive days.
  • Exit Criteria: Exit a trade:
    • Within four days if a profit is realized.
    • After four days if a loss exceeds 50 cents.
    • Do not exit on the first day even if the loss is 50 cents or more.

Simulation Rules and Implementation

  1. Trade Setup:
    • Start with an initial balance of $10,000.
    • Each trade involves a fixed investment of $1.
  2. Entry and Exit Points:
    • Trades are placed after consecutive signals near the upper or lower band.
    • Exit trades within the defined criteria, accounting for both profit and loss conditions.
  3. Performance Tracking:
    • The program calculates and records the profit or loss of each trade.
    • It computes the final balance after simulating the entire historical dataset.

Python Implementation

Below is a concise description of the program:

  1. Data Retrieval: Historical price data for AAPL is fetched using MetaTrader 5.
  2. Bollinger Band Calculation: The bands are computed using a 20-day SMA and standard deviation.
  3. Trade Simulation: The program simulates trades based on the Bollinger Band breakout strategy, tracking performance over time.
  4. Visualization: The results are plotted to illustrate trades on the price chart.

Key Results

The program evaluates the performance of this strategy over the given period. Each trade entry and exit point, along with its profit or loss, is displayed. Here's an example of the output:

1
2
3
4
5
6
Initial Balance: 10000.00 USD
Final Balance: 10032.75 USD

Trade Results:
Buy | Entry: 185.43 | Exit: 186.89 | Profit: 1.46 USD | Entry Date: 2024-01-31 | Exit Date: 2024-02-02
Sell | Entry: 183.21 | Exit: 182.71 | Profit: 0.50 USD | Entry Date: 2024-02-10 | Exit Date: 2024-02-14

Actual result:
With the trading rules implemented, The current balance was 10046.60 USD. if I adjust the trade amount from $1 to $5, I still get 10233.00 USD!

Chart Visualization

The chart includes:

  • Price movements with Bollinger Bands (upper, lower, and middle).
  • Entry and exit points for each trade marked on the chart.

Here’s an example of what the visualization looks like:

  • Blue Line: Closing prices.
  • Green Line: Upper Band.
  • Red Line: Lower Band.
  • Orange Line: SMA (Middle Band).
  • Trade Points: Green dots for Buy entries and red dots for Sell entries, with exit points marked as crosses.



Insights

This simulation demonstrates how the Bollinger Band breakout strategy can be used to identify potential trading opportunities. While the strategy does not guarantee profits, it offers a systematic approach to trend-following and momentum trading.


Next Steps

You can further refine this strategy by:

  1. Adjusting the Bollinger Band parameters (e.g., SMA length or standard deviation multiplier).
  2. Incorporating additional indicators for confirmation.
  3. Exploring advanced position-sizing methods to optimize returns.
Here is the complete program listing:
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import MetaTrader5 as mt5
import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime

# Initialize and connect to MetaTrader 5
account = nnnn  # Replace with your account number
password = "xxxx"  # Replace with your password
server = "MetaQuotes-Demo"  # Replace with your server

# Simulated starting balance
initial_balance = 10000  # Starting balance in USD
trade_amount = 1  # Trade size in USD
current_balance = initial_balance

if not mt5.initialize(login=account, password=password, server=server):
    print(f"Failed to initialize MT5: {mt5.last_error()}")
    quit()

# Symbol and timeframe
SYMBOL = "AAPL"
TIMEFRAME = mt5.TIMEFRAME_D1  # Daily timeframe

# Get historical data from 2024/04/01 to present
start_date = datetime(2024, 4, 1)
end_date = datetime.now()

rates = mt5.copy_rates_range(SYMBOL, TIMEFRAME, start_date, end_date)
if rates is None:
    print(f"Failed to retrieve data for {SYMBOL}")
    mt5.shutdown()
    quit()

# Convert to DataFrame
data = pd.DataFrame(rates)
data['time'] = pd.to_datetime(data['time'], unit='s')
data.set_index('time', inplace=True)

# Calculate Bollinger Bands
window = 20  # 20-day moving average
data['SMA'] = data['close'].rolling(window=window).mean()
data['STD'] = data['close'].rolling(window=window).std()
data['Upper Band'] = data['SMA'] + 2 * data['STD']
data['Lower Band'] = data['SMA'] - 2 * data['STD']

# Identify proximity to Bollinger Bands
threshold = 0.15  # Define "near" as within 1% of the band
data['Near Upper Band'] = abs(data['close'] - data['Upper Band']) / data['Upper Band'] < threshold
data['Near Lower Band'] = abs(data['close'] - data['Lower Band']) / data['Lower Band'] < threshold

# Simulate trades
open_trade = None  # Track the current open trade
trade_results = []  # Store trade outcomes

for i in range(len(data)):
    row = data.iloc[i]
    if open_trade is None:
        # Check for entry signals
        if (
            row['Near Upper Band']
            and data.iloc[i - 1]['Near Upper Band']
        ):
            open_trade = {
                'type': 'Buy',
                'entry_price': row['close'],
                'entry_date': row.name,
                'days_held': 0
            }
        elif (
            row['Near Lower Band']
            and data.iloc[i - 1]['Near Lower Band']
        ):
            open_trade = {
                'type': 'Sell',
                'entry_price': row['close'],
                'entry_date': row.name,
                'days_held': 0
            }
    else:
        # Update trade duration
        open_trade['days_held'] += 1

        # Calculate profit/loss
        if open_trade['type'] == 'Buy':
            profit = trade_amount * ((row['close'] - open_trade['entry_price']) )
        else:  # Sell trade
            profit = trade_amount * ((open_trade['entry_price'] - row['close']))

        # Check exit conditions
        if profit > 0 and open_trade['days_held'] <= 4:
            # Exit with profit within 4 days
            current_balance += profit
            trade_results.append({
                'type': open_trade['type'],
                'entry_price': open_trade['entry_price'],
                'exit_price': row['close'],
                'profit': profit,
                'entry_date': open_trade['entry_date'],
                'exit_date': row.name
            })
            open_trade = None
        elif open_trade['days_held'] > 3 and profit <= -0.5:
            # Exit if loss exceeds 50 cents after 4 days
            current_balance += profit
            trade_results.append({
                'type': open_trade['type'],
                'entry_price': open_trade['entry_price'],
                'exit_price': row['close'],
                'profit': profit,
                'entry_date': open_trade['entry_date'],
                'exit_date': row.name
            })
            open_trade = None

# Display trade results
print(f"Initial Balance: {initial_balance:.2f} USD")
print(f"Final Balance: {current_balance:.2f} USD")
print("\nTrade Results:")
for trade in trade_results:
    print(f"{trade['type']} | Entry: {trade['entry_price']:.2f} | Exit: {trade['exit_price']:.2f} | "
          f"Profit: {trade['profit']:.2f} USD | "
          f"Entry Date: {trade['entry_date']} | Exit Date: {trade['exit_date']}")

# Plot the chart with trades
plt.figure(figsize=(15, 8))
plt.plot(data.index, data['close'], label="Close Price", color="blue")
plt.plot(data.index, data['Upper Band'], label="Upper Band", color="green", alpha=0.5)
plt.plot(data.index, data['Lower Band'], label="Lower Band", color="red", alpha=0.5)
plt.plot(data.index, data['SMA'], label="Middle Band (SMA)", color="orange")

# Plot trade entry/exit points
for trade in trade_results:
    color = "green" if trade['type'] == 'Buy' else "red"
    marker = "o" if trade['type'] == 'Buy' else "x"
    plt.scatter(trade['entry_date'], trade['entry_price'], color="green", marker="o", zorder=5)
    plt.scatter(trade['exit_date'], trade['exit_price'], color="red", marker="x", zorder=5)

# Add a single entry and exit symbol to the legend
plt.scatter([], [], color="green", marker="o", label="Entry")
plt.scatter([], [], color="red", marker="x", label="Exit")

# Plot settings
plt.title(f"Bollinger Bands Strategy Simulation with Trade Rules")
plt.xlabel("Date")
plt.ylabel("Price")
plt.legend(loc="best")
plt.grid(True)

# Display the chart
plt.show()
# Shutdown MT5
mt5.shutdown()

Monday, December 2, 2024

Python Script to Connect to MetaTrader 5, Retrieve Ticker Info, Historical Data, and Run a Trading Bot

 In this blog post, we’ll explore a Python script that connects to MetaTrader 5 (MT5), retrieves ticker information, historical data, and executes trades with a simple trading bot. This example script demonstrates the power of MT5's Python API, offering a blend of real-time trading and data analysis, which is essential for developing automated trading systems.

Prerequisites

Before we dive into the code, make sure you have the following:

  1. MetaTrader 5 Installed: MT5 should be installed on your system.
  2. MetaTrader 5 Python API (MetaTrader5 module): You can install it via pip: pip install MetaTrader5
  3. A Demo Account on MetaTrader 5: This is required for trading simulations.
  4. Pandas Library: To handle the historical data in a DataFrame.

Script Breakdown

1. Initialize the MT5 Connection

To begin with, we need to establish a connection with MetaTrader 5. The script initializes the connection using login credentials, password, and the server name. If the connection fails, the script prints the error and exits.

2. Account Information and Symbol Selection

Next, the script retrieves and prints the account balance. It then checks if the specified symbol (in this case, TSLA) is available on the selected server. If the symbol is not available, the script shuts down the connection.

3. Retrieving Current Price

The script then retrieves the current price of the symbol. This information is essential for calculating the lot size for our trade request.

4. Trade Execution

Now that we have the current price, the script calculates the lot size based on the amount we wish to invest. The symbol_info.volume_min and symbol_info.volume_step are used to ensure that the lot size conforms to the broker’s requirements. A buy order is then created and sent.

5. Retrieving Historical Data

The script retrieves historical data for the given symbol within a defined date range. The data is then converted into a Pandas DataFrame, which allows for easy manipulation and analysis.

6. Shutdown MT5 Connection

Finally, after completing the data retrieval and trade, the script shuts down the MetaTrader 5 connection.

How This Script Works

  • Initialize Connection: Establishes a connection to MT5 with your demo account credentials.
  • Retrieve Ticker Info: Fetches the current price of the TSLA stock.
  • Calculate Lot Size: Based on the current price and the desired investment, the script calculates the appropriate lot size for the trade.
  • Execute Trade: A buy order for TSLA is placed with a specified lot size and price.
  • Historical Data Retrieval: It fetches daily historical data from 2022 to the present.
  • Shutdown: Cleanly shuts down the connection to MT5.
Bonus:

1. Run MT5 Without GUI (Headless Mode)

By default, when you run MetaTrader 5, the application window opens. However, you can prevent this window from opening by using a special configuration or by running MT5 in a detached or background mode.

To run MetaTrader 5 in headless mode:

  • MetaTrader 5 on Windows: You can start MT5 in headless mode by running the terminal with specific command-line arguments. The MT5 terminal itself does not need to be visible for the API (Python) to connect and interact with it.

Steps:

  1. Launch MT5 in Headless Mode:

    • Windows: You can start the MetaTrader 5 terminal with the -silent argument in the command line to suppress the GUI. Command: terminal.exe -silent

    • Connecting from Python: Once the terminal is running in the background (in headless mode), your Python script can connect to the MetaTrader 5 application just like it would in normal mode, and it will not open the MT5 application window.

Conclusion

This Python script provides a solid foundation for interacting with MetaTrader 5. It allows you to execute trades, retrieve real-time ticker information, and analyze historical market data using the MT5 Python API. You can expand this framework to include more advanced trading strategies, risk management features, and data analysis tools, making it a versatile tool for developing automated trading systems.

By combining Python's power with MetaTrader 5's trading capabilities, you open the door to fully automated trading, where your algorithm can handle trading decisions in real time. Happy coding, and may your trades be profitable!


Here is the complete program listing:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
import MetaTrader5 as mt5
import time
import pandas as pd
from datetime import datetime

# Account details
account = nnn #your username
password = "xxx"#password
server = "MetaQuotes-Demo" #server

SYMBOL = "TSLA"
AMOUNT = 1
start_date = datetime(2022, 1, 1)  # Start date for data
end_date = datetime.now()  # End date is the current date

# Initialize MetaTrader 5
if not mt5.initialize(login=account, password=password, server=server):
    print(f"Failed to initialize MetaTrader 5: {mt5.last_error()}")
    quit()

# Check account information
account_info = mt5.account_info()
if account_info:
    print(f"Account balance: {account_info.balance} USD")
else:
    print(f"Failed to retrieve account info: {mt5.last_error()}")
    mt5.shutdown()
    quit()

# Check and select symbol
if not mt5.symbol_select(SYMBOL, True):
    print(f"Symbol {SYMBOL} not available or not selected.")
    mt5.shutdown()
    quit()

# Get symbol info
symbol_info = mt5.symbol_info(SYMBOL)
if not symbol_info:
    print(f"Failed to retrieve symbol info for {SYMBOL}")
    mt5.shutdown()
    quit()

# Get current price
tick = mt5.symbol_info_tick(SYMBOL)
if not tick:
    print(f"Failed to get price info for {SYMBOL}")
    mt5.shutdown()
    quit()

price = tick.ask

# Calculate lot size
lot_size = max(symbol_info.volume_min, round((AMOUNT / price) / symbol_info.volume_step) * symbol_info.volume_step)

# Create trade request
trade_request = {
    "action": mt5.TRADE_ACTION_DEAL,
    "symbol": SYMBOL,
    "volume": lot_size,
    "type": mt5.ORDER_TYPE_BUY,
    "price": price,
    "deviation": 20,
#    "magic": 123456,
    "comment": "Buy TSLA $500 trade",
    "type_time": mt5.ORDER_TIME_GTC,
    "type_filling": mt5.ORDER_FILLING_FOK,  # Changed to IOC
}

# Optional: Wait for 1 second before sending the order
time.sleep(1)

# Send trade request
result = mt5.order_send(trade_request)

# Check trade result
if result.retcode == mt5.TRADE_RETCODE_DONE:
    print(f"Trade executed successfully. Ticket={result.order}")
else:
    print(f"Trade execution failed. Retcode={result.retcode}")
    print(f"Error details: {mt5.last_error()}")


# Retrieve historical data for the specified date range
rates = mt5.copy_rates_range(SYMBOL, mt5.TIMEFRAME_D1, start_date, end_date)

# Check if data was retrieved
if rates is None:
    print(f"Failed to retrieve data for {symbol}")
    mt5.shutdown()
    quit()

# Convert the data to a pandas DataFrame
data = pd.DataFrame(rates)

# Convert timestamps to datetime
data['time'] = pd.to_datetime(data['time'], unit='s')

# Set the time column as the index
data.set_index('time', inplace=True)

# Print the first few rows of the data
print(data.head())
# Shutdown MT5
mt5.shutdown()




Sunday, November 19, 2023

Building a Stock Screener in Python to Identify MACD Equals Signal Opportunities

 Introduction:

Stock screeners are invaluable tools for investors looking to identify potential trading opportunities. In this article, we'll explore how to create a simple yet effective stock screener in Python using financial data from Yahoo Finance. Specifically, we'll focus on scanning for stocks where the MACD (Moving Average Convergence Divergence) equals the Signal line within the last 24 hours. Check out my related article about MACD Trading Strategy(Making some modifications on my article "How I will trade using MACD as indicator").


Prerequisites:

Before diving into the code, make sure you have the required libraries installed. You can install them using:

pip install pandas yahoo_fin yfinance


The Python Code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import pandas as pd
from yahoo_fin import stock_info as si
import yfinance as yf
import datetime


# Fetch the list of Nasdaq tickers
df2 = pd.DataFrame(si.tickers_nasdaq())

# Set the date range for the stock data
end_date = datetime.datetime.today().strftime('%Y-%m-%d')
start_date = (datetime.datetime.today() - datetime.timedelta(days=1)).strftime('%Y-%m-%d')

# Loop through each stock ticker
for ticker in df2[0]:
    try:
        # Download stock data from Yahoo Finance
        data = yf.download(ticker, start=start_date, end=end_date, interval='1m')
        
        # Calculate MACD and Signal line
        # Calculate MACD
        data['ema_12'] = data['Close'].ewm(span=12, adjust=False).mean()
        data['ema_26'] = data['Close'].ewm(span=26, adjust=False).mean()
        data['macd'] = data['ema_12'] - data['ema_26']
        data['signal'] = data['macd'].ewm(span=9, adjust=False).mean()
        # Delete rows where macd and signal both equal 0
        data = data[(data['macd'] != 0) & (data['signal'] != 0)]
        # Check if there is an instance where MACD equals Signal
        if any(data['macd'] == data['signal']):
            print(f"Opportunity found for {ticker}: MACD equals Signal")
    except:
        pass  # Handle exceptions, e.g., delisted stocks or connection issues

Understanding the Code:

1. Fetching Tickers: We use the si.tickers_nasdaq() function from the yahoo_fin library to get a list of Nasdaq stock tickers. 
2. Downloading Stock Data: For each ticker, we use yfinance to download historical stock data for the last 24 hours with a 1-minute interval.
3. Calculating MACD and Signal:
    • data['ema_12'] = data['Close'].ewm(span=12, adjust=False).mean(): This line calculates the 12-period Exponential Moving Average (EMA) of the closing prices. It uses the ewm method in pandas, which stands for exponentially weighted moving average. The span parameter defines the window size, and adjust=False means that equal weights are assigned to all values in the window.
    • data['ema_26'] = data['Close'].ewm(span=26, adjust=False).mean(): Similar to the first line, this calculates the 26-period EMA of the closing prices.
    • data['macd'] = data['ema_12'] - data['ema_26']: This line computes the MACD line by subtracting the 26-period EMA from the 12-period EMA. The MACD line represents the difference between these two moving averages.
    • data['signal'] = data['macd'].ewm(span=9, adjust=False).mean(): Here, the code calculates the 9-period EMA of the MACD line, creating the signal line. The signal line is often used to generate buy or sell signals when it crosses the MACD line.
              4.Identifying Opportunities: We check if there is any instance where the MACD equals the Signal line. If found, we print a message indicating a potential trading opportunity.

              Conclusion:

              By building this simple stock screener in Python, you can quickly identify stocks exhibiting MACD equals Signal patterns. However, it's crucial to conduct further analysis and consider other factors before making any trading decisions. Additionally, remember to handle exceptions gracefully to ensure the robustness of your stock screener.

              This is just one of several scenarios, for complete details check the related article on my patreon.

              A Rule-Based Multi-Indicator Trading Strategy Built for Machine Learning

              This post presents a rule-based forex trading strategy using Stochastic Oscillator, RSI, MACD, and EMA-200 . On its own, the strategy deli...