diff --git a/ChartDisplay/ChartDisplayMain.pas b/ChartDisplay/ChartDisplayMain.pas index 5e41630..c71f1c9 100644 --- a/ChartDisplay/ChartDisplayMain.pas +++ b/ChartDisplay/ChartDisplayMain.pas @@ -68,7 +68,7 @@ type procedure PaintBoxMouseLeave(Sender: TObject); procedure SymbolComboBoxChange(Sender: TObject); // Event handler for symbol selection change private - FDataServer: IDataServer; + FDataServer: IAuraDataServer; FActiveOHLCCache: TOHLC; FCandleWidthInPixels: Integer; FCandleSpacing: Integer; @@ -87,7 +87,7 @@ type FSymbolFiles: TArray; // Stores full paths from FindOldestFilesPerSymbol procedure PopulateTimeframeComboBox; - procedure PopulateSymbolComboBox; + procedure SetupDataServer; procedure EnsureCacheForCurrentTimeframe(ARebuildIfPresent: Boolean = False); procedure ClearAllCachesInForm; procedure DrawChart(ACanvas: TCanvas; ARect: TRect); @@ -260,8 +260,6 @@ var BuilderNCaptionKey, BuilderVCaptionKey: string; // These will be the unique keys BuilderNDescription, BuilderVDescription: string; begin - FDataServer := TAskBidServer.Create; - IsMemoryLow := function: Boolean var @@ -280,6 +278,9 @@ begin end; end; + // Assuming we have a valid default value in the Path-Edit: + SetupDataServer; + FCandleWidthInPixels := InitialCandleWidth; FCandleSpacing := InitialCandleSpacing; FDisplayStartIndex := 0; @@ -323,10 +324,6 @@ begin FMouseCurrentlyInChartArea := False; PaintBox.OnMouseLeave := PaintBoxMouseLeave; FBufferBitmap := TBitmap.Create; - SetLength(FSymbolFiles, 0); // Initialize FSymbolFiles array - - // Assuming we have a valid default value in the Path-Edit: - PopulateSymbolComboBox; end; procedure TChartForm.FormDestroy(Sender: TObject); @@ -597,7 +594,7 @@ begin begin DataPathEdit.Text := selectedDirectory; // Update the edit box with the selected directory Memo.Lines.Add(FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + ' - Data directory selected: ' + selectedDirectory); - PopulateSymbolComboBox; + SetupDataServer; end else begin @@ -1475,16 +1472,18 @@ begin Memo.Lines.Add('GoToCurrent: Already showing the latest candles.'); end; -procedure TChartForm.PopulateSymbolComboBox; +procedure TChartForm.SetupDataServer; var fileName: string; pathValue, symbolValue: string; yearValue, monthValue: Integer; i: Integer; begin + FDataServer := TAuraTABFileServer.Create( DataPathEdit.Text ); + var currTxt := SymbolComboBox.Text; - FSymbolFiles := FindOldestFilesPerSymbol(DataPathEdit.Text); // Find all relevant symbol files [cite: 238] + FSymbolFiles := FDataServer.EnumerateAssetFiles; SymbolComboBox.Items.Clear; SymbolComboBox.ClearSelection; // Ensure no previous selection remains SymbolComboBox.Text := ''; @@ -1502,8 +1501,7 @@ begin for i := Low(FSymbolFiles) to High(FSymbolFiles) do begin fileName := FSymbolFiles[i]; - // TryParseFileName extracts symbol from the filename. [cite: 237] - if TryParseFileName(fileName, pathValue, symbolValue, yearValue, monthValue) then + if TAuraTABFileServer.TryParseFileName(fileName, pathValue, symbolValue, yearValue, monthValue) then begin SymbolComboBox.Items.AddObject( Format('%s %.2d-%.4d', [symbolValue, monthValue, yearValue]), diff --git a/common/Myc.ChartDataController.pas b/common/Myc.ChartDataController.pas index eeb878d..e46c195 100644 --- a/common/Myc.ChartDataController.pas +++ b/common/Myc.ChartDataController.pas @@ -3,15 +3,20 @@ unit Myc.ChartDataController; interface uses - System.SysUtils, System.Classes, System.Generics.Collections, System.StrUtils, + System.SysUtils, + System.Classes, + System.Generics.Collections, + System.StrUtils, Myc.Futures, - Myc.Trade.DataPoint, Myc.Trade.DataStream, Myc.OHLCCache; + Myc.Trade.DataPoint, + Myc.Trade.DataStream, + Myc.OHLCCache; type TTimeframeSetting = record - Caption: string; // Unique key identifier (e.g., "M1", "Auto", "Every 20 Ticks") + Caption: string; // Unique key identifier (e.g., "M1", "Auto", "Every 20 Ticks") Description: string; // User-friendly description for UI - Seconds: Int64; // TF_AUTO_AGGREGATION, TF_TICK_BY_TICK, TF_GENERIC_CALLBACK, or >0 + Seconds: Int64; // TF_AUTO_AGGREGATION, TF_TICK_BY_TICK, TF_GENERIC_CALLBACK, or >0 end; TChartDataController = class @@ -21,22 +26,24 @@ type FTimeframeSettingsArray: TArray; // Now includes registered generic builders FMemoLog: TStrings; FGenericBuilders: TDictionary; // Keyed by TTimeframeSetting.Caption - FDataServer: IDataServer; + FDataServer: IAuraDataServer; procedure InitializeBaseTimeframeSettings; // Renamed for clarity function GetGenericBuilder(const ABuilderCaption: string): IGenericCandleBuilder; public - constructor Create(const ADataServer: IDataServer; AMemoTarget: TStrings); + constructor Create(const ADataServer: IAuraDataServer; AMemoTarget: TStrings); destructor Destroy; override; procedure LoadTickDataSeries(const AFileName: string); // GetOrBuildCache now uses a single ASelectedTimeframeCaption for all types of timeframes. - function GetOrBuildCache( const ASelectedTimeframeCaption: string; // e.g., "M1", "Auto", "Every 20 Ticks" - AAutoAggCandleWidth: Integer; - AAutoAggCandleSpacing: Integer; - AAutoAggPaintBoxClientWidth: Integer; - AForceRebuild: Boolean): TOHLC; + function GetOrBuildCache( + const ASelectedTimeframeCaption: string; // e.g., "M1", "Auto", "Every 20 Ticks" + AAutoAggCandleWidth: Integer; + AAutoAggCandleSpacing: Integer; + AAutoAggPaintBoxClientWidth: Integer; + AForceRebuild: Boolean + ): TOHLC; procedure ClearAllDataAndCaches; function GetTickDataCount: Integer; @@ -45,9 +52,7 @@ type function HasData: Boolean; // ABuilderCaption is the unique key used for FTimeframeSettingsArray.Caption and FGenericBuilders key. - procedure RegisterGenericBuilder(const ABuilderCaption: string; - const ABuilderDescription: string; - ABuilder: IGenericCandleBuilder); + procedure RegisterGenericBuilder(const ABuilderCaption: string; const ABuilderDescription: string; ABuilder: IGenericCandleBuilder); // GetGenericBuilderCaptions is removed. Use GetAvailableTimeframes. function GetAvailableTimeframes: TArray; end; @@ -62,12 +67,12 @@ const { TChartDataController } -constructor TChartDataController.Create(const ADataServer: IDataServer; AMemoTarget: TStrings); +constructor TChartDataController.Create(const ADataServer: IAuraDataServer; AMemoTarget: TStrings); var I: Integer; // Standard loop variable begin inherited Create; - FDataServer:=ADataServer; + FDataServer := ADataServer; FMemoLog := AMemoTarget; FGenericBuilders := TDictionary.Create; @@ -105,20 +110,59 @@ begin InitialSettings := TList.Create; try // Define standard, non-generic timeframes - ts.Caption := 'Auto'; ts.Description := 'Auto (Screen Fit)'; ts.Seconds := TF_AUTO_AGGREGATION; InitialSettings.Add(ts); - ts.Caption := 'Tick'; ts.Description := 'Tick-by-Tick'; ts.Seconds := TF_TICK_BY_TICK; InitialSettings.Add(ts); + ts.Caption := 'Auto'; + ts.Description := 'Auto (Screen Fit)'; + ts.Seconds := TF_AUTO_AGGREGATION; + InitialSettings.Add(ts); + ts.Caption := 'Tick'; + ts.Description := 'Tick-by-Tick'; + ts.Seconds := TF_TICK_BY_TICK; + InitialSettings.Add(ts); // The old "Generic Rule" placeholder is removed. Generic rules are added via RegisterGenericBuilder. - ts.Caption := 'M1'; ts.Description := '1 Minute (M1)'; ts.Seconds := 60; InitialSettings.Add(ts); - ts.Caption := 'M5'; ts.Description := '5 Minutes (M5)'; ts.Seconds := 5 * 60; InitialSettings.Add(ts); - ts.Caption := 'M10'; ts.Description := '10 Minutes (M10)'; ts.Seconds := 10 * 60; InitialSettings.Add(ts); - ts.Caption := 'M15'; ts.Description := '15 Minutes (M15)'; ts.Seconds := 15 * 60; InitialSettings.Add(ts); - ts.Caption := 'M30'; ts.Description := '30 Minutes (M30)'; ts.Seconds := 30 * 60; InitialSettings.Add(ts); - ts.Caption := 'H1'; ts.Description := '1 Hour (H1)'; ts.Seconds := 60 * 60; InitialSettings.Add(ts); - ts.Caption := 'H4'; ts.Description := '4 Hours (H4)'; ts.Seconds := 4 * 60 * 60; InitialSettings.Add(ts); - ts.Caption := 'D1'; ts.Description := '1 Day (D1)'; ts.Seconds := dcSecsPerDayConst; InitialSettings.Add(ts); - ts.Caption := 'W1'; ts.Description := '1 Week (W1)'; ts.Seconds := 7 * dcSecsPerDayConst; InitialSettings.Add(ts); - ts.Caption := 'MN1'; ts.Description := '1 Month (MN1)'; ts.Seconds := 30 * dcSecsPerDayConst; InitialSettings.Add(ts); - ts.Caption := 'Y1'; ts.Description := '1 Year (Y1)'; ts.Seconds := 365 * dcSecsPerDayConst; InitialSettings.Add(ts); + ts.Caption := 'M1'; + ts.Description := '1 Minute (M1)'; + ts.Seconds := 60; + InitialSettings.Add(ts); + ts.Caption := 'M5'; + ts.Description := '5 Minutes (M5)'; + ts.Seconds := 5 * 60; + InitialSettings.Add(ts); + ts.Caption := 'M10'; + ts.Description := '10 Minutes (M10)'; + ts.Seconds := 10 * 60; + InitialSettings.Add(ts); + ts.Caption := 'M15'; + ts.Description := '15 Minutes (M15)'; + ts.Seconds := 15 * 60; + InitialSettings.Add(ts); + ts.Caption := 'M30'; + ts.Description := '30 Minutes (M30)'; + ts.Seconds := 30 * 60; + InitialSettings.Add(ts); + ts.Caption := 'H1'; + ts.Description := '1 Hour (H1)'; + ts.Seconds := 60 * 60; + InitialSettings.Add(ts); + ts.Caption := 'H4'; + ts.Description := '4 Hours (H4)'; + ts.Seconds := 4 * 60 * 60; + InitialSettings.Add(ts); + ts.Caption := 'D1'; + ts.Description := '1 Day (D1)'; + ts.Seconds := dcSecsPerDayConst; + InitialSettings.Add(ts); + ts.Caption := 'W1'; + ts.Description := '1 Week (W1)'; + ts.Seconds := 7 * dcSecsPerDayConst; + InitialSettings.Add(ts); + ts.Caption := 'MN1'; + ts.Description := '1 Month (MN1)'; + ts.Seconds := 30 * dcSecsPerDayConst; + InitialSettings.Add(ts); + ts.Caption := 'Y1'; + ts.Description := '1 Year (Y1)'; + ts.Seconds := 365 * dcSecsPerDayConst; + InitialSettings.Add(ts); FTimeframeSettingsArray := InitialSettings.ToArray; finally InitialSettings.Free; @@ -130,9 +174,11 @@ begin Result := Copy(FTimeframeSettingsArray); // Returns a copy of the current (potentially dynamic) list end; -procedure TChartDataController.RegisterGenericBuilder(const ABuilderCaption: string; - const ABuilderDescription: string; - ABuilder: IGenericCandleBuilder); +procedure TChartDataController.RegisterGenericBuilder( + const ABuilderCaption: string; + const ABuilderDescription: string; + ABuilder: IGenericCandleBuilder +); var NewTimeframeSetting: TTimeframeSetting; I: Integer; @@ -150,7 +196,12 @@ begin if CompareText(FTimeframeSettingsArray[I].Caption, ABuilderCaption) = 0 then begin if FMemoLog <> nil then - FMemoLog.Add(Format('DataController: Cannot register generic builder. A timeframe (base or generic) with caption "%s" already exists.', [ABuilderCaption])); + FMemoLog.Add( + Format( + 'DataController: Cannot register generic builder. A timeframe (base or generic) with caption "%s" already exists.', + [ABuilderCaption] + ) + ); Exit; end; end; @@ -173,7 +224,12 @@ begin // for an existing generic timeframe caption that was somehow registered differently. // For safety, let's log and replace if this happens. if FMemoLog <> nil then - FMemoLog.Add(Format('DataController: Warning - Generic builder key "%s" already in dictionary, but not found as a TimeframeSetting caption. Replacing builder interface.', [ABuilderCaption])); + FMemoLog.Add( + Format( + 'DataController: Warning - Generic builder key "%s" already in dictionary, but not found as a TimeframeSetting caption. Replacing builder interface.', + [ABuilderCaption] + ) + ); FGenericBuilders.Remove(ABuilderCaption); FGenericBuilders.Add(ABuilderCaption, ABuilder); end @@ -194,7 +250,12 @@ begin FOHLCCacheList.Add(nil); // Use TList.Add method if FMemoLog <> nil then - FMemoLog.Add(Format('DataController: Registered new generic builder timeframe: Caption="%s", Description="%s".', [ABuilderCaption, ABuilderDescription])); + FMemoLog.Add( + Format( + 'DataController: Registered new generic builder timeframe: Caption="%s", Description="%s".', + [ABuilderCaption, ABuilderDescription] + ) + ); end; end; @@ -214,8 +275,7 @@ begin try FTickDataArray := FDataServer.LoadDataSeries(AFileName).WaitFor(); if FMemoLog <> nil then - FMemoLog.Add(Format('DataController: Loading tick records from series: %s', - [AFileName])); + FMemoLog.Add(Format('DataController: Loading tick records from series: %s', [AFileName])); except on E: Exception do begin @@ -226,9 +286,11 @@ begin end; // ASelectedTimeframeCaption is the UNIQUE key (e.g. "M1", "Auto", or "Every 20 Ticks") -function TChartDataController.GetOrBuildCache(const ASelectedTimeframeCaption: string; +function TChartDataController.GetOrBuildCache( + const ASelectedTimeframeCaption: string; AAutoAggCandleWidth, AAutoAggCandleSpacing, AAutoAggPaintBoxClientWidth: Integer; - AForceRebuild: Boolean): TOHLC; + AForceRebuild: Boolean +): TOHLC; var Index: Integer; CacheNeedsRebuild: Boolean; @@ -269,28 +331,48 @@ begin FOHLCCacheList[Index] := CacheAtIndex; CacheNeedsRebuild := True; if FMemoLog <> nil then - FMemoLog.Add(Format('DataController: Cache for [%s] (Desc: "%s") is nil. Creating & Building...', - [CurrentTimeframeSetting.Caption, CurrentTimeframeSetting.Description])); + FMemoLog.Add( + Format( + 'DataController: Cache for [%s] (Desc: "%s") is nil. Creating & Building...', + [CurrentTimeframeSetting.Caption, CurrentTimeframeSetting.Description] + ) + ); end else if not CacheAtIndex.IsValid then begin CacheNeedsRebuild := True; if FMemoLog <> nil then - FMemoLog.Add(Format('DataController: Cache for [%s] (Desc: "%s") exists but is invalid. Rebuilding...', - [CurrentTimeframeSetting.Caption, CurrentTimeframeSetting.Description])); + FMemoLog.Add( + Format( + 'DataController: Cache for [%s] (Desc: "%s") exists but is invalid. Rebuilding...', + [CurrentTimeframeSetting.Caption, CurrentTimeframeSetting.Description] + ) + ); end else if CacheAtIndex.AggregationTimeframeSeconds <> CurrentTimeframeSetting.Seconds then begin if FMemoLog <> nil then - FMemoLog.Add(Format('DataController: Aggregation timeframe (seconds) mismatch for [%s] (Desc: "%s"). Cache has %ds, requested %ds. Forcing rebuild.', - [CurrentTimeframeSetting.Caption, CurrentTimeframeSetting.Description, CacheAtIndex.AggregationTimeframeSeconds, CurrentTimeframeSetting.Seconds])); + FMemoLog.Add( + Format( + 'DataController: Aggregation timeframe (seconds) mismatch for [%s] (Desc: "%s"). Cache has %ds, requested %ds. Forcing rebuild.', + [ + CurrentTimeframeSetting.Caption, + CurrentTimeframeSetting.Description, + CacheAtIndex.AggregationTimeframeSeconds, + CurrentTimeframeSetting.Seconds + ] + ) + ); CacheNeedsRebuild := True; end; // If it's a generic callback timeframe, an additional check for builder matching might be needed if not forced. // The TOHLC's ApproxTimePerCandleText stores "(BuilderCaption)" for generic builds. // ASelectedTimeframeCaption *is* the builder caption for generic rules. - if (CurrentTimeframeSetting.Seconds = TF_GENERIC_CALLBACK) and (not CacheNeedsRebuild) and Assigned(CacheAtIndex) and CacheAtIndex.IsValid then + if (CurrentTimeframeSetting.Seconds = TF_GENERIC_CALLBACK) + and (not CacheNeedsRebuild) + and Assigned(CacheAtIndex) + and CacheAtIndex.IsValid then begin // For generic, CurrentTimeframeSetting.Caption is the Builder's caption. // We check if the existing cache was built with this same builder. @@ -301,8 +383,17 @@ begin if CacheAtIndex.ApproxTimePerCandleText <> ExpectedCacheBuilderInfo then begin if FMemoLog <> nil then - FMemoLog.Add(Format('DataController: Generic builder changed for timeframe key [%s] (Desc: "%s"). Cache has info "%s", current builder key implies info "%s". Forcing rebuild.', - [CurrentTimeframeSetting.Caption, CurrentTimeframeSetting.Description, CacheAtIndex.ApproxTimePerCandleText, ExpectedCacheBuilderInfo])); + FMemoLog.Add( + Format( + 'DataController: Generic builder changed for timeframe key [%s] (Desc: "%s"). Cache has info "%s", current builder key implies info "%s". Forcing rebuild.', + [ + CurrentTimeframeSetting.Caption, + CurrentTimeframeSetting.Description, + CacheAtIndex.ApproxTimePerCandleText, + ExpectedCacheBuilderInfo + ] + ) + ); CacheNeedsRebuild := True; end; end; @@ -312,8 +403,12 @@ begin if HasData then begin if FMemoLog <> nil then - FMemoLog.Add(Format('DataController: Building cache for [%s] (Desc: "%s")...', - [CurrentTimeframeSetting.Caption, CurrentTimeframeSetting.Description])); + FMemoLog.Add( + Format( + 'DataController: Building cache for [%s] (Desc: "%s")...', + [CurrentTimeframeSetting.Caption, CurrentTimeframeSetting.Description] + ) + ); if CurrentTimeframeSetting.Seconds = TF_GENERIC_CALLBACK then begin @@ -322,8 +417,12 @@ begin if not Assigned(LGenericBuilder) then begin if FMemoLog <> nil then - FMemoLog.Add(Format('DataController: Cannot build cache for generic timeframe [%s] (Desc: "%s"). Builder with this caption not found in dictionary.', - [CurrentTimeframeSetting.Caption, CurrentTimeframeSetting.Description])); + FMemoLog.Add( + Format( + 'DataController: Cannot build cache for generic timeframe [%s] (Desc: "%s"). Builder with this caption not found in dictionary.', + [CurrentTimeframeSetting.Caption, CurrentTimeframeSetting.Description] + ) + ); CacheAtIndex.ClearCache(); Result := CacheAtIndex; Exit; @@ -350,8 +449,12 @@ begin else begin if FMemoLog <> nil then - FMemoLog.Add(Format('DataController: Using existing valid cache for [%s] (Desc: "%s")', - [CurrentTimeframeSetting.Caption, CurrentTimeframeSetting.Description])); + FMemoLog.Add( + Format( + 'DataController: Using existing valid cache for [%s] (Desc: "%s")', + [CurrentTimeframeSetting.Caption, CurrentTimeframeSetting.Description] + ) + ); end; Result := CacheAtIndex; end; @@ -394,7 +497,9 @@ begin // The current behavior is that builder registrations and the full list of timeframe definitions persist. if FMemoLog <> nil then // This is the line (approx. 376) the user reported - FMemoLog.Add('DataController: Instance data and OHLC caches cleared. Registered builders and timeframe definitions (including custom ones) remain.'); + FMemoLog.Add( + 'DataController: Instance data and OHLC caches cleared. Registered builders and timeframe definitions (including custom ones) remain.' + ); end; function TChartDataController.GetTickDataCount: Integer; @@ -414,7 +519,9 @@ begin else begin if FMemoLog <> nil then - FMemoLog.Add(Format('DataController: Attempted to access invalid tick data index %d. Count is %d.', [AIndex, Length(FTickDataArray)])); + FMemoLog.Add( + Format('DataController: Attempted to access invalid tick data index %d. Count is %d.', [AIndex, Length(FTickDataArray)]) + ); Result := Default(TDataRecord); end; end;