CRingBuffer - Numerischer Ringpuffer mit lightweight performanter Statistik-Engine

 

Hallo zusammen,

Ich habe mit CRingBuffer eine eigene MQL5-Bibliothek entwickelt, die speziell für effiziente Rolling-Window-Analysen im Trading ausgelegt ist. Mein Fokus lag dabei klar auf Performance: Die Statistik-Berechnungen sind so optimiert, dass nach jedem Einfügevorgang sofort aktualisierte Ergebnisse vorliegen – bei möglichst geringem CPU-Verbrauch.

Die Bibliothek liefert in Echtzeit Kennzahlen wie Mittelwert, Varianz, Standardabweichung, Perzentile, Z-Scores sowie Min-/Max-Tracking und normalisierte Werte. Je nach Operation bewege ich mich dabei in Laufzeiten von O(1) bis O(n log n), was sie besonders für performanzkritische Strategien interessant macht.

Wenn ihr interesse habt, dann könnt ihr sie hier downloaden: https://www.mql5.com/de/market/product/174460


1. Zweck und Einsatzgebiete

CRingBuffer speichert numerische Werte in zeitlicher Reihenfolge und stellt nach jedem Einfügevorgang sofort nutzbare statistische Kennzahlen bereit. Die Klasse ist auf schnelle Rolling-Window-Analysen und robuste Signalaufbereitung ausgelegt.

Typische Einsatzgebiete:
  • Preise, Spreads und Volumen fortlaufend beobachten
  • Rolling-Window-Analysen mit fester oder variabler Länge
  • Signalnormalisierung auf [0,1]
  • Min/Max-Tracking mit virtuellem Positionsindex
  • Z-Score-basierte Ausreißererkennung
  • Historienbasierte Perzentilberechnung
  • Numerisch stabile Online-Statistik

2. Datenmodell: Virtueller Index

Alle Public-Methoden arbeiten mit einem virtuellen Index. Dadurch bleibt die interne Speicheranordnung verborgen und die Nutzung für Anwender klar und konsistent.

0 = ältester gespeicherter Wert getTotal() - 1 = neuester, zuletzt hinzugefügter Wert

3. Konstruktion: Statischer vs. dynamischer Buffer

CRingBuffer(const int buf_size, const bool is_dynamic = false)

Statischer Buffer
  • Feste Größe, unveränderlich nach Konstruktion
  • Bei voller Kapazität wird der älteste Wert überschrieben
  • removeValue() und setMaxTotal() sind nicht erlaubt
  • Sinnvoll für feste Rolling Windows, z. B. ATR-14
Dynamischer Buffer
  • Größe kann angepasst werden
  • Wachsen über setMaxTotal() und setDynBufferTotal()
  • Schrumpfen über shrinkBuffer()
  • Einzelne Werte können mit removeValue() entfernt werden
  • clear() setzt zusätzlich die Kapazität auf 0 zurück
Hinweis: Nach clear() muss ein dynamischer Buffer neu aufgebaut werden, z. B. über addValue() oder setMaxTotal().

4. Platzhalter und gültige Werte

CRingBuffer unterscheidet zwischen statistisch gültigen Werten und Platzhaltern. Das schützt Kennzahlen vor Verzerrungen.
Ein Platzhalter liegt vor, wenn:
  • der Wert NaN oder Inf ist
  • der Wert EMPTY_VALUE (1e+308) ist
  • |value| ≥ 1e100 ist
Folgen:
  • Wird gespeichert und belegt einen Slot
  • Wird nicht für Summe, Mittelwert, Varianz oder Standardabweichung genutzt
  • Wird nicht für Min/Max, Perzentile oder Z-Score-Fenster genutzt
  • getTotal() zählt alle Slots, getValidCount() nur gültige Werte

fill_rate = valid_count / total_count

5. Statistikfunktionen im Überblick

CRingBuffer aktualisiert seine Kennzahlen direkt nach jedem addValue()-Aufruf. Dadurch stehen wichtige Zustands- und Analysewerte sofort bereit, ohne dass der komplette Buffer jedes Mal neu berechnet werden muss.
Die Tabelle fasst die wichtigsten Statistikgruppen zusammen und zeigt, wofür die jeweiligen Methoden typischerweise verwendet werden.
Gruppe Methoden Nutzen
Basisstatistik getSum(), getSumSq(), getMean(), getVariance(), getStdDev() Liefert die klassischen Kennzahlen für Mittelwert, Streuung und Gesamtsumme der gültigen Werte.
Welford getWelfordMean(), getWelfordVariance(), getWelfordStdDev() Bietet numerisch stabilere Alternativen für lange Serien, hohe Preisniveaus und kleine Wertunterschiede.
Min/Max getMin(), getMax(), getMinIndex(), getMaxIndex(), getMinMaxRange() Beschreibt Extremwerte, deren Positionen und die aktuelle Spannweite des Buffers für schnelle Zustandsbewertungen.
Range-Historie getAverageRange(), getRangeHistory() Zeigt, wie sich die Spannweite im Zeitverlauf entwickelt und unterstützt Volatilitätsanalysen.
Änderung getAverageDiff() Misst die durchschnittliche absolute Änderung zwischen aufeinanderfolgenden gültigen Werten und hilft bei der Beurteilung der Dynamik.
Empfehlung: Für robuste Analysen mit großen Preisniveaus sind die Welford-Methoden oft die bessere Wahl, während die Basisstatistik für kompakte Echtzeitabfragen ideal ist.

6. Erweiterung durch Vererbung

Abgeleitete Klassen können Hooks überschreiben. Diese Hooks werden erst nach vollständiger Aktualisierung der Statistiken aufgerufen.
  void OnAddValue(const double value)
       Wird nach dem erfolgreichen Einfuegen eines neuen Wertes gerufen.
       Alle Statistiken sind zu diesem Zeitpunkt bereits aktuell.

  void OnRemoveValue(const double value)
       Wird aufgerufen wenn ein Wert aus dem Buffer entfernt oder
       ueberschrieben wird.

  void OnChangeValue(const int vIndexID,
                     const double prev_value,
                     const double new_value)
       Wird nach replaceValue() gerufen.
       Alle Statistiken sind zu diesem Zeitpunkt bereits aktuell.

  void OnChangeArray()
       Wird nach jeder strukturellen Aenderung gerufen (addValue,
       replaceValue, removeValue, clear, shrinkBuffer, setMaxTotal).

  void OnSetMaxTotal(const int max_total)
       Wird nach setMaxTotal() gerufen (nur dynamische Buffer).

  void OnShrink()
       Wird nach shrinkBuffer() gerufen.

7. Codebeispiele

Die folgenden Beispiele zeigen typische Einsatzmuster der Klasse. Hervorgehoben wird jeweils nur der eigentliche Codeblock.
Beispiel 1 · Statischer Buffer mit Überschreiben
  CRingBuffer buf(3, false);        // Statischer Buffer, Kapazitaet 3

  buf.addValue(10.0);
  buf.addValue(20.0);
  buf.addValue(30.0);
  buf.addValue(40.0);               // Ueberschreibt 10.0 (aeltester Wert)

  double values[];
  buf.ToArray(values);
  // Ergebnis: values[0]=20.0  values[1]=30.0  values[2]=40.0

  double last = buf.getLast();      // 40.0
  double old  = buf.getOldest();    // 20.0
  int    n    = buf.getTotal();     // 3
Der Buffer behält nur die letzten drei Werte. ToArray() liefert sie in chronologischer Reihenfolge zurück.
Beispiel 2 · Mittelwert, Varianz und Welford-Vergleich
  CRingBuffer buf(10, false);

  buf.addValue(1.0);
  buf.addValue(2.0);
  buf.addValue(3.0);
  buf.addValue(4.0);
  buf.addValue(5.0);

  // Summenformel (schnell, leicht anfaellig fuer Ausloeschung)
  double mean_a = buf.getMean();           // 3.0
  double var_a  = buf.getVariance();       // 2.5
  double std_a  = buf.getStdDev();         // ~1.581

  // Welford (numerisch stabiler, empfohlen fuer grosse Werte)
  double mean_b = buf.getWelfordMean();    // 3.0
  double var_b  = buf.getWelfordVariance(); // 2.5
  double std_b  = buf.getWelfordStdDev();  // ~1.581

  // Perzentile
  double median = buf.getPercentile(50.0); // 3.0
  double q25    = buf.getPercentile(25.0); // 2.0
  double q75    = buf.getPercentile(75.0); // 4.0
Die Standardformeln sind schnell. Die Welford-Varianten sind bei langen Serien und großen Zahlenwerten numerisch stabiler.
Beispiel 3 · Min, Max und Normalisierung
  CRingBuffer buf(10, false);

  buf.addValue(100.0);
  buf.addValue(120.0);
  buf.addValue(140.0);
  buf.addValue(110.0);

  double min_val  = buf.getMin();              // 100.0
  double max_val  = buf.getMax();              // 140.0
  double range    = buf.getMinMaxRange();      // 40.0
  int    min_idx  = buf.getMinIndex();         // 0  (virtueller Index)
  int    max_idx  = buf.getMaxIndex();         // 2  (virtueller Index)

  double norm_110 = buf.getNormalizedValue(110.0);  // 0.25
  double norm_last = buf.getNormalizedValueAt(buf.getCurrentVirtIndex()); // 0.25

  double norm_arr[];
  buf.getNormalizedValues(norm_arr);
  // norm_arr: [0.0, 0.5, 1.0, 0.25]
Mit Min/Max und virtuellen Indizes lässt sich der aktuelle Bufferzustand schnell bewerten und normalisieren.
Beispiel 4 · Z-Score des letzten Wertes

CRingBuffer buf(20, false);

buf.addValue(10.0);
buf.addValue(11.0);
buf.addValue(10.5);
buf.addValue(10.8);
buf.addValue(11.2);
buf.addValue(10.3);
buf.addValue(25.0);   // Ausreißer

double z_last   = buf.getLastZScore();
double z_last_5 = buf.getLastZScore(5);
double z_at_3   = buf.getZScoreAt(3);

double z_values[];
buf.getZScores(z_values);            

getLastZScore() beschreibt den aktuellen Zustand. getZScoreAt() eignet sich für Backtests, weil der bewertete Wert nicht Teil seiner eigenen Statistik ist.

Beispiel 5 · Dynamischer Buffer: Entfernen und Verkleinern

CRingBuffer buf(10, true);        // Dynamischer Buffer

buf.addValue(5.0);
buf.addValue(6.0);
buf.addValue(7.0);
buf.addValue(8.0);
buf.addValue(9.0);
// getTotal() == 5

buf.removeValue(0);               // Entfernt aeltesten Wert (6.0 wird Index 0)
// getTotal() == 4, Werte: [6.0, 7.0, 8.0, 9.0]

buf.shrinkBuffer(2);              // Behaelt die neuesten 2 Werte
// getTotal() == 2, Werte: [8.0, 9.0]

double values[];
buf.ToArray(values);
// values[0]=8.0  values[1]=9.0

buf.clear();                      // Buffer vollstaendig leeren
// getTotal() == 0, m_buf_size_total == 0 (dynamisch!)

buf.setMaxTotal(5);               // Neue Kapazitaet setzen
buf.addValue(1.0);                // Neu befuellen
removeValue() arbeitet mit virtuellen Indizes. shrinkBuffer() behält immer die neuesten Werte und ist ideal für flexible Historienfenster.

8. Wichtige Verhaltensdetails und Fallstricke

  • ToArray(): count > 0 exportiert die neuesten count Werte, nicht die ältesten.
  • Z-Score: getZScoreAt() und getZScores(arr, 0) sind look-ahead-frei.
  • clear() bei dynamisch: Nach clear() ist die Kapazität 0.
  • Min/Max-Index: Vor Zugriff immer auf -1 prüfen.
  • getTotal() vs. getValidCount(): Platzhalter werden nur von getTotal() vollständig erfasst.

9. Struct RBufStats

RBufStats ist eine Snapshot-Struct für den aktuellen Bufferzustand. Sie bündelt Kennzahlen in einer transportfähigen Form und eignet sich für Logging, Weitergabe an andere Klassen oder den Vergleich mehrerer Zustände.
Eintragsgruppe Felder Kurzbeschreibung
Basisstatistik mean, variance, stddev, min, max, range, sum, total_count, valid_count, last_value, previous_value, oldest_value Beschreibt den grundlegenden Zustand des Buffers: Lage, Streuung, Extremwerte und Belegung.
Welford-Statistik welford_mean, welford_variance, welford_stddev Numerisch stabile Varianten für Mittelwert, Varianz und Standardabweichung.
Perzentile quantile_05, quantile_10, quantile_25, median, quantile_75, quantile_90, quantile_95, iqr Zeigt die Verteilung der gültigen Werte und unterstützt robuste Schwellenbildung.
Z-Score & Normalisierung zscore, zscore_prev, zscore_delta, norm_last, norm_oldest Hilft bei Signalbewertung, Vergleich auf einheitlicher Skala und Änderungserkennung.
Typischer Einsatz: Snapshot befüllen, mit Validate() prüfen und anschließend gesammelt protokollieren oder an andere Verarbeitungsschritte weitergeben.

10. Praktische Hinweise

  • Statischer Buffer für feste Rolling Windows, dynamischer Buffer für variable Historien.
  • Welford bevorzugen bei großen Preisniveaus und langen Laufzeiten.
  • Für Backtesting getZScoreAt() oder getZScores(arr, 0) verwenden.
  • getMinIndex() und getMaxIndex() immer auf -1 prüfen.
  • getPercentiles() ist effizienter als mehrfach getPercentile().

11. Public-API-Referenz

Legende: [S] statisch und dynamisch · [D] nur dynamisch · O(1) konstante Laufzeit · O(n) lineare Laufzeit

11.1 Konstruktion

Methode: CRingBuffer(const int buf_size, const bool is_dynamic = false)
Typ: [S/D]
Laufzeit: Initialisierung
Beschreibung: Erstellt einen statischen oder dynamischen Buffer mit definierter Startkapazität.
Hinweis: Der Modus bestimmt, ob Werte später entfernt oder die Kapazität geändert werden dürfen.
Methode: ~CRingBuffer()
Typ: [S/D]
Laufzeit: Freigabe
Beschreibung: Gibt die belegten Ressourcen nach der Nutzung frei.
Hinweis: Relevant beim sauberen Lifecycle-Management in längeren Analyseläufen.

11.2 Werte hinzufügen und ändern

Methode: addValue(long|int|double)
Typ: [S/D]
Laufzeit: O(1)
Beschreibung: Fügt einen neuen Wert hinzu und aktualisiert die Statistiken unmittelbar.
Hinweis: Im statischen Modus wird bei voller Kapazität der älteste Wert überschrieben.
Methode: replaceValue(vIndexID, value)
Typ: [S/D]
Laufzeit: O(1)
Beschreibung: Ersetzt einen vorhandenen Wert gezielt am virtuellen Index.
Hinweis: Sinnvoll für Korrekturen einzelner Datenpunkte ohne kompletten Neuaufbau.
Methode: removeValue(vIndexID)
Typ: [D]
Laufzeit: O(n)
Beschreibung: Entfernt einen Eintrag aus einem dynamischen Buffer.
Hinweis: Danach verschieben sich die virtuellen Indizes der nachfolgenden Werte.

11.3 Direkter Datenzugriff

Methode: getValue(vIndexID)
Typ: [S/D]
Laufzeit: O(1)
Beschreibung: Liest einen bestimmten Wert anhand des virtuellen Index aus.
Hinweis: Eignet sich für gezielten Zugriff auf historische Positionen.
Methode: getLast(), getPrevious(), getOldest()
Typ: [S/D]
Laufzeit: O(1)
Beschreibung: Liefert die wichtigsten Positionswerte direkt aus dem Buffer.
Hinweis: Besonders nützlich für aktuelle Signale und Vergleiche mit dem ältesten Wert.

11.4 Buffergröße und Zustand

Methode: getMaxTotal(), getTotal(), getValidCount(), getCurrentVirtIndex()
Typ: [S/D]
Laufzeit: O(1)
Beschreibung: Beschreibt Kapazität, belegte Slots, gültige Werte und die aktuelle Position des Buffers.
Hinweis: Besonders wichtig, wenn Platzhalterwerte und gültige Werte unterschieden werden müssen.
Methode: setMaxTotal(max_total), setDynBufferTotal(new_size)
Typ: [D]
Laufzeit: Größenanpassung
Beschreibung: Passt die Kapazität eines dynamischen Buffers an.
Hinweis: Beim Verkleinern bleiben die neuesten Werte erhalten.
Methode: shrinkBuffer(new_size = -1), clear(), isDynBuffer()
Typ: shrinkBuffer [D], clear/isDynBuffer [S/D]
Laufzeit: shrinkBuffer O(n), sonst O(1)
Beschreibung: Reduziert, leert oder prüft den Betriebsmodus des Buffers.
Hinweis: clear() setzt bei dynamischen Buffern zusätzlich die Kapazität auf 0.

11.5 Index- und Arrayoperationen

Methode: resolveIdx(vIndexID)
Typ: [S/D]
Laufzeit: O(1)
Beschreibung: Übersetzt einen virtuellen Index in den internen Speicherindex.
Hinweis: Vor allem für interne oder erweiterte Verarbeitung nützlich.
Methode: ToArray(array, count = -1)
Typ: [S/D]
Laufzeit: O(n)
Beschreibung: Exportiert Werte chronologisch in ein Array.
Hinweis: count > 0 exportiert die neuesten count Werte, nicht die ältesten.

11.6 Basisstatistik

Methode: getSum(), getSumSq(), getMean(), getVariance(), getStdDev()
Typ: [S/D]
Laufzeit: O(1)
Beschreibung: Liefert die klassischen statistischen Kennzahlen der gültigen Werte.
Hinweis: Für große Wertebereiche kann die Welford-Statistik numerisch robuster sein.
Methode: getPercentile(percentile), getPercentiles(percentiles, results)
Typ: [S/D]
Laufzeit: O(n log n), mehrere Perzentile O(n log n + k)
Beschreibung: Berechnet ein oder mehrere Perzentile auf Basis der gültigen Bufferwerte.
Hinweis: getPercentiles() ist effizienter, wenn mehrere Perzentile gleichzeitig benötigt werden.

11.7 Z-Score-Methoden

Methode: getZScore(value, periods = 0), getLastZScore(periods = 0)
Typ: [S/D]
Laufzeit: O(n)
Beschreibung: Bewertet einen beliebigen oder den neuesten Wert relativ zur aktuellen Historie.
Hinweis: Geeignet für Zustandsbeschreibung und Ausreißererkennung in Echtzeit.
Methode: getZScoreAt(vIndexID, periods = 0), getZScores(z_scores, periods = 0)
Typ: [S/D]
Laufzeit: O(n)
Beschreibung: Bewertet historische Einträge look-ahead-frei oder erzeugt eine komplette Z-Score-Reihe.
Hinweis: Für Backtesting und Replay ist getZScoreAt() die sicherere Wahl.

11.8 Min/Max und Range

Methode: getMin(), getMax(), getMinIndex(), getMaxIndex(), getMinMaxRange()
Typ: [S/D]
Laufzeit: O(1)
Beschreibung: Beschreibt den aktuellen Extremwertbereich des Buffers inklusive Positionen.
Hinweis: getMinIndex() und getMaxIndex() können -1 liefern und sollten vor Nutzung geprüft werden.
Methode: getAverageRange(), getAverageDiff(), getRangeHistory(range_history, count = 0), getMinMax(min_value, max_value)
Typ: [S/D]
Laufzeit: O(1) bis O(n)
Beschreibung: Liefert geglättete Spannweiten, Änderungsmaße, die Range-Historie oder Minimum und Maximum in einem Aufruf.
Hinweis: Hilfreich für Volatilitätsprofile und adaptive Schwellenwerte.

11.9 Welford-Statistik

Methode: getWelfordVariance(), getWelfordStdDev(), getWelfordMean()
Typ: [S/D]
Laufzeit: O(1)
Beschreibung: Bietet numerisch stabile Alternativen für Mittelwert und Streuung.
Hinweis: Besonders sinnvoll bei sehr großen Preisniveaus oder langen Laufzeiten.

11.10 Normalisierung

Methode: getNormalizedValue(value), getNormalizedValueAt(vIndexID), getNormalizedValues(norm_values)
Typ: [S/D]
Laufzeit: O(1) bis O(n)
Beschreibung: Normalisiert einzelne oder alle Werte auf den Bereich [0,1].
Hinweis: Bei konstanter Range wird als definierter Fallback 0.5 verwendet.

11.11 Debugging

Methode: dumpBuffer(limit = false), dumpBufferValues()
Typ: [S/D]
Laufzeit: Diagnoseausgabe
Beschreibung: Gibt den Bufferzustand oder die gespeicherten Rohwerte für Analyse und Fehlersuche aus.

Hinweis: Hilfreich bei Entwicklung, Test und Validierung des Bufferverhaltens.


CRingBuffer | Kaufen Sie Bibliothek für den MetaTrader 5
CRingBuffer | Kaufen Sie Bibliothek für den MetaTrader 5
  • www.mql5.com
CRingBuffer - Numerischer Ringpuffer mit lightweight performanter Statistik-Engine CRingBuffer ist eine leistungsstarke MQL5-Bibliothek für numerische