Refactoring DataStream

This commit is contained in:
Michael Schimmel
2025-06-07 16:42:12 +02:00
parent 94f94e3a27
commit d53742fd12
3 changed files with 43 additions and 35 deletions
+19 -13
View File
@@ -32,8 +32,8 @@ uses
System.UITypes, System.UITypes,
System.Types, System.Types,
Vcl.FileCtrl, // Added for SelectDirectory Vcl.FileCtrl, // Added for SelectDirectory
Myc.Trade.DataSeries, // For TTickData, FindOldestFilesPerSymbol, TryParseFileName [cite: 231, 237, 238]
Myc.OHLCCache, // For TOHLC, IGenericCandleBuilder, TF_ constants Myc.OHLCCache, // For TOHLC, IGenericCandleBuilder, TF_ constants
Myc.Trade.DataPoint, Myc.Trade.DataStream,
Myc.ChartDataController, // Uses the updated DataController Myc.ChartDataController, // Uses the updated DataController
Vcl.Buttons; Vcl.Buttons;
@@ -68,6 +68,7 @@ type
procedure PaintBoxMouseLeave(Sender: TObject); procedure PaintBoxMouseLeave(Sender: TObject);
procedure SymbolComboBoxChange(Sender: TObject); // Event handler for symbol selection change procedure SymbolComboBoxChange(Sender: TObject); // Event handler for symbol selection change
private private
FDataServer: IDataServer<TAskBidItem>;
FActiveOHLCCache: TOHLC; FActiveOHLCCache: TOHLC;
FCandleWidthInPixels: Integer; FCandleWidthInPixels: Integer;
FCandleSpacing: Integer; FCandleSpacing: Integer;
@@ -116,8 +117,8 @@ type
FDescriptionText: string; // User-friendly description FDescriptionText: string; // User-friendly description
public public
constructor Create(ATickCount: Integer); constructor Create(ATickCount: Integer);
function Init(const ADataSet: TArray<TAskBid.TTick>): Boolean; function Init(const ADataSet: TArray<TDataRecord<TAskBidItem>>): Boolean;
function IsNewBar(AIndex: Integer; const ATick: TAskBid.TTick): Boolean; function IsNewBar(AIndex: Integer; const ATick: TDataRecord<TAskBidItem>): Boolean;
function GetCaption: string; // Returns the Description for UI function GetCaption: string; // Returns the Description for UI
property Caption: string read GetCaption; // Interface property uses GetCaption property Caption: string read GetCaption; // Interface property uses GetCaption
end; end;
@@ -129,8 +130,8 @@ type
FDescriptionText: string; // User-friendly description FDescriptionText: string; // User-friendly description
public public
constructor Create(ATargetTickVolume: Integer); constructor Create(ATargetTickVolume: Integer);
function Init(const ADataSet: TArray<TAskBid.TTick>): Boolean; function Init(const ADataSet: TArray<TDataRecord<TAskBidItem>>): Boolean;
function IsNewBar(AIndex: Integer; const ATick: TAskBid.TTick): Boolean; function IsNewBar(AIndex: Integer; const ATick: TDataRecord<TAskBidItem>): Boolean;
function GetCaption: string; // Returns the Description for UI function GetCaption: string; // Returns the Description for UI
property Caption: string read GetCaption; // Interface property uses GetCaption property Caption: string read GetCaption; // Interface property uses GetCaption
end; end;
@@ -146,12 +147,12 @@ begin
FDescriptionText := Format('Every %d Ticks', [FTickCountForNewBar]); FDescriptionText := Format('Every %d Ticks', [FTickCountForNewBar]);
end; end;
function TEveryNTicksBuilder.Init(const ADataSet: TArray<TAskBid.TTick>): Boolean; function TEveryNTicksBuilder.Init(const ADataSet: TArray<TDataRecord<TAskBidItem>>): Boolean;
begin begin
Result := True; Result := True;
end; end;
function TEveryNTicksBuilder.IsNewBar(AIndex: Integer; const ATick: TAskBid.TTick): Boolean; function TEveryNTicksBuilder.IsNewBar(AIndex: Integer; const ATick: TDataRecord<TAskBidItem>): Boolean;
begin begin
Result := (AIndex mod FTickCountForNewBar = 0); Result := (AIndex mod FTickCountForNewBar = 0);
end; end;
@@ -172,13 +173,13 @@ begin
FCurrentTickInBarCount := 0; FCurrentTickInBarCount := 0;
end; end;
function TVolumeAccumulationBuilder.Init(const ADataSet: TArray<TAskBid.TTick>): Boolean; function TVolumeAccumulationBuilder.Init(const ADataSet: TArray<TDataRecord<TAskBidItem>>): Boolean;
begin begin
FCurrentTickInBarCount := 0; FCurrentTickInBarCount := 0;
Result := True; Result := True;
end; end;
function TVolumeAccumulationBuilder.IsNewBar(AIndex: Integer; const ATick: TAskBid.TTick): Boolean; function TVolumeAccumulationBuilder.IsNewBar(AIndex: Integer; const ATick: TDataRecord<TAskBidItem>): Boolean;
begin begin
Inc(FCurrentTickInBarCount); Inc(FCurrentTickInBarCount);
if FCurrentTickInBarCount >= FTargetTickVolumePerBar then if FCurrentTickInBarCount >= FTargetTickVolumePerBar then
@@ -259,6 +260,8 @@ var
BuilderNCaptionKey, BuilderVCaptionKey: string; // These will be the unique keys BuilderNCaptionKey, BuilderVCaptionKey: string; // These will be the unique keys
BuilderNDescription, BuilderVDescription: string; BuilderNDescription, BuilderVDescription: string;
begin begin
FDataServer := TAskBidServer.Create;
IsMemoryLow := IsMemoryLow :=
function: Boolean function: Boolean
var var
@@ -284,7 +287,7 @@ begin
FDragStartX := 0; FDragStartX := 0;
FDragStartDisplayIndex := 0; FDragStartDisplayIndex := 0;
FDataController := TChartDataController.Create(Memo.Lines); FDataController := TChartDataController.Create(FDataServer, Memo.Lines);
// Create and register generic builders // Create and register generic builders
BuilderN := TEveryNTicksBuilder.Create(DefaultEveryNTicksCount); BuilderN := TEveryNTicksBuilder.Create(DefaultEveryNTicksCount);
@@ -331,6 +334,9 @@ begin
FreeAndNil(FDataController); FreeAndNil(FDataController);
FreeAndNil(FBufferBitmap); FreeAndNil(FBufferBitmap);
FActiveOHLCCache := nil; FActiveOHLCCache := nil;
FDataServer.ClearCache;
FDataServer := nil;
end; end;
procedure TChartForm.ClearAllCachesInForm; procedure TChartForm.ClearAllCachesInForm;
@@ -752,8 +758,8 @@ begin
and (HoveredCandle.OriginalDataIndexEnd >= 0) and (HoveredCandle.OriginalDataIndexEnd >= 0)
and (HoveredCandle.OriginalDataIndexEnd < FDataController.GetTickDataLength) then // Use GetTickDataLength and (HoveredCandle.OriginalDataIndexEnd < FDataController.GetTickDataLength) then // Use GetTickDataLength
begin begin
UTCTimeStart := FDataController.GetTickDataItem(HoveredCandle.OriginalDataIndexStart).OADateTime; UTCTimeStart := FDataController.GetTickDataItem(HoveredCandle.OriginalDataIndexStart).TimeStamp;
UTCTimeEnd := FDataController.GetTickDataItem(HoveredCandle.OriginalDataIndexEnd).OADateTime; UTCTimeEnd := FDataController.GetTickDataItem(HoveredCandle.OriginalDataIndexEnd).TimeStamp;
LocalTimeStart := TTimeZone.Local.ToLocalTime(UTCTimeStart); LocalTimeStart := TTimeZone.Local.ToLocalTime(UTCTimeStart);
LocalTimeEnd := TTimeZone.Local.ToLocalTime(UTCTimeEnd); LocalTimeEnd := TTimeZone.Local.ToLocalTime(UTCTimeEnd);
TimeZoneStr := TTimeZone.Local.GetAbbreviation(LocalTimeStart); // Get abbreviation for the local time zone TimeZoneStr := TTimeZone.Local.GetAbbreviation(LocalTimeStart); // Get abbreviation for the local time zone
@@ -1296,7 +1302,7 @@ begin
if (CrosshairCandle.OriginalDataIndexStart >= 0) if (CrosshairCandle.OriginalDataIndexStart >= 0)
and (CrosshairCandle.OriginalDataIndexStart < FDataController.GetTickDataLength) then // Use GetTickDataLength and (CrosshairCandle.OriginalDataIndexStart < FDataController.GetTickDataLength) then // Use GetTickDataLength
begin begin
UTCTimeStart := FDataController.GetTickDataItem(CrosshairCandle.OriginalDataIndexStart).OADateTime; UTCTimeStart := FDataController.GetTickDataItem(CrosshairCandle.OriginalDataIndexStart).TimeStamp;
LocalTimeStart := TTimeZone.Local.ToLocalTime(UTCTimeStart); LocalTimeStart := TTimeZone.Local.ToLocalTime(UTCTimeStart);
TimeStr := FormatDateTime('hh:nn:ss', LocalTimeStart); // Display time part TimeStr := FormatDateTime('hh:nn:ss', LocalTimeStart); // Display time part
+10 -8
View File
@@ -5,7 +5,7 @@ interface
uses uses
System.SysUtils, System.Classes, System.Generics.Collections, System.StrUtils, System.SysUtils, System.Classes, System.Generics.Collections, System.StrUtils,
Myc.Futures, Myc.Futures,
Myc.Trade.DataSeries, Myc.OHLCCache; Myc.Trade.DataPoint, Myc.Trade.DataStream, Myc.OHLCCache;
type type
TTimeframeSetting = record TTimeframeSetting = record
@@ -16,16 +16,17 @@ type
TChartDataController = class TChartDataController = class
private private
FTickDataArray: TArray<TAskBid.TTick>; FTickDataArray: TArray<TDataRecord<TAskBidItem>>;
FOHLCCacheList: TList<TOHLC>; FOHLCCacheList: TList<TOHLC>;
FTimeframeSettingsArray: TArray<TTimeframeSetting>; // Now includes registered generic builders FTimeframeSettingsArray: TArray<TTimeframeSetting>; // Now includes registered generic builders
FMemoLog: TStrings; FMemoLog: TStrings;
FGenericBuilders: TDictionary<string, IGenericCandleBuilder>; // Keyed by TTimeframeSetting.Caption FGenericBuilders: TDictionary<string, IGenericCandleBuilder>; // Keyed by TTimeframeSetting.Caption
FDataServer: IDataServer<TAskBidItem>;
procedure InitializeBaseTimeframeSettings; // Renamed for clarity procedure InitializeBaseTimeframeSettings; // Renamed for clarity
function GetGenericBuilder(const ABuilderCaption: string): IGenericCandleBuilder; function GetGenericBuilder(const ABuilderCaption: string): IGenericCandleBuilder;
public public
constructor Create(AMemoTarget: TStrings); constructor Create(const ADataServer: IDataServer<TAskBidItem>; AMemoTarget: TStrings);
destructor Destroy; override; destructor Destroy; override;
procedure LoadTickDataSeries(const AFileName: string); procedure LoadTickDataSeries(const AFileName: string);
@@ -40,7 +41,7 @@ type
procedure ClearAllDataAndCaches; procedure ClearAllDataAndCaches;
function GetTickDataCount: Integer; function GetTickDataCount: Integer;
function GetTickDataLength: Integer; function GetTickDataLength: Integer;
function GetTickDataItem(AIndex: Integer): TAskBid.TTick; function GetTickDataItem(AIndex: Integer): TDataRecord<TAskBidItem>;
function HasData: Boolean; function HasData: Boolean;
// ABuilderCaption is the unique key used for FTimeframeSettingsArray.Caption and FGenericBuilders key. // ABuilderCaption is the unique key used for FTimeframeSettingsArray.Caption and FGenericBuilders key.
@@ -61,11 +62,12 @@ const
{ TChartDataController } { TChartDataController }
constructor TChartDataController.Create(AMemoTarget: TStrings); constructor TChartDataController.Create(const ADataServer: IDataServer<TAskBidItem>; AMemoTarget: TStrings);
var var
I: Integer; // Standard loop variable I: Integer; // Standard loop variable
begin begin
inherited Create; inherited Create;
FDataServer:=ADataServer;
FMemoLog := AMemoTarget; FMemoLog := AMemoTarget;
FGenericBuilders := TDictionary<string, IGenericCandleBuilder>.Create; FGenericBuilders := TDictionary<string, IGenericCandleBuilder>.Create;
@@ -210,7 +212,7 @@ procedure TChartDataController.LoadTickDataSeries(const AFileName: string);
begin begin
ClearAllDataAndCaches(); ClearAllDataAndCaches();
try try
FTickDataArray := TAskBid.LoadDataSeries(AFileName).WaitFor(); FTickDataArray := FDataServer.LoadDataSeries(AFileName).WaitFor();
if FMemoLog <> nil then if FMemoLog <> nil then
FMemoLog.Add(Format('DataController: Loading tick records from series: %s', FMemoLog.Add(Format('DataController: Loading tick records from series: %s',
[AFileName])); [AFileName]));
@@ -405,7 +407,7 @@ begin
Result := Length(FTickDataArray); Result := Length(FTickDataArray);
end; end;
function TChartDataController.GetTickDataItem(AIndex: Integer): TAskBid.TTick; function TChartDataController.GetTickDataItem(AIndex: Integer): TDataRecord<TAskBidItem>;
begin begin
if (AIndex >= 0) and (AIndex < Length(FTickDataArray)) then if (AIndex >= 0) and (AIndex < Length(FTickDataArray)) then
Result := FTickDataArray[AIndex] Result := FTickDataArray[AIndex]
@@ -413,7 +415,7 @@ begin
begin begin
if FMemoLog <> nil then 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(TAskBid.TTick); Result := Default(TDataRecord<TAskBidItem>);
end; end;
end; end;
+13 -13
View File
@@ -35,7 +35,7 @@ interface
uses uses
System.Classes, System.SysUtils, System.Math, System.Classes, System.SysUtils, System.Math,
System.Generics.Collections, System.DateUtils, System.Generics.Collections, System.DateUtils,
Myc.Trade.DataSeries; Myc.Trade.DataStream, Myc.Trade.DataPoint;
const const
TF_AUTO_AGGREGATION = 0; TF_AUTO_AGGREGATION = 0;
@@ -56,8 +56,8 @@ type
// Interface for generic candle building logic // Interface for generic candle building logic
IGenericCandleBuilder = interface IGenericCandleBuilder = interface
function Init(const ADataSet: TArray<TAskBid.TTick>): Boolean; // Changed DataSet to ADataSet for clarity function Init(const ADataSet: TArray<TDataRecord<TAskBidItem>>): Boolean; // Changed DataSet to ADataSet for clarity
function IsNewBar(AIndex: Integer; const ATick: TAskBid.TTick): Boolean; // Changed Idx to AIndex, Tick to ATick function IsNewBar(AIndex: Integer; const ATick: TDataRecord<TAskBidItem>): Boolean; // Changed Idx to AIndex, Tick to ATick
function GetCaption: string; // Method for the Caption property function GetCaption: string; // Method for the Caption property
property Caption: string read GetCaption; property Caption: string read GetCaption;
end; end;
@@ -80,7 +80,7 @@ type
public public
constructor Create; constructor Create;
procedure ClearCache; procedure ClearCache;
procedure Build(const ATickData: TArray<TAskBid.TTick>; procedure Build(const ATickData: TArray<TDataRecord<TAskBidItem>>;
ASelectedTimeframeSeconds: Int64; ASelectedTimeframeSeconds: Int64;
AAutoAggCandleWidth: Integer; AAutoAggCandleWidth: Integer;
AAutoAggCandleSpacing: Integer; AAutoAggCandleSpacing: Integer;
@@ -188,7 +188,7 @@ begin
Result := Length(FCachedCandles); Result := Length(FCachedCandles);
end; end;
procedure TOHLC.Build(const ATickData: TArray<TAskBid.TTick>; procedure TOHLC.Build(const ATickData: TArray<TDataRecord<TAskBidItem>>;
ASelectedTimeframeSeconds: Int64; ASelectedTimeframeSeconds: Int64;
AAutoAggCandleWidth: Integer; AAutoAggCandleSpacing: Integer; AAutoAggPaintBoxClientWidth: Integer; AAutoAggCandleWidth: Integer; AAutoAggCandleSpacing: Integer; AAutoAggPaintBoxClientWidth: Integer;
AMemoForLogging: TStrings; AGenericCandleBuilder: IGenericCandleBuilder = nil); // Parameter changed AMemoForLogging: TStrings; AGenericCandleBuilder: IGenericCandleBuilder = nil); // Parameter changed
@@ -200,7 +200,7 @@ var
CandleList: TList<TCachedCandle>; CandleList: TList<TCachedCandle>;
TempCandle: TCachedCandle; TempCandle: TCachedCandle;
LogTimePerCandleStr: string; LogTimePerCandleStr: string;
CurrentTick: TAskBid.TTick; CurrentTick: TDataRecord<TAskBidItem>;
begin begin
if AMemoForLogging <> nil then if AMemoForLogging <> nil then
AMemoForLogging.Add(FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + Format(' - TOHLC.Build: TF Secs: %d...', AMemoForLogging.Add(FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + Format(' - TOHLC.Build: TF Secs: %d...',
@@ -353,8 +353,8 @@ begin
SetLength(FCachedCandles, NumCandlesToCacheAuto); SetLength(FCachedCandles, NumCandlesToCacheAuto);
if (NumDataPoints > 1) and (NumCandlesToCacheAuto > 0) then if (NumDataPoints > 1) and (NumCandlesToCacheAuto > 0) then
begin begin
var FirstTickTimeVal_A := ATickData[0].OADateTime; var FirstTickTimeVal_A := ATickData[0].TimeStamp;
var LastTickTimeVal_A := ATickData[High(ATickData)].OADateTime; var LastTickTimeVal_A := ATickData[High(ATickData)].TimeStamp;
var TotalDataDurationDaysVal_A := LastTickTimeVal_A - FirstTickTimeVal_A; var TotalDataDurationDaysVal_A := LastTickTimeVal_A - FirstTickTimeVal_A;
if NumCandlesToCacheAuto > 0 then // Avoid division by zero if NumCandlesToCacheAuto is somehow 0 here if NumCandlesToCacheAuto > 0 then // Avoid division by zero if NumCandlesToCacheAuto is somehow 0 here
begin begin
@@ -432,7 +432,7 @@ begin
CurrentTickIndex := 0; CurrentTickIndex := 0;
while CurrentTickIndex <= High(ATickData) do while CurrentTickIndex <= High(ATickData) do
begin begin
BucketStartTimeOADate := Floor(ATickData[CurrentTickIndex].OADateTime / TimeframeIntervalOADays) * TimeframeIntervalOADays; BucketStartTimeOADate := Floor(ATickData[CurrentTickIndex].TimeStamp / TimeframeIntervalOADays) * TimeframeIntervalOADays;
BucketEndTimeOADate := BucketStartTimeOADate + TimeframeIntervalOADays; BucketEndTimeOADate := BucketStartTimeOADate + TimeframeIntervalOADays;
DataIndexStart := CurrentTickIndex; DataIndexStart := CurrentTickIndex;
@@ -444,8 +444,8 @@ begin
// Iterate through ticks that fall into the current bucket // Iterate through ticks that fall into the current bucket
while (CurrentTickIndex <= High(ATickData)) and while (CurrentTickIndex <= High(ATickData)) and
(ATickData[CurrentTickIndex].OADateTime >= BucketStartTimeOADate) and (ATickData[CurrentTickIndex].TimeStamp >= BucketStartTimeOADate) and
(ATickData[CurrentTickIndex].OADateTime < BucketEndTimeOADate) do (ATickData[CurrentTickIndex].TimeStamp < BucketEndTimeOADate) do
begin begin
if ATickData[CurrentTickIndex].Data.Ask > HighPrice then HighPrice := ATickData[CurrentTickIndex].Data.Ask; // Changed if ATickData[CurrentTickIndex].Data.Ask > HighPrice then HighPrice := ATickData[CurrentTickIndex].Data.Ask; // Changed
if ATickData[CurrentTickIndex].Data.Ask < LowPrice then LowPrice := ATickData[CurrentTickIndex].Data.Ask; // Changed if ATickData[CurrentTickIndex].Data.Ask < LowPrice then LowPrice := ATickData[CurrentTickIndex].Data.Ask; // Changed
@@ -492,8 +492,8 @@ begin
if Length(ATickData) > 0 then if Length(ATickData) > 0 then
begin begin
var FirstTickTimeVal := ATickData[0].OADateTime; var FirstTickTimeVal := ATickData[0].TimeStamp;
var LastTickTimeVal := ATickData[High(ATickData)].OADateTime; var LastTickTimeVal := ATickData[High(ATickData)].TimeStamp;
FTotalDataTimeSpanText := FormatTimeSpanFromSeconds((LastTickTimeVal - FirstTickTimeVal) * SecsPerDay_Double); FTotalDataTimeSpanText := FormatTimeSpanFromSeconds((LastTickTimeVal - FirstTickTimeVal) * SecsPerDay_Double);
if AMemoForLogging <> nil then if AMemoForLogging <> nil then
AMemoForLogging.Add(Format('TOHLC.Build: Total data time span: %s.', [FTotalDataTimeSpanText])); AMemoForLogging.Add(Format('TOHLC.Build: Total data time span: %s.', [FTotalDataTimeSpanText]));