From 06ec0db10589731cab02ae00adb5816afcfdedf7 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Fri, 23 May 2025 12:51:32 +0200 Subject: [PATCH] 1st commit --- .gitignore | 9 + ChartDisplay/ChartDisplayMain.dfm | 121 +++ ChartDisplay/ChartDisplayMain.pas | 1220 ++++++++++++++++++++++++ ChartDisplay/ChartDisplayProject.dpr | 16 + ChartDisplay/ChartDisplayProject.dproj | 1143 ++++++++++++++++++++++ ChartDisplay/ChartDisplayProject.res | Bin 0 -> 154852 bytes FIX-Connect/FIXConnectProject.dpr | 14 + FIX-Connect/FIXConnectProject.dproj | 1125 ++++++++++++++++++++++ FIX-Connect/FIXConnectProject.res | Bin 0 -> 154840 bytes FIX-Connect/MainUnit.dfm | 88 ++ FIX-Connect/MainUnit.pas | 463 +++++++++ Loader/TickLoader.dpr | 66 ++ Loader/TickLoader.dproj | 1172 +++++++++++++++++++++++ Loader/TickLoader.res | Bin 0 -> 96 bytes common/Myc.ChartDataController.pas | 435 +++++++++ common/Myc.OHLCCache.pas | 513 ++++++++++ common/Myc.TickLoader.pas | 254 +++++ 17 files changed, 6639 insertions(+) create mode 100644 .gitignore create mode 100644 ChartDisplay/ChartDisplayMain.dfm create mode 100644 ChartDisplay/ChartDisplayMain.pas create mode 100644 ChartDisplay/ChartDisplayProject.dpr create mode 100644 ChartDisplay/ChartDisplayProject.dproj create mode 100644 ChartDisplay/ChartDisplayProject.res create mode 100644 FIX-Connect/FIXConnectProject.dpr create mode 100644 FIX-Connect/FIXConnectProject.dproj create mode 100644 FIX-Connect/FIXConnectProject.res create mode 100644 FIX-Connect/MainUnit.dfm create mode 100644 FIX-Connect/MainUnit.pas create mode 100644 Loader/TickLoader.dpr create mode 100644 Loader/TickLoader.dproj create mode 100644 Loader/TickLoader.res create mode 100644 common/Myc.ChartDataController.pas create mode 100644 common/Myc.OHLCCache.pas create mode 100644 common/Myc.TickLoader.pas diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fc293d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.dcu +*.local +*.identcache +*.exe +*.rsm +__history +__recovery +Win32 +Win64 \ No newline at end of file diff --git a/ChartDisplay/ChartDisplayMain.dfm b/ChartDisplay/ChartDisplayMain.dfm new file mode 100644 index 0000000..d65acd2 --- /dev/null +++ b/ChartDisplay/ChartDisplayMain.dfm @@ -0,0 +1,121 @@ +object ChartForm: TChartForm + Left = 0 + Top = 0 + Caption = 'ChartForm' + ClientHeight = 835 + ClientWidth = 900 + Color = clBtnFace + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -12 + Font.Name = 'Segoe UI' + Font.Style = [] + OnCreate = FormCreate + OnMouseWheel = FormMouseWheel + DesignSize = ( + 900 + 835) + TextHeight = 15 + object FileSelectButton: TSpeedButton + Left = 647 + Top = 17 + Width = 23 + Height = 22 + Anchors = [akTop, akRight] + Caption = '...' + OnClick = FileSelectButtonClick + end + object MouseHoverDataLabel: TLabel + Left = 32 + Top = 565 + Width = 593 + Height = 15 + Anchors = [akLeft, akRight, akBottom] + AutoSize = False + ExplicitTop = 359 + end + object FileNameEdit: TEdit + Left = 32 + Top = 17 + Width = 609 + Height = 23 + Anchors = [akLeft, akTop, akRight] + TabOrder = 0 + Text = + 'C:\Users\Brummel\Documents\cAlgo\TickData\Pepperstone\EURUSD_201' + + '7.tab' + end + object LoadButton: TButton + Left = 686 + Top = 16 + Width = 75 + Height = 25 + Anchors = [akTop, akRight] + Caption = 'Load' + TabOrder = 1 + OnClick = LoadButtonClick + end + object Memo: TMemo + Left = 32 + Top = 606 + Width = 817 + Height = 221 + Anchors = [akLeft, akRight, akBottom] + ScrollBars = ssVertical + TabOrder = 2 + end + object RefreshButton: TButton + Left = 774 + Top = 16 + Width = 75 + Height = 25 + Anchors = [akTop, akRight] + Caption = 'Refresh' + TabOrder = 3 + OnClick = RefreshButtonClick + end + object TimeframeComboBox: TComboBox + Left = 649 + Top = 562 + Width = 145 + Height = 23 + Anchors = [akRight, akBottom] + TabOrder = 4 + OnChange = TimeframeComboBoxChange + end + object GoToCurrentButton: TButton + Left = 800 + Top = 561 + Width = 49 + Height = 25 + Anchors = [akRight, akBottom] + Caption = '-->' + TabOrder = 5 + OnClick = GoToCurrentButtonClick + end + object ChartScrollBox: TScrollBox + Left = 33 + Top = 47 + Width = 815 + Height = 496 + Anchors = [akLeft, akTop, akRight, akBottom] + DoubleBuffered = True + ParentDoubleBuffered = False + TabOrder = 6 + object PaintBox: TPaintBox + Left = 0 + Top = 0 + Width = 811 + Height = 492 + Align = alClient + OnMouseDown = PaintBoxMouseDown + OnMouseMove = PaintBoxMouseMove + OnMouseUp = PaintBoxMouseUp + OnPaint = PaintBoxPaint + ExplicitLeft = 32 + ExplicitTop = 34 + ExplicitWidth = 377 + ExplicitHeight = 97 + end + end +end diff --git a/ChartDisplay/ChartDisplayMain.pas b/ChartDisplay/ChartDisplayMain.pas new file mode 100644 index 0000000..acc548f --- /dev/null +++ b/ChartDisplay/ChartDisplayMain.pas @@ -0,0 +1,1220 @@ +unit ChartDisplayMain; + +// ------------------------------------------------------------------------------ +// Unit Name: ChartDisplayMain +// Author: Delphi Algo Trading Assistant +// Date: May 22, 2025 +// Purpose: Main form for chart display. Uses TChartDataController for data +// management and OHLC aggregation, including IGenericCandleBuilder. +// Timeframes are selected via unique captions (keys), while user-friendly +// descriptions are shown in the UI. +// ------------------------------------------------------------------------------ + +interface + +uses + Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, + Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, + System.Generics.Collections, System.DateUtils, System.Math, System.IOUtils, + System.UITypes, System.Types, + Myc.TickLoader, // For TTickData + Myc.OHLCCache, // For TOHLC, IGenericCandleBuilder, TF_ constants + Myc.ChartDataController, // Uses the updated DataController + Vcl.Buttons; + +type + // TTimeframeSetting is defined in Myc.ChartDataController.pas + // IGenericCandleBuilder is defined in Myc.OHLCCache.pas + + TChartForm = class( TForm ) + FileNameEdit: TEdit; + LoadButton: TButton; + Memo: TMemo; + RefreshButton: TButton; + TimeframeComboBox: TComboBox; + GoToCurrentButton: TButton; + FileSelectButton: TSpeedButton; + MouseHoverDataLabel: TLabel; + ChartScrollBox: TScrollBox; + PaintBox: TPaintBox; + procedure LoadButtonClick( Sender: TObject ); + procedure PaintBoxPaint( Sender: TObject ); + procedure RefreshButtonClick( Sender: TObject ); + procedure FormCreate( Sender: TObject ); + procedure FormDestroy( Sender: TObject ); + procedure FormMouseWheel( Sender: TObject; Shift: TShiftState; + WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean ); + procedure PaintBoxMouseDown( Sender: TObject; Button: TMouseButton; + Shift: TShiftState; X, Y: Integer ); + procedure PaintBoxMouseMove( Sender: TObject; Shift: TShiftState; X, + Y: Integer ); + procedure PaintBoxMouseUp( Sender: TObject; Button: TMouseButton; + Shift: TShiftState; X, Y: Integer ); + procedure TimeframeComboBoxChange( Sender: TObject ); + procedure GoToCurrentButtonClick( Sender: TObject ); + procedure FileSelectButtonClick( Sender: TObject ); + procedure PaintBoxMouseLeave( Sender: TObject ); + private + FActiveOHLCCache: TOHLC; + FCandleWidthInPixels: Integer; + FCandleSpacing: Integer; + FDisplayStartIndex: Integer; + FIsDragging: Boolean; + FDragStartX: Integer; + FDragStartDisplayIndex: Integer; + FSelectedTimeframeSeconds: Int64; // Stores the .Seconds value of the currently selected timeframe setting + FTimeframeSettings: TArray; // Local copy of all available timeframe settings + FCurrentPriceRect: TRect; + FCurrentVolumeHistRect: TRect; + FMouseLastChartPos: TPoint; + FMouseCurrentlyInChartArea: Boolean; + FBufferBitmap: TBitmap; + FDataController: TChartDataController; + + procedure PopulateTimeframeComboBox; + procedure EnsureCacheForCurrentTimeframe( ARebuildIfPresent: Boolean = False ); + procedure ClearAllCachesInForm; + procedure DrawChart( ACanvas: TCanvas; ARect: TRect ); + public + end; + +var + ChartForm: TChartForm; + +implementation + +{$R *.dfm} + + +const + DefaultChartMargin = 20; + DojiPenWidth = 2; + InitialCandleWidth = 5; + InitialCandleSpacing = 2; + DefaultEveryNTicksCount = 20; + DefaultVolumeAccumulationThreshold = 10; + + // --- Generic Candle Builder Implementations --- +type + TEveryNTicksBuilder = class( TInterfacedObject, IGenericCandleBuilder ) + private + FTickCountForNewBar: Integer; + FDescriptionText: string; // User-friendly description + public + constructor Create( ATickCount: Integer ); + function Init( const ADataSet: TArray ): Boolean; + function IsNewBar( AIndex: Integer; const ATick: TTickData ): Boolean; + function GetCaption: string; // Returns the Description for UI + property Caption: string read GetCaption; // Interface property uses GetCaption + end; + + TVolumeAccumulationBuilder = class( TInterfacedObject, IGenericCandleBuilder ) + private + FTargetTickVolumePerBar: Integer; + FCurrentTickInBarCount: Integer; + FDescriptionText: string; // User-friendly description + public + constructor Create( ATargetTickVolume: Integer ); + function Init( const ADataSet: TArray ): Boolean; + function IsNewBar( AIndex: Integer; const ATick: TTickData ): Boolean; + function GetCaption: string; // Returns the Description for UI + property Caption: string read GetCaption; // Interface property uses GetCaption + end; + + { TEveryNTicksBuilder } +constructor TEveryNTicksBuilder.Create( ATickCount: Integer ); +begin + inherited Create; + FTickCountForNewBar := ATickCount; + if FTickCountForNewBar <= 0 then + FTickCountForNewBar := 1; + // The Caption property of IGenericCandleBuilder is used as the Description when registering + FDescriptionText := Format( 'Every %d Ticks', [FTickCountForNewBar] ); +end; + +function TEveryNTicksBuilder.Init( const ADataSet: TArray ): Boolean; +begin + Result := True; +end; + +function TEveryNTicksBuilder.IsNewBar( AIndex: Integer; const ATick: TTickData ): Boolean; +begin + Result := ( AIndex mod FTickCountForNewBar = 0 ); +end; + +function TEveryNTicksBuilder.GetCaption: string; +begin + Result := FDescriptionText; // This is what TChartDataController gets if it calls ABuilder.Caption +end; + +{ TVolumeAccumulationBuilder } +constructor TVolumeAccumulationBuilder.Create( ATargetTickVolume: Integer ); +begin + inherited Create; + FTargetTickVolumePerBar := ATargetTickVolume; + if FTargetTickVolumePerBar <= 0 then + FTargetTickVolumePerBar := 1; + FDescriptionText := Format( 'Ticks/Bar: %d', [FTargetTickVolumePerBar] ); + FCurrentTickInBarCount := 0; +end; + +function TVolumeAccumulationBuilder.Init( const ADataSet: TArray ): Boolean; +begin + FCurrentTickInBarCount := 0; + Result := True; +end; + +function TVolumeAccumulationBuilder.IsNewBar( AIndex: Integer; const ATick: TTickData ): Boolean; +begin + Inc( FCurrentTickInBarCount ); + if FCurrentTickInBarCount >= FTargetTickVolumePerBar then + begin + FCurrentTickInBarCount := 0; + Result := True; + end + else + Result := False; +end; + +function TVolumeAccumulationBuilder.GetCaption: string; +begin + Result := FDescriptionText; // This is what TChartDataController gets if it calls ABuilder.Caption +end; + +{ TChartForm } + +procedure TChartForm.PopulateTimeframeComboBox; +var + i: Integer; + DefaultItemIndex: Integer; + PreferredDefaultCaptionKey: string; // The short KEY caption for the preferred default +begin + TimeframeComboBox.Items.Clear; + if not Assigned( FDataController ) then + begin + Memo.Lines.Add( 'Error: DataController not initialized for PopulateTimeframeComboBox.' ); + Exit; + end; + + // Get all available timeframes, including base and registered generic ones + FTimeframeSettings := FDataController.GetAvailableTimeframes( ); + + DefaultItemIndex := -1; + PreferredDefaultCaptionKey := 'Auto'; // Short key for "Auto (Screen Fit)" + + for i := Low( FTimeframeSettings ) to High( FTimeframeSettings ) do + begin + // Add the user-friendly Description to the ComboBox + TimeframeComboBox.Items.Add( FTimeframeSettings[i].Description ); + // Check against the short Caption (key) for default selection + if CompareText( FTimeframeSettings[i].Caption, PreferredDefaultCaptionKey ) = 0 then + DefaultItemIndex := i; + end; + + if TimeframeComboBox.Items.Count > 0 then + begin + if ( DefaultItemIndex = -1 ) then + DefaultItemIndex := 0; // Fallback to the first item + TimeframeComboBox.ItemIndex := DefaultItemIndex; + + if ( TimeframeComboBox.ItemIndex >= 0 ) and ( TimeframeComboBox.ItemIndex < Length( FTimeframeSettings ) ) then + FSelectedTimeframeSeconds := FTimeframeSettings[TimeframeComboBox.ItemIndex].Seconds + else + FSelectedTimeframeSeconds := TF_AUTO_AGGREGATION; // Should ideally not happen + end + else + begin + FSelectedTimeframeSeconds := TF_AUTO_AGGREGATION; // Default if no timeframes + Memo.Lines.Add( 'Warning: No timeframes available to populate ComboBox.' ); + end; + + if TimeframeComboBox.Items.Count > 0 then // Log current selection + Memo.Lines.Add( 'Timeframes populated. Default UI: "' + TimeframeComboBox.Text + + Format( '", Key: "%s", AggSecs: %d', + [FTimeframeSettings[TimeframeComboBox.ItemIndex].Caption, FSelectedTimeframeSeconds] ) ) + else + Memo.Lines.Add( 'Timeframes populated, but ComboBox is empty. No default set.' ); +end; + +procedure TChartForm.FormCreate( Sender: TObject ); +var + BuilderN: IGenericCandleBuilder; + BuilderV: IGenericCandleBuilder; + BuilderNCaptionKey, BuilderVCaptionKey: string; // These will be the unique keys + BuilderNDescription, BuilderVDescription: string; +begin + FCandleWidthInPixels := InitialCandleWidth; + FCandleSpacing := InitialCandleSpacing; + FDisplayStartIndex := 0; + FIsDragging := False; + FDragStartX := 0; + FDragStartDisplayIndex := 0; + + FDataController := TChartDataController.Create( Memo.Lines ); + + // Create and register generic builders + BuilderN := TEveryNTicksBuilder.Create( DefaultEveryNTicksCount ); + BuilderNCaptionKey := Format( 'NTICKS_%d', [DefaultEveryNTicksCount] ); // e.g., "NTICKS_20" - this is the unique key + BuilderNDescription := BuilderN.Caption; // BuilderN.Caption returns "Every 20 Ticks" + FDataController.RegisterGenericBuilder( BuilderNCaptionKey, BuilderNDescription, BuilderN ); + + BuilderV := TVolumeAccumulationBuilder.Create( DefaultVolumeAccumulationThreshold ); + BuilderVCaptionKey := Format( 'VOLACC_%d', [DefaultVolumeAccumulationThreshold] ); // e.g., "VOLACC_10" - unique key + BuilderVDescription := BuilderV.Caption; // BuilderV.Caption returns "Ticks/Bar: 10" + FDataController.RegisterGenericBuilder( BuilderVCaptionKey, BuilderVDescription, BuilderV ); + + PopulateTimeframeComboBox; // Populates from FDataController, now includes generic builders + + if TimeframeComboBox.ItemIndex <> -1 then + TimeframeComboBoxChange( TimeframeComboBox ) // Trigger initial cache build + else if Length( FTimeframeSettings ) > 0 then + begin // If ComboBox empty but settings exist (e.g. Populate didn't find default) + TimeframeComboBox.ItemIndex := 0; + TimeframeComboBoxChange( TimeframeComboBox ); + end + else + begin + Memo.Lines.Add( 'FormCreate: No timeframes to select after population. Ensuring cache for a default (likely empty).' ); + EnsureCacheForCurrentTimeframe( True ); // Attempt to build something + end; + + FActiveOHLCCache := nil; + FCurrentPriceRect := Rect( 0, 0, 0, 0 ); + FCurrentVolumeHistRect := Rect( 0, 0, 0, 0 ); + MouseHoverDataLabel.Caption := ''; + FMouseLastChartPos := Point( -1, -1 ); + FMouseCurrentlyInChartArea := False; + PaintBox.OnMouseLeave := PaintBoxMouseLeave; + FBufferBitmap := TBitmap.Create; +end; + +procedure TChartForm.FormDestroy( Sender: TObject ); +begin + FreeAndNil( FDataController ); + FreeAndNil( FBufferBitmap ); + FActiveOHLCCache := nil; +end; + +procedure TChartForm.ClearAllCachesInForm; +begin + Memo.Lines.Add( 'TChartForm: Clearing all caches via DataController...' ); + if Assigned( FDataController ) then + FDataController.ClearAllDataAndCaches( ); + FActiveOHLCCache := nil; +end; + +procedure TChartForm.EnsureCacheForCurrentTimeframe( ARebuildIfPresent: Boolean = False ); +var + Index: Integer; + EffectivePaintBoxClientWidthForAuto: Integer; + SelectedTFKeyCaption: string; // The short key caption (e.g., "M1", "NTICKS_20") + SelectedTFDescription: string; // The UI description (e.g., "1 Minute (M1)") +begin + Index := TimeframeComboBox.ItemIndex; + if ( Index < Low( FTimeframeSettings ) ) or ( Index > High( FTimeframeSettings ) ) then + begin + Memo.Lines.Add( 'EnsureCache: Invalid TF ComboBox Index: ' + IntToStr( Index ) ); + FActiveOHLCCache := nil; + PaintBox.Invalidate; + Exit; + end; + + if not Assigned( FDataController ) then + begin + Memo.Lines.Add( 'EnsureCache: FDataController not assigned.' ); + FActiveOHLCCache := nil; + PaintBox.Invalidate; + Exit; + end; + + SelectedTFKeyCaption := FTimeframeSettings[Index].Caption; // This is the short key + SelectedTFDescription := FTimeframeSettings[Index].Description; // This is for UI/logging + FSelectedTimeframeSeconds := FTimeframeSettings[Index].Seconds; // Still useful for some logic + + EffectivePaintBoxClientWidthForAuto := PaintBox.ClientWidth - ( 2 * DefaultChartMargin ); + if EffectivePaintBoxClientWidthForAuto < 0 then + EffectivePaintBoxClientWidthForAuto := 0; + + // Call GetOrBuildCache using the selected timeframe's short KEY CAPTION. + // The ASelectedGenericBuilderCaption parameter was removed from GetOrBuildCache. + FActiveOHLCCache := FDataController.GetOrBuildCache( + SelectedTFKeyCaption, + FCandleWidthInPixels, + FCandleSpacing, + EffectivePaintBoxClientWidthForAuto, + ARebuildIfPresent + ); + + if Assigned( FActiveOHLCCache ) and FActiveOHLCCache.IsValid then + Memo.Lines.Add( Format( 'Active Cache UI: "%s" (Key: %s). Candles: %d. AggSecs: %d. ApproxTime/Candle: %s', + [SelectedTFDescription, SelectedTFKeyCaption, FActiveOHLCCache.Count, + FActiveOHLCCache.AggregationTimeframeSeconds, FActiveOHLCCache.ApproxTimePerCandleText] ) ) + else + Memo.Lines.Add( Format( 'Active Cache for UI: "%s" (Key: %s) not valid or empty after EnsureCache.', + [SelectedTFDescription, SelectedTFKeyCaption] ) ); +end; + +procedure TChartForm.TimeframeComboBoxChange( Sender: TObject ); +var + CurrentTFKeyCaption: string; +begin + if TimeframeComboBox.ItemIndex < 0 then + begin + Memo.Lines.Add( 'TimeframeComboBoxChange: Invalid ItemIndex (-1).' ); + Exit; + end; + + if ( TimeframeComboBox.ItemIndex >= Low( FTimeframeSettings ) ) and + ( TimeframeComboBox.ItemIndex <= High( FTimeframeSettings ) ) then + begin + FSelectedTimeframeSeconds := FTimeframeSettings[TimeframeComboBox.ItemIndex].Seconds; + CurrentTFKeyCaption := FTimeframeSettings[TimeframeComboBox.ItemIndex].Caption; + Memo.Lines.Add( FormatDateTime( 'yyyy-mm-dd hh:nn:ss', Now ) + + Format( ' - TF Change Event: UI shows "%s" (Key: %s, Resolved AggSecs: %d)', + [TimeframeComboBox.Text, // This is FTimeframeSettings[Index].Description + CurrentTFKeyCaption, + FSelectedTimeframeSeconds] ) ); + end + else + begin + Memo.Lines.Add( Format( 'Error: Invalid ItemIndex %d in TimeframeComboBoxChange. FTimeframeSettings valid range %d to %d.', + [TimeframeComboBox.ItemIndex, Low( FTimeframeSettings ), High( FTimeframeSettings )] ) ); + if Length( FTimeframeSettings ) > 0 then + FSelectedTimeframeSeconds := FTimeframeSettings[0].Seconds + else + FSelectedTimeframeSeconds := TF_AUTO_AGGREGATION; + end; + + FDisplayStartIndex := 0; + EnsureCacheForCurrentTimeframe( True ); + PaintBox.Invalidate; +end; + +procedure TChartForm.LoadButtonClick( Sender: TObject ); +begin + Memo.Clear; + Memo.Lines.Add( FormatDateTime( 'yyyy-mm-dd hh:nn:ss', Now ) + ' - Starting data load...' ); + ClearAllCachesInForm( ); + FDisplayStartIndex := 0; + + if Trim( FileNameEdit.Text ) = '' then + begin + Memo.Lines.Add( 'Error: File name cannot be empty.' ); + MessageDlg( 'Please enter a file name.', mtWarning, [mbOK], 0 ); + PaintBox.Invalidate; + Exit; + end; + Memo.Lines.Add( 'Loading data from file: ' + FileNameEdit.Text ); + + if Assigned( FDataController ) then + begin + FDataController.LoadTickDataSeries( FileNameEdit.Text ); + if FDataController.HasData then + begin + Memo.Lines.Add( FormatDateTime( 'yyyy-mm-dd hh:nn:ss', Now ) + Format( ' - Successfully loaded %d ticks by controller.', + [FDataController.GetTickDataCount] ) ); + EnsureCacheForCurrentTimeframe( True ); + end + else + begin + Memo.Lines.Add( FormatDateTime( 'yyyy-mm-dd hh:nn:ss', Now ) + + ' - No tick data was loaded by controller, or an error occurred.' ); + FActiveOHLCCache := nil; + end; + end + else + Memo.Lines.Add( 'Error: DataController is not initialized. Cannot load data.' ); + + PaintBox.Invalidate; +end; + +procedure TChartForm.RefreshButtonClick( Sender: TObject ); +var + TargetStartTickIndex: Integer; + VisibleStartIndexInOldCache: Integer; + ActualNumVisibleCandlesOld: Integer; + ChartDrawWidth, OldDisplaySlotWidth, NumCandlesDrawableOld: Integer; + NewOptimalStartIndex, i: Integer; +begin + Memo.Lines.Add( FormatDateTime( 'yyyy-mm-dd hh:nn:ss', Now ) + ' - Refresh Button clicked...' ); + TargetStartTickIndex := -1; + + if Assigned( FActiveOHLCCache ) and FActiveOHLCCache.IsValid and ( FActiveOHLCCache.Count > 0 ) and + Assigned( FDataController ) and FDataController.HasData then + begin + VisibleStartIndexInOldCache := FDisplayStartIndex; + if FCurrentPriceRect.Width > 0 then + ChartDrawWidth := FCurrentPriceRect.Width + else + ChartDrawWidth := PaintBox.ClientWidth - ( 2 * DefaultChartMargin ); + + OldDisplaySlotWidth := FCandleWidthInPixels + FCandleSpacing; + NumCandlesDrawableOld := 0; + if OldDisplaySlotWidth > 0 then + NumCandlesDrawableOld := ChartDrawWidth div OldDisplaySlotWidth; + + ActualNumVisibleCandlesOld := 0; + if ( VisibleStartIndexInOldCache >= 0 ) and ( VisibleStartIndexInOldCache < FActiveOHLCCache.Count ) then + ActualNumVisibleCandlesOld := Min( NumCandlesDrawableOld, FActiveOHLCCache.Count - VisibleStartIndexInOldCache ); + + if ActualNumVisibleCandlesOld > 0 then + begin + TargetStartTickIndex := FActiveOHLCCache.Candles[VisibleStartIndexInOldCache].OriginalDataIndexStart; + Memo.Lines.Add( Format( 'Refresh S1: Old Cache Index %d maps to Original Tick Index: %d', + [VisibleStartIndexInOldCache, TargetStartTickIndex] ) ); + end + else + begin + Memo.Lines.Add( 'Refresh S1: No candles visible in old cache or invalid start index. Defaulting to Tick Index 0.' ); + TargetStartTickIndex := 0; + end; + end + else + begin + Memo.Lines.Add( 'Refresh S1: No active cache or no raw data. Defaulting Target Tick Index to 0.' ); + TargetStartTickIndex := 0; + end; + + if Assigned( FDataController ) and FDataController.HasData then + begin + TargetStartTickIndex := Max( 0, TargetStartTickIndex ); + if FDataController.GetTickDataLength > 0 then + TargetStartTickIndex := Min( TargetStartTickIndex, FDataController.GetTickDataLength - 1 ) + else + TargetStartTickIndex := 0; + end + else + TargetStartTickIndex := -1; + + Memo.Lines.Add( Format( 'Refresh S1a: Final Target Original Tick Index: %d', [TargetStartTickIndex] ) ); + + FCandleWidthInPixels := InitialCandleWidth; + FCandleSpacing := InitialCandleSpacing; + Memo.Lines.Add( Format( 'Refresh S2: Visuals (Candle Width/Spacing) reset to W:%dpx, S:%dpx.', + [FCandleWidthInPixels, FCandleSpacing] ) ); + + EnsureCacheForCurrentTimeframe( True ); + + NewOptimalStartIndex := 0; + if Assigned( FActiveOHLCCache ) and FActiveOHLCCache.IsValid and ( FActiveOHLCCache.Count > 0 ) and + ( TargetStartTickIndex >= 0 ) and Assigned( FDataController ) and FDataController.HasData then + begin + for i := 0 to FActiveOHLCCache.Count - 1 do + begin + if FActiveOHLCCache.Candles[i].OriginalDataIndexStart >= TargetStartTickIndex then + begin + NewOptimalStartIndex := i; + Break; + end + else if FActiveOHLCCache.Candles[i].OriginalDataIndexEnd >= TargetStartTickIndex then + begin + NewOptimalStartIndex := i; + Break; + end; + if i = FActiveOHLCCache.Count - 1 then + NewOptimalStartIndex := i; + end; + Memo.Lines.Add( Format( 'Refresh S4a: New Optimal Start Index in new cache = %d (for Original Tick Index %d).', + [NewOptimalStartIndex, TargetStartTickIndex] ) ); + + FDisplayStartIndex := NewOptimalStartIndex; + if FActiveOHLCCache.Count > 0 then + begin + FDisplayStartIndex := Max( 0, FDisplayStartIndex ); + FDisplayStartIndex := Min( FDisplayStartIndex, FActiveOHLCCache.Count - 1 ); + end + else + FDisplayStartIndex := 0; + Memo.Lines.Add( Format( 'Refresh S4b: Final FDisplayStartIndex: %d (New Cache Candle Count: %d).', + [FDisplayStartIndex, FActiveOHLCCache.Count] ) ); + end + else + begin + FDisplayStartIndex := 0; + Memo.Lines.Add( 'Refresh S4c: New cache invalid/empty, or no target tick. FDisplayStartIndex reset to 0.' ); + end; + + PaintBox.Invalidate; +end; + +procedure TChartForm.FileSelectButtonClick( Sender: TObject ); +var + OpenDialog: TOpenDialog; + InitialDirectory: string; + SelectedPath, SelectedSymbol: string; + SelectedYear: Integer; + IsValidSyntax: Boolean; +begin + OpenDialog := TOpenDialog.Create( Self ); + try + OpenDialog.Title := 'Open Tick Data File'; + OpenDialog.Filter := 'Tick Data Files (*.tab)|*.TAB|All Files (*.*)|*.*'; + OpenDialog.DefaultExt := 'tab'; + OpenDialog.Options := [ofFileMustExist, ofPathMustExist, ofHideReadOnly, ofEnableSizing]; + + if Trim( FileNameEdit.Text ) <> '' then + begin + InitialDirectory := System.IOUtils.TPath.GetDirectoryName( FileNameEdit.Text ); + if ( InitialDirectory <> '' ) and System.IOUtils.TDirectory.Exists( InitialDirectory ) then + begin + OpenDialog.InitialDir := InitialDirectory; + Memo.Lines.Add( 'FSButton: OpenDialog InitialDir set to: ' + InitialDirectory ); + end + else if InitialDirectory <> '' then + Memo.Lines.Add( 'FSButton: Directory from FileNameEdit not found: ' + InitialDirectory ) + else + Memo.Lines.Add( 'FSButton: No directory information in FileNameEdit.' ); + end + else + Memo.Lines.Add( 'FSButton: FileNameEdit is empty, using default InitialDir.' ); + + if OpenDialog.Execute then + begin + FileNameEdit.Text := OpenDialog.FileName; + Memo.Lines.Add( FormatDateTime( 'yyyy-mm-dd hh:nn:ss', Now ) + ' - File selected via dialog: ' + OpenDialog.FileName ); + + IsValidSyntax := Myc.TickLoader.TryParseFileName( OpenDialog.FileName, + SelectedPath, SelectedSymbol, SelectedYear ); + + if not IsValidSyntax then + begin + MessageDlg( Format( 'Warning: The selected filename ''%s'' does not match the expected format (Prefix_Symbol_Year.tab).', + [System.IOUtils.TPath.GetFileName( OpenDialog.FileName )] ), mtWarning, [mbOK], 0 ); + Memo.Lines.Add( 'Warning: Incorrect filename syntax chosen by user.' ); + end; + + LoadButtonClick( Self ); + end + else + Memo.Lines.Add( FormatDateTime( 'yyyy-mm-dd hh:nn:ss', Now ) + ' - File selection dialog cancelled by user.' ); + finally + OpenDialog.Free; + end; +end; + +procedure TChartForm.PaintBoxMouseLeave( Sender: TObject ); +begin + if FMouseCurrentlyInChartArea then + begin + FMouseCurrentlyInChartArea := False; + MouseHoverDataLabel.Caption := ''; + PaintBox.Invalidate; + end; + FMouseLastChartPos := Point( -1, -1 ); +end; + +procedure TChartForm.PaintBoxMouseDown( Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer ); +var + IsOverPrice, IsOverVolume: Boolean; +begin + if Button = mbLeft then + begin + FMouseLastChartPos := Point( X, Y ); + + IsOverPrice := ( FCurrentPriceRect.Width > 0 ) and PtInRect( FCurrentPriceRect, FMouseLastChartPos ); + IsOverVolume := ( FCurrentVolumeHistRect.Width > 0 ) and ( FCurrentVolumeHistRect.Height > 0 ) and + PtInRect( FCurrentVolumeHistRect, FMouseLastChartPos ); + FMouseCurrentlyInChartArea := IsOverPrice or IsOverVolume; + + if FMouseCurrentlyInChartArea and Assigned( FActiveOHLCCache ) and + FActiveOHLCCache.IsValid and ( FActiveOHLCCache.Count > 0 ) then + begin + FIsDragging := True; + FDragStartX := X; + FDragStartDisplayIndex := FDisplayStartIndex; + PaintBox.Cursor := crSizeWE; + Memo.Lines.Add( FormatDateTime( 'yyyy-mm-dd hh:nn:ss', Now ) + + Format( ' - Drag Start: MouseX=%d, StartDisplayIdx=%d', [X, FDisplayStartIndex] ) ); + end + else if Assigned( FActiveOHLCCache ) and FActiveOHLCCache.IsValid and ( FActiveOHLCCache.Count > 0 ) then + begin + Memo.Lines.Add( FormatDateTime( 'yyyy-mm-dd hh:nn:ss', Now ) + + Format( ' - MouseDown at X=%d, Y=%d (outside chart drawing areas). No drag initiated.', [X, Y] ) ); + end; + + if FMouseCurrentlyInChartArea then + PaintBox.Invalidate; + end; +end; + +procedure TChartForm.PaintBoxMouseMove( Sender: TObject; Shift: TShiftState; X, Y: Integer ); +var + DeltaX, NumCandlesToScroll, PrevDisplayStartIndex, CurrentFixedSlotWidth, + MaxPossibleStartIndex, ChartDrawWidthForDrag, DrawableCandlesInViewPort, + RelativeX, CandleIndexInViewPort, ActualCacheIndex, CurrentDrawingSlotWidthForHover: Integer; + HoveredCandle: TCachedCandle; + UTCTimeStart, UTCTimeEnd, LocalTimeStart, LocalTimeEnd: TDateTime; + StartTimeStr, EndTimeStr, TimeZoneStr, HoverInfo: string; + IsOverPrice, IsOverVolume, NeedsInvalidate, OldMouseCurrentlyInChartArea: Boolean; +begin + OldMouseCurrentlyInChartArea := FMouseCurrentlyInChartArea; + FMouseLastChartPos := Point( X, Y ); + + if FIsDragging then + begin + DeltaX := X - FDragStartX; + CurrentFixedSlotWidth := FCandleWidthInPixels + FCandleSpacing; + + if CurrentFixedSlotWidth > 0 then + begin + NumCandlesToScroll := Round( -DeltaX / CurrentFixedSlotWidth ); + PrevDisplayStartIndex := FDisplayStartIndex; + FDisplayStartIndex := FDragStartDisplayIndex + NumCandlesToScroll; + + if FCurrentPriceRect.Width > 0 then + ChartDrawWidthForDrag := FCurrentPriceRect.Width + else + ChartDrawWidthForDrag := PaintBox.ClientWidth - ( 2 * DefaultChartMargin ); + + DrawableCandlesInViewPort := Max( 1, ChartDrawWidthForDrag div CurrentFixedSlotWidth ); + + if Assigned( FActiveOHLCCache ) and ( FActiveOHLCCache.Count > 0 ) then + begin + MaxPossibleStartIndex := FActiveOHLCCache.Count - DrawableCandlesInViewPort; + if MaxPossibleStartIndex < 0 then + MaxPossibleStartIndex := 0; + FDisplayStartIndex := Max( 0, FDisplayStartIndex ); + FDisplayStartIndex := Min( FDisplayStartIndex, MaxPossibleStartIndex ); + end + else + FDisplayStartIndex := 0; + + if FDisplayStartIndex <> PrevDisplayStartIndex then + begin + Memo.Lines.Add( FormatDateTime( 'yyyy-mm-dd hh:nn:ss', Now ) + + Format( ' - Dragging: DeltaX=%d, NumScroll=%d, NewDisplayIdx=%d', + [DeltaX, NumCandlesToScroll, FDisplayStartIndex] ) ); + PaintBox.Invalidate; + end; + end; + + IsOverPrice := ( FCurrentPriceRect.Width > 0 ) and PtInRect( FCurrentPriceRect, FMouseLastChartPos ); + IsOverVolume := ( FCurrentVolumeHistRect.Width > 0 ) and PtInRect( FCurrentVolumeHistRect, FMouseLastChartPos ); + FMouseCurrentlyInChartArea := IsOverPrice or IsOverVolume; + + if FMouseCurrentlyInChartArea <> OldMouseCurrentlyInChartArea then + PaintBox.Invalidate; + + MouseHoverDataLabel.Caption := ''; + Exit; + end; + + NeedsInvalidate := False; + IsOverPrice := ( FCurrentPriceRect.Width > 0 ) and PtInRect( FCurrentPriceRect, FMouseLastChartPos ); + IsOverVolume := ( FCurrentVolumeHistRect.Width > 0 ) and PtInRect( FCurrentVolumeHistRect, FMouseLastChartPos ); + FMouseCurrentlyInChartArea := IsOverPrice or IsOverVolume; + + if FMouseCurrentlyInChartArea <> OldMouseCurrentlyInChartArea then + NeedsInvalidate := True; + if FMouseCurrentlyInChartArea then + NeedsInvalidate := True; + + if FMouseCurrentlyInChartArea and Assigned( FActiveOHLCCache ) and FActiveOHLCCache.IsValid and + ( FActiveOHLCCache.Count > 0 ) and Assigned( FDataController ) and FDataController.HasData then + begin + CurrentDrawingSlotWidthForHover := FCandleWidthInPixels + FCandleSpacing; + if CurrentDrawingSlotWidthForHover > 0 then + begin + RelativeX := FMouseLastChartPos.X - FCurrentPriceRect.Left; + CandleIndexInViewPort := RelativeX div CurrentDrawingSlotWidthForHover; + ActualCacheIndex := FDisplayStartIndex + CandleIndexInViewPort; + + if ( ActualCacheIndex >= 0 ) and ( ActualCacheIndex < FActiveOHLCCache.Count ) then + begin + HoveredCandle := FActiveOHLCCache.Candles[ActualCacheIndex]; + if ( HoveredCandle.OriginalDataIndexStart >= 0 ) and + ( HoveredCandle.OriginalDataIndexStart < FDataController.GetTickDataLength ) and + ( HoveredCandle.OriginalDataIndexEnd >= 0 ) and + ( HoveredCandle.OriginalDataIndexEnd < FDataController.GetTickDataLength ) then + begin + UTCTimeStart := FDataController.GetTickDataItem( HoveredCandle.OriginalDataIndexStart ).OADateTime; + UTCTimeEnd := FDataController.GetTickDataItem( HoveredCandle.OriginalDataIndexEnd ).OADateTime; + LocalTimeStart := TTimeZone.Local.ToLocalTime( UTCTimeStart ); + LocalTimeEnd := TTimeZone.Local.ToLocalTime( UTCTimeEnd ); + TimeZoneStr := TTimeZone.Local.GetAbbreviation( LocalTimeStart ); + StartTimeStr := FormatDateTime( 'yy-mm-dd hh:nn:ss', LocalTimeStart ); + if System.DateUtils.DateOf( LocalTimeStart ) <> System.DateUtils.DateOf( LocalTimeEnd ) then + EndTimeStr := FormatDateTime( 'yy-mm-dd hh:nn:ss', LocalTimeEnd ) + else + EndTimeStr := FormatDateTime( 'hh:nn:ss', LocalTimeEnd ); + HoverInfo := Format( 'S: %s E: %s (%s) O:%.5f H:%.5f L:%.5f C:%.5f Vol:%d', + [StartTimeStr, EndTimeStr, TimeZoneStr, HoveredCandle.OpenPrice, + HoveredCandle.HighPrice, HoveredCandle.LowPrice, HoveredCandle.ClosePrice, + HoveredCandle.TickVolume] ); + MouseHoverDataLabel.Caption := HoverInfo; + end + else + MouseHoverDataLabel.Caption := 'Candle (Original Tick Index Error)'; + end + else + MouseHoverDataLabel.Caption := ''; + end + else + MouseHoverDataLabel.Caption := ''; + end + else + MouseHoverDataLabel.Caption := ''; + + if NeedsInvalidate then + PaintBox.Invalidate; +end; + +procedure TChartForm.PaintBoxMouseUp( Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer ); +begin + if Button = mbLeft then + begin + if FIsDragging then + begin + FIsDragging := False; + PaintBox.Cursor := crDefault; + Memo.Lines.Add( FormatDateTime( 'yyyy-mm-dd hh:nn:ss', Now ) + + ' - Drag End. Final DisplayIdx=' + IntToStr( FDisplayStartIndex ) ); + FMouseLastChartPos := Point( X, Y ); + PaintBoxMouseMove( Sender, Shift, X, Y ); + end; + end; +end; + +procedure TChartForm.PaintBoxPaint( Sender: TObject ); +begin + if not Assigned( FBufferBitmap ) then + begin + Memo.Lines.Add( 'Error: FBufferBitmap not assigned in PaintBoxPaint.' ); + Exit; + end; + + if ( PaintBox.ClientWidth <= 0 ) or ( PaintBox.ClientHeight <= 0 ) then + begin + if Assigned( PaintBox.Canvas ) then + PaintBox.Canvas.FillRect( PaintBox.ClientRect ); + Exit; + end; + + if ( FBufferBitmap.Width <> PaintBox.ClientWidth ) or + ( FBufferBitmap.Height <> PaintBox.ClientHeight ) then + begin + FBufferBitmap.SetSize( PaintBox.ClientWidth, PaintBox.ClientHeight ); + end; + + DrawChart( FBufferBitmap.Canvas, Rect( 0, 0, FBufferBitmap.Width, FBufferBitmap.Height ) ); + PaintBox.Canvas.Draw( 0, 0, FBufferBitmap ); +end; + +procedure TChartForm.DrawChart( ACanvas: TCanvas; ARect: TRect ); +const + DefaultVolumeAreaHeightRatio = 0.20; + MinVolumeAreaPixelHeight = 30; + MaxVolumeAreaPixelHeight = 80; + PriceToVolumeSeparatorPixels = 5; + CrosshairPriceTimeMargin = 3; +var + LoopIdx, CacheIdx: Integer; + ScaleY: Double; + Y_open, Y_high, Y_low, Y_close: Integer; + NumCachedCandlesInActiveCache: Integer; + CurrentDrawingSlotWidth: Integer; + ActualNumCandlesToDraw: Integer; + CurrentCandleXStart: Integer; + X_wick_center: Integer; + BodyRect: TRect; + CachedCandleRec: TCachedCandle; + NumCandlesDrawableInViewPort: Integer; + LocalMinYValue, LocalMaxYValue: Single; + VisibleCandleForScale: TCachedCandle; + k_scale: Integer; + CurrentMinYToUse, CurrentMaxYToUse: Single; + ExpansionValue: Single; + DrawableContentRect, PriceRect_Local, VolumeHistRect_Local: TRect; + ActualVolumeHistAreaHeight: Integer; + MaxVisibleTickVolume: Integer; + VolumeScaleY: Double; + VolumeBarHeight: Integer; + VolBarRect: TRect; + RelativeX, CandleIndexInViewPortForCrosshair, VerticalLineX: Integer; + ShowVolumeBars: Boolean; + PriceAtMouseY: Double; + PriceStr, TimeStr: string; + TextX, TextY: Integer; + CrosshairCandle: TCachedCandle; + UTCTimeStart, LocalTimeStart: TDateTime; + OriginalPenStyle: TPenStyle; + OriginalPenWidth: Integer; + OriginalFontColor: TColor; + OriginalBrushStyle: TBrushStyle; + OriginalFontSize: Integer; // For saving and restoring font size +begin + ACanvas.Brush.Color := clWhite; + ACanvas.FillRect( ARect ); + + FCurrentPriceRect := Rect( 0, 0, 0, 0 ); + FCurrentVolumeHistRect := Rect( 0, 0, 0, 0 ); + + if not Assigned( FActiveOHLCCache ) or not FActiveOHLCCache.IsValid or ( FActiveOHLCCache.Count = 0 ) then + begin + ACanvas.Font.Color := clGray; + ACanvas.TextOut( ARect.Left + 10, ARect.Top + 10, 'No data in active cache or cache is invalid.' ); + Exit; + end; + + DrawableContentRect := Rect( ARect.Left + DefaultChartMargin, ARect.Top + DefaultChartMargin, + ARect.Right - DefaultChartMargin, ARect.Bottom - DefaultChartMargin ); + + if ( DrawableContentRect.Width <= 0 ) or ( DrawableContentRect.Height <= 0 ) then + begin + ACanvas.Font.Color := clRed; + ACanvas.TextOut( ARect.Left + 10, ARect.Top + 10, 'Drawable area too small.' ); + Exit; + end; + + ShowVolumeBars := ( FActiveOHLCCache.AggregationTimeframeSeconds <> TF_AUTO_AGGREGATION ) and + ( FActiveOHLCCache.AggregationTimeframeSeconds <> TF_TICK_BY_TICK ); + + ActualVolumeHistAreaHeight := 0; + if ShowVolumeBars then + begin + ActualVolumeHistAreaHeight := Round( DrawableContentRect.Height * DefaultVolumeAreaHeightRatio ); + ActualVolumeHistAreaHeight := Max( MinVolumeAreaPixelHeight, Min( ActualVolumeHistAreaHeight, MaxVolumeAreaPixelHeight ) ); + if DrawableContentRect.Height < ( ActualVolumeHistAreaHeight + PriceToVolumeSeparatorPixels + 50 ) then + begin + ActualVolumeHistAreaHeight := Max( 0, DrawableContentRect.Height - PriceToVolumeSeparatorPixels - 50 ); + if ActualVolumeHistAreaHeight < MinVolumeAreaPixelHeight then + ActualVolumeHistAreaHeight := 0; + end; + end; + if ActualVolumeHistAreaHeight <= 0 then + ShowVolumeBars := False; + + if ShowVolumeBars then + begin + VolumeHistRect_Local := Rect( DrawableContentRect.Left, DrawableContentRect.Bottom - ActualVolumeHistAreaHeight, + DrawableContentRect.Right, DrawableContentRect.Bottom ); + PriceRect_Local := Rect( DrawableContentRect.Left, DrawableContentRect.Top, + DrawableContentRect.Right, VolumeHistRect_Local.Top - PriceToVolumeSeparatorPixels ); + end + else + begin // Corrected block for when ShowVolumeBars is false + PriceRect_Local := DrawableContentRect; + VolumeHistRect_Local := Rect( 0, 0, 0, 0 ); + end; + + if ( PriceRect_Local.Height <= 0 ) then + begin + ACanvas.Font.Color := clRed; + ACanvas.TextOut( ARect.Left + 10, ARect.Top + 10, 'Price chart area too small to draw.' ); + Exit; + end; + + FCurrentPriceRect := PriceRect_Local; + FCurrentVolumeHistRect := VolumeHistRect_Local; + + NumCachedCandlesInActiveCache := FActiveOHLCCache.Count; + CurrentDrawingSlotWidth := FCandleWidthInPixels + FCandleSpacing; + if CurrentDrawingSlotWidth <= 0 then + Exit; + + NumCandlesDrawableInViewPort := PriceRect_Local.Width div CurrentDrawingSlotWidth; + ActualNumCandlesToDraw := 0; + if ( FDisplayStartIndex >= 0 ) and ( FDisplayStartIndex < NumCachedCandlesInActiveCache ) then + ActualNumCandlesToDraw := Min( NumCandlesDrawableInViewPort, NumCachedCandlesInActiveCache - FDisplayStartIndex ); + + MaxVisibleTickVolume := 0; + if ActualNumCandlesToDraw > 0 then + begin + VisibleCandleForScale := FActiveOHLCCache.Candles[FDisplayStartIndex]; + LocalMinYValue := VisibleCandleForScale.LowPrice; + LocalMaxYValue := VisibleCandleForScale.HighPrice; + if ShowVolumeBars then + MaxVisibleTickVolume := VisibleCandleForScale.TickVolume; + for k_scale := 1 to ActualNumCandlesToDraw - 1 do + begin + CacheIdx := FDisplayStartIndex + k_scale; + VisibleCandleForScale := FActiveOHLCCache.Candles[CacheIdx]; + if VisibleCandleForScale.LowPrice < LocalMinYValue then + LocalMinYValue := VisibleCandleForScale.LowPrice; + if VisibleCandleForScale.HighPrice > LocalMaxYValue then + LocalMaxYValue := VisibleCandleForScale.HighPrice; + if ShowVolumeBars and ( VisibleCandleForScale.TickVolume > MaxVisibleTickVolume ) then + MaxVisibleTickVolume := VisibleCandleForScale.TickVolume; + end; + if Abs( LocalMaxYValue - LocalMinYValue ) < 1E-6 then + begin + ExpansionValue := Max( Abs( LocalMinYValue ) * 0.001, 0.0001 ); + LocalMaxYValue := LocalMinYValue + ExpansionValue; + LocalMinYValue := LocalMinYValue - ExpansionValue; + end; + CurrentMinYToUse := LocalMinYValue; + CurrentMaxYToUse := LocalMaxYValue; + if ShowVolumeBars and ( MaxVisibleTickVolume = 0 ) then + MaxVisibleTickVolume := 1; + end + else + begin + CurrentMinYToUse := FActiveOHLCCache.GlobalMinY; + CurrentMaxYToUse := FActiveOHLCCache.GlobalMaxY; + if Abs( CurrentMaxYToUse - CurrentMinYToUse ) < 1E-6 then + begin + CurrentMaxYToUse := CurrentMinYToUse + 0.0002; + CurrentMinYToUse := CurrentMinYToUse - 0.0002; + end; + end; + + OriginalFontSize := ACanvas.Font.Size; // Save original font size + ACanvas.Font.Size := 8; + ACanvas.Font.Color := clGray; + ACanvas.TextOut( PriceRect_Local.Left + 3, PriceRect_Local.Top + 3, Format( '%.5f', [CurrentMaxYToUse] ) ); + ACanvas.TextOut( PriceRect_Local.Left + 3, PriceRect_Local.Bottom - ACanvas.TextHeight( '0' ) - 3, + Format( '%.5f', [CurrentMinYToUse] ) ); + if ShowVolumeBars and ( VolumeHistRect_Local.Height > 0 ) then + ACanvas.TextOut( VolumeHistRect_Local.Left + 3, VolumeHistRect_Local.Top + 3, Format( '%d', [MaxVisibleTickVolume] ) ); + ACanvas.Font.Size := OriginalFontSize; // Restore original font size + + if Abs( CurrentMaxYToUse - CurrentMinYToUse ) < 1E-7 then + ScaleY := 1.0 + else + ScaleY := PriceRect_Local.Height / ( CurrentMaxYToUse - CurrentMinYToUse ); + if ScaleY <= 0 then + ScaleY := 1.0; + VolumeScaleY := 0; + if ShowVolumeBars and ( VolumeHistRect_Local.Height > 0 ) and ( MaxVisibleTickVolume > 0 ) then + VolumeScaleY := VolumeHistRect_Local.Height / MaxVisibleTickVolume; + + CurrentCandleXStart := PriceRect_Local.Left; + if ActualNumCandlesToDraw > 0 then + begin + for LoopIdx := 0 to ActualNumCandlesToDraw - 1 do + begin + CacheIdx := FDisplayStartIndex + LoopIdx; + CachedCandleRec := FActiveOHLCCache.Candles[CacheIdx]; + Y_high := PriceRect_Local.Bottom - Round( ( CachedCandleRec.HighPrice - CurrentMinYToUse ) * ScaleY ); + Y_low := PriceRect_Local.Bottom - Round( ( CachedCandleRec.LowPrice - CurrentMinYToUse ) * ScaleY ); + Y_open := PriceRect_Local.Bottom - Round( ( CachedCandleRec.OpenPrice - CurrentMinYToUse ) * ScaleY ); + Y_close := PriceRect_Local.Bottom - Round( ( CachedCandleRec.ClosePrice - CurrentMinYToUse ) * ScaleY ); + Y_high := Max( PriceRect_Local.Top, Min( PriceRect_Local.Bottom, Y_high ) ); + Y_low := Max( PriceRect_Local.Top, Min( PriceRect_Local.Bottom, Y_low ) ); + Y_open := Max( PriceRect_Local.Top, Min( PriceRect_Local.Bottom, Y_open ) ); + Y_close := Max( PriceRect_Local.Top, Min( PriceRect_Local.Bottom, Y_close ) ); + X_wick_center := CurrentCandleXStart + ( FCandleWidthInPixels div 2 ); + ACanvas.Pen.Color := clBlack; + ACanvas.Pen.Width := 1; + ACanvas.MoveTo( X_wick_center, Y_high ); + ACanvas.LineTo( X_wick_center, Y_low ); + BodyRect.Left := CurrentCandleXStart; + BodyRect.Right := CurrentCandleXStart + FCandleWidthInPixels; + BodyRect.Top := Min( Y_open, Y_close ); + BodyRect.Bottom := Max( Y_open, Y_close ); + if Abs( Y_open - Y_close ) < 1 then + begin + ACanvas.Pen.Width := DojiPenWidth; + ACanvas.MoveTo( BodyRect.Left, Y_open ); + ACanvas.LineTo( BodyRect.Right, Y_open ); + ACanvas.Pen.Width := 1; + end + else + begin + if BodyRect.Bottom <= BodyRect.Top then + BodyRect.Bottom := BodyRect.Top + 1; + if CachedCandleRec.ClosePrice > CachedCandleRec.OpenPrice then + ACanvas.Brush.Color := clWhite + else + ACanvas.Brush.Color := clBlack; + ACanvas.Pen.Color := clBlack; + ACanvas.Rectangle( BodyRect ); + end; + if ShowVolumeBars and ( VolumeScaleY > 0 ) then + begin + VolumeBarHeight := Round( CachedCandleRec.TickVolume * VolumeScaleY ); + VolumeBarHeight := Max( 0, Min( VolumeBarHeight, VolumeHistRect_Local.Height ) ); + VolBarRect := Rect( CurrentCandleXStart, VolumeHistRect_Local.Bottom - VolumeBarHeight, + CurrentCandleXStart + FCandleWidthInPixels, VolumeHistRect_Local.Bottom ); + if CachedCandleRec.ClosePrice > CachedCandleRec.OpenPrice then + ACanvas.Brush.Color := TColor( $00B0F0B0 ) + else if CachedCandleRec.ClosePrice < CachedCandleRec.OpenPrice then + ACanvas.Brush.Color := TColor( $00B0B0F0 ) + else + ACanvas.Brush.Color := clSilver; + ACanvas.Pen.Color := clGray; + ACanvas.Rectangle( VolBarRect ); + end; + CurrentCandleXStart := CurrentCandleXStart + CurrentDrawingSlotWidth; + end; + end; + + ACanvas.Pen.Style := psSolid; + ACanvas.Pen.Width := 1; + ACanvas.Pen.Color := clBlack; + ACanvas.Brush.Style := bsClear; + ACanvas.Rectangle( PriceRect_Local ); + if ShowVolumeBars and ( VolumeHistRect_Local.Width > 0 ) and ( VolumeHistRect_Local.Height > 0 ) then + ACanvas.Rectangle( VolumeHistRect_Local ); + + if FMouseCurrentlyInChartArea and Assigned( FActiveOHLCCache ) and FActiveOHLCCache.IsValid and ( FActiveOHLCCache.Count > 0 ) and + ( FMouseLastChartPos.X >= 0 ) and Assigned( FDataController ) and FDataController.HasData then + begin + OriginalPenStyle := ACanvas.Pen.Style; + OriginalPenWidth := ACanvas.Pen.Width; + OriginalFontColor := ACanvas.Font.Color; + OriginalBrushStyle := ACanvas.Brush.Style; + OriginalFontSize := ACanvas.Font.Size; + ACanvas.Pen.Color := clGray; + ACanvas.Pen.Style := psDot; + ACanvas.Pen.Width := 1; + ACanvas.Font.Color := clWindowText; + ACanvas.Brush.Style := bsClear; + ACanvas.Font.Size := 8; + var + HorzYClamped := Max( DrawableContentRect.Top, Min( FMouseLastChartPos.Y, DrawableContentRect.Bottom ) ); + ACanvas.MoveTo( DrawableContentRect.Left, HorzYClamped ); + ACanvas.LineTo( DrawableContentRect.Right, HorzYClamped ); + if ( ScaleY > 1E-6 ) and ( PriceRect_Local.Height > 0 ) then + begin + PriceAtMouseY := CurrentMinYToUse + ( ( PriceRect_Local.Bottom - HorzYClamped ) / ScaleY ); + PriceStr := Format( '%.5f', [PriceAtMouseY] ); + TextX := DrawableContentRect.Right + CrosshairPriceTimeMargin; + TextY := HorzYClamped - ( ACanvas.TextHeight( PriceStr ) div 2 ); + ACanvas.TextOut( TextX, TextY, PriceStr ); + end; + if CurrentDrawingSlotWidth > 0 then + begin + RelativeX := FMouseLastChartPos.X - PriceRect_Local.Left; + if RelativeX >= 0 then + begin + CandleIndexInViewPortForCrosshair := RelativeX div CurrentDrawingSlotWidth; + VerticalLineX := PriceRect_Local.Left + ( CandleIndexInViewPortForCrosshair * CurrentDrawingSlotWidth ) + + ( FCandleWidthInPixels div 2 ); + if ( VerticalLineX >= DrawableContentRect.Left ) and ( VerticalLineX <= DrawableContentRect.Right ) then + begin + ACanvas.MoveTo( VerticalLineX, DrawableContentRect.Top ); + ACanvas.LineTo( VerticalLineX, DrawableContentRect.Bottom ); + var + ActualCacheIdxForCrosshair := FDisplayStartIndex + CandleIndexInViewPortForCrosshair; + if ( ActualCacheIdxForCrosshair >= 0 ) and ( ActualCacheIdxForCrosshair < FActiveOHLCCache.Count ) then + begin + CrosshairCandle := FActiveOHLCCache.Candles[ActualCacheIdxForCrosshair]; + if ( CrosshairCandle.OriginalDataIndexStart >= 0 ) and + ( CrosshairCandle.OriginalDataIndexStart < FDataController.GetTickDataLength ) then + begin + UTCTimeStart := FDataController.GetTickDataItem( CrosshairCandle.OriginalDataIndexStart ).OADateTime; + LocalTimeStart := TTimeZone.Local.ToLocalTime( UTCTimeStart ); + TimeStr := FormatDateTime( 'hh:nn:ss', LocalTimeStart ); + TextX := VerticalLineX - ( ACanvas.TextWidth( TimeStr ) div 2 ); + TextY := DrawableContentRect.Top - ACanvas.TextHeight( TimeStr ) - CrosshairPriceTimeMargin; + if TextY < ARect.Top then + TextY := ARect.Top; + ACanvas.TextOut( TextX, TextY, TimeStr ); + end; + end; + end; + end; + end; + ACanvas.Pen.Style := OriginalPenStyle; + ACanvas.Pen.Width := OriginalPenWidth; + ACanvas.Font.Color := OriginalFontColor; + ACanvas.Brush.Style := OriginalBrushStyle; + ACanvas.Font.Size := OriginalFontSize; + end; +end; + +procedure TChartForm.FormMouseWheel( Sender: TObject; Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean ); +const + MinDisplayCandleWidth = 1; + MaxDisplayCandleWidth = 100; + ZoomStep = 1; +var + LMousePosInPaintBox: TPoint; + OldCandleWidthInPixels, OriginalFDisplayStartIndex: Integer; + MouseX_InChartArea_Relative: Integer; + OldSlotWidth, NewSlotWidth: Integer; + GlobalCandleIndexUnderMouse_Float: Double; + ChartDrawWidth, DrawableCandlesInViewPort, MaxPossibleStartIndex: Integer; + ZoomToCursorActive: Boolean; +begin + Handled := False; + LMousePosInPaintBox := PaintBox.ScreenToClient( MousePos ); + if not PtInRect( PaintBox.ClientRect, LMousePosInPaintBox ) then + Exit; + ZoomToCursorActive := Assigned( FActiveOHLCCache ) and FActiveOHLCCache.IsValid and ( FActiveOHLCCache.Count > 0 ) and + ( FCurrentPriceRect.Width > 0 ) and PtInRect( FCurrentPriceRect, LMousePosInPaintBox ); + OriginalFDisplayStartIndex := FDisplayStartIndex; + OldCandleWidthInPixels := FCandleWidthInPixels; + if WheelDelta > 0 then + FCandleWidthInPixels := Min( MaxDisplayCandleWidth, FCandleWidthInPixels + ZoomStep ) + else if WheelDelta < 0 then + FCandleWidthInPixels := Max( MinDisplayCandleWidth, FCandleWidthInPixels - ZoomStep ) + else + Exit; + if FCandleWidthInPixels = OldCandleWidthInPixels then + Exit; + NewSlotWidth := FCandleWidthInPixels + FCandleSpacing; + if NewSlotWidth <= 0 then + begin + FCandleWidthInPixels := OldCandleWidthInPixels; + Exit; + end; + if ZoomToCursorActive then + begin + OldSlotWidth := OldCandleWidthInPixels + FCandleSpacing; + if OldSlotWidth <= 0 then + ZoomToCursorActive := False + else + begin + MouseX_InChartArea_Relative := LMousePosInPaintBox.X - FCurrentPriceRect.Left; + GlobalCandleIndexUnderMouse_Float := OriginalFDisplayStartIndex + ( MouseX_InChartArea_Relative / OldSlotWidth ); + FDisplayStartIndex := Round( GlobalCandleIndexUnderMouse_Float - ( MouseX_InChartArea_Relative / NewSlotWidth ) ); + end; + end; + if FCurrentPriceRect.Width > 0 then + ChartDrawWidth := FCurrentPriceRect.Width + else + ChartDrawWidth := PaintBox.ClientWidth - ( 2 * DefaultChartMargin ); + DrawableCandlesInViewPort := Max( 1, ChartDrawWidth div NewSlotWidth ); + MaxPossibleStartIndex := 0; + if Assigned( FActiveOHLCCache ) and ( FActiveOHLCCache.Count > 0 ) then + MaxPossibleStartIndex := FActiveOHLCCache.Count - DrawableCandlesInViewPort; + if MaxPossibleStartIndex < 0 then + MaxPossibleStartIndex := 0; + FDisplayStartIndex := Max( 0, FDisplayStartIndex ); + FDisplayStartIndex := Min( FDisplayStartIndex, MaxPossibleStartIndex ); + if Assigned( FActiveOHLCCache ) and ( FActiveOHLCCache.Count = 0 ) then + FDisplayStartIndex := 0; + Memo.Lines.Add( Format( 'MouseWheel: CW: %d->%d. NewStartIdx: %d. ZoomToCursor: %s', [OldCandleWidthInPixels, FCandleWidthInPixels, + FDisplayStartIndex, BoolToStr( ZoomToCursorActive, True )] ) ); + PaintBox.Invalidate; + Handled := True; + PaintBoxMouseMove( Sender, Shift, LMousePosInPaintBox.X, LMousePosInPaintBox.Y ); +end; + +procedure TChartForm.GoToCurrentButtonClick( Sender: TObject ); +var + ChartDrawWidth, CurrentDrawingSlotWidth, NumCandlesDrawableInViewPort, NewDisplayStartIndex: Integer; +begin + Memo.Lines.Add( FormatDateTime( 'yyyy-mm-dd hh:nn:ss', Now ) + ' - GoToCurrentButton clicked.' ); + if not Assigned( FActiveOHLCCache ) or not FActiveOHLCCache.IsValid or ( FActiveOHLCCache.Count = 0 ) then + begin + Memo.Lines.Add( 'GoToCurrent: No data or cache invalid.' ); + Exit; + end; + if FCurrentPriceRect.Width > 0 then + ChartDrawWidth := FCurrentPriceRect.Width + else + ChartDrawWidth := PaintBox.ClientWidth - ( 2 * DefaultChartMargin ); + CurrentDrawingSlotWidth := FCandleWidthInPixels + FCandleSpacing; + if CurrentDrawingSlotWidth <= 0 then + begin + Memo.Lines.Add( 'GoToCurrent: Invalid candle slot width.' ); + Exit; + end; + NumCandlesDrawableInViewPort := Max( 1, ChartDrawWidth div CurrentDrawingSlotWidth ); + NewDisplayStartIndex := Max( 0, FActiveOHLCCache.Count - NumCandlesDrawableInViewPort ); + if FActiveOHLCCache.Count > 0 then + NewDisplayStartIndex := Min( NewDisplayStartIndex, FActiveOHLCCache.Count - 1 ); + if FDisplayStartIndex <> NewDisplayStartIndex then + begin + FDisplayStartIndex := NewDisplayStartIndex; + Memo.Lines.Add( Format( 'GoToCurrent: Scrolled to display last candles. New StartIdx: %d', [FDisplayStartIndex] ) ); + PaintBox.Invalidate; + end + else + Memo.Lines.Add( 'GoToCurrent: Already showing the latest candles.' ); +end; + +end. diff --git a/ChartDisplay/ChartDisplayProject.dpr b/ChartDisplay/ChartDisplayProject.dpr new file mode 100644 index 0000000..9d12255 --- /dev/null +++ b/ChartDisplay/ChartDisplayProject.dpr @@ -0,0 +1,16 @@ +program ChartDisplayProject; + +uses + Vcl.Forms, + ChartDisplayMain in 'ChartDisplayMain.pas' {ChartForm}, + Myc.OHLCCache in '..\common\Myc.OHLCCache.pas', + Myc.ChartDataController in '..\common\Myc.ChartDataController.pas'; + +{$R *.res} + +begin + Application.Initialize; + Application.MainFormOnTaskbar := True; + Application.CreateForm(TChartForm, ChartForm); + Application.Run; +end. diff --git a/ChartDisplay/ChartDisplayProject.dproj b/ChartDisplay/ChartDisplayProject.dproj new file mode 100644 index 0000000..80778da --- /dev/null +++ b/ChartDisplay/ChartDisplayProject.dproj @@ -0,0 +1,1143 @@ + + + {969CBF7A-035D-4583-9619-6E58BDF60C88} + 20.3 + VCL + True + Release + Win64 + ChartDisplayProject + 2 + Application + ChartDisplayProject.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;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace) + $(BDS)\bin\delphi_PROJECTICON.ico + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + ChartDisplayProject + ..\common;$(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;appanalytics;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 + + + vclwinx;fmx;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;FireDACCommonODBC;FireDACCommonDriver;appanalytics;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 + + + DEBUG;$(DCC_Define) + true + false + true + true + true + true + true + + + false + PerMonitorV2 + true + 1033 + + + PerMonitorV2 + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + PerMonitorV2 + + + PerMonitorV2 + + + + MainSource + + +
ChartForm
+ dfm +
+ + + + Base + + + Cfg_1 + Base + + + Cfg_2 + Base + +
+ + Delphi.Personality.12 + Application + + + + ChartDisplayProject.dpr + + + Embarcadero C++Builder Office 2000 Servers Package + Embarcadero C++Builder Office XP Servers Package + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + + + ChartDisplayProject.exe + true + + + + + ChartDisplayProject.exe + true + + + + + ChartDisplayProject.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 + + + + + + + + + + + + + + + + + False + True + + + 12 + + + + +
diff --git a/ChartDisplay/ChartDisplayProject.res b/ChartDisplay/ChartDisplayProject.res new file mode 100644 index 0000000000000000000000000000000000000000..9fda4f16dfdf62dc82d659f947c9bb7935dacb0d GIT binary patch literal 154852 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�Uz6FZEM1|vJ4Z5 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 literal 0 HcmV?d00001 diff --git a/FIX-Connect/FIXConnectProject.dpr b/FIX-Connect/FIXConnectProject.dpr new file mode 100644 index 0000000..dbe2b28 --- /dev/null +++ b/FIX-Connect/FIXConnectProject.dpr @@ -0,0 +1,14 @@ +program FIXConnectProject; + +uses + Vcl.Forms, + MainUnit in 'MainUnit.pas' {Form1}; + +{$R *.res} + +begin + Application.Initialize; + Application.MainFormOnTaskbar := True; + Application.CreateForm(TForm1, Form1); + Application.Run; +end. diff --git a/FIX-Connect/FIXConnectProject.dproj b/FIX-Connect/FIXConnectProject.dproj new file mode 100644 index 0000000..c24eb00 --- /dev/null +++ b/FIX-Connect/FIXConnectProject.dproj @@ -0,0 +1,1125 @@ + + + {47B13125-8991-4584-9CFD-EF4C93BDAF21} + 20.3 + VCL + True + Debug + Win64 + FIXConnectProject + 3 + Application + FIXConnectProject.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;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace) + $(BDS)\bin\delphi_PROJECTICON.ico + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + FIXConnectProject + + + 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 + + + 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 + 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 + + + DEBUG;$(DCC_Define) + true + false + true + true + true + true + true + + + false + PerMonitorV2 + + + PerMonitorV2 + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + PerMonitorV2 + + + PerMonitorV2 + + + + MainSource + + +

Form1
+ dfm + + + Base + + + Cfg_1 + Base + + + Cfg_2 + Base + + + + Delphi.Personality.12 + Application + + + + FIXConnectProject.dpr + + + + + + FIXConnectProject.exe + true + + + + + FIXConnectProject.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/FIX-Connect/FIXConnectProject.res b/FIX-Connect/FIXConnectProject.res new file mode 100644 index 0000000000000000000000000000000000000000..f07804fac038dd2d631542f1d452dee6128a6966 GIT binary patch literal 154840 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 Z@C*jvu3yCU5!d09KKR4jkJiQS{vW(3(`*0$ literal 0 HcmV?d00001 diff --git a/FIX-Connect/MainUnit.dfm b/FIX-Connect/MainUnit.dfm new file mode 100644 index 0000000..03c322a --- /dev/null +++ b/FIX-Connect/MainUnit.dfm @@ -0,0 +1,88 @@ +object Form1: TForm1 + Left = 0 + Top = 0 + Caption = 'Form1' + ClientHeight = 633 + ClientWidth = 803 + Color = clBtnFace + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -12 + Font.Name = 'Segoe UI' + Font.Style = [] + OnCreate = FormCreate + OnDestroy = FormDestroy + TextHeight = 15 + object Label1: TLabel + Left = 88 + Top = 11 + Width = 34 + Height = 15 + Caption = 'Label1' + end + object Label2: TLabel + Left = 88 + Top = 75 + Width = 34 + Height = 15 + Caption = 'Label2' + end + object Label3: TLabel + Left = 568 + Top = 80 + Width = 34 + Height = 15 + Caption = 'Label3' + end + object ConnectButton: TButton + Left = 88 + Top = 112 + Width = 137 + Height = 25 + Caption = 'Connect' + TabOrder = 0 + OnClick = ConnectButtonClick + end + object PasswordEdit: TEdit + Left = 160 + Top = 72 + Width = 121 + Height = 23 + TabOrder = 1 + Text = 'wBz687@6rXdKkNK' + end + object LogMemo: TMemo + Left = 88 + Top = 160 + Width = 513 + Height = 345 + Lines.Strings = ( + 'LogMemo') + TabOrder = 2 + end + object SenderCompIDEdit: TEdit + Left = 160 + Top = 8 + Width = 121 + Height = 23 + TabOrder = 3 + Text = 'demo.pepperstoneuk.4132560' + end + object DisconnectButton: TButton + Left = 231 + Top = 112 + Width = 137 + Height = 25 + Caption = 'Disconnect' + TabOrder = 4 + OnClick = DisconnectButtonClick + end + object UserIdEdit: TEdit + Left = 160 + Top = 43 + Width = 121 + Height = 23 + TabOrder = 5 + Text = '4132560' + end +end diff --git a/FIX-Connect/MainUnit.pas b/FIX-Connect/MainUnit.pas new file mode 100644 index 0000000..3c30cc1 --- /dev/null +++ b/FIX-Connect/MainUnit.pas @@ -0,0 +1,463 @@ +unit MainUnit; + +interface + +uses + Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, + Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, + IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdGlobal, IdException, + System.DateUtils, System.StrUtils; + +const + FIX_SEPARATOR = #1; // SOH character for FIX messages + + // Broker specific connection details - SET TO DEMO & PLAIN TEXT + BROKER_HOST = 'demo-uk-eqx-01.p.c-trader.com'; + BROKER_PLAINTEXT_PORT = 5201; // Plain text port + + TARGET_COMP_ID_CONST = 'CSERVER'; + SENDER_SUB_ID_CONST = 'QUOTE'; // For Price Connection + // Based on your observation that "NO exception" occurs with 57=QUOTE + // Standard cTrader examples often use 'TRADE' here. + TARGET_SUB_ID_CONST = 'QUOTE'; // << ACHTUNG: Testwert basierend auf Ihrer letzten Beobachtung + +type + TForm1 = class(TForm) + ConnectButton: TButton; + PasswordEdit: TEdit; + LogMemo: TMemo; + Label1: TLabel; + SenderCompIDEdit: TEdit; + Label2: TLabel; + DisconnectButton: TButton; + UserIdEdit: TEdit; + Label3: TLabel; + procedure ConnectButtonClick(Sender: TObject); + procedure FormCreate(Sender: TObject); + procedure FormDestroy(Sender: TObject); + procedure DisconnectButtonClick(Sender: TObject); + private + FIdTCPClient: TIdTCPClient; + FMsgSeqNum: Integer; + + function CreateFixTag(Tag: Integer; Value: string): string; + function CalculateChecksum(const MessageBody: string): string; + function GetUtcDateTimeString: string; + procedure Log(const Text: string); + procedure SendFixMessage(const MessageType: string; const AdditionalBodyFields: TStrings); + procedure SendMarketDataRequest(const Symbol: string; const MDReqID: string); // NEU + public + end; + +var + Form1: TForm1; + +implementation + +{$R *.dfm} + +function TForm1.CreateFixTag(Tag: Integer; Value: string): string; +begin + Result := IntToStr(Tag) + '=' + Value + FIX_SEPARATOR; +end; + +function TForm1.GetUtcDateTimeString: string; +var + nowUtc: TDateTime; +begin + nowUtc := TTimeZone.Local.ToUniversalTime(Now); + Result := FormatDateTime('yyyymmdd-hh:nn:ss.zzz', nowUtc); +end; + +function TForm1.CalculateChecksum(const MessageBody: string): string; +var + sum: Cardinal; + i: Integer; + checkSumVal: Integer; +begin + sum := 0; + for i := 1 to Length(MessageBody) do + begin + sum := sum + Ord(MessageBody[i]); + end; + checkSumVal := sum mod 256; + Result := Format('%.3d', [checkSumVal]); +end; + +procedure TForm1.Log(const Text: string); +begin + if Assigned(LogMemo) then + begin + LogMemo.Lines.Add(FormatDateTime('[hh:nn:ss.zzz] ', Time) + Text); + end; +end; + +procedure TForm1.FormCreate(Sender: TObject); +begin + FMsgSeqNum := 1; + Log('FormCreate: Initializing components for PLAIN TEXT connection.'); + + try + FIdTCPClient := TIdTCPClient.Create(nil); + Log('FormCreate: TCPClient created.'); + except + on E: Exception do + begin + Log('CRITICAL ERROR during FormCreate TCPClient creation: ' + E.Message); + ShowMessage('CRITICAL ERROR during FormCreate: ' + E.Message); + FIdTCPClient := nil; + Exit; + end; + end; + + if Assigned(FIdTCPClient) then + begin + FIdTCPClient.Host := BROKER_HOST; + FIdTCPClient.Port := BROKER_PLAINTEXT_PORT; + FIdTCPClient.ReadTimeout := 10000; // 10 Sekunden Timeout für Leseoperationen + FIdTCPClient.ConnectTimeout := 10000; + Log('FormCreate: TCPClient configured for PLAIN TEXT - Host: ' + FIdTCPClient.Host + ' Port: ' + IntToStr(FIdTCPClient.Port)); + end + else + begin + Log('FormCreate: FIdTCPClient is NIL after creation attempt. Network functions disabled.'); + end; + + // --- WICHTIG: DIESE MIT IHREN EXAKTEN DEMO-ZUGANGSDATEN ERSETZEN --- + SenderCompIDEdit.Text := 'demo.pepperstoneuk.4132560'; + UserIdEdit.Text := '4132560'; + PasswordEdit.Text := 'testing'; // BITTE DAS VON PEPPERSTONE BESTÄTIGTE DEMO-PASSWORT VERWENDEN! + Log('FormCreate: Default credentials set. SenderCompID: ' + SenderCompIDEdit.Text + ', UserID: ' + UserIdEdit.Text); +end; + +procedure TForm1.FormDestroy(Sender: TObject); +var + logoutFields: TStringList; + currentSenderCompID: string; +begin + Log('FormDestroy: Cleaning up.'); + if Assigned(FIdTCPClient) and FIdTCPClient.Connected then + begin + Log('FormDestroy: Still connected. Sending Logout message...'); + currentSenderCompID := Trim(SenderCompIDEdit.Text); + if currentSenderCompID <> '' then + begin + logoutFields := TStringList.Create; + try + logoutFields.Add(CreateFixTag(49, currentSenderCompID)); + logoutFields.Add(CreateFixTag(56, TARGET_COMP_ID_CONST)); + logoutFields.Add(CreateFixTag(34, IntToStr(FMsgSeqNum))); + logoutFields.Add(CreateFixTag(52, GetUtcDateTimeString)); + SendFixMessage('5', logoutFields); + finally + FreeAndNil(logoutFields); + end; + end + else + begin + Log('FormDestroy: Cannot send Logout - SenderCompID is empty on form.'); + end; + FIdTCPClient.Disconnect; + Log('FormDestroy: Disconnected.'); + end; + FreeAndNil(FIdTCPClient); + Log('FormDestroy: Components freed.'); +end; + +procedure TForm1.SendFixMessage(const MessageType: string; const AdditionalBodyFields: TStrings); +var + headerBuilder: TStringBuilder; + bodyProperBuilder: TStringBuilder; + fullMessageBuilder: TStringBuilder; + actualBodyLength: Integer; + checksum: string; + i: Integer; + rawMessage: string; +begin + if not (Assigned(FIdTCPClient) and FIdTCPClient.Connected) then + begin + Log('Error: Not connected to server (SendFixMessage for MsgType: ' + MessageType + ').'); + Exit; + end; + + headerBuilder := TStringBuilder.Create; + bodyProperBuilder := TStringBuilder.Create; + fullMessageBuilder := TStringBuilder.Create; + + try + bodyProperBuilder.Append(CreateFixTag(35, MessageType)); + if Assigned(AdditionalBodyFields) then + begin + for i := 0 to AdditionalBodyFields.Count - 1 do + begin + bodyProperBuilder.Append(AdditionalBodyFields[i]); + end; + end; + + actualBodyLength := Length(bodyProperBuilder.ToString); + Log('SendFixMessage: MsgType=' + MessageType + ', Calculated actualBodyLength (Tag 9 Value) = ' + IntToStr(actualBodyLength)); + + headerBuilder.Append(CreateFixTag(8, 'FIX.4.4')); + headerBuilder.Append(CreateFixTag(9, IntToStr(actualBodyLength))); + + fullMessageBuilder.Append(headerBuilder.ToString); + fullMessageBuilder.Append(bodyProperBuilder.ToString); + + checksum := CalculateChecksum(fullMessageBuilder.ToString); + fullMessageBuilder.Append(CreateFixTag(10, checksum)); + + rawMessage := fullMessageBuilder.ToString; + Log('Sending (MsgType ' + MessageType + '): ' + StringReplace(rawMessage, FIX_SEPARATOR, '|', [rfReplaceAll, rfIgnoreCase])); + + try + FIdTCPClient.IOHandler.Write(rawMessage, IndyTextEncoding_UTF8); + FIdTCPClient.IOHandler.WriteBufferFlush(); + except + on E: EIdException do + begin + Log('Error sending message (MsgType ' + MessageType + ', Indy Exception): ' + E.ClassName + ': ' + E.Message); + Exit; + end; + on E: Exception do + begin + Log('Error sending message (MsgType ' + MessageType + ', General Exception): ' + E.Message); + Exit; + end; + end; + Inc(FMsgSeqNum); + finally + FreeAndNil(headerBuilder); + FreeAndNil(bodyProperBuilder); + FreeAndNil(fullMessageBuilder); + end; +end; + +// NEUE Prozedur für Market Data Request +procedure TForm1.SendMarketDataRequest(const Symbol: string; const MDReqID: string); +var + mdFields: TStringList; + tempFieldValue: string; +begin + if not (Assigned(FIdTCPClient) and FIdTCPClient.Connected) then + begin + Log('Error: Not connected to server (SendMarketDataRequest).'); + Exit; + end; + + Log('SendMarketDataRequest: Preparing for Symbol: ' + Symbol + ', MDReqID: ' + MDReqID); + mdFields := TStringList.Create; + try + // Tag 262: MDReqID + mdFields.Add(CreateFixTag(262, MDReqID)); + Log('SendMarketDataRequest: Tag 262 Value="' + MDReqID + '" (Length=' + IntToStr(Length(MDReqID)) + ')'); + + // Tag 263: SubscriptionRequestType (0=Snapshot, 1=Snapshot+Updates, 2=Disable previous) + tempFieldValue := '1'; // Snapshot + Updates + mdFields.Add(CreateFixTag(263, tempFieldValue)); + Log('SendMarketDataRequest: Tag 263 Value="' + tempFieldValue + '" (Length=' + IntToStr(Length(tempFieldValue)) + ')'); + + // Tag 264: MarketDepth (0=Full Book, 1=Top of Book) + tempFieldValue := '1'; // Top of Book + mdFields.Add(CreateFixTag(264, tempFieldValue)); + Log('SendMarketDataRequest: Tag 264 Value="' + tempFieldValue + '" (Length=' + IntToStr(Length(tempFieldValue)) + ')'); + + // Tag 267: NoMDEntryTypes (Anzahl der MDEntryType-Gruppen) + tempFieldValue := '2'; // Für Bid und Offer + mdFields.Add(CreateFixTag(267, tempFieldValue)); + Log('SendMarketDataRequest: Tag 267 Value="' + tempFieldValue + '" (Length=' + IntToStr(Length(tempFieldValue)) + ')'); + + // Tag 269: MDEntryType (0=Bid) + tempFieldValue := '0'; + mdFields.Add(CreateFixTag(269, tempFieldValue)); + Log('SendMarketDataRequest: Tag 269 (Bid) Value="' + tempFieldValue + '" (Length=' + IntToStr(Length(tempFieldValue)) + ')'); + + // Tag 269: MDEntryType (1=Offer) + tempFieldValue := '1'; + mdFields.Add(CreateFixTag(269, tempFieldValue)); + Log('SendMarketDataRequest: Tag 269 (Offer) Value="' + tempFieldValue + '" (Length=' + IntToStr(Length(tempFieldValue)) + ')'); + + // Tag 146: NoRelatedSym (Anzahl der Instrumente) + tempFieldValue := '1'; // Für ein Instrument + mdFields.Add(CreateFixTag(146, tempFieldValue)); + Log('SendMarketDataRequest: Tag 146 Value="' + tempFieldValue + '" (Length=' + IntToStr(Length(tempFieldValue)) + ')'); + + // Tag 55: Symbol + // WICHTIG: Ersetzen Sie '1' durch eine gültige cTrader Symbol ID für die Demo-Umgebung! + // Dies ist oft eine numerische ID (z.B. "1" für EURUSD, "22" für GER30). + // Fragen Sie Ihren Broker (Pepperstone) nach den korrekten IDs für die Demo. + mdFields.Add(CreateFixTag(55, Symbol)); + Log('SendMarketDataRequest: Tag 55 Value="' + Symbol + '" (Length=' + IntToStr(Length(Symbol)) + ')'); + + SendFixMessage('V', mdFields); // 'V' ist MsgType für Market Data Request + finally + FreeAndNil(mdFields); + end; +end; + +procedure TForm1.ConnectButtonClick(Sender: TObject); +var + logonFields: TStringList; + currentSenderCompID: string; + currentPasswordValue: string; + currentUsernameFromEdit: string; + sendingTime: string; + response: string; + tempFieldValue: string; + // Für Market Data Request + symbolToRequest: string; + marketDataRequestID: string; +begin + Log('ConnectButtonClick: Initiated for PLAIN TEXT.'); + if not Assigned(FIdTCPClient) then + begin + Log('ConnectButtonClick: TCP Client not initialized. Check FormCreate logs.'); + Exit; + end; + + if FIdTCPClient.Connected then + begin + Log('ConnectButtonClick: Already connected. Please disconnect first.'); + Exit; + end; + + currentSenderCompID := Trim(SenderCompIDEdit.Text); + currentPasswordValue := PasswordEdit.Text; + currentUsernameFromEdit := Trim(UserIdEdit.Text); + + Log('ConnectButtonClick: Input Values - SenderCompID="' + currentSenderCompID + '" (Length=' + IntToStr(Length(currentSenderCompID)) + + '), UserID="' + currentUsernameFromEdit + '" (Length=' + IntToStr(Length(currentUsernameFromEdit)) + + '), Password="' + currentPasswordValue + '" (Length=' + IntToStr(Length(currentPasswordValue)) + ')'); + + if (currentSenderCompID = '') or (currentPasswordValue = '') or (currentUsernameFromEdit = '') then + begin + Log('ConnectButtonClick: SenderCompID, UserID, and Password are required.'); + Exit; + end; + + FMsgSeqNum := 1; + Log('Attempting to connect to DEMO (PLAIN TEXT): ' + FIdTCPClient.Host + ':' + IntToStr(FIdTCPClient.Port) + '...'); + try + FIdTCPClient.Connect; + Log('Connected successfully (PLAIN TEXT).'); + except + on E: EIdException do + begin + Log('Connection failed (Indy Exception): ' + E.ClassName + ': ' + E.Message); + Exit; + end; + on E: Exception do + begin + Log('Connection failed (General Exception): ' + E.ClassName + ': ' + E.Message); + Exit; + end; + end; + + logonFields := TStringList.Create; + try + sendingTime := GetUtcDateTimeString; + Log('ConnectButtonClick: SendingTime (Tag 52 Value)="' + sendingTime + '" (Length=' + IntToStr(Length(sendingTime)) + ')'); + + logonFields.Add(CreateFixTag(49, currentSenderCompID)); + logonFields.Add(CreateFixTag(56, TARGET_COMP_ID_CONST)); + Log('ConnectButtonClick: Tag 56 Value="' + TARGET_COMP_ID_CONST + '" (Length=' + IntToStr(Length(TARGET_COMP_ID_CONST)) + ')'); + logonFields.Add(CreateFixTag(50, SENDER_SUB_ID_CONST)); // 'QUOTE' für Preis-Feed + Log('ConnectButtonClick: Tag 50 Value="' + SENDER_SUB_ID_CONST + '" (Length=' + IntToStr(Length(SENDER_SUB_ID_CONST)) + ')'); + logonFields.Add(CreateFixTag(57, TARGET_SUB_ID_CONST)); // 'QUOTE' oder 'TRADE' - Testen Sie, was funktioniert! + Log('ConnectButtonClick: Tag 57 Value="' + TARGET_SUB_ID_CONST + '" (Length=' + IntToStr(Length(TARGET_SUB_ID_CONST)) + ')'); + tempFieldValue := IntToStr(FMsgSeqNum); + logonFields.Add(CreateFixTag(34, tempFieldValue)); + Log('ConnectButtonClick: Tag 34 Value="' + tempFieldValue + '" (Length=' + IntToStr(Length(tempFieldValue)) + ')'); + + logonFields.Add(CreateFixTag(52, sendingTime)); + + tempFieldValue := '0'; + logonFields.Add(CreateFixTag(98, tempFieldValue)); + Log('ConnectButtonClick: Tag 98 Value="' + tempFieldValue + '" (Length=' + IntToStr(Length(tempFieldValue)) + ')'); + tempFieldValue := '30'; + logonFields.Add(CreateFixTag(108, tempFieldValue)); + Log('ConnectButtonClick: Tag 108 Value="' + tempFieldValue + '" (Length=' + IntToStr(Length(tempFieldValue)) + ')'); + + tempFieldValue := 'Y'; // ResetSeqNumFlag=Y + logonFields.Add(CreateFixTag(141, tempFieldValue)); + Log('ConnectButtonClick: Tag 141 Value="' + tempFieldValue + '" (Length=' + IntToStr(Length(tempFieldValue)) + ')'); + + logonFields.Add(CreateFixTag(553, currentUsernameFromEdit)); + logonFields.Add(CreateFixTag(554, currentPasswordValue)); + + SendFixMessage('A', logonFields); // Logon senden + + // Kurze Pause und dann Market Data Request senden + //Sleep(500); // 0.5 Sekunden warten + + // WICHTIG: Ersetzen Sie '1' durch eine gültige cTrader Symbol ID für Pepperstone Demo + // und "MDReq_001" durch eine eindeutige ID für Ihren Request. +// symbolToRequest := '1'; // Beispiel: EURUSD (numerische ID von Pepperstone erfragen!) +// marketDataRequestID := currentUsernameFromEdit + '_' + symbolToRequest + '_' + IntToStr(FMsgSeqNum); // Eindeutige MDReqID + +// Log('ConnectButtonClick: Proceeding to send Market Data Request.'); +// SendMarketDataRequest(symbolToRequest, marketDataRequestID); + + // Auf Antwort warten + try + Log('Waiting for response after Logon and MD Request (PLAIN TEXT)...'); + repeat + response := FIdTCPClient.IOHandler.ReadLn(FIX_SEPARATOR, FIdTCPClient.ReadTimeout, -1, IndyTextEncoding_UTF8); +// response := FIdTCPClient.IOHandler.AllData(IndyTextEncoding_UTF8); + Log('Received (PLAIN TEXT): ' + StringReplace(response, FIX_SEPARATOR, '|', [rfReplaceAll, rfIgnoreCase])); + until (response = '') or (LeftStr(response, 3)='10='); + + // Hier Parsing-Logik für die Antwort einfügen + // z.B. auf 35=W (Snapshot), 35=X (Incremental), 35=Y (Reject) prüfen + + except + on E: EIdException do + begin + Log('Error reading response or timeout (PLAIN TEXT - Indy Exception): ' + E.ClassName + ': ' + E.Message); + end; + on E: Exception do + begin + Log('Error reading response (PLAIN TEXT - General Exception): ' + E.ClassName + ': ' + E.Message); + end; + end; + finally + FreeAndNil(logonFields); + end; +end; + +procedure TForm1.DisconnectButtonClick(Sender: TObject); +var + logoutFields: TStringList; + currentSenderCompID: string; +begin + Log('DisconnectButtonClick: Initiated.'); + if Assigned(FIdTCPClient) and FIdTCPClient.Connected then + begin + Log('DisconnectButtonClick: Sending Logout message...'); + currentSenderCompID := Trim(SenderCompIDEdit.Text); + if currentSenderCompID <> '' then + begin + logoutFields := TStringList.Create; + try + logoutFields.Add(CreateFixTag(49, currentSenderCompID)); + logoutFields.Add(CreateFixTag(56, TARGET_COMP_ID_CONST)); + logoutFields.Add(CreateFixTag(34, IntToStr(FMsgSeqNum))); + logoutFields.Add(CreateFixTag(52, GetUtcDateTimeString)); + SendFixMessage('5', logoutFields); + finally + FreeAndNil(logoutFields); + end; + end + else + begin + Log('DisconnectButtonClick: Cannot send Logout - SenderCompID is empty.'); + end; + FIdTCPClient.Disconnect; + Log('DisconnectButtonClick: Disconnected from server.'); + end + else + begin + Log('DisconnectButtonClick: Not connected.'); + end; +end; + +end. diff --git a/Loader/TickLoader.dpr b/Loader/TickLoader.dpr new file mode 100644 index 0000000..59047ce --- /dev/null +++ b/Loader/TickLoader.dpr @@ -0,0 +1,66 @@ +program TickLoader; + +{$APPTYPE CONSOLE} + +uses + System.SysUtils, + System.Math, // Added for Min function + Myc.TickLoader in 'Myc.TickLoader.pas'; + +var + TickData: TArray; + DataFile: string; + I: Integer; // Loop counter + RecordsToDisplay: Integer; +begin + ReportMemoryLeaksOnShutdown := True; + + DataFile := 'C:\Users\Brummel\Documents\cAlgo\TickDataBacktests\Pep_GER40_2017.tab'; + + try + WriteLn('Attempting to load tick data from: ', DataFile); + TickData := LoadTickDataSeries(DataFile); + WriteLn(Format('%d tick data records loaded.', [Length(TickData)])); + WriteLn(''); // Add a blank line for better readability + + if Length(TickData) > 0 then + begin + WriteLn('--- Sample Tick Data Output ---'); + WriteLn('Index | OADateTime | Ask | Bid'); + WriteLn('------------------------------------'); + + // Determine how many records to display, e.g., first 10 or all if fewer than 10 + RecordsToDisplay := Min(Length(TickData), 10); // Display up to the first 10 records + + for I := 0 to RecordsToDisplay - 1 do // Iterate through the selected number of records + begin + // Output each field of the TTickData record, formatted for alignment + WriteLn(Format('%-5d | %-14.5f | %-8.5f | %.5f', [ + I, + TickData[I].OADateTime, + TickData[I].Ask, + TickData[I].Bid + ])); + end; + WriteLn('------------------------------------'); + if Length(TickData) > RecordsToDisplay then + begin + WriteLn(Format('(Showing first %d of %d records)', [RecordsToDisplay, Length(TickData)])); + end; + end + else + begin + WriteLn('No tick data loaded to display.'); + end; + + except + on E: Exception do + begin + WriteLn(Format('Error: %s', [E.Message])); + end; + end; + + WriteLn(''); + WriteLn('Press Enter to exit.'); + ReadLn; +end. diff --git a/Loader/TickLoader.dproj b/Loader/TickLoader.dproj new file mode 100644 index 0000000..494f008 --- /dev/null +++ b/Loader/TickLoader.dproj @@ -0,0 +1,1172 @@ + + + {E0687624-A418-4998-ACCB-000BC8C02CE8} + 20.3 + None + True + Debug + Win64 + TickLoader + 2 + Console + TickLoader.dpr + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + .\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + TickLoader + + + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_192x192.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + $(BDS)\bin\Artwork\Android\FM_AdaptiveIcon_Monochrome.xml + $(BDS)\bin\Artwork\Android\FM_AdaptiveIcon_Foreground.xml + $(BDS)\bin\Artwork\Android\FM_AdaptiveIcon_Background.xml + $(BDS)\bin\Artwork\Android\FM_VectorizedSplash.xml + $(BDS)\bin\Artwork\Android\FM_VectorizedSplashDark.xml + $(BDS)\bin\Artwork\Android\FM_VectorizedSplashV31.xml + $(BDS)\bin\Artwork\Android\FM_VectorizedSplashV31Dark.xml + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png + false + true + $(BDS)\bin\Artwork\Android\FM_VectorizedNotificationIcon.xml + + + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_192x192.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + $(BDS)\bin\Artwork\Android\FM_AdaptiveIcon_Monochrome.xml + $(BDS)\bin\Artwork\Android\FM_AdaptiveIcon_Foreground.xml + $(BDS)\bin\Artwork\Android\FM_AdaptiveIcon_Background.xml + $(BDS)\bin\Artwork\Android\FM_VectorizedSplash.xml + $(BDS)\bin\Artwork\Android\FM_VectorizedSplashDark.xml + $(BDS)\bin\Artwork\Android\FM_VectorizedSplashV31.xml + $(BDS)\bin\Artwork\Android\FM_VectorizedSplashV31Dark.xml + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png + false + true + $(BDS)\bin\Artwork\Android\FM_VectorizedNotificationIcon.xml + + + vclwinx;fmx;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;FireDACCommonODBC;FireDACCommonDriver;appanalytics;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 + 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 + true + $(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;appanalytics;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) + true + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + 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 + + + DEBUG;$(DCC_Define) + true + false + true + true + true + true + true + + + false + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + + MainSource + + + + Base + + + Cfg_1 + Base + + + Cfg_2 + Base + + + + Delphi.Personality.12 + Application + + + + TickLoader.dpr + + + + + + true + + + + + true + + + + + true + + + + + TickLoader.exe + 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 + + + + + + + + + + + + + + + + + False + False + False + True + + + 12 + + + + + diff --git a/Loader/TickLoader.res b/Loader/TickLoader.res new file mode 100644 index 0000000000000000000000000000000000000000..bf572307691fab4ba172ac48cd5c9239d147af8b GIT binary patch literal 96 zcmZQzU|>)H;{X347|28cEI^(O5dZ(r#Sp;Y!{Epe!r;c>&k)4m3uHM0X?F%!AS)QE O%YcCsqy-F^fFuBpn+qcV literal 0 HcmV?d00001 diff --git a/common/Myc.ChartDataController.pas b/common/Myc.ChartDataController.pas new file mode 100644 index 0000000..cbcef03 --- /dev/null +++ b/common/Myc.ChartDataController.pas @@ -0,0 +1,435 @@ +unit Myc.ChartDataController; + +interface + +uses + System.SysUtils, System.Classes, System.Generics.Collections, System.StrUtils, + Myc.TickLoader, Myc.OHLCCache; // OHLCCache for TOHLC, IGenericCandleBuilder, TF_ constants + +type + TTimeframeSetting = record + 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 + end; + + TChartDataController = class + private + FTickDataArray: TArray; + FOHLCCacheList: TList; + FTimeframeSettingsArray: TArray; // Now includes registered generic builders + FMemoLog: TStrings; + FGenericBuilders: TDictionary; // Keyed by TTimeframeSetting.Caption + + procedure InitializeBaseTimeframeSettings; // Renamed for clarity + function GetGenericBuilder(const ABuilderCaption: string): IGenericCandleBuilder; + public + constructor Create(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; + + procedure ClearAllDataAndCaches; + function GetTickDataCount: Integer; + function GetTickDataArrayPtr: PTTickData; + function GetTickDataLength: Integer; + function GetTickDataItem(AIndex: Integer): TTickData; + 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); + // GetGenericBuilderCaptions is removed. Use GetAvailableTimeframes. + function GetAvailableTimeframes: TArray; + end; + +implementation + +uses + System.DateUtils; // For FormatDateTime in logging + +const + dcSecsPerDayConst = 24 * 60 * 60; + +{ TChartDataController } + +constructor TChartDataController.Create(AMemoTarget: TStrings); +var + I: Integer; // Standard loop variable +begin + inherited Create; + FMemoLog := AMemoTarget; + FGenericBuilders := TDictionary.Create; + + InitializeBaseTimeframeSettings; // This populates FTimeframeSettingsArray + + FOHLCCacheList := TList.Create; // Create the TList object + // Add nil placeholders for each timeframe setting defined in FTimeframeSettingsArray + // This ensures FOHLCCacheList is parallel to FTimeframeSettingsArray from the start. + for I := 0 to High(FTimeframeSettingsArray) do + begin + FOHLCCacheList.Add(nil); // Add a nil item to the list + end; + + SetLength(FTickDataArray, 0); + if FMemoLog <> nil then + FMemoLog.Add('TChartDataController instance created. Base timeframes initialized.'); +end; + +destructor TChartDataController.Destroy; +begin + if FMemoLog <> nil then + FMemoLog.Add('TChartDataController instance destroying...'); + ClearAllDataAndCaches(); // This will free TOHLC objects + FreeAndNil(FOHLCCacheList); + FreeAndNil(FGenericBuilders); // Frees dictionary, ARC handles interfaces + if FMemoLog <> nil then + FMemoLog.Add('TChartDataController instance destroyed.'); + inherited Destroy; +end; + +procedure TChartDataController.InitializeBaseTimeframeSettings; +var + InitialSettings: TList; + ts: TTimeframeSetting; +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); + // 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); + FTimeframeSettingsArray := InitialSettings.ToArray; + finally + InitialSettings.Free; + end; +end; + +function TChartDataController.GetAvailableTimeframes: TArray; +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); +var + NewTimeframeSetting: TTimeframeSetting; + I: Integer; +begin + if not Assigned(ABuilder) then + begin + if FMemoLog <> nil then + FMemoLog.Add(Format('DataController: Attempt to register a nil generic builder for caption "%s".', [ABuilderCaption])); + Exit; + end; + + // Check for duplicate caption in existing timeframe settings (both base and previously registered builders) + for I := 0 to High(FTimeframeSettingsArray) do + 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])); + Exit; + end; + end; + + // Although the loop above should catch caption clashes for FTimeframeSettingsArray, + // FGenericBuilders is a separate dictionary. A builder might be re-registered + // with the same caption but perhaps a different description or instance. + // The current logic is to prevent adding a new TimeframeSetting if the caption exists. + // If a builder with the same caption is already in FGenericBuilders (but somehow not in FTimeframeSettingsArray, + // which would be an inconsistent state), the Add below would fail. + // For robustness, we ensure it's not in FGenericBuilders OR handle replacement. + // Given the check against FTimeframeSettingsArray already prevents duplicate *timeframe captions*, + // if ABuilderCaption is new for timeframes, it must be new for FGenericBuilders too. + + if FGenericBuilders.ContainsKey(ABuilderCaption) then + begin + // This case should ideally not be reached if the previous loop correctly exits + // for existing captions in FTimeframeSettingsArray. + // If it is reached, it implies an inconsistent state or a desire to replace only the builder object + // 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])); + FGenericBuilders.Remove(ABuilderCaption); + FGenericBuilders.Add(ABuilderCaption, ABuilder); + end + else + begin + // Add to builder dictionary + FGenericBuilders.Add(ABuilderCaption, ABuilder); + + // Add to FTimeframeSettingsArray + NewTimeframeSetting.Caption := ABuilderCaption; + NewTimeframeSetting.Description := ABuilderDescription; + NewTimeframeSetting.Seconds := TF_GENERIC_CALLBACK; + + SetLength(FTimeframeSettingsArray, Length(FTimeframeSettingsArray) + 1); + FTimeframeSettingsArray[High(FTimeframeSettingsArray)] := NewTimeframeSetting; // This is line 181 from your error reference. + + // Extend FOHLCCacheList with a nil placeholder - CORRECTED PART + 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])); + end; +end; + +function TChartDataController.GetGenericBuilder(const ABuilderCaption: string): IGenericCandleBuilder; +begin + Result := nil; + if not FGenericBuilders.TryGetValue(ABuilderCaption, Result) then + begin + if FMemoLog <> nil then + FMemoLog.Add(Format('DataController: Generic builder with caption/key "%s" not found in dictionary.', [ABuilderCaption])); + end; +end; + +procedure TChartDataController.LoadTickDataSeries(const AFileName: string); +begin + ClearAllDataAndCaches(); + try + FTickDataArray := Myc.TickLoader.LoadTickDataSeries(AFileName); + if FMemoLog <> nil then + FMemoLog.Add(Format('DataController: Loaded %d tick records from series: %s', + [Length(FTickDataArray), AFileName])); + except + on E: Exception do + begin + if FMemoLog <> nil then + FMemoLog.Add(Format('DataController: Error loading tick data from %s - %s', [AFileName, E.Message])); + SetLength(FTickDataArray, 0); + end; + end; +end; + +// ASelectedTimeframeCaption is the UNIQUE key (e.g. "M1", "Auto", or "Every 20 Ticks") +function TChartDataController.GetOrBuildCache(const ASelectedTimeframeCaption: string; + AAutoAggCandleWidth, AAutoAggCandleSpacing, AAutoAggPaintBoxClientWidth: Integer; + AForceRebuild: Boolean): TOHLC; +var + Index: Integer; + CacheNeedsRebuild: Boolean; + FoundTimeframe: Boolean; + CacheAtIndex: TOHLC; + LGenericBuilder: IGenericCandleBuilder; + CurrentTimeframeSetting: TTimeframeSetting; +begin + Result := nil; + FoundTimeframe := False; + Index := -1; + LGenericBuilder := nil; + + for var I := 0 to High(FTimeframeSettingsArray) do + begin + if CompareText(FTimeframeSettingsArray[I].Caption, ASelectedTimeframeCaption) = 0 then + begin + Index := I; + CurrentTimeframeSetting := FTimeframeSettingsArray[I]; + FoundTimeframe := True; + Break; + end; + end; + + if not FoundTimeframe then + begin + if FMemoLog <> nil then + FMemoLog.Add(Format('DataController: Timeframe setting for caption "%s" not found.', [ASelectedTimeframeCaption])); + Exit; + end; + + CacheAtIndex := FOHLCCacheList[Index]; + CacheNeedsRebuild := AForceRebuild; + + if CacheAtIndex = nil then + begin + CacheAtIndex := TOHLC.Create; + 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])); + 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])); + 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])); + 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 + begin + // For generic, CurrentTimeframeSetting.Caption is the Builder's caption. + // We check if the existing cache was built with this same builder. + // TOHLC.ApproxTimePerCandleText stores "(BuilderCaption)". + var ExpectedCacheBuilderInfo: string; + ExpectedCacheBuilderInfo := Format('(%s)', [CurrentTimeframeSetting.Caption]); // Caption is the builder's key + + 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])); + CacheNeedsRebuild := True; + end; + end; + + if CacheNeedsRebuild then + begin + if HasData then + begin + if FMemoLog <> nil then + FMemoLog.Add(Format('DataController: Building cache for [%s] (Desc: "%s")...', + [CurrentTimeframeSetting.Caption, CurrentTimeframeSetting.Description])); + + if CurrentTimeframeSetting.Seconds = TF_GENERIC_CALLBACK then + begin + // ASelectedTimeframeCaption IS the key for the generic builder in this new design. + LGenericBuilder := GetGenericBuilder(ASelectedTimeframeCaption); + 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])); + CacheAtIndex.ClearCache(); + Result := CacheAtIndex; + Exit; + end; + end; + + CacheAtIndex.Build( + FTickDataArray, + CurrentTimeframeSetting.Seconds, + AAutoAggCandleWidth, + AAutoAggCandleSpacing, + AAutoAggPaintBoxClientWidth, + FMemoLog, + LGenericBuilder // This will be nil if not TF_GENERIC_CALLBACK, or the found builder + ); + end + else + begin + if FMemoLog <> nil then + FMemoLog.Add('DataController: No TickData to build cache for ' + CurrentTimeframeSetting.Description); + CacheAtIndex.ClearCache(); + end; + end + else + begin + if FMemoLog <> nil then + FMemoLog.Add(Format('DataController: Using existing valid cache for [%s] (Desc: "%s")', + [CurrentTimeframeSetting.Caption, CurrentTimeframeSetting.Description])); + end; + Result := CacheAtIndex; +end; + +procedure TChartDataController.ClearAllDataAndCaches; +var + I: Integer; +begin + if FMemoLog <> nil then + FMemoLog.Add('DataController: Clearing all data and caches from instance...'); + SetLength(FTickDataArray, 0); // Clear the raw tick data + + if Assigned(FOHLCCacheList) then + begin + // First, free all TOHLC objects currently in the list + for I := 0 to FOHLCCacheList.Count - 1 do + begin + FreeAndNil(FOHLCCacheList[I]); // Free the TOHLC object + end; + FOHLCCacheList.Clear; // Clears the list, setting its Count to 0. + + // Re-populate FOHLCCacheList with nil placeholders, + // one for each setting in FTimeframeSettingsArray. + // This ensures FOHLCCacheList remains parallel to FTimeframeSettingsArray. + if Assigned(FTimeframeSettingsArray) then // FTimeframeSettingsArray should be assigned + begin + for I := 0 to High(FTimeframeSettingsArray) do // Iterate based on the actual array + begin + FOHLCCacheList.Add(nil); // Add a nil placeholder + end; + end; + end; + + // Note on FGenericBuilders and FTimeframeSettingsArray: + // This procedure currently does NOT clear registered generic builders from FGenericBuilders + // nor does it remove their corresponding entries from FTimeframeSettingsArray. + // If a full reset including unregistering builders is desired, FGenericBuilders.Clear and + // re-calling InitializeBaseTimeframeSettings (to reset FTimeframeSettingsArray) would be needed here, + // followed by re-initializing FOHLCCacheList. + // 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.'); +end; + +function TChartDataController.GetTickDataCount: Integer; +begin + Result := Length(FTickDataArray); +end; + +function TChartDataController.GetTickDataArrayPtr: PTTickData; +begin + if Length(FTickDataArray) > 0 then + Result := @FTickDataArray[0] + else + Result := nil; +end; + +function TChartDataController.GetTickDataLength: Integer; +begin + Result := Length(FTickDataArray); +end; + +function TChartDataController.GetTickDataItem(AIndex: Integer): TTickData; +begin + if (AIndex >= 0) and (AIndex < Length(FTickDataArray)) then + Result := FTickDataArray[AIndex] + else + begin + if FMemoLog <> nil then + FMemoLog.Add(Format('DataController: Attempted to access invalid tick data index %d. Count is %d.', [AIndex, Length(FTickDataArray)])); + Result := Default(TTickData); + end; +end; + +function TChartDataController.HasData: Boolean; +begin + Result := Length(FTickDataArray) > 0; +end; + +end. diff --git a/common/Myc.OHLCCache.pas b/common/Myc.OHLCCache.pas new file mode 100644 index 0000000..5aa552a --- /dev/null +++ b/common/Myc.OHLCCache.pas @@ -0,0 +1,513 @@ +(* ------------------------------------------------------------------------------ + Unit Name: Myc.OHLCCache + Author: Delphi Algo Trading Assistant (Generated by AI) + Date: May 21, 2025 + Purpose: This unit provides a class, TOHLC, designed to aggregate raw + tick data into OHLC (Open, High, Low, Close) candle data and + manage this cached candle data. It supports various aggregation + timeframes, including a generic callback mode using an interface, + and provides helper functions for data interpretation. + + Main Features: + - OHLC Candle Structure: Defines TCachedCandle record. + - TOHLC Class: Encapsulates OHLC candle building and storage. + * Aggregation Modes: + - Fixed Timeframes: Aggregates ticks into candles of a specified duration. + - Tick-by-Tick: Each incoming tick forms a separate OHLC candle. + - Auto Aggregation: Calculates aggregation to fit display width. + - Generic Builder: Uses a user-defined class implementing IGenericCandleBuilder + to determine new candle breaks. + * Data Caching, Global Price Range, Validity Status, Metadata, Informational Properties. + - Time Formatting: Helper function FormatTimeSpanFromSeconds. + + Key Constants: + - TF_AUTO_AGGREGATION, TF_TICK_BY_TICK, TF_GENERIC_CALLBACK. + Key Types: + - TCachedCandle, IGenericCandleBuilder, TOHLC. + Key Methods in TOHLC: + - Create, ClearCache, Build. + Dependencies: (As before) + ------------------------------------------------------------------------------ *) +unit Myc.OHLCCache; + +interface + +uses + System.Classes, System.SysUtils, System.Math, + System.Generics.Collections, System.DateUtils, + Myc.TickLoader; + +const + TF_AUTO_AGGREGATION = 0; + TF_TICK_BY_TICK = -1; + TF_GENERIC_CALLBACK = -2; // Constant remains, signifies generic mode + SecsPerDay_Double = 24.0 * 60.0 * 60.0; + +type + TCachedCandle = record + OpenPrice: Single; + HighPrice: Single; + LowPrice: Single; + ClosePrice: Single; + OriginalDataIndexStart: Integer; + OriginalDataIndexEnd: Integer; + TickVolume: Integer; + end; + + // Interface for generic candle building logic + IGenericCandleBuilder = interface + ['{B4F6A9C1-0A7E-42D3-847B-170705A8CC8D}'] // Added GUID + function Init(const ADataSet: TArray): Boolean; // Changed DataSet to ADataSet for clarity + function IsNewBar(AIndex: Integer; const ATick: TTickData): Boolean; // Changed Idx to AIndex, Tick to ATick + function GetCaption: string; // Method for the Caption property + property Caption: string read GetCaption; + end; + + TOHLC = class + private + FCachedCandles: TArray; + FGlobalMinY: Single; + FGlobalMaxY: Single; + FIsValid: Boolean; + FAggregationTimeframeSeconds: Int64; + FAutoAggCandleDisplayWidth: Integer; + FAutoAggPaintBoxClientWidth: Integer; + FAutoAggNumCandlesInCache: Integer; + FApproxTimePerCandleText: string; + FTotalDataTimeSpanText: string; + + function GetCount: Integer; + function GetCandle(Index: Integer): TCachedCandle; + public + constructor Create; + procedure ClearCache; + procedure Build(const ATickData: TArray; + ASelectedTimeframeSeconds: Int64; + AAutoAggCandleWidth: Integer; + AAutoAggCandleSpacing: Integer; + AAutoAggPaintBoxClientWidth: Integer; + AMemoForLogging: TStrings; + // Parameter changed from TGenericCandleProc to IGenericCandleBuilder + AGenericCandleBuilder: IGenericCandleBuilder = nil); + + property Candles[Index: Integer]: TCachedCandle read GetCandle; default; + property Count: Integer read GetCount; + property IsValid: Boolean read FIsValid; + property GlobalMinY: Single read FGlobalMinY; + property GlobalMaxY: Single read FGlobalMaxY; + property AggregationTimeframeSeconds: Int64 read FAggregationTimeframeSeconds; + property ApproxTimePerCandleText: string read FApproxTimePerCandleText; + property TotalDataTimeSpanText: string read FTotalDataTimeSpanText; + property AutoAggNumCandlesInCache: Integer read FAutoAggNumCandlesInCache; + end; + +implementation + +function FormatTimeSpanFromSeconds(const TotalSecondsInput: Double): string; +const + SecsPerMin = 60; + SecsPerHour = SecsPerMin * 60; + SecsPerDayConst = SecsPerHour * 24; +var + Days, Hours, Minutes, SecComponent: Int64; + sTotalSeconds, FracSec: Double; + ResultBuilder: TStringBuilder; +begin // Standard implementation as previously provided + sTotalSeconds := Abs(TotalSecondsInput); + if sTotalSeconds < 0.001 then + Result := 'less than 1 ms' + else if sTotalSeconds < 1.0 then + Result := Format('%.0f ms', [sTotalSeconds * 1000.0]) + else if sTotalSeconds < SecsPerMin then + Result := Format('%.2f seconds', [sTotalSeconds]) + else if sTotalSeconds < SecsPerHour then + Result := Format('%.1f minutes (approx. %d secs)', [sTotalSeconds / SecsPerMin, Floor(sTotalSeconds)]) + else if sTotalSeconds < SecsPerDayConst then + Result := Format('%.1f hours (approx. %d mins)', [sTotalSeconds / SecsPerHour, Floor(sTotalSeconds / SecsPerMin)]) + else + begin + ResultBuilder := TStringBuilder.Create; + try + Days := Trunc(sTotalSeconds / SecsPerDayConst); + sTotalSeconds := sTotalSeconds - (Days * SecsPerDayConst); + Hours := Trunc(sTotalSeconds / SecsPerHour); + sTotalSeconds := sTotalSeconds - (Hours * SecsPerHour); + Minutes := Trunc(sTotalSeconds / SecsPerMin); + sTotalSeconds := sTotalSeconds - (Minutes * SecsPerMin); + SecComponent := Trunc(sTotalSeconds); + FracSec := Frac(sTotalSeconds); + if Days > 0 then + ResultBuilder.Append(Format('%d days, ', [Days])); + if Hours > 0 then + ResultBuilder.Append(Format('%d hours, ', [Hours])); + if Minutes > 0 then + ResultBuilder.Append(Format('%d minutes, ', [Minutes])); + ResultBuilder.Append(Format('%d', [SecComponent])); + if FracSec > 0.001 then + ResultBuilder.Append(Format('.%02d', [Round(FracSec * 100)])); + ResultBuilder.Append(' seconds'); + Result := ResultBuilder.ToString; + finally + ResultBuilder.Free; + end; + end; + if TotalSecondsInput < 0.0 then + Result := '-' + Result; +end; + +{ TOHLC } + +constructor TOHLC.Create; +begin + ClearCache; +end; + +procedure TOHLC.ClearCache; +begin + FIsValid := False; + SetLength(FCachedCandles, 0); + FGlobalMinY := 0; + FGlobalMaxY := 0; + FAggregationTimeframeSeconds := 0; + FAutoAggCandleDisplayWidth := 0; + FAutoAggPaintBoxClientWidth := 0; + FAutoAggNumCandlesInCache := 0; + FApproxTimePerCandleText := 'N/A'; + FTotalDataTimeSpanText := 'N/A'; +end; + +function TOHLC.GetCandle(Index: Integer): TCachedCandle; +begin + if (Index >= 0) and (Index < Length(FCachedCandles)) then + Result := FCachedCandles[Index] + else + Result := Default(TCachedCandle); +end; + +function TOHLC.GetCount: Integer; +begin + Result := Length(FCachedCandles); +end; + +procedure TOHLC.Build(const ATickData: TArray; + ASelectedTimeframeSeconds: Int64; + AAutoAggCandleWidth: Integer; AAutoAggCandleSpacing: Integer; AAutoAggPaintBoxClientWidth: Integer; + AMemoForLogging: TStrings; AGenericCandleBuilder: IGenericCandleBuilder = nil); // Parameter changed +var + I, CurrentTickIndex, DataIndexStart, DataIndexEnd, J, NumDataPoints, ChartPixelWidthForAutoAgg, NumCandlesToCacheAuto, + SlotWidthForAutoAgg: Integer; + OpenPrice, HighPrice, LowPrice, ClosePrice: Single; + BucketStartTimeOADate, BucketEndTimeOADate, TimeframeIntervalOADays: Double; + CandleList: TList; + TempCandle: TCachedCandle; + LogTimePerCandleStr: string; + CurrentTick: TTickData; +begin + if AMemoForLogging <> nil then + AMemoForLogging.Add(FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + Format(' - TOHLC.Build: TF Secs: %d...', + [ASelectedTimeframeSeconds])); + ClearCache(); + FAggregationTimeframeSeconds := ASelectedTimeframeSeconds; + NumDataPoints := Length(ATickData); + + if NumDataPoints < 1 then + begin + if AMemoForLogging <> nil then + AMemoForLogging.Add('TOHLC.Build: No tick data.'); + Exit; + end; + + FGlobalMinY := ATickData[0].Ask; + FGlobalMaxY := ATickData[0].Ask; + for I := 1 to High(ATickData) do + begin + if ATickData[I].Ask < FGlobalMinY then + FGlobalMinY := ATickData[I].Ask; + if ATickData[I].Ask > FGlobalMaxY then + FGlobalMaxY := ATickData[I].Ask; + end; + if FGlobalMaxY = FGlobalMinY then + begin + FGlobalMaxY := FGlobalMinY + 0.0001; // Ensure a minimal range + FGlobalMinY := FGlobalMinY - 0.0001; + end; + if FGlobalMaxY = FGlobalMinY then // Still equal (e.g. if original was 0) + FGlobalMaxY := FGlobalMinY + 1; // Ensure a non-zero range + + if AMemoForLogging <> nil then + AMemoForLogging.Add(Format('TOHLC.Build: Global Y-Range: %.5f to %.5f', [FGlobalMinY, FGlobalMaxY])); + LogTimePerCandleStr := 'N/A'; + + // Generic Candle Building using IGenericCandleBuilder + if (ASelectedTimeframeSeconds = TF_GENERIC_CALLBACK) and Assigned(AGenericCandleBuilder) then + begin + if AMemoForLogging <> nil then + AMemoForLogging.Add(Format('TOHLC.Build (Generic Builder: %s): Aggregating %d ticks.', [AGenericCandleBuilder.Caption, NumDataPoints])); + + if not AGenericCandleBuilder.Init(ATickData) then + begin + if AMemoForLogging <> nil then + AMemoForLogging.Add(Format('TOHLC.Build (Generic Builder: %s): Init method failed.', [AGenericCandleBuilder.Caption])); + FIsValid := False; // Ensure cache is marked as invalid + Exit; // Exit if Init fails + end; + + CandleList := TList.Create; + try + if NumDataPoints > 0 then + begin + DataIndexStart := 0; + OpenPrice := ATickData[0].Ask; + HighPrice := ATickData[0].Ask; + LowPrice := ATickData[0].Ask; + ClosePrice := ATickData[0].Ask; + + for I := 0 to NumDataPoints - 1 do + begin + CurrentTick := ATickData[I]; + // Use IGenericCandleBuilder.IsNewBar + if (I > DataIndexStart) and AGenericCandleBuilder.IsNewBar(I, CurrentTick) then // Current tick starts a NEW candle + begin + // Finalize the PREVIOUS candle (ending at I-1) + TempCandle.OpenPrice := OpenPrice; + TempCandle.HighPrice := HighPrice; + TempCandle.LowPrice := LowPrice; + TempCandle.ClosePrice := ATickData[I - 1].Ask; // Close of previous candle is the tick before the new bar starts + TempCandle.OriginalDataIndexStart := DataIndexStart; + TempCandle.OriginalDataIndexEnd := I - 1; + TempCandle.TickVolume := (I - 1) - DataIndexStart + 1; + CandleList.Add(TempCandle); + + // Current tick 'I' starts a new candle + DataIndexStart := I; + OpenPrice := CurrentTick.Ask; + HighPrice := CurrentTick.Ask; + LowPrice := CurrentTick.Ask; + ClosePrice := CurrentTick.Ask; + end + else // Current tick continues the existing candle OR is the first tick of the very first candle + begin + if I = DataIndexStart then // This is the first tick of the current (or overall first) candle + begin + OpenPrice := CurrentTick.Ask; // Open is set + HighPrice := CurrentTick.Ask; // Initial High + LowPrice := CurrentTick.Ask; // Initial Low + end + else // Update High/Low for ongoing candle + begin + if CurrentTick.Ask > HighPrice then + HighPrice := CurrentTick.Ask; + if CurrentTick.Ask < LowPrice then + LowPrice := CurrentTick.Ask; + end; + ClosePrice := CurrentTick.Ask; // Always update close to current tick + end; + + // If this is the last tick in the dataset, finalize the current candle + if I = NumDataPoints - 1 then + begin + TempCandle.OpenPrice := OpenPrice; + TempCandle.HighPrice := HighPrice; + TempCandle.LowPrice := LowPrice; + TempCandle.ClosePrice := ClosePrice; // Close price is this tick's price + TempCandle.OriginalDataIndexStart := DataIndexStart; + TempCandle.OriginalDataIndexEnd := I; + TempCandle.TickVolume := I - DataIndexStart + 1; + CandleList.Add(TempCandle); + end; + end; + end; + FCachedCandles := CandleList.ToArray; + LogTimePerCandleStr := Format('(%s)', [AGenericCandleBuilder.Caption]); // Use Builder's Caption + if AMemoForLogging <> nil then + AMemoForLogging.Add(Format('TOHLC.Build (Generic Builder: %s): Created %d candles.', [AGenericCandleBuilder.Caption, Length(FCachedCandles)])); + finally + CandleList.Free; + end; + end + else if FAggregationTimeframeSeconds = TF_AUTO_AGGREGATION then + begin // Auto Aggregation logic + FAutoAggCandleDisplayWidth := AAutoAggCandleWidth; + FAutoAggPaintBoxClientWidth := AAutoAggPaintBoxClientWidth; + ChartPixelWidthForAutoAgg := FAutoAggPaintBoxClientWidth; + if ChartPixelWidthForAutoAgg <= 0 then + begin + if AMemoForLogging <> nil then AMemoForLogging.Add('TOHLC.Build (Auto): PaintBox width too small.'); + Exit; + end; + SlotWidthForAutoAgg := FAutoAggCandleDisplayWidth + AAutoAggCandleSpacing; + if SlotWidthForAutoAgg <= 0 then + begin + if AMemoForLogging <> nil then AMemoForLogging.Add('TOHLC.Build (Auto): SlotWidth zero or negative.'); + Exit; + end; + NumCandlesToCacheAuto := ChartPixelWidthForAutoAgg div SlotWidthForAutoAgg; + FAutoAggNumCandlesInCache := NumCandlesToCacheAuto; + if AMemoForLogging <> nil then + AMemoForLogging.Add(Format('TOHLC.Build (Auto): Aggregating with CW %dpx, Sp %dpx. SlotW %dpx. NumCandles: %d.', + [FAutoAggCandleDisplayWidth, AAutoAggCandleSpacing, SlotWidthForAutoAgg, NumCandlesToCacheAuto])); + + if NumCandlesToCacheAuto <= 0 then + begin + if AMemoForLogging <> nil then AMemoForLogging.Add('TOHLC.Build (Auto): Not enough space for candles.'); + Exit; + end; + SetLength(FCachedCandles, NumCandlesToCacheAuto); + if (NumDataPoints > 1) and (NumCandlesToCacheAuto > 0) then + begin + var FirstTickTimeVal_A := ATickData[0].OADateTime; + var LastTickTimeVal_A := ATickData[High(ATickData)].OADateTime; + var TotalDataDurationDaysVal_A := LastTickTimeVal_A - FirstTickTimeVal_A; + if NumCandlesToCacheAuto > 0 then // Avoid division by zero if NumCandlesToCacheAuto is somehow 0 here + begin + var AvgCandleDurationDaysVal_A := TotalDataDurationDaysVal_A / NumCandlesToCacheAuto; + LogTimePerCandleStr := FormatTimeSpanFromSeconds(AvgCandleDurationDaysVal_A * SecsPerDay_Double); + end + else + LogTimePerCandleStr := 'N/A (Auto)'; + end; + for I := 0 to NumCandlesToCacheAuto - 1 do + begin + DataIndexStart := Floor((I * SlotWidthForAutoAgg) * (NumDataPoints / ChartPixelWidthForAutoAgg)); + DataIndexEnd := Floor(((I + 1) * SlotWidthForAutoAgg) * (NumDataPoints / ChartPixelWidthForAutoAgg)) - 1; + DataIndexStart := Max(0, DataIndexStart); + DataIndexEnd := Min(High(ATickData), Max(DataIndexStart, DataIndexEnd)); + + if (DataIndexStart > High(ATickData)) or (DataIndexStart > DataIndexEnd) then + begin + FCachedCandles[I] := Default(TCachedCandle); // Initialize with default values + Continue; + end; + + OpenPrice := ATickData[DataIndexStart].Ask; + ClosePrice := ATickData[DataIndexEnd].Ask; + HighPrice := OpenPrice; // Initialize with Open + LowPrice := OpenPrice; // Initialize with Open + + for J := DataIndexStart to DataIndexEnd do + begin + if ATickData[J].Ask > HighPrice then HighPrice := ATickData[J].Ask; + if ATickData[J].Ask < LowPrice then LowPrice := ATickData[J].Ask; + end; + // Ensure High/Low encompass Open/Close after loop + HighPrice := Max(HighPrice, Max(OpenPrice, ClosePrice)); + LowPrice := Min(LowPrice, Min(OpenPrice, ClosePrice)); + + FCachedCandles[I].OpenPrice := OpenPrice; + FCachedCandles[I].HighPrice := HighPrice; + FCachedCandles[I].LowPrice := LowPrice; + FCachedCandles[I].ClosePrice := ClosePrice; + FCachedCandles[I].OriginalDataIndexStart := DataIndexStart; + FCachedCandles[I].OriginalDataIndexEnd := DataIndexEnd; + FCachedCandles[I].TickVolume := DataIndexEnd - DataIndexStart + 1; + end; + end + else if FAggregationTimeframeSeconds = TF_TICK_BY_TICK then + begin // Tick-by-Tick logic + if AMemoForLogging <> nil then + AMemoForLogging.Add(Format('TOHLC.Build (Tick-by-Tick): Creating %d candles.', [NumDataPoints])); + SetLength(FCachedCandles, NumDataPoints); + for I := 0 to NumDataPoints - 1 do + begin + FCachedCandles[I].OpenPrice := ATickData[I].Ask; + FCachedCandles[I].HighPrice := ATickData[I].Ask; + FCachedCandles[I].LowPrice := ATickData[I].Ask; + FCachedCandles[I].ClosePrice := ATickData[I].Ask; + FCachedCandles[I].OriginalDataIndexStart := I; + FCachedCandles[I].OriginalDataIndexEnd := I; + FCachedCandles[I].TickVolume := 1; + end; + LogTimePerCandleStr := '(duration of one tick)'; + end + else // Fixed Timeframe (FAggregationTimeframeSeconds > 0) + begin // Fixed Timeframe logic + CandleList := TList.Create; + try + TimeframeIntervalOADays := FAggregationTimeframeSeconds / SecsPerDay_Double; + if TimeframeIntervalOADays <= 1E-9 then // Avoid division by zero or too small interval + begin + if AMemoForLogging <> nil then + AMemoForLogging.Add(Format('TOHLC.Build (Fixed TF): Error - Interval too small (%.10f).', [TimeframeIntervalOADays])); + Exit; + end; + + CurrentTickIndex := 0; + while CurrentTickIndex <= High(ATickData) do + begin + BucketStartTimeOADate := Floor(ATickData[CurrentTickIndex].OADateTime / TimeframeIntervalOADays) * TimeframeIntervalOADays; + BucketEndTimeOADate := BucketStartTimeOADate + TimeframeIntervalOADays; + + DataIndexStart := CurrentTickIndex; + OpenPrice := ATickData[CurrentTickIndex].Ask; + HighPrice := ATickData[CurrentTickIndex].Ask; + LowPrice := ATickData[CurrentTickIndex].Ask; + ClosePrice := ATickData[CurrentTickIndex].Ask; // Initialize close with open + DataIndexEnd := CurrentTickIndex; // Initialize end with start + + // Iterate through ticks that fall into the current bucket + while (CurrentTickIndex <= High(ATickData)) and + (ATickData[CurrentTickIndex].OADateTime >= BucketStartTimeOADate) and + (ATickData[CurrentTickIndex].OADateTime < BucketEndTimeOADate) do + begin + if ATickData[CurrentTickIndex].Ask > HighPrice then HighPrice := ATickData[CurrentTickIndex].Ask; + if ATickData[CurrentTickIndex].Ask < LowPrice then LowPrice := ATickData[CurrentTickIndex].Ask; + ClosePrice := ATickData[CurrentTickIndex].Ask; // Continuously update close price + DataIndexEnd := CurrentTickIndex; // Mark the last tick included in this candle + Inc(CurrentTickIndex); + end; + + // If no ticks were processed in the inner loop (e.g., if the first tick was already >= BucketEndTimeOADate) + // ensure we advance CurrentTickIndex to avoid an infinite loop if DataIndexStart remains unchanged. + // This situation should ideally be rare if data is sorted and dense enough for the timeframe. + // However, if DataIndexStart equals CurrentTickIndex here, it means the inner loop didn't run. + // This could happen if ATickData[CurrentTickIndex].OADateTime was already >= BucketEndTimeOADate. + // In such case, we still form a candle with the single tick at DataIndexStart, and then CurrentTickIndex must advance. + // If the loop DID run, CurrentTickIndex is already pointing to the next tick outside the bucket. + if (DataIndexStart = DataIndexEnd) And (CurrentTickIndex = DataIndexStart) then // Only one tick considered, and it might not have advanced CurrentTickIndex + begin + if CurrentTickIndex <= High(ATickData) then // If it wasn't the very last tick. + Inc(CurrentTickIndex); // Ensure progress if loop condition makes it stick. + end; + // If the inner while loop exited because CurrentTickIndex > High(ATickData), DataIndexStart might still be a valid index + // for the last candle. + // Or, if the loop exited due to time boundary, CurrentTickIndex is correctly positioned for the next bucket. + + TempCandle.OpenPrice := OpenPrice; + TempCandle.HighPrice := HighPrice; + TempCandle.LowPrice := LowPrice; + TempCandle.ClosePrice := ClosePrice; + TempCandle.OriginalDataIndexStart := DataIndexStart; + TempCandle.OriginalDataIndexEnd := DataIndexEnd; + TempCandle.TickVolume := (DataIndexEnd - DataIndexStart) + 1; + CandleList.Add(TempCandle); + + if CurrentTickIndex > High(ATickData) then Break; // All ticks processed + end; + FCachedCandles := CandleList.ToArray; + LogTimePerCandleStr := FormatTimeSpanFromSeconds(FAggregationTimeframeSeconds); + if AMemoForLogging <> nil then + AMemoForLogging.Add(Format('TOHLC.Build (Fixed TF): Created %d candles.', [Length(FCachedCandles)])); + finally + CandleList.Free; + end; + end; + + if Length(ATickData) > 0 then + begin + var FirstTickTimeVal := ATickData[0].OADateTime; + var LastTickTimeVal := ATickData[High(ATickData)].OADateTime; + FTotalDataTimeSpanText := FormatTimeSpanFromSeconds((LastTickTimeVal - FirstTickTimeVal) * SecsPerDay_Double); + if AMemoForLogging <> nil then + AMemoForLogging.Add(Format('TOHLC.Build: Total data time span: %s.', [FTotalDataTimeSpanText])); + end; + + FApproxTimePerCandleText := LogTimePerCandleStr; + if AMemoForLogging <> nil then + AMemoForLogging.Add(Format('TOHLC.Build: Time per cached candle: %s.', [FApproxTimePerCandleText])); + + FIsValid := True; + if AMemoForLogging <> nil then + AMemoForLogging.Add(FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + ' - TOHLC.Build: OHLC cache built.'); +end; + +end. diff --git a/common/Myc.TickLoader.pas b/common/Myc.TickLoader.pas new file mode 100644 index 0000000..fb85cb2 --- /dev/null +++ b/common/Myc.TickLoader.pas @@ -0,0 +1,254 @@ +unit Myc.TickLoader; + +(*------------------------------------------------------------------------------ + Unit: Myc.TickLoader + Author: Delphi Algo Trading (Assistant) + Date: May 22, 2025 + Description: + 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 now follow the naming convention: Symbol_Year.tab. + (Prefix has been removed from the convention). + Loading occurs in two phases: + 1. All .tab files are processed chronologically. 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 (for 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 yearly .tab files. + + Main Function: + LoadTickDataSeries - Loads a series of tick data files. +------------------------------------------------------------------------------*) + +interface + +uses + System.SysUtils, + System.Classes, + System.Generics.Collections, + System.IOUtils; + +type + TTickData = packed record + OADateTime: Double; + Ask: Single; + Bid: Single; + end; + PTTickData = ^TTickData; + +// TryParseFileName now expects filenames like "Symbol_Year" (or "Symbol_Part2_Year") +// PrefixValue will be returned as an empty string. +function TryParseFileName(const FileName: string; out PathValue, SymbolValue: string; out YearValue: Integer): Boolean; +function LoadTickDataSeries(const FileName: string): TArray; + +implementation + +function TryParseFileName(const FileName: string; out PathValue, SymbolValue: string; out YearValue: Integer): Boolean; +var + fileNameOnly: string; + parts: TArray; +begin + Result := False; + PathValue := TPath.GetDirectoryName(FileName); + fileNameOnly := TPath.GetFileNameWithoutExtension(FileName); // Handles any extension for parsing basename + + parts := fileNameOnly.Split(['_']); + + // Expected format is Symbol_Year or SymbolPart1_SymbolPart2_Year, so at least 2 parts. + if Length(parts) < 2 then + begin + SymbolValue := ''; YearValue := 0; // Clear out params on minimum parts failure + Exit; + end; + + // Year is the last part. + if not TryStrToInt(parts[High(parts)], YearValue) then + begin + SymbolValue := ''; // Clear SymbolValue as well on year parse failure + Exit; + end; + + // Symbol consists of all parts before the year part. + // Copy all parts except the last one (which is the Year). + // The second parameter of Copy (for TArray) is the starting index, + // the third parameter is the count of elements to copy. + // We want to copy elements from index 0 up to (but not including) the last element. + // So, the count is High(parts) (which is Length(parts) - 1). + if High(parts) > 0 then // Ensure there's at least one part for the symbol + SymbolValue := string.Join('_', Copy(parts, 0, High(parts))) + else if Length(parts) = 1 then // Edge case: File is "Year.tab" - this is invalid by new rule "Symbol_Year" + begin SymbolValue := ''; Exit; end // Or handle as Symbol = parts[0] if "Year" itself is a symbol + else // Should not happen if Length(parts) < 2 already exited + SymbolValue := ''; + + + Result := True; +end; + +function LoadTickDataSeries(const FileName: string): TArray; +var + tickList: TList; + initialYear, currentTabYear, liveProcessingYear: Integer; + pathName, symbolName: string; // prefixNameOut will be an empty out param + tabFileName, liveFileName: string; + overallLastTabTickTime: Double; + maxTabYearFound: Integer; + + procedure ReadFileContentAndGetMaxTime(const AFileName: string; + out Records: TArray; + out ActualMaxTickTimeInFile: Double); + var + fs: TFileStream; + fileSize: Int64; + recordCount: Integer; + bytesRead: Integer; + iterTickData: TTickData; + currentFileMaxOA: Double; + begin + SetLength(Records, 0); + ActualMaxTickTimeInFile := 0.0; + currentFileMaxOA := 0.0; + + if not TFile.Exists(AFileName) then // Added check for file existence + begin + // Silently exit or log if preferred, but don't try to open non-existent file. + Exit; + end; + + fs := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite); + try + fileSize := fs.Size; + if fileSize = 0 then Exit; + + if (fileSize mod SizeOf(TTickData)) <> 0 then + raise EReadError.CreateFmt('File %s: Corrupted. Size %d is not a multiple of record size %d.', + [AFileName, fileSize, SizeOf(TTickData)]); + + recordCount := fileSize div SizeOf(TTickData); + if recordCount > 0 then + begin + SetLength(Records, recordCount); + bytesRead := fs.Read(Records[0], fileSize); + if bytesRead <> fileSize then + raise EReadError.CreateFmt('File %s: Read error. Expected to read %d bytes, but actually read %d bytes.', + [AFileName, fileSize, bytesRead]); + + for iterTickData in Records do + begin + if iterTickData.OADateTime > currentFileMaxOA then + currentFileMaxOA := iterTickData.OADateTime; + end; + ActualMaxTickTimeInFile := currentFileMaxOA; + end; + finally + fs.Free; + end; + end; + +begin // Start of LoadTickDataSeries + tickList := TList.Create; + overallLastTabTickTime := 0.0; + maxTabYearFound := -1; + + try + // Initial FileName must be the path to the first .tab file (e.g., Symbol_Year.tab) + // prefixNameOut will be an out parameter from TryParseFileName but will be empty. + if not TryParseFileName(FileName, pathName, symbolName, initialYear) then + begin + raise EArgumentException.CreateFmt('Invalid initial file name format: %s. Expected Symbol_Year.tab (or Symbol_Part2_Year.tab)', [FileName]); + end; + + // --- PHASE 1: Load all .tab files and determine overallLastTabTickTime --- + currentTabYear := initialYear; + // Construct the first .tab file name using the parsed components (symbol, year). + // Prefix is no longer used in the format string. + tabFileName := TPath.Combine(pathName, Format('%s_%d.tab', [symbolName, currentTabYear])); + + var tabFileRecords: TArray; + var maxTimeInThisTabFile: Double; + + while TFile.Exists(tabFileName) do + begin + maxTabYearFound := currentTabYear; + ReadFileContentAndGetMaxTime(tabFileName, tabFileRecords, maxTimeInThisTabFile); + + if Length(tabFileRecords) > 0 then + begin + tickList.AddRange(tabFileRecords); + end; + + if maxTimeInThisTabFile > overallLastTabTickTime then + overallLastTabTickTime := maxTimeInThisTabFile; + + Inc(currentTabYear); + // Construct next tab filename without prefix + tabFileName := TPath.Combine(pathName, Format('%s_%d.tab', [symbolName, currentTabYear])); + end; + + // --- PHASE 2: Load .tab-live files --- + if maxTabYearFound >= initialYear then + begin + for liveProcessingYear := initialYear to maxTabYearFound do + begin + // Construct .tab-live filename without prefix + liveFileName := TPath.Combine(pathName, Format('%s_%d.tab-live', [symbolName, liveProcessingYear])); + + if TFile.Exists(liveFileName) then + begin + var liveFileRecords: TArray; + var maxTimeInThisLiveFile: Double; + var ticksToActuallyAddFromLive: TList; + var liveDataAddedThisFile: Boolean; + var iterLiveTickData: TTickData; + + ReadFileContentAndGetMaxTime(liveFileName, liveFileRecords, maxTimeInThisLiveFile); + liveDataAddedThisFile := False; + + if Length(liveFileRecords) > 0 then + begin + ticksToActuallyAddFromLive := TList.Create; + try + for iterLiveTickData in liveFileRecords do + begin + if iterLiveTickData.OADateTime > overallLastTabTickTime then + begin + ticksToActuallyAddFromLive.Add(iterLiveTickData); + liveDataAddedThisFile := True; + end; + end; + + if ticksToActuallyAddFromLive.Count > 0 then + tickList.AddRange(ticksToActuallyAddFromLive); + finally + ticksToActuallyAddFromLive.Free; + end; + end; + + if not liveDataAddedThisFile then + begin + try + TFile.Delete(liveFileName); + except + on E: Exception do {/* Optional: Log error, e.g., using a global logger or passed logger */} + end; + end; + end; + end; + end; + + Result := tickList.ToArray; + finally + tickList.Free; + end; +end; + +end.