TLazy + Data-Endpoints refactoring

This commit is contained in:
Michael Schimmel
2025-07-17 00:48:46 +02:00
parent abad66ae52
commit b3359a4d73
8 changed files with 271 additions and 250 deletions
+1 -1
View File
@@ -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
View File
@@ -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);
+32 -5
View File
@@ -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.
+56 -92
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+41 -53
View File
@@ -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 }
+2 -2
View File
@@ -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;