Refactoring
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
Zweck und Ziele
|
||||
|
||||
* Unterstütze mich bei der Entwicklung unter Embarcadero Delphi.
|
||||
* Ich bin ein sehr erfahrener Softwareentwickler. Fasse dich kurz und nutze Fachsprache.
|
||||
* Wir nutzen immer die neueste Delphi-Version, aktuell ist das Delphi 12 Athens.
|
||||
|
||||
|
||||
|
||||
Regeln
|
||||
|
||||
* Sprache im Code und in den Kommentaren: Englisch
|
||||
* Sprache im Chat: Deutsch
|
||||
|
||||
* Ändere niemals den Code, den ich poste - es sei denn, ich fordere dich ausdrücklich dazu auf. Wenn ich Code poste, dann analysiere ihn zunächst und weise mich gegebenenfalls auf Unstimmigkeiten hin.
|
||||
|
||||
* Von mir geposteter Code ersetzt grundsätzlich die ältere Versionen des selben Codes.
|
||||
|
||||
* Bei der Code-Analyse zählt nur die tatsächliche Implementierung. Kommentare können veraltet sein. Weise mich auf Differenzen zwischen Implementierung und Kommentaren hin.
|
||||
|
||||
* Finde Schlüsselstellen im Code und zeige mir durch eine kurze Erklärung, dass du sie verstanden hast.
|
||||
|
||||
* Effizienz ist mir sehr wichtig. Wenn dir etwas auffällt, das die Performance negativ beeinflussen kann, dann weise mich darauf hin.
|
||||
|
||||
* Schlage gegebenenfalls Korrekturen vor. Warte auf meine Zustimmung, bevor die sie vornimmst.
|
||||
|
||||
* Erkläre niemals grundlegende Syntax, es sei denn ich frage ausdrücklich danach.
|
||||
|
||||
* Fasse dich kurz.
|
||||
|
||||
* Behalte den Kontext während der gesamten Konversation bei. Alle Ideen und Antworten sollen mit der vorherigen Diskussion in Verbindung stehen. Schweife nicht ab.
|
||||
|
||||
* Wenn ich unvollständigen Code poste, erstelle einen Plan, wie die Implementierung aussehen könnte und präsentiere ihn kurz und prägnant.
|
||||
|
||||
|
||||
Code-Generierung
|
||||
|
||||
* Befolge die gängigen Delphi-Formatierungsstandards mit folgenden Ausnahmen:
|
||||
|
||||
- Einrückung mit 4 Leerzeichen anstelle von 2. Auch bei Kommentaren.
|
||||
- Das Code-Format ist UTF-8.
|
||||
- Folgende Schlüsselwörter müssen klein geschrieben werden:
|
||||
and, or, not, mod, div, in, as, is, array of, sizeof
|
||||
|
||||
* Ändere niemals vorhandene Bezeichner im vom Benutzer bereitgestellten Code, es sei denn du wirst dazu aufgefordert.
|
||||
|
||||
* Bei verketteten Vergleichen innerhalb von if-Anweisungen müssen immer runde Klammern verwendet werden, um Teilausdrücke klar zu gruppieren:
|
||||
"if (a > b) and (c < d) then"
|
||||
|
||||
* Funktions- und Prozedurparameter dürfen keinen Präfix haben. Sie sollten großgeschrieben werden:
|
||||
"procedure ProcessData(InputArray: TIntegerArray; const Count: Integer)"
|
||||
|
||||
* Ausnahme: Parameter von Konstruktoren haben "A" als Präfix:
|
||||
"constructor Create(const AValue: Integer)"
|
||||
|
||||
* Lokale Variablen sollten mit einem kleinen Buchstaben beginnen (camel case) (z.B. tempValue: Integer;). Wenn ich von dieser Regel abweiche, ist das in Ordnung.
|
||||
|
||||
* Achte beim Erstellen von Format-Strings (z. B. mit Format()), darauf, dass die Anzahl der Parameter genau der Anzahl der Format-Tags (z. B. %s, %d) entspricht. Überprüfe die Typkompatibilität.
|
||||
|
||||
* Denke daran, dass Delphi nicht zwischen Groß- und Kleinschreibung unterscheidet. Bezeichner müssen sich von Schlüsselwörtern unterscheiden.
|
||||
|
||||
* Interfaces benötigen keine GUIDs. Füge keine GUIDs in Interfaces ein und schlagen Sie dies auch nicht vor. Wenn ein gegebenes Interface keine GUID hat, ist das so gewollt. GUIDs werden ausschließlich von mir vergeben. Wenn du der Meinung bist, dass ein Interface unbedingt eine GUID benötigt, dann weise mich darauf hin.
|
||||
|
||||
* TThread ist in System.Classes definiert.
|
||||
* TInterlocked ist in System.SyncObjs definiert.
|
||||
|
||||
* Statement-Blöcke werden mit begin..end eingekapselt. (Niemals mit Klammern!)
|
||||
* begin und end stehen am Anfang einer neuen Zeile. then steht nie am Anfang einer neuen Zeile.
|
||||
* RECORDs, die einen Initialize-Operator haben, sind Managed Records. Sie benötigen also kein explizites Create.
|
||||
|
||||
Unit-Tests
|
||||
|
||||
* Verwende nur statische Strings für Log-Einträge und Asserts. Keine Format(), ToString usw. (Das verursacht Speicherlecks außerhalb des Test-Gültigkeitsbereichs, sodass ein Leak vom Memory Manager gemeldet wird.)
|
||||
|
||||
* Verwende das Attribut für parametrische Tests, z. B. [TestCase('TestName', 'Parameter1,Parameter2,...')]
|
||||
|
||||
|
||||
Kommentare im Code
|
||||
|
||||
* Vermeide jegliche Kommentare, die Änderungen am Code beschreiben. Z.B. "// hier wurde was geändert". Das mag ich gar nicht.
|
||||
|
||||
* Benutze keine HTML-Tags (<summary>, etc.)!
|
||||
* Benutze "//" oder "(* *)" und fasse dich extrem kurz. Meistens genügen Einzeiler vor den Deklarationen.
|
||||
|
||||
* Kommentare in der interface-Sektion einer Unit sollen die Schnittstelle dokumentieren. Dokumentiere ausschließlich Elemente, die auch von außen zugänglich sind. Und beziehe dich nur auf Elemente, die von außen zugänglich sind. Im Interface wird beschrieben, WAS eine Funktion macht. Es wird nicht beschrieben WIE sie es macht!
|
||||
|
||||
* Kommentare im Implementation-Teil sollten sehr sparsam eingesetzt werden. Sie sind nur nötig, wenn etwas wirklich kompliziertes Beschrieben werden muss und auch nur, wenn sich die Funktion nicht aus dem Quelltext ergibt.
|
||||
|
||||
|
||||
Interface helper
|
||||
|
||||
* Ein "interface helper" ist ein record, das immer nur genau ein Interface referenziert. Wenn ich dich dazu auffordere sollst du ihn so weit wie möglich selbst erzeugen, oder einen unvollständigen helper ergänzen. Auf jeden Fall enthält ein interface helper:
|
||||
- einen Konstruktor
|
||||
- zwei implicit-operatoren, die vom helper zum interface casten (und umgekehrt)
|
||||
- Wrapper für die Methoden und Properties des Interface.
|
||||
- ein optionales class property "Null".
|
||||
|
||||
Beispiel:
|
||||
|
||||
type
|
||||
IFuture = interface
|
||||
function GetValue: T;
|
||||
function GetDone: TState;
|
||||
end;
|
||||
|
||||
TFuture<T> = record
|
||||
{$REGION 'private'}
|
||||
strict private
|
||||
class var
|
||||
FNull: IFuture;
|
||||
|
||||
class constructor CreateClass;
|
||||
|
||||
private
|
||||
FFuture: IFuture;
|
||||
function GetDone: TState; inline;
|
||||
function GetValue: T; inline;
|
||||
{$ENDREGION}
|
||||
public
|
||||
constructor Create(const AFuture: IFuture);
|
||||
|
||||
class operator Initialize(out Dest: TFuture<T>);
|
||||
class operator Implicit(const A: IFuture): TFuture<T>; overload;
|
||||
class operator Implicit(const A: TFuture<T>): IFuture; overload;
|
||||
|
||||
class property Null: IFuture read FNull;
|
||||
|
||||
// Wrapper methods for IFuture
|
||||
property Done: TState read GetDone;
|
||||
property Value: T read GetValue;
|
||||
end;
|
||||
|
||||
constructor TFuture<T>.Create(const AFuture: IFuture);
|
||||
begin
|
||||
FFuture := AFuture;
|
||||
if not Assigned(FFuture) then
|
||||
FFuture := FNull;
|
||||
end;
|
||||
|
||||
class constructor TFuture<T>.CreateClass;
|
||||
begin
|
||||
// init FNull
|
||||
end;
|
||||
|
||||
class operator TFuture<T>.Implicit(const A: IFuture): TFuture<T>;
|
||||
begin
|
||||
Result.Create(A);
|
||||
end;
|
||||
|
||||
class operator TFuture<T>.Implicit(const A: TFuture<T>): IFuture;
|
||||
begin
|
||||
Result := A.FFuture;
|
||||
end;
|
||||
|
||||
class operator TFuture<T>.Initialize(out Dest: TFuture<T>);
|
||||
begin
|
||||
Dest.FFuture := FNull;
|
||||
end;
|
||||
|
||||
function TFuture<T>.GetDone: TState;
|
||||
begin
|
||||
Result := FFuture.Done;
|
||||
end;
|
||||
|
||||
function TFuture<T>.GetValue: T;
|
||||
begin
|
||||
Result := FFuture.Value;
|
||||
end;
|
||||
@@ -4,7 +4,7 @@ interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Generics.Collections, // E2003: Added for TList<T>
|
||||
System.Generics.Collections,
|
||||
DUnitX.TestFramework,
|
||||
Myc.Signals,
|
||||
Myc.Lazy,
|
||||
@@ -14,6 +14,8 @@ uses
|
||||
Myc.Trade.DataServer;
|
||||
|
||||
type
|
||||
// Test fixture for TDataServer and TAuraTABFileServer, focusing on data equivalence
|
||||
// with TDataSeries.LoadDataSeries.
|
||||
[TestFixture]
|
||||
[IgnoreMemoryLeaks(true)]
|
||||
TTest_TABFileServer_Equivalence = class(TObject)
|
||||
@@ -48,25 +50,27 @@ const
|
||||
|
||||
procedure TTest_TABFileServer_Equivalence.SetupFixture;
|
||||
begin
|
||||
// IsMemoryLow := function: Boolean begin exit(false); end;
|
||||
// Load the ground truth data using the original LoadDataSeries function.
|
||||
// SetupTaskManagerMock;
|
||||
// TAskBid.ClearCache ensures a clean state for data series loading.
|
||||
TAskBid.ClearCache;
|
||||
end;
|
||||
|
||||
procedure TTest_TABFileServer_Equivalence.TearDownFixture;
|
||||
begin
|
||||
// Resets the global IsMemoryLow function pointer, if it was set for testing.
|
||||
IsMemoryLow := nil;
|
||||
end;
|
||||
|
||||
procedure TTest_TABFileServer_Equivalence.Setup;
|
||||
begin
|
||||
FServer := TTABFileServer.Create(C_TEST_FILENAME);
|
||||
// Initializes the data server with a specific test file.
|
||||
FServer := TAuraTABFileServer.Create(C_TEST_FILENAME);
|
||||
end;
|
||||
|
||||
procedure TTest_TABFileServer_Equivalence.TearDown;
|
||||
begin
|
||||
// Releases the data server instance.
|
||||
FServer := nil;
|
||||
// Clears any cached data to prevent interference with subsequent tests.
|
||||
TAskBid.ClearCache;
|
||||
end;
|
||||
|
||||
@@ -76,11 +80,13 @@ var
|
||||
Dst: TArray<TDataPoint<TAskBidItem>>;
|
||||
i: Int64;
|
||||
begin
|
||||
// Loads the expected data directly using TAskBid.LoadDataSeries.WaitFor.
|
||||
var ExpectedData := TAskBid.LoadDataSeries(C_TEST_FILENAME).WaitFor;
|
||||
SetLength(Dst, Length(ExpectedData));
|
||||
|
||||
var cnt: Int64 := 0;
|
||||
var n: Integer;
|
||||
// Fetches data chunks from the server until all data is retrieved or server indicates live data.
|
||||
while not FServer.IsLiveData.Value and (cnt < Length(Dst)) do
|
||||
begin
|
||||
n := FServer.GetChunk(chunk);
|
||||
@@ -91,16 +97,14 @@ begin
|
||||
|
||||
// --- Assertions ---
|
||||
|
||||
// 1. Verify that the total number of records matches.
|
||||
// 1. Verify that the total number of records fetched from the server matches the expected count.
|
||||
Assert.AreEqual(Length(ExpectedData), cnt, 'Data record count mismatch');
|
||||
|
||||
// 2. 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;
|
||||
// E2003: Removed assertion for 'Index', as it's not part of the IDataPoint interface.
|
||||
// The index is an implementation detail of the faulty ConvertTicks and cannot be tested here.
|
||||
Assert.AreEqual(Dst[i].Idx, i, 'Index mismatch ');
|
||||
Assert.AreEqual(ExpectedData[i].TimeStamp, Dst[i].Time, 'Timestamp mismatch');
|
||||
Assert.AreEqual(ExpectedData[i].Data.Ask, Dst[i].Data.Ask, 'Ask price mismatch');
|
||||
Assert.AreEqual(ExpectedData[i].Data.Bid, Dst[i].Data.Bid, 'Bid price mismatch');
|
||||
@@ -108,5 +112,6 @@ begin
|
||||
end;
|
||||
|
||||
initialization
|
||||
// Registers the test fixture with DUnitX.
|
||||
TDUnitX.RegisterTestFixture(TTest_TABFileServer_Equivalence);
|
||||
end.
|
||||
|
||||
@@ -46,7 +46,9 @@ uses
|
||||
System.IOUtils,
|
||||
Myc.Futures,
|
||||
Myc.Signals,
|
||||
Myc.Trade.DataPoint;
|
||||
Myc.Lazy,
|
||||
Myc.Trade.DataPoint,
|
||||
Myc.Trade.DataServer;
|
||||
|
||||
type
|
||||
// Generic class for loading and managing sequential time-series data.
|
||||
@@ -94,6 +96,31 @@ type
|
||||
TTick = TDataSeries<TAskBidItem>.TDataPoint;
|
||||
end;
|
||||
|
||||
// Implements a data server that reads data from Aura-specific historical data files.
|
||||
// It handles sequential loading of data files and transitions to "live data" mode
|
||||
// when all historical files are exhausted.
|
||||
TAuraFileServer<T: record> = class(TDataServer<TDataPoint<T>>, IDataServer<TDataPoint<T>>)
|
||||
private
|
||||
FIsLiveData: TMutable<Boolean>.IWriteable;
|
||||
FCurrentFileName: string;
|
||||
FCurrentData: TFuture<TArray<TDataSeries<T>.TDataPoint>>;
|
||||
FNextFileName: string;
|
||||
FNextData: TFuture<TArray<TDataSeries<T>.TDataPoint>>;
|
||||
FCurrPosInFile: Int64;
|
||||
FCurrentIdx: Int64;
|
||||
FLastTimeStamp: TDateTime;
|
||||
|
||||
protected
|
||||
function GetChunk(var Data: array of TDataPoint<T>): Integer; override;
|
||||
function GetIsLiveData: TMutable<Boolean>; override;
|
||||
public
|
||||
constructor Create(const AFilename: String);
|
||||
procedure AfterConstruction; override;
|
||||
end;
|
||||
|
||||
// Specialized TAuraFileServer for TAskBidItem data.
|
||||
TAuraTABFileServer = TAuraFileServer<TAskBidItem>;
|
||||
|
||||
// Parses a filename to extract symbol, year, month, and path.
|
||||
function TryParseFileName(const FileName: string; out PathValue, SymbolValue: string; out YearValue, MonthValue: Integer): Boolean;
|
||||
|
||||
@@ -636,4 +663,95 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TAuraFileServer<T> }
|
||||
|
||||
constructor TAuraFileServer<T>.Create(const AFilename: String);
|
||||
begin
|
||||
inherited Create;
|
||||
FCurrentFileName := AFilename;
|
||||
FIsLiveData := TMutable<Boolean>.CreateWriteable(false);
|
||||
end;
|
||||
|
||||
procedure TAuraFileServer<T>.AfterConstruction;
|
||||
begin
|
||||
inherited;
|
||||
|
||||
// Asynchronously loads the initial data file.
|
||||
FCurrentData := TDataSeries<T>.LoadDataFile(FCurrentFileName);
|
||||
FCurrPosInFile := 0;
|
||||
FCurrentIdx := 0;
|
||||
// Initially sets the server to "not live data".
|
||||
FIsLiveData.SetValue(false);
|
||||
FLastTimeStamp := 0;
|
||||
|
||||
// Determines and asynchronously loads the next chronological file, if available.
|
||||
FNextFileName := FindNextDataFile(FCurrentFileName);
|
||||
if FNextFileName <> '' then
|
||||
FNextData := TDataSeries<T>.LoadDataFile(FNextFileName);
|
||||
end;
|
||||
|
||||
function TAuraFileServer<T>.GetChunk(var Data: array of TDataPoint<T>): Integer;
|
||||
begin
|
||||
Result := 0;
|
||||
|
||||
// Checks if the current data file is ready.
|
||||
if FCurrentData.Done.IsSet then
|
||||
begin
|
||||
var maxLen := Length(Data);
|
||||
|
||||
var currData := FCurrentData.Value;
|
||||
while Result < MaxLen do
|
||||
begin
|
||||
if FCurrPosInFile >= Length(currData) then
|
||||
begin
|
||||
// Transition to the next file if the current one is exhausted.
|
||||
FCurrentData := FNextData;
|
||||
FCurrentFileName := FNextFileName;
|
||||
FCurrPosInFile := 0;
|
||||
|
||||
FNextFileName := '';
|
||||
FNextData := TFuture<TArray<TDataSeries<T>.TDataPoint>>.Null;
|
||||
|
||||
if FCurrentFileName <> '' then
|
||||
begin
|
||||
// Loads the next file in the series.
|
||||
FNextFileName := FindNextDataFile(FCurrentFileName);
|
||||
if FNextFileName <> '' then
|
||||
FNextData := TDataSeries<T>.LoadDataFile(FNextFileName);
|
||||
|
||||
// If the newly assigned current data is already loaded, continue processing.
|
||||
if FCurrentData.Done.IsSet then
|
||||
begin
|
||||
currData := FCurrentData.Value;
|
||||
continue;
|
||||
end;
|
||||
end
|
||||
else
|
||||
begin
|
||||
// All historical files processed; transition to "live data" mode.
|
||||
FIsLiveData.SetValue(true);
|
||||
end;
|
||||
break; // Exit loop if no more data or transition occurred.
|
||||
end;
|
||||
|
||||
// Processes data point if its timestamp is strictly greater than the last processed one.
|
||||
// This ensures chronological order and handles potential duplicate timestamps.
|
||||
var item := currData[FCurrPosInFile];
|
||||
if FLastTimeStamp < item.TimeStamp then
|
||||
begin
|
||||
FLastTimeStamp := item.TimeStamp;
|
||||
Data[Result].Create(FCurrentIdx, FLastTimeStamp, item.Data);
|
||||
inc(Result);
|
||||
inc(FCurrentIdx);
|
||||
end;
|
||||
inc(FCurrPosInFile);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TAuraFileServer<T>.GetIsLiveData: TMutable<Boolean>;
|
||||
begin
|
||||
Result := FIsLiveData;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
+13
-116
@@ -4,152 +4,49 @@ interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
Myc.Lazy,
|
||||
Myc.Trade.DataPoint,
|
||||
Myc.Trade.DataSeries,
|
||||
Myc.Signals, // Added for TLatch
|
||||
Myc.Futures; // Added for TFuture
|
||||
Myc.Signals,
|
||||
Myc.Futures;
|
||||
|
||||
type
|
||||
// Represents a generic data server capable of providing sequential data chunks.
|
||||
// The type parameter T specifies the type of data points served.
|
||||
IDataServer<T> = interface
|
||||
['{A6E246A2-E84E-49AB-A63E-333E561E488C}']
|
||||
// Provides a TMutable<Boolean> that indicates if the server is currently
|
||||
// serving live data (true) or historical data (false).
|
||||
function GetIsLiveData: TMutable<Boolean>;
|
||||
// Retrieves a chunk of data into the provided dynamic array.
|
||||
// Returns the number of items successfully read into the array.
|
||||
function GetChunk(var Data: array of T): Integer;
|
||||
property IsLiveData: TMutable<Boolean> read GetIsLiveData;
|
||||
end;
|
||||
|
||||
// Abstract base class for IDataServer implementations.
|
||||
TDataServer<T> = class(TInterfacedObject, IDataServer<T>)
|
||||
protected
|
||||
function GetIsLiveData: TMutable<Boolean>; virtual; abstract;
|
||||
function GetChunk(var Data: array of T): Integer; virtual; abstract;
|
||||
end;
|
||||
|
||||
// Represents a factory for creating IDataServer instances.
|
||||
// The type parameter T specifies the type of data points the created server will provide.
|
||||
IDataServerNode<T> = interface
|
||||
['{DA3531F1-158E-4A63-8E5A-34089C537B1B}']
|
||||
// Creates and returns a new instance of an IDataServer.
|
||||
function CreateDataServer: IDataServer<T>;
|
||||
end;
|
||||
|
||||
// Abstract base class for IDataServerNode implementations.
|
||||
TDataServerNode<T> = class(TInterfacedObject, IDataServerNode<T>)
|
||||
public
|
||||
// Factory method to create the actual data server instance.
|
||||
function CreateDataServer: IDataServer<T>; virtual; abstract;
|
||||
end;
|
||||
|
||||
TTABFileServer = class(TDataServer<TDataPoint<TAskBidItem>>)
|
||||
|
||||
private
|
||||
FFilename: String;
|
||||
FIsLiveData: TMutable<Boolean>.IWriteable;
|
||||
|
||||
// Internal state
|
||||
FCurrentFileName: string;
|
||||
FCurrentData: TFuture<TArray<TAskBid.TTick>>;
|
||||
FNextFileName: string;
|
||||
FNextData: TFuture<TArray<TAskBid.TTick>>;
|
||||
FCurrPosInFile: Int64;
|
||||
FCurrentIdx: Int64;
|
||||
FLastTimeStamp: TDateTime;
|
||||
|
||||
protected
|
||||
function GetChunk(var Data: array of TDataPoint<TAskBidItem>): Integer; override;
|
||||
function GetIsLiveData: TMutable<Boolean>; override;
|
||||
public
|
||||
constructor Create(const AFilename: String);
|
||||
procedure AfterConstruction; override;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.IOUtils;
|
||||
|
||||
{ TTABFileServer<T> }
|
||||
|
||||
constructor TTABFileServer.Create(const AFilename: String);
|
||||
begin
|
||||
inherited Create;
|
||||
FFilename := AFilename;
|
||||
|
||||
FNextData := TFuture<TArray<TAskBid.TTick>>.Null;
|
||||
FIsLiveData := TMutable<Boolean>.CreateWriteable(false);
|
||||
end;
|
||||
|
||||
procedure TTABFileServer.AfterConstruction;
|
||||
begin
|
||||
inherited;
|
||||
|
||||
FCurrentFileName := FFilename;
|
||||
FCurrentData := TAskBid.LoadDataFile(FCurrentFileName);
|
||||
FCurrPosInFile := 0;
|
||||
FCurrentIdx := 0;
|
||||
FIsLiveData.SetValue(false);
|
||||
FLastTimeStamp := 0;
|
||||
FFilename := '';
|
||||
|
||||
FNextFileName := FindNextDataFile(FCurrentFileName);
|
||||
if FNextFileName <> '' then
|
||||
FNextData := TAskBid.LoadDataFile(FNextFileName);
|
||||
exit;
|
||||
end;
|
||||
|
||||
function TTABFileServer.GetChunk(var Data: array of TDataPoint<TAskBidItem>): Integer;
|
||||
begin
|
||||
Result := 0;
|
||||
|
||||
if FCurrentData.Done.IsSet then
|
||||
begin
|
||||
var maxLen := Length(Data);
|
||||
|
||||
var currData := FCurrentData.Value;
|
||||
while Result < MaxLen do
|
||||
begin
|
||||
if FCurrPosInFile >= Length(currData) then
|
||||
begin
|
||||
// Next File!
|
||||
FCurrentData := FNextData;
|
||||
FCurrentFileName := FNextFileName;
|
||||
FCurrPosInFile := 0;
|
||||
|
||||
FNextFileName := '';
|
||||
FNextData := TFuture<TArray<TAskBid.TTick>>.Null;
|
||||
|
||||
if FCurrentFileName <> '' then
|
||||
begin
|
||||
FNextFileName := FindNextDataFile(FCurrentFileName);
|
||||
if FNextFileName <> '' then
|
||||
FNextData := TAskBid.LoadDataFile(FNextFileName);
|
||||
|
||||
if FCurrentData.Done.IsSet then
|
||||
begin
|
||||
currData := FCurrentData.Value;
|
||||
continue;
|
||||
end;
|
||||
end
|
||||
else
|
||||
begin
|
||||
// We're done with this series
|
||||
FIsLiveData.SetValue(true);
|
||||
end;
|
||||
break;
|
||||
end;
|
||||
|
||||
var item := currData[FCurrPosInFile];
|
||||
if FLastTimeStamp < item.TimeStamp then
|
||||
begin
|
||||
FLastTimeStamp := item.TimeStamp;
|
||||
Data[Result].Create(FCurrentIdx, FLastTimeStamp, item.Data);
|
||||
inc(Result);
|
||||
inc(FCurrentIdx);
|
||||
end;
|
||||
inc(FCurrPosInFile);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TTABFileServer.GetIsLiveData: TMutable<Boolean>;
|
||||
begin
|
||||
Result := FIsLiveData;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
Reference in New Issue
Block a user