TLazy + Data-Endpoints refactoring
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
<ProjectVersion>20.3</ProjectVersion>
|
||||
<FrameworkType>FMX</FrameworkType>
|
||||
<Base>True</Base>
|
||||
<Config Condition="'$(Config)'==''">Release</Config>
|
||||
<Config Condition="'$(Config)'==''">Debug</Config>
|
||||
<Platform Condition="'$(Platform)'==''">Win64</Platform>
|
||||
<ProjectName Condition="'$(ProjectName)'==''">AuraTrader</ProjectName>
|
||||
<TargetedPlatforms>3</TargetedPlatforms>
|
||||
|
||||
+17
-8
@@ -483,13 +483,18 @@ begin
|
||||
var Highest: Double := Double.MinValue;
|
||||
|
||||
var ATR := Ohlc[0].Chain<Double>(TIndicators.CreateATR(50)).MakeParallel;
|
||||
var ATRSeries := TConverter.CreateEndpoint<Double>(ATR.Sender, 5);
|
||||
|
||||
var HullSeries := TConverter.CreateEndpoint<Double>(Hull.Sender, 5);
|
||||
var SmaSeries := TConverter.CreateEndpoint<Double>(Sma.Sender, 5);
|
||||
|
||||
// next stage
|
||||
|
||||
var ATREndPoint := TConverter.CreateEndpoint<Double>(ATR.Sender, 5);
|
||||
var ATRSeries: TSeries<Double>;
|
||||
|
||||
var HullEndPoint := TConverter.CreateEndpoint<Double>(Hull.Sender, 5);
|
||||
var HullSeries: TSeries<Double>;
|
||||
|
||||
var SmaEndPoint := TConverter.CreateEndpoint<Double>(Sma.Sender, 5);
|
||||
var SmaSeries: TSeries<Double>;
|
||||
|
||||
var curr: TSignal;
|
||||
curr.SL := Double.NaN;
|
||||
curr.Entry := Double.NaN;
|
||||
@@ -508,7 +513,11 @@ begin
|
||||
Result.Sig := 0;
|
||||
var pnl: double := NaN;
|
||||
|
||||
if (HullSeries.Value[0] < SmaSeries.Value[0]) and (HullSeries.Value[1] >= SmaSeries.Value[1]) then
|
||||
ATREndPoint.Update(ATRSeries);
|
||||
HullEndPoint.Update(HullSeries);
|
||||
SmaEndPoint.Update(SmaSeries);
|
||||
|
||||
if (HullSeries[0] < SmaSeries[0]) and (HullSeries[1] >= SmaSeries[1]) then
|
||||
begin
|
||||
if curr.Sig > 0 then
|
||||
pnl := Ohlc.Close - curr.Entry;
|
||||
@@ -518,7 +527,7 @@ begin
|
||||
curr.Entry := Ohlc.Close;
|
||||
Result := curr;
|
||||
end
|
||||
else if (HullSeries.Value[0] > SmaSeries.Value[0]) and (HullSeries.Value[1] <= SmaSeries.Value[1]) then
|
||||
else if (HullSeries[0] > SmaSeries[0]) and (HullSeries[1] <= SmaSeries[1]) then
|
||||
begin
|
||||
if curr.Sig < 0 then
|
||||
pnl := curr.Entry - Ohlc.Close;
|
||||
@@ -529,7 +538,7 @@ begin
|
||||
Result := curr;
|
||||
end;
|
||||
|
||||
var atr := 15 * ATRSeries.Value[0];
|
||||
var atr := 15 * ATRSeries[0];
|
||||
if curr.Sig > 0 then
|
||||
begin
|
||||
if Ohlc.Close > curr.SL then
|
||||
@@ -600,8 +609,8 @@ begin
|
||||
panel.AddOhlcSeries(Ohlc[0].Sender);
|
||||
panel.AddDoubleSeries(Hull.Sender, TAlphaColors.Cornflowerblue, 2);
|
||||
panel.AddDoubleSeries(Sma.Sender, TAlphaColors.Brown, 1.5);
|
||||
panel.AddDoubleSeries(Signal.Field<Double>('SL').Sender, TAlphaColors.Red, 2);
|
||||
panel.AddDoubleSeries(Signal.Field<Double>('Entry').Sender, TAlphaColors.Green, 1);
|
||||
panel.AddDoubleSeries(Signal.Field<Double>('SL').Sender, TAlphaColors.Red, 2);
|
||||
|
||||
var pnlChart := TMycChart.Create(Self);
|
||||
AlignControl(pnlChart);
|
||||
|
||||
@@ -10,7 +10,7 @@ uses
|
||||
Myc.Mutable;
|
||||
|
||||
type
|
||||
TMycNullMutable<T> = class(TInterfacedObject, TMutable<T>.IMutable)
|
||||
TMycConstMutable<T> = class(TInterfacedObject, TMutable<T>.IMutable)
|
||||
private
|
||||
FValue: T;
|
||||
function GetChanged: TSignal;
|
||||
@@ -64,25 +64,34 @@ type
|
||||
procedure SetValue(const Value: T);
|
||||
end;
|
||||
|
||||
TMycConstLazy<T> = class(TInterfacedObject, TLazy<T>.ILazy)
|
||||
private
|
||||
FValue: T;
|
||||
function GetChanged: TState;
|
||||
function Update(var Value: T): Boolean;
|
||||
public
|
||||
constructor Create(AValue: T);
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.Generics.Defaults;
|
||||
|
||||
constructor TMycNullMutable<T>.Create(AValue: T);
|
||||
constructor TMycConstMutable<T>.Create(AValue: T);
|
||||
begin
|
||||
inherited Create;
|
||||
FValue := AValue;
|
||||
end;
|
||||
|
||||
{ TMycNullMutable<T> }
|
||||
{ TMycConstMutable<T> }
|
||||
|
||||
function TMycNullMutable<T>.GetChanged: TSignal;
|
||||
function TMycConstMutable<T>.GetChanged: TSignal;
|
||||
begin
|
||||
Result := TSignal.Null;
|
||||
end;
|
||||
|
||||
function TMycNullMutable<T>.GetValue: T;
|
||||
function TMycConstMutable<T>.GetValue: T;
|
||||
begin
|
||||
Result := FValue;
|
||||
end;
|
||||
@@ -179,4 +188,22 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
constructor TMycConstLazy<T>.Create(AValue: T);
|
||||
begin
|
||||
inherited Create;
|
||||
FValue := AValue;
|
||||
end;
|
||||
|
||||
{ TMycConstLazy<T> }
|
||||
|
||||
function TMycConstLazy<T>.GetChanged: TState;
|
||||
begin
|
||||
Result := TState.Null;
|
||||
end;
|
||||
|
||||
function TMycConstLazy<T>.Update(var Value: T): Boolean;
|
||||
begin
|
||||
Result := false;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
@@ -15,32 +15,17 @@ uses
|
||||
Myc.Fmx.Chart;
|
||||
|
||||
type
|
||||
TChartSeriesProcessor<T> = class(TMycChart.TSeries)
|
||||
strict private
|
||||
FDataSeries: TSeries<T>;
|
||||
private
|
||||
FData: TSeries<T>;
|
||||
FDataProvider: TDataProvider<T>;
|
||||
FReceiver: TMutable<TSeries<T>>;
|
||||
protected
|
||||
function GetCount: Int64; override;
|
||||
function GetTotalCount: Int64; override;
|
||||
procedure Update;
|
||||
|
||||
public
|
||||
constructor Create(const ADataProvider: TDataProvider<T>; const ALookback: TMutable<Int64>);
|
||||
destructor Destroy; override;
|
||||
property Data: TSeries<T> read FData;
|
||||
end;
|
||||
|
||||
{ TChartCustomLayer }
|
||||
TChartCustomLayer<T> = class abstract(TMycChart.TDataLayer)
|
||||
private
|
||||
FSeries: TChartSeriesProcessor<T>;
|
||||
FDataProvider: TDataProvider<T>;
|
||||
FReceiver: TLazy<TSeries<T>>;
|
||||
FData: TSeries<T>;
|
||||
protected
|
||||
function GetSeries: TMycChart.TSeries; override;
|
||||
function GetCount: Int64; override;
|
||||
function GetTotalCount: Int64; override;
|
||||
function GetValueRange(First, Last: Int64; out MinValue, MaxValue: Double): Boolean; override; abstract;
|
||||
procedure Update; override;
|
||||
function Update: Boolean; override;
|
||||
procedure Paint(
|
||||
const Canvas: TCanvas;
|
||||
First, Last: Int64;
|
||||
@@ -49,7 +34,7 @@ type
|
||||
); override; abstract;
|
||||
public
|
||||
constructor Create(AParent: TMycChart.TPanel; const ADataProvider: TDataProvider<T>);
|
||||
destructor Destroy; override;
|
||||
property Data: TSeries<T> read FData;
|
||||
end;
|
||||
|
||||
{ TChartOhlcLayer }
|
||||
@@ -98,14 +83,17 @@ type
|
||||
{ TChartXAxisLayer }
|
||||
TChartXAxisLayer<T> = class(TMycChart.TXAxisLayer)
|
||||
private
|
||||
FSeries: TChartSeriesProcessor<T>;
|
||||
FDataProvider: TDataProvider<T>;
|
||||
FReceiver: TLazy<TSeries<T>>;
|
||||
FData: TSeries<T>;
|
||||
protected
|
||||
function GetSeries: TMycChart.TSeries; override;
|
||||
procedure Update; override;
|
||||
function Update: Boolean; override;
|
||||
function GetCaption(Idx: Int64): String; override;
|
||||
function GetCount: Int64; override;
|
||||
function GetTotalCount: Int64; override;
|
||||
public
|
||||
constructor Create(AOwner: TMycChart; const ADataProvider: TDataProvider<T>);
|
||||
destructor Destroy; override;
|
||||
property Data: TSeries<T> read FData;
|
||||
end;
|
||||
|
||||
{ TChartXAxisTimestampLayer }
|
||||
@@ -126,37 +114,6 @@ uses
|
||||
System.Math,
|
||||
FMX.Types;
|
||||
|
||||
{ TChartSeriesProcessor<T> }
|
||||
|
||||
constructor TChartSeriesProcessor<T>.Create(const ADataProvider: TDataProvider<T>; const ALookback: TMutable<Int64>);
|
||||
begin
|
||||
inherited Create;
|
||||
FDataProvider := ADataProvider;
|
||||
FData := FDataSeries;
|
||||
|
||||
FReceiver := TConverter.CreateEndpoint<T>(FDataProvider, ALookback.Value);
|
||||
end;
|
||||
|
||||
destructor TChartSeriesProcessor<T>.Destroy;
|
||||
begin
|
||||
inherited;
|
||||
end;
|
||||
|
||||
function TChartSeriesProcessor<T>.GetCount: Int64;
|
||||
begin
|
||||
Result := FData.Count;
|
||||
end;
|
||||
|
||||
function TChartSeriesProcessor<T>.GetTotalCount: Int64;
|
||||
begin
|
||||
Result := FData.TotalCount;
|
||||
end;
|
||||
|
||||
procedure TChartSeriesProcessor<T>.Update;
|
||||
begin
|
||||
FData := FReceiver.Value;
|
||||
end;
|
||||
|
||||
{ TChartLineLayer }
|
||||
|
||||
constructor TChartLineLayer.Create(
|
||||
@@ -179,14 +136,16 @@ begin
|
||||
MaxValue := -MaxDouble;
|
||||
for i := First to Last do
|
||||
begin
|
||||
var v := FSeries.Data.Items[i];
|
||||
var v := Data.Items[i];
|
||||
if not IsNaN(v) then
|
||||
begin
|
||||
MinValue := Min(MinValue, v);
|
||||
MaxValue := Max(MaxValue, v);
|
||||
if v < MinValue then
|
||||
MinValue := v;
|
||||
if v > MaxValue then
|
||||
MaxValue := v;
|
||||
end;
|
||||
end;
|
||||
Result := (MinValue < MaxValue);
|
||||
Result := (MinValue <= MaxValue);
|
||||
end;
|
||||
|
||||
procedure TChartLineLayer.Paint(
|
||||
@@ -205,18 +164,18 @@ begin
|
||||
while n < Last do
|
||||
begin
|
||||
// Skip warmup/gap data
|
||||
while (n <= Last) and (IsNaN(FSeries.Data[n])) do
|
||||
while (n <= Last) and (IsNaN(Data[n])) do
|
||||
inc(n);
|
||||
|
||||
if n < Last then
|
||||
begin
|
||||
points.MoveTo(TPointF.Create(XForm(n), YForm(FSeries.Data[n])));
|
||||
points.MoveTo(TPointF.Create(XForm(n), YForm(Data[n])));
|
||||
inc(n);
|
||||
while n <= Last do
|
||||
begin
|
||||
if IsNaN(FSeries.Data[n]) then
|
||||
if IsNaN(Data[n]) then
|
||||
break;
|
||||
points.LineTo(TPointF.Create(XForm(n), YForm(FSeries.Data[n])));
|
||||
points.LineTo(TPointF.Create(XForm(n), YForm(Data[n])));
|
||||
inc(n);
|
||||
end;
|
||||
end;
|
||||
@@ -256,11 +215,13 @@ begin
|
||||
|
||||
for i := First to Last do
|
||||
begin
|
||||
var item := FSeries.Data.Items[i];
|
||||
MinValue := Min(MinValue, item.Low);
|
||||
MaxValue := Max(MaxValue, item.High);
|
||||
var item := Data.Items[i];
|
||||
if item.Low < MinValue then
|
||||
MinValue := item.Low;
|
||||
if item.High > MaxValue then
|
||||
MaxValue := item.High;
|
||||
end;
|
||||
Result := (MinValue <> MaxDouble);
|
||||
Result := MinValue <= MaxDouble;
|
||||
end;
|
||||
|
||||
procedure TChartOhlcLayer.Paint(
|
||||
@@ -280,7 +241,7 @@ begin
|
||||
|
||||
for i := First to Last do
|
||||
begin
|
||||
item := FSeries.Data.Items[i];
|
||||
item := Data.Items[i];
|
||||
x := XForm(i);
|
||||
yOpen := YForm(item.Open);
|
||||
yClose := YForm(item.Close);
|
||||
@@ -309,23 +270,24 @@ end;
|
||||
constructor TChartCustomLayer<T>.Create(AParent: TMycChart.TPanel; const ADataProvider: TDataProvider<T>);
|
||||
begin
|
||||
inherited Create(AParent);
|
||||
FSeries := TChartSeriesProcessor<T>.Create(ADataProvider, AParent.Owner.Lookback.AsMutable);
|
||||
FDataProvider := ADataProvider;
|
||||
|
||||
FReceiver := TConverter.CreateEndpoint<T>(FDataProvider, Owner.Lookback.Value);
|
||||
end;
|
||||
|
||||
destructor TChartCustomLayer<T>.Destroy;
|
||||
function TChartCustomLayer<T>.GetCount: Int64;
|
||||
begin
|
||||
FSeries.Free;
|
||||
inherited;
|
||||
Result := FData.Count;
|
||||
end;
|
||||
|
||||
function TChartCustomLayer<T>.GetSeries: TMycChart.TSeries;
|
||||
function TChartCustomLayer<T>.GetTotalCount: Int64;
|
||||
begin
|
||||
Result := FSeries;
|
||||
Result := FData.TotalCount;
|
||||
end;
|
||||
|
||||
procedure TChartCustomLayer<T>.Update;
|
||||
function TChartCustomLayer<T>.Update: Boolean;
|
||||
begin
|
||||
FSeries.Update;
|
||||
Result := FReceiver.Update(FData);
|
||||
end;
|
||||
|
||||
{ TChartXAxisLayer }
|
||||
@@ -333,30 +295,32 @@ end;
|
||||
constructor TChartXAxisLayer<T>.Create(AOwner: TMycChart; const ADataProvider: TDataProvider<T>);
|
||||
begin
|
||||
inherited Create(AOwner);
|
||||
FSeries := TChartSeriesProcessor<T>.Create(ADataProvider, AOwner.Lookback.AsMutable);
|
||||
end;
|
||||
|
||||
destructor TChartXAxisLayer<T>.Destroy;
|
||||
begin
|
||||
FSeries.Free;
|
||||
inherited;
|
||||
FDataProvider := ADataProvider;
|
||||
FReceiver := TConverter.CreateEndpoint<T>(FDataProvider, Owner.Lookback.Value);
|
||||
end;
|
||||
|
||||
function TChartXAxisLayer<T>.GetCaption(Idx: Int64): String;
|
||||
begin
|
||||
Result := IntToStr(Idx);
|
||||
Result := IntToStr(Owner.ViewStartIndex + Idx);
|
||||
end;
|
||||
|
||||
function TChartXAxisLayer<T>.GetSeries: TMycChart.TSeries;
|
||||
function TChartXAxisLayer<T>.GetCount: Int64;
|
||||
begin
|
||||
Result := FSeries;
|
||||
Result := FData.Count;
|
||||
end;
|
||||
|
||||
procedure TChartXAxisLayer<T>.Update;
|
||||
function TChartXAxisLayer<T>.GetTotalCount: Int64;
|
||||
begin
|
||||
FSeries.Update;
|
||||
Result := FData.TotalCount;
|
||||
end;
|
||||
|
||||
function TChartXAxisLayer<T>.Update: Boolean;
|
||||
begin
|
||||
Result := FReceiver.Update(FData);
|
||||
end;
|
||||
|
||||
{ TChartXAxisTimestampLayer }
|
||||
|
||||
constructor TChartXAxisTimestampLayer.Create(AOwner: TMycChart; ATimeframe: TTimeframe; const ADataProvider: TDataProvider<TDateTime>);
|
||||
begin
|
||||
inherited Create(AOwner, ADataProvider);
|
||||
@@ -369,9 +333,9 @@ var
|
||||
formatStr: string;
|
||||
begin
|
||||
Result := '';
|
||||
if (Idx >= 0) and (Idx < FSeries.Count) then
|
||||
if (Idx >= 0) and (Idx < Data.Count) then
|
||||
begin
|
||||
dt := FSeries.Data[Idx];
|
||||
dt := Data[Idx];
|
||||
|
||||
// Choose format based on the series timeframe.
|
||||
if FTimeframe >= D then // Daily, Weekly, Monthly, Yearly
|
||||
|
||||
+59
-70
@@ -26,26 +26,18 @@ type
|
||||
type
|
||||
TPanel = class;
|
||||
|
||||
// Abstract base for a data series within the chart.
|
||||
TSeries = class abstract(TObject)
|
||||
protected
|
||||
function GetCount: Int64; virtual; abstract;
|
||||
function GetTotalCount: Int64; virtual; abstract;
|
||||
public
|
||||
property Count: Int64 read GetCount;
|
||||
property TotalCount: Int64 read GetTotalCount;
|
||||
end;
|
||||
|
||||
// Abstract base for a drawable layer in a panel.
|
||||
TLayer = class abstract(TObject)
|
||||
protected
|
||||
function GetCount: Int64; virtual; abstract;
|
||||
function GetTotalCount: Int64; virtual; abstract;
|
||||
function GetOwner: TMycChart; virtual; abstract;
|
||||
function GetSeries: TSeries; virtual; abstract;
|
||||
procedure Update; virtual; abstract;
|
||||
function Update: Boolean; virtual; abstract;
|
||||
public
|
||||
procedure Repaint;
|
||||
property Count: Int64 read GetCount;
|
||||
property TotalCount: Int64 read GetTotalCount;
|
||||
property Owner: TMycChart read GetOwner;
|
||||
property Series: TSeries read GetSeries;
|
||||
end;
|
||||
|
||||
TDataLayer = class abstract(TLayer)
|
||||
@@ -93,12 +85,12 @@ type
|
||||
TPanel = class(TObject)
|
||||
private
|
||||
FOwner: TMycChart;
|
||||
FSeriesList: TObjectList<TMycChart.TDataLayer>;
|
||||
FLayers: TObjectList<TMycChart.TDataLayer>;
|
||||
FWeight: Single;
|
||||
procedure SetWeight(const Value: Single);
|
||||
protected
|
||||
procedure Paint(const Canvas: TCanvas; const Viewport: TRectF; MasterTotalCount: Int64; ViewStartIndex, ViewCount: Int64);
|
||||
procedure Update(MasterTotalCount: Int64; ViewStartIndex: Int64; out SeriesChanged, SeriesRepaint: Boolean);
|
||||
function Update: Boolean;
|
||||
|
||||
public
|
||||
constructor Create(AOwner: TMycChart; AWeight: Single);
|
||||
@@ -144,6 +136,7 @@ type
|
||||
protected
|
||||
procedure Paint; override;
|
||||
procedure DoIdle;
|
||||
function Update: Boolean;
|
||||
procedure DoMouseLeave; override;
|
||||
procedure MouseWheel(Shift: TShiftState; WheelDelta: Integer; var Handled: Boolean); override;
|
||||
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); override;
|
||||
@@ -168,6 +161,7 @@ type
|
||||
property JumpButtonRect: TRectF read FJumpButtonRect;
|
||||
property ViewCount: Int64 read FViewCount;
|
||||
property ViewStartIndex: Int64 read FViewStartIndex;
|
||||
property XAxisSeries: TMycChart.TXAxisLayer read FXAxisSeries;
|
||||
end;
|
||||
|
||||
implementation
|
||||
@@ -230,7 +224,7 @@ begin
|
||||
Result.Point.X := X;
|
||||
Result.Point.Y := Y;
|
||||
Result.Idx := Round((rect.Right - X) * (FViewCount - 1) / rect.Width);
|
||||
if (Result.Idx < 0) or (Result.Idx >= FXAxisSeries.Series.Count) then
|
||||
if (Result.Idx < 0) or (Result.Idx >= FXAxisSeries.Count) then
|
||||
Result.Idx := -1;
|
||||
Result.BarX := NaN;
|
||||
if Result.Idx >= 0 then
|
||||
@@ -248,34 +242,34 @@ begin
|
||||
end;
|
||||
|
||||
procedure TMycChart.DoIdle;
|
||||
var
|
||||
prevTotalCount, newTotalCount: Int64;
|
||||
seriesChanged, seriesRepaint, repaintSignal: Boolean;
|
||||
countDelta: Int64;
|
||||
begin
|
||||
if Update then
|
||||
Repaint;
|
||||
end;
|
||||
|
||||
function TMycChart.Update: Boolean;
|
||||
begin
|
||||
if not Assigned(FXAxisSeries) then
|
||||
exit;
|
||||
exit(false);
|
||||
|
||||
prevTotalCount := FXAxisSeries.Series.TotalCount;
|
||||
FXAxisSeries.Update;
|
||||
newTotalCount := FXAxisSeries.Series.TotalCount;
|
||||
var prevTotalCount := FXAxisSeries.TotalCount;
|
||||
var seriesChanged := false;
|
||||
|
||||
seriesChanged := prevTotalCount <> newTotalCount;
|
||||
seriesRepaint := (FViewStartIndex = 0);
|
||||
if FXAxisSeries.Update then
|
||||
seriesChanged := (FViewStartIndex = 0);
|
||||
|
||||
for var panel in FPanelList do
|
||||
begin
|
||||
var panelChanged, panelRepaint: Boolean;
|
||||
panel.Update(prevTotalCount, FViewStartIndex, panelChanged, panelRepaint);
|
||||
seriesChanged := seriesChanged or panelChanged;
|
||||
seriesRepaint := seriesRepaint or panelRepaint;
|
||||
if panel.Update then
|
||||
seriesChanged := true;
|
||||
end;
|
||||
|
||||
// When we are not displaying live data, adjust start index accordingly
|
||||
if (FViewStartIndex > 0) then
|
||||
if FViewStartIndex > 0 then
|
||||
begin
|
||||
countDelta := newTotalCount - prevTotalCount;
|
||||
if (countDelta > 0) then
|
||||
var newTotalCount := FXAxisSeries.TotalCount;
|
||||
var countDelta := newTotalCount - prevTotalCount;
|
||||
if countDelta > 0 then
|
||||
begin
|
||||
FViewStartIndex := FViewStartIndex + countDelta;
|
||||
if (FViewStartIndex + FViewCount > newTotalCount) then
|
||||
@@ -283,10 +277,9 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
repaintSignal := FNeedRepaint.Reset;
|
||||
var repaintSignal := FNeedRepaint.Reset;
|
||||
|
||||
if repaintSignal or (seriesRepaint and seriesChanged) then
|
||||
Repaint;
|
||||
Result := repaintSignal or seriesChanged;
|
||||
end;
|
||||
|
||||
procedure TMycChart.DoMouseLeave;
|
||||
@@ -312,14 +305,14 @@ begin
|
||||
inherited;
|
||||
rect := Self.LocalRect;
|
||||
|
||||
if (not Assigned(FXAxisSeries)) or (FXAxisSeries.Series.Count <= 1) or (FViewCount <= 1) or (PanelCount = 0) then
|
||||
if (not Assigned(FXAxisSeries)) or (FXAxisSeries.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;
|
||||
masterTotalCount := FXAxisSeries.TotalCount;
|
||||
|
||||
// Calculate total weight for proportional panel height
|
||||
totalWeight := 0;
|
||||
@@ -332,6 +325,7 @@ begin
|
||||
for var i := 0 to PanelCount - 1 do
|
||||
begin
|
||||
var panel := Panels[i];
|
||||
|
||||
panelHeight := panel.Weight * rect.Height / totalWeight;
|
||||
panelRect := TRectF.Create(rect.Left, currentY, rect.Right, currentY + panelHeight);
|
||||
|
||||
@@ -542,7 +536,7 @@ begin
|
||||
FViewStartIndex := FViewStartIndex - indexDelta;
|
||||
|
||||
// Clamp values
|
||||
maxIndex := FXAxisSeries.Series.Count - FViewCount;
|
||||
maxIndex := FXAxisSeries.Count - FViewCount;
|
||||
if (maxIndex < 0) then
|
||||
maxIndex := 0;
|
||||
if (FViewStartIndex < 0) then
|
||||
@@ -602,7 +596,7 @@ begin
|
||||
if not Assigned(FXAxisSeries) then
|
||||
exit;
|
||||
|
||||
var xAxisCount := FXAxisSeries.Series.Count;
|
||||
var xAxisCount := FXAxisSeries.Count;
|
||||
if (xAxisCount = 0) or (Width <= 0) then
|
||||
exit;
|
||||
|
||||
@@ -667,13 +661,13 @@ constructor TMycChart.TPanel.Create(AOwner: TMycChart; AWeight: Single);
|
||||
begin
|
||||
inherited Create;
|
||||
FOwner := AOwner;
|
||||
FSeriesList := TObjectList<TMycChart.TDataLayer>.Create(true);
|
||||
FLayers := TObjectList<TMycChart.TDataLayer>.Create(true);
|
||||
FWeight := AWeight;
|
||||
end;
|
||||
|
||||
destructor TMycChart.TPanel.Destroy;
|
||||
begin
|
||||
FSeriesList.Free;
|
||||
FLayers.Free;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
@@ -684,7 +678,7 @@ function TMycChart.TPanel.AddDoubleSeries(
|
||||
): TMycChart.TDataLayer;
|
||||
begin
|
||||
Result := TChartLineLayer.Create(Self, DataProvider, ALineColor, ALineWidth);
|
||||
FSeriesList.Add(Result);
|
||||
FLayers.Add(Result);
|
||||
end;
|
||||
|
||||
function TMycChart.TPanel.AddOhlcSeries(
|
||||
@@ -695,7 +689,7 @@ function TMycChart.TPanel.AddOhlcSeries(
|
||||
begin
|
||||
Result :=
|
||||
TChartOhlcLayer.Create(Self, DataProvider, TMutable<TAlphaColor>.Constant(AUpColor), TMutable<TAlphaColor>.Constant(ADownColor));
|
||||
FSeriesList.Add(Result);
|
||||
FLayers.Add(Result);
|
||||
end;
|
||||
|
||||
procedure TMycChart.TPanel.Paint(const Canvas: TCanvas; const Viewport: TRectF; MasterTotalCount, ViewStartIndex, ViewCount: Int64);
|
||||
@@ -706,16 +700,16 @@ var
|
||||
xTransform: TFunc<Int64, Single>;
|
||||
yTransform: TFunc<Double, Single>;
|
||||
|
||||
function CalcView(Series: TMycChart.TSeries; out First, Last: Int64): Boolean;
|
||||
function CalcView(Layer: TMycChart.TDataLayer; out First, Last: Int64): Boolean;
|
||||
var
|
||||
seriesTotalCount, offset, startIdx: Int64;
|
||||
begin
|
||||
seriesTotalCount := series.TotalCount;
|
||||
seriesTotalCount := Layer.TotalCount;
|
||||
offset := MasterTotalCount - seriesTotalCount;
|
||||
startIdx := ViewStartIndex - offset;
|
||||
|
||||
First := Max(0, startIdx);
|
||||
Last := Min(series.Count, startIdx + ViewCount) - 1;
|
||||
Last := Min(Layer.Count, startIdx + ViewCount) - 1;
|
||||
Result := First <= Last;
|
||||
end;
|
||||
|
||||
@@ -724,12 +718,16 @@ begin
|
||||
localMin := 0;
|
||||
localMax := 0;
|
||||
|
||||
// Update data state
|
||||
for var layer in FLayers do
|
||||
layer.Update;
|
||||
|
||||
// 1. Calculate value range (Y-Axis) for this panel only
|
||||
for var series in FSeriesList do
|
||||
for var layer in FLayers do
|
||||
begin
|
||||
if CalcView(series.Series, seriesFirst, seriesLast) then
|
||||
if CalcView(layer, seriesFirst, seriesLast) then
|
||||
begin
|
||||
if series.GetValueRange(seriesFirst, seriesLast, seriesMin, seriesMax) then
|
||||
if layer.GetValueRange(seriesFirst, seriesLast, seriesMin, seriesMax) then
|
||||
begin
|
||||
if not rangeInitialized then
|
||||
begin
|
||||
@@ -760,18 +758,18 @@ begin
|
||||
yTransform := function(value: Double): Single begin Result := top + (1 - (value - localMin) / range) * height; end;
|
||||
|
||||
// 3. Paint all layers in this panel
|
||||
for var series in FSeriesList do
|
||||
for var layer in FLayers do
|
||||
begin
|
||||
if CalcView(series.Series, seriesFirst, seriesLast) then
|
||||
if CalcView(layer, seriesFirst, seriesLast) then
|
||||
begin
|
||||
var offset := MasterTotalCount - series.Series.TotalCount;
|
||||
var offset := MasterTotalCount - layer.TotalCount;
|
||||
xTransform :=
|
||||
function(index: Int64): Single
|
||||
begin
|
||||
Result := Viewport.Right - (((index + offset - ViewStartIndex) / (ViewCount - 1)) * Viewport.Width);
|
||||
end;
|
||||
|
||||
series.Paint(Canvas, seriesFirst, seriesLast, xTransform, yTransform);
|
||||
layer.Paint(Canvas, seriesFirst, seriesLast, xTransform, yTransform);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
@@ -789,23 +787,14 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMycChart.TPanel.Update(MasterTotalCount: Int64; ViewStartIndex: Int64; out SeriesChanged, SeriesRepaint: Boolean);
|
||||
var
|
||||
seriesCount: Int64;
|
||||
function TMycChart.TPanel.Update: Boolean;
|
||||
begin
|
||||
SeriesChanged := false;
|
||||
SeriesRepaint := false;
|
||||
for var series in FSeriesList do
|
||||
Result := false;
|
||||
for var layer in FLayers do
|
||||
begin
|
||||
seriesCount := series.Series.TotalCount;
|
||||
|
||||
if (ViewStartIndex <= MasterTotalCount - seriesCount) then
|
||||
SeriesRepaint := true;
|
||||
|
||||
series.Update;
|
||||
|
||||
if (seriesCount <> series.Series.TotalCount) then
|
||||
SeriesChanged := true;
|
||||
if layer.Update then
|
||||
if (Owner.ViewStartIndex <= Owner.FXAxisSeries.TotalCount - layer.TotalCount) then
|
||||
Result := true;
|
||||
end;
|
||||
end;
|
||||
|
||||
@@ -846,7 +835,7 @@ var
|
||||
textSize: TRectF;
|
||||
begin
|
||||
// Do not draw if no data or view is invalid
|
||||
if (not Assigned(Series)) or (Series.Count = 0) then
|
||||
if GetCount = 0 then
|
||||
exit;
|
||||
|
||||
// 1. Snap X to the candle's center
|
||||
|
||||
+63
-19
@@ -73,15 +73,38 @@ type
|
||||
end;
|
||||
|
||||
TLazy<T> = record
|
||||
type
|
||||
ILazy = interface
|
||||
{$REGION 'property access'}
|
||||
function GetChanged: TState;
|
||||
{$ENDREGION}
|
||||
function Update(var Value: T): Boolean;
|
||||
property Changed: TState read GetChanged;
|
||||
end;
|
||||
|
||||
{$REGION 'private'}
|
||||
strict private
|
||||
class var
|
||||
FNull: ILazy;
|
||||
class constructor CreateClass;
|
||||
|
||||
private
|
||||
FMutable: TMutable<T>;
|
||||
FChanged: TFlag;
|
||||
FChangeState: TSignal.TSubscription;
|
||||
function GetValue: T; inline;
|
||||
FLazy: ILazy;
|
||||
function GetChanged: TState; inline;
|
||||
{$ENDREGION}
|
||||
public
|
||||
constructor Create(const AMutable: TMutable<T>);
|
||||
function Update: Boolean;
|
||||
property Value: T read GetValue;
|
||||
constructor Create(const ALazy: ILazy);
|
||||
class operator Initialize(out Dest: TLazy<T>);
|
||||
class operator Implicit(const A: ILazy): TLazy<T>; overload;
|
||||
class operator Implicit(const A: TLazy<T>): ILazy; overload;
|
||||
|
||||
class property Null: ILazy read FNull;
|
||||
|
||||
class function Constant(const Value: T): TLazy<T>; overload; static;
|
||||
|
||||
function Update(var Value: T): Boolean; inline;
|
||||
|
||||
property Changed: TState read GetChanged;
|
||||
end;
|
||||
|
||||
implementation
|
||||
@@ -99,12 +122,12 @@ end;
|
||||
|
||||
class constructor TMutable<T>.CreateClass;
|
||||
begin
|
||||
FNull := TMycNullMutable<T>.Create(Default(T));
|
||||
FNull := TMycConstMutable<T>.Create(Default(T));
|
||||
end;
|
||||
|
||||
class function TMutable<T>.Constant(const Value: T): TMutable<T>;
|
||||
begin
|
||||
Result := TMycNullMutable<T>.Create(Value);
|
||||
Result := TMycConstMutable<T>.Create(Value);
|
||||
end;
|
||||
|
||||
class function TMutable<T>.Construct(const Changing: TSignal; const Proc: TFunc<T>): TMutable<T>;
|
||||
@@ -195,23 +218,44 @@ end;
|
||||
|
||||
{ TLazy<T> }
|
||||
|
||||
constructor TLazy<T>.Create(const AMutable: TMutable<T>);
|
||||
constructor TLazy<T>.Create(const ALazy: ILazy);
|
||||
begin
|
||||
FMutable := AMutable;
|
||||
FChanged := TFlag.CreateFlag;
|
||||
FChangeState := AMutable.Changed.Subscribe(FChanged);
|
||||
FLazy := ALazy;
|
||||
end;
|
||||
|
||||
function TLazy<T>.GetValue: T;
|
||||
class constructor TLazy<T>.CreateClass;
|
||||
begin
|
||||
Result := FMutable.Value;
|
||||
FNull := TMycConstLazy<T>.Create(Default(T));
|
||||
end;
|
||||
|
||||
function TLazy<T>.Update: Boolean;
|
||||
class function TLazy<T>.Constant(const Value: T): TLazy<T>;
|
||||
begin
|
||||
Result := FChanged.State.IsSet;
|
||||
if Result then
|
||||
FChanged.Reset;
|
||||
Result := TMycConstLazy<T>.Create(Value);
|
||||
end;
|
||||
|
||||
function TLazy<T>.GetChanged: TState;
|
||||
begin
|
||||
Result := FLazy.Changed;
|
||||
end;
|
||||
|
||||
function TLazy<T>.Update(var Value: T): Boolean;
|
||||
begin
|
||||
Result := FLazy.Update(Value);
|
||||
end;
|
||||
|
||||
class operator TLazy<T>.Implicit(const A: TLazy<T>): ILazy;
|
||||
begin
|
||||
Result := A.FLazy;
|
||||
end;
|
||||
|
||||
class operator TLazy<T>.Implicit(const A: ILazy): TLazy<T>;
|
||||
begin
|
||||
Result.FLazy := A;
|
||||
end;
|
||||
|
||||
class operator TLazy<T>.Initialize(out Dest: TLazy<T>);
|
||||
begin
|
||||
Dest.FLazy := FNull;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
@@ -157,7 +157,7 @@ type
|
||||
end;
|
||||
|
||||
// Endpoint that collects data into a series.
|
||||
TMycDataEndpoint<T> = class(TInterfacedObject, TMutable<TSeries<T>>.IMutable)
|
||||
TMycDataEndpoint<T> = class(TInterfacedObject, TLazy<TSeries<T>>.ILazy)
|
||||
type
|
||||
PItem = ^TItem;
|
||||
TItem = record
|
||||
@@ -169,16 +169,16 @@ type
|
||||
FTag: TDataProvider<T>.TTag;
|
||||
FDataProvider: TDataProvider<T>;
|
||||
FLookback: Int64;
|
||||
FData: TSeries<T>;
|
||||
FChanged: TEvent;
|
||||
FChanged: TFlag;
|
||||
FLock: TLightweightMREW;
|
||||
FFirst: PItem;
|
||||
function GetChanged: TSignal;
|
||||
function GetValue: TSeries<T>;
|
||||
FCount: Integer;
|
||||
function GetChanged: TState;
|
||||
function ProcessData(const Value: T): TState;
|
||||
public
|
||||
constructor Create(const ADataProvider: TDataProvider<T>; ALookback: Int64);
|
||||
destructor Destroy; override;
|
||||
function Update(var Value: TSeries<T>): Boolean;
|
||||
end;
|
||||
|
||||
TTickAggregation = class(TMycConverter<TDataPoint<Double>, TDataPoint<TOhlcItem>>)
|
||||
@@ -475,55 +475,13 @@ begin
|
||||
inherited;
|
||||
end;
|
||||
|
||||
function TMycDataEndpoint<T>.GetChanged: TSignal;
|
||||
function TMycDataEndpoint<T>.GetChanged: TState;
|
||||
begin
|
||||
Result := FChanged.Signal;
|
||||
end;
|
||||
|
||||
function TMycDataEndpoint<T>.GetValue: TSeries<T>;
|
||||
begin
|
||||
FLock.BeginWrite;
|
||||
try
|
||||
var cnt := 0;
|
||||
var tmp: PItem := nil;
|
||||
var item: PItem;
|
||||
|
||||
while FFirst <> nil do
|
||||
begin
|
||||
item := FFirst;
|
||||
FFirst := item.Next;
|
||||
item.Next := tmp;
|
||||
tmp := item;
|
||||
inc(cnt);
|
||||
end;
|
||||
|
||||
if cnt > 0 then
|
||||
begin
|
||||
var Arr: TArray<T>;
|
||||
SetLength(Arr, cnt);
|
||||
|
||||
cnt := 0;
|
||||
while tmp <> nil do
|
||||
begin
|
||||
item := tmp;
|
||||
tmp := item.Next;
|
||||
Arr[cnt] := item.Value;
|
||||
inc(cnt);
|
||||
Dispose(item);
|
||||
end;
|
||||
|
||||
FData := FData.Add(Arr, 0, cnt, FLookback);
|
||||
end;
|
||||
|
||||
Result := FData;
|
||||
finally
|
||||
FLock.EndWrite;
|
||||
end;
|
||||
Result := FChanged.State
|
||||
end;
|
||||
|
||||
function TMycDataEndpoint<T>.ProcessData(const Value: T): TState;
|
||||
begin
|
||||
Result := TState.Null;
|
||||
FLock.BeginWrite;
|
||||
try
|
||||
var P: PItem;
|
||||
@@ -531,13 +489,43 @@ begin
|
||||
P.Next := FFirst;
|
||||
FFirst := P;
|
||||
P.Value := Value;
|
||||
|
||||
// Add new data point, respecting the lookback period.
|
||||
// FData := FData.Add(Value, FLookback);
|
||||
inc(FCount);
|
||||
FChanged.Notify;
|
||||
finally
|
||||
FLock.EndWrite;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TMycDataEndpoint<T>.Update(var Value: TSeries<T>): Boolean;
|
||||
begin
|
||||
FLock.BeginWrite;
|
||||
try
|
||||
Result := FChanged.Reset;
|
||||
if Result then
|
||||
begin
|
||||
if FCount > 0 then
|
||||
begin
|
||||
var item: PItem;
|
||||
var Arr: TArray<T>;
|
||||
|
||||
SetLength(Arr, FCount);
|
||||
while FFirst <> nil do
|
||||
begin
|
||||
item := FFirst;
|
||||
FFirst := item.Next;
|
||||
dec(FCount);
|
||||
Assert(FCount >= 0);
|
||||
Arr[FCount] := item.Value;
|
||||
Dispose(item);
|
||||
end;
|
||||
Assert(FCount = 0);
|
||||
|
||||
Value := Value.Add(Arr, 0, Length(Arr), FLookback);
|
||||
end;
|
||||
end;
|
||||
finally
|
||||
FLock.EndWrite;
|
||||
end;
|
||||
FChanged.Notify;
|
||||
end;
|
||||
|
||||
{ TTickAggregation }
|
||||
|
||||
@@ -104,7 +104,7 @@ type
|
||||
|
||||
// Factory for creating specific converter instances.
|
||||
TConverter = record
|
||||
class function CreateEndpoint<T>(const DataProvider: TDataProvider<T>; Lookback: Int64): TMutable<TSeries<T>>; static;
|
||||
class function CreateEndpoint<T>(const DataProvider: TDataProvider<T>; Lookback: Int64): TLazy<TSeries<T>>; static;
|
||||
|
||||
class function CreateCounter<T>: TConverter<T, Int64>; static;
|
||||
class function CreateTicker<T>: TConverter<TArray<T>, T>; static;
|
||||
@@ -277,7 +277,7 @@ begin
|
||||
);
|
||||
end;
|
||||
|
||||
class function TConverter.CreateEndpoint<T>(const DataProvider: TDataProvider<T>; Lookback: Int64): TMutable<TSeries<T>>;
|
||||
class function TConverter.CreateEndpoint<T>(const DataProvider: TDataProvider<T>; Lookback: Int64): TLazy<TSeries<T>>;
|
||||
begin
|
||||
Result := TMycDataEndpoint<T>.Create(DataProvider, Lookback);
|
||||
end;
|
||||
|
||||
Reference in New Issue
Block a user