From a9aff8c41acf41ce044f8ed99e71ca9447864637 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Tue, 17 Jun 2025 21:49:09 +0200 Subject: [PATCH] AuraTrader Sample --- AuraTrader/AuraTrader.dpr | 15 + AuraTrader/AuraTrader.dproj | 1146 ++++++++++++++++++++++++++++++++++ AuraTrader/AuraTrader.mm | 224 +++++++ AuraTrader/AuraTrader.res | Bin 0 -> 154804 bytes AuraTrader/MainForm.fmx | 68 ++ AuraTrader/MainForm.pas | 172 +++++ AuraTrader/Projektplan.md | 74 +++ Src/Myc.Core.FileCache.pas | 8 +- Src/Myc.Core.Futures.pas | 53 +- Src/Myc.Futures.pas | 8 + Src/Myc.Signals.FMX.pas | 53 +- Src/Myc.Signals.pas | 45 +- Src/Myc.Trade.DataStream.pas | 533 ++++++++-------- 13 files changed, 2093 insertions(+), 306 deletions(-) create mode 100644 AuraTrader/AuraTrader.dpr create mode 100644 AuraTrader/AuraTrader.dproj create mode 100644 AuraTrader/AuraTrader.mm create mode 100644 AuraTrader/AuraTrader.res create mode 100644 AuraTrader/MainForm.fmx create mode 100644 AuraTrader/MainForm.pas create mode 100644 AuraTrader/Projektplan.md diff --git a/AuraTrader/AuraTrader.dpr b/AuraTrader/AuraTrader.dpr new file mode 100644 index 0000000..3ee392a --- /dev/null +++ b/AuraTrader/AuraTrader.dpr @@ -0,0 +1,15 @@ +program AuraTrader; + +uses + FastMM5, + System.StartUpCopy, + FMX.Forms, + MainForm in 'MainForm.pas' {Form1}; + +{$R *.res} + +begin + Application.Initialize; + Application.CreateForm(TForm1, Form1); + Application.Run; +end. diff --git a/AuraTrader/AuraTrader.dproj b/AuraTrader/AuraTrader.dproj new file mode 100644 index 0000000..d7d0f0e --- /dev/null +++ b/AuraTrader/AuraTrader.dproj @@ -0,0 +1,1146 @@ + + + {9EC42A5B-872B-410F-AF42-A0A62DBF91E0} + 20.3 + FMX + True + Debug + Win64 + AuraTrader + 3 + Application + AuraTrader.dpr + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + .\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + $(BDS)\bin\delphi_PROJECTICON.ico + $(BDS)\bin\delphi_PROJECTICNS.icns + AuraTrader + T:\Myc\Src;$(DCC_UnitSearchPath) + 1031 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + + + vclwinx;fmx;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;FireDACCommonODBC;FireDACCommonDriver;IndyProtocols;vclx;Skia.Package.RTL;IndyIPClient;dbxcds;vcledge;bindcompvclwinx;FmxTeeUI;bindcompfmx;inetdb;FireDACSqliteDriver;DbxClientDriver;Tee;soapmidas;vclactnband;TeeUI;fmxFireDAC;dbexpress;DBXMySQLDriver;VclSmp;inet;vcltouch;fmxase;dbrtl;Skia.Package.FMX;fmxdae;TeeDB;FireDACMSAccDriver;CustomIPTransport;vcldsnap;DBXInterBaseDriver;IndySystem;Skia.Package.VCL;vcldb;vclFireDAC;bindcomp;FireDACCommon;inetstn;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;FireDACADSDriver;RESTComponents;DBXSqliteDriver;vcl;IndyIPServer;dsnapxml;dsnapcon;adortl;vclimg;FireDACPgDriver;FireDAC;inetdbxpress;xmlrtl;tethering;bindcompvcl;dsnap;CloudService;fmxobj;bindcompvclsmp;FMXTee;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + 1033 + $(BDS)\bin\default_app.manifest + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + vclwinx;fmx;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;FireDACCommonODBC;FireDACCommonDriver;IndyProtocols;vclx;IndyIPClient;dbxcds;vcledge;bindcompvclwinx;FmxTeeUI;bindcompfmx;inetdb;FireDACSqliteDriver;DbxClientDriver;Tee;soapmidas;vclactnband;TeeUI;fmxFireDAC;dbexpress;DBXMySQLDriver;VclSmp;inet;vcltouch;fmxase;dbrtl;fmxdae;TeeDB;FireDACMSAccDriver;CustomIPTransport;vcldsnap;DBXInterBaseDriver;IndySystem;Skia.Package.VCL;vcldb;vclFireDAC;bindcomp;FireDACCommon;inetstn;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;FireDACADSDriver;RESTComponents;DBXSqliteDriver;vcl;IndyIPServer;dsnapxml;dsnapcon;adortl;vclimg;FireDACPgDriver;FireDAC;inetdbxpress;xmlrtl;tethering;bindcompvcl;dsnap;CloudService;fmxobj;bindcompvclsmp;FMXTee;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + true + 1033 + $(BDS)\bin\default_app.manifest + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + DEBUG;$(DCC_Define) + true + false + true + true + true + true + true + + + false + PerMonitorV2 + + + PerMonitorV2 + true + 1033 + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + PerMonitorV2 + + + PerMonitorV2 + + + + MainSource + + +
Form1
+
+ + Base + + + Cfg_1 + Base + + + Cfg_2 + Base + +
+ + Delphi.Personality.12 + Application + + + + AuraTrader.dpr + + + + + + true + + + + + true + + + + + true + + + + + AuraTrader.exe + true + + + + + AuraTrader.rsm + true + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi + 1 + + + library\lib\armeabi + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\mips + 1 + + + library\lib\mips + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-anydpi-v21 + 1 + + + res\drawable-anydpi-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values-v31 + 1 + + + res\values-v31 + 1 + + + + + res\values-v35 + 1 + + + res\values-v35 + 1 + + + + + res\drawable-anydpi-v26 + 1 + + + res\drawable-anydpi-v26 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-anydpi-v33 + 1 + + + res\drawable-anydpi-v33 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\values-night-v21 + 1 + + + res\values-night-v21 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-ldpi + 1 + + + res\drawable-ldpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-small + 1 + + + res\drawable-small + 1 + + + + + res\drawable-normal + 1 + + + res\drawable-normal + 1 + + + + + res\drawable-large + 1 + + + res\drawable-large + 1 + + + + + res\drawable-xlarge + 1 + + + res\drawable-xlarge + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable-anydpi-v24 + 1 + + + res\drawable-anydpi-v24 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-night-anydpi-v21 + 1 + + + res\drawable-night-anydpi-v21 + 1 + + + + + res\drawable-anydpi-v31 + 1 + + + res\drawable-anydpi-v31 + 1 + + + + + res\drawable-night-anydpi-v31 + 1 + + + res\drawable-night-anydpi-v31 + 1 + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + 0 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + ..\ + 1 + + + + + Contents + 1 + + + Contents + 1 + + + Contents + 1 + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + Contents\MacOS + 1 + + + Contents\MacOS + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen + 64 + + + ..\$(PROJECTNAME).launchscreen + 64 + + + + + 1 + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + + + + + + + + + + + + + True + True + + + 12 + + + + +
diff --git a/AuraTrader/AuraTrader.mm b/AuraTrader/AuraTrader.mm new file mode 100644 index 0000000..82e44a4 --- /dev/null +++ b/AuraTrader/AuraTrader.mm @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AuraTrader/AuraTrader.res b/AuraTrader/AuraTrader.res new file mode 100644 index 0000000000000000000000000000000000000000..5c25dbd94b8baa36dc7add956848281becc41748 GIT binary patch literal 154804 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 z�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 QXMEBJf0+BxOYpn@2VW@4#sB~S literal 0 HcmV?d00001 diff --git a/AuraTrader/MainForm.fmx b/AuraTrader/MainForm.fmx new file mode 100644 index 0000000..ce121c7 --- /dev/null +++ b/AuraTrader/MainForm.fmx @@ -0,0 +1,68 @@ +object Form1: TForm1 + Left = 0 + Top = 0 + Caption = 'Form1' + ClientHeight = 840 + ClientWidth = 808 + FormFactor.Width = 320 + FormFactor.Height = 480 + FormFactor.Devices = [Desktop] + OnCreate = FormCreate + OnDestroy = FormDestroy + DesignerMasterStyle = 0 + object Panel1: TPanel + Align = Top + Size.Width = 808.00000000000000000 + Size.Height = 529.00000000000000000 + Size.PlatformDefault = False + TabOrder = 2 + object RandomButton: TButton + Position.X = 697.00000000000000000 + Position.Y = 38.00000000000000000 + TabOrder = 0 + Text = 'Random' + TextSettings.Trimming = None + OnClick = RandomButtonClick + end + object Path1: TPath + Anchors = [akLeft, akTop, akRight] + Position.X = 8.00000000000000000 + Position.Y = 8.00000000000000000 + Size.Width = 489.00000000000000000 + Size.Height = 513.00000000000000000 + Size.PlatformDefault = False + end + object SymbolsComboBox: TComboBox + Anchors = [akTop, akRight] + Position.X = 505.00000000000000000 + Position.Y = 8.00000000000000000 + Size.Width = 217.00000000000000000 + Size.Height = 22.00000000000000000 + Size.PlatformDefault = False + TabOrder = 2 + end + object LoadButton: TButton + Anchors = [akTop, akRight] + Position.X = 730.00000000000000000 + Position.Y = 8.00000000000000000 + Size.Width = 47.00000000000000000 + Size.Height = 22.00000000000000000 + Size.PlatformDefault = False + TabOrder = 3 + Text = 'Load' + TextSettings.Trimming = None + OnClick = LoadButtonClick + end + end + object LogMemo: TMemo + Touch.InteractiveGestures = [Pan, LongTap, DoubleTap] + DataDetectorTypes = [] + Align = Client + Size.Width = 808.00000000000000000 + Size.Height = 311.00000000000000000 + Size.PlatformDefault = False + TabOrder = 1 + Viewport.Width = 804.00000000000000000 + Viewport.Height = 307.00000000000000000 + end +end diff --git a/AuraTrader/MainForm.pas b/AuraTrader/MainForm.pas new file mode 100644 index 0000000..d2fdf64 --- /dev/null +++ b/AuraTrader/MainForm.pas @@ -0,0 +1,172 @@ +unit MainForm; + +interface + +uses + System.SysUtils, + System.Types, + System.UITypes, + System.Classes, + System.Variants, + System.DateUtils, + FMX.Types, + FMX.Controls, + FMX.Forms, + FMX.Graphics, + FMX.Dialogs, + FMX.Controls.Presentation, + FMX.StdCtrls, + FMX.ListView.Types, + FMX.ListView.Appearances, + FMX.ListView.Adapters.Base, + FMX.ListView, + FMX.Memo.Types, + FMX.ScrollBox, + FMX.Memo, + FMX.Objects, + Myc.Futures, + Myc.Trade.DataStream, + Myc.Trade.DataPoint, + Myc.Signals, + Myc.Signals.FMX, + FMX.ListBox; + +type + TForm1 = class(TForm) + LogMemo: TMemo; + Panel1: TPanel; + RandomButton: TButton; + Path1: TPath; + SymbolsComboBox: TComboBox; + LoadButton: TButton; + procedure RandomButtonClick(Sender: TObject); + procedure FormCreate(Sender: TObject); + procedure FormDestroy(Sender: TObject); + procedure LoadButtonClick(Sender: TObject); + private + FOnEvent: TNotifyEvent; + { Private declarations } + FServer: IDataServer; + FSymbolsValid: TFMXValidationState; + FStreamUpdate: TFMXValidationState; + FSymbols: TFuture>; + FStream: IDataStream; + FRandomStream: array[0..19] of IDataStream; + FStreamIdx: Integer; + procedure DoIdle(Sender: TObject; var Done: Boolean); + public + { Public declarations } + published + property OnEvent: TNotifyEvent read FOnEvent write FOnEvent; + end; + +var + Form1: TForm1; + +implementation + +{$R *.fmx} + +procedure TForm1.FormCreate(Sender: TObject); +begin + // Create an instance of the TAuraTABFileServer. The server can be reused for multiple stream creations. [364] + FServer := TAuraTABFileServer.Create('\\COFFEE\TickData\Pepperstone'); + + SymbolsComboBox.Enabled := false; + + FSymbols := FServer.EnumerateSymbols; + + FSymbolsValid := + TFMXValidationState.Create( + SymbolsComboBox, + FSymbols.Done, + procedure + begin + SymbolsComboBox.BeginUpdate; + try + SymbolsComboBox.Items.Clear; + SymbolsComboBox.Items.AddStrings(FSymbols.WaitFor); + if SymbolsComboBox.Items.Count > 0 then + begin + if SymbolsComboBox.ItemIndex < 0 then + SymbolsComboBox.ItemIndex := SymbolsComboBox.Items.IndexOf('GER40'); + SymbolsComboBox.Enabled := true; + end; + finally + SymbolsComboBox.EndUpdate; + end; + end + ); + + Application.OnIdle := DoIdle; +end; + +procedure TForm1.FormDestroy(Sender: TObject); +begin + Application.OnIdle := nil; + FSymbolsValid.Free; +end; + +procedure TForm1.LoadButtonClick(Sender: TObject); +begin + FStream := FServer.CreateStream(FSymbols.WaitFor[SymbolsComboBox.ItemIndex]); + FStreamUpdate := TFMXValidationState.Create(Path1, FStream.HasData, + procedure + var + Data: array [0..5] of TDataPoint; + begin + var cnt := FStream.GetChunk(Data); + if cnt = 0 then exit; + + for var i:=0 to cnt-1 do + Path1.Data.LineTo( PointF(Path1.Data.Count, Data[i].Data.Ask) ); + end); +end; + +procedure TForm1.RandomButtonClick(Sender: TObject); +begin + var ass := FSymbols.WaitFor; + FRandomStream[FStreamIdx] := FServer.CreateStream(ass[Random(Length(ass))]); + + for var i := 0 to High(FRandomStream) do + if LogMemo.Lines.Count = i then + LogMemo.Lines.Add(''); + LogMemo.Lines[FStreamIdx] := FRandomStream[FStreamIdx].Symbol + ' loading...'; + + FStreamIdx := (FStreamIdx + 1) mod Length(FRandomStream); +end; + +procedure TForm1.DoIdle(Sender: TObject; var Done: Boolean); +var + Items: TArray>; +begin + SetLength(Items, 200); + + LogMemo.BeginUpdate; + try + for var i := 0 to High(FRandomStream) do + begin + var str := ''; + if LogMemo.Lines.Count > i then + str := LogMemo.Lines[i] + else + LogMemo.Lines.Add(''); + + if Assigned(FRandomStream[i]) then + begin + var cnt := FRandomStream[i].GetChunk(Items); + if cnt > 0 then + begin + var d := Items[cnt - 1]; + str := FRandomStream[i].Symbol + ' ' + cnt.ToString + ' ' + d.Time.ToString + ': ' + d.Data.Ask.ToString; + end; + end; + + LogMemo.Lines[i] := str; + end; + finally + LogMemo.EndUpdate; + end; +end; + +end. diff --git a/AuraTrader/Projektplan.md b/AuraTrader/Projektplan.md new file mode 100644 index 0000000..85d8f0a --- /dev/null +++ b/AuraTrader/Projektplan.md @@ -0,0 +1,74 @@ +# Projektplan: Meilenstein Daten-Infrastruktur + +*Datum: 13. Juni 2025* + +## Status: Design-Phase abgeschlossen, Basis-Implementierung erfolgt + +--- + +## Stufe 1: Asynchrone Datenstrom-Schnittstelle (`IDataStream`) - **ABGESCHLOSSEN** + +* **Motivation**: Das ursprüngliche, zustandsbehaftete Design war für die Verarbeitung asynchron eintreffender Daten (z.B. von `TFuture`-Objekten) zu komplex und fehleranfällig. + +* **Ziel**: Die Schaffung eines robusten, ereignisgesteuerten und non-blocking Modells, das den Datenproduzenten sauber vom Konsumenten entkoppelt. + +* **Ergebnis**: + * Die `IDataStream`-Schnittstelle wurde überarbeitet. Die `HasData`-Eigenschaft liefert nun ein `TSignal`, das Konsumenten aktiv über potenziell neue Daten informiert. + * Die Referenzimplementierung `TAuraFileStream` wurde erfolgreich angepasst und nutzt eine saubere Ereignis-Kopplung (`Subscribe`) für eine robuste und wartungsarme Logik. + * Die korrekte Funktionalität wurde durch eine angepasste DUnitX-Test-Suite verifiziert. + +--- + +## Stufe 2: Abstraktion für Handelssysteme (`IDataSeriesProvider`) - **ENTWORFEN** + +* **Motivation**: Ein Handelssystem benötigt einen stets validen und kontinuierlichen Daten-Lookback. Ein roher `IDataStream` kann dies nicht garantieren, da Lücken in den Daten auftreten können (z.B. beim Übergang von Historie zu Live). + +* **Ziel**: Die Konzeption einer übergeordneten Abstraktionsschicht, die diese komplexe Anforderung kapselt, die Datenintegrität sicherstellt und dem Handelssystem eine einfache, sichere Schnittstelle bietet. + +* **Ergebnis**: + Entworfen wurde der `IDataSeriesProvider`, der als "Black Box" für das Handelssystem fungiert und die Komplexität der Datenbeschaffung vollständig verbirgt. Er wurde mit zwei unterschiedlichen, vom Anwender wählbaren Betriebsmodi konzipiert: + + ### Modus 1: "Live-Handel" + * **Motivation**: Um einen echten **"Sofort-Start"** im Live-Handel zu ermöglichen, muss die Lücke zwischen den statischen, lokalen Historiendaten und dem aktuellen Zeitpunkt geschlossen werden. + * **Ziel**: Ein lückenloser, tagesaktueller Start des Handelssystems ohne manuelles Eingreifen oder lange Wartezeiten für den Nutzer. + * **Ergebnis**: Das Design einer **Drei-Phasen-Synchronisation**: (1) Lokale History laden, (2) "Catch-up"-Daten vom Broker holen, (3) auf den Live-Stream umschalten. + + ### Modus 2: "Simulation & Backtest" + * **Motivation**: Für Entwicklung, Test und Analyse muss die Software **völlig autonom** und ohne Abhängigkeit von einer externen, potenziell nicht verfügbaren Broker-API lauffähig sein. + * **Ziel**: Einen "Sofort-Start" für Backtests zu jedem beliebigen Zeitpunkt in der Vergangenheit zu ermöglichen, der rein auf lokalen Dateien basiert. + * **Ergebnis**: Ein Design, bei dem der relevante Datenkontext in den Speicher geladen wird, um von dort aus einen schnellen Start und ein "Playback" der Daten zu ermöglichen. + +# TODO-Einträge: Dateninfrastruktur + +## Stufe 2: `IDataSeriesProvider` + +- [ ] **Interface und Basis-Implementierung erstellen** + * Definiere das `IDataSeriesProvider`-Interface. + * Definiere die `TProviderStatus`-Enumeration (`psInitializing`, `psReadyContinuous`, `psReadyWithGap`, `psFaulted`). + * Implementiere eine abstrakte Basisklasse `TDataSeriesProvider`, die die grundlegenden Felder (z.B. für die interne `TDataSeries`) und Methoden bereitstellt. + +- [ ] **Simulations-Modus implementieren** + * Erstelle eine konkrete Implementierung (z.B. `TSimulationSeriesProvider`). + * Implementiere die Start-Logik: + * Laden des gesamten relevanten Datenkontexts via `TAuraDataServer.LoadDataSeries`. + * Positionierung auf einen wählbaren Startzeitpunkt via `TDataSeries.IndexOf`. + * Extrahieren des initialen Lookback-Puffers. + * Implementiere das "Playback" der nachfolgenden Datenpunkte aus der im Speicher gehaltenen Serie. + * Setze den `Status` korrekt auf `psReadyContinuous`, da im Simulationsmodus per Definition keine Lücke zur "Gegenwart" existiert. + +- [ ] **Live-Handel Modus implementieren** + * **Voraussetzung**: Definiere eine Schnittstelle für Live-Datenquellen (z.B. `ILiveDataSource`), die eine Methode zur Abfrage rezenter historischer Daten (`GetCatchUpData`) enthält. + * **Start-Logik (Drei-Phasen-Synchronisation)**: + * Implementiere den parallelen Start der asynchronen Operationen: + 1. Laden der lokalen Historie. + 2. Herstellen der Broker-Verbindung und Abrufen der "Catch-up"-Daten. + * Implementiere die "Splicing"-Logik, die beide Datenquellen zu einem nahtlosen Lookback-Puffer verbindet. + * Implementiere die Prüfung auf ein "Markt-Gap" beim Übergang, um den initialen Status korrekt auf `psReadyContinuous` oder `psReadyWithGap` zu setzen. + * **Laufzeit-Logik**: + * Implementiere das kontinuierliche Verarbeiten des Live-Streams. + * Implementiere die Fehlerbehandlung für Laufzeit-Unterbrechungen (Verbindungsverlust), inklusive des Wechsels in den `psFaulted`-Status und des sicheren "Re-Priming". + +- [ ] **Session-Kalender entwerfen und implementieren** + * **Motivation**: Notwendig für die Unterscheidung zwischen "Markt-Gaps" (natürlich) und "Daten-Lücken" (Fehler) im Live-Betrieb. + * **Anforderung**: Muss pro Asset konfigurierbar sein (Handelszeiten, Feiertage). + * **Implementierung**: Erstelle eine Klasse, die für ein gegebenes Zeitintervall prüfen kann, ob der Markt geöffnet oder geschlossen war. Integriere diese Prüfung in die Laufzeit-Logik des `TDataSeriesProvider` im Live-Modus. \ No newline at end of file diff --git a/Src/Myc.Core.FileCache.pas b/Src/Myc.Core.FileCache.pas index f790ba9..675e7a2 100644 --- a/Src/Myc.Core.FileCache.pas +++ b/Src/Myc.Core.FileCache.pas @@ -49,7 +49,9 @@ var implementation uses - {$IFDEF MSWINDOWS} Winapi.Windows, {$endif} +{$IFDEF MSWINDOWS} + Winapi.Windows, +{$endif} System.Math; { TDataFileCache } @@ -170,7 +172,7 @@ begin end; initialization - {$IFDEF MSWINDOWS} +{$IFDEF MSWINDOWS} IsMemoryLow := function: Boolean var @@ -184,6 +186,6 @@ initialization Result := memStatusEx.dwMemoryLoad >= 80; end; end; - {$endif} +{$endif} end. diff --git a/Src/Myc.Core.Futures.pas b/Src/Myc.Core.Futures.pas index 42e699a..27ca3c2 100644 --- a/Src/Myc.Core.Futures.pas +++ b/Src/Myc.Core.Futures.pas @@ -13,6 +13,8 @@ type protected function GetValue: T; virtual; abstract; function GetDone: TState; virtual; abstract; + public + procedure BeforeDestruction; override; end; TMycNullFuture = class(TMycFuture) @@ -34,8 +36,27 @@ type destructor Destroy; override; end; + TMycFutureManaged = class(TMycFuture) + private + FFuture: TFuture.IFuture; + protected + function GetValue: T; override; + function GetDone: TState; override; + public + constructor Create(const AFuture: TFuture.IFuture); + destructor Destroy; override; + end; + implementation +{ TMycFuture } + +procedure TMycFuture.BeforeDestruction; +begin + inherited; + Assert(GetDone.IsSet, 'Trying to destroy an unfinished future'); +end; + { TMycNullFuture } function TMycNullFuture.GetDone: TState; @@ -79,8 +100,6 @@ end; destructor TMycGateFuncFuture.Destroy; begin - Assert(FDone.State.IsSet, 'Future not done'); - FInit.Unsubscribe; inherited Destroy; end; @@ -96,4 +115,34 @@ begin Result := FResult; end; +{ TMycFutureManaged } + +constructor TMycFutureManaged.Create(const AFuture: TFuture.IFuture); +begin + inherited Create; + FFuture := AFuture; +end; + +destructor TMycFutureManaged.Destroy; +begin + if GetTypeKind(T) = tkClass then + begin + var val := GetValue; + var obj := TObject(PPointer(@val)^); + if obj <> nil then + obj.Free; + end; + inherited; +end; + +function TMycFutureManaged.GetDone: TState; +begin + Result := FFuture.Done; +end; + +function TMycFutureManaged.GetValue: T; +begin + Result := FFuture.Value; +end; + end. diff --git a/Src/Myc.Futures.pas b/Src/Myc.Futures.pas index 67cfe74..4d0754e 100644 --- a/Src/Myc.Futures.pas +++ b/Src/Myc.Futures.pas @@ -49,6 +49,8 @@ type class property Null: IFuture read FNull; + procedure Manage; + function Chain(const Proc: TFunc): TFuture; overload; function Chain(const Proc: TFuncConst): TFuture; overload; function WaitFor: T; @@ -113,6 +115,12 @@ begin Result := FFuture.Value; end; +procedure TFuture.Manage; +begin + if GetTypeKind(T) = tkClass then + FFuture := TMycFutureManaged.Create(FFuture); +end; + function TFuture.WaitFor: T; begin TaskManager.WaitFor(FFuture.Done); diff --git a/Src/Myc.Signals.FMX.pas b/Src/Myc.Signals.FMX.pas index 3cf79d8..aa78c16 100644 --- a/Src/Myc.Signals.FMX.pas +++ b/Src/Myc.Signals.FMX.pas @@ -4,23 +4,27 @@ interface uses System.Classes, + System.SysUtils, FMX.Controls, Myc.Signals; type - TFMXSignalLink = class(TComponent, TSignal.ISubscriber) + TFMXValidationState = class(TComponent, TSignal.ISubscriber) private [volatile] FInvalidated: Integer; - procedure InvalidateControl; - + FSignal: TSignal.ISignal; + FSubscription: TSignal.TSubscriptionTag; + FProc: TProc; + procedure CallProc; protected function Notify: Boolean; public - constructor Create(AOwner: TControl); reintroduce; + constructor Create(AOwner: TControl; const ASignal: TSignal.ISignal; const AProc: TProc); reintroduce; destructor Destroy; override; - function Reset: Boolean; + procedure AfterConstruction; override; + procedure BeforeDestruction; override; end; implementation @@ -28,36 +32,45 @@ implementation uses System.SyncObjs; -{ TFMXSignalLink } +{ TFMXValidationState } -constructor TFMXSignalLink.Create(AOwner: TControl); +constructor TFMXValidationState.Create(AOwner: TControl; const ASignal: TSignal.ISignal; const AProc: TProc); begin inherited Create(AOwner); FInvalidated := 0; + FSignal := ASignal; + FProc := AProc; end; -destructor TFMXSignalLink.Destroy; +destructor TFMXValidationState.Destroy; begin - TThread.RemoveQueuedEvents(InvalidateControl); + TThread.RemoveQueuedEvents(CallProc); inherited; end; -procedure TFMXSignalLink.InvalidateControl; +procedure TFMXValidationState.AfterConstruction; begin - with TControl(Owner) do - InvalidateRect(LocalRect); + inherited; + FSubscription := FSignal.Subscribe(Self); end; -function TFMXSignalLink.Notify: Boolean; +procedure TFMXValidationState.BeforeDestruction; +begin + FSignal.Unsubscribe(FSubscription); + inherited; +end; + +procedure TFMXValidationState.CallProc; +begin + if TInterlocked.Exchange(FInvalidated, 0) > 0 then + FProc; +end; + +function TFMXValidationState.Notify: Boolean; begin if TInterlocked.Exchange(FInvalidated, 1) = 0 then - TThread.Queue(nil, InvalidateControl); - exit( true ); -end; - -function TFMXSignalLink.Reset: Boolean; -begin - Result := TInterlocked.Exchange(FInvalidated, 0) > 0; + TThread.Queue(nil, CallProc); + exit(true); end; end. diff --git a/Src/Myc.Signals.pas b/Src/Myc.Signals.pas index 8303edf..dfd9855 100644 --- a/Src/Myc.Signals.pas +++ b/Src/Myc.Signals.pas @@ -3,8 +3,7 @@ unit Myc.Signals; interface uses - System.SysUtils, - System.SyncObjs; + System.SysUtils; type TSignal = record @@ -137,8 +136,7 @@ type strict private class var FNull: ILatch; - FQueueGate: TLatch; - FQueueLock: TSpinLock; + FQueueLock: Integer; class constructor ClassCreate; private @@ -153,8 +151,8 @@ type class function CreateLatch(Count: Integer): TLatch; static; - class function Enqueue(var Gate: TLatch): TState; overload; static; - class function Enqueue: TState; overload; static; + class function Enqueue1(var Gate: TLatch): TState; overload; static; + function Enqueue: TState; overload; class property Null: ILatch read FNull; @@ -394,7 +392,7 @@ class constructor TLatch.ClassCreate; begin // Create a singleton null latch instance that is initially (and always) set. FNull := TMycNullLatch.Create; - FQueueLock := TSpinLock.Create(false); + FQueueLock := 0; end; constructor TLatch.Create(const ALatch: TLatch.ILatch); @@ -412,23 +410,46 @@ begin Result := FNull; end; -class function TLatch.Enqueue(var Gate: TLatch): TState; +class function TLatch.Enqueue1(var Gate: TLatch): TState; begin var Latch: TLatch.ILatch := TMycLatch.Create(1); - FQueueLock.Enter; + repeat + if FQueueLock > 0 then + begin + YieldProcessor; + continue; + end; + until AtomicCmpExchange(FQueueLock, 1, 0) = 0; + try Gate.State.Subscribe(Latch); Gate := Latch; Result := Latch.State; finally - FQueueLock.Exit; + AtomicDecrement(FQueueLock); end; end; -class function TLatch.Enqueue: TState; +function TLatch.Enqueue: TState; begin - Result := Enqueue(FQueueGate); + var Latch: ILatch := TMycLatch.Create(1); + + repeat + if FQueueLock > 0 then + begin + YieldProcessor; + continue; + end; + until AtomicCmpExchange(FQueueLock, 1, 0) = 0; + + try + FLatch.State.Subscribe(Latch); + FLatch := Latch; + Result := FLatch.State; + finally + AtomicDecrement(FQueueLock); + end; end; function TLatch.GetState: TState; diff --git a/Src/Myc.Trade.DataStream.pas b/Src/Myc.Trade.DataStream.pas index 06571a9..789c1db 100644 --- a/Src/Myc.Trade.DataStream.pas +++ b/Src/Myc.Trade.DataStream.pas @@ -30,34 +30,6 @@ uses Myc.Core.FileCache; type - // Represents metadata for a single data file. - TDataFile = record - private - FExtension: String; - FPath: String; - FSymbol: String; - FYear: Integer; - FMonth: Integer; - function GetIsValid: Boolean; - public - constructor Create(const APath, ASymbol, AExtension: String; AYear, AMonth: Integer); - function GetBaseFileName: string; - function GetFullFileName: string; - // Gets the next consecutive data file, if it exists on disk. - function GetNextFile: TDataFile; - class function ParseFileName(const FileName: string): TDataFile; static; - // Scans a directory to find the oldest file for a specific symbol. - class function FindFirst(const DirectoryPath, Symbol: string): TDataFile; static; - // Scans a directory and returns the oldest file found for each symbol. - class function FindOldestPerSymbol(const DirectoryPath: string): TArray; static; - property IsValid: Boolean read GetIsValid; - property Extension: String read FExtension; - property Path: String read FPath; - property Symbol: String read FSymbol; - property Year: Integer read FYear; - property Month: Integer read FMonth; - end; - // Represents a generic data stream capable of providing sequential data chunks. // IsHistory: // - true, if this stream is a history stream. Once HasData becomes false, it reached it's end and will not provide more data. @@ -99,34 +71,66 @@ type ['{1F8E5A9D-E92A-44C1-9F3F-C4B82A6E94B3}'] function CreateStream(const Symbol: String): IDataStream; procedure ClearCache; - function EnumerateSymbols: TArray; + function EnumerateSymbols: TFuture>; end; // Aura files + // Represents metadata for a single data file. + TAuraDataFile = record + private + FExtension: String; + FPath: String; + FSymbol: String; + FYear: Integer; + FMonth: Integer; + function GetIsValid: Boolean; + public + constructor Create(const APath, ASymbol, AExtension: String; AYear, AMonth: Integer); + function GetBaseFileName: string; + function GetFullFileName: string; + // Gets the next consecutive data file, if it exists on disk. + function GetNextFile: TAuraDataFile; + property IsValid: Boolean read GetIsValid; + property Extension: String read FExtension; + property Path: String read FPath; + property Symbol: String read FSymbol; + property Year: Integer read FYear; + property Month: Integer read FMonth; + end; + // Generic server for loading and managing sequential time-series data from files. TAuraDataServer = class(TInterfacedObject, IDataServer) private FCachedFiles: TDataFileCache>>; FPath: String; - function DoLoad(const FileName: string): TFuture>>; + FSymbols: TFuture>; function GetPath: String; + // Used by cache to actually load an uncached file. + function DoLoad(const FileName: string): TFuture>>; + strict private class var FLoadGate: TLatch; protected - class function ReadCompressedData(const InputStream: TStream): TArray>; static; - class function ReadUncompressedData(const InputStream: TStream): TArray>; static; + // Scans a directory to find the oldest file for a specific symbol. + function FindFirstFile(const Symbol: string): TFuture; + function ParseFileName(const FileName: string): TAuraDataFile; virtual; abstract; + + class function ReadCompressedData(const InputStream: TStream): TArray>; virtual; abstract; + class function ReadUncompressedData(const InputStream: TStream): TArray>; virtual; abstract; public constructor Create(const APath: String); destructor Destroy; override; + procedure AfterConstruction; override; function CreateStream(const Symbol: String): IDataStream; virtual; abstract; procedure ClearCache; - function EnumerateSymbols: TArray; - function LoadDataFile(const DataFile: TDataFile): TFuture>>; + procedure UpdateSymbols; + function EnumerateSymbols: TFuture>; + function LoadDataFile(const DataFile: TAuraDataFile): TFuture>>; property Path: String read GetPath; end; @@ -135,7 +139,7 @@ type private type TDataStream = record - FileInfo: TDataFile; + FileInfo: TAuraDataFile; Data: TFuture>>; end; private @@ -162,9 +166,14 @@ type // Aura tick data file Ask-Bid TAuraTABFileServer = class(TAuraDataServer) + protected + function ParseFileName(const FileName: string): TAuraDataFile; override; + // Scans a directory and returns the oldest file found for each symbol. + class function ReadCompressedData(const InputStream: TStream): TArray>; override; + class function ReadUncompressedData(const InputStream: TStream): TArray>; override; public function CreateStream(const Symbol: String): IDataStream; override; - function LoadDataSeries(const InitialFile: TDataFile): TFuture>>; + function LoadDataSeries(const InitialFile: TAuraDataFile): TFuture>>; end; implementation @@ -175,9 +184,9 @@ uses System.StrUtils, Myc.TaskManager; -{ TDataFile } +{ TAuraDataFile } -constructor TDataFile.Create(const APath, ASymbol, AExtension: String; AYear, AMonth: Integer); +constructor TAuraDataFile.Create(const APath, ASymbol, AExtension: String; AYear, AMonth: Integer); begin FPath := APath; FSymbol := ASymbol; @@ -186,28 +195,28 @@ begin FMonth := AMonth; end; -function TDataFile.GetBaseFileName: string; +function TAuraDataFile.GetBaseFileName: string; begin Result := Format('%s_%.4d_%.2d', [FSymbol, FYear, FMonth]); end; -function TDataFile.GetFullFileName: string; +function TAuraDataFile.GetFullFileName: string; begin Result := TPath.Combine(FPath, GetBaseFileName + FExtension); end; -function TDataFile.GetIsValid: Boolean; +function TAuraDataFile.GetIsValid: Boolean; begin Result := (FSymbol <> '') and (FYear > 0); end; -function TDataFile.GetNextFile: TDataFile; +function TAuraDataFile.GetNextFile: TAuraDataFile; var nextMonth, nextYear: Integer; - nextFile: TDataFile; + nextFile: TAuraDataFile; begin if not IsValid then - exit(Default(TDataFile)); + exit(Default(TAuraDataFile)); nextMonth := FMonth + 1; nextYear := FYear; @@ -218,137 +227,19 @@ begin end; // Probe for zipped file first - nextFile := TDataFile.Create(FPath, FSymbol, '.tab_zip', nextYear, nextMonth); + nextFile := TAuraDataFile.Create(FPath, FSymbol, '.tab_zip', nextYear, nextMonth); if TFile.Exists(nextFile.GetFullFileName) then exit(nextFile); // Probe for uncompressed file - nextFile := TDataFile.Create(FPath, FSymbol, '.tab', nextYear, nextMonth); + nextFile := TAuraDataFile.Create(FPath, FSymbol, '.tab', nextYear, nextMonth); if TFile.Exists(nextFile.GetFullFileName) then exit(nextFile); // No next file found - Result := Default(TDataFile); + Result := Default(TAuraDataFile); end; -class function TDataFile.ParseFileName(const FileName: string): TDataFile; -var - fileNameNoPath, nameForParsing, baseName, ext, path, symbol: string; - year, month: Integer; - parts: TArray; -begin - Result := Default(TDataFile); - - path := TPath.GetDirectoryName(FileName); - fileNameNoPath := TPath.GetFileName(FileName); - nameForParsing := fileNameNoPath; - - ext := ''; - if nameForParsing.EndsWith('_zip', True) then - begin - ext := '_zip'; - nameForParsing := nameForParsing.Substring(0, nameForParsing.Length - 4); - end; - - var fileExt := TPath.GetExtension(nameForParsing) + ext; - baseName := TPath.GetFileNameWithoutExtension(nameForParsing); - - parts := baseName.Split(['_']); - - if Length(parts) < 3 then - exit; - - if not TryStrToInt(parts[High(parts)], month) then - exit; - - if (month < 1) or (month > 12) then - exit; - - if not TryStrToInt(parts[High(parts) - 1], year) then - exit; - - if year <= 0 then - exit; - - symbol := string.Join('_', Copy(parts, 0, Length(parts) - 2)); - - if symbol = '' then - exit; - - Result := TDataFile.Create(path, symbol, fileExt, year, month); -end; - -class function TDataFile.FindFirst(const DirectoryPath, Symbol: string): TDataFile; -var - fileNames: TArray; - currentFileName: string; - parsedFile: TDataFile; -begin - Result := Default(TDataFile); - - if not TDirectory.Exists(DirectoryPath) then - exit; - - fileNames := TDirectory.GetFiles(DirectoryPath); - for currentFileName in fileNames do - begin - parsedFile := TDataFile.ParseFileName(currentFileName); - if parsedFile.IsValid then - begin - if SameText(parsedFile.Symbol, Symbol) then - begin - // If it's the first match or older than the current result, update. - if (not Result.IsValid) or - (parsedFile.Year < Result.Year) or - ((parsedFile.Year = Result.Year) and (parsedFile.Month < Result.Month)) then - begin - Result := parsedFile; - end; - end; - end; - end; -end; - -class function TDataFile.FindOldestPerSymbol(const DirectoryPath: string): TArray; -var - oldestFilesPerSymbol: TDictionary; - fileNames: TArray; - currentFile: string; - dataFile: TDataFile; - trackedInfo: TDataFile; -begin - oldestFilesPerSymbol := TDictionary.Create; - try - if not TDirectory.Exists(DirectoryPath) then - exit(nil); - - fileNames := TDirectory.GetFiles(DirectoryPath); - for currentFile in fileNames do - begin - dataFile := TDataFile.ParseFileName(currentFile); - if dataFile.IsValid then - begin - if oldestFilesPerSymbol.TryGetValue(dataFile.Symbol, trackedInfo) then - begin - if (dataFile.Year < trackedInfo.Year) or ((dataFile.Year = trackedInfo.Year) and (dataFile.Month < trackedInfo.Month)) then - begin - oldestFilesPerSymbol.AddOrSetValue(dataFile.Symbol, dataFile); - end; - end - else - begin - oldestFilesPerSymbol.Add(dataFile.Symbol, dataFile); - end; - end; - end; - - Result := oldestFilesPerSymbol.Values.ToArray; - finally - oldestFilesPerSymbol.Free; - end; -end; - - { TAuraDataServer } constructor TAuraDataServer.Create(const APath: String); @@ -368,27 +259,17 @@ begin inherited Destroy; end; +procedure TAuraDataServer.AfterConstruction; +begin + inherited; + UpdateSymbols; +end; + procedure TAuraDataServer.ClearCache; begin FCachedFiles.Clear; end; -function TAuraDataServer.EnumerateSymbols: TArray; -var - dataFiles: TArray; - i: Integer; -begin - dataFiles := TDataFile.FindOldestPerSymbol(FPath); - SetLength(Result, Length(dataFiles)); - for i := 0 to High(dataFiles) do - Result[i] := dataFiles[i].Symbol; -end; - -function TAuraDataServer.GetPath: String; -begin - Result := FPath; -end; - function TAuraDataServer.DoLoad(const FileName: string): TFuture>>; begin Result := TFuture>>.Null; @@ -401,9 +282,7 @@ begin begin Result := TFuture - .Construct( - TLatch.Enqueue(FLoadGate), - function: TBytes begin Result := TFile.ReadAllBytes(capFileName); end) + .Construct(FLoadGate.Enqueue, function: TBytes begin Result := TFile.ReadAllBytes(capFileName); end) .Chain>>( function(bytes: TBytes): TArray> begin @@ -420,7 +299,7 @@ begin begin Result := TFuture>>.Construct( - TLatch.Enqueue(FLoadGate), + FLoadGate.Enqueue, function: TArray> begin if TFile.Exists(capFileName) then @@ -442,81 +321,82 @@ begin end; end; -function TAuraDataServer.LoadDataFile(const DataFile: TDataFile): TFuture>>; +procedure TAuraDataServer.UpdateSymbols; +begin + FSymbols := + TFuture>.Construct( + FSymbols.Done, + function: TDictionary + var + fileNames: TArray; + currentFile: string; + dataFile: TAuraDataFile; + trackedInfo: TAuraDataFile; + begin + Result := TDictionary.Create; + if not TDirectory.Exists(FPath) then + exit; + + fileNames := TDirectory.GetFiles(FPath); + for currentFile in fileNames do + begin + dataFile := ParseFileName(currentFile); + if dataFile.IsValid then + begin + if Result.TryGetValue(dataFile.Symbol, trackedInfo) then + begin + if (dataFile.Year < trackedInfo.Year) + or ((dataFile.Year = trackedInfo.Year) and (dataFile.Month < trackedInfo.Month)) then + begin + Result.AddOrSetValue(dataFile.Symbol, dataFile); + end; + end + else + begin + Result.Add(dataFile.Symbol, dataFile); + end; + end; + end; + end + ); + + FSymbols.Manage; +end; + +function TAuraDataServer.EnumerateSymbols: TFuture>; +var + dataFiles: TArray; + i: Integer; +begin + Result := + FSymbols.Chain>( + function(Symbols: TDictionary): TArray begin Result := Symbols.Keys.ToArray; end + ); +end; + +function TAuraDataServer.FindFirstFile(const Symbol: string): TFuture; +begin + var symName := Symbol; + Result := + FSymbols.Chain( + function(Symbols: TDictionary): TAuraDataFile + begin + if not Symbols.TryGetValue(symName, Result) then + Result := Default(TAuraDataFile); + end + ); +end; + +function TAuraDataServer.GetPath: String; +begin + Result := FPath; +end; + +function TAuraDataServer.LoadDataFile(const DataFile: TAuraDataFile): TFuture>>; begin Result := FCachedFiles.GetOrAdd(DataFile.GetFullFileName); end; -class function TAuraDataServer.ReadCompressedData(const InputStream: TStream): TArray>; -var - decompressionStream: TStream; - localHeader: TZipHeader; - entryIndex, i: Integer; - zipFileInstance: TZipFile; -begin - SetLength(Result, 0); - decompressionStream := nil; - zipFileInstance := nil; - try - InputStream.Position := 0; - zipFileInstance := TZipFile.Create; - zipFileInstance.Open(InputStream, TZipMode.zmRead); - - if zipFileInstance.FileCount = 0 then - exit; - - entryIndex := -1; - for i := 0 to zipFileInstance.FileCount - 1 do - if zipFileInstance.FileNames[i].EndsWith('.tab', True) then - begin - entryIndex := i; - break; - end; - - if entryIndex = -1 then - entryIndex := 0; - - zipFileInstance.Read(entryIndex, decompressionStream, localHeader); - if not Assigned(decompressionStream) then - exit; - Result := ReadUncompressedData(decompressionStream); - finally - decompressionStream.Free; - zipFileInstance.Free; - end; -end; - -class function TAuraDataServer.ReadUncompressedData(const InputStream: TStream): TArray>; -type - TFileRecord = packed record - TimeStamp: TDateTime; - Data: T; - end; -var - fileSize: Int64; - recordCount, bytesRead: Integer; - rec: TFileRecord; -begin - SetLength(Result, 0); - 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 - begin - SetLength(Result, recordCount); - for var i := 0 to High(Result) do - begin - 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); - end; - end; -end; - { TAuraFileStream } constructor TAuraFileStream.Create(ADataServer: TAuraDataServer; const ASymbol: String); @@ -535,18 +415,16 @@ end; procedure TAuraFileStream.AfterConstruction; var - firstFile: TFuture; - serverPath: string; + firstFile: TFuture; begin inherited; // We need the path from the server instance to find the file. - serverPath := (FDataServer as TAuraDataServer).Path; - firstFile := TFuture.Construct(function: TDataFile begin Result := TDataFile.FindFirst(serverPath, FSymbol); end); + firstFile := FDataServer.FindFirstFile(FSymbol); FCurrent := firstFile.Chain( - function(const FileInfo: TDataFile): TDataStream + function(const FileInfo: TAuraDataFile): TDataStream begin if FileInfo.IsValid then begin @@ -638,7 +516,7 @@ begin FCurrent.Chain( function(const Prev: TDataStream): TDataStream var - nextFileInfo: TDataFile; + nextFileInfo: TAuraDataFile; begin if Prev.FileInfo.IsValid then begin @@ -661,16 +539,16 @@ begin Result := TAuraFileStream.Create(Self, Symbol); end; -function TAuraTABFileServer.LoadDataSeries(const InitialFile: TDataFile): TFuture>>; +function TAuraTABFileServer.LoadDataSeries(const InitialFile: TAuraDataFile): TFuture>>; var loadedState: TState; loadedFiles: TArray>>>; liveData: TFuture>>; - tabFiles: TList; - liveFile: TDataFile; - currentFileInfo: TDataFile; + tabFiles: TList; + liveFile: TAuraDataFile; + currentFileInfo: TAuraDataFile; begin - tabFiles := TList.Create; + tabFiles := TList.Create; try currentFileInfo := InitialFile; while currentFileInfo.IsValid do @@ -679,14 +557,14 @@ begin currentFileInfo := currentFileInfo.GetNextFile; end; - liveFile := Default(TDataFile); + liveFile := Default(TAuraDataFile); if tabFiles.Count > 0 then begin var lastTabFile := tabFiles.Last; var liveFileBaseName := Format('%s_%d_%02d.tab-live', [lastTabFile.Symbol, lastTabFile.Year, lastTabFile.Month]); var potentialLivePath := TPath.Combine(lastTabFile.Path, liveFileBaseName); if TFile.Exists(potentialLivePath) then - liveFile := TDataFile.ParseFileName(potentialLivePath); + liveFile := ParseFileName(potentialLivePath); end; var loadedFileList := TList>>>.Create; @@ -745,4 +623,121 @@ begin ); end; +function TAuraTABFileServer.ParseFileName(const FileName: string): TAuraDataFile; +var + fileNameNoPath, nameForParsing, baseName, ext, path, symbol: string; + year, month: Integer; + parts: TArray; +begin + Result := Default(TAuraDataFile); + + path := TPath.GetDirectoryName(FileName); + fileNameNoPath := TPath.GetFileName(FileName); + nameForParsing := fileNameNoPath; + + ext := ''; + if nameForParsing.EndsWith('_zip', True) then + begin + ext := '_zip'; + nameForParsing := nameForParsing.Substring(0, nameForParsing.Length - 4); + end; + + var fileExt := TPath.GetExtension(nameForParsing) + ext; + baseName := TPath.GetFileNameWithoutExtension(nameForParsing); + + parts := baseName.Split(['_']); + + if Length(parts) < 3 then + exit; + + if not TryStrToInt(parts[High(parts)], month) then + exit; + + if (month < 1) or (month > 12) then + exit; + + if not TryStrToInt(parts[High(parts) - 1], year) then + exit; + + if year <= 0 then + exit; + + symbol := string.Join('_', Copy(parts, 0, Length(parts) - 2)); + + if symbol = '' then + exit; + + Result := TAuraDataFile.Create(path, symbol, fileExt, year, month); +end; + +class function TAuraTABFileServer.ReadCompressedData(const InputStream: TStream): TArray>; +var + decompressionStream: TStream; + localHeader: TZipHeader; + entryIndex, i: Integer; + zipFileInstance: TZipFile; +begin + SetLength(Result, 0); + decompressionStream := nil; + zipFileInstance := nil; + try + InputStream.Position := 0; + zipFileInstance := TZipFile.Create; + zipFileInstance.Open(InputStream, TZipMode.zmRead); + + if zipFileInstance.FileCount = 0 then + exit; + + entryIndex := -1; + for i := 0 to zipFileInstance.FileCount - 1 do + if zipFileInstance.FileNames[i].EndsWith('.tab', True) then + begin + entryIndex := i; + break; + end; + + if entryIndex = -1 then + entryIndex := 0; + + zipFileInstance.Read(entryIndex, decompressionStream, localHeader); + if not Assigned(decompressionStream) then + exit; + Result := ReadUncompressedData(decompressionStream); + finally + decompressionStream.Free; + zipFileInstance.Free; + end; +end; + +class function TAuraTABFileServer.ReadUncompressedData(const InputStream: TStream): TArray>; +type + TFileRecord = packed record + TimeStamp: TDateTime; + Data: TAskBidItem; + end; +var + fileSize: Int64; + recordCount, bytesRead: Integer; + rec: TFileRecord; +begin + SetLength(Result, 0); + 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 + begin + SetLength(Result, recordCount); + for var i := 0 to High(Result) do + begin + 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); + end; + end; +end; + end.