4d67e587ba
*Datum: 13. Juni 2025*
## Status: Design-Phase abgeschlossen, Basis-Implementierung erfolgt
---
## Stufe 1: Asynchrone Datenstrom-Schnittstelle (`IDataStream<T>`) - **ABGESCHLOSSEN**
* **Motivation**: Das ursprüngliche, zustandsbehaftete Design war für die Verarbeitung asynchron eintreffender Daten (z.B. von `TFuture`-Objekten) zu komplex und fehleranfällig.
* **Ziel**: Die Schaffung eines robusten, ereignisgesteuerten und non-blocking Modells, das den Datenproduzenten sauber vom Konsumenten entkoppelt.
* **Ergebnis**:
* Die `IDataStream<T>`-Schnittstelle wurde überarbeitet. Die `HasData`-Eigenschaft liefert nun ein `TSignal`, das Konsumenten aktiv über potenziell neue Daten informiert.
* Die Referenzimplementierung `TAuraFileStream<T>` wurde erfolgreich angepasst und nutzt eine saubere Ereignis-Kopplung (`Subscribe`) für eine robuste und wartungsarme Logik.
* Die korrekte Funktionalität wurde durch eine angepasste DUnitX-Test-Suite verifiziert.
---
## Stufe 2: Abstraktion für Handelssysteme (`IDataSeriesProvider`) - **ENTWORFEN**
* **Motivation**: Ein Handelssystem benötigt einen stets validen und kontinuierlichen Daten-Lookback. Ein roher `IDataStream<T>` kann dies nicht garantieren, da Lücken in den Daten auftreten können (z.B. beim Übergang von Historie zu Live).
* **Ziel**: Die Konzeption einer übergeordneten Abstraktionsschicht, die diese komplexe Anforderung kapselt, die Datenintegrität sicherstellt und dem Handelssystem eine einfache, sichere Schnittstelle bietet.
* **Ergebnis**:
Entworfen wurde der `IDataSeriesProvider`, der als "Black Box" für das Handelssystem fungiert und die Komplexität der Datenbeschaffung vollständig verbirgt. Er wurde mit zwei unterschiedlichen, vom Anwender wählbaren Betriebsmodi konzipiert:
### Modus 1: "Live-Handel"
* **Motivation**: Um einen echten **"Sofort-Start"** im Live-Handel zu ermöglichen, muss die Lücke zwischen den statischen, lokalen Historiendaten und dem aktuellen Zeitpunkt geschlossen werden.
* **Ziel**: Ein lückenloser, tagesaktueller Start des Handelssystems ohne manuelles Eingreifen oder lange Wartezeiten für den Nutzer.
* **Ergebnis**: Das Design einer **Drei-Phasen-Synchronisation**: (1) Lokale History laden, (2) "Catch-up"-Daten vom Broker holen, (3) auf den Live-Stream umschalten.
### Modus 2: "Simulation & Backtest"
* **Motivation**: Für Entwicklung, Test und Analyse muss die Software **völlig autonom** und ohne Abhängigkeit von einer externen, potenziell nicht verfügbaren Broker-API lauffähig sein.
* **Ziel**: Einen "Sofort-Start" für Backtests zu jedem beliebigen Zeitpunkt in der Vergangenheit zu ermöglichen, der rein auf lokalen Dateien basiert.
* **Ergebnis**: Ein Design, bei dem der relevante Datenkontext in den Speicher geladen wird, um von dort aus einen schnellen Start und ein "Playback" der Daten zu ermöglichen.
147 lines
4.6 KiB
ObjectPascal
147 lines
4.6 KiB
ObjectPascal
unit Myc.Test.Trade.DataStream;
|
|
|
|
interface
|
|
|
|
uses
|
|
System.SysUtils,
|
|
System.Generics.Collections,
|
|
DUnitX.TestFramework,
|
|
Myc.Signals,
|
|
Myc.Lazy,
|
|
Myc.Futures,
|
|
Myc.Trade.DataPoint,
|
|
Myc.Trade.DataStream;
|
|
|
|
type
|
|
// Test fixture for TDataServer and TAuraTABFileServer, focusing on data equivalence
|
|
// with TDataSeries.LoadDataSeries.
|
|
[TestFixture]
|
|
[IgnoreMemoryLeaks(true)]
|
|
TTest_TABFileServer_Equivalence = class(TObject)
|
|
private
|
|
FServer: IAuraDataServer<TAskBidItem>;
|
|
FStream: IDataStream<TAskBidItem>;
|
|
public
|
|
[SetupFixture]
|
|
procedure SetupFixture;
|
|
[Setup]
|
|
procedure Setup;
|
|
[TearDown]
|
|
procedure TearDown;
|
|
[TearDownFixture]
|
|
procedure TearDownFixture;
|
|
|
|
[Test]
|
|
procedure Test_ServerReturnsSameDataAs_LoadDataSeries;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
Myc.TaskManager;
|
|
|
|
const
|
|
// The reference data file for this test.
|
|
C_TEST_PATH = '\\COFFEE\TickData\Pepperstone';
|
|
C_TEST_SYMBOL = 'GER40';
|
|
// The chunk size for fetching data from the server.
|
|
C_MAX_FETCH = 200;
|
|
|
|
{ TTest_TABFileServer_Equivalence }
|
|
|
|
procedure TTest_TABFileServer_Equivalence.SetupFixture;
|
|
begin
|
|
FServer := TAuraTABFileServer.Create(C_TEST_PATH);
|
|
// TAskBid.ClearCache ensures a clean state for data series loading.
|
|
FServer.ClearCache;
|
|
end;
|
|
|
|
procedure TTest_TABFileServer_Equivalence.TearDownFixture;
|
|
begin
|
|
FServer := nil;
|
|
|
|
// Resets the global IsMemoryLow function pointer, if it was set for testing.
|
|
IsMemoryLow := nil;
|
|
end;
|
|
|
|
procedure TTest_TABFileServer_Equivalence.Setup;
|
|
begin
|
|
// Initializes the data server with a specific test file.
|
|
FStream := FServer.CreateStream(C_TEST_SYMBOL);
|
|
end;
|
|
|
|
procedure TTest_TABFileServer_Equivalence.TearDown;
|
|
begin
|
|
// Releases the data server instance.
|
|
FStream := nil;
|
|
// Clears any cached data to prevent interference with subsequent tests.
|
|
FServer.ClearCache;
|
|
end;
|
|
|
|
procedure TTest_TABFileServer_Equivalence.Test_ServerReturnsSameDataAs_LoadDataSeries;
|
|
var
|
|
chunk: array[0..C_MAX_FETCH - 1] of TDataPoint<TAskBidItem>;
|
|
dst: TArray<TDataPoint<TAskBidItem>>;
|
|
i: Int64;
|
|
n: Integer;
|
|
cnt: Int64;
|
|
timeout: Integer;
|
|
filename: string;
|
|
expectedData: TArray<TDataPoint<TAskBidItem>>;
|
|
begin
|
|
// 1. Expected data is loaded directly using LoadDataSeries.
|
|
filename := TAuraTABFileServer.FindFirstDataFile(C_TEST_PATH, C_TEST_SYMBOL);
|
|
Assert.IsNotEmpty(filename, 'Test data file could not be found.');
|
|
|
|
expectedData := FServer.LoadDataSeries(filename).WaitFor;
|
|
SetLength(dst, Length(expectedData));
|
|
|
|
// 2. Fetch data chunks from the stream until all data is retrieved.
|
|
cnt := 0;
|
|
timeout := 0;
|
|
while (cnt < Length(expectedData)) do
|
|
begin
|
|
n := FStream.GetChunk(chunk);
|
|
if n > 0 then
|
|
begin
|
|
Move(chunk[0], dst[cnt], n * SizeOf(TDataPoint<TAskBidItem>));
|
|
Inc(cnt, n);
|
|
timeout := 0; // Reset timeout on progress
|
|
end
|
|
else
|
|
begin
|
|
// If GetChunk returns 0, the stream might be waiting for async I/O.
|
|
// A small sleep prevents a tight loop from consuming 100% CPU.
|
|
Sleep(1);
|
|
Inc(timeout);
|
|
Assert.IsTrue(timeout < 5000, 'Test timed out waiting for data from stream.');
|
|
end;
|
|
end;
|
|
|
|
// 3. A final call should return 0, as the stream must be depleted.
|
|
n := FStream.GetChunk(chunk);
|
|
Assert.AreEqual(0, n, 'Stream returned data after it should have been depleted.');
|
|
n := FStream.GetChunk(chunk);
|
|
Assert.AreEqual(0, n, 'Stream returned data after it should have been depleted.');
|
|
|
|
// --- Assertions ---
|
|
|
|
// 4. Verify that the total number of records fetched matches the expected count.
|
|
Assert.AreEqual(Length(expectedData), cnt, 'Data record count mismatch.');
|
|
|
|
// 5. Verify that the content of each record is identical.
|
|
// The loop checks every 1000th element for efficiency.
|
|
for n := 0 to High(expectedData) div 1000 do
|
|
begin
|
|
i := n * 1000;
|
|
Assert.AreEqual(expectedData[i].Time, dst[i].Time, 'Timestamp mismatch at index ' + i.ToString);
|
|
Assert.AreEqual(expectedData[i].Data.Ask, dst[i].Data.Ask, 'Ask price mismatch at index ' + i.ToString);
|
|
Assert.AreEqual(expectedData[i].Data.Bid, dst[i].Data.Bid, 'Bid price mismatch at index ' + i.ToString);
|
|
end;
|
|
end;
|
|
|
|
initialization
|
|
// Registers the test fixture with DUnitX.
|
|
TDUnitX.RegisterTestFixture(TTest_TABFileServer_Equivalence);
|
|
end.
|