This commit is contained in:
Michael Schimmel
2025-12-08 10:54:22 +01:00
parent 95de9a155e
commit 7aa0056799
2 changed files with 79 additions and 79 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ unit Myc.Data.Records;
interface
uses
system.generics.collections,
System.Generics.Collections,
Myc.Data.Keyword,
Myc.Data.Value;
+78 -78
View File
@@ -27,7 +27,7 @@ type
// Aura files
// Represents metadata for a single data file.
TAuraDataFile = record
TDataFile = record
private
FExtension: String;
FPath: String;
@@ -49,12 +49,12 @@ type
end;
// Generic server for loading and managing sequential time-series data from files.
TAuraDataServer<T: record> = class(TInterfacedObject, IDataServer<T>)
TDataServer<T: record> = class(TInterfacedObject, IDataServer<T>)
private
FCachedFiles: TDataFileCache<TArray<TArray<TDataPoint<T>>>>;
FPath: String;
// The cache now holds a sorted array of all files for each symbol.
FSymbols: TFuture<TDictionary<String, TArray<TAuraDataFile>>>;
FSymbols: TFuture<TDictionary<String, TArray<TDataFile>>>;
function GetPath: String;
function ProcessChunks(
@@ -64,7 +64,7 @@ type
): TState;
function ProcessFile(
FileInfo: TAuraDataFile;
FileInfo: TDataFile;
const DataFile: TFuture<TArray<TArray<TDataPoint<T>>>>;
const Terminated: TState;
Processor: IConsumer<TArray<TDataPoint<T>>>
@@ -78,7 +78,7 @@ type
// Used by cache to actually load an uncached file. Now virtual and abstract.
function DoLoad(const FileName: string; const Gate: TLatch): TFuture<TArray<TArray<TDataPoint<T>>>>; virtual; abstract;
// Gets the next consecutive data file from the cached, sorted list.
function GetNextFile(const Curr: TAuraDataFile): TAuraDataFile; virtual;
function GetNextFile(const Curr: TDataFile): TDataFile; virtual;
public
constructor Create(const APath: String);
@@ -87,12 +87,12 @@ type
procedure ClearCache;
procedure UpdateSymbols;
// Scans a directory to find the oldest file for a specific symbol.
function FindFirstFile(const Symbol: string): TFuture<TAuraDataFile>;
function ParseFileName(const FileName: string): TAuraDataFile; virtual; abstract;
function FindFirstFile(const Symbol: string): TFuture<TDataFile>;
function ParseFileName(const FileName: string): TDataFile; virtual; abstract;
function EnumerateSymbols: TFuture<TArray<String>>;
// Load data file and split content into chunks.
function LoadDataFile(const DataFile: TAuraDataFile): TFuture<TArray<TArray<TDataPoint<T>>>>;
function LoadDataFile(const DataFile: TDataFile): TFuture<TArray<TArray<TDataPoint<T>>>>;
function ProcessData(const Symbol: String; const Terminated: TState; const Processor: IConsumer<TArray<TDataPoint<T>>>): TState;
@@ -101,23 +101,23 @@ type
// Aura tick data file Ask-Bid
TAuraAskBidFileItem = packed record
TAskBidFileItem = packed record
Ask: Single;
Bid: Single;
end;
TAuraTABFileServer = class(TAuraDataServer<TAuraAskBidFileItem>)
TAskBidFileServer = class(TDataServer<TAskBidFileItem>)
private
class function ReadCompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<TAuraAskBidFileItem>>>;
class function ReadUncompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<TAuraAskBidFileItem>>>;
class function ReadCompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<TAskBidFileItem>>>;
class function ReadUncompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<TAskBidFileItem>>>;
protected
function DoLoad(const FileName: string; const Gate: TLatch): TFuture<TArray<TArray<TDataPoint<TAuraAskBidFileItem>>>>; override;
function DoLoad(const FileName: string; const Gate: TLatch): TFuture<TArray<TArray<TDataPoint<TAskBidFileItem>>>>; override;
public
function ParseFileName(const FileName: string): TAuraDataFile; override;
function ParseFileName(const FileName: string): TDataFile; override;
end;
// File record for cTrader M1 data
TAuraM1FileItem = packed record
TM1FileItem = packed record
OADateTime: Double;
Open: Int64;
High: Int64;
@@ -130,14 +130,14 @@ type
// File server for cTrader M1 data. Note that the generic type is TOhlcItem,
// as the server converts the file data into standard OHLC items.
TAuraM1FileServer = class(TAuraDataServer<TOhlcItem>)
TM1FileServer = class(TDataServer<TOhlcItem>)
private
class function ReadCompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<TOhlcItem>>>;
class function ReadUncompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<TOhlcItem>>>;
protected
function DoLoad(const FileName: string; const Gate: TLatch): TFuture<TArray<TArray<TDataPoint<TOhlcItem>>>>; override;
public
function ParseFileName(const FileName: string): TAuraDataFile; override;
function ParseFileName(const FileName: string): TDataFile; override;
end;
implementation
@@ -153,9 +153,9 @@ const
// The number of records per data chunk when loading files.
ChunkSize = 512;
{ TAuraDataFile }
{ TDataFile }
constructor TAuraDataFile.Create(const APath, ASymbol, AExtension: String; AYear, AMonth: Integer; const ABasename: String);
constructor TDataFile.Create(const APath, ASymbol, AExtension: String; AYear, AMonth: Integer; const ABasename: String);
begin
FPath := APath;
FSymbol := ASymbol;
@@ -165,24 +165,24 @@ begin
FBasename := ABasename;
end;
function TAuraDataFile.GetBaseFileName: string;
function TDataFile.GetBaseFileName: string;
begin
Result := FBasename;
end;
function TAuraDataFile.GetFullFileName: string;
function TDataFile.GetFullFileName: string;
begin
Result := TPath.Combine(FPath, GetBaseFileName + FExtension);
end;
function TAuraDataFile.GetIsValid: Boolean;
function TDataFile.GetIsValid: Boolean;
begin
Result := (FSymbol <> '') and (FYear > 0);
end;
{ TAuraDataServer<T> }
{ TDataServer<T> }
constructor TAuraDataServer<T>.Create(const APath: String);
constructor TDataServer<T>.Create(const APath: String);
begin
inherited Create;
FPath := APath;
@@ -197,38 +197,38 @@ begin
);
end;
destructor TAuraDataServer<T>.Destroy;
destructor TDataServer<T>.Destroy;
begin
ClearCache;
FCachedFiles.Free;
inherited Destroy;
end;
procedure TAuraDataServer<T>.AfterConstruction;
procedure TDataServer<T>.AfterConstruction;
begin
inherited;
UpdateSymbols;
end;
procedure TAuraDataServer<T>.ClearCache;
procedure TDataServer<T>.ClearCache;
begin
FCachedFiles.Clear;
end;
procedure TAuraDataServer<T>.UpdateSymbols;
procedure TDataServer<T>.UpdateSymbols;
begin
FSymbols :=
TFuture<TDictionary<String, TArray<TAuraDataFile>>>.Construct(
TFuture<TDictionary<String, TArray<TDataFile>>>.Construct(
FSymbols.Done,
function: TDictionary<String, TArray<TAuraDataFile>>
function: TDictionary<String, TArray<TDataFile>>
var
fileNames: TArray<string>;
tempSymbolMap: TDictionary<String, TList<TAuraDataFile>>;
dataFile: TAuraDataFile;
symbolList: TList<TAuraDataFile>;
tempSymbolMap: TDictionary<String, TList<TDataFile>>;
dataFile: TDataFile;
symbolList: TList<TDataFile>;
begin
Result := TDictionary<String, TArray<TAuraDataFile>>.Create;
tempSymbolMap := TDictionary<String, TList<TAuraDataFile>>.Create;
Result := TDictionary<String, TArray<TDataFile>>.Create;
tempSymbolMap := TDictionary<String, TList<TDataFile>>.Create;
try
if not TDirectory.Exists(FPath) then
exit;
@@ -241,7 +241,7 @@ begin
begin
if not tempSymbolMap.TryGetValue(dataFile.Symbol, symbolList) then
begin
symbolList := TList<TAuraDataFile>.Create;
symbolList := TList<TDataFile>.Create;
tempSymbolMap.Add(dataFile.Symbol, symbolList);
end;
symbolList.Add(dataFile);
@@ -252,8 +252,8 @@ begin
begin
symbolList := kvp.Value;
symbolList.Sort(
TComparer<TAuraDataFile>.Construct(
function(const Left, Right: TAuraDataFile): Integer
TComparer<TDataFile>.Construct(
function(const Left, Right: TDataFile): Integer
begin
if Left.Year < Right.Year then
Result := -1
@@ -277,37 +277,37 @@ begin
FSymbols.Manage;
end;
function TAuraDataServer<T>.EnumerateSymbols: TFuture<TArray<String>>;
function TDataServer<T>.EnumerateSymbols: TFuture<TArray<String>>;
begin
Result :=
FSymbols.Chain<TArray<String>>(
function(const Symbols: TDictionary<String, TArray<TAuraDataFile>>): TArray<String> begin Result := Symbols.Keys.ToArray; end
function(const Symbols: TDictionary<String, TArray<TDataFile>>): TArray<String> begin Result := Symbols.Keys.ToArray; end
);
end;
function TAuraDataServer<T>.FindFirstFile(const Symbol: string): TFuture<TAuraDataFile>;
function TDataServer<T>.FindFirstFile(const Symbol: string): TFuture<TDataFile>;
begin
var symName := Symbol;
Result :=
FSymbols.Chain<TAuraDataFile>(
function(const Symbols: TDictionary<String, TArray<TAuraDataFile>>): TAuraDataFile
FSymbols.Chain<TDataFile>(
function(const Symbols: TDictionary<String, TArray<TDataFile>>): TDataFile
var
symbolFiles: TArray<TAuraDataFile>;
symbolFiles: TArray<TDataFile>;
begin
if Symbols.TryGetValue(symName, symbolFiles) and (Length(symbolFiles) > 0) then
Result := symbolFiles[0]
else
Result := Default(TAuraDataFile);
Result := Default(TDataFile);
end
);
end;
function TAuraDataServer<T>.GetNextFile(const Curr: TAuraDataFile): TAuraDataFile;
function TDataServer<T>.GetNextFile(const Curr: TDataFile): TDataFile;
var
allSymbolFiles: TArray<TAuraDataFile>;
allSymbolFiles: TArray<TDataFile>;
foundIndex: Integer;
begin
Result := Default(TAuraDataFile);
Result := Default(TDataFile);
if not Curr.IsValid or not FSymbols.Done.IsSet then
exit;
@@ -330,12 +330,12 @@ begin
end;
end;
function TAuraDataServer<T>.GetPath: String;
function TDataServer<T>.GetPath: String;
begin
Result := FPath;
end;
function TAuraDataServer<T>.ProcessData(
function TDataServer<T>.ProcessData(
const Symbol: String;
const Terminated: TState;
const Processor: IConsumer<TArray<TDataPoint<T>>>
@@ -347,13 +347,13 @@ begin
Result :=
FindFirstFile(Symbol)
.Chain(
function(const FirstFileInfo: TAuraDataFile): TState
function(const FirstFileInfo: TDataFile): TState
begin
Result := ProcessFile(FirstFileInfo, LoadDataFile(FirstFileInfo), cTerminated, cProc);
end);
end;
function TAuraDataServer<T>.LoadDataFile(const DataFile: TAuraDataFile): TFuture<TArray<TArray<TDataPoint<T>>>>;
function TDataServer<T>.LoadDataFile(const DataFile: TDataFile): TFuture<TArray<TArray<TDataPoint<T>>>>;
begin
if DataFile.IsValid then
Result := FCachedFiles.GetOrAdd(DataFile.GetFullFileName)
@@ -361,7 +361,7 @@ begin
Result := TFuture<TArray<TArray<TDataPoint<T>>>>.Construct(TArray<TArray<TDataPoint<T>>>(nil));
end;
function TAuraDataServer<T>.ProcessChunks(
function TDataServer<T>.ProcessChunks(
const DataChunks: TArray<TArray<TDataPoint<T>>>;
Terminated: TState;
Processor: IConsumer<TArray<TDataPoint<T>>>
@@ -387,8 +387,8 @@ begin
end
end;
function TAuraDataServer<T>.ProcessFile(
FileInfo: TAuraDataFile;
function TDataServer<T>.ProcessFile(
FileInfo: TDataFile;
const DataFile: TFuture<TArray<TArray<TDataPoint<T>>>>;
const Terminated: TState;
Processor: IConsumer<TArray<TDataPoint<T>>>
@@ -417,11 +417,11 @@ begin
);
end;
{ TAuraTABFileServer }
{ TAskBidFileServer }
function TAuraTABFileServer.DoLoad(const FileName: string; const Gate: TLatch): TFuture<TArray<TArray<TDataPoint<TAuraAskBidFileItem>>>>;
function TAskBidFileServer.DoLoad(const FileName: string; const Gate: TLatch): TFuture<TArray<TArray<TDataPoint<TAskBidFileItem>>>>;
begin
Result := TFuture<TArray<TArray<TDataPoint<TAuraAskBidFileItem>>>>.Null;
Result := TFuture<TArray<TArray<TDataPoint<TAskBidFileItem>>>>.Null;
if not TFile.Exists(FileName) then
exit;
@@ -432,8 +432,8 @@ begin
Result :=
TFuture<TBytes>
.Construct(Gate.Enqueue, function: TBytes begin Result := TFile.ReadAllBytes(capFileName); end)
.Chain<TArray<TArray<TDataPoint<TAuraAskBidFileItem>>>>(
function(bytes: TBytes): TArray<TArray<TDataPoint<TAuraAskBidFileItem>>>
.Chain<TArray<TArray<TDataPoint<TAskBidFileItem>>>>(
function(bytes: TBytes): TArray<TArray<TDataPoint<TAskBidFileItem>>>
begin
var compMemoryStream := TBytesStream.Create(bytes);
try
@@ -447,9 +447,9 @@ begin
else
begin
Result :=
TFuture<TArray<TArray<TDataPoint<TAuraAskBidFileItem>>>>.Construct(
TFuture<TArray<TArray<TDataPoint<TAskBidFileItem>>>>.Construct(
Gate.Enqueue,
function: TArray<TArray<TDataPoint<TAuraAskBidFileItem>>>
function: TArray<TArray<TDataPoint<TAskBidFileItem>>>
begin
if TFile.Exists(capFileName) then
begin
@@ -465,13 +465,13 @@ begin
end;
end;
function TAuraTABFileServer.ParseFileName(const FileName: string): TAuraDataFile;
function TAskBidFileServer.ParseFileName(const FileName: string): TDataFile;
var
fileNameNoPath, nameForParsing, baseName, ext, path, symbol: string;
year, month: Integer;
parts: TArray<string>;
begin
Result := Default(TAuraDataFile);
Result := Default(TDataFile);
path := TPath.GetDirectoryName(FileName);
fileNameNoPath := TPath.GetFileName(FileName);
@@ -509,10 +509,10 @@ begin
if symbol = '' then
exit;
Result := TAuraDataFile.Create(path, symbol, fileExt, year, month, baseName);
Result := TDataFile.Create(path, symbol, fileExt, year, month, baseName);
end;
class function TAuraTABFileServer.ReadCompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<TAuraAskBidFileItem>>>;
class function TAskBidFileServer.ReadCompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<TAskBidFileItem>>>;
var
decompressionStream: TStream;
localHeader: TZipHeader;
@@ -551,11 +551,11 @@ begin
end;
end;
class function TAuraTABFileServer.ReadUncompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<TAuraAskBidFileItem>>>;
class function TAskBidFileServer.ReadUncompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<TAskBidFileItem>>>;
type
TFileRecord = packed record
TimeStamp: TDateTime;
Data: TAuraAskBidFileItem;
Data: TAskBidFileItem;
end;
var
fileSize: Int64;
@@ -592,15 +592,15 @@ begin
raise EReadError.CreateFmt('Read error. Expected %d bytes, read %d.', [SizeOf(TFileRecord), bytesRead]);
// Corrected syntax: Assign the newly created record instance.
Result[chunkIndex][indexInChunk] := TDataPoint<TAuraAskBidFileItem>.Create(rec.TimeStamp, rec.Data);
Result[chunkIndex][indexInChunk] := TDataPoint<TAskBidFileItem>.Create(rec.TimeStamp, rec.Data);
Inc(indexInChunk);
end;
end;
end;
{ TAuraM1FileServer }
{ TM1FileServer }
function TAuraM1FileServer.DoLoad(const FileName: string; const Gate: TLatch): TFuture<TArray<TArray<TDataPoint<TOhlcItem>>>>;
function TM1FileServer.DoLoad(const FileName: string; const Gate: TLatch): TFuture<TArray<TArray<TDataPoint<TOhlcItem>>>>;
begin
Result := TFuture<TArray<TArray<TDataPoint<TOhlcItem>>>>.Null;
@@ -628,13 +628,13 @@ begin
end;
end;
function TAuraM1FileServer.ParseFileName(const FileName: string): TAuraDataFile;
function TM1FileServer.ParseFileName(const FileName: string): TDataFile;
var
path, fileNameNoPath, baseName, ext, symbol: string;
year, month: Integer;
parts, dateParts: TArray<string>;
begin
Result := Default(TAuraDataFile);
Result := Default(TDataFile);
path := TPath.GetDirectoryName(FileName);
fileNameNoPath := TPath.GetFileName(FileName);
@@ -670,11 +670,11 @@ begin
if symbol = '' then
exit;
// The TAuraDataFile only represents the start date of the file batch.
Result := TAuraDataFile.Create(path, symbol, ext, year, month, baseName);
// The TDataFile only represents the start date of the file batch.
Result := TDataFile.Create(path, symbol, ext, year, month, baseName);
end;
class function TAuraM1FileServer.ReadCompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<TOhlcItem>>>;
class function TM1FileServer.ReadCompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<TOhlcItem>>>;
var
decompressionStream: TStream;
localHeader: TZipHeader;
@@ -725,9 +725,9 @@ begin
end;
end;
class function TAuraM1FileServer.ReadUncompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<TOhlcItem>>>;
class function TM1FileServer.ReadUncompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<TOhlcItem>>>;
type
TFileRecord = TAuraM1FileItem;
TFileRecord = TM1FileItem;
var
fileSize: Int64;
totalRecordCount, bytesRead, chunkIndex, indexInChunk, recordIndex: Integer;