diff --git a/AuraTrader/AuraTrader.dproj b/AuraTrader/AuraTrader.dproj index de77ac4..f68fd0b 100644 --- a/AuraTrader/AuraTrader.dproj +++ b/AuraTrader/AuraTrader.dproj @@ -4,7 +4,7 @@ 20.3 FMX True - Release + Debug Win64 AuraTrader 3 diff --git a/AuraTrader/MainForm.pas b/AuraTrader/MainForm.pas index d75cc4b..95071f1 100644 --- a/AuraTrader/MainForm.pas +++ b/AuraTrader/MainForm.pas @@ -483,13 +483,18 @@ begin var Highest: Double := Double.MinValue; var ATR := Ohlc[0].Chain(TIndicators.CreateATR(50)).MakeParallel; - var ATRSeries := TConverter.CreateEndpoint(ATR.Sender, 5); - - var HullSeries := TConverter.CreateEndpoint(Hull.Sender, 5); - var SmaSeries := TConverter.CreateEndpoint(Sma.Sender, 5); // next stage + var ATREndPoint := TConverter.CreateEndpoint(ATR.Sender, 5); + var ATRSeries: TSeries; + + var HullEndPoint := TConverter.CreateEndpoint(Hull.Sender, 5); + var HullSeries: TSeries; + + var SmaEndPoint := TConverter.CreateEndpoint(Sma.Sender, 5); + var SmaSeries: TSeries; + 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('SL').Sender, TAlphaColors.Red, 2); panel.AddDoubleSeries(Signal.Field('Entry').Sender, TAlphaColors.Green, 1); + panel.AddDoubleSeries(Signal.Field('SL').Sender, TAlphaColors.Red, 2); var pnlChart := TMycChart.Create(Self); AlignControl(pnlChart); diff --git a/Src/Myc.Core.Mutable.pas b/Src/Myc.Core.Mutable.pas index a3ea304..c89d631 100644 --- a/Src/Myc.Core.Mutable.pas +++ b/Src/Myc.Core.Mutable.pas @@ -10,7 +10,7 @@ uses Myc.Mutable; type - TMycNullMutable = class(TInterfacedObject, TMutable.IMutable) + TMycConstMutable = class(TInterfacedObject, TMutable.IMutable) private FValue: T; function GetChanged: TSignal; @@ -64,25 +64,34 @@ type procedure SetValue(const Value: T); end; + TMycConstLazy = class(TInterfacedObject, TLazy.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.Create(AValue: T); +constructor TMycConstMutable.Create(AValue: T); begin inherited Create; FValue := AValue; end; -{ TMycNullMutable } +{ TMycConstMutable } -function TMycNullMutable.GetChanged: TSignal; +function TMycConstMutable.GetChanged: TSignal; begin Result := TSignal.Null; end; -function TMycNullMutable.GetValue: T; +function TMycConstMutable.GetValue: T; begin Result := FValue; end; @@ -179,4 +188,22 @@ begin end; end; +constructor TMycConstLazy.Create(AValue: T); +begin + inherited Create; + FValue := AValue; +end; + +{ TMycConstLazy } + +function TMycConstLazy.GetChanged: TState; +begin + Result := TState.Null; +end; + +function TMycConstLazy.Update(var Value: T): Boolean; +begin + Result := false; +end; + end. diff --git a/Src/Myc.Fmx.Chart.Series.pas b/Src/Myc.Fmx.Chart.Series.pas index 798e690..f2991b9 100644 --- a/Src/Myc.Fmx.Chart.Series.pas +++ b/Src/Myc.Fmx.Chart.Series.pas @@ -15,32 +15,17 @@ uses Myc.Fmx.Chart; type - TChartSeriesProcessor = class(TMycChart.TSeries) - strict private - FDataSeries: TSeries; - private - FData: TSeries; - FDataProvider: TDataProvider; - FReceiver: TMutable>; - protected - function GetCount: Int64; override; - function GetTotalCount: Int64; override; - procedure Update; - - public - constructor Create(const ADataProvider: TDataProvider; const ALookback: TMutable); - destructor Destroy; override; - property Data: TSeries read FData; - end; - { TChartCustomLayer } TChartCustomLayer = class abstract(TMycChart.TDataLayer) private - FSeries: TChartSeriesProcessor; + FDataProvider: TDataProvider; + FReceiver: TLazy>; + FData: TSeries; 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); - destructor Destroy; override; + property Data: TSeries read FData; end; { TChartOhlcLayer } @@ -98,14 +83,17 @@ type { TChartXAxisLayer } TChartXAxisLayer = class(TMycChart.TXAxisLayer) private - FSeries: TChartSeriesProcessor; + FDataProvider: TDataProvider; + FReceiver: TLazy>; + FData: TSeries; 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); - destructor Destroy; override; + property Data: TSeries read FData; end; { TChartXAxisTimestampLayer } @@ -126,37 +114,6 @@ uses System.Math, FMX.Types; -{ TChartSeriesProcessor } - -constructor TChartSeriesProcessor.Create(const ADataProvider: TDataProvider; const ALookback: TMutable); -begin - inherited Create; - FDataProvider := ADataProvider; - FData := FDataSeries; - - FReceiver := TConverter.CreateEndpoint(FDataProvider, ALookback.Value); -end; - -destructor TChartSeriesProcessor.Destroy; -begin - inherited; -end; - -function TChartSeriesProcessor.GetCount: Int64; -begin - Result := FData.Count; -end; - -function TChartSeriesProcessor.GetTotalCount: Int64; -begin - Result := FData.TotalCount; -end; - -procedure TChartSeriesProcessor.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.Create(AParent: TMycChart.TPanel; const ADataProvider: TDataProvider); begin inherited Create(AParent); - FSeries := TChartSeriesProcessor.Create(ADataProvider, AParent.Owner.Lookback.AsMutable); + FDataProvider := ADataProvider; + + FReceiver := TConverter.CreateEndpoint(FDataProvider, Owner.Lookback.Value); end; -destructor TChartCustomLayer.Destroy; +function TChartCustomLayer.GetCount: Int64; begin - FSeries.Free; - inherited; + Result := FData.Count; end; -function TChartCustomLayer.GetSeries: TMycChart.TSeries; +function TChartCustomLayer.GetTotalCount: Int64; begin - Result := FSeries; + Result := FData.TotalCount; end; -procedure TChartCustomLayer.Update; +function TChartCustomLayer.Update: Boolean; begin - FSeries.Update; + Result := FReceiver.Update(FData); end; { TChartXAxisLayer } @@ -333,30 +295,32 @@ end; constructor TChartXAxisLayer.Create(AOwner: TMycChart; const ADataProvider: TDataProvider); begin inherited Create(AOwner); - FSeries := TChartSeriesProcessor.Create(ADataProvider, AOwner.Lookback.AsMutable); -end; - -destructor TChartXAxisLayer.Destroy; -begin - FSeries.Free; - inherited; + FDataProvider := ADataProvider; + FReceiver := TConverter.CreateEndpoint(FDataProvider, Owner.Lookback.Value); end; function TChartXAxisLayer.GetCaption(Idx: Int64): String; begin - Result := IntToStr(Idx); + Result := IntToStr(Owner.ViewStartIndex + Idx); end; -function TChartXAxisLayer.GetSeries: TMycChart.TSeries; +function TChartXAxisLayer.GetCount: Int64; begin - Result := FSeries; + Result := FData.Count; end; -procedure TChartXAxisLayer.Update; +function TChartXAxisLayer.GetTotalCount: Int64; begin - FSeries.Update; + Result := FData.TotalCount; end; +function TChartXAxisLayer.Update: Boolean; +begin + Result := FReceiver.Update(FData); +end; + +{ TChartXAxisTimestampLayer } + constructor TChartXAxisTimestampLayer.Create(AOwner: TMycChart; ATimeframe: TTimeframe; const ADataProvider: TDataProvider); 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 diff --git a/Src/Myc.Fmx.Chart.pas b/Src/Myc.Fmx.Chart.pas index b0a27bd..7c296d0 100644 --- a/Src/Myc.Fmx.Chart.pas +++ b/Src/Myc.Fmx.Chart.pas @@ -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; + FLayers: TObjectList; 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.Create(true); + FLayers := TObjectList.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.Constant(AUpColor), TMutable.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; yTransform: TFunc; - 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 diff --git a/Src/Myc.Mutable.pas b/Src/Myc.Mutable.pas index f11fff1..8663aaf 100644 --- a/Src/Myc.Mutable.pas +++ b/Src/Myc.Mutable.pas @@ -73,15 +73,38 @@ type end; TLazy = 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; - FChanged: TFlag; - FChangeState: TSignal.TSubscription; - function GetValue: T; inline; + FLazy: ILazy; + function GetChanged: TState; inline; + {$ENDREGION} public - constructor Create(const AMutable: TMutable); - function Update: Boolean; - property Value: T read GetValue; + constructor Create(const ALazy: ILazy); + class operator Initialize(out Dest: TLazy); + class operator Implicit(const A: ILazy): TLazy; overload; + class operator Implicit(const A: TLazy): ILazy; overload; + + class property Null: ILazy read FNull; + + class function Constant(const Value: T): TLazy; overload; static; + + function Update(var Value: T): Boolean; inline; + + property Changed: TState read GetChanged; end; implementation @@ -99,12 +122,12 @@ end; class constructor TMutable.CreateClass; begin - FNull := TMycNullMutable.Create(Default(T)); + FNull := TMycConstMutable.Create(Default(T)); end; class function TMutable.Constant(const Value: T): TMutable; begin - Result := TMycNullMutable.Create(Value); + Result := TMycConstMutable.Create(Value); end; class function TMutable.Construct(const Changing: TSignal; const Proc: TFunc): TMutable; @@ -195,23 +218,44 @@ end; { TLazy } -constructor TLazy.Create(const AMutable: TMutable); +constructor TLazy.Create(const ALazy: ILazy); begin - FMutable := AMutable; - FChanged := TFlag.CreateFlag; - FChangeState := AMutable.Changed.Subscribe(FChanged); + FLazy := ALazy; end; -function TLazy.GetValue: T; +class constructor TLazy.CreateClass; begin - Result := FMutable.Value; + FNull := TMycConstLazy.Create(Default(T)); end; -function TLazy.Update: Boolean; +class function TLazy.Constant(const Value: T): TLazy; begin - Result := FChanged.State.IsSet; - if Result then - FChanged.Reset; + Result := TMycConstLazy.Create(Value); +end; + +function TLazy.GetChanged: TState; +begin + Result := FLazy.Changed; +end; + +function TLazy.Update(var Value: T): Boolean; +begin + Result := FLazy.Update(Value); +end; + +class operator TLazy.Implicit(const A: TLazy): ILazy; +begin + Result := A.FLazy; +end; + +class operator TLazy.Implicit(const A: ILazy): TLazy; +begin + Result.FLazy := A; +end; + +class operator TLazy.Initialize(out Dest: TLazy); +begin + Dest.FLazy := FNull; end; end. diff --git a/Src/Myc.Trade.DataPoint.Impl.pas b/Src/Myc.Trade.DataPoint.Impl.pas index d79f58d..4aca7c8 100644 --- a/Src/Myc.Trade.DataPoint.Impl.pas +++ b/Src/Myc.Trade.DataPoint.Impl.pas @@ -157,7 +157,7 @@ type end; // Endpoint that collects data into a series. - TMycDataEndpoint = class(TInterfacedObject, TMutable>.IMutable) + TMycDataEndpoint = class(TInterfacedObject, TLazy>.ILazy) type PItem = ^TItem; TItem = record @@ -169,16 +169,16 @@ type FTag: TDataProvider.TTag; FDataProvider: TDataProvider; FLookback: Int64; - FData: TSeries; - FChanged: TEvent; + FChanged: TFlag; FLock: TLightweightMREW; FFirst: PItem; - function GetChanged: TSignal; - function GetValue: TSeries; + FCount: Integer; + function GetChanged: TState; function ProcessData(const Value: T): TState; public constructor Create(const ADataProvider: TDataProvider; ALookback: Int64); destructor Destroy; override; + function Update(var Value: TSeries): Boolean; end; TTickAggregation = class(TMycConverter, TDataPoint>) @@ -475,55 +475,13 @@ begin inherited; end; -function TMycDataEndpoint.GetChanged: TSignal; +function TMycDataEndpoint.GetChanged: TState; begin - Result := FChanged.Signal; -end; - -function TMycDataEndpoint.GetValue: TSeries; -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; - 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.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.Update(var Value: TSeries): Boolean; +begin + FLock.BeginWrite; + try + Result := FChanged.Reset; + if Result then + begin + if FCount > 0 then + begin + var item: PItem; + var Arr: TArray; + + 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 } diff --git a/Src/Myc.Trade.DataPoint.pas b/Src/Myc.Trade.DataPoint.pas index 7342343..d4ce1ce 100644 --- a/Src/Myc.Trade.DataPoint.pas +++ b/Src/Myc.Trade.DataPoint.pas @@ -104,7 +104,7 @@ type // Factory for creating specific converter instances. TConverter = record - class function CreateEndpoint(const DataProvider: TDataProvider; Lookback: Int64): TMutable>; static; + class function CreateEndpoint(const DataProvider: TDataProvider; Lookback: Int64): TLazy>; static; class function CreateCounter: TConverter; static; class function CreateTicker: TConverter, T>; static; @@ -277,7 +277,7 @@ begin ); end; -class function TConverter.CreateEndpoint(const DataProvider: TDataProvider; Lookback: Int64): TMutable>; +class function TConverter.CreateEndpoint(const DataProvider: TDataProvider; Lookback: Int64): TLazy>; begin Result := TMycDataEndpoint.Create(DataProvider, Lookback); end;