Chart now solid
This commit is contained in:
+103
-50
@@ -91,17 +91,17 @@ type
|
||||
// Generic server for loading and managing sequential time-series data from files.
|
||||
TAuraDataServer<T: record> = class(TInterfacedObject, IDataServer<T>)
|
||||
private
|
||||
FCachedFiles: TDataFileCache<TArray<TDataPoint<T>>>;
|
||||
FCachedFiles: TDataFileCache<TArray<TArray<TDataPoint<T>>>>;
|
||||
FPath: String;
|
||||
FSymbols: TFuture<TDictionary<String, TAuraDataFile>>;
|
||||
function GetPath: String;
|
||||
|
||||
// Used by cache to actually load an uncached file.
|
||||
function DoLoad(const FileName: string): TFuture<TArray<TDataPoint<T>>>;
|
||||
function DoLoad(const FileName: string): TFuture<TArray<TArray<TDataPoint<T>>>>;
|
||||
|
||||
function ProcessFile(
|
||||
FileInfo: TAuraDataFile;
|
||||
const DataFile: TFuture<TArray<TDataPoint<T>>>;
|
||||
const DataFile: TFuture<TArray<TArray<TDataPoint<T>>>>;
|
||||
const Terminated: TState;
|
||||
Processor: IMycProcessor<TArray<TDataPoint<T>>>
|
||||
): TState;
|
||||
@@ -111,8 +111,10 @@ type
|
||||
FLoadGate: TLatch;
|
||||
|
||||
protected
|
||||
class function ReadCompressedData(const InputStream: TStream): TArray<TDataPoint<T>>; virtual; abstract;
|
||||
class function ReadUncompressedData(const InputStream: TStream): TArray<TDataPoint<T>>; virtual; abstract;
|
||||
// Read data from stream and return it chunked.
|
||||
class function ReadCompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<T>>>; virtual; abstract;
|
||||
// Read data from stream and return it chunked.
|
||||
class function ReadUncompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<T>>>; virtual; abstract;
|
||||
|
||||
public
|
||||
constructor Create(const APath: String);
|
||||
@@ -125,7 +127,9 @@ type
|
||||
function FindFirstFile(const Symbol: string): TFuture<TAuraDataFile>;
|
||||
function ParseFileName(const FileName: string): TAuraDataFile; virtual; abstract;
|
||||
function EnumerateSymbols: TFuture<TArray<String>>;
|
||||
function LoadDataFile(const DataFile: TAuraDataFile): TFuture<TArray<TDataPoint<T>>>;
|
||||
|
||||
// Load data file and split content into chunks.
|
||||
function LoadDataFile(const DataFile: TAuraDataFile): TFuture<TArray<TArray<TDataPoint<T>>>>;
|
||||
|
||||
function ProcessData(const Symbol: String; const Terminated: TState; const Processor: IMycProcessor<TArray<TDataPoint<T>>>): TState;
|
||||
|
||||
@@ -138,7 +142,7 @@ type
|
||||
type
|
||||
TDataStream = record
|
||||
FileInfo: TAuraDataFile;
|
||||
Data: TFuture<TArray<TDataPoint<T>>>;
|
||||
Data: TFuture<TArray<TArray<TDataPoint<T>>>>;
|
||||
end;
|
||||
private
|
||||
FDataServer: TAuraDataServer<T>;
|
||||
@@ -147,6 +151,7 @@ type
|
||||
FNextReady: TFlag;
|
||||
FCurrent: TFuture<TDataStream>;
|
||||
FNext: TFuture<TDataStream>;
|
||||
FCurrChunkInFile: Int64;
|
||||
FCurrPosInFile: Int64;
|
||||
FLastTimeStamp: TDateTime;
|
||||
procedure PreloadNext;
|
||||
@@ -172,8 +177,8 @@ type
|
||||
TAuraTABFileServer = class(TAuraDataServer<TAuraAskBidFileItem>)
|
||||
protected
|
||||
// Scans a directory and returns the oldest file found for each symbol.
|
||||
class function ReadCompressedData(const InputStream: TStream): TArray<TDataPoint<TAuraAskBidFileItem>>; override;
|
||||
class function ReadUncompressedData(const InputStream: TStream): TArray<TDataPoint<TAuraAskBidFileItem>>; override;
|
||||
class function ReadCompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<TAuraAskBidFileItem>>>; override;
|
||||
class function ReadUncompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<TAuraAskBidFileItem>>>; override;
|
||||
public
|
||||
function CreateStream(const Symbol: String): IDataStream<TAuraAskBidFileItem>; override;
|
||||
function LoadDataSeries(const InitialFile: TAuraDataFile): TFuture<TArray<TDataPoint<TAuraAskBidFileItem>>>;
|
||||
@@ -188,6 +193,10 @@ uses
|
||||
System.StrUtils,
|
||||
Myc.TaskManager;
|
||||
|
||||
const
|
||||
// The number of records per data chunk when loading files.
|
||||
ChunkSize = 8192;
|
||||
|
||||
{ TAuraDataFile }
|
||||
|
||||
constructor TAuraDataFile.Create(const APath, ASymbol, AExtension: String; AYear, AMonth: Integer);
|
||||
@@ -252,8 +261,8 @@ begin
|
||||
FPath := APath;
|
||||
|
||||
FCachedFiles :=
|
||||
TDataFileCache<TArray<TDataPoint<T>>>
|
||||
.Create(function(const Filename: String): TFuture<TArray<TDataPoint<T>>> begin Result := DoLoad(Filename); end);
|
||||
TDataFileCache<TArray<TArray<TDataPoint<T>>>>
|
||||
.Create(function(const Filename: String): TFuture<TArray<TArray<TDataPoint<T>>>> begin Result := DoLoad(Filename); end);
|
||||
end;
|
||||
|
||||
destructor TAuraDataServer<T>.Destroy;
|
||||
@@ -274,9 +283,9 @@ begin
|
||||
FCachedFiles.Clear;
|
||||
end;
|
||||
|
||||
function TAuraDataServer<T>.DoLoad(const FileName: string): TFuture<TArray<TDataPoint<T>>>;
|
||||
function TAuraDataServer<T>.DoLoad(const FileName: string): TFuture<TArray<TArray<TDataPoint<T>>>>;
|
||||
begin
|
||||
Result := TFuture<TArray<TDataPoint<T>>>.Null;
|
||||
Result := TFuture<TArray<TArray<TDataPoint<T>>>>.Null;
|
||||
|
||||
if not TFile.Exists(FileName) then
|
||||
exit;
|
||||
@@ -287,8 +296,8 @@ begin
|
||||
Result :=
|
||||
TFuture<TBytes>
|
||||
.Construct(FLoadGate.Enqueue, function: TBytes begin Result := TFile.ReadAllBytes(capFileName); end)
|
||||
.Chain<TArray<TDataPoint<T>>>(
|
||||
function(bytes: TBytes): TArray<TDataPoint<T>>
|
||||
.Chain<TArray<TArray<TDataPoint<T>>>>(
|
||||
function(bytes: TBytes): TArray<TArray<TDataPoint<T>>>
|
||||
begin
|
||||
var zipMemoryStream := TBytesStream.Create(bytes);
|
||||
try
|
||||
@@ -302,9 +311,9 @@ begin
|
||||
else
|
||||
begin
|
||||
Result :=
|
||||
TFuture<TArray<TDataPoint<T>>>.Construct(
|
||||
TFuture<TArray<TArray<TDataPoint<T>>>>.Construct(
|
||||
FLoadGate.Enqueue,
|
||||
function: TArray<TDataPoint<T>>
|
||||
function: TArray<TArray<TDataPoint<T>>>
|
||||
begin
|
||||
if TFile.Exists(capFileName) then
|
||||
begin
|
||||
@@ -411,7 +420,7 @@ begin
|
||||
end);
|
||||
end;
|
||||
|
||||
function TAuraDataServer<T>.LoadDataFile(const DataFile: TAuraDataFile): TFuture<TArray<TDataPoint<T>>>;
|
||||
function TAuraDataServer<T>.LoadDataFile(const DataFile: TAuraDataFile): TFuture<TArray<TArray<TDataPoint<T>>>>;
|
||||
begin
|
||||
if DataFile.IsValid then
|
||||
Result := FCachedFiles.GetOrAdd(DataFile.GetFullFileName);
|
||||
@@ -419,7 +428,7 @@ end;
|
||||
|
||||
function TAuraDataServer<T>.ProcessFile(
|
||||
FileInfo: TAuraDataFile;
|
||||
const DataFile: TFuture<TArray<TDataPoint<T>>>;
|
||||
const DataFile: TFuture<TArray<TArray<TDataPoint<T>>>>;
|
||||
const Terminated: TState;
|
||||
Processor: IMycProcessor<TArray<TDataPoint<T>>>
|
||||
): TState;
|
||||
@@ -434,11 +443,20 @@ begin
|
||||
var cTerminated := Terminated;
|
||||
Result :=
|
||||
DataFile.Chain(
|
||||
function(const Data: TArray<TDataPoint<T>>): TState
|
||||
function(const DataChunks: TArray<TArray<TDataPoint<T>>>): TState
|
||||
begin
|
||||
if not Processor.ProcessData(Data) then
|
||||
exit(TState.Null);
|
||||
Processor.Update;
|
||||
if Assigned(DataChunks) then
|
||||
begin
|
||||
// Process each chunk, checking for termination between chunks.
|
||||
for var chunk in DataChunks do
|
||||
begin
|
||||
if cTerminated.IsSet then
|
||||
exit(TState.Null);
|
||||
|
||||
if not Processor.ProcessData(chunk) then
|
||||
exit(TState.Null);
|
||||
end;
|
||||
end;
|
||||
|
||||
Result := ProcessFile(nextFileInfo, nextFile, cTerminated, Processor);
|
||||
end
|
||||
@@ -485,6 +503,7 @@ begin
|
||||
);
|
||||
|
||||
FLastTimeStamp := 0;
|
||||
FCurrChunkInFile := 0;
|
||||
FCurrPosInFile := 0;
|
||||
|
||||
PreloadNext;
|
||||
@@ -502,6 +521,8 @@ end;
|
||||
function TAuraFileStream<T>.GetChunk(var Data: array of TDataPoint<T>): Integer;
|
||||
var
|
||||
item: TDataPoint<T>;
|
||||
fileChunks: TArray<TArray<TDataPoint<T>>>;
|
||||
currentChunk: TArray<TDataPoint<T>>;
|
||||
label
|
||||
loop;
|
||||
begin
|
||||
@@ -516,19 +537,33 @@ begin
|
||||
if not FCurrent.Value.FileInfo.IsValid then
|
||||
exit;
|
||||
|
||||
if FCurrPosInFile >= Length(FCurrent.Value.Data.Value) then
|
||||
fileChunks := FCurrent.Value.Data.Value;
|
||||
if (fileChunks = nil) or (FCurrChunkInFile >= Length(fileChunks)) then
|
||||
begin
|
||||
// Finished with current file, move to next
|
||||
FCurrChunkInFile := 0;
|
||||
FCurrPosInFile := 0;
|
||||
FCurrent := FNext;
|
||||
PreloadNext;
|
||||
goto loop;
|
||||
end;
|
||||
|
||||
var currData := FCurrent.Value.Data.Value;
|
||||
var maxLen := Length(Data);
|
||||
while (Result < maxLen) and (FCurrPosInFile < Length(currData)) do
|
||||
while (Result < maxLen) do
|
||||
begin
|
||||
item := currData[FCurrPosInFile];
|
||||
// Check if we need to advance to the next chunk
|
||||
if (FCurrChunkInFile < Length(fileChunks)) and (FCurrPosInFile >= Length(fileChunks[FCurrChunkInFile])) then
|
||||
begin
|
||||
Inc(FCurrChunkInFile);
|
||||
FCurrPosInFile := 0;
|
||||
end;
|
||||
|
||||
// Exit loop if all chunks are processed for the current file
|
||||
if FCurrChunkInFile >= Length(fileChunks) then
|
||||
break;
|
||||
|
||||
currentChunk := fileChunks[FCurrChunkInFile];
|
||||
item := currentChunk[FCurrPosInFile];
|
||||
if FLastTimeStamp < item.Time then
|
||||
begin
|
||||
FLastTimeStamp := item.Time;
|
||||
@@ -538,7 +573,8 @@ begin
|
||||
Inc(FCurrPosInFile);
|
||||
end;
|
||||
|
||||
if (FCurrPosInFile < Length(currData)) or FNextReady.Reset then
|
||||
// Notify if there is potentially more data
|
||||
if (FCurrChunkInFile < Length(fileChunks)) or FNextReady.Reset then
|
||||
begin
|
||||
FHasData.Notify;
|
||||
end;
|
||||
@@ -593,8 +629,8 @@ end;
|
||||
function TAuraTABFileServer.LoadDataSeries(const InitialFile: TAuraDataFile): TFuture<TArray<TDataPoint<TAuraAskBidFileItem>>>;
|
||||
var
|
||||
loadedState: TState;
|
||||
loadedFiles: TArray<TFuture<TArray<TDataPoint<TAuraAskBidFileItem>>>>;
|
||||
liveData: TFuture<TArray<TDataPoint<TAuraAskBidFileItem>>>;
|
||||
loadedFiles: TArray<TFuture<TArray<TArray<TDataPoint<TAuraAskBidFileItem>>>>>;
|
||||
liveData: TFuture<TArray<TArray<TDataPoint<TAuraAskBidFileItem>>>>;
|
||||
tabFiles: TList<TAuraDataFile>;
|
||||
liveFile: TAuraDataFile;
|
||||
currentFileInfo: TAuraDataFile;
|
||||
@@ -618,7 +654,7 @@ begin
|
||||
liveFile := ParseFileName(potentialLivePath);
|
||||
end;
|
||||
|
||||
var loadedFileList := TList<TFuture<TArray<TDataPoint<TAuraAskBidFileItem>>>>.Create;
|
||||
var loadedFileList := TList<TFuture<TArray<TArray<TDataPoint<TAuraAskBidFileItem>>>>>.Create;
|
||||
var loadStates := TList<TState>.Create;
|
||||
try
|
||||
for var fileInfo in tabFiles do
|
||||
@@ -628,7 +664,7 @@ begin
|
||||
loadStates.Add(data.Done);
|
||||
end;
|
||||
|
||||
liveData := TFuture<TArray<TDataPoint<TAuraAskBidFileItem>>>.Null;
|
||||
liveData := TFuture<TArray<TArray<TDataPoint<TAuraAskBidFileItem>>>>.Null;
|
||||
if liveFile.IsValid then
|
||||
begin
|
||||
liveData := LoadDataFile(liveFile);
|
||||
@@ -656,16 +692,18 @@ begin
|
||||
var overallLastTabTickTime: TDateTime := 0;
|
||||
var cnt := 0;
|
||||
for var P in loadedFiles do
|
||||
inc(cnt, Length(P.Value));
|
||||
for var chunk in P.Value do
|
||||
inc(cnt, Length(chunk));
|
||||
tickList.Capacity := cnt;
|
||||
|
||||
for var P in loadedFiles do
|
||||
for var iterTickData in P.Value do
|
||||
if overallLastTabTickTime < iterTickData.Time then
|
||||
begin
|
||||
tickList.Add(iterTickData);
|
||||
overallLastTabTickTime := iterTickData.Time;
|
||||
end;
|
||||
for var chunk in P.Value do
|
||||
for var iterTickData in chunk do
|
||||
if overallLastTabTickTime < iterTickData.Time then
|
||||
begin
|
||||
tickList.Add(iterTickData);
|
||||
overallLastTabTickTime := iterTickData.Time;
|
||||
end;
|
||||
Result := tickList.ToArray;
|
||||
finally
|
||||
tickList.Free;
|
||||
@@ -721,14 +759,14 @@ begin
|
||||
Result := TAuraDataFile.Create(path, symbol, fileExt, year, month);
|
||||
end;
|
||||
|
||||
class function TAuraTABFileServer.ReadCompressedData(const InputStream: TStream): TArray<TDataPoint<TAuraAskBidFileItem>>;
|
||||
class function TAuraTABFileServer.ReadCompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<TAuraAskBidFileItem>>>;
|
||||
var
|
||||
decompressionStream: TStream;
|
||||
localHeader: TZipHeader;
|
||||
entryIndex, i: Integer;
|
||||
zipFileInstance: TZipFile;
|
||||
begin
|
||||
SetLength(Result, 0);
|
||||
Result := nil;
|
||||
decompressionStream := nil;
|
||||
zipFileInstance := nil;
|
||||
try
|
||||
@@ -760,7 +798,7 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
class function TAuraTABFileServer.ReadUncompressedData(const InputStream: TStream): TArray<TDataPoint<TAuraAskBidFileItem>>;
|
||||
class function TAuraTABFileServer.ReadUncompressedData(const InputStream: TStream): TArray<TArray<TDataPoint<TAuraAskBidFileItem>>>;
|
||||
type
|
||||
TFileRecord = packed record
|
||||
TimeStamp: TDateTime;
|
||||
@@ -768,25 +806,40 @@ type
|
||||
end;
|
||||
var
|
||||
fileSize: Int64;
|
||||
recordCount, bytesRead: Integer;
|
||||
totalRecordCount, bytesRead, chunkIndex, indexInChunk, recordIndex: Integer;
|
||||
rec: TFileRecord;
|
||||
begin
|
||||
SetLength(Result, 0);
|
||||
Result := nil;
|
||||
InputStream.Position := 0;
|
||||
fileSize := InputStream.Size;
|
||||
if (fileSize = 0) or ((fileSize mod SizeOf(TFileRecord)) <> 0) then
|
||||
exit;
|
||||
|
||||
recordCount := fileSize div SizeOf(TFileRecord);
|
||||
if recordCount > 0 then
|
||||
totalRecordCount := fileSize div SizeOf(TFileRecord);
|
||||
if totalRecordCount > 0 then
|
||||
begin
|
||||
SetLength(Result, recordCount);
|
||||
for var i := 0 to High(Result) do
|
||||
var numChunks := (totalRecordCount + ChunkSize - 1) div ChunkSize;
|
||||
SetLength(Result, numChunks);
|
||||
chunkIndex := 0;
|
||||
indexInChunk := 0;
|
||||
SetLength(Result[chunkIndex], Min(ChunkSize, totalRecordCount));
|
||||
|
||||
for recordIndex := 0 to totalRecordCount - 1 do
|
||||
begin
|
||||
if indexInChunk >= ChunkSize then
|
||||
begin
|
||||
Inc(chunkIndex);
|
||||
indexInChunk := 0;
|
||||
var remainingRecords := totalRecordCount - (chunkIndex * ChunkSize);
|
||||
SetLength(Result[chunkIndex], Min(ChunkSize, remainingRecords));
|
||||
end;
|
||||
|
||||
bytesRead := InputStream.Read(rec, SizeOf(TFileRecord));
|
||||
if bytesRead <> SizeOf(TFileRecord) then
|
||||
raise EReadError.CreateFmt('Read error. Expected %d bytes, read %d.', [fileSize, bytesRead]);
|
||||
Result[i].Create(rec.TimeStamp, rec.Data);
|
||||
raise EReadError.CreateFmt('Read error. Expected %d bytes, read %d.', [SizeOf(TFileRecord), bytesRead]);
|
||||
|
||||
Result[chunkIndex][indexInChunk].Create(rec.TimeStamp, rec.Data);
|
||||
Inc(indexInChunk);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
Reference in New Issue
Block a user