DataServer
Bugfix in DataSeries
This commit is contained in:
@@ -79,7 +79,7 @@ end;
|
|||||||
|
|
||||||
destructor TMycGateFuncFuture<T>.Destroy;
|
destructor TMycGateFuncFuture<T>.Destroy;
|
||||||
begin
|
begin
|
||||||
Assert(FDone.State.IsSet);
|
Assert(FDone.State.IsSet, 'Future not done');
|
||||||
|
|
||||||
FInit.Unsubscribe;
|
FInit.Unsubscribe;
|
||||||
inherited Destroy;
|
inherited Destroy;
|
||||||
|
|||||||
@@ -371,10 +371,7 @@ begin
|
|||||||
begin
|
begin
|
||||||
FSubscribers.Notify(function(Subscriber: TSignal.ISubscriber): Boolean begin Result := Subscriber.Notify; end);
|
FSubscribers.Notify(function(Subscriber: TSignal.ISubscriber): Boolean begin Result := Subscriber.Notify; end);
|
||||||
end;
|
end;
|
||||||
// The return value of this Notify (as an TSignal.ISubscriber) indicates if this
|
Result := true;
|
||||||
// TMycFlag instance itself would want more notifications if it were subscribed to something.
|
|
||||||
// Here, it reflects whether a state change to dirty occurred due to this call.
|
|
||||||
Result := wasPreviouslyClean;
|
|
||||||
finally
|
finally
|
||||||
FSubscribers.Release;
|
FSubscribers.Release;
|
||||||
end;
|
end;
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ type
|
|||||||
private
|
private
|
||||||
FMutable: IMutable;
|
FMutable: IMutable;
|
||||||
function GetChanged: TSignal; inline;
|
function GetChanged: TSignal; inline;
|
||||||
|
function GetValue: T; inline;
|
||||||
{$ENDREGION}
|
{$ENDREGION}
|
||||||
public
|
public
|
||||||
constructor Create(const AMutable: IMutable);
|
constructor Create(const AMutable: IMutable);
|
||||||
@@ -44,6 +45,7 @@ type
|
|||||||
|
|
||||||
class function CreateWriteable(const Init: T): IWriteable; overload; static;
|
class function CreateWriteable(const Init: T): IWriteable; overload; static;
|
||||||
|
|
||||||
|
property Value: T read GetValue;
|
||||||
property Changed: TSignal read GetChanged;
|
property Changed: TSignal read GetChanged;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -74,6 +76,7 @@ type
|
|||||||
class operator Implicit(const A: TLazy<T>): ILazy; overload;
|
class operator Implicit(const A: TLazy<T>): ILazy; overload;
|
||||||
|
|
||||||
class function Construct(const Changing: TSignal.ISignal; const Proc: TFunc<T>): TLazy<T>; overload; static;
|
class function Construct(const Changing: TSignal.ISignal; const Proc: TFunc<T>): TLazy<T>; overload; static;
|
||||||
|
class function Construct(const Mutable: TMutable<T>): TLazy<T>; overload; static;
|
||||||
class property Null: ILazy read FNull;
|
class property Null: ILazy read FNull;
|
||||||
|
|
||||||
function Pop(out Res: T): Boolean; inline;
|
function Pop(out Res: T): Boolean; inline;
|
||||||
@@ -115,6 +118,11 @@ begin
|
|||||||
Result := FMutable.Changed;
|
Result := FMutable.Changed;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TMutable<T>.GetValue: T;
|
||||||
|
begin
|
||||||
|
Result := FMutable.Value;
|
||||||
|
end;
|
||||||
|
|
||||||
class operator TMutable<T>.Implicit(const A: TMutable<T>): IMutable;
|
class operator TMutable<T>.Implicit(const A: TMutable<T>): IMutable;
|
||||||
begin
|
begin
|
||||||
Result := A.FMutable;
|
Result := A.FMutable;
|
||||||
@@ -149,6 +157,12 @@ begin
|
|||||||
FNull := TMycNullLazy<T>.Create;
|
FNull := TMycNullLazy<T>.Create;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
class function TLazy<T>.Construct(const Mutable: TMutable<T>): TLazy<T>;
|
||||||
|
begin
|
||||||
|
var cap := Mutable;
|
||||||
|
Result := TMycFuncLazy<T>.Create(cap.Changed, function: T begin exit(Mutable.Value) end);
|
||||||
|
end;
|
||||||
|
|
||||||
function TLazy<T>.GetChanged: TState;
|
function TLazy<T>.GetChanged: TState;
|
||||||
begin
|
begin
|
||||||
Result := FLazy.Changed;
|
Result := FLazy.Changed;
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ type
|
|||||||
TTestMycCoreLazy = class(TObject)
|
TTestMycCoreLazy = class(TObject)
|
||||||
private
|
private
|
||||||
procedure Helper_ConsumeInitialPop(const ALazy: TLazy<Integer>.ILazy; InitialExpectedValue: Integer); overload;
|
procedure Helper_ConsumeInitialPop(const ALazy: TLazy<Integer>.ILazy; InitialExpectedValue: Integer); overload;
|
||||||
procedure Helper_ConsumeInitialPop(const ALazy: TLazy<String>.ILazy; const InitialExpectedValue: string); overload;
|
|
||||||
public
|
public
|
||||||
[Setup]
|
[Setup]
|
||||||
procedure Setup;
|
procedure Setup;
|
||||||
@@ -77,18 +76,6 @@ begin
|
|||||||
Assert.IsFalse(ALazy.GetChanged.IsSet, 'Changed.IsSet should be false after consuming initial pop');
|
Assert.IsFalse(ALazy.GetChanged.IsSet, 'Changed.IsSet should be false after consuming initial pop');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TTestMycCoreLazy.Helper_ConsumeInitialPop(const ALazy: TLazy<String>.ILazy; const InitialExpectedValue: string);
|
|
||||||
var
|
|
||||||
tempValue: string;
|
|
||||||
popResult: Boolean;
|
|
||||||
begin
|
|
||||||
Assert.IsTrue(ALazy.GetChanged.IsSet, 'Changed.IsSet should be true before consuming initial pop');
|
|
||||||
popResult := ALazy.Pop(tempValue);
|
|
||||||
Assert.IsTrue(popResult, 'Consuming initial Pop should return true');
|
|
||||||
Assert.AreEqual(InitialExpectedValue, tempValue, 'Value from initial Pop is unexpected');
|
|
||||||
Assert.IsFalse(ALazy.GetChanged.IsSet, 'Changed.IsSet should be false after consuming initial pop');
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ TTestMycCoreLazy Test Methods }
|
{ TTestMycCoreLazy Test Methods }
|
||||||
|
|
||||||
procedure TTestMycCoreLazy.Setup;
|
procedure TTestMycCoreLazy.Setup;
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ begin
|
|||||||
notifyResult := dirtyFlag.Notify; // Call TSignal.ISubscriber.Notify to make it dirty
|
notifyResult := dirtyFlag.Notify; // Call TSignal.ISubscriber.Notify to make it dirty
|
||||||
|
|
||||||
Assert.IsTrue(dirtyFlag.State.IsSet, 'Flag should be dirty (IsSet) after Notify on clean flag.');
|
Assert.IsTrue(dirtyFlag.State.IsSet, 'Flag should be dirty (IsSet) after Notify on clean flag.');
|
||||||
Assert.IsTrue(notifyResult, 'Notify() should return True indicating a state change from clean to dirty.');
|
Assert.IsTrue(notifyResult);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TTestMycDirtyFlag.TestNotify_OnDirtyFlag_StaysDirtyAndReturnsFalse;
|
procedure TTestMycDirtyFlag.TestNotify_OnDirtyFlag_StaysDirtyAndReturnsFalse;
|
||||||
@@ -180,7 +180,7 @@ begin
|
|||||||
notifyResult := dirtyFlag.Notify; // Call TSignal.ISubscriber.Notify again
|
notifyResult := dirtyFlag.Notify; // Call TSignal.ISubscriber.Notify again
|
||||||
|
|
||||||
Assert.IsTrue(dirtyFlag.State.IsSet, 'Flag should remain dirty (IsSet).');
|
Assert.IsTrue(dirtyFlag.State.IsSet, 'Flag should remain dirty (IsSet).');
|
||||||
Assert.IsFalse(notifyResult, 'Notify() should return False as flag was already dirty (no state change to dirty).');
|
Assert.IsTrue(notifyResult);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Category 2: Subscriber Notifications
|
// Category 2: Subscriber Notifications
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
unit Myc.Test.Trade.DataServer;
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.SysUtils,
|
||||||
|
System.Generics.Collections, // E2003: Added for TList<T>
|
||||||
|
DUnitX.TestFramework,
|
||||||
|
Myc.Signals,
|
||||||
|
Myc.Lazy,
|
||||||
|
Myc.Futures,
|
||||||
|
Myc.Trade.DataPoint,
|
||||||
|
Myc.Trade.DataSeries,
|
||||||
|
Myc.Trade.DataServer;
|
||||||
|
|
||||||
|
type
|
||||||
|
// Define ITickData as an alias for the specific IDataPoint interface.
|
||||||
|
ITickData = IDataPoint<ITick>;
|
||||||
|
|
||||||
|
[TestFixture]
|
||||||
|
TTest_TABFileServer_Equivalence = class(TObject)
|
||||||
|
private
|
||||||
|
FServer: IDataServer<ITickData>;
|
||||||
|
FExpectedData: TArray<TAskBid.TTick>;
|
||||||
|
public
|
||||||
|
[Setup]
|
||||||
|
procedure Setup;
|
||||||
|
[TearDown]
|
||||||
|
procedure TearDown;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
procedure Test_ServerReturnsSameDataAs_LoadDataSeries;
|
||||||
|
end;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
uses
|
||||||
|
Myc.TaskManager;
|
||||||
|
|
||||||
|
const
|
||||||
|
// The reference data file for this test.
|
||||||
|
C_TEST_FILENAME = '\\COFFEE\TickData\Pepperstone\AUDNZD_2024_05.tab_zip';
|
||||||
|
// The chunk size for fetching data from the server.
|
||||||
|
C_MAX_FETCH = 20;
|
||||||
|
|
||||||
|
{ TTest_TABFileServer_Equivalence }
|
||||||
|
|
||||||
|
procedure TTest_TABFileServer_Equivalence.Setup;
|
||||||
|
begin
|
||||||
|
// SetupTaskManagerMock;
|
||||||
|
|
||||||
|
// IsMemoryLow := function: Boolean begin exit(false); end;
|
||||||
|
|
||||||
|
// 1. Load the ground truth data using the original LoadDataSeries function.
|
||||||
|
FExpectedData := TAskBid.LoadDataSeries(C_TEST_FILENAME).WaitFor;
|
||||||
|
|
||||||
|
// 2. Set up the filename and the TABFileServer instance.
|
||||||
|
FServer := TTABFileServer.Create(C_TEST_FILENAME, C_MAX_FETCH);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTest_TABFileServer_Equivalence.TearDown;
|
||||||
|
begin
|
||||||
|
FServer := nil;
|
||||||
|
SetLength(FExpectedData, 0);
|
||||||
|
IsMemoryLow := nil;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTest_TABFileServer_Equivalence.Test_ServerReturnsSameDataAs_LoadDataSeries;
|
||||||
|
var
|
||||||
|
actualDataList: TList<ITickData>;
|
||||||
|
chunk: TArray<ITickData>;
|
||||||
|
newChunk: TLazy<TArray<ITickData>>;
|
||||||
|
begin
|
||||||
|
actualDataList := TList<ITickData>.Create;
|
||||||
|
try
|
||||||
|
newChunk := TLazy<TArray<ITickData>>.Construct(FServer.Data);
|
||||||
|
|
||||||
|
// Loop until the server stops providing new data chunks.
|
||||||
|
var cnt: Int64 := 0;
|
||||||
|
while not FServer.IsLiveData.Value do
|
||||||
|
begin
|
||||||
|
FServer.Fetch;
|
||||||
|
|
||||||
|
// Get the data chunk produced by the last Fetch call.
|
||||||
|
if newChunk.Pop(chunk) then
|
||||||
|
begin
|
||||||
|
for var i := 0 to High(chunk) do
|
||||||
|
begin
|
||||||
|
actualDataList.Add(chunk[i]);
|
||||||
|
Assert.AreEqual(chunk[i].Idx, cnt, 'Index mismatch');
|
||||||
|
inc(cnt);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// --- Assertions ---
|
||||||
|
|
||||||
|
// 1. Verify that the total number of records matches.
|
||||||
|
Assert.AreEqual(Length(FExpectedData), actualDataList.Count, 'Data record count mismatch');
|
||||||
|
|
||||||
|
// 2. Verify that the content of each record is identical.
|
||||||
|
for var i := 0 to High(FExpectedData) do
|
||||||
|
begin
|
||||||
|
var expectedTick := FExpectedData[i];
|
||||||
|
var actualTick := actualDataList[i];
|
||||||
|
|
||||||
|
// 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(expectedTick.OADateTime, actualTick.Time, 'Timestamp mismatch ');
|
||||||
|
Assert.IsTrue(Abs(expectedTick.Data.Ask - actualTick.Data.Ask) < 0.0001, 'Ask price mismatch');
|
||||||
|
Assert.IsTrue(Abs(expectedTick.Data.Bid - actualTick.Data.Bid) < 0.0001, 'Bid price mismatch');
|
||||||
|
end;
|
||||||
|
finally
|
||||||
|
actualDataList.Free;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
initialization
|
||||||
|
TDUnitX.RegisterTestFixture(TTest_TABFileServer_Equivalence);
|
||||||
|
end.
|
||||||
@@ -9,12 +9,14 @@ type
|
|||||||
IDataPoint<T> = interface
|
IDataPoint<T> = interface
|
||||||
['{6A52697A-2868-42A9-982D-993542B7B360}']
|
['{6A52697A-2868-42A9-982D-993542B7B360}']
|
||||||
function GetData: T;
|
function GetData: T;
|
||||||
|
function GetIdx: Int64;
|
||||||
function GetTime: TDateTime;
|
function GetTime: TDateTime;
|
||||||
function GetVolume: Double;
|
function GetVolume: Double;
|
||||||
|
|
||||||
property Time: TDateTime read GetTime;
|
property Time: TDateTime read GetTime;
|
||||||
property Volume: Double read GetVolume;
|
property Volume: Double read GetVolume;
|
||||||
property Data: T read GetData;
|
property Data: T read GetData;
|
||||||
|
property Idx: Int64 read GetIdx;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
ITick = interface
|
ITick = interface
|
||||||
@@ -25,6 +27,7 @@ type
|
|||||||
property Ask: Double read GetAsk;
|
property Ask: Double read GetAsk;
|
||||||
property Bid: Double read GetBid;
|
property Bid: Double read GetBid;
|
||||||
end;
|
end;
|
||||||
|
ITickData = IDataPoint<ITick>;
|
||||||
|
|
||||||
IOHLC = interface
|
IOHLC = interface
|
||||||
['{05374E3C-731B-44FA-A23E-051F1461B78D}']
|
['{05374E3C-731B-44FA-A23E-051F1461B78D}']
|
||||||
@@ -38,6 +41,7 @@ type
|
|||||||
property Low: Double read GetLow;
|
property Low: Double read GetLow;
|
||||||
property Close: Double read GetClose;
|
property Close: Double read GetClose;
|
||||||
end;
|
end;
|
||||||
|
IOHLCData = IDataPoint<IOHLC>;
|
||||||
|
|
||||||
// Abstract base class for all data points.
|
// Abstract base class for all data points.
|
||||||
TDataPoint<T> = class abstract(TInterfacedObject, IDataPoint<T>)
|
TDataPoint<T> = class abstract(TInterfacedObject, IDataPoint<T>)
|
||||||
@@ -45,11 +49,13 @@ type
|
|||||||
fTime: TDateTime;
|
fTime: TDateTime;
|
||||||
fVolume: Double;
|
fVolume: Double;
|
||||||
FData: T;
|
FData: T;
|
||||||
|
FIdx: Int64;
|
||||||
function GetTime: TDateTime;
|
function GetTime: TDateTime;
|
||||||
function GetVolume: Double;
|
function GetVolume: Double;
|
||||||
function GetData: T;
|
function GetData: T;
|
||||||
|
function GetIdx: Int64;
|
||||||
public
|
public
|
||||||
constructor Create(Time: TDateTime; Volume: Double; const AData: T);
|
constructor Create(Idx: Int64; Time: TDateTime; Volume: Double; const AData: T);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TTick = class(TInterfacedObject, ITick)
|
TTick = class(TInterfacedObject, ITick)
|
||||||
@@ -80,8 +86,10 @@ implementation
|
|||||||
|
|
||||||
{ TDataPoint }
|
{ TDataPoint }
|
||||||
|
|
||||||
constructor TDataPoint<T>.Create(Time: TDateTime; Volume: Double; const AData: T);
|
constructor TDataPoint<T>.Create(Idx: Int64; Time: TDateTime; Volume: Double; const AData: T);
|
||||||
begin
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FIdx := Idx;
|
||||||
fTime := Time;
|
fTime := Time;
|
||||||
fVolume := Volume;
|
fVolume := Volume;
|
||||||
FData := AData;
|
FData := AData;
|
||||||
@@ -92,6 +100,11 @@ begin
|
|||||||
Result := FData;
|
Result := FData;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TDataPoint<T>.GetIdx: Int64;
|
||||||
|
begin
|
||||||
|
Result := FIdx;
|
||||||
|
end;
|
||||||
|
|
||||||
function TDataPoint<T>.GetTime: TDateTime;
|
function TDataPoint<T>.GetTime: TDateTime;
|
||||||
begin
|
begin
|
||||||
result := fTime;
|
result := fTime;
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ type
|
|||||||
type
|
type
|
||||||
// Represents a single data point with a timestamp and generic data.
|
// Represents a single data point with a timestamp and generic data.
|
||||||
TDataPoint = packed record
|
TDataPoint = packed record
|
||||||
OADateTime: Double;
|
OADateTime: TDateTime;
|
||||||
Data: T;
|
Data: T;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ type
|
|||||||
class function ReadUncompressedData(const InputStream: TStream): TArray<TDataPoint>; static;
|
class function ReadUncompressedData(const InputStream: TStream): TArray<TDataPoint>; static;
|
||||||
public
|
public
|
||||||
// Asynchronously loads a single data file with caching support.
|
// Asynchronously loads a single data file with caching support.
|
||||||
class function LoadDataFile(var LoadGate: TLatch; const FileName: string): TFuture<TArray<TDataPoint>>; static;
|
class function LoadDataFile(const LoadGate: TState; const FileName: string): TFuture<TArray<TDataPoint>>; static;
|
||||||
// Asynchronously loads a complete chronological series of data files.
|
// Asynchronously loads a complete chronological series of data files.
|
||||||
class function LoadDataSeries(const FileName: string): TFuture<TArray<TDataPoint>>; static;
|
class function LoadDataSeries(const FileName: string): TFuture<TArray<TDataPoint>>; static;
|
||||||
end;
|
end;
|
||||||
@@ -325,7 +325,7 @@ end;
|
|||||||
|
|
||||||
// Asynchronously reads tick data from a single specified file.
|
// Asynchronously reads tick data from a single specified file.
|
||||||
// Returns a TFuture that will provide an array of TAskBidSeries.
|
// Returns a TFuture that will provide an array of TAskBidSeries.
|
||||||
class function TDataSeries<T>.LoadDataFile(var LoadGate: TLatch; const FileName: string): TFuture<TArray<TDataPoint>>;
|
class function TDataSeries<T>.LoadDataFile(const LoadGate: TState; const FileName: string): TFuture<TArray<TDataPoint>>;
|
||||||
var
|
var
|
||||||
parsedPath, parsedSymbol: string;
|
parsedPath, parsedSymbol: string;
|
||||||
parsedYear, parsedMonth: Integer;
|
parsedYear, parsedMonth: Integer;
|
||||||
@@ -379,7 +379,7 @@ begin
|
|||||||
Result :=
|
Result :=
|
||||||
TFuture<TBytes>
|
TFuture<TBytes>
|
||||||
.Construct(
|
.Construct(
|
||||||
TLatch.Enqueue(LoadGate),
|
LoadGate,
|
||||||
function: TBytes
|
function: TBytes
|
||||||
begin
|
begin
|
||||||
Result := TFile.ReadAllBytes(capFileName); // Read all bytes of the .zip file
|
Result := TFile.ReadAllBytes(capFileName); // Read all bytes of the .zip file
|
||||||
@@ -401,7 +401,7 @@ begin
|
|||||||
var capFileName := FileName;
|
var capFileName := FileName;
|
||||||
Result :=
|
Result :=
|
||||||
TFuture<TArray<TDataPoint>>.Construct(
|
TFuture<TArray<TDataPoint>>.Construct(
|
||||||
TLatch.Enqueue(LoadGate),
|
LoadGate,
|
||||||
function: TArray<TDataPoint>
|
function: TArray<TDataPoint>
|
||||||
begin
|
begin
|
||||||
if TFile.Exists(capFileName) then
|
if TFile.Exists(capFileName) then
|
||||||
@@ -447,6 +447,7 @@ var
|
|||||||
liveFilePath: string;
|
liveFilePath: string;
|
||||||
pathName, symbolName: string;
|
pathName, symbolName: string;
|
||||||
yearValue, monthValue: Integer;
|
yearValue, monthValue: Integer;
|
||||||
|
LoadGate: TLatch;
|
||||||
begin
|
begin
|
||||||
// 1. Discover all sequential .tab/.tab_zip files using the new helper function.
|
// 1. Discover all sequential .tab/.tab_zip files using the new helper function.
|
||||||
tabFiles := TList<string>.Create;
|
tabFiles := TList<string>.Create;
|
||||||
@@ -482,12 +483,10 @@ begin
|
|||||||
var loadedFileList := TList<TFuture<TArray<TDataPoint>>>.Create;
|
var loadedFileList := TList<TFuture<TArray<TDataPoint>>>.Create;
|
||||||
var loadStates := TList<TState>.Create;
|
var loadStates := TList<TState>.Create;
|
||||||
try
|
try
|
||||||
var LoadGate: TLatch;
|
|
||||||
|
|
||||||
// Create load tasks for the historical .tab files.
|
// Create load tasks for the historical .tab files.
|
||||||
for currentFile in tabFiles do
|
for currentFile in tabFiles do
|
||||||
begin
|
begin
|
||||||
var data := LoadDataFile(LoadGate, currentFile);
|
var data := LoadDataFile(TLatch.Enqueue(LoadGate), currentFile);
|
||||||
loadedFileList.Add(data);
|
loadedFileList.Add(data);
|
||||||
loadStates.Add(data.Done);
|
loadStates.Add(data.Done);
|
||||||
end;
|
end;
|
||||||
@@ -496,7 +495,7 @@ begin
|
|||||||
liveData := TFuture<TArray<TDataPoint>>.Null;
|
liveData := TFuture<TArray<TDataPoint>>.Null;
|
||||||
if liveFilePath <> '' then
|
if liveFilePath <> '' then
|
||||||
begin
|
begin
|
||||||
liveData := LoadDataFile(LoadGate, liveFilePath);
|
liveData := LoadDataFile(TLatch.Enqueue(LoadGate), liveFilePath);
|
||||||
loadedFileList.Add(liveData);
|
loadedFileList.Add(liveData);
|
||||||
loadStates.Add(liveData.Done);
|
loadStates.Add(liveData.Done);
|
||||||
end;
|
end;
|
||||||
@@ -520,7 +519,7 @@ begin
|
|||||||
var tickList := TList<TDataPoint>.Create;
|
var tickList := TList<TDataPoint>.Create;
|
||||||
try
|
try
|
||||||
var totalTicks := Length(liveData.Value);
|
var totalTicks := Length(liveData.Value);
|
||||||
var overallLastTabTickTime := 0.0;
|
var overallLastTabTickTime: TDateTime := 0;
|
||||||
|
|
||||||
for var P in loadedFiles do
|
for var P in loadedFiles do
|
||||||
Inc(totalTicks, Length(P.Value));
|
Inc(totalTicks, Length(P.Value));
|
||||||
@@ -529,15 +528,12 @@ begin
|
|||||||
|
|
||||||
for var P in loadedFiles do
|
for var P in loadedFiles do
|
||||||
begin
|
begin
|
||||||
if Length(P.Value) > 0 then
|
for var iterTickData in P.Value do
|
||||||
begin
|
if overallLastTabTickTime < iterTickData.OADateTime then
|
||||||
for var iterTickData in P.Value do
|
begin
|
||||||
if iterTickData.OADateTime > overallLastTabTickTime then
|
tickList.Add(iterTickData);
|
||||||
begin
|
overallLastTabTickTime := iterTickData.OADateTime;
|
||||||
tickList.Add(iterTickData);
|
end;
|
||||||
overallLastTabTickTime := iterTickData.OADateTime;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
end;
|
end;
|
||||||
Result := tickList.ToArray;
|
Result := tickList.ToArray;
|
||||||
finally
|
finally
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ uses
|
|||||||
System.Generics.Collections,
|
System.Generics.Collections,
|
||||||
Myc.Lazy,
|
Myc.Lazy,
|
||||||
Myc.Trade.DataPoint,
|
Myc.Trade.DataPoint,
|
||||||
Myc.Trade.DataSeries;
|
Myc.Trade.DataSeries,
|
||||||
|
Myc.Signals, // Added for TLatch
|
||||||
|
Myc.Futures; // Added for TFuture
|
||||||
|
|
||||||
type
|
type
|
||||||
IDataProvider = interface
|
IDataProvider = interface
|
||||||
@@ -15,7 +17,7 @@ type
|
|||||||
procedure Fetch;
|
procedure Fetch;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
IDataServer<T> = interface
|
IDataServer<T> = interface(IDataProvider)
|
||||||
['{A6E246A2-E84E-49AB-A63E-333E561E488C}']
|
['{A6E246A2-E84E-49AB-A63E-333E561E488C}']
|
||||||
function GetData: TMutable<TArray<T>>;
|
function GetData: TMutable<TArray<T>>;
|
||||||
function GetIsLiveData: TMutable<Boolean>;
|
function GetIsLiveData: TMutable<Boolean>;
|
||||||
@@ -41,10 +43,154 @@ type
|
|||||||
function CreateDataServer: IDataServer<T>; virtual; abstract;
|
function CreateDataServer: IDataServer<T>; virtual; abstract;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TFileStreamServerNode<T> = class(TDataServerNode<T>)
|
TTABFileServer = class(TDataServer<ITickData>)
|
||||||
|
private
|
||||||
|
FFilename: String;
|
||||||
|
// The maximum number of data points to be produced per Fetch call.
|
||||||
|
FMaxFetch: Integer;
|
||||||
|
FHasNewData: TEvent;
|
||||||
|
FNeedNewChunk: Boolean;
|
||||||
|
FIsLiveData: TMutable<Boolean>.IWriteable;
|
||||||
|
|
||||||
|
// Internal state
|
||||||
|
FCurrentFileName: string;
|
||||||
|
FCurrentData: TFuture<TArray<TAskBid.TTick>>;
|
||||||
|
FNextFileName: string;
|
||||||
|
FNextData: TFuture<TArray<TAskBid.TTick>>;
|
||||||
|
FData: TMutable<TArray<ITickData>>;
|
||||||
|
FCurrPosInFile: Int64;
|
||||||
|
FCurrChunk: TArray<ITickData>;
|
||||||
|
FLoadGate: TLatch;
|
||||||
|
FCurrentIdx: Int64;
|
||||||
|
FLastTimeStamp: TDateTime;
|
||||||
|
|
||||||
|
function ConvertTicks(var Idx: Int64; const Ticks: TArray<TAskBid.TTick>): TArray<ITickData>;
|
||||||
|
function UpdateChunk: TArray<ITickData>;
|
||||||
|
protected
|
||||||
|
procedure Fetch; override;
|
||||||
|
function GetData: TMutable<TArray<ITickData>>; override;
|
||||||
|
function GetIsLiveData: TMutable<Boolean>; override;
|
||||||
|
public
|
||||||
|
constructor Create(const AFilename: String; AMaxFetch: Integer);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
|
uses
|
||||||
|
System.IOUtils;
|
||||||
|
|
||||||
|
{ TTABFileServer<T> }
|
||||||
|
|
||||||
|
constructor TTABFileServer.Create(const AFilename: String; AMaxFetch: Integer);
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FFilename := AFilename;
|
||||||
|
FMaxFetch := AMaxFetch;
|
||||||
|
|
||||||
|
FNextData := TFuture<TArray<TAskBid.TTick>>.Null;
|
||||||
|
FNeedNewChunk := true;
|
||||||
|
FIsLiveData := TMutable<Boolean>.CreateWriteable(false);
|
||||||
|
|
||||||
|
FHasNewData := TEvent.CreateEvent;
|
||||||
|
FData := TMutable<TArray<ITickData>>.Construct(FHasNewData.Signal, function: TArray<ITickData> begin Result := UpdateChunk; end);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TTABFileServer.ConvertTicks(var Idx: Int64; const Ticks: TArray<TAskBid.TTick>): TArray<ITickData>;
|
||||||
|
begin
|
||||||
|
SetLength(Result, Length(Ticks));
|
||||||
|
for var i := 0 to High(Ticks) do
|
||||||
|
begin
|
||||||
|
Result[i] := TDataPoint<ITick>.Create(Idx, Ticks[i].OADateTime, 1, TTick.Create(Ticks[i].Data.Ask, Ticks[i].Data.Bid));
|
||||||
|
inc(Idx);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TTABFileServer.Fetch;
|
||||||
|
begin
|
||||||
|
|
||||||
|
// Check if the source filename has changed.
|
||||||
|
if (FFilename <> FCurrentFileName) and (FFilename <> '') then
|
||||||
|
begin
|
||||||
|
// Filename has changed, initialize by loading the new file.
|
||||||
|
FCurrentFileName := FFilename;
|
||||||
|
FCurrentData := TAskBid.LoadDataFile(TLatch.Enqueue(FLoadGate), FCurrentFileName);
|
||||||
|
FCurrPosInFile := 0;
|
||||||
|
FCurrentIdx := 0;
|
||||||
|
FIsLiveData.SetValue(false);
|
||||||
|
FLastTimeStamp := 0;
|
||||||
|
FFilename := '';
|
||||||
|
|
||||||
|
FNextFileName := FindNextDataFile(FCurrentFileName);
|
||||||
|
if FNextFileName <> '' then
|
||||||
|
FNextData := TAskBid.LoadDataFile(TLatch.Enqueue(FLoadGate), FNextFileName);
|
||||||
|
end;
|
||||||
|
|
||||||
|
FHasNewData.Notify;
|
||||||
|
FNeedNewChunk := true;
|
||||||
|
FCurrChunk := nil;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TTABFileServer.GetData: TMutable<TArray<ITickData>>;
|
||||||
|
begin
|
||||||
|
Result := FData;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TTABFileServer.GetIsLiveData: TMutable<Boolean>;
|
||||||
|
begin
|
||||||
|
Result := FIsLiveData;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TTABFileServer.UpdateChunk: TArray<ITickData>;
|
||||||
|
begin
|
||||||
|
if not FNeedNewChunk then
|
||||||
|
exit(FCurrChunk);
|
||||||
|
FNeedNewChunk := false;
|
||||||
|
|
||||||
|
if FCurrentData.Done.IsSet then
|
||||||
|
begin
|
||||||
|
SetLength(FCurrChunk, FMaxFetch);
|
||||||
|
var n := 0;
|
||||||
|
while n < FMaxFetch do
|
||||||
|
begin
|
||||||
|
if FCurrPosInFile >= Length(FCurrentData.Value) 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(TLatch.Enqueue(FLoadGate), FNextFileName);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
// We're done with this series
|
||||||
|
FIsLiveData.SetValue(true);
|
||||||
|
end;
|
||||||
|
|
||||||
|
SetLength(FCurrChunk, n);
|
||||||
|
break;
|
||||||
|
end;
|
||||||
|
|
||||||
|
var currData := FCurrentData.Value[FCurrPosInFile];
|
||||||
|
if FLastTimeStamp < currData.OADateTime then
|
||||||
|
begin
|
||||||
|
FLastTimeStamp := currData.OADateTime;
|
||||||
|
FCurrChunk[n] :=
|
||||||
|
TDataPoint<ITick>.Create(FCurrentIdx, FLastTimeStamp, 1, TTick.Create(currData.Data.Ask, currData.Data.Bid));
|
||||||
|
inc(FCurrentIdx);
|
||||||
|
inc(n);
|
||||||
|
end;
|
||||||
|
inc(FCurrPosInFile);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
Result := FCurrChunk;
|
||||||
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
+2
-1
@@ -35,7 +35,8 @@ uses
|
|||||||
Myc.Test.Signals.Dirty in '..\Src\Myc.Test.Signals.Dirty.pas',
|
Myc.Test.Signals.Dirty in '..\Src\Myc.Test.Signals.Dirty.pas',
|
||||||
Myc.Trade.DataPoint in '..\Src\Myc.Trade.DataPoint.pas',
|
Myc.Trade.DataPoint in '..\Src\Myc.Trade.DataPoint.pas',
|
||||||
Myc.Trade.DataServer in '..\Src\Myc.Trade.DataServer.pas',
|
Myc.Trade.DataServer in '..\Src\Myc.Trade.DataServer.pas',
|
||||||
Myc.Trade.Node in '..\Src\Myc.Trade.Node.pas';
|
Myc.Trade.Node in '..\Src\Myc.Trade.Node.pas',
|
||||||
|
Myc.Test.Trade.DataServer in '..\Src\Myc.Test.Trade.DataServer.pas';
|
||||||
|
|
||||||
{ keep comment here to protect the following conditional from being removed by the IDE when adding a unit }
|
{ keep comment here to protect the following conditional from being removed by the IDE when adding a unit }
|
||||||
{$IFNDEF TESTINSIGHT}
|
{$IFNDEF TESTINSIGHT}
|
||||||
|
|||||||
+12
-3
@@ -4,7 +4,7 @@
|
|||||||
<ProjectVersion>20.3</ProjectVersion>
|
<ProjectVersion>20.3</ProjectVersion>
|
||||||
<FrameworkType>None</FrameworkType>
|
<FrameworkType>None</FrameworkType>
|
||||||
<Base>True</Base>
|
<Base>True</Base>
|
||||||
<Config Condition="'$(Config)'==''">Debug</Config>
|
<Config Condition="'$(Config)'==''">Release</Config>
|
||||||
<Platform Condition="'$(Platform)'==''">Win64</Platform>
|
<Platform Condition="'$(Platform)'==''">Win64</Platform>
|
||||||
<ProjectName Condition="'$(ProjectName)'==''">MycTests</ProjectName>
|
<ProjectName Condition="'$(ProjectName)'==''">MycTests</ProjectName>
|
||||||
<TargetedPlatforms>3</TargetedPlatforms>
|
<TargetedPlatforms>3</TargetedPlatforms>
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
<UsingDelphiRTL>true</UsingDelphiRTL>
|
<UsingDelphiRTL>true</UsingDelphiRTL>
|
||||||
<Icon_MainIcon>$(BDS)\bin\delphi_PROJECTICON.ico</Icon_MainIcon>
|
<Icon_MainIcon>$(BDS)\bin\delphi_PROJECTICON.ico</Icon_MainIcon>
|
||||||
<Icns_MainIcns>$(BDS)\bin\delphi_PROJECTICNS.icns</Icns_MainIcns>
|
<Icns_MainIcns>$(BDS)\bin\delphi_PROJECTICNS.icns</Icns_MainIcns>
|
||||||
<DCC_UnitSearchPath>$(DUnitX);..\Src;$(DCC_UnitSearchPath)</DCC_UnitSearchPath>
|
<DCC_UnitSearchPath>$(DUnitX);T:\Myc\Src;$(DCC_UnitSearchPath)</DCC_UnitSearchPath>
|
||||||
<SanitizedProjectName>MycTests</SanitizedProjectName>
|
<SanitizedProjectName>MycTests</SanitizedProjectName>
|
||||||
<VerInfo_Locale>1031</VerInfo_Locale>
|
<VerInfo_Locale>1031</VerInfo_Locale>
|
||||||
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
|
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
|
||||||
@@ -93,6 +93,9 @@ $(PreBuildEvent)]]></PreBuildEvent>
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Cfg_1_Win32)'!=''">
|
<PropertyGroup Condition="'$(Cfg_1_Win32)'!=''">
|
||||||
<DCC_RemoteDebug>false</DCC_RemoteDebug>
|
<DCC_RemoteDebug>false</DCC_RemoteDebug>
|
||||||
|
<VerInfo_Locale>1033</VerInfo_Locale>
|
||||||
|
<Manifest_File>(None)</Manifest_File>
|
||||||
|
<AppDPIAwarenessMode>none</AppDPIAwarenessMode>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Cfg_1_Win64)'!=''">
|
<PropertyGroup Condition="'$(Cfg_1_Win64)'!=''">
|
||||||
<VerInfo_Locale>1033</VerInfo_Locale>
|
<VerInfo_Locale>1033</VerInfo_Locale>
|
||||||
@@ -133,6 +136,7 @@ $(PreBuildEvent)]]></PreBuildEvent>
|
|||||||
<DCCReference Include="..\Src\Myc.Trade.DataPoint.pas"/>
|
<DCCReference Include="..\Src\Myc.Trade.DataPoint.pas"/>
|
||||||
<DCCReference Include="..\Src\Myc.Trade.DataServer.pas"/>
|
<DCCReference Include="..\Src\Myc.Trade.DataServer.pas"/>
|
||||||
<DCCReference Include="..\Src\Myc.Trade.Node.pas"/>
|
<DCCReference Include="..\Src\Myc.Trade.Node.pas"/>
|
||||||
|
<DCCReference Include="..\Src\Myc.Test.Trade.DataServer.pas"/>
|
||||||
<BuildConfiguration Include="Base">
|
<BuildConfiguration Include="Base">
|
||||||
<Key>Base</Key>
|
<Key>Base</Key>
|
||||||
</BuildConfiguration>
|
</BuildConfiguration>
|
||||||
@@ -153,7 +157,12 @@ $(PreBuildEvent)]]></PreBuildEvent>
|
|||||||
<Source>
|
<Source>
|
||||||
<Source Name="MainSource">MycTests.dpr</Source>
|
<Source Name="MainSource">MycTests.dpr</Source>
|
||||||
</Source>
|
</Source>
|
||||||
<Excluded_Packages/>
|
<Excluded_Packages>
|
||||||
|
<Excluded_Packages Name="$(BDSBIN)\bcboffice2k290.bpl">Embarcadero C++Builder Office 2000 Servers Package</Excluded_Packages>
|
||||||
|
<Excluded_Packages Name="$(BDSBIN)\bcbofficexp290.bpl">Embarcadero C++Builder Office XP Servers Package</Excluded_Packages>
|
||||||
|
<Excluded_Packages Name="$(BDSBIN)\dcloffice2k290.bpl">Microsoft Office 2000 Sample Automation Server Wrapper Components</Excluded_Packages>
|
||||||
|
<Excluded_Packages Name="$(BDSBIN)\dclofficexp290.bpl">Microsoft Office XP Sample Automation Server Wrapper Components</Excluded_Packages>
|
||||||
|
</Excluded_Packages>
|
||||||
</Delphi.Personality>
|
</Delphi.Personality>
|
||||||
<Deployment Version="5">
|
<Deployment Version="5">
|
||||||
<DeployFile LocalName="$(BDS)\Redist\iossimulator\libcgunwind.1.0.dylib" Class="DependencyModule">
|
<DeployFile LocalName="$(BDS)\Redist\iossimulator\libcgunwind.1.0.dylib" Class="DependencyModule">
|
||||||
|
|||||||
Reference in New Issue
Block a user