diff --git a/Src/Myc.Core.Lazy.pas b/Src/Myc.Core.Lazy.pas index 8e0554c..c8e9cfb 100644 --- a/Src/Myc.Core.Lazy.pas +++ b/Src/Myc.Core.Lazy.pas @@ -35,7 +35,7 @@ type constructor Create(const AChanged: TSignal; const AProc: TFunc); end; - TMycWriteableMutable = class(TInterfacedObject, TMutable.IMutable, TMutable.IWriteableMutable) + TMycWriteableMutable = class(TInterfacedObject, TMutable.IMutable, TMutable.IWriteable) private FValue: T; FChanged: TEvent; diff --git a/Src/Myc.Lazy.pas b/Src/Myc.Lazy.pas index c0cfc45..61a0105 100644 --- a/Src/Myc.Lazy.pas +++ b/Src/Myc.Lazy.pas @@ -18,7 +18,7 @@ type property Value: T read GetValue; end; - IWriteableMutable = interface(IMutable) + IWriteable = interface(IMutable) procedure SetValue(const Value: T); end; @@ -42,7 +42,7 @@ type class function Construct(const Changing: TSignal; const Proc: TFunc): TMutable; overload; static; - class function CreateWriteable(const Init: T): IWriteableMutable; overload; static; + class function CreateWriteable(const Init: T): IWriteable; overload; static; property Changed: TSignal read GetChanged; end; @@ -105,7 +105,7 @@ begin Result := TMycFuncMutable.Create(Changing, Proc); end; -class function TMutable.CreateWriteable(const Init: T): IWriteableMutable; +class function TMutable.CreateWriteable(const Init: T): IWriteable; begin Result := TMycWriteableMutable.Create(Init); end; diff --git a/Src/Myc.Trade.DataPoint.pas b/Src/Myc.Trade.DataPoint.pas new file mode 100644 index 0000000..356413a --- /dev/null +++ b/Src/Myc.Trade.DataPoint.pas @@ -0,0 +1,155 @@ +unit Myc.Trade.DataPoint; + +interface + +uses + System.SysUtils; + +type + IDataPoint = interface + ['{6A52697A-2868-42A9-982D-993542B7B360}'] + function GetData: T; + function GetTime: TDateTime; + function GetVolume: Double; + + property Time: TDateTime read GetTime; + property Volume: Double read GetVolume; + property Data: T read GetData; + end; + + ITick = interface + ['{B342223B-D299-4A1C-82F3-569F54417A66}'] + function GetAsk: Double; + function GetBid: Double; + + property Ask: Double read GetAsk; + property Bid: Double read GetBid; + end; + + IOHLC = interface + ['{05374E3C-731B-44FA-A23E-051F1461B78D}'] + function GetOpen: Double; + function GetHigh: Double; + function GetLow: Double; + function GetClose: Double; + + property Open: Double read GetOpen; + property High: Double read GetHigh; + property Low: Double read GetLow; + property Close: Double read GetClose; + end; + + // Abstract base class for all data points. + TDataPoint = class abstract(TInterfacedObject, IDataPoint) + protected + fTime: TDateTime; + fVolume: Double; + FData: T; + function GetTime: TDateTime; + function GetVolume: Double; + function GetData: T; + public + constructor Create(Time: TDateTime; Volume: Double; const AData: T); + end; + + TTick = class(TInterfacedObject, ITick) + private + fAsk: Double; + fBid: Double; + function GetAsk: Double; + function GetBid: Double; + public + constructor Create(Ask: Double; Bid: Double); + end; + + TOHLC = class(TInterfacedObject, IOHLC) + private + fOpen: Double; + fHigh: Double; + fLow: Double; + fClose: Double; + function GetClose: Double; + function GetHigh: Double; + function GetLow: Double; + function GetOpen: Double; + public + constructor Create(Open, High, Low, Close: Double); + end; + +implementation + +{ TDataPoint } + +constructor TDataPoint.Create(Time: TDateTime; Volume: Double; const AData: T); +begin + fTime := Time; + fVolume := Volume; + FData := AData; +end; + +function TDataPoint.GetData: T; +begin + Result := FData; +end; + +function TDataPoint.GetTime: TDateTime; +begin + result := fTime; +end; + +function TDataPoint.GetVolume: Double; +begin + result := fVolume; +end; + +{ TTick } + +constructor TTick.Create(Ask: Double; Bid: Double); +begin + inherited Create; + fAsk := Ask; + fBid := Bid; +end; + +function TTick.GetAsk: Double; +begin + result := fAsk; +end; + +function TTick.GetBid: Double; +begin + result := fBid; +end; + +{ TOHLC } + +constructor TOHLC.Create(Open, High, Low, Close: Double); +begin + inherited Create; + fOpen := Open; + fHigh := High; + fLow := Low; + fClose := Close; +end; + +function TOHLC.GetClose: Double; +begin + result := fClose; +end; + +function TOHLC.GetHigh: Double; +begin + result := fHigh; +end; + +function TOHLC.GetLow: Double; +begin + result := fLow; +end; + +function TOHLC.GetOpen: Double; +begin + result := fOpen; +end; + +end. diff --git a/Src/Myc.Trade.DataSeries.pas b/Src/Myc.Trade.DataSeries.pas index c6a761f..d30c2f0 100644 --- a/Src/Myc.Trade.DataSeries.pas +++ b/Src/Myc.Trade.DataSeries.pas @@ -1,39 +1,5 @@ unit Myc.Trade.DataSeries; -(* ------------------------------------------------------------------------------ - This unit provides functions to load tick data (TTickData) from - binary files. The data originates from a C# application - and has the structure OADateTime (Double), Ask (Single), and Bid (Single). - - The files follow the naming convention: - For .tab files: Symbol_Year_MM.tab OR Symbol_Year_MM.tab_zip - For .tab-live files: Symbol_Year_MM.tab-live (NEVER compressed) - (MM represents the two-digit month, e.g., 01 for January, 12 for December) - - If both compressed (.tab_zip) and uncompressed (.tab) versions of a .tab - file exist, the compressed version is preferred. Files ending with _zip - are expected to be ZIP archives containing the actual data file - (e.g., Symbol_Year_MM.tab). - - Loading occurs in two phases: - 1. All .tab files (regular or _zip) are processed chronologically by - month and year. Unique ticks are loaded, and the timestamp of the - latest tick across all .tab files is determined (overallLastTabTickTime). - Assumes timestamps are unique across all .tab files. - 2. All .tab-live files (which are never compressed), for months/years - corresponding to found .tab files, are processed. Ticks from a - .tab-live file are only integrated if their timestamp is strictly later - than overallLastTabTickTime. Assumes such ticks are globally unique. - - If a .tab-live file results in no new data being added under these rules, - it is deleted after processing. - - The loading function automatically detects subsequent monthly/yearly files. - - Main Function: - LoadTickDataSeries - Loads a series of tick data files. - ------------------------------------------------------------------------------ *) - interface uses @@ -293,9 +259,9 @@ begin for var i := 0 to High(fL) do begin - FCachedFiles.Remove(fL[i].Name); if not IsMemoryLow then break; + FCachedFiles.Remove(fL[i].Name); end; end; @@ -319,10 +285,6 @@ begin if FileName.EndsWith('_zip', True) then begin - var zipFileBytes := TFile.ReadAllBytes(FileName); // Read all bytes of the .zip file - if Length(zipFileBytes) = 0 then - exit; - var capFileName := FileName; Result := TFuture @@ -335,7 +297,7 @@ begin .Chain>( function(bytes: TBytes): TArray begin - var zipMemoryStream := TBytesStream.Create(zipFileBytes); // Create a memory stream to hold the zip file content + var zipMemoryStream := TBytesStream.Create(bytes); // Create a memory stream to hold the zip file content try zipMemoryStream.Position := 0; Result := ReadCompressedData(zipMemoryStream); @@ -349,6 +311,7 @@ begin var capFileName := FileName; Result := TFuture>.Construct( + TLatch.Enqueue(LoadGate), function: TArray begin if TFile.Exists(capFileName) then @@ -375,7 +338,6 @@ begin begin var cachedFile: TCachedFile; cachedFile.Name := Filename; - var age: TDateTime; if FileAge(Filename, cachedFile.Age, true) then begin cachedFile.Data := Result; @@ -418,7 +380,7 @@ begin // Start of LoadDataSeries var maxTabYearFound := -1; var maxTabMonthFound := -1; - var filesToLoadPhase1 := TList.Create; // Create list with the named record type + var filesToLoad := TList.Create; // Create list with the named record type try while True do begin @@ -443,7 +405,7 @@ begin // Start of LoadDataSeries tabFileEntryRecord.Path := actualTabFileToLoad; tabFileEntryRecord.Year := currentTabYear; tabFileEntryRecord.Month := currentTabMonth; - filesToLoadPhase1.Add(tabFileEntryRecord); + filesToLoad.Add(tabFileEntryRecord); // Advance to the next month Inc(currentTabMonth); @@ -460,7 +422,7 @@ begin // Start of LoadDataSeries try var LoadGate: TLatch; - for var tabFileEntry: TPhase1FileEntry in filesToLoadPhase1 do // Iterate with the correct type + for var tabFileEntry: TPhase1FileEntry in filesToLoad do // Iterate with the correct type begin var data := LoadDataFile(LoadGate, tabFileEntry.Path); @@ -490,7 +452,7 @@ begin // Start of LoadDataSeries loadedFileList.Free; end; finally - filesToLoadPhase1.Free; + filesToLoad.Free; end; Result := diff --git a/Src/Myc.Trade.DataServer.pas b/Src/Myc.Trade.DataServer.pas new file mode 100644 index 0000000..ea77da6 --- /dev/null +++ b/Src/Myc.Trade.DataServer.pas @@ -0,0 +1,50 @@ +unit Myc.Trade.DataServer; + +interface + +uses + System.SysUtils, + System.Generics.Collections, + Myc.Lazy, + Myc.Trade.DataPoint, + Myc.Trade.DataSeries; + +type + IDataProvider = interface + ['{B2A9996C-724B-4416-A882-595B7E58066D}'] + procedure Fetch; + end; + + IDataServer = interface + ['{A6E246A2-E84E-49AB-A63E-333E561E488C}'] + function GetData: TMutable>; + function GetIsLiveData: TMutable; + property Data: TMutable> read GetData; + property IsLiveData: TMutable read GetIsLiveData; + end; + + TDataServer = class(TInterfacedObject, IDataServer, IDataProvider) + protected + function GetData: TMutable>; virtual; abstract; + function GetIsLiveData: TMutable; virtual; abstract; + procedure Fetch; virtual; abstract; + end; + + IDataServerNode = interface + ['{DA3531F1-158E-4A63-8E5A-34089C537B1B}'] + function CreateDataServer: IDataServer; + end; + + TDataServerNode = class(TInterfacedObject, IDataServerNode) + public + // Factory method to create the actual data server instance. + function CreateDataServer: IDataServer; virtual; abstract; + end; + + TFileStreamServerNode = class(TDataServerNode) + + end; + +implementation + +end. diff --git a/Src/Myc.Trade.Node.pas b/Src/Myc.Trade.Node.pas new file mode 100644 index 0000000..2aa9703 --- /dev/null +++ b/Src/Myc.Trade.Node.pas @@ -0,0 +1,48 @@ +unit Myc.Trade.Node; + +interface + +uses + System.SysUtils; + +type + // Base interface for all objects created by an INode factory. + ITradeObject = interface + ['{B8A29A67-272E-456A-A816-A19A76E43213}'] + end; + + // Represents a node in the trading graph, acting as a factory for a trade object. + INode = interface + ['{E879B5E4-3A4E-434A-8A4F-A9F71B0B7E1C}'] + function GetCaption: string; + // Factory method to create the runtime object. + function CreateObject: ITradeObject; + + property Caption: string read GetCaption; + end; + + // Abstract base class for all node implementations. + TNodeBase = class abstract(TInterfacedObject, INode) + protected + fCaption: string; + function GetCaption: string; + public + constructor Create(const Caption: string); + function CreateObject: ITradeObject; virtual; abstract; + end; + +implementation + +{ TNodeBase } + +constructor TNodeBase.Create(const Caption: string); +begin + fCaption := Caption; +end; + +function TNodeBase.GetCaption: string; +begin + result := fCaption; +end; + +end. diff --git a/Test/MycTests.dpr b/Test/MycTests.dpr index 2e8b02a..a5ee521 100644 --- a/Test/MycTests.dpr +++ b/Test/MycTests.dpr @@ -32,7 +32,10 @@ uses Myc.Test.Core.Atomic in '..\Src\Myc.Test.Core.Atomic.pas', Myc.Trade.Ticker in '..\Src\Myc.Trade.Ticker.pas', Myc.Test.Signals.Latch in '..\Src\Myc.Test.Signals.Latch.pas', - 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.DataServer in '..\Src\Myc.Trade.DataServer.pas', + Myc.Trade.Node in '..\Src\Myc.Trade.Node.pas'; { keep comment here to protect the following conditional from being removed by the IDE when adding a unit } {$IFNDEF TESTINSIGHT} diff --git a/Test/MycTests.dproj b/Test/MycTests.dproj index 1f010d5..d0da37a 100644 --- a/Test/MycTests.dproj +++ b/Test/MycTests.dproj @@ -130,6 +130,9 @@ $(PreBuildEvent)]]> + + + Base diff --git a/Test/MycTests.res b/Test/MycTests.res index 333684a..e30ea80 100644 Binary files a/Test/MycTests.res and b/Test/MycTests.res differ