English Русский 日本語
preview
MetaTrader 5 – Blaupause für maschinelles Lernen (Teil 7): Von verstreuten Experimenten zu reproduzierbaren Ergebnissen

MetaTrader 5 – Blaupause für maschinelles Lernen (Teil 7): Von verstreuten Experimenten zu reproduzierbaren Ergebnissen

MetaTrader 5Beispiele |
42 0
Patrick Murimi Njoroge
Patrick Murimi Njoroge

Einführung

In dieser Reihe haben wir wichtige Komponenten des maschinellen Lernens für den Handel behandelt: Datenstrukturen, Labeling und Meta-Labeling, Stichprobengewichtung und zeitlich bereinigte Kreuzvalidierung (Purged Cross-Validation). Doch diese Methoden, so leistungsfähig sie auch einzeln sein mögen, entfalten ihr volles Potenzial erst, wenn sie in ein einheitliches Forschungssystem eingebunden sind. In diesem Artikel werde ich zeigen, wie man diese Bausteine zu einer produktionsreifen Pipeline zusammenfügt, die Ad-hoc-Experimente in nachvollziehbare und auditierbare Forschungsergebnisse umwandelt, und dabei auf der in meinem vorherigen Artikel entwickelten Caching-Architektur aufbaut.

Der Code, den wir uns ansehen werden, ist nicht nur ein weiteres Beispiel, sondern ein wesentlicher Bestandteil des Systems, das ich zur Entwicklung von Handelsmodellen nutze. Es verarbeitet alles – von Roh-Tickdaten bis zu ONNX-Modellen für den Einsatz in MetaTrader 5, wobei dabei automatisch umfassende Protokollierungs-, Caching- und Analyseberichte erstellt werden. Der Übersichtlichkeit halber werde ich einige wichtige Themen nur am Rande behandeln, wie beispielsweise die Analyse der Merkmalsbedeutung und die Auswahl optimaler Schwellenwerte für die Triple-Barrier-Methode, um nur einige zu nennen. Dieser Artikel geht davon aus, dass die vorangegangenen Forschungsschritte bereits durchgeführt wurden, und konzentriert sich daher auf die Erstellung reproduzierbarer Pipelines. Was macht ein Forschungssystem „produktionsreif“?

  • Reproduzierbarkeit: Führen Sie denselben Code zweimal aus, um identische Ergebnisse zu erhalten.
  • Rückverfolgbarkeit: Für jedes Modell exakt nachvollziehen können, mit welchen Daten jedes Modell trainiert wurde.
  • Effizienz: Rechenintensive Berechnungen werden zwischengespeichert, sodass keine Arbeit doppelt ausgeführt werden muss
  • Validierung: Fehler erkennen, bevor sie im Live-Handel wirksam werden
  • Dokumentation: Automatische Berichte, die jede Entscheidung erläutern

Dateiorganisation


Das Problem: Forschungschaos

Bevor wir uns mit der Lösung befassen, sollten wir uns zunächst das Problem vor Augen führen. Die meisten Forschungsarbeiten zum Thema maschinelles Lernen im Handel sehen folgendermaßen aus:

# Somewhere in notebook cell 47... 

df = load_data("EURUSD", "2020-01-01", "2023-12-31") 
features = calculate_indicators(df)
X_train, y_train = features[:-1000], labels[:-1000]
model = RandomForestClassifier(n_estimators=100) 
model.fit(X_train, y_train) 

# Score: 0.67 - is that good? What parameters did I use?
# When did I run this? Which data version?

Drei Monate später lassen sich diese Ergebnisse nicht mehr reproduzieren. Die Daten haben sich geändert. Sie wissen nicht mehr, welche Merkmale Sie genutzt haben. Die Modelldatei enthält keine Metadaten. Sie stehen wieder ganz am Anfang.

Das ist nicht nur lästig, sondern auch gefährlich. Im Live-Handel bedeutet eine nicht reproduzierbare Analyse, dass Sie Ihrem Backtest nicht vertrauen können. Fehler lassen sich nicht debuggen. Man kann nicht sicher iterieren.



Die Lösung: Eine methodisch fundierte Pipeline

Unser Forschungssystem geht diese Probleme anhand von vier Leitprinzipien an:

  1. Zeitabhängiges Caching: Lassen Sie niemals zukünftige Daten in das Training einfließen, sondern speichern Sie alles sicher im Cache.
  2. Automatische Dokumentation: Erstellen Sie in jeder Phase umfassende Berichte
  3. Strukturierte Speicherung: Navigieren Sie durch Ihre Experimente nach Strategie, Symbol und Parametern
  4. Validierung zuerst: Fehler in der Forschung aufspüren, nicht in der Produktion

Hier ist die allgemeine Architektur:

Jede Phase wird zwischengespeichert, jede Entscheidung protokolliert, jede Ausgabe überprüft.

Ich möchte noch einmal betonen, dass es sich hierbei um eine übergeordnete Architektur handelt, die keineswegs darauf ausgelegt ist, alle Schritte des Entwicklungsprozesses abzudecken. 



Intelligente Datenarchitektur: Warum ein Tick-RAM-Cache plus Parquet-Bars alles verändert.

Bevor wir uns mit den einzelnen Phasen der Pipeline befassen, wollen wir uns zunächst mit einer entscheidenden architektonischen Entscheidung beschäftigen, die das gesamte System erst praktikabel macht: der Strategie zur Datenverarbeitung.

Das Dilemma mit den Tick-Daten

Bei der Entwicklung von Handelsstrategien stehen Sie vor einem klassischen Optimierungsproblem:

Option 1: Bars direkt vom Broker laden (MetaTrader 5)

Option 2: Alles auf der Festplatte zwischenspeichern


Option 3: Hybridansatz (die Lösung)


  • ✅ Aktuelle Daten
  • ✅ Kein Speicher-Overhead
  • ❌ Langsam (Netzwerklatenz)
  • ❌ Uneinheitlich (es können Lücken beim Broker bestehen)
  • ❌ Historische Experimente lassen sich nicht reproduzieren
  • ✅ Schnelle Folgeladungen
  • ✅ Reproduzierbar
  • ❌ Enormer Speicherplatzbedarf (die Tick-Daten sind sehr umfangreich)
  • ❌ Langsame Festplatten-E/A
  • ❌ Komplexität der Cache-Invalidierung
  • ✅ Geschwindigkeit des RAM-Caches für Tick-Daten
  • ✅ Effizienz von in Parquet gespeicherten Bars
  • ✅ Keine Datenduplizierung
  • ✅ Mit minimalem Speicherplatz reproduzierbar

Ich möchte Ihnen zeigen, warum dieser dritte Ansatz genial ist.

Der TickDataLoader: RAM-Caching richtig umgesetzt

Hier ist die entscheidende Erkenntnis: Tick-Daten sollten im Arbeitsspeicher (RAM) und nicht auf der Festplatte gespeichert werden, da es sich dabei um das Ausgangsmaterial handelt, das Sie auf vielfältige Weise verarbeiten. Die Klasse TickDataLoader kontrolliert alle Daten, die bereits in der aktuellen Sitzung geladen wurden. Wenn wir unsere Modellentwicklung mit EURUSD-Daten aus dem Jahr 2023 beginnen und später beschließen, Daten aus den Jahren 2021–2022 hinzuzufügen, wird nur der fehlende Teil geladen – der Cache wird nahtlos erweitert. Stattdessen ruft TickDataLoader nur die Daten ab, die sich nicht im Cache befanden, und fügt sie den bereits geladenen Daten hinzu. Der folgende Code ist ein Ausschnitt aus der Klasse TickDataLoader. Den vollständigen Code finden Sie in der beigefügten Datei model_development.py.

class TickDataLoader:
    """
    Loader for tick-level bid/ask data with intelligent local caching.

    Features:
    1. Smart caching that checks if requested date range is within cached ranges
    2. Handles partial overlaps by reusing available cached data
    3. Memory management with cache size limits
    4. Cache statistics tracking

    Notes
    -----
    - Typical performance: ~0.5s for cached retrieval
    - Memory usage: ~100MB per 1M ticks
    """

    def __init__(self, max_cache_size_mb: int = 3000, max_cached_symbols: int = 20):
        """
        Initialize the tick data loader.

        Parameters
        ----------
        max_cache_size_mb : int, optional
            Maximum cache size in MB (default: 5000MB)
        max_cached_symbols : int, optional
            Maximum number of symbols to keep in cache (default: 20)
        """
        self._cache: Dict[Tuple[str, str], pd.DataFrame] = {}  # (symbol, account_name) -> DataFrame
        self._cache_metadata: Dict[Tuple[str, str], Dict] = {}  # (symbol, account_name) -> metadata
        self.max_cache_size_mb = max_cache_size_mb
        self.max_cached_symbols = max_cached_symbols
        self.cache_stats = {
            "hits": 0,
            "misses": 0,
            "partial_hits": 0,
            "total_loaded": 0,
        }

    def get_tick_data(
        self, symbol: str, start_date: str, end_date: str, account_name: str
    ) -> pd.DataFrame:
        """
        Retrieve tick-level bid/ask data with intelligent caching.

        Parameters
        ----------
        symbol : str
            Trading instrument symbol (e.g., 'EURUSD')
        start_date : str
            Start date in 'YYYY-MM-DD' format
        end_date : str
            End date in 'YYYY-MM-DD' format
        account_name : str
            MT5 account identifier for data retrieval

        Returns
        -------
        pd.DataFrame
            Tick data with columns ['bid', 'ask'] indexed by timestamp

        Notes
        -----
        - Checks if cached data fully covers requested date range
        - If partial coverage exists, loads only missing data
        - Merges cached and newly loaded data seamlessly
        """
        cache_key = (symbol, account_name)
        start_dt, end_dt = date_conversion(start_date, end_date)

        # Check if we have cached data for this symbol/account
        if cache_key in self._cache:
            cached_df = self._cache[cache_key]
            metadata = self._cache_metadata[cache_key]
            cached_start, cached_end = date_conversion(metadata["start_date"], metadata["end_date"])

            # Check if cached data fully covers requested range
            if cached_start <= start_dt and cached_end >= end_dt:
                self.cache_stats["hits"] += 1
                logger.debug(f"Cache hit for {symbol} {start_date} to {end_date}")

                # Return subset of cached data
                mask = (cached_df.index >= start_dt) & (cached_df.index <= end_dt)
                return cached_df[mask].copy()

            # Check if there's partial overlap
            if cached_end >= start_dt and cached_start <= end_dt:
                self.cache_stats["partial_hits"] += 1
                logger.debug(f"Partial cache hit for {symbol}")
                return self._load_with_partial_cache(
                    symbol, start_date, end_date, account_name, cache_key
                )

        # No cache hit, load all data
        self.cache_stats["misses"] += 1
        logger.debug(f"Cache miss for {symbol} {start_date} to {end_date}")
        return self._load_and_cache_data(symbol, start_date, end_date, account_name, cache_key)

Das System geht intelligent mit drei Szenarien um.


Entscheidungsablauf von TickDataLoader

Im Folgenden finden Sie ein Beispiel für die Verwendung der Klasse TickDataLoader:

# Initialize the loader once per session
from ..production.model_development import TickDataLoader

loader = TickDataLoader(
    max_cache_size_mb=3000, # 3GB RAM limit
    max_cached_symbols=20, # Cache up to 20 symbols
)

# First request - loads from disk/MT5 and caches
ticks = loader.get_tick_data(
    symbol="EURUSD",
    start_date="2023-01-01",
    end_date="2023-12-31",
    account_name="FUNDEDNEXT_STLR2_6K"
)
# Takes ~30 seconds (first load)

# Second request - same data, instant from RAM
ticks = loader.get_tick_data(
    symbol="EURUSD",
    start_date="2023-01-01",
    end_date="2023-12-31",
    account_name="FUNDEDNEXT_STLR2_6K"
)
# Takes ~0.5 seconds (cache hit)

# Partial request - subset is instant
ticks = loader.get_tick_data(
    symbol="EURUSD",
    start_date="2023-01-01",
    end_date="2023-03-31",
    account_name="FUNDEDNEXT_STLR2_6K"
)
# Takes ~0.5 seconds (subset from cache)

ticks = loader.get_tick_data(
    symbol="EURUSD",
    start_date="2022-01-01",
    end_date="2023-12-31",
    account_name="FUNDEDNEXT_STLR2_6K"
)
# Takes ~15 seconds (only 2022 data)

Im Folgenden sind Merkmale aufgeführt, die Tick-Daten verwenden:

# Example of features that use tick data

from ..util.volatility import two_time_scale_realized_vol

def two_time_scale_realized_vol(
    tick_prices: pd.Series, slow_freq: str = "5min"
) -> float:
    """
    Two-Time-Scale Realized Volatility Estimator

    Advanced estimator for tick data that removes microstructure noise while
    preserving information. Combines high-frequency and low-frequency sampling
    to extract true volatility signal.

    **What it measures:**
    - True underlying volatility from noisy tick data
    - Removes bid-ask bounce and other microstructure effects
    - Preserves information lost in simple low-frequency sampling

    **Mathematical foundation:**
    - TSRV = RV_slow - (n_slow/n_fast) * (RV_fast - RV_slow)
    - Uses ratio of observation counts to properly scale noise estimate
    - Asymptotically consistent under jump-diffusion models

    **Best used for:**
    - High-frequency trading strategies
    - When you have access to tick data
    - Precision-critical applications (research, risk management)
    - Markets with significant microstructure noise

    **Advantages:**
    - Most accurate volatility estimate for tick data
    - Removes upward bias from microstructure noise
    - Retains more information than sparse sampling
    - Theoretically well-founded

    **Computational considerations:**
    - More intensive than simple realized volatility
    - Requires choice of slow sampling frequency
    - Benefits increase with data quality and frequency

    **Typical slow frequencies:**
    - 1 minute: Very liquid assets, high precision needed
    - 5 minutes: Most common, good noise reduction
    - 15-30 minutes: Less liquid assets

    :param tick_prices: (pd.Series) Tick-level price data, datetime indexed
    :param slow_freq: (str) Slow sampling frequency ('5min', '1min', etc.)
    :return: (float) Two-time-scale realized volatility estimate
    """
    # Fast scale (tick-by-tick)
    tick_returns = np.log(tick_prices / tick_prices.shift(1)).dropna()
    rv_fast = (tick_returns**2).sum()
    n_fast = len(tick_returns)

    # Slow scale (e.g., 5-minute)
    slow_prices = tick_prices.resample(slow_freq).last().dropna()
    slow_returns = np.log(slow_prices / slow_prices.shift(1)).dropna()
    rv_slow = (slow_returns**2).sum()
    n_slow = len(slow_returns)

    # Two-time-scale estimator with proper scaling
    if n_fast > 0 and n_slow > 0:
        tsrv = rv_slow - (n_slow / n_fast) * (rv_fast - rv_slow)
        return max(tsrv, 0)  # Ensure non-negative result
    else:
        return rv_slow


two_time_scale_vol = two_time_scale_realized_vol(tick_prices=ticks["bid"], slow_freq="5min")

Warum RAM statt Festplatte?

Denken Sie einmal über Ihren typischen Forschungsablauf nach:

# You might experiment with:
tick_bars_M1 = make_bars(ticks, "tick", "M1", "mid_price")
tick_bars_M5 = make_bars(ticks, "tick", "M5", "mid_price")
volume_bars = make_bars(ticks, "volume", 1000, "mid_price")
dollar_bars = make_bars(ticks, "dollar", 5000, "mid_price")

# And different price types:
tick_bars_bid = make_bars(ticks, "tick", "M1", "bid")
tick_bars_ask = make_bars(ticks, "tick", "M1", "ask")
Sie erstellen 6 verschiedene Bar-Typen aus denselben Tick-Daten. Wenn Sie Ticks und Bars auf der Festplatte zwischenspeichern würden, würden Sie dieselben Basisdaten mehrfach in leicht unterschiedlichen Aggregationen speichern. Das ist Verschwendung. Möglicherweise entscheiden Sie sich auch dafür, mikrostrukturelle Merkmale zu erstellen (wofür Tick-Daten erforderlich sind). Aus diesen Gründen ist es am besten, die Tick-Daten im RAM zu speichern, falls Sie sie später einmal benötigen sollten.

Stattdessen:

  • Ticks werden im RAM gespeichert (schneller Zugriff, werden zwischen den Sitzungen gelöscht)
  • Jede einzelne Barkonfiguration wird im Parquet-Format zwischengespeichert (persistent, kompakt)
  • Die Tick-Daten dienen als verbindliche Referenzquelle für die Sitzung.
  • Parquet-Dateien dienen als kompilierte Artefakte für die Langzeitarchivierung

Die Architektur aus TickDataLoader + Parquet funktioniert, weil sie einer grundlegenden Erkenntnis der Forschung im Bereich des maschinellen Lernens Rechnung trägt:

Rohdaten sollten schnell verfügbar sein (RAM), verarbeitete Daten sollten dauerhaft gespeichert werden (Parquet), und man sollte niemals etwas duplizieren, was man neu generieren kann.

Diese Datenarchitektur lässt sich von der individuellen Forschung bis hin zur Zusammenarbeit im Team skalieren. Wenn alle mit Parquet-Dateien arbeiten, ergibt sich Folgendes:

  • Einheitliche Datentypen (kein „Auf meinem Rechner funktioniert es“)
  • Effiziente Speicherung (kompatibel mit Versionskontrollsystemen)
  • Schnelle Iteration (die Kosten der Experimente nähern sich Null)
  • Reproduzierbarkeit (Parquet-Dateien sind binärstabil)

Vergleichen Sie dies mit CSV-basierten Arbeitsabläufen, bei denen Sie ständig mit folgenden Problemen zu kämpfen haben:

  • Inkonsistente Datumsauswertung
  • Probleme mit der Gleitkommagenauigkeit
  • Kodierungsprobleme
  • Lange Ladezeiten
  • Große Dateigrößen
  • Probleme bei der Schemavalidierung

Die Anfangsinvestition in die Einrichtung eines Parquet-basierten Speichers zahlt sich jeden Tag aus. Und wenn man dies mit einem intelligenten RAM-Caching der Quelldaten (Ticks) kombiniert, erhält man ein System, das sowohl schnell als auch effizient ist – ganz ohne Kompromisse.


Der Arbeitsablauf

Lassen Sie mich Ihnen ein konkretes Beispiel erläutern: die Entwicklung einer Bollinger-Band-Umkehrstrategie für den EURUSD. Wir werden genau sehen, was in jeder Phase passiert und welche Artefakte dabei entstehen.

Phase 1: Konfiguration

Alles beginnt mit der Konfiguration. Keine über mehrere Dateien verstreuten Parameter, sondern eine einzige maßgebliche Konfigurationsquelle:

from model_development import ModelDevelopmentPipeline
from strategies import BollingerBandStrategy

# Data configuration
data_config = {
    "symbol": "EURUSD",
    "train_start": "2023-01-01",
    "train_end": "2023-12-31",
    "account_name": "FUNDEDNEXT_STLR2_6K",
    "bar_type": "tick",
    "bar_size": "M1",  # Will be converted to tick count
    "price": "mid_price"
}

# Feature configuration
feature_config = {
    "func": calculate_bollinger_features,
    "params": {
        "window": 20,
        "num_std": 2.0
    }
}

# Triple-barrier labeling
target_config = {
    "func": get_daily_vol,
    "params": {"lookback": 20}
}

label_config = {
    "profit_target": 1.0,
    "stop_loss": 2.0,
    "max_holding_period": {"days": 1},
    "min_ret": 0.0,
    "vertical_barrier_zero": True,
    "filter_as_series": False
}

# Model training parameters
model_params = {
    "pipe_clf": RandomForestClassifier(
        criterion="entropy",
        class_weight="balanced_subsample",
        min_weight_fraction_leaf=0.05
    ),
    "param_grid": {
        "n_estimators": [100, 200, 300],
        "max_depth": [3, 5, 7, 10],
        "min_samples_split": [2, 5, 10]
    },
    "cv_splits": 5,
    "bagging_n_estimators": 10,
    "n_jobs": -1,
    "random_state": 42
}

# Initialize strategy
strategy = BollingerBandStrategy(
    window=20,
    std=2.0,
)

Beachten Sie, dass jeder Parameter explizit angegeben und dokumentiert ist. In drei Monaten werden Sie genau wissen, was Sie getan haben.

Stufe 2: Initialisierung der Pipeline

Nun erstellen wir die Pipeline mit intelligenter Dateiverwaltung:

pipeline = ModelDevelopmentPipeline(
    strategy=strategy,
    data_config=data_config,
    feature_config=feature_config,
    target_config=target_config,
    label_config=label_config,
    model_params=model_params,
    base_dir="Models"
)
Im Hintergrund wird dadurch eine Verzeichnisstruktur erstellt:

 
Models/
  └── BollingerBandStrategy/ 
    └── EURUSD/ 
      └── FUNDEDNEXT_STLR2_6K/ 
        └── tick/ 
          └── M1/ 
            └── 20230101_20231231/ 
              └── a3f7b2e9/ # Config hash 
                ├── config.json 
                ├── logs/ 
                ├── plots/ 
                └── reports/ 
Jede Konfiguration erhält ein eigenes Verzeichnis. Kein „model_v2_final_WIRKLICH_FINAL.pkl“ mehr.

Phase 3: Ausführung

Nachdem die Konfiguration abgeschlossen ist, führen wir die gesamte Pipeline aus:

model, features, metrics, config = pipeline.run(
    generate_reports=True,
    save=True,
    export_onnx=True,
    verbose=True
)
Jetzt beginnt der Zauber. Schauen wir uns einmal an, was in den einzelnen Phasen geschieht:

Schritt 1: Laden der Daten (mit Smart Caching)

[Schritt 1/7] Trainingsdaten werden geladen...

✓ Cache-Treffer für EURUSD vom 01.01.2023 bis zum 31.12.2023
✓ 15.847.392 Ticks in 0,4 s abgerufen
✓ 262.800 M1-Bars erzeugt

Das System prüft, ob wir diese Daten bereits geladen haben. In diesem Fall werden sie innerhalb von Millisekunden statt Minuten aus dem Cache abgerufen. Aber hier kommt der entscheidende Punkt: Das Caching ist zeitabhängig. Es protokolliert, auf welche Daten wann zugegriffen wurde, und verhindert so, dass zukünftige Daten in Ihren Trainingsdatensatz gelangen.

# From model_development.py
@cacheable(time_aware=True)
def load_and_prepare_training_data(symbol, start_date, end_date, ...):
    tick_df = loader.get_tick_data(symbol, start_date, end_date, account_name)
    data = make_bars(tick_df, bar_type, bar_size, price)
    
    # Log data access for contamination tracking
    log_data_access(
        dataset_name=f"{symbol}_{bar_type}_{bar_size}_{price}".lower(),
        start_date=data.index[0],
        end_date=data.index[-1],
        purpose="train"
    )
    
    return data
Schritt 2: Feature-Engineering

[Schritt 2/7] Merkmale werden berechnet...

✓ 47 Merkmale generiert
  - 12 Merkmale des Bollinger-Bands
  - 15 Momentum-Merkmale
  - 8 volumenbasierte Merkmale
  - 12 zeitbasierte Merkmale

Auch Merkmale werden zwischengespeichert, allerdings mit einem Abhängigkeitsgraphen:

@cacheable(time_aware=True)
def create_feature_engineering_pipeline(data, feature_config, data_config):
    func = feature_config["func"]
    features = func(data, **feature_config["params"])
    time_feat = get_time_features(data, timeframe=data_config["bar_size"])
    return features.join(time_feat).dropna()
Wenn sich Ihre Feature-Funktion ändert, wird der Cache automatisch ungültig gemacht. Wenn sich nur die Daten ändern, werden die Merkmale effizient neu berechnet.

Schritt 3: Label-Erstellung

[Schritt 3/7] Ereignisse werden generiert...

✓ 89.247 Ereignisse generiert
✓ Verteilung der Labels:
  - Long   (1): 38.562 (43,2 %)
  - None   (0): 24.891 (27,9 %)
  - Short (-1): 25.794 (28,9 %)

Hier erfolgt das Labeling nach der Triple-Barrier-Methode unter Verwendung der von uns festgelegten Konfiguration:

@cacheable()
def generate_events_triple_barrier(data, strategy, target_config, 
                                   profit_target, stop_loss, ...):
    # Calculate dynamic barriers based on volatility
    close = data["close"]
    fn = target_config["func"]
    params = target_config["params"]
    target = fn(close, **params)
    
    # Get entry signals from strategy
    side, t_events = get_entries(strategy, data, target.mean())
    
    # Apply triple-barrier method
    vb = add_vertical_barrier(t_events, close, **max_holding_period)
    events = triple_barrier_labels(close, target, t_events, 
                                   vertical_barrier_times=vb,
                                   side_prediction=side,
                                   pt_sl=[profit_target, stop_loss], ...)
    
    # Add sample weights
    events = get_event_weights(events, close)
    return events

Schritt 4: Optimierung der Stichprobengewichtung

Jetzt wird es erst richtig spannend. Anstatt die Stichprobengewichte zu schätzen, suchen wir nach dem optimalen Gewichtungsschema:

[Schritt 4/7] Berechnung der Stichprobengewichte...

  Testen verschiedener Gewichtungsschemata:
    - ungewichtet + linearer Abklingvorgang
    - ungewichtet + exponentieller Abklingfaktor
    - ertragsbasiert + linearer Abklingfaktor
    - ertragsbasiert + exponentieller Abklingfaktor
    - Einzigartigkeit + linearer Abklingvorgang
    - Einzigartigkeit + exponentieller Abklingvorgang

  ✓ Bestes Schema: uniqueness_linear_0.7428 (CV-Wert: 0,6847)

Das System probiert verschiedene Gewichtungsansätze aus und wählt denjenigen aus, der bei der bereinigten Kreuzvalidierung die beste Leistung erbringt:

@cacheable()
def get_optimal_sample_weight(
    data_index: pd.DatetimeIndex,
    events: pd.DataFrame,
    features: pd.DataFrame,
    cv_splits: int = 5,
    n_iter: int = 10,
) -> pd.Series:
    """
    Compute best sample weight with time decay.

    Parameters
    ----------
    data_index: pd.DatetimeIndex
        Price data index.
    events : pd.DataFrame
        Event labels with uniqueness weights.
    features: pd.DataFrame
        Training features
    cv_splits : int, optional
        Number of cross-validation splits (default: 5).
    n_iter : int, optional
        Number of random search iterations (default: 10).

    Returns
    -------
    weights : pd.Series
        Computed sample weights.
    cv_results : dict
        Cross-validation results.
    """
    valid_index = features.index.intersection(events.index)
    cont = events.loc[valid_index]
    X = features.loc[valid_index]
    y = cont["bin"]

    classifier = RandomForestClassifier(
        criterion="entropy",
        class_weight="balanced_subsample",
        max_samples=cont["tW"].mean(),
        max_depth=4,
        min_weight_fraction_leaf=0.05,
    )
    scoring = "f1" if set(y.unique()) == {0, 1} else "neg_log_loss"
    cv_gen = PurgedKFold(n_splits=cv_splits, t1=cont["t1"], pct_embargo=0.02)
    weights = [
        ("return", cont["w"]),
        ("unweighted", pd.Series(1.0, index=cont.index)),
        ("uniqueness", cont["tW"]),
    ]
    best_score = 0
    cv_results = pd.DataFrame()

    for scheme, weight in tqdm(weights, desc="Analyzing weighting schemes", total=len(weights)):
        scores = ml_cross_val_score(
            classifier,
            X,
            y,
            cv_gen,
            sample_weight_train=weight,
            sample_weight_score=weight,
            scoring=scoring,
        )
        score = scores.mean()
        cv_results[scheme] = scores

        if not np.isinf(score) and score > best_score:
            best_score = score
            best_weight = weight
            best_scheme = scheme

    est = weighted_estimator(classifier, cont, data_index)
    param_distributions = {
        "scheme": [best_scheme],
        "decay": uniform(0, 1),  # decay factor between 0 and 1 inclusive
        "linear": [True, False],
    }
    gs = RandomizedSearchCV(
        estimator=est,
        param_distributions=param_distributions,
        n_iter=n_iter,
        cv=cv_gen,
        scoring=scoring,
        n_jobs=-1,
        random_state=42,
        refit=False,
    )
    gs.fit(X, y)

    scheme, decay, linear = [gs.best_params_[k] for k in ["scheme", "decay", "linear"]]
    best_scheme = f"{scheme}_{'linear' if linear else 'exp'}_{decay}"
    logger.info(f"Best sample weight scheme: {best_scheme}")

    decay_vec = get_weights_by_time_decay_optimized(
        triple_barrier_events=cont,
        close_index=data_index,
        last_weight=decay,
        linear=linear,
        av_uniqueness=cont["tW"],
    )

    best_weight *= decay_vec

    cv_results = {
        "best_score": best_score,
        "cv_results_scheme": cv_results,
        "cv_results": pd.DataFrame(gs.cv_results_),
        "scoring": scoring,
        "best_scheme": best_scheme,
    }

    return best_weight, cv_results

Auf diese Weise finden Sie heraus, welches Gewichtungsschema Ihrer spezifischen Strategie tatsächlich am meisten nützt.

Schritt 5: Meta-Merkmale

[Schritt 5/7] Berechnung der rollierenden Meta-Label-Merkmale...

  ✓ 8 Meta-Merkmale hinzugefügt
    - rolling_accuracy_20, rolling_accuracy_50
    - rolling_precision_20, rolling_precision_50
    - rolling_recall_20, rolling_recall_50
    - rolling_f1_20, rolling_f1_50

Anhand dieser Merkmale lässt sich feststellen, wie gut das Modell in letzter Zeit abgeschnitten hat, was die Leistung beim Meta-Labeling verbessern kann. Die Berechnung wurde mit Numba auf Geschwindigkeit optimiert:

@njit(parallel=True, fastmath=True, cache=True)
def _rolling_metrics_numba(y_true, y_pred, weights, window):
    """Numba-accelerated rolling metrics calculation."""
    n = len(y_true)
    accuracy = np.full(n, np.nan)
    precision = np.full(n, np.nan)
    recall = np.full(n, np.nan)
    f1 = np.full(n, np.nan)

    for i in prange(window - 1, n):
        start = i - window + 1
        tp = fp = tn = fn = 0.0

        # Inner loop for window
        for j in range(start, i + 1):
            if y_true[j] == 1 and y_pred[j] == 1:
                tp += weights[j]
            elif y_true[j] == 0 and y_pred[j] == 1:
                fp += weights[j]
            elif y_true[j] == 0 and y_pred[j] == 0:
                tn += weights[j]
            elif y_true[j] == 1 and y_pred[j] == 0:
                fn += weights[j]

        total = tp + fp + tn + fn
        if total > 0:
            accuracy[i] = (tp + tn) / total

        denom_prec = tp + fp
        if denom_prec > 0:
            precision[i] = tp / denom_prec

        denom_rec = tp + fn
        if denom_rec > 0:
            recall[i] = tp / denom_rec

        if not np.isnan(precision[i]) and not np.isnan(recall[i]):
            denom_f1 = precision[i] + recall[i]
            if denom_f1 > 0:
                f1[i] = 2 * (precision[i] * recall[i]) / denom_f1

    return accuracy, precision, recall, f1

Schritt 6: Modelltraining mit Hyperparameter-Suche

Dies ist das Herzstück des Systems – intelligente Hyperparameter-Optimierung mit ordnungsgemäßer Kreuzvalidierung:

[Schritt 6/7] Modell mit Kreuzvalidierung trainieren...

  Fortschritt der Rastersuche: Testen von 81 Parameterkombinationen

  Verwendung einer 5-fachen, bereinigten Kreuzvalidierung

  Embargo: 1 % zwischen den Folds

  [████████████████████████████████████] 81/81 (100 %)

  ✓ Optimale Parameter ermittelt:
    - n_estimators: 200
    - max_depth: 7
    - min_samples_split: 5

  ✓ Kreuzvalidierungswerte:
    - Mittelwert F1: 0,6847 ± 0,0234
    - Fold 0: 0,6923
    - Fold 1: 0,6801
    - Fold 2: 0,6889
    - Fold 3: 0,6712
    - Fold 4: 0,6912

  ✓ Bagging-Ensemble aus trainierten Modellen (10 Schätzer)...
  ✓ Modelltraining abgeschlossen

Bei der Hyperparameter-Suche wird eine purged k-fold-Kreuzvalidierung verwendet, um Datenleckagen zu verhindern:

def train_model_with_cv(
    features: pd.DataFrame,
    events: pd.DataFrame,
    sample_weight: np.ndarray,
    pipe_clf: Union[ClassifierMixin, Pipeline],
    param_grid: Dict,
    cv_splits: int = 5,
    bagging_n_estimators: int = 0,
    bagging_max_samples: float = 1.0,
    bagging_max_features: float = 1.0,
    rnd_search_iter: int = 0,
    n_jobs: int = -1,
    pct_embargo: float = 0.02,
    random_state: int = None,
    verbose: int = 0,
) -> Tuple[RandomForestClassifier, Dict]:
    """
    Train model with cross-validation using cached hyperparameter search.

    Parameters
    ----------
    features : pd.DataFrame
        Feature matrix.
    events : pd.DataFrame
        Event labels.
    sample_weight : np.ndarray
        Sample weights aligned with events.
    pipe_clf : sklearn.Pipeline
        Pipeline including classifier.
    param_grid : dict
        Hyperparameter grid for search.
    cv_splits : int, default=5
        Number of CV splits.
    bagging_n_estimators : int, default=0
        Number of bagging estimators.
    bagging_max_samples : float, default=1.0
        Max samples for bagging.
    bagging_max_features : float, default=1.0
        Max features for bagging.
    rnd_search_iter : int, default=0
        Randomized search iterations.
    n_jobs : int, default=-1
        Parallel jobs.
    pct_embargo : float, default=0.02
        Embargo percentage for purging CV splits.
    random_state : int, optional
        Random seed.
    verbose : int, default=0
        Verbosity flag.

    Returns
    -------
    best_model : RandomForestClassifier
        Trained best model.
    cv_results : dict
        Cross-validation results.

    """
    valid_index = features.index.intersection(events.index)
    cont = events.loc[valid_index]
    X = features.loc[valid_index]
    y = cont["bin"]
    t1 = cont["t1"]
    w = sample_weight.loc[valid_index]

    best_model, cv_results = clf_hyper_fit(
        features=X,
        labels=y,
        t1=t1,
        pipe_clf=pipe_clf,
        param_grid=param_grid,
        cv=cv_splits,
        bagging_n_estimators=bagging_n_estimators,
        bagging_max_samples=bagging_max_samples,
        bagging_max_features=bagging_max_features,
        rnd_search_iter=rnd_search_iter,
        n_jobs=n_jobs,
        pct_embargo=pct_embargo,
        random_state=random_state,
        verbose=verbose,
        sample_weight=w,
    )

    return best_model, cv_results

  • Leistungsverteilung über alle Parameterkombinationen hinweg
  • Streudiagramm: Punktzahl vs. Stabilität
  • Analyse des Verhältnisses von Punktzahl zu Trainingszeit
  • Rangliste der Parameter nach ihrer Bedeutung
  • Konsistenz der Kreuzvalidierungsfolds

Schritt 7: Analyse der Merkmalsbedeutung

[Step 7/7] Analyzing feature importance...
2025-12-16 11:18:21 [info     ] pipeline_step                  duration_seconds=0.1509995460510254 metrics={'top_feature': 'rolling_accuracy_20'} status=completed step=feature_analysis

Top 10 Features:
            feature  importance
rolling_accuracy_20    0.166330
      rolling_f1_20    0.132372
rolling_accuracy_50    0.131260
      rolling_f1_50    0.109463
             spread    0.029035
             d1_vol    0.025773
        hour_cos_h2    0.022493
        ret_1_lag_1    0.022391
   parkinson_vol_10    0.019278
 london_session_vol    0.018018 

Die Analyse der Merkmalsbedeutung hilft uns zu verstehen, was das Modell gelernt hat, und potenzielle Probleme zu erkennen.

Phase 4: Automatisierte Berichterstellung

Nach Abschluss des Trainings erstellt das System automatisch umfassende Analyseberichte. So sieht die Ausgabe nach Abschluss des Vorgangs aus:

2025-12-16 11:14:08 | afml.production.model_development | INFO | Starting pipeline for EURUSD
2025-12-16 11:14:08 | afml.production.model_development | INFO | Training period: 2022-01-01 to 2023-12-31
2025-12-16 11:14:08 | afml.production.model_development | INFO | Output directory: Models\Bollinger_w10_std1_5\EURUSD\FUNDEDNEXT_STLR2_6K\time\M1\20220101_20231231\bdad004f

======================================================================
PRODUCTION MODEL DEVELOPMENT PIPELINE
======================================================================

Configuration
--------------------------------------------------
strategy                 Bollinger_w10_std1.5
symbol                                 EURUSD
training_start                     2022-01-01
training_end                       2023-12-31
account_name              FUNDEDNEXT_STLR2_6K
bar_type                                 time
bar_size                                   M1
price                               mid_price
target_lookback                            10
profit_target                               1
stop_loss                                   2
max_holding_period                {'days': 1}
min_ret                                     0
vertical_barrier_zero                    True
filter_as_series                        False
2025-12-16 11:14:09 [info     ] pipeline_step                  status=started step=pipeline_start strategy=Bollinger_w10_std1.5 symbol=EURUSD train_period=2022-01-01_2023-12-31

[Step 1/7] Loading training data...
2025-12-16 11:14:09 | afml.cache.unified_cache_system | DEBUG | Cache HIT: afml.production.model_development.load_and_prepare_training_data (key: d890725d...)
2025-12-16 11:14:09 [info     ] pipeline_step                  duration_seconds=0.10399937629699707 metrics={'samples': 738824} status=completed step=data_loading
✓ Loaded 738,824 samples

[Step 2/7] Computing features...
2025-12-16 11:14:09 | afml.cache.unified_cache_system | DEBUG | Cache HIT: afml.production.model_development.create_feature_engineering_pipeline (key: 4e32ea77...)
2025-12-16 11:14:09 [info     ] pipeline_step                  duration_seconds=0.699995756149292 metrics={'features_generated': 68} status=completed step=feature_engineering
✓ Generated 68 features

[Step 3/7] Generating events...
2025-12-16 11:14:09 | afml.cache.unified_cache_system | DEBUG | Cache HIT: afml.production.model_development.generate_events_triple_barrier (key: 5a7b1eb0...)
2025-12-16 11:14:09 [info     ] pipeline_step                  duration_seconds=0.14899849891662598 metrics={'events_generated': 75975, 'label_distribution':       count  proportion
bin                    
1    49,791     0.65536
0    26,184     0.34464, 'average_uniqueness': 0.4667911271632548} status=completed step=label_generation
✓ Generated events: 
      count  proportion
bin                    
1    49,791     0.65536
0    26,184     0.34464

Average Uniqueness: 0.4668

[Step 4/7] Computing sample weights...
2025-12-16 11:18:04 | afml.production.model_development | INFO | Best Weighting Scheme: Uniqueness Exp 0.05808
2025-12-16 11:18:07 | afml.cache.unified_cache_system | DEBUG | Cache MISS: afml.production.model_development.find_optimal_sample_weight (key: 9d10ce42...) (00:03:57)
2025-12-16 11:18:07 [info     ] pipeline_step                  duration_seconds=237.31548690795898 metrics={'weighting_scheme': 'uniqueness_exp_0.05808361216819946', 'weight_cv_score': 0.7457131539000748} status=completed step=weight_computation
✓ Best weighting scheme: uniqueness_exp_0.05808361216819946

[Step 5/7] Computing rolling meta-label features...
2025-12-16 11:18:07 | afml.cache.unified_cache_system | DEBUG | Cache HIT: afml.production.model_development.calculate_rolling_metrics (key: ae8cdce2...)
2025-12-16 11:18:20 [info     ] pipeline_step                  duration_seconds=13.350001573562622 metrics={'meta_features_added': 8, 'features_after_preprocessing': 72, 'features_removed': -4} status=completed step=meta_features_preprocessing
✓ Computed rolling meta-label features
✓ Preprocessed features: 72 features retained

[Step 6/7] Training model with cross-validation...
2025-12-16 11:18:21 | afml.cache.unified_cache_system | DEBUG | Cache HIT: afml.cross_validation.hyper_fit.clf_hyper_fit_internal (key: 907bd2af...)
2025-12-16 11:18:21 [info     ] pipeline_step                  duration_seconds=0.4719998836517334 metrics={'cv_score': 0.6915768921130778, 'best_params': {'clf__max_depth': 6, 'clf__max_features': 0.12075618253727419, 'clf__min_samples_leaf': 3, 'clf__min_samples_split': 13, 'clf__min_weight_fraction_leaf': 0.28140549728612524, 'clf__n_estimators': 369}, 'n_splits': 5} status=completed step=model_training
✓ Best CV score: 0.6916
✓ Best params: {'clf__max_depth': 6, 'clf__max_features': 0.12075618253727419, 'clf__min_samples_leaf': 3, 'clf__min_samples_split': 13, 'clf__min_weight_fraction_leaf': 0.28140549728612524, 'clf__n_estimators': 369}

[Step 7/7] Analyzing feature importance...
2025-12-16 11:18:21 [info     ] pipeline_step                  duration_seconds=0.1509995460510254 metrics={'top_feature': 'rolling_accuracy_20'} status=completed step=feature_analysis

Top 10 Features:
            feature  importance
rolling_accuracy_20    0.166330
      rolling_f1_20    0.132372
rolling_accuracy_50    0.131260
      rolling_f1_50    0.109463
             spread    0.029035
             d1_vol    0.025773
        hour_cos_h2    0.022493
        ret_1_lag_1    0.022391
   parkinson_vol_10    0.019278
 london_session_vol    0.018018 


[Generating Reports] Creating analysis reports...
🔍 Running hyperparameter analysis...
================================================================================
HYPERPARAMETER ANALYSIS REPORT
================================================================================

1. TOP PERFORMING MODELS (sorted by mean_test_score):
--------------------------------------------------
                                                                                                                                                                                                       params  mean_test_score  std_test_score  mean_fit_time
4   {'clf__max_depth': 6, 'clf__max_features': 0.12075618253727419, 'clf__min_samples_leaf': 3, 'clf__min_samples_split': 13, 'clf__min_weight_fraction_leaf': 0.28140549728612524, 'clf__n_estimators': 369}         0.691577        0.011027      28.989994
2    {'clf__max_depth': 4, 'clf__max_features': 0.15077042112439024, 'clf__min_samples_leaf': 8, 'clf__min_samples_split': 13, 'clf__min_weight_fraction_leaf': 0.4723487190570876, 'clf__n_estimators': 435}         0.683852        0.011899      28.105996
6    {'clf__max_depth': 4, 'clf__max_features': 0.9539969835279999, 'clf__min_samples_leaf': 2, 'clf__min_samples_split': 10, 'clf__min_weight_fraction_leaf': 0.05718481349909639, 'clf__n_estimators': 435}         0.677322        0.006976     614.569299
17    {'clf__max_depth': 4, 'clf__max_features': 0.8984914683186939, 'clf__min_samples_leaf': 1, 'clf__min_samples_split': 4, 'clf__min_weight_fraction_leaf': 0.10381741067223577, 'clf__n_estimators': 180}         0.675696        0.006911     175.406800
1     {'clf__max_depth': 5, 'clf__max_features': 0.5133240027692805, 'clf__min_samples_leaf': 5, 'clf__min_samples_split': 5, 'clf__min_weight_fraction_leaf': 0.11429006806487335, 'clf__n_estimators': 180}         0.670490        0.007110      95.500397
5     {'clf__max_depth': 3, 'clf__max_features': 0.14180537144799796, 'clf__min_samples_leaf': 3, 'clf__min_samples_split': 8, 'clf__min_weight_fraction_leaf': 0.1267358556592812, 'clf__n_estimators': 216}         0.669676        0.009235      33.474998
10    {'clf__max_depth': 4, 'clf__max_features': 0.905344615384884, 'clf__min_samples_leaf': 8, 'clf__min_samples_split': 15, 'clf__min_weight_fraction_leaf': 0.13819228808861533, 'clf__n_estimators': 239}         0.669449        0.008087     196.870216
16    {'clf__max_depth': 4, 'clf__max_features': 0.1858691048413702, 'clf__min_samples_leaf': 7, 'clf__min_samples_split': 6, 'clf__min_weight_fraction_leaf': 0.37832278025212884, 'clf__n_estimators': 409}         0.667428        0.007923      36.593196
13   {'clf__max_depth': 5, 'clf__max_features': 0.4810613326357327, 'clf__min_samples_leaf': 1, 'clf__min_samples_split': 13, 'clf__min_weight_fraction_leaf': 0.18206967862311718, 'clf__n_estimators': 112}         0.664284        0.002792      40.329997
12   {'clf__max_depth': 3, 'clf__max_features': 0.3528410587186427, 'clf__min_samples_leaf': 9, 'clf__min_samples_split': 16, 'clf__min_weight_fraction_leaf': 0.12437012257835113, 'clf__n_estimators': 394}         0.664078        0.007689     133.290791

2. PERFORMANCE SUMMARY:
--------------------------------------------------
Average mean_test_score: 0.6656 ± 0.0106
Best mean_test_score: 0.6916
Worst mean_test_score: 0.6482
Performance Range: 0.0434

3. STABILITY ANALYSIS:
--------------------------------------------------
Models with stable performance (std ≤ 0.03): 20
Best stable model: 0.6916 ± 0.0110

4. TIME-EFFICIENCY ANALYSIS:
--------------------------------------------------

5. HYPERPARAMETER TRENDS:
--------------------------------------------------

Parameter: clf__max_depth
Optimal value: 4 (score: 0.6716)
Performance by value:
                      score_mean  score_std  count  fold_std_mean  time_mean
param_clf__max_depth                                                        
4                         0.6716     0.0098      6         0.0088   184.0137
6                         0.6659     0.0177      4         0.0104    99.7223
3                         0.6638     0.0048      4         0.0088   108.2067
5                         0.6607     0.0073      6         0.0076    60.1069

Parameter: clf__max_features
Optimal value: 0.12075618253727419 (score: 0.6916)
Performance by value:
                         score_mean  score_std  count  fold_std_mean  time_mean
param_clf__max_features                                                        
0.120756                     0.6916        NaN      1         0.0110    28.9900
0.150770                     0.6839        NaN      1         0.0119    28.1060
0.953997                     0.6773        NaN      1         0.0070   614.5693
0.898491                     0.6757        NaN      1         0.0069   175.4068
0.513324                     0.6705        NaN      1         0.0071    95.5004
0.141805                     0.6697        NaN      1         0.0092    33.4750
0.905345                     0.6694        NaN      1         0.0081   196.8702
0.185869                     0.6674        NaN      1         0.0079    36.5932
0.481061                     0.6643        NaN      1         0.0028    40.3300
0.352841                     0.6641        NaN      1         0.0077   133.2908
0.749557                     0.6636        NaN      1         0.0093   166.1914
0.316923                     0.6623        NaN      1         0.0069   119.1400
0.484787                     0.6620        NaN      1         0.0052    37.7866
0.816889                     0.6600        NaN      1         0.0078   117.2920
0.278958                     0.6595        NaN      1         0.0110    46.2450
0.860080                     0.6581        NaN      1         0.0081   132.6173
0.933671                     0.6579        NaN      1         0.0089    99.8698
0.568061                     0.6556        NaN      1         0.0112    52.5364
0.992990                     0.6515        NaN      1         0.0156   118.1418
0.918388                     0.6482        NaN      1         0.0115    23.4877

Parameter: clf__min_samples_leaf
Optimal value: 3 (score: 0.6744)
Performance by value:
                             score_mean  score_std  count  fold_std_mean  time_mean
param_clf__min_samples_leaf                                                        
3                                0.6744     0.0153      3         0.0085    33.4172
5                                0.6705        NaN      1         0.0071    95.5004
8                                0.6682     0.0114      4         0.0097    97.1283
2                                0.6677     0.0136      2         0.0076   373.5933
7                                0.6644     0.0027      3         0.0081   107.3082
1                                0.6638     0.0121      3         0.0084   111.2929
9                                0.6610     0.0044      2         0.0083   116.5803
4                                0.6519     0.0052      2         0.0113    38.0120

Parameter: clf__min_samples_split
Optimal value: 10 (score: 0.6773)
Performance by value:
                              score_mean  score_std  count  fold_std_mean  time_mean
param_clf__min_samples_split                                                        
10                                0.6773        NaN      1         0.0070   614.5693
13                                0.6707     0.0165      5         0.0097    66.9416
5                                 0.6705        NaN      1         0.0071    95.5004
4                                 0.6676     0.0115      2         0.0090   110.8259
6                                 0.6674        NaN      1         0.0079    36.5932
15                                0.6665     0.0041      2         0.0087   181.5308
8                                 0.6624     0.0051      4         0.0078    72.1058
16                                0.6611     0.0042      2         0.0079   132.9540
19                                0.6519     0.0052      2         0.0113    38.0120

Parameter: clf__min_weight_fraction_leaf
Optimal value: 0.28140549728612524 (score: 0.6916)
Performance by value:
                                     score_mean  score_std  count  fold_std_mean  time_mean
param_clf__min_weight_fraction_leaf                                                        
0.281405                                 0.6916        NaN      1         0.0110    28.9900
0.472349                                 0.6839        NaN      1         0.0119    28.1060
0.057185                                 0.6773        NaN      1         0.0070   614.5693
0.103817                                 0.6757        NaN      1         0.0069   175.4068
0.114290                                 0.6705        NaN      1         0.0071    95.5004
0.126736                                 0.6697        NaN      1         0.0092    33.4750
0.138192                                 0.6694        NaN      1         0.0081   196.8702
0.378323                                 0.6674        NaN      1         0.0079    36.5932
0.182070                                 0.6643        NaN      1         0.0028    40.3300
0.124370                                 0.6641        NaN      1         0.0077   133.2908
0.272208                                 0.6636        NaN      1         0.0093   166.1914
0.272830                                 0.6623        NaN      1         0.0069   119.1400
0.263917                                 0.6620        NaN      1         0.0052    37.7866
0.250625                                 0.6600        NaN      1         0.0078   117.2920
0.405579                                 0.6595        NaN      1         0.0110    46.2450
0.422932                                 0.6581        NaN      1         0.0081   132.6173
0.432517                                 0.6579        NaN      1         0.0089    99.8698
0.398810                                 0.6556        NaN      1         0.0112    52.5364
0.325244                                 0.6515        NaN      1         0.0156   118.1418
0.348135                                 0.6482        NaN      1         0.0115    23.4877

6. CROSS-VALIDATION CONSISTENCY:
--------------------------------------------------
Fold performance consistency:
  Fold 0: 0.6758 ± 0.0113
  Fold 1: 0.6734 ± 0.0104
  Fold 2: 0.6607 ± 0.0091
  Fold 3: 0.6612 ± 0.0127
  Fold 4: 0.6570 ± 0.0149

7. MODEL SELECTION RECOMMENDATION:
--------------------------------------------------
✅ RECOMMENDATION: Choose stable model
   Score: 0.6916 (vs best: 0.6916)
   Stability: 0.0110 (vs best: 0.0110)
   Performance difference: 0.0000 (insignificant)

🎯 RECOMMENDED HYPERPARAMETERS:
   clf__max_depth: 6
   clf__max_features: 0.12075618253727419
   clf__min_samples_leaf: 3
   clf__min_samples_split: 13
   clf__min_weight_fraction_leaf: 0.28140549728612524
   clf__n_estimators: 369

8. GENERATING VISUALIZATIONS...

9. PRACTICAL INTERPRETATION FOR TRADING:
--------------------------------------------------
Expected Strategy Performance:
  • Best mean_test_score: 0.6916
  • Cross-validation Consistency: Good

✅ LOW RISK: Excellent consistency across CV folds
   Strategy likely to perform similarly in live trading

================================================================================
ANALYSIS COMPLETE
================================================================================
SPECIFIC INSIGHTS FROM YOUR RESULTS:
================================================================================

1. KEY OBSERVATIONS:
--------------------------------------------------
max_depth: 6
min_samples_leaf: 3
min_samples_split: 13
n_estimators: 369
Best Model mean_test_score: 0.6916
Standard Deviation: 0.0110
Training Time: 28.99s

Best Simple Model (param_clf__max_depth ≤ 4):
  param_clf__max_depth=4
  mean_test_score: 0.6839 (vs best: 0.6916)

2. PERFORMANCE SATURATION:
--------------------------------------------------
Maximum performance by max_depth:
  depth=3: 0.6697
  depth=4: 0.6839
  depth=5: 0.6705
  depth=6: 0.6916

3. ACTIONABLE RECOMMENDATIONS:
--------------------------------------------------
✅ Excellent performance achieved!
   Consider testing with additional features or ensemble methods

4. PRODUCTION CONSIDERATIONS:
--------------------------------------------------
Expected Inference Speed: ~199.1ms per prediction
Training Time Range: 23.49s to 614.57s
Average Model Size: ~303 trees
✅ Markdown report generated: Models\Bollinger_w10_std1_5\EURUSD\FUNDEDNEXT_STLR2_6K\time\M1\20220101_20231231\bdad004f\reports\hyperparameter_analysis_report.md
2025-12-16 11:18:24 | afml.production.model_development | INFO | Generated hyperparameter report: Models\Bollinger_w10_std1_5\EURUSD\FUNDEDNEXT_STLR2_6K\time\M1\20220101_20231231\bdad004f\reports\hyperparameter_analysis_report.md
2025-12-16 11:18:25 | afml.production.model_development | INFO | Generated feature importance plot: Models\Bollinger_w10_std1_5\EURUSD\FUNDEDNEXT_STLR2_6K\time\M1\20220101_20231231\bdad004f\plots\feature_importance.png
2025-12-16 11:18:25 | afml.production.model_development | WARNING | HTML summary generation failed: 'numpy.ndarray' object is not callable
✓ Reports generated in 4.15s

[Saving] Writing artifacts to disk...
2025-12-16 11:18:29 | afml.production.model_development | INFO | Saved all artifacts to Models\Bollinger_w10_std1_5\EURUSD\FUNDEDNEXT_STLR2_6K\time\M1\20220101_20231231\bdad004f
✓ Saved to Models\Bollinger_w10_std1_5\EURUSD\FUNDEDNEXT_STLR2_6K\time\M1\20220101_20231231\bdad004f
2025-12-16 11:18:29 [info     ] pipeline_step                  duration_seconds=260.72733187675476 metrics={'total_duration': 260.72733187675476, 'model_trained': True, 'reports_generated': True} status=completed step=pipeline_complete

✓ Pipeline completed in 00:04:21
======================================================================

Phase 5: Modellexport und Validierung

Im letzten Schritt wird das Modell nach einer umfassenden Validierung in das ONNX-Format exportiert:

[Saving] Writing artifacts to disk... ONNX EXPORT PIPELINE 

======================================================================
ONNX EXPORT PIPELINE
======================================================================

[Step 1/5] Preparing metadata...
✓ Model type: RandomForestClassifier
✓ Features: 74
✓ Version: 1.0

[Step 2/5] Converting to ONNX format...
✓ Conversion successful
✓ ONNX opset: 12 (MQL5 compatible)

[Step 3/5] Saving ONNX model...
✓ Saved to: C:\Users\JoeN\Documents\GitHub\Machine-Learning-Blueprint\models\bollinger_meta_model_eurusd_tick_m1.onxx
✓ File size: 0.16 MB

[Step 4/5] Validating ONNX model...
✓ ONNX model structure valid

[Step 5/5] Comparing Python vs ONNX predictions...

Generating test data...
Computing Python predictions...
Computing ONNX predictions...

✓ ONNX returned 2 output(s)
  Output 0: shape=(1000,), dtype=int64
    Sample values: 1
  Output 1: shape=(1000, 2), dtype=float32
    Sample values: [0.48720157 0.5127984 ]

✓ Using output 1 (probabilities)
✓ Extracted positive class probabilities from shape (1000, 2)

Prediction Comparison (1000 samples):
  • Max difference:  1.52e-07
  • Mean difference: 4.91e-08
  • Std difference:  3.30e-08

✅ VALIDATION PASSED - Predictions match within tolerance (1.00e-05)

Sample Predictions (first 5):
Index    Python       ONNX         Diff        
--------------------------------------------------
0        0.512798     0.512798     8.19e-08    
1        0.459688     0.459688     5.50e-08    
2        0.451748     0.451748     7.06e-08    
3        0.478857     0.478857     6.04e-08    
4        0.460223     0.460223     6.36e-08    

======================================================================
✅ EXPORT SUCCESSFUL - Model ready for MQL5 deployment
======================================================================

Dieser Validierungsschritt ist von entscheidender Bedeutung. Das System generiert zufällige Testdaten und überprüft, ob das ONNX-Modell bitweise identische Vorhersagen wie das Python-Modell liefert. Sollte es zu Abweichungen kommen, schlägt der Export sofort fehl, anstatt im Produktivbetrieb unbemerkt falsche Vorhersagen zu liefern.



Die Ergebnisse verstehen

Sobald der Prozess abgeschlossen ist, verfügen Sie über eine vollständige Nachvollziehbarkeit aller Schritte. Jede Datei ist mit einem Zeitstempel versehen, versioniert und verlinkt. Sie können Ihre Forschungshistorie wie eine gut organisierte Bibliothek durchstöbern.

Verzeichnisstruktur

Dateiformate (Farbe als Schlüssel)
Funktion
Konfigurationsdateien
Alle Einstellungen, die zur Wiederholung dieses Experiments erforderlich sind. Der Konfigurations-Hash gewährleistet die Eindeutigkeit.
Modelldateien
Das trainierte Modell in verschiedenen Formaten: Python (joblib) für die Forschung, ONNX für die Bereitstellung auf MQL5.
Datenartefakte
Verarbeitete Merkmale, Labels und Gewichtungen. Ermöglicht die Modellanalyse ohne Neuberechnung.
Berichte und Protokolle
Umfassende Dokumentation: Ausführungsprotokolle, Leistungsdiagramme, Analyseberichte.

Was Ihnen diese Organisation bietet

  • Einfache Navigation: Jedes Experiment lässt sich nach Strategie, Symbol und Datumsbereich auffinden.
  • Perfekte Reproduzierbarkeit: Der Konfigurations-Hash gewährleistet Eindeutigkeit und Rückverfolgbarkeit
  • Experimente vergleichen: Direkter Vergleich verschiedener Konfigurationen
  • Schnelle Bereitstellung: ONNX-Datei, die direkt in einen MQL5-EA eingefügt werden kann
  • Vollständige Nachvollziehbarkeit aller Schritte: Alles wird automatisch dokumentiert

Um trainierte Modelle zu finden, die bestimmten Kriterien entsprechen, können Sie den folgenden Code verwenden:

from ..production.utils import ModelFileManager

file_manager = ModelFileManager()

base_dir = "Models"  # base_dir is path to where trained models are saved
search_criteria = {"bar_size": "M1", "bar_type": "time"} 

file_manager.find_models(search_criteria, base_dir) 




Die Vorteile des Cachings

Lassen Sie uns darüber sprechen, was dieses System so schnell macht. Der Kern dieser Effizienz liegt in der intelligenten Logik zur Cache-Invalidierung, die sicherstellt, dass bei Änderungen nur die notwendigen Komponenten der Pipeline neu berechnet werden. Indem der Workflow des maschinellen Lernens als Abhängigkeitsgraf behandelt wird, kann das System zwischen umfangreichen Änderungen – wie beispielsweise der Änderung von Datensymbolen, die eine vollständige Neuberechnung aller Stufen erfordern – und geringfügigeren Änderungen – wie der Optimierung von Modell-Hyperparametern – unterscheiden. Im letzteren Fall verwendet das System zwischengespeicherte Daten für die Bars, Merkmale, Labels und Gewichte wieder und führt lediglich den abschließenden Schritt des Modelltrainings durch. Diese detaillierte Steuerung verhindert redundante Berechnungen und ermöglicht es Forschern, bestimmte Teile der Strategie – wie beispielsweise das Feature-Engineering oder die Label-Erzeugung – zu optimieren, ohne Zeit mit der erneuten Verarbeitung des gesamten Datensatzes zu verschwenden.

Wenn Sie mehr über mein Caching-System erfahren möchten, lesen Sie den letzten Artikel meiner Reihe.

Cache-Abhängigkeit


Nach dem ersten Durchlauf laufen die nachfolgenden Experimente deutlich schneller ab.



Cache-Sicherheit und Datenkontamination

Das wichtigste Merkmal des Caching-Systems ist der Schutz vor Datenkontamination. Jeder Datenzugriff wird protokolliert:

@cacheable(time_aware=True) 
def load_and_prepare_training_data(...): 
    # ... load data ... 
    log_data_access(
            dataset_name=f"{symbol}_{bar_type}_{bar_size}".lower(), 
            start_date=data.index[0], 
            end_date=data.index[-1], 
            purpose="train", 
            data_shape=data.shape)

    return data

Sie können jederzeit prüfen, ob eine Datenkontamination vorliegt:

from ..cache import print_contamination_report


print_contamination_report()

Diese automatische Kontaminationsprüfung hat mir unzählige Stunden erspart, die ich sonst mit der Fehlersuche bei rätselhaften Leistungseinbußen im Walk-Forward-Testing verbracht hätte.



Praktische Anwendungsbeispiele

Ich möchte Ihnen einige gängige Forschungsabläufe vorstellen:

1. Strategievergleich

strategies = [ 
        BollingerBandStrategy(window=20, num_std=2.0), 
        BollingerBandStrategy(window=20, num_std=2.5), 
        BollingerBandStrategy(window=30, num_std=2.0), 
] 
results = [] 

for strategy in strategies: 
        pipeline = ModelDevelopmentPipeline( 
                                symbol="EURUSD", 
                                train_start="2023-01-01", 
                                train_end="2023-12-31", 
                                strategy=strategy, 
                                # ... config ... 
                ) 
        model, features, metrics, config = pipeline.run(verbose=False) 
        results.append({ 
                        "strategy": strategy.get_strategy_name(), 
                        "cv_score": metrics["cv_results"]["best_score"], 
                        "features": len(features), "model": model 
                }) 

comparison = pd.DataFrame(results).sort_values("cv_score", ascending=False) 
print(comparison)

Dank des Cachings lässt sich der Vergleich der Strategien schnell durchführen – lediglich das Modelltraining ändert sich.

2. Experimente zur Merkmalsentwicklung

feature_configs = [ 
        {"func": calculate_basic_features, "params": {}}, 
        {"func": calculate_advanced_features, "params": {"lookback": 20}}, 
        {"func": calculate_advanced_features, "params": {"lookback": 50}}, 
        ] 

for i, feat_config in enumerate(feature_configs): 
        pipeline = ModelDevelopmentPipeline( 
			# ... same data config ... feature_config=feat_config, 
			# ... rest of config ...
			) 
        model, features, metrics, _ = pipeline.run() 
	print(f"Feature set {i+1}: {metrics['cv_results']['best_score']:.4f}")

3. Walk-Forward-Analyse

train_periods = [ 
	("2022-01-01", "2022-12-31"), 
	("2022-07-01", "2023-06-30"), 
	("2023-01-01", "2023-12-31"), 
	] 
models = [] 

for train_start, train_end in train_periods: 
        pipeline = ModelDevelopmentPipeline(
                                symbol="EURUSD", 
                                train_start=train_start, 
                                train_end=train_end, 
                                # ... config ... 
                                ) 
        model, _, metrics, _ = pipeline.run() 
        models.append({ 
                        "period": f"{train_start} to {train_end}", 
                        "score": metrics["cv_results"]["best_score"], 
                        "model_path": pipeline.file_paths["model"]
                         })

Die übersichtliche Verzeichnisstruktur erleichtert den Vergleich von Modellen über verschiedene Zeiträume hinweg.



Leistungsüberwachung

Das System erfasst seine eigene Leistung:

from ..cache import print_health_report

print_health_report()



Integration mit MQL5

Der ONNX-Export vereinfacht die Bereitstellung erheblich. Hier ist ein minimalistisches MQL5-EA-Grundgerüst:

//+------------------------------------------------------------------+
//|                                     BollingerBandStrategy_EA.mq5 |
//+------------------------------------------------------------------+

#property version   "1.00"
#property strict

//--- Include Resources
#include <Trade\Trade.mqh>

//--- Load the ONNX model from resources
#resource "\\Models\\BollingerBandStrategy\\EURUSD\\model.onnx" as uchar model_data[]

//--- Input Parameters
input double   InpLotSize      = 0.01;   // Trade Lot Size
input double   InpThresholdBuy = 0.55;   // Probability to Buy
input double   InpThresholdExit = 0.45;  // Probability to Exit/Sell

//--- Global Variables
long           model_handle    = INVALID_HANDLE;
CTrade         trade;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    // Load ONNX model from the resource buffer
    model_handle = OnnxCreateFromBuffer(model_data, ONNX_DEFAULT);
    
    if(model_handle == INVALID_HANDLE)
    {
        Print("Error: Failed to load ONNX model. Code: ", GetLastError());
        return(INIT_FAILED);
    }

    // Set output shape if necessary (depends on your model export)
    // OnnxSetOutputShape(model_handle, 0, [1, 1]); 

    Print("Model loaded successfully. Handle: ", model_handle);
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    if(model_handle != INVALID_HANDLE)
    {
        OnnxRelease(model_handle);
        model_handle = INVALID_HANDLE;
    }
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    // 1. Prepare Features
    double features[47];
    if(!CalculateFeatures(features)) return;

    // 2. Prepare ONNX Vectors
    vector v_features;
    v_features.Assign(features);
    vector v_output;

    // 3. Run Inference
    if(!OnnxRun(model_handle, ONNX_NO_CONVERSION, v_features, v_output))
    {
        Print("Inference failed. Error: ", GetLastError());
        return;
    }

    // 4. Trade Logic
    double probability = v_output[0];
    CheckTradeLogic(probability);
}

//+------------------------------------------------------------------+
//| Logic for Entry and Exit                                         |
//+------------------------------------------------------------------+
void CheckTradeLogic(double prob)
{
    bool hasPosition = (PositionsTotal() > 0);

    // Buy Logic
    if(prob > InpThresholdBuy && !hasPosition)
    {
        trade.Buy(InpLotSize, _Symbol, SymbolInfoDouble(_Symbol, SYMBOL_ASK), 0, 0, "ONNX Signal");
    }
    // Exit Logic
    else if(prob < InpThresholdExit && hasPosition)
    {
        trade.PositionClose(_Symbol);
    }
}

//+------------------------------------------------------------------+
//| Feature Calculation - MUST MATCH PYTHON PRE-PROCESSING           |
//+------------------------------------------------------------------+
bool CalculateFeatures(double &features[])
{
    ArrayInitialize(features, 0.0);
    
    // Example: Getting Bollinger Bands data for the current bar
    double bb_upper = iBands(_Symbol, PERIOD_CURRENT, 20, 2.0, 0, PRICE_CLOSE, MODE_UPPER, 0);
    double bb_lower = iBands(_Symbol, PERIOD_CURRENT, 20, 2.0, 0, PRICE_CLOSE, MODE_LOWER, 0);
    double close    = iClose(_Symbol, PERIOD_CURRENT, 0);

    // Fill your 47 features here exactly as your model expects
    // features[0] = (close - bb_lower) / (bb_upper - bb_lower); // Normalized example
    // ...
    
    return true;
}

Entscheidend ist, dass Ihre MQL5-Merkmale exakt mit den in Python berechneten Merkmalen übereinstimmen. Dabei hilft die im Trainingsbericht enthaltene Merkmalsliste.



Debugging und Fehlerbehebung

Wenn ein Fehler auftritt, liefert das System detaillierte Diagnoseinformationen:

Beispiel: Leistungseinbußen des Modells

# Get all experiments for a symbol 
file_manager = ModelFileManager("Models") 
models = file_manager.find_models({ "symbol": "EURUSD", "strategy": "BollingerBandStrategy" }) 

# Compare performance over time 
for model_info in models: 
        metrics = json.load(open(model_info["file_path"].replace(".joblib", "_metrics.json"))) 
        print(f"{model_info['date_range']}: {metrics['cv_results']['best_score']:.4f}")

Sie können schnell feststellen, wann sich die Leistung verändert hat, und die entsprechende Konfiguration laden, um zu sehen, was anders war.

Beispiel: Cache-Probleme

Wenn Sie den Verdacht haben, dass der Cache beschädigt ist:

from cache import clear_cache 

# Clear specific function cache 
clear_cache("load_and_prepare_training_data") 

# Or clear everything 
clear_cache()

Das System wird von Grund auf neu aufgebaut, wodurch die Korrektheit gewährleistet wird.



Bewährte Verfahren

Nachdem ich dieses System nun seit Monaten nutze, sind hier meine Empfehlungen:

1. Verwenden Sie stets aussagekräftige Strategienamen

# Good strategy name
BollingerBandStrategy(window=20, num_std=2.0) 
strategy.get_strategy_name() # "BollingerBand_w20_std2.0"

# Better - includes your hypothesis strategy
BollingerBandStrategy(window=20, num_std=2.0, name="BB_MeanReversion_v1")

Ihr zukünftiges Ich wird es Ihnen danken, wenn Sie später durch 50 Experimente navigieren müssen.

2. Beginnen Sie mit kleinen Parametergittern

# First iteration - quick feedback
param_grid = { 
	"n_estimators": [100, 200], 
	"max_depth": [3, 5, 7] 
	}

# After you understand the landscape
param_grid = { 
	"n_estimators": [100, 200, 300, 500], 
	"max_depth": [3, 5, 7, 10, 15], 
	"min_samples_split": [2, 5, 10, 20] 
	}

Zuerst schnell iterieren, dann verfeinern.

3. Alles speichern

Speicherplatz ist günstig, Reproduzierbarkeit ist unbezahlbar:

pipeline.run( 
        generate_reports=True, # Always 
        save=True, # Always 
        export_onnx=True, # If deploying 
        verbose=True # Until you're confident 
        )

4. Berichte vor der Bereitstellung prüfen

Treffen Sie Ihre Bereitstellungsentscheidungen nicht allein auf der Grundlage des CV-Scores. Überprüfen:

  • Konsistenz der CV-Folds (hohe Varianz = unzuverlässig)
  • Bedeutung der Merkmale (sinnvolle Merkmale?)
  • Verteilung der Labels (ausgewogen?)
  • ONNX-Validierung (bestanden?)

Die Berichte machen das ganz einfach.

5. Versionieren Sie Ihre Merkmalsfunktionen

def calculate_features_v1(data, **params): 
        """Initial feature set - baseline.""" 
        # ... features ... 

def calculate_features_v2(data, **params): 
        """Added momentum indicators.""" 
        # ... features ... 

feature_config = { "func": calculate_features_v2, # Track version in function name 
                   "params": {}
		}

Wenn Sie Merkmale verbessern, erstellen Sie eine neue Funktion. Der Cache übernimmt die Versionsverwaltung automatisch.



Schlussfolgerung

Der Aufbau eines produktionsreifen Forschungssystems erfordert zwar Anfangsinvestitionen, doch der Nutzen ist enorm:

  1. Geschwindigkeit: 10-mal mehr Experimente in derselben Zeit
  2. Zuverlässigkeit: Reproduzierbare Ergebnisse, auf die Sie sich verlassen können
  3. Einblick: Eine automatische Analyse deckt Muster auf, die man sonst übersehen würde
  4. Bereitstellung: Export nach MQL5 mit nur einem Klick
  5. Wartung: Einfach zu debuggen, einfach zu erweitern

Der Code, den wir uns angesehen haben, veranschaulicht einige der wichtigsten Aspekte einer Forschungsmethodik im Bereich des maschinellen Lernens für den Finanzsektor. Es sorgt für die Einhaltung bewährter Verfahren: ordnungsgemäße Kreuzvalidierung, Stichprobengewichtung, Merkmalsanalyse (wenn auch in sehr begrenztem Umfang) und Validierung. So wird der richtige Weg zum einfachen Weg.

Vor allem aber können Sie sich so auf das Wesentliche konzentrieren: die Entwicklung besserer Handelsstrategien. Die technische Infrastruktur funktioniert einfach.



Nächste Schritte

So richten Sie dieses System ein:

  1. Fangen Sie klein an: Beginnen Sie mit einer einzigen Strategie und einem Symbol
  2. Überprüfen des Caching: Führen Sie die Pipeline zweimal aus und überprüfen Sie die Beschleunigung
  3. Prüfen der Berichte: Überprüfen Sie alle erstellten Berichte
  4. ONNX validieren: Stellen Sie sicher, dass die Vorhersagen exakt übereinstimmen
  5. Vorsichtig vorgehen: Beginnen Sie mit dem Papierhandel
  6. Iterieren: Nutzen Sie das System, um systematische Experimente durchzuführen

Der vollständige Code ist in den beigefügten Dateien verfügbar. Beginnen Sie mit der Datei model_development.py im Verzeichnis „production“ und arbeiten Sie sich durch das Beispiel-Notebook.

Denken Sie daran: Trading ist schon schwer genug, ohne dass Sie sich zusätzlich mit Ihren Forschungswerkzeugen herumschlagen müssen. Entwickeln Sie Systeme, die Ihnen zum Erfolg verhelfen.


Datei-Referenzhandbuch

Datei Hauptzweck Wichtige Klassen/Funktionen Abhängigkeiten Wann zu verwenden
__init__.py Modul-Exporte complete_export_workflow()
export_model_to_onnx()
extract_onnx_metadata()
validate_onnx_predictions()
model_export.py Stellt die ONNX-Exportfunktion auf Paketebene bereit
dual_model_development.py Bid/Ask-sensitive separate Long/Short-Modelle BidAskLongShortPipeline
train_bidask_longshort_models()
model_development.py, Strategieklassen Wenn Sie separate Modelle für Long- und Short-Positionen unter Verwendung realistischer Ausführungskurse benötigen (Long → Ask, Short → Bid)
model_development.py Kern-Produktionspipeline ModelDevelopmentPipeline
TickDataLoader
load_and_prepare_training_data()
generate_events_triple_barrier()
train_model_with_cv()
feature_engine, sklearn, numba, Cache-System Der zentrale Arbeitsablauf für das Training und die Bewertung jedes einzelnen Modells mit vollständiger Reproduzierbarkeit
model_export.py ONNX-Konvertierung und -Validierung export_model_to_onnx()
validate_onnx_predictions()
complete_export_workflow()
skl2onnx, onnxruntime Letzter Schritt vor der MQL5-Bereitstellung – stellt sicher, dass das Produktionsmodell exakt mit Python übereinstimmt
utils.py Dateiverwaltung und -organisation ConfigPathGenerator
ModelFileManager
find_models()
load_artifacts()
hashlib, json, pickle, pathlib Verwaltung der Modellverzeichnisstruktur, Speichern und Laden von Artefakten sowie Auffinden früherer Experimente


Dateibeziehungen:

  • __init__.py → model_export.py (stellt Exportfunktionen bereit)
  • dual_model_development.py → model_development.py (erweitert die Basis-Pipeline)
  • model_development.py → utils.py (nutzt Dateiverwaltung)
  • model_export.py → Eigenständig (kann unabhängig laufen)
  • Alle Dateien → System-Cache (für die Leistung)

Wichtige Integrationspunkte:

  • dual_model_development.py erstellt zwei ModelDevelopmentPipeline-Instanzen (lang/kurz)
  • Der ModelFileManager in utils.py wird von ModelDevelopmentPipeline zur strukturierten Speicherung verwendet
  • export_model_to_onnx() wird von ModelDevelopmentPipeline aufgerufen, wenn export_onnx=True ist

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/20451

Beigefügte Dateien |
afml.zip (372.91 KB)
Verwendung des MQL5-Wirtschaftskalenders zur Nachrichtenfilterung (Teil 1): Implementierung von Zeitfenstern vor und nach Nachrichtenereignissen in MQL5 Verwendung des MQL5-Wirtschaftskalenders zur Nachrichtenfilterung (Teil 1): Implementierung von Zeitfenstern vor und nach Nachrichtenereignissen in MQL5
Wir entwickeln einen kalendergesteuerten Nachrichtenfilter vollständig in MQL5 und verzichten dabei auf Webanfragen und externe DLLs. Teil 1 behandelt das Laden und Zwischenspeichern von Ereignissen, deren Zuordnung zu Symbolen nach Währung, das Filtern nach Bedeutung, die Definition von Vor- und Nachlaufzeitfenstern sowie das Sperren neuer Trades während aktiver Nachrichten, mit der optionalen Möglichkeit, Positionen vor Nachrichtenereignissen zu schließen. Das Ergebnis ist ein konfigurierbarer, prop-firm-tauglicher Kontrollmechanismus, der falsche Handelspausen vermeidet und Einstiegspunkte bei hohen Schwankungen schützt.
Bivariate Copulas in MQL5 (Teil 3): Implementierung und Optimierung von Mixed-Copula-Modellen in MQL5 Bivariate Copulas in MQL5 (Teil 3): Implementierung und Optimierung von Mixed-Copula-Modellen in MQL5
Der Artikel erweitert unser Copula-Toolkit um gemischte Copulas, die nativ in MQL5 implementiert sind. Wir konstruieren aus Clayton–Frank–Gumbel und Clayton–Student–t–Gumbel gemischte Copula-Modelle, schätzen diese mittels EM und ermöglichen eine Sparsitätskontrolle durch SCAD mit Kreuzvalidierung. Die bereitgestellten Skripte optimieren Hyperparameter, vergleichen Mischmodelle anhand von Informationskriterien und speichern bereits trainierte Modelle. Anwender können diese Komponenten nutzen, um asymmetrische Tail-Abhängigkeit zu erfassen und das ausgewählte Modell in Indikatoren oder Expert Advisors einzubinden.
MQL5-Trading Tools (Teil 19): Entwicklung einer interaktiven Werkzeugpalette zum Zeichnen in Charts MQL5-Trading Tools (Teil 19): Entwicklung einer interaktiven Werkzeugpalette zum Zeichnen in Charts
In diesem Artikel erstellen wir in MQL5 eine interaktive Werkzeugpalette zum Zeichnen in Charts mit verschiebbaren und in der Größe anpassbaren Panels sowie der Möglichkeit, das Design zu wechseln. Wir fügen Schaltflächen für Werkzeuge wie Fadenkreuz, Trendlinien, Linien, Rechtecke, Fibonacci, Text und Pfeile hinzu und verarbeiten Mausereignisse zur Aktivierung und für Anweisungen. Dieses System verbessert die Handelsanalyse durch eine anpassbare Benutzeroberfläche und unterstützt Echtzeit-Interaktionen mit den Charts.
Workshop zu benutzerdefinierten Indikatoren (Teil 2): Entwicklung eines praxistauglichen Supertrend-Expert-Advisors in MQL5 Workshop zu benutzerdefinierten Indikatoren (Teil 2): Entwicklung eines praxistauglichen Supertrend-Expert-Advisors in MQL5
Erfahren Sie, wie Sie einen auf dem Supertrend basierenden Expert Advisor in MQL5 von Grund auf erstellen. Der Artikel behandelt die Einbindung des Indikators als Ressource, das Auslesen von Pufferwerten bei geschlossenen Kerzen, das Erkennen bestätigter Trendwenden, das Ausrichten offener Positionen am Signal sowie die Konfiguration von Stop-Loss-Modi und der Positionsgrößenbestimmung. Abschließend werden die Einrichtung des Strategy Testers und reproduzierbare Tests behandelt, sodass Sie am Ende über einen konfigurierbaren EA sowie einen klaren Rahmen für weitere Untersuchungen und Erweiterungen verfügen.