diff --git a/AuraTrader/AuraTrader.dpr b/AuraTrader/AuraTrader.dpr
index d5fa573..018becb 100644
--- a/AuraTrader/AuraTrader.dpr
+++ b/AuraTrader/AuraTrader.dpr
@@ -13,7 +13,8 @@ uses
FirstStrategy in 'FirstStrategy.pas',
Myc.Trade.DataArray in '..\Src\Myc.Trade.DataArray.pas',
Myc.FMX.Chart.Series in '..\Src\Myc.FMX.Chart.Series.pas',
- Myc.Trade.Indicators in '..\Src\Myc.Trade.Indicators.pas';
+ Myc.Trade.Indicators in '..\Src\Myc.Trade.Indicators.pas',
+ Myc.Trade.Types in '..\Src\Myc.Trade.Types.pas';
{$R *.res}
diff --git a/AuraTrader/AuraTrader.dproj b/AuraTrader/AuraTrader.dproj
index 6670364..b01b2ad 100644
--- a/AuraTrader/AuraTrader.dproj
+++ b/AuraTrader/AuraTrader.dproj
@@ -4,7 +4,7 @@
20.3
FMX
True
- Release
+ Debug
Win64
AuraTrader
3
@@ -142,6 +142,7 @@
+
Base
@@ -185,6 +186,12 @@
true
+
+
+ AuraTrader.rsm
+ true
+
+
AuraTrader.exe
diff --git a/AuraTrader/FirstStrategy.pas b/AuraTrader/FirstStrategy.pas
index 6b7e93a..16f8b47 100644
--- a/AuraTrader/FirstStrategy.pas
+++ b/AuraTrader/FirstStrategy.pas
@@ -8,6 +8,7 @@ uses
Myc.Signals,
Myc.Lazy,
Myc.TaskManager,
+ Myc.Trade.Types,
Myc.Trade.DataPoint,
Myc.Trade.DataArray;
diff --git a/AuraTrader/MainForm.pas b/AuraTrader/MainForm.pas
index 3f0a087..40b78f4 100644
--- a/AuraTrader/MainForm.pas
+++ b/AuraTrader/MainForm.pas
@@ -27,6 +27,7 @@ uses
FMX.Memo,
FMX.Objects,
Myc.Futures,
+ Myc.Trade.Types,
Myc.Trade.DataStream,
Myc.Trade.DataPoint,
Myc.Trade.DataProvider,
@@ -183,17 +184,17 @@ begin
var Hull: IMycConverter := TGenericIndicator.Create(TIndicators.CreateHMA(150));
Closes.Sender.Link(Hull);
- chart.AddDoubleSeries(Hull.Sender, TAlphaColors.Aliceblue);
+ chart.Panels[0].AddDoubleSeries(Hull.Sender, TAlphaColors.Aliceblue);
// Add SMA (Simple Moving Average)
var Sma: IMycConverter := TGenericIndicator.Create(TIndicators.CreateSMA(50));
Closes.Sender.Link(Sma);
- chart.AddDoubleSeries(Sma.Sender, TAlphaColors.Yellow);
+ chart.Panels[0].AddDoubleSeries(Sma.Sender, TAlphaColors.Yellow);
// Add EMA (Exponential Moving Average)
var Ema: IMycConverter := TGenericIndicator.Create(TIndicators.CreateEMA(21));
Closes.Sender.Link(Ema);
- chart.AddDoubleSeries(Ema.Sender, TAlphaColors.Aqua);
+ chart.Panels[0].AddDoubleSeries(Ema.Sender, TAlphaColors.Aqua);
// Add Bollinger Bands (20, 2.0)
var Boli: IMycConverter :=
@@ -204,30 +205,26 @@ begin
TMycGenericConverter
.Create(function(const Item: TBollingerBandsResult): Double begin Result := Item.UpperBand; end);
Boli.Sender.Link(BoliUpper);
- chart.AddDoubleSeries(BoliUpper.Sender, TAlphaColors.Gray);
+ chart.Panels[0].AddDoubleSeries(BoliUpper.Sender, TAlphaColors.Gray);
var BoliMiddle: IMycConverter :=
TMycGenericConverter
.Create(function(const Item: TBollingerBandsResult): Double begin Result := Item.MiddleBand; end);
Boli.Sender.Link(BoliMiddle);
- chart.AddDoubleSeries(BoliMiddle.Sender, TAlphaColors.Darkgray, 1.0);
+ chart.Panels[0].AddDoubleSeries(BoliMiddle.Sender, TAlphaColors.Darkgray, 1.0);
var BoliLower: IMycConverter :=
TMycGenericConverter
.Create(function(const Item: TBollingerBandsResult): Double begin Result := Item.LowerBand; end);
Boli.Sender.Link(BoliLower);
- chart.AddDoubleSeries(BoliLower.Sender, TAlphaColors.Gray);
+ chart.Panels[0].AddDoubleSeries(BoliLower.Sender, TAlphaColors.Gray);
- var rsiChart := TMycChart.Create(Self);
- AlignControl(rsiChart);
- rsiChart.Height := Layout.ChildrenRect.Width * 9 / 32;
- rsiChart.Lookback.Value := 50000;
- rsiChart.SetXAxisSeries(Timestamps.Sender);
+ chart.AddPanel;
// Add RSI (Relative Strength Index)
var Rsi: IMycConverter := TGenericIndicator.Create(TIndicators.CreateRSI(14));
Closes.Sender.Link(Rsi);
- rsiChart.AddDoubleSeries(Rsi.Sender, TAlphaColors.Fuchsia);
+ chart.Panels[1].AddDoubleSeries(Rsi.Sender, TAlphaColors.Fuchsia);
{
// Add MACD (12, 26, 9)
var Macd: IMycConverter := TGenericIndicator.Create(TIndicators.CreateMACD(12, 26, 9));
@@ -260,7 +257,7 @@ begin
OhlcPoint.Sender.Link(TimeStamps);
chart.SetXAxisSeries(Timestamps.Sender);
- chart.AddOhlcSeries(Ohlc.Sender);
+ chart.Panels[0].AddOhlcSeries(Ohlc.Sender);
var done := ExecuteStrategy(Symbol, OhlcPoint);
diff --git a/Src/Myc.Fmx.Chart.Series.pas b/Src/Myc.Fmx.Chart.Series.pas
index 214f982..3dbfe24 100644
--- a/Src/Myc.Fmx.Chart.Series.pas
+++ b/Src/Myc.Fmx.Chart.Series.pas
@@ -9,6 +9,7 @@ uses
FMX.Graphics,
Myc.Signals,
Myc.Lazy,
+ Myc.Trade.Types,
Myc.Trade.DataArray,
Myc.Trade.DataPoint,
Myc.Fmx.Chart;
@@ -66,7 +67,7 @@ type
procedure Update; override;
procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc); override;
public
- constructor Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider);
+ constructor Create(AParent: TMycChart.TPanel; const ADataProvider: IMycDataProvider);
destructor Destroy; override;
end;
@@ -80,7 +81,7 @@ type
procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc); override;
public
constructor Create(
- AOwner: TMycChart;
+ AParent: TMycChart.TPanel;
const ADataProvider: IMycDataProvider;
const AUpColor, ADownColor: TMutable
);
@@ -96,7 +97,7 @@ type
procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc); override;
public
constructor Create(
- AOwner: TMycChart;
+ AParent: TMycChart.TPanel;
const ADataProvider: IMycDataProvider;
const ALineColor: TAlphaColor;
ALineWidth: Single
@@ -176,13 +177,13 @@ end;
{ TChartLineLayer }
constructor TChartLineLayer.Create(
- AOwner: TMycChart;
+ AParent: TMycChart.TPanel;
const ADataProvider: IMycDataProvider;
const ALineColor: TAlphaColor;
ALineWidth: Single
);
begin
- inherited Create(AOwner, ADataProvider);
+ inherited Create(AParent, ADataProvider);
FLineColor := ALineColor;
FLineWidth := ALineWidth;
end;
@@ -235,12 +236,12 @@ end;
{ TChartOhlcLayer }
constructor TChartOhlcLayer.Create(
- AOwner: TMycChart;
+ AParent: TMycChart.TPanel;
const ADataProvider: IMycDataProvider;
const AUpColor, ADownColor: TMutable
);
begin
- inherited Create(AOwner, ADataProvider);
+ inherited Create(AParent, ADataProvider);
FUpColor := AUpColor;
FDownColor := ADownColor;
@@ -302,10 +303,10 @@ end;
{ TChartCustomLayer }
-constructor TChartCustomLayer.Create(AOwner: TMycChart; const ADataProvider: IMycDataProvider);
+constructor TChartCustomLayer.Create(AParent: TMycChart.TPanel; const ADataProvider: IMycDataProvider);
begin
- inherited Create(AOwner);
- FSeries := TChartSeriesProcessor.Create(ADataProvider, AOwner.Lookback.AsMutable);
+ inherited Create(AParent);
+ FSeries := TChartSeriesProcessor.Create(ADataProvider, AParent.Owner.Lookback.AsMutable);
end;
destructor TChartCustomLayer.Destroy;
diff --git a/Src/Myc.Fmx.Chart.pas b/Src/Myc.Fmx.Chart.pas
index b263d14..30d432f 100644
--- a/Src/Myc.Fmx.Chart.pas
+++ b/Src/Myc.Fmx.Chart.pas
@@ -15,6 +15,7 @@ uses
FMX.Controls,
FMX.Graphics,
FMX.Forms,
+ Myc.Trade.Types,
Myc.Trade.DataPoint,
Myc.Signals,
Myc.Lazy;
@@ -23,6 +24,9 @@ type
TMycChart = class(TStyledControl)
public
type
+ TPanel = class;
+
+ // Abstract base for a data series within the chart.
TSeries = class abstract(TObject)
protected
function GetCount: Int64; virtual; abstract;
@@ -32,25 +36,54 @@ type
property TotalCount: Int64 read GetTotalCount;
end;
+ // Abstract base for a drawable layer in a panel.
TLayer = class abstract(TObject)
private
- FOwner: TMycChart;
+ FParent: TPanel;
+ function GetOwner: TMycChart;
protected
function GetSeries: TSeries; virtual; abstract;
procedure Update; virtual; abstract;
- function GetValueRange(StartIndex, Count: Int64; out Min, Max: Double): Boolean; virtual; abstract;
- // The signature is changed to support viewport painting with an index offset
+ function GetValueRange(First, Last: Int64; out Min, Max: Double): Boolean; virtual; abstract;
procedure Paint(const Canvas: TCanvas; First, Last: Int64; const XForm, YForm: TFunc); virtual; abstract;
public
- constructor Create(AOwner: TMycChart);
+ constructor Create(AOwner: TPanel);
procedure Repaint;
- property Owner: TMycChart read FOwner;
+ property Owner: TMycChart read GetOwner;
property Series: TSeries read GetSeries;
end;
+ // A panel is a rectangular area in the chart with its own Y-axis, which can contain multiple layers.
+ TPanel = class(TObject)
+ private
+ FOwner: TMycChart;
+ FSeriesList: TObjectList;
+ protected
+ procedure Paint(const Canvas: TCanvas; const Viewport: TRectF; MasterTotalCount: Int64; ViewStartIndex, ViewCount: Int64);
+ procedure UpdateAndCheckChanges(MasterTotalCount: Int64; ViewStartIndex: Int64; out SeriesChanged, SeriesRepaint: Boolean);
+
+ public
+ constructor Create(AOwner: TMycChart);
+ destructor Destroy; override;
+
+ function AddOhlcSeries(
+ const DataProvider: IMycDataProvider;
+ const AUpColor: TAlphaColor = TAlphaColors.Green;
+ const ADownColor: TAlphaColor = TAlphaColors.Red
+ ): TMycChart.TLayer;
+
+ function AddDoubleSeries(
+ const DataProvider: IMycDataProvider;
+ const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue;
+ const ALineWidth: Single = 1.5
+ ): TMycChart.TLayer;
+
+ property Owner: TMycChart read FOwner;
+ end;
+
private
- FSeriesList: TList;
- FXAxisSeries: TLayer;
+ FPanelList: TObjectList;
+ FXAxisSeries: TMycChart.TLayer;
FLookback: TWriteable;
FIdleSubscrId: TMessageSubscriptionId;
FViewStartIndex: Int64;
@@ -61,6 +94,8 @@ type
FJumpButtonHot: Boolean;
FJumpButtonPressed: Boolean;
FNeedRepaint: TFlag;
+ function GetPanel(Index: Integer): TPanel;
+ function GetPanelCount: Integer;
protected
procedure Paint; override;
procedure DoIdle;
@@ -72,27 +107,16 @@ type
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
+ // Adds a new, empty panel to the bottom of the chart.
+ function AddPanel: TPanel;
+
// Sets the master series that defines the time scale (X-axis). This series is not drawn.
- // The chart will not render any data until the X-axis series is set.
- function SetXAxisSeries(const DataProvider: IMycDataProvider): TLayer;
-
- // Creates an OHLC candlestick/bar series and returns it.
- // The data provider directly supplies OHLC items, as timestamps are managed by the data series internally.
- function AddOhlcSeries(
- const DataProvider: IMycDataProvider;
- const AUpColor: TAlphaColor = TAlphaColors.Green;
- const ADownColor: TAlphaColor = TAlphaColors.Red
- ): TLayer;
-
- // Creates a simple line series for double values and returns it
- function AddDoubleSeries(
- const DataProvider: IMycDataProvider;
- const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue;
- const ALineWidth: Single = 1.5
- ): TLayer;
+ function SetXAxisSeries(const DataProvider: IMycDataProvider): TMycChart.TLayer;
property Lookback: TWriteable read FLookback write FLookback;
property NeedRepaint: TFlag read FNeedRepaint;
+ property PanelCount: Integer read GetPanelCount;
+ property Panels[Index: Integer]: TPanel read GetPanel; default;
end;
implementation
@@ -106,18 +130,21 @@ uses
constructor TMycChart.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
- FSeriesList := TObjectList.Create(true);
+ FPanelList := TObjectList.Create(true);
FXAxisSeries := nil;
- FLookback := TWriteable.CreateWriteable(1000); // Load more data for panning
+ FLookback := TWriteable.CreateWriteable(1000);
FViewStartIndex := 0;
- FViewCount := 100; // Show 100 items by default
+ FViewCount := 100;
FIsDragging := false;
FIdleSubscrId :=
TMessageManager
.DefaultManager
.SubscribeToMessage(TIdleMessage, procedure(const Sender: TObject; const M: TMessage) begin DoIdle; end);
+
+ // Create the default panel.
+ AddPanel;
end;
destructor TMycChart.Destroy;
@@ -125,97 +152,159 @@ begin
TMessageManager.DefaultManager.Unsubscribe(TIdleMessage, FIdleSubscrId);
FXAxisSeries.Free;
- FSeriesList.Free;
+ FPanelList.Free;
inherited;
end;
-function TMycChart.SetXAxisSeries(const DataProvider: IMycDataProvider): TLayer;
+function TMycChart.AddPanel: TPanel;
+begin
+ Result := TPanel.Create(Self);
+ FPanelList.Add(Result);
+ Repaint;
+end;
+
+function TMycChart.GetPanel(Index: Integer): TPanel;
+begin
+ Result := FPanelList[Index];
+end;
+
+function TMycChart.GetPanelCount: Integer;
+begin
+ Result := FPanelList.Count;
+end;
+
+function TMycChart.SetXAxisSeries(const DataProvider: IMycDataProvider): TMycChart.TLayer;
begin
- // Free the old one if it exists, and create the new master series
FXAxisSeries.Free;
-
+ // Note: TChartCustomLayer needs its owner to be a TPanel. We use the first panel.
var counter := TChartSeriesCounter.Create;
-
DataProvider.Link(counter);
-
- FXAxisSeries := TChartCustomLayer.Create(Self, counter.Sender);
+ FXAxisSeries := TChartCustomLayer.Create(Self.Panels[0], counter.Sender);
Result := FXAxisSeries;
end;
-function TMycChart.AddDoubleSeries(
- const DataProvider: IMycDataProvider;
- const ALineColor: TAlphaColor = TAlphaColors.Cornflowerblue;
- const ALineWidth: Single = 1.5
-): TLayer;
-begin
- Result := TChartLineLayer.Create(Self, DataProvider, ALineColor, ALineWidth);
- FSeriesList.Add(Result);
-end;
-
-function TMycChart.AddOhlcSeries(
- const DataProvider: IMycDataProvider;
- const AUpColor: TAlphaColor = TAlphaColors.Green;
- const ADownColor: TAlphaColor = TAlphaColors.Red
-): TLayer;
-begin
- Result :=
- TChartOhlcLayer.Create(Self, DataProvider, TMutable.Constant(AUpColor), TMutable.Constant(ADownColor));
- FSeriesList.Add(Result);
-end;
-
procedure TMycChart.DoIdle;
+var
+ prevTotalCount, newTotalCount: Int64;
+ seriesChanged, seriesRepaint, repaintSignal: Boolean;
+ countDelta: Int64;
begin
- // An X-axis series is required for any operation
if not Assigned(FXAxisSeries) then
exit;
- // First update the master series
- var prevTotalCount := FXAxisSeries.Series.TotalCount;
+ prevTotalCount := FXAxisSeries.Series.TotalCount;
FXAxisSeries.Update;
+ newTotalCount := FXAxisSeries.Series.TotalCount;
- // Check if the master series itself has changed
- var newTotalCount := FXAxisSeries.Series.TotalCount;
- var seriesChanged := prevTotalCount <> newTotalCount;
+ seriesChanged := prevTotalCount <> newTotalCount;
+ seriesRepaint := (FViewStartIndex = 0);
- // Repaint if the last bar is visible, so we display live data
- var seriesRepaint := FViewStartIndex = 0;
-
- for var series in FSeriesList do
+ for var panel in FPanelList do
begin
- var seriesCount := series.Series.TotalCount;
-
- // Check if this series may need a repaint (if it lags behind the master series and these lags are currently visible)
- if FViewStartIndex <= prevTotalCount - seriesCount then
- seriesRepaint := true;
-
- // Update the series
- series.Update;
-
- if seriesCount <> series.Series.TotalCount then
- seriesChanged := true;
+ var panelChanged, panelRepaint: Boolean;
+ panel.UpdateAndCheckChanges(prevTotalCount, FViewStartIndex, panelChanged, panelRepaint);
+ seriesChanged := seriesChanged or panelChanged;
+ seriesRepaint := seriesRepaint or panelRepaint;
end;
- // Don't move chart, if the last bar isn't visible
- if FViewStartIndex > 0 then
+ if (FViewStartIndex > 0) then
begin
- var countDelta := newTotalCount - prevTotalCount;
- if countDelta > 0 then
+ countDelta := newTotalCount - prevTotalCount;
+ if (countDelta > 0) then
begin
- // Adjust the start index based on the absolute change in total data points
FViewStartIndex := FViewStartIndex + countDelta;
- // Clamp to beginning of dataset
- if FViewStartIndex + FViewCount > newTotalCount then
+ if (FViewStartIndex + FViewCount > newTotalCount) then
FViewStartIndex := newTotalCount - FViewCount;
end;
end;
- // finally, check the repaint signal
- var repaintSignal := false; // FNeedRepaint.Reset;
+ repaintSignal := FNeedRepaint.Reset;
if repaintSignal or (seriesRepaint and seriesChanged) then
Repaint;
end;
+procedure TMycChart.Paint;
+var
+ rect, panelRect: TRectF;
+ isButtonVisible: Boolean;
+ buttonColor: TAlphaColor;
+ path: TPathData;
+ masterTotalCount: Int64;
+ panelHeight: Single;
+begin
+ inherited;
+ rect := Self.LocalRect;
+
+ if (not Assigned(FXAxisSeries)) or (FXAxisSeries.Series.Count <= 1) or (FViewCount <= 1) or (PanelCount = 0) then
+ begin
+ Canvas.Fill.Color := TAlphaColors.Gray;
+ Canvas.FillText(rect, 'No Data', false, 1, [], TTextAlign.Center, TTextAlign.Center);
+ Exit;
+ end;
+
+ masterTotalCount := FXAxisSeries.Series.TotalCount;
+ panelHeight := rect.Height / PanelCount;
+
+ for var i := 0 to PanelCount - 1 do
+ begin
+ var panel := Panels[i];
+ panelRect := TRectF.Create(rect.Left, rect.Top + (i * panelHeight), rect.Right, rect.Top + ((i + 1) * panelHeight));
+
+ // Draw panel content
+ panel.Paint(Self.Canvas, panelRect, masterTotalCount, FViewStartIndex, FViewCount);
+
+ // Draw separator line
+ if (i > 0) then
+ begin
+ Canvas.Stroke.Color := TAlphaColors.Gray;
+ Canvas.Stroke.Thickness := 1;
+ Canvas.DrawLine(PointF(panelRect.Left, panelRect.Top), PointF(panelRect.Right, panelRect.Top), 1);
+ end;
+ end;
+
+ // --- Draw the "jump to latest" button (code unchanged) ---
+ isButtonVisible := (FViewStartIndex > 0);
+
+ if isButtonVisible then
+ begin
+ FJumpButtonRect := TRectF.Create(Self.Width - 44, Self.Height - 44, Self.Width - 10, Self.Height - 10);
+
+ if FJumpButtonPressed then
+ buttonColor := $FF707070
+ else if FJumpButtonHot then
+ buttonColor := $FF505050
+ else
+ buttonColor := $FF303030;
+
+ Canvas.Fill.Color := buttonColor;
+ Canvas.FillRect(FJumpButtonRect, 4, 4, AllCorners, 0.7);
+
+ Canvas.Stroke.Color := TAlphaColors.White;
+ Canvas.Stroke.Thickness := 1.5;
+
+ path := TPathData.Create;
+ try
+ var cx := FJumpButtonRect.CenterPoint.X;
+ var cy := FJumpButtonRect.CenterPoint.Y;
+ path.MoveTo(PointF(cx - 5, cy - 6));
+ path.LineTo(PointF(cx, cy));
+ path.LineTo(PointF(cx - 5, cy + 6));
+ path.MoveTo(PointF(cx + 2, cy - 6));
+ path.LineTo(PointF(cx + 7, cy));
+ path.LineTo(PointF(cx + 2, cy + 6));
+ Canvas.DrawPath(path, 1);
+ finally
+ path.Free;
+ end;
+ end
+ else
+ begin
+ FJumpButtonRect := TRectF.Empty;
+ FJumpButtonPressed := false;
+ end;
+end;
+
procedure TMycChart.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single);
begin
// Check for button press first
@@ -382,153 +471,156 @@ begin
Repaint;
end;
-procedure TMycChart.Paint;
+{ TMycChart.TLayer }
- function CalcView(Series: TSeries; MasterCount: Int64; out First: Int64; out Last: Int64): Boolean;
+constructor TMycChart.TLayer.Create(AOwner: TPanel);
+begin
+ inherited Create;
+ FParent := AOwner;
+end;
+
+function TMycChart.TLayer.GetOwner: TMycChart;
+begin
+ Result := FParent.Owner;
+end;
+
+procedure TMycChart.TLayer.Repaint;
+begin
+ FParent.Owner.Repaint;
+end;
+
+{ TMycChart.TPanel }
+
+constructor TMycChart.TPanel.Create(AOwner: TMycChart);
+begin
+ inherited Create;
+ FOwner := AOwner;
+ FSeriesList := TObjectList.Create(true);
+end;
+
+destructor TMycChart.TPanel.Destroy;
+begin
+ FSeriesList.Free;
+ inherited Destroy;
+end;
+
+function TMycChart.TPanel.AddDoubleSeries(
+ const DataProvider: IMycDataProvider;
+ const ALineColor: TAlphaColor;
+ const ALineWidth: Single
+): TMycChart.TLayer;
+begin
+ Result := TChartLineLayer.Create(Self, DataProvider, ALineColor, ALineWidth);
+ FSeriesList.Add(Result);
+end;
+
+function TMycChart.TPanel.AddOhlcSeries(
+ const DataProvider: IMycDataProvider;
+ const AUpColor, ADownColor: TAlphaColor
+): TMycChart.TLayer;
+begin
+ Result :=
+ TChartOhlcLayer.Create(Self, DataProvider, TMutable.Constant(AUpColor), TMutable.Constant(ADownColor));
+ FSeriesList.Add(Result);
+end;
+
+procedure TMycChart.TPanel.Paint(const Canvas: TCanvas; const Viewport: TRectF; MasterTotalCount, ViewStartIndex, ViewCount: Int64);
+var
+ localMin, localMax, seriesMin, seriesMax, padding: Double;
+ rangeInitialized: Boolean;
+ seriesFirst, seriesLast: Int64;
+ xTransform, yTransform: TFunc;
+
+ function CalcView(Series: TMycChart.TSeries; out First, Last: Int64): Boolean;
+ var
+ seriesTotalCount, offset, startIdx: Int64;
begin
- var seriesTotalCount := series.TotalCount;
- var offset := MasterCount - seriesTotalCount;
- var startIdx := FViewStartIndex - offset;
+ seriesTotalCount := series.TotalCount;
+ offset := MasterTotalCount - seriesTotalCount;
+ startIdx := ViewStartIndex - offset;
First := Max(0, startIdx);
- Last := Min(series.Count, startIdx + FViewCount) - 1;
-
- Result := First <= Last - 1;
+ Last := Min(series.Count, startIdx + ViewCount) - 1;
+ Result := First <= Last;
end;
-var
- rect: TRectF;
- globalMin, globalMax, seriesMin, seriesMax, padding: Double;
- rangeInitialized: Boolean;
- xTransform: TFunc;
- yTransform: TFunc;
- isButtonVisible: Boolean;
- buttonColor: TAlphaColor;
- path: TPathData;
- masterTotalCount, seriesFirst, seriesLast: Int64;
begin
- inherited;
- rect := Self.LocalRect;
-
- // The chart requires an X-axis series to be set before it can draw anything.
- if not Assigned(FXAxisSeries) or (FXAxisSeries.Series.Count <= 1) or (FViewCount <= 1) then
- begin
- Canvas.Fill.Color := TAlphaColors.Gray;
- Canvas.FillText(rect, 'No Data', false, 1, [], TTextAlign.Center, TTextAlign.Center);
- Exit;
- end;
-
rangeInitialized := false;
- masterTotalCount := FXAxisSeries.Series.TotalCount;
+ // 1. Calculate value range (Y-Axis) for this panel only
for var series in FSeriesList do
begin
- if CalcView(series.Series, masterTotalCount, seriesFirst, seriesLast) then
+ if CalcView(series.Series, seriesFirst, seriesLast) then
begin
if series.GetValueRange(seriesFirst, seriesLast, seriesMin, seriesMax) then
begin
if not rangeInitialized then
begin
- globalMin := seriesMin;
- globalMax := seriesMax;
+ localMin := seriesMin;
+ localMax := seriesMax;
rangeInitialized := true;
end
else
begin
- globalMin := Min(globalMin, seriesMin);
- globalMax := Max(globalMax, seriesMax);
+ localMin := Min(localMin, seriesMin);
+ localMax := Max(localMax, seriesMax);
end;
end;
end;
end;
if not rangeInitialized then
- Exit;
+ Exit; // Nothing to draw in this panel
- padding := (globalMax - globalMin) * 0.1;
- globalMin := globalMin - padding;
- globalMax := globalMax + padding;
+ padding := (localMax - localMin) * 0.1;
+ localMin := localMin - padding;
+ localMax := localMax + padding;
- if (globalMax - globalMin) = 0 then
- exit;
+ if (localMax - localMin) = 0 then
+ localMax := localMin + 1; // Avoid division by zero
- // Base transform maps an index from the viewport to a screen coordinate
+ // 2. Create transforms for this panel
yTransform :=
- function(value: Double): Single begin Result := rect.Top + (1 - (value - globalMin) / (globalMax - globalMin)) * rect.Height; end;
+ function(value: Double): Single
+ begin
+ Result := Viewport.Top + (1 - (value - localMin) / (localMax - localMin)) * Viewport.Height;
+ end;
- // --- Draw Series with Offset Compensation ---
- // The masterTotalCount is already calculated from the range finding loop
+ // 3. Paint all layers in this panel
for var series in FSeriesList do
begin
- if CalcView(series.Series, masterTotalCount, seriesFirst, seriesLast) then
+ if CalcView(series.Series, seriesFirst, seriesLast) then
begin
- var offset := masterTotalCount - series.Series.TotalCount;
+ var offset := MasterTotalCount - series.Series.TotalCount;
xTransform :=
function(index: Double): Single
begin
- Result := rect.Right - (((index + offset - FViewStartIndex) / (FViewCount - 1)) * rect.Width);
+ Result := Viewport.Right - (((index + offset - ViewStartIndex) / (ViewCount - 1)) * Viewport.Width);
end;
- series.Paint(Self.Canvas, seriesFirst, seriesLast, xTransform, yTransform);
+ series.Paint(Canvas, seriesFirst, seriesLast, xTransform, yTransform);
end;
end;
-
- // --- Draw the "jump to latest" button ---
- isButtonVisible := (FViewStartIndex > 0);
-
- if isButtonVisible then
- begin
- // Define button position and size
- FJumpButtonRect := TRectF.Create(Self.Width - 44, Self.Height - 44, Self.Width - 10, Self.Height - 10);
-
- // Determine color based on state
- if FJumpButtonPressed then
- buttonColor := $FF707070 // Pressed color
- else if FJumpButtonHot then
- buttonColor := $FF505050 // Hover color
- else
- buttonColor := $FF303030; // Default color
-
- // Draw button background
- Canvas.Fill.Color := buttonColor;
- Canvas.FillRect(FJumpButtonRect, 4, 4, AllCorners, 0.7);
-
- // Draw an icon
- Canvas.Stroke.Color := TAlphaColors.White;
- Canvas.Stroke.Thickness := 1.5;
-
- path := TPathData.Create;
- try
- var cx := FJumpButtonRect.CenterPoint.X;
- var cy := FJumpButtonRect.CenterPoint.Y;
- path.MoveTo(PointF(cx - 5, cy - 6));
- path.LineTo(PointF(cx, cy));
- path.LineTo(PointF(cx - 5, cy + 6));
- path.MoveTo(PointF(cx + 2, cy - 6));
- path.LineTo(PointF(cx + 7, cy));
- path.LineTo(PointF(cx + 2, cy + 6));
- Canvas.DrawPath(path, 1);
- finally
- path.Free;
- end;
- end
- else
- begin
- // Reset button state when not visible
- FJumpButtonRect := TRectF.Empty;
- FJumpButtonPressed := false;
- end;
end;
-constructor TMycChart.TLayer.Create(AOwner: TMycChart);
+procedure TMycChart.TPanel.UpdateAndCheckChanges(MasterTotalCount, ViewStartIndex: Int64; out SeriesChanged, SeriesRepaint: Boolean);
+var
+ seriesCount: Int64;
begin
- inherited Create;
- FOwner := AOwner;
-end;
+ SeriesChanged := false;
+ SeriesRepaint := false;
+ for var series in FSeriesList do
+ begin
+ seriesCount := series.Series.TotalCount;
-procedure TMycChart.TLayer.Repaint;
-begin
- FOwner.Repaint;
+ if (ViewStartIndex <= MasterTotalCount - seriesCount) then
+ SeriesRepaint := true;
+
+ series.Update;
+
+ if (seriesCount <> series.Series.TotalCount) then
+ SeriesChanged := true;
+ end;
end;
end.
diff --git a/Src/Myc.Trade.DataPoint.pas b/Src/Myc.Trade.DataPoint.pas
index 52702a5..a02b888 100644
--- a/Src/Myc.Trade.DataPoint.pas
+++ b/Src/Myc.Trade.DataPoint.pas
@@ -5,25 +5,10 @@ interface
uses
System.TimeSpan,
Myc.Signals,
- Myc.Core.Notifier;
+ Myc.Core.Notifier,
+ Myc.Trade.Types;
type
- // A data record for an Ask/Bid price pair.
- TAskBidItem = packed record
- Ask: Double;
- Bid: Double;
- constructor Create(AAsk, ABid: Double);
- end;
-
- TOhlcItem = record
- Open: Double;
- High: Double;
- Low: Double;
- Close: Double;
- Volume: Double;
- constructor Create(AOpen, AHigh, ALow, AClose, AVolume: Double);
- end;
-
// Represents a time-stamped data point in a series.
TDataPoint = record
Time: TDateTime;
@@ -295,25 +280,6 @@ begin
Result := Self.Convert(function(const Ohlc: TDataPoint): Single begin Result := Ohlc.Data.Volume; end);
end;
-{ TAskBidItem }
-
-constructor TAskBidItem.Create(AAsk, ABid: Double);
-begin
- Ask := AAsk;
- Bid := ABid;
-end;
-
-{ TOhlcItem }
-
-constructor TOhlcItem.Create(AOpen, AHigh, ALow, AClose, AVolume: Double);
-begin
- Open := AOpen;
- High := AHigh;
- Low := ALow;
- Close := AClose;
- Volume := AVolume;
-end;
-
{ TDataPoint }
constructor TDataPoint.Create(ATime: TDateTime; const AData: T);
diff --git a/Src/Myc.Trade.Indicators.pas b/Src/Myc.Trade.Indicators.pas
index 4cdc180..7c0c342 100644
--- a/Src/Myc.Trade.Indicators.pas
+++ b/Src/Myc.Trade.Indicators.pas
@@ -5,8 +5,8 @@ interface
uses
System.SysUtils,
System.Math,
- Myc.Trade.DataArray,
- Myc.Trade.DataPoint;
+ Myc.Trade.Types,
+ Myc.Trade.DataArray;
type
// Result for the Moving Average Convergence Divergence (MACD) indicator.
diff --git a/Src/Myc.Trade.Types.pas b/Src/Myc.Trade.Types.pas
new file mode 100644
index 0000000..e123c7d
--- /dev/null
+++ b/Src/Myc.Trade.Types.pas
@@ -0,0 +1,43 @@
+unit Myc.Trade.Types;
+
+interface
+
+type
+ // A data record for an Ask/Bid price pair.
+ TAskBidItem = packed record
+ Ask: Double;
+ Bid: Double;
+ constructor Create(AAsk, ABid: Double);
+ end;
+
+ TOhlcItem = record
+ Open: Double;
+ High: Double;
+ Low: Double;
+ Close: Double;
+ Volume: Double;
+ constructor Create(AOpen, AHigh, ALow, AClose, AVolume: Double);
+ end;
+
+implementation
+
+{ TAskBidItem }
+
+constructor TAskBidItem.Create(AAsk, ABid: Double);
+begin
+ Ask := AAsk;
+ Bid := ABid;
+end;
+
+{ TOhlcItem }
+
+constructor TOhlcItem.Create(AOpen, AHigh, ALow, AClose, AVolume: Double);
+begin
+ Open := AOpen;
+ High := AHigh;
+ Low := ALow;
+ Close := AClose;
+ Volume := AVolume;
+end;
+
+end.